aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/usb
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/usb')
-rw-r--r--sys/dev/usb/controller/atmegadci.c2091
-rw-r--r--sys/dev/usb/controller/atmegadci.h284
-rw-r--r--sys/dev/usb/controller/avr32dci.c2053
-rw-r--r--sys/dev/usb/controller/avr32dci.h254
-rw-r--r--sys/dev/usb/controller/dwc3/aw_dwc3.c142
-rw-r--r--sys/dev/usb/controller/dwc3/dwc3.c619
-rw-r--r--sys/dev/usb/controller/dwc3/dwc3.h137
-rw-r--r--sys/dev/usb/controller/dwc3/rk_dwc3.c199
-rw-r--r--sys/dev/usb/controller/dwc_otg.c4970
-rw-r--r--sys/dev/usb/controller/dwc_otg.h226
-rw-r--r--sys/dev/usb/controller/dwc_otg_acpi.c182
-rw-r--r--sys/dev/usb/controller/dwc_otg_fdt.c214
-rw-r--r--sys/dev/usb/controller/dwc_otg_fdt.h38
-rw-r--r--sys/dev/usb/controller/dwc_otg_hisi.c91
-rw-r--r--sys/dev/usb/controller/dwc_otgreg.h803
-rw-r--r--sys/dev/usb/controller/ehci.c3862
-rw-r--r--sys/dev/usb/controller/ehci.h453
-rw-r--r--sys/dev/usb/controller/ehci_fsl.c418
-rw-r--r--sys/dev/usb/controller/ehci_imx.c506
-rw-r--r--sys/dev/usb/controller/ehci_msm.c221
-rw-r--r--sys/dev/usb/controller/ehci_mv.c384
-rw-r--r--sys/dev/usb/controller/ehci_pci.c610
-rw-r--r--sys/dev/usb/controller/ehcireg.h194
-rw-r--r--sys/dev/usb/controller/generic_ehci.c189
-rw-r--r--sys/dev/usb/controller/generic_ehci.h40
-rw-r--r--sys/dev/usb/controller/generic_ehci_acpi.c81
-rw-r--r--sys/dev/usb/controller/generic_ehci_fdt.c238
-rw-r--r--sys/dev/usb/controller/generic_ohci.c330
-rw-r--r--sys/dev/usb/controller/generic_usb_if.m58
-rw-r--r--sys/dev/usb/controller/generic_xhci.c198
-rw-r--r--sys/dev/usb/controller/generic_xhci.h43
-rw-r--r--sys/dev/usb/controller/generic_xhci_acpi.c81
-rw-r--r--sys/dev/usb/controller/generic_xhci_fdt.c135
-rw-r--r--sys/dev/usb/controller/musb_otg.c4188
-rw-r--r--sys/dev/usb/controller/musb_otg.h443
-rw-r--r--sys/dev/usb/controller/musb_otg_allwinner.c617
-rw-r--r--sys/dev/usb/controller/ohci.c2686
-rw-r--r--sys/dev/usb/controller/ohci.h264
-rw-r--r--sys/dev/usb/controller/ohci_pci.c380
-rw-r--r--sys/dev/usb/controller/ohcireg.h125
-rw-r--r--sys/dev/usb/controller/uhci.c3159
-rw-r--r--sys/dev/usb/controller/uhci.h250
-rw-r--r--sys/dev/usb/controller/uhci_pci.c481
-rw-r--r--sys/dev/usb/controller/uhcireg.h96
-rw-r--r--sys/dev/usb/controller/usb_controller.c1035
-rw-r--r--sys/dev/usb/controller/usb_nop_xceiv.c206
-rw-r--r--sys/dev/usb/controller/uss820dci.c2380
-rw-r--r--sys/dev/usb/controller/uss820dci.h361
-rw-r--r--sys/dev/usb/controller/xhci.c4410
-rw-r--r--sys/dev/usb/controller/xhci.h592
-rw-r--r--sys/dev/usb/controller/xhci_pci.c558
-rw-r--r--sys/dev/usb/controller/xhcireg.h229
-rw-r--r--sys/dev/usb/controller/xlnx_dwc3.c148
-rw-r--r--sys/dev/usb/gadget/g_audio.c608
-rw-r--r--sys/dev/usb/gadget/g_audio.h41
-rw-r--r--sys/dev/usb/gadget/g_keyboard.c409
-rw-r--r--sys/dev/usb/gadget/g_keyboard.h36
-rw-r--r--sys/dev/usb/gadget/g_modem.c537
-rw-r--r--sys/dev/usb/gadget/g_modem.h40
-rw-r--r--sys/dev/usb/gadget/g_mouse.c454
-rw-r--r--sys/dev/usb/gadget/g_mouse.h37
-rw-r--r--sys/dev/usb/input/atp.c2632
-rw-r--r--sys/dev/usb/input/uep.c537
-rw-r--r--sys/dev/usb/input/uhid.c944
-rw-r--r--sys/dev/usb/input/uhid_snes.c643
-rw-r--r--sys/dev/usb/input/ukbd.c2257
-rw-r--r--sys/dev/usb/input/ums.c1232
-rw-r--r--sys/dev/usb/input/usb_rdesc.h37
-rw-r--r--sys/dev/usb/input/usbhid.c899
-rw-r--r--sys/dev/usb/input/wmt.c1008
-rw-r--r--sys/dev/usb/input/wsp.c1662
-rw-r--r--sys/dev/usb/misc/cp2112.c1433
-rw-r--r--sys/dev/usb/misc/i2ctinyusb.c301
-rw-r--r--sys/dev/usb/misc/udbp.c851
-rw-r--r--sys/dev/usb/misc/udbp.h77
-rw-r--r--sys/dev/usb/misc/ugold.c402
-rw-r--r--sys/dev/usb/misc/uled.c291
-rw-r--r--sys/dev/usb/net/if_aue.c1066
-rw-r--r--sys/dev/usb/net/if_auereg.h220
-rw-r--r--sys/dev/usb/net/if_axe.c1499
-rw-r--r--sys/dev/usb/net/if_axereg.h363
-rw-r--r--sys/dev/usb/net/if_axge.c1100
-rw-r--r--sys/dev/usb/net/if_axgereg.h216
-rw-r--r--sys/dev/usb/net/if_cdce.c1745
-rw-r--r--sys/dev/usb/net/if_cdceem.c873
-rw-r--r--sys/dev/usb/net/if_cdcereg.h123
-rw-r--r--sys/dev/usb/net/if_cue.c654
-rw-r--r--sys/dev/usb/net/if_cuereg.h132
-rw-r--r--sys/dev/usb/net/if_ipheth.c541
-rw-r--r--sys/dev/usb/net/if_iphethvar.h85
-rw-r--r--sys/dev/usb/net/if_kue.c702
-rw-r--r--sys/dev/usb/net/if_kuefw.h685
-rw-r--r--sys/dev/usb/net/if_kuereg.h141
-rw-r--r--sys/dev/usb/net/if_mos.c1012
-rw-r--r--sys/dev/usb/net/if_mosreg.h176
-rw-r--r--sys/dev/usb/net/if_muge.c2268
-rw-r--r--sys/dev/usb/net/if_mugereg.h376
-rw-r--r--sys/dev/usb/net/if_rue.c923
-rw-r--r--sys/dev/usb/net/if_ruereg.h178
-rw-r--r--sys/dev/usb/net/if_smsc.c1855
-rw-r--r--sys/dev/usb/net/if_smscreg.h277
-rw-r--r--sys/dev/usb/net/if_udav.c883
-rw-r--r--sys/dev/usb/net/if_udavreg.h168
-rw-r--r--sys/dev/usb/net/if_umb.c2932
-rw-r--r--sys/dev/usb/net/if_umbreg.h443
-rw-r--r--sys/dev/usb/net/if_ure.c2244
-rw-r--r--sys/dev/usb/net/if_urereg.h620
-rw-r--r--sys/dev/usb/net/if_urndis.c1057
-rw-r--r--sys/dev/usb/net/if_urndisreg.h56
-rw-r--r--sys/dev/usb/net/if_usie.c1608
-rw-r--r--sys/dev/usb/net/if_usievar.h257
-rw-r--r--sys/dev/usb/net/mbim.h727
-rw-r--r--sys/dev/usb/net/ruephy.c217
-rw-r--r--sys/dev/usb/net/ruephyreg.h38
-rw-r--r--sys/dev/usb/net/uhso.c1928
-rw-r--r--sys/dev/usb/net/usb_ethernet.c666
-rw-r--r--sys/dev/usb/net/usb_ethernet.h123
-rw-r--r--sys/dev/usb/quirk/usb_quirk.c1064
-rw-r--r--sys/dev/usb/quirk/usb_quirk.h126
-rw-r--r--sys/dev/usb/serial/u3g.c1338
-rw-r--r--sys/dev/usb/serial/uark.c463
-rw-r--r--sys/dev/usb/serial/ubsa.c692
-rw-r--r--sys/dev/usb/serial/ubser.c552
-rw-r--r--sys/dev/usb/serial/uchcom.c951
-rw-r--r--sys/dev/usb/serial/ucycom.c609
-rw-r--r--sys/dev/usb/serial/ufoma.c1259
-rw-r--r--sys/dev/usb/serial/uftdi.c2030
-rw-r--r--sys/dev/usb/serial/uftdi_reg.h313
-rw-r--r--sys/dev/usb/serial/ugensa.c404
-rw-r--r--sys/dev/usb/serial/uipaq.c1371
-rw-r--r--sys/dev/usb/serial/ulpt.c765
-rw-r--r--sys/dev/usb/serial/umcs.c1101
-rw-r--r--sys/dev/usb/serial/umcs.h645
-rw-r--r--sys/dev/usb/serial/umct.c676
-rw-r--r--sys/dev/usb/serial/umodem.c1042
-rw-r--r--sys/dev/usb/serial/umoscom.c730
-rw-r--r--sys/dev/usb/serial/uplcom.c1147
-rw-r--r--sys/dev/usb/serial/usb_serial.c1846
-rw-r--r--sys/dev/usb/serial/usb_serial.h234
-rw-r--r--sys/dev/usb/serial/uslcom.c957
-rw-r--r--sys/dev/usb/serial/uvisor.c675
-rw-r--r--sys/dev/usb/serial/uvscom.c766
-rw-r--r--sys/dev/usb/storage/cfumass.c992
-rw-r--r--sys/dev/usb/storage/rio500_usb.h47
-rw-r--r--sys/dev/usb/storage/umass.c2990
-rw-r--r--sys/dev/usb/storage/urio.c493
-rw-r--r--sys/dev/usb/storage/ustorage_fs.c1942
-rw-r--r--sys/dev/usb/template/usb_template.c1473
-rw-r--r--sys/dev/usb/template/usb_template.h129
-rw-r--r--sys/dev/usb/template/usb_template_audio.c479
-rw-r--r--sys/dev/usb/template/usb_template_cdce.c352
-rw-r--r--sys/dev/usb/template/usb_template_cdceem.c262
-rw-r--r--sys/dev/usb/template/usb_template_kbd.c292
-rw-r--r--sys/dev/usb/template/usb_template_midi.c313
-rw-r--r--sys/dev/usb/template/usb_template_modem.c327
-rw-r--r--sys/dev/usb/template/usb_template_mouse.c290
-rw-r--r--sys/dev/usb/template/usb_template_msc.c261
-rw-r--r--sys/dev/usb/template/usb_template_mtp.c328
-rw-r--r--sys/dev/usb/template/usb_template_multi.c514
-rw-r--r--sys/dev/usb/template/usb_template_phone.c503
-rw-r--r--sys/dev/usb/template/usb_template_serialnet.c464
-rw-r--r--sys/dev/usb/ufm_ioctl.h43
-rw-r--r--sys/dev/usb/uftdiio.h98
-rw-r--r--sys/dev/usb/uled_ioctl.h41
-rw-r--r--sys/dev/usb/usb.h810
-rw-r--r--sys/dev/usb/usb_bus.h127
-rw-r--r--sys/dev/usb/usb_busdma.c1110
-rw-r--r--sys/dev/usb/usb_busdma.h163
-rw-r--r--sys/dev/usb/usb_cdc.h289
-rw-r--r--sys/dev/usb/usb_controller.h198
-rw-r--r--sys/dev/usb/usb_core.c82
-rw-r--r--sys/dev/usb/usb_core.h195
-rw-r--r--sys/dev/usb/usb_debug.c292
-rw-r--r--sys/dev/usb/usb_debug.h91
-rw-r--r--sys/dev/usb/usb_dev.c2470
-rw-r--r--sys/dev/usb/usb_dev.h161
-rw-r--r--sys/dev/usb/usb_device.c3113
-rw-r--r--sys/dev/usb/usb_device.h350
-rw-r--r--sys/dev/usb/usb_dynamic.c180
-rw-r--r--sys/dev/usb/usb_dynamic.h65
-rw-r--r--sys/dev/usb/usb_endian.h122
-rw-r--r--sys/dev/usb/usb_error.c95
-rw-r--r--sys/dev/usb/usb_fdt_support.c165
-rw-r--r--sys/dev/usb/usb_fdt_support.h46
-rw-r--r--sys/dev/usb/usb_freebsd.h107
-rw-r--r--sys/dev/usb/usb_freebsd_loader.h102
-rw-r--r--sys/dev/usb/usb_generic.c2570
-rw-r--r--sys/dev/usb/usb_generic.h35
-rw-r--r--sys/dev/usb/usb_handle_request.c811
-rw-r--r--sys/dev/usb/usb_hid.c153
-rw-r--r--sys/dev/usb/usb_hub.c2993
-rw-r--r--sys/dev/usb/usb_hub.h81
-rw-r--r--sys/dev/usb/usb_hub_acpi.c609
-rw-r--r--sys/dev/usb/usb_hub_private.h85
-rw-r--r--sys/dev/usb/usb_if.m65
-rw-r--r--sys/dev/usb/usb_ioctl.h393
-rw-r--r--sys/dev/usb/usb_lookup.c152
-rw-r--r--sys/dev/usb/usb_mbuf.c96
-rw-r--r--sys/dev/usb/usb_mbuf.h91
-rw-r--r--sys/dev/usb/usb_msctest.c1150
-rw-r--r--sys/dev/usb/usb_msctest.h61
-rw-r--r--sys/dev/usb/usb_parse.c317
-rw-r--r--sys/dev/usb/usb_pci.h42
-rw-r--r--sys/dev/usb/usb_pf.c531
-rw-r--r--sys/dev/usb/usb_pf.h120
-rw-r--r--sys/dev/usb/usb_process.c536
-rw-r--r--sys/dev/usb/usb_process.h89
-rw-r--r--sys/dev/usb/usb_request.c2345
-rw-r--r--sys/dev/usb/usb_request.h101
-rw-r--r--sys/dev/usb/usb_transfer.c3750
-rw-r--r--sys/dev/usb/usb_transfer.h151
-rw-r--r--sys/dev/usb/usb_util.c249
-rw-r--r--sys/dev/usb/usb_util.h39
-rw-r--r--sys/dev/usb/usbdevs5156
-rw-r--r--sys/dev/usb/usbdi.h718
-rw-r--r--sys/dev/usb/usbdi_util.h91
-rw-r--r--sys/dev/usb/usbhid.h97
-rw-r--r--sys/dev/usb/video/udl.c1153
-rw-r--r--sys/dev/usb/video/udl.h319
-rw-r--r--sys/dev/usb/wlan/if_mtw.c4685
-rw-r--r--sys/dev/usb/wlan/if_mtwreg.h1439
-rw-r--r--sys/dev/usb/wlan/if_mtwvar.h387
-rw-r--r--sys/dev/usb/wlan/if_rsu.c3762
-rw-r--r--sys/dev/usb/wlan/if_rsureg.h903
-rw-r--r--sys/dev/usb/wlan/if_rum.c3292
-rw-r--r--sys/dev/usb/wlan/if_rumfw.h212
-rw-r--r--sys/dev/usb/wlan/if_rumreg.h305
-rw-r--r--sys/dev/usb/wlan/if_rumvar.h185
-rw-r--r--sys/dev/usb/wlan/if_run.c6441
-rw-r--r--sys/dev/usb/wlan/if_runreg.h1658
-rw-r--r--sys/dev/usb/wlan/if_runvar.h266
-rw-r--r--sys/dev/usb/wlan/if_uath.c2886
-rw-r--r--sys/dev/usb/wlan/if_uathreg.h600
-rw-r--r--sys/dev/usb/wlan/if_uathvar.h245
-rw-r--r--sys/dev/usb/wlan/if_upgt.c2343
-rw-r--r--sys/dev/usb/wlan/if_upgtvar.h479
-rw-r--r--sys/dev/usb/wlan/if_ural.c2213
-rw-r--r--sys/dev/usb/wlan/if_uralreg.h209
-rw-r--r--sys/dev/usb/wlan/if_uralvar.h134
-rw-r--r--sys/dev/usb/wlan/if_urtw.c4430
-rw-r--r--sys/dev/usb/wlan/if_urtwreg.h432
-rw-r--r--sys/dev/usb/wlan/if_urtwvar.h185
-rw-r--r--sys/dev/usb/wlan/if_zyd.c2911
-rw-r--r--sys/dev/usb/wlan/if_zydfw.h1145
-rw-r--r--sys/dev/usb/wlan/if_zydreg.h1313
245 files changed, 204428 insertions, 0 deletions
diff --git a/sys/dev/usb/controller/atmegadci.c b/sys/dev/usb/controller/atmegadci.c
new file mode 100644
index 000000000000..8e9bc029dc10
--- /dev/null
+++ b/sys/dev/usb/controller/atmegadci.c
@@ -0,0 +1,2091 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2009 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the driver for the ATMEGA series USB OTG Controller. This
+ * driver currently only supports the DCI mode of the USB hardware.
+ */
+
+/*
+ * NOTE: When the chip detects BUS-reset it will also reset the
+ * endpoints, Function-address and more.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#define USB_DEBUG_VAR atmegadci_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#include <dev/usb/controller/atmegadci.h>
+
+#define ATMEGA_BUS2SC(bus) \
+ __containerof(bus, struct atmegadci_softc, sc_bus)
+
+#define ATMEGA_PC2SC(pc) \
+ ATMEGA_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus)
+
+#ifdef USB_DEBUG
+static int atmegadci_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, atmegadci, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB ATMEGA DCI");
+SYSCTL_INT(_hw_usb_atmegadci, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &atmegadci_debug, 0, "ATMEGA DCI debug level");
+#endif
+
+#define ATMEGA_INTR_ENDPT 1
+
+/* prototypes */
+
+static const struct usb_bus_methods atmegadci_bus_methods;
+static const struct usb_pipe_methods atmegadci_device_non_isoc_methods;
+static const struct usb_pipe_methods atmegadci_device_isoc_fs_methods;
+
+static atmegadci_cmd_t atmegadci_setup_rx;
+static atmegadci_cmd_t atmegadci_data_rx;
+static atmegadci_cmd_t atmegadci_data_tx;
+static atmegadci_cmd_t atmegadci_data_tx_sync;
+static void atmegadci_device_done(struct usb_xfer *, usb_error_t);
+static void atmegadci_do_poll(struct usb_bus *);
+static void atmegadci_standard_done(struct usb_xfer *);
+static void atmegadci_root_intr(struct atmegadci_softc *sc);
+
+/*
+ * Here is a list of what the chip supports:
+ */
+static const struct usb_hw_ep_profile
+ atmegadci_ep_profile[2] = {
+ [0] = {
+ .max_in_frame_size = 64,
+ .max_out_frame_size = 64,
+ .is_simplex = 1,
+ .support_control = 1,
+ },
+ [1] = {
+ .max_in_frame_size = 64,
+ .max_out_frame_size = 64,
+ .is_simplex = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_isochronous = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+};
+
+static void
+atmegadci_get_hw_ep_profile(struct usb_device *udev,
+ const struct usb_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+ if (ep_addr == 0)
+ *ppf = atmegadci_ep_profile;
+ else if (ep_addr < ATMEGA_EP_MAX)
+ *ppf = atmegadci_ep_profile + 1;
+ else
+ *ppf = NULL;
+}
+
+static void
+atmegadci_clocks_on(struct atmegadci_softc *sc)
+{
+ if (sc->sc_flags.clocks_off &&
+ sc->sc_flags.port_powered) {
+ DPRINTFN(5, "\n");
+
+ /* turn on clocks */
+ (sc->sc_clocks_on) (&sc->sc_bus);
+
+ ATMEGA_WRITE_1(sc, ATMEGA_USBCON,
+ ATMEGA_USBCON_USBE |
+ ATMEGA_USBCON_OTGPADE |
+ ATMEGA_USBCON_VBUSTE);
+
+ sc->sc_flags.clocks_off = 0;
+
+ /* enable transceiver ? */
+ }
+}
+
+static void
+atmegadci_clocks_off(struct atmegadci_softc *sc)
+{
+ if (!sc->sc_flags.clocks_off) {
+ DPRINTFN(5, "\n");
+
+ /* disable Transceiver ? */
+
+ ATMEGA_WRITE_1(sc, ATMEGA_USBCON,
+ ATMEGA_USBCON_USBE |
+ ATMEGA_USBCON_OTGPADE |
+ ATMEGA_USBCON_FRZCLK |
+ ATMEGA_USBCON_VBUSTE);
+
+ /* turn clocks off */
+ (sc->sc_clocks_off) (&sc->sc_bus);
+
+ sc->sc_flags.clocks_off = 1;
+ }
+}
+
+static void
+atmegadci_pull_up(struct atmegadci_softc *sc)
+{
+ /* pullup D+, if possible */
+
+ if (!sc->sc_flags.d_pulled_up &&
+ sc->sc_flags.port_powered) {
+ sc->sc_flags.d_pulled_up = 1;
+ ATMEGA_WRITE_1(sc, ATMEGA_UDCON, 0);
+ }
+}
+
+static void
+atmegadci_pull_down(struct atmegadci_softc *sc)
+{
+ /* pulldown D+, if possible */
+
+ if (sc->sc_flags.d_pulled_up) {
+ sc->sc_flags.d_pulled_up = 0;
+ ATMEGA_WRITE_1(sc, ATMEGA_UDCON, ATMEGA_UDCON_DETACH);
+ }
+}
+
+static void
+atmegadci_wakeup_peer(struct atmegadci_softc *sc)
+{
+ uint8_t temp;
+
+ if (!sc->sc_flags.status_suspend) {
+ return;
+ }
+
+ temp = ATMEGA_READ_1(sc, ATMEGA_UDCON);
+ ATMEGA_WRITE_1(sc, ATMEGA_UDCON, temp | ATMEGA_UDCON_RMWKUP);
+
+ /* wait 8 milliseconds */
+ /* Wait for reset to complete. */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+
+ /* hardware should have cleared RMWKUP bit */
+}
+
+static void
+atmegadci_set_address(struct atmegadci_softc *sc, uint8_t addr)
+{
+ DPRINTFN(5, "addr=%d\n", addr);
+
+ addr |= ATMEGA_UDADDR_ADDEN;
+
+ ATMEGA_WRITE_1(sc, ATMEGA_UDADDR, addr);
+}
+
+static uint8_t
+atmegadci_setup_rx(struct atmegadci_td *td)
+{
+ struct atmegadci_softc *sc;
+ struct usb_device_request req;
+ uint16_t count;
+ uint8_t temp;
+
+ /* get pointer to softc */
+ sc = ATMEGA_PC2SC(td->pc);
+
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no);
+
+ /* check endpoint status */
+ temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX);
+
+ DPRINTFN(5, "UEINTX=0x%02x\n", temp);
+
+ if (!(temp & ATMEGA_UEINTX_RXSTPI)) {
+ goto not_complete;
+ }
+ /* clear did stall */
+ td->did_stall = 0;
+ /* get the packet byte count */
+ count =
+ (ATMEGA_READ_1(sc, ATMEGA_UEBCHX) << 8) |
+ (ATMEGA_READ_1(sc, ATMEGA_UEBCLX));
+
+ /* mask away undefined bits */
+ count &= 0x7FF;
+
+ /* verify data length */
+ if (count != td->remainder) {
+ DPRINTFN(0, "Invalid SETUP packet "
+ "length, %d bytes\n", count);
+ goto not_complete;
+ }
+ if (count != sizeof(req)) {
+ DPRINTFN(0, "Unsupported SETUP packet "
+ "length, %d bytes\n", count);
+ goto not_complete;
+ }
+ /* receive data */
+ ATMEGA_READ_MULTI_1(sc, ATMEGA_UEDATX,
+ (void *)&req, sizeof(req));
+
+ /* copy data into real buffer */
+ usbd_copy_in(td->pc, 0, &req, sizeof(req));
+
+ td->offset = sizeof(req);
+ td->remainder = 0;
+
+ /* sneak peek the set address */
+ if ((req.bmRequestType == UT_WRITE_DEVICE) &&
+ (req.bRequest == UR_SET_ADDRESS)) {
+ sc->sc_dv_addr = req.wValue[0] & 0x7F;
+ /* must write address before ZLP */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDADDR, sc->sc_dv_addr);
+ } else {
+ sc->sc_dv_addr = 0xFF;
+ }
+
+ /* Clear SETUP packet interrupt and all other previous interrupts */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0);
+ return (0); /* complete */
+
+not_complete:
+ /* abort any ongoing transfer */
+ if (!td->did_stall) {
+ DPRINTFN(5, "stalling\n");
+ ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+ ATMEGA_UECONX_EPEN |
+ ATMEGA_UECONX_STALLRQ);
+ td->did_stall = 1;
+ }
+ if (temp & ATMEGA_UEINTX_RXSTPI) {
+ /* clear SETUP packet interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ~ATMEGA_UEINTX_RXSTPI);
+ }
+ /* we only want to know if there is a SETUP packet */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, ATMEGA_UEIENX_RXSTPE);
+ return (1); /* not complete */
+}
+
+static uint8_t
+atmegadci_data_rx(struct atmegadci_td *td)
+{
+ struct atmegadci_softc *sc;
+ struct usb_page_search buf_res;
+ uint16_t count;
+ uint8_t temp;
+ uint8_t to;
+ uint8_t got_short;
+
+ to = 3; /* don't loop forever! */
+ got_short = 0;
+
+ /* get pointer to softc */
+ sc = ATMEGA_PC2SC(td->pc);
+
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no);
+
+repeat:
+ /* check if any of the FIFO banks have data */
+ /* check endpoint status */
+ temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX);
+
+ DPRINTFN(5, "temp=0x%02x rem=%u\n", temp, td->remainder);
+
+ if (temp & ATMEGA_UEINTX_RXSTPI) {
+ if (td->remainder == 0) {
+ /*
+ * We are actually complete and have
+ * received the next SETUP
+ */
+ DPRINTFN(5, "faking complete\n");
+ return (0); /* complete */
+ }
+ /*
+ * USB Host Aborted the transfer.
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ /* check status */
+ if (!(temp & (ATMEGA_UEINTX_FIFOCON |
+ ATMEGA_UEINTX_RXOUTI))) {
+ /* no data */
+ goto not_complete;
+ }
+ /* get the packet byte count */
+ count =
+ (ATMEGA_READ_1(sc, ATMEGA_UEBCHX) << 8) |
+ (ATMEGA_READ_1(sc, ATMEGA_UEBCLX));
+
+ /* mask away undefined bits */
+ count &= 0x7FF;
+
+ /* verify the packet byte count */
+ if (count != td->max_packet_size) {
+ if (count < td->max_packet_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ }
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ while (count > 0) {
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* receive data */
+ ATMEGA_READ_MULTI_1(sc, ATMEGA_UEDATX,
+ buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* clear OUT packet interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ATMEGA_UEINTX_RXOUTI ^ 0xFF);
+
+ /* release FIFO bank */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, ATMEGA_UEINTX_FIFOCON ^ 0xFF);
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+not_complete:
+ /* we only want to know if there is a SETUP packet or OUT packet */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX,
+ ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_RXOUTE);
+ return (1); /* not complete */
+}
+
+static uint8_t
+atmegadci_data_tx(struct atmegadci_td *td)
+{
+ struct atmegadci_softc *sc;
+ struct usb_page_search buf_res;
+ uint16_t count;
+ uint8_t to;
+ uint8_t temp;
+
+ to = 3; /* don't loop forever! */
+
+ /* get pointer to softc */
+ sc = ATMEGA_PC2SC(td->pc);
+
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no);
+
+repeat:
+
+ /* check endpoint status */
+ temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX);
+
+ DPRINTFN(5, "temp=0x%02x rem=%u\n", temp, td->remainder);
+
+ if (temp & ATMEGA_UEINTX_RXSTPI) {
+ /*
+ * The current transfer was aborted
+ * by the USB Host
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+
+ temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X);
+ if (temp & 3) {
+ /* cannot write any data - a bank is busy */
+ goto not_complete;
+ }
+
+ count = td->max_packet_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ while (count > 0) {
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* transmit data */
+ ATMEGA_WRITE_MULTI_1(sc, ATMEGA_UEDATX,
+ buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* clear IN packet interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0xFF ^ ATMEGA_UEINTX_TXINI);
+
+ /* allocate FIFO bank */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEINTX, 0xFF ^ ATMEGA_UEINTX_FIFOCON);
+
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+not_complete:
+ /* we only want to know if there is a SETUP packet or free IN packet */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX,
+ ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_TXINE);
+ return (1); /* not complete */
+}
+
+static uint8_t
+atmegadci_data_tx_sync(struct atmegadci_td *td)
+{
+ struct atmegadci_softc *sc;
+ uint8_t temp;
+
+ /* get pointer to softc */
+ sc = ATMEGA_PC2SC(td->pc);
+
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, td->ep_no);
+
+ /* check endpoint status */
+ temp = ATMEGA_READ_1(sc, ATMEGA_UEINTX);
+
+ DPRINTFN(5, "temp=0x%02x\n", temp);
+
+ if (temp & ATMEGA_UEINTX_RXSTPI) {
+ DPRINTFN(5, "faking complete\n");
+ /* Race condition */
+ return (0); /* complete */
+ }
+ /*
+ * The control endpoint has only got one bank, so if that bank
+ * is free the packet has been transferred!
+ */
+ temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X);
+ if (temp & 3) {
+ /* cannot write any data - a bank is busy */
+ goto not_complete;
+ }
+ if (sc->sc_dv_addr != 0xFF) {
+ /* set new address */
+ atmegadci_set_address(sc, sc->sc_dv_addr);
+ }
+ return (0); /* complete */
+
+not_complete:
+ /* we only want to know if there is a SETUP packet or free IN packet */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX,
+ ATMEGA_UEIENX_RXSTPE | ATMEGA_UEIENX_TXINE);
+ return (1); /* not complete */
+}
+
+static uint8_t
+atmegadci_xfer_do_fifo(struct usb_xfer *xfer)
+{
+ struct atmegadci_td *td;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+ while (1) {
+ if ((td->func) (td)) {
+ /* operation in progress */
+ break;
+ }
+ if (((void *)td) == xfer->td_transfer_last) {
+ goto done;
+ }
+ if (td->error) {
+ goto done;
+ } else if (td->remainder > 0) {
+ /*
+ * We had a short transfer. If there is no alternate
+ * next, stop processing !
+ */
+ if (!td->alt_next) {
+ goto done;
+ }
+ }
+ /*
+ * Fetch the next transfer descriptor and transfer
+ * some flags to the next transfer descriptor
+ */
+ td = td->obj_next;
+ xfer->td_transfer_cache = td;
+ }
+ return (1); /* not complete */
+
+done:
+ /* compute all actual lengths */
+
+ atmegadci_standard_done(xfer);
+ return (0); /* complete */
+}
+
+static void
+atmegadci_interrupt_poll(struct atmegadci_softc *sc)
+{
+ struct usb_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (!atmegadci_xfer_do_fifo(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+static void
+atmegadci_vbus_interrupt(struct atmegadci_softc *sc, uint8_t is_on)
+{
+ DPRINTFN(5, "vbus = %u\n", is_on);
+
+ if (is_on) {
+ if (!sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 1;
+
+ /* complete root HUB interrupt endpoint */
+
+ atmegadci_root_intr(sc);
+ }
+ } else {
+ if (sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* complete root HUB interrupt endpoint */
+
+ atmegadci_root_intr(sc);
+ }
+ }
+}
+
+void
+atmegadci_interrupt(struct atmegadci_softc *sc)
+{
+ uint8_t status;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* read interrupt status */
+ status = ATMEGA_READ_1(sc, ATMEGA_UDINT);
+
+ /* clear all set interrupts */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDINT, (~status) & 0x7D);
+
+ DPRINTFN(14, "UDINT=0x%02x\n", status);
+
+ /* check for any bus state change interrupts */
+ if (status & ATMEGA_UDINT_EORSTI) {
+ DPRINTFN(5, "end of reset\n");
+
+ /* set correct state */
+ sc->sc_flags.status_bus_reset = 1;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* disable resume interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN,
+ ATMEGA_UDINT_SUSPE |
+ ATMEGA_UDINT_EORSTE);
+
+ /* complete root HUB interrupt endpoint */
+ atmegadci_root_intr(sc);
+ }
+ /*
+ * If resume and suspend is set at the same time we interpret
+ * that like RESUME. Resume is set when there is at least 3
+ * milliseconds of inactivity on the USB BUS.
+ */
+ if (status & ATMEGA_UDINT_WAKEUPI) {
+ DPRINTFN(5, "resume interrupt\n");
+
+ if (sc->sc_flags.status_suspend) {
+ /* update status bits */
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 1;
+
+ /* disable resume interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN,
+ ATMEGA_UDINT_SUSPE |
+ ATMEGA_UDINT_EORSTE);
+
+ /* complete root HUB interrupt endpoint */
+ atmegadci_root_intr(sc);
+ }
+ } else if (status & ATMEGA_UDINT_SUSPI) {
+ DPRINTFN(5, "suspend interrupt\n");
+
+ if (!sc->sc_flags.status_suspend) {
+ /* update status bits */
+ sc->sc_flags.status_suspend = 1;
+ sc->sc_flags.change_suspend = 1;
+
+ /* disable suspend interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN,
+ ATMEGA_UDINT_WAKEUPE |
+ ATMEGA_UDINT_EORSTE);
+
+ /* complete root HUB interrupt endpoint */
+ atmegadci_root_intr(sc);
+ }
+ }
+ /* check VBUS */
+ status = ATMEGA_READ_1(sc, ATMEGA_USBINT);
+
+ /* clear all set interrupts */
+ ATMEGA_WRITE_1(sc, ATMEGA_USBINT, (~status) & 0x03);
+
+ if (status & ATMEGA_USBINT_VBUSTI) {
+ uint8_t temp;
+
+ DPRINTFN(5, "USBINT=0x%02x\n", status);
+
+ temp = ATMEGA_READ_1(sc, ATMEGA_USBSTA);
+ atmegadci_vbus_interrupt(sc, temp & ATMEGA_USBSTA_VBUS);
+ }
+ /* check for any endpoint interrupts */
+ status = ATMEGA_READ_1(sc, ATMEGA_UEINT);
+ /* the hardware will clear the UEINT bits automatically */
+ if (status) {
+ DPRINTFN(5, "real endpoint interrupt UEINT=0x%02x\n", status);
+
+ atmegadci_interrupt_poll(sc);
+ }
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+atmegadci_setup_standard_chain_sub(struct atmegadci_std_temp *temp)
+{
+ struct atmegadci_td *td;
+
+ /* get current Transfer Descriptor */
+ td = temp->td_next;
+ temp->td = td;
+
+ /* prepare for next TD */
+ temp->td_next = td->obj_next;
+
+ /* fill out the Transfer Descriptor */
+ td->func = temp->func;
+ td->pc = temp->pc;
+ td->offset = temp->offset;
+ td->remainder = temp->len;
+ td->error = 0;
+ td->did_stall = temp->did_stall;
+ td->short_pkt = temp->short_pkt;
+ td->alt_next = temp->setup_alt_next;
+}
+
+static void
+atmegadci_setup_standard_chain(struct usb_xfer *xfer)
+{
+ struct atmegadci_std_temp temp;
+ struct atmegadci_td *td;
+ uint32_t x;
+ uint8_t need_sync;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpointno),
+ xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
+
+ temp.max_frame_size = xfer->max_frame_size;
+
+ td = xfer->td_start[0];
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ /* setup temp */
+
+ temp.pc = NULL;
+ temp.td = NULL;
+ temp.td_next = xfer->td_start[0];
+ temp.offset = 0;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok ||
+ xfer->flags_int.isochronous_xfr;
+ temp.did_stall = !xfer->flags_int.control_stall;
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ temp.func = &atmegadci_setup_rx;
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.short_pkt = temp.len ? 1 : 0;
+ /* check for last frame */
+ if (xfer->nframes == 1) {
+ /* no STATUS stage yet, SETUP is last */
+ if (xfer->flags_int.control_act)
+ temp.setup_alt_next = 0;
+ }
+
+ atmegadci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ if (x != xfer->nframes) {
+ if (xfer->endpointno & UE_DIR_IN) {
+ temp.func = &atmegadci_data_tx;
+ need_sync = 1;
+ } else {
+ temp.func = &atmegadci_data_rx;
+ need_sync = 0;
+ }
+
+ /* setup "pc" pointer */
+ temp.pc = xfer->frbuffers + x;
+ } else {
+ need_sync = 0;
+ }
+ while (x != xfer->nframes) {
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+
+ x++;
+
+ if (x == xfer->nframes) {
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_act) {
+ temp.setup_alt_next = 0;
+ }
+ } else {
+ temp.setup_alt_next = 0;
+ }
+ }
+ if (temp.len == 0) {
+ /* make sure that we send an USB packet */
+
+ temp.short_pkt = 0;
+
+ } else {
+ /* regular data transfer */
+
+ temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ atmegadci_setup_standard_chain_sub(&temp);
+
+ if (xfer->flags_int.isochronous_xfr) {
+ temp.offset += temp.len;
+ } else {
+ /* get next Page Cache pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr) {
+ /* always setup a valid "pc" pointer for status and sync */
+ temp.pc = xfer->frbuffers + 0;
+ temp.len = 0;
+ temp.short_pkt = 0;
+ temp.setup_alt_next = 0;
+
+ /* check if we need to sync */
+ if (need_sync) {
+ /* we need a SYNC point after TX */
+ temp.func = &atmegadci_data_tx_sync;
+ atmegadci_setup_standard_chain_sub(&temp);
+ }
+
+ /* check if we should append a status stage */
+ if (!xfer->flags_int.control_act) {
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ if (xfer->endpointno & UE_DIR_IN) {
+ temp.func = &atmegadci_data_rx;
+ need_sync = 0;
+ } else {
+ temp.func = &atmegadci_data_tx;
+ need_sync = 1;
+ }
+
+ atmegadci_setup_standard_chain_sub(&temp);
+ if (need_sync) {
+ /* we need a SYNC point after TX */
+ temp.func = &atmegadci_data_tx_sync;
+ atmegadci_setup_standard_chain_sub(&temp);
+ }
+ }
+ }
+ /* must have at least one frame! */
+ td = temp.td;
+ xfer->td_transfer_last = td;
+}
+
+static void
+atmegadci_timeout(void *arg)
+{
+ struct usb_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ atmegadci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+atmegadci_start_standard_chain(struct usb_xfer *xfer)
+{
+ DPRINTFN(9, "\n");
+
+ /* poll one time - will turn on interrupts */
+ if (atmegadci_xfer_do_fifo(xfer)) {
+ /* put transfer on interrupt queue */
+ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usbd_transfer_timeout_ms(xfer,
+ &atmegadci_timeout, xfer->timeout);
+ }
+ }
+}
+
+static void
+atmegadci_root_intr(struct atmegadci_softc *sc)
+{
+ DPRINTFN(9, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* set port bit */
+ sc->sc_hub_idata[0] = 0x02; /* we only have one port */
+
+ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
+ sizeof(sc->sc_hub_idata));
+ }
+
+static usb_error_t
+atmegadci_standard_done_sub(struct usb_xfer *xfer)
+{
+ struct atmegadci_td *td;
+ uint32_t len;
+ uint8_t error;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+
+ do {
+ len = td->remainder;
+
+ if (xfer->aframes != xfer->nframes) {
+ /*
+ * Verify the length and subtract
+ * the remainder from "frlengths[]":
+ */
+ if (len > xfer->frlengths[xfer->aframes]) {
+ td->error = 1;
+ } else {
+ xfer->frlengths[xfer->aframes] -= len;
+ }
+ }
+ /* Check for transfer error */
+ if (td->error) {
+ /* the transfer is finished */
+ error = 1;
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len > 0) {
+ if (xfer->flags_int.short_frames_ok ||
+ xfer->flags_int.isochronous_xfr) {
+ /* follow alt next */
+ if (td->alt_next) {
+ td = td->obj_next;
+ } else {
+ td = NULL;
+ }
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ error = 0;
+ break;
+ }
+ td = td->obj_next;
+
+ /* this USB frame is complete */
+ error = 0;
+ break;
+
+ } while (0);
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ return (error ?
+ USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+atmegadci_standard_done(struct usb_xfer *xfer)
+{
+ usb_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
+ xfer, xfer->endpoint);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ err = atmegadci_standard_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+ err = atmegadci_standard_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+ err = atmegadci_standard_done_sub(xfer);
+ }
+done:
+ atmegadci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * atmegadci_device_done
+ *
+ * NOTE: this function can be called more than one time on the
+ * same USB transfer!
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_done(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+ uint8_t ep_no;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTFN(9, "xfer=%p, endpoint=%p, error=%d\n",
+ xfer, xfer->endpoint, error);
+
+ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
+ ep_no = (xfer->endpointno & UE_ADDR);
+
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no);
+
+ /* disable endpoint interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, 0);
+
+ DPRINTFN(15, "disabled interrupts!\n");
+ }
+ /* dequeue transfer and start next transfer */
+ usbd_transfer_done(xfer, error);
+}
+
+static void
+atmegadci_xfer_stall(struct usb_xfer *xfer)
+{
+ atmegadci_device_done(xfer, USB_ERR_STALLED);
+}
+
+static void
+atmegadci_set_stall(struct usb_device *udev,
+ struct usb_endpoint *ep, uint8_t *did_stall)
+{
+ struct atmegadci_softc *sc;
+ uint8_t ep_no;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ DPRINTFN(5, "endpoint=%p\n", ep);
+
+ sc = ATMEGA_BUS2SC(udev->bus);
+ /* get endpoint number */
+ ep_no = (ep->edesc->bEndpointAddress & UE_ADDR);
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no);
+ /* set stall */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+ ATMEGA_UECONX_EPEN |
+ ATMEGA_UECONX_STALLRQ);
+}
+
+static void
+atmegadci_clear_stall_sub(struct atmegadci_softc *sc, uint8_t ep_no,
+ uint8_t ep_type, uint8_t ep_dir)
+{
+ uint8_t temp;
+
+ if (ep_type == UE_CONTROL) {
+ /* clearing stall is not needed */
+ return;
+ }
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, ep_no);
+
+ /* set endpoint reset */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST, ATMEGA_UERST_MASK(ep_no));
+
+ /* clear endpoint reset */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0);
+
+ /* set stall */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+ ATMEGA_UECONX_EPEN |
+ ATMEGA_UECONX_STALLRQ);
+
+ /* reset data toggle */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+ ATMEGA_UECONX_EPEN |
+ ATMEGA_UECONX_RSTDT);
+
+ /* clear stall */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+ ATMEGA_UECONX_EPEN |
+ ATMEGA_UECONX_STALLRQC);
+
+ do {
+ if (ep_type == UE_BULK) {
+ temp = ATMEGA_UECFG0X_EPTYPE2;
+ } else if (ep_type == UE_INTERRUPT) {
+ temp = ATMEGA_UECFG0X_EPTYPE3;
+ } else {
+ temp = ATMEGA_UECFG0X_EPTYPE1;
+ }
+ if (ep_dir & UE_DIR_IN) {
+ temp |= ATMEGA_UECFG0X_EPDIR;
+ }
+ /* two banks, 64-bytes wMaxPacket */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECFG0X, temp);
+ ATMEGA_WRITE_1(sc, ATMEGA_UECFG1X,
+ ATMEGA_UECFG1X_ALLOC |
+ ATMEGA_UECFG1X_EPBK0 | /* one bank */
+ ATMEGA_UECFG1X_EPSIZE(3));
+
+ temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X);
+ if (!(temp & ATMEGA_UESTA0X_CFGOK)) {
+ device_printf(sc->sc_bus.bdev,
+ "Chip rejected configuration\n");
+ }
+ } while (0);
+}
+
+static void
+atmegadci_clear_stall(struct usb_device *udev, struct usb_endpoint *ep)
+{
+ struct atmegadci_softc *sc;
+ struct usb_endpoint_descriptor *ed;
+
+ DPRINTFN(5, "endpoint=%p\n", ep);
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ /* check mode */
+ if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ /* get softc */
+ sc = ATMEGA_BUS2SC(udev->bus);
+
+ /* get endpoint descriptor */
+ ed = ep->edesc;
+
+ /* reset endpoint */
+ atmegadci_clear_stall_sub(sc,
+ (ed->bEndpointAddress & UE_ADDR),
+ (ed->bmAttributes & UE_XFERTYPE),
+ (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+}
+
+usb_error_t
+atmegadci_init(struct atmegadci_softc *sc)
+{
+ uint8_t n;
+
+ DPRINTF("start\n");
+
+ /* set up the bus structure */
+ sc->sc_bus.usbrev = USB_REV_1_1;
+ sc->sc_bus.methods = &atmegadci_bus_methods;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* make sure USB is enabled */
+ ATMEGA_WRITE_1(sc, ATMEGA_USBCON,
+ ATMEGA_USBCON_USBE |
+ ATMEGA_USBCON_FRZCLK);
+
+ /* enable USB PAD regulator */
+ ATMEGA_WRITE_1(sc, ATMEGA_UHWCON,
+ ATMEGA_UHWCON_UVREGE |
+ ATMEGA_UHWCON_UIMOD);
+
+ /* the following register sets up the USB PLL, assuming 16MHz X-tal */
+ ATMEGA_WRITE_1(sc, 0x49 /* PLLCSR */, 0x14 | 0x02);
+
+ /* wait for PLL to lock */
+ for (n = 0; n != 20; n++) {
+ if (ATMEGA_READ_1(sc, 0x49) & 0x01)
+ break;
+ /* wait a little bit for PLL to start */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+ }
+
+ /* make sure USB is enabled */
+ ATMEGA_WRITE_1(sc, ATMEGA_USBCON,
+ ATMEGA_USBCON_USBE |
+ ATMEGA_USBCON_OTGPADE |
+ ATMEGA_USBCON_VBUSTE);
+
+ /* turn on clocks */
+ (sc->sc_clocks_on) (&sc->sc_bus);
+
+ /* make sure device is re-enumerated */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDCON, ATMEGA_UDCON_DETACH);
+
+ /* wait a little for things to stabilise */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 20);
+
+ /* enable interrupts */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN,
+ ATMEGA_UDINT_SUSPE |
+ ATMEGA_UDINT_EORSTE);
+
+ /* reset all endpoints */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST,
+ (1 << ATMEGA_EP_MAX) - 1);
+
+ /* disable reset */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0);
+
+ /* disable all endpoints */
+ for (n = 0; n != ATMEGA_EP_MAX; n++) {
+ /* select endpoint */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, n);
+
+ /* disable endpoint interrupt */
+ ATMEGA_WRITE_1(sc, ATMEGA_UEIENX, 0);
+
+ /* disable endpoint */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECONX, 0);
+ }
+
+ /* turn off clocks */
+
+ atmegadci_clocks_off(sc);
+
+ /* read initial VBUS state */
+
+ n = ATMEGA_READ_1(sc, ATMEGA_USBSTA);
+ atmegadci_vbus_interrupt(sc, n & ATMEGA_USBSTA_VBUS);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch any lost interrupts */
+
+ atmegadci_do_poll(&sc->sc_bus);
+
+ return (0); /* success */
+}
+
+void
+atmegadci_uninit(struct atmegadci_softc *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* turn on clocks */
+ (sc->sc_clocks_on) (&sc->sc_bus);
+
+ /* disable interrupts */
+ ATMEGA_WRITE_1(sc, ATMEGA_UDIEN, 0);
+
+ /* reset all endpoints */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST,
+ (1 << ATMEGA_EP_MAX) - 1);
+
+ /* disable reset */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0);
+
+ sc->sc_flags.port_powered = 0;
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ atmegadci_pull_down(sc);
+ atmegadci_clocks_off(sc);
+
+ /* disable USB PAD regulator */
+ ATMEGA_WRITE_1(sc, ATMEGA_UHWCON, 0);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+atmegadci_suspend(struct atmegadci_softc *sc)
+{
+ /* TODO */
+}
+
+static void
+atmegadci_resume(struct atmegadci_softc *sc)
+{
+ /* TODO */
+}
+
+static void
+atmegadci_do_poll(struct usb_bus *bus)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ atmegadci_interrupt_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * atmegadci bulk support
+ * atmegadci control support
+ * atmegadci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_non_isoc_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_non_isoc_close(struct usb_xfer *xfer)
+{
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_device_non_isoc_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_non_isoc_start(struct usb_xfer *xfer)
+{
+ /* setup TDs */
+ atmegadci_setup_standard_chain(xfer);
+ atmegadci_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods atmegadci_device_non_isoc_methods =
+{
+ .open = atmegadci_device_non_isoc_open,
+ .close = atmegadci_device_non_isoc_close,
+ .enter = atmegadci_device_non_isoc_enter,
+ .start = atmegadci_device_non_isoc_start,
+};
+
+/*------------------------------------------------------------------------*
+ * atmegadci full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+atmegadci_device_isoc_fs_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_device_isoc_fs_close(struct usb_xfer *xfer)
+{
+ atmegadci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+atmegadci_device_isoc_fs_enter(struct usb_xfer *xfer)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(xfer->xroot->bus);
+ uint32_t nframes;
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->endpoint->isoc_next, xfer->nframes);
+
+ /* get the current frame index */
+
+ nframes =
+ (ATMEGA_READ_1(sc, ATMEGA_UDFNUMH) << 8) |
+ (ATMEGA_READ_1(sc, ATMEGA_UDFNUML));
+
+ if (usbd_xfer_get_isochronous_start_frame(
+ xfer, nframes, 0, 1, ATMEGA_FRAME_MASK, NULL))
+ DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
+
+ /* setup TDs */
+ atmegadci_setup_standard_chain(xfer);
+}
+
+static void
+atmegadci_device_isoc_fs_start(struct usb_xfer *xfer)
+{
+ /* start TD chain */
+ atmegadci_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods atmegadci_device_isoc_fs_methods =
+{
+ .open = atmegadci_device_isoc_fs_open,
+ .close = atmegadci_device_isoc_fs_close,
+ .enter = atmegadci_device_isoc_fs_enter,
+ .start = atmegadci_device_isoc_fs_start,
+};
+
+/*------------------------------------------------------------------------*
+ * atmegadci root control support
+ *------------------------------------------------------------------------*
+ * Simulate a hardware HUB by handling all the necessary requests.
+ *------------------------------------------------------------------------*/
+
+static const struct usb_device_descriptor atmegadci_devd = {
+ .bLength = sizeof(struct usb_device_descriptor),
+ .bDescriptorType = UDESC_DEVICE,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_FSHUB,
+ .bMaxPacketSize = 64,
+ .bcdDevice = {0x00, 0x01},
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .bNumConfigurations = 1,
+};
+
+static const struct atmegadci_config_desc atmegadci_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(atmegadci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0,
+ },
+ .ifcd = {
+ .bLength = sizeof(struct usb_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = 0,
+ },
+ .endpd = {
+ .bLength = sizeof(struct usb_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = (UE_DIR_IN | ATMEGA_INTR_ENDPT),
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8,
+ .bInterval = 255,
+ },
+};
+#define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) }
+
+static const struct usb_hub_descriptor_min atmegadci_hubd = {
+ .bDescLength = sizeof(atmegadci_hubd),
+ .bDescriptorType = UDESC_HUB,
+ .bNbrPorts = 1,
+ HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)),
+ .bPwrOn2PwrGood = 50,
+ .bHubContrCurrent = 0,
+ .DeviceRemovable = {0}, /* port is removable */
+};
+
+#define STRING_VENDOR \
+ "A\0T\0M\0E\0G\0A"
+
+#define STRING_PRODUCT \
+ "D\0C\0I\0 \0R\0o\0o\0t\0 \0H\0U\0B"
+
+USB_MAKE_STRING_DESC(STRING_VENDOR, atmegadci_vendor);
+USB_MAKE_STRING_DESC(STRING_PRODUCT, atmegadci_product);
+
+static usb_error_t
+atmegadci_roothub_exec(struct usb_device *udev,
+ struct usb_device_request *req, const void **pptr, uint16_t *plength)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(udev->bus);
+ const void *ptr;
+ uint16_t len;
+ uint16_t value;
+ uint16_t index;
+ uint8_t temp;
+ usb_error_t err;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* buffer reset */
+ ptr = (const void *)&sc->sc_hub_temp;
+ len = 0;
+ err = 0;
+
+ value = UGETW(req->wValue);
+ index = UGETW(req->wIndex);
+
+ /* demultiplex the control request */
+
+ switch (req->bmRequestType) {
+ case UT_READ_DEVICE:
+ switch (req->bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_descriptor;
+ case UR_GET_CONFIG:
+ goto tr_handle_get_config;
+ case UR_GET_STATUS:
+ goto tr_handle_get_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_DEVICE:
+ switch (req->bRequest) {
+ case UR_SET_ADDRESS:
+ goto tr_handle_set_address;
+ case UR_SET_CONFIG:
+ goto tr_handle_set_config;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_DESCRIPTOR:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_ENDPOINT:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ switch (UGETW(req->wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_clear_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_clear_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SET_FEATURE:
+ switch (UGETW(req->wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_set_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_set_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SYNCH_FRAME:
+ goto tr_valid; /* nop */
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_ENDPOINT:
+ switch (req->bRequest) {
+ case UR_GET_STATUS:
+ goto tr_handle_get_ep_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_INTERFACE:
+ switch (req->bRequest) {
+ case UR_SET_INTERFACE:
+ goto tr_handle_set_interface;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_INTERFACE:
+ switch (req->bRequest) {
+ case UR_GET_INTERFACE:
+ goto tr_handle_get_interface;
+ case UR_GET_STATUS:
+ goto tr_handle_get_iface_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_INTERFACE:
+ case UT_WRITE_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_READ_CLASS_INTERFACE:
+ case UT_READ_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_WRITE_CLASS_DEVICE:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_valid;
+ case UR_SET_DESCRIPTOR:
+ case UR_SET_FEATURE:
+ break;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_OTHER:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_handle_clear_port_feature;
+ case UR_SET_FEATURE:
+ goto tr_handle_set_port_feature;
+ case UR_CLEAR_TT_BUFFER:
+ case UR_RESET_TT:
+ case UR_STOP_TT:
+ goto tr_valid;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_OTHER:
+ switch (req->bRequest) {
+ case UR_GET_TT_STATE:
+ goto tr_handle_get_tt_state;
+ case UR_GET_STATUS:
+ goto tr_handle_get_port_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_DEVICE:
+ switch (req->bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_class_descriptor;
+ case UR_GET_STATUS:
+ goto tr_handle_get_class_status;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_valid;
+
+tr_handle_get_descriptor:
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ len = sizeof(atmegadci_devd);
+ ptr = (const void *)&atmegadci_devd;
+ goto tr_valid;
+ case UDESC_CONFIG:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ len = sizeof(atmegadci_confd);
+ ptr = (const void *)&atmegadci_confd;
+ goto tr_valid;
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ len = sizeof(usb_string_lang_en);
+ ptr = (const void *)&usb_string_lang_en;
+ goto tr_valid;
+
+ case 1: /* Vendor */
+ len = sizeof(atmegadci_vendor);
+ ptr = (const void *)&atmegadci_vendor;
+ goto tr_valid;
+
+ case 2: /* Product */
+ len = sizeof(atmegadci_product);
+ ptr = (const void *)&atmegadci_product;
+ goto tr_valid;
+ default:
+ break;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_stalled;
+
+tr_handle_get_config:
+ len = 1;
+ sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+ goto tr_valid;
+
+tr_handle_get_status:
+ len = 2;
+ USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED);
+ goto tr_valid;
+
+tr_handle_set_address:
+ if (value & 0xFF00) {
+ goto tr_stalled;
+ }
+ sc->sc_rt_addr = value;
+ goto tr_valid;
+
+tr_handle_set_config:
+ if (value >= 2) {
+ goto tr_stalled;
+ }
+ sc->sc_conf = value;
+ goto tr_valid;
+
+tr_handle_get_interface:
+ len = 1;
+ sc->sc_hub_temp.wValue[0] = 0;
+ goto tr_valid;
+
+tr_handle_get_tt_state:
+tr_handle_get_class_status:
+tr_handle_get_iface_status:
+tr_handle_get_ep_status:
+ len = 2;
+ USETW(sc->sc_hub_temp.wValue, 0);
+ goto tr_valid;
+
+tr_handle_set_halt:
+tr_handle_set_interface:
+tr_handle_set_wakeup:
+tr_handle_clear_wakeup:
+tr_handle_clear_halt:
+ goto tr_valid;
+
+tr_handle_clear_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
+
+ switch (value) {
+ case UHF_PORT_SUSPEND:
+ atmegadci_wakeup_peer(sc);
+ break;
+
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 0;
+ break;
+
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ case UHF_C_PORT_ENABLE:
+ case UHF_C_PORT_OVER_CURRENT:
+ case UHF_C_PORT_RESET:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 0;
+ atmegadci_pull_down(sc);
+ atmegadci_clocks_off(sc);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ /* clear connect change flag */
+ sc->sc_flags.change_connect = 0;
+
+ if (!sc->sc_flags.status_bus_reset) {
+ /* we are not connected */
+ break;
+ }
+
+ /* configure the control endpoint */
+
+ /* select endpoint number */
+ ATMEGA_WRITE_1(sc, ATMEGA_UENUM, 0);
+
+ /* set endpoint reset */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST, ATMEGA_UERST_MASK(0));
+
+ /* clear endpoint reset */
+ ATMEGA_WRITE_1(sc, ATMEGA_UERST, 0);
+
+ /* enable and stall endpoint */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECONX,
+ ATMEGA_UECONX_EPEN |
+ ATMEGA_UECONX_STALLRQ);
+
+ /* one bank, 64-bytes wMaxPacket */
+ ATMEGA_WRITE_1(sc, ATMEGA_UECFG0X,
+ ATMEGA_UECFG0X_EPTYPE0);
+ ATMEGA_WRITE_1(sc, ATMEGA_UECFG1X,
+ ATMEGA_UECFG1X_ALLOC |
+ ATMEGA_UECFG1X_EPBK0 |
+ ATMEGA_UECFG1X_EPSIZE(3));
+
+ /* check valid config */
+ temp = ATMEGA_READ_1(sc, ATMEGA_UESTA0X);
+ if (!(temp & ATMEGA_UESTA0X_CFGOK)) {
+ device_printf(sc->sc_bus.bdev,
+ "Chip rejected EP0 configuration\n");
+ }
+ break;
+ case UHF_C_PORT_SUSPEND:
+ sc->sc_flags.change_suspend = 0;
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_set_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(9, "UR_SET_PORT_FEATURE\n");
+
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 1;
+ break;
+ case UHF_PORT_SUSPEND:
+ case UHF_PORT_RESET:
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 1;
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_get_port_status:
+
+ DPRINTFN(9, "UR_GET_PORT_STATUS\n");
+
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ if (sc->sc_flags.status_vbus) {
+ atmegadci_clocks_on(sc);
+ atmegadci_pull_up(sc);
+ } else {
+ atmegadci_pull_down(sc);
+ atmegadci_clocks_off(sc);
+ }
+
+ /* Select FULL-speed and Device Side Mode */
+
+ value = UPS_PORT_MODE_DEVICE;
+
+ if (sc->sc_flags.port_powered) {
+ value |= UPS_PORT_POWER;
+ }
+ if (sc->sc_flags.port_enabled) {
+ value |= UPS_PORT_ENABLED;
+ }
+ if (sc->sc_flags.status_vbus &&
+ sc->sc_flags.status_bus_reset) {
+ value |= UPS_CURRENT_CONNECT_STATUS;
+ }
+ if (sc->sc_flags.status_suspend) {
+ value |= UPS_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortStatus, value);
+
+ value = 0;
+
+ if (sc->sc_flags.change_connect) {
+ value |= UPS_C_CONNECT_STATUS;
+ }
+ if (sc->sc_flags.change_suspend) {
+ value |= UPS_C_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortChange, value);
+ len = sizeof(sc->sc_hub_temp.ps);
+ goto tr_valid;
+
+tr_handle_get_class_descriptor:
+ if (value & 0xFF) {
+ goto tr_stalled;
+ }
+ ptr = (const void *)&atmegadci_hubd;
+ len = sizeof(atmegadci_hubd);
+ goto tr_valid;
+
+tr_stalled:
+ err = USB_ERR_STALLED;
+tr_valid:
+done:
+ *plength = len;
+ *pptr = ptr;
+ return (err);
+}
+
+static void
+atmegadci_xfer_setup(struct usb_setup_params *parm)
+{
+ const struct usb_hw_ep_profile *pf;
+ struct usb_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t n;
+ uint8_t ep_no;
+
+ xfer = parm->curr_xfer;
+
+ /*
+ * NOTE: This driver does not use any of the parameters that
+ * are computed from the following values. Just set some
+ * reasonable dummies:
+ */
+ parm->hc_max_packet_size = 0x500;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x500;
+
+ usbd_transfer_setup_sub(parm);
+
+ /*
+ * compute maximum number of TDs
+ */
+ if ((xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) {
+ ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */
+ + 1 /* SYNC 2 */ ;
+ } else {
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+ }
+
+ /*
+ * check if "usbd_transfer_setup_sub" set an error
+ */
+ if (parm->err)
+ return;
+
+ /*
+ * allocate transfer descriptors
+ */
+ last_obj = NULL;
+
+ /*
+ * get profile stuff
+ */
+ ep_no = xfer->endpointno & UE_ADDR;
+ atmegadci_get_hw_ep_profile(parm->udev, &pf, ep_no);
+
+ if (pf == NULL) {
+ /* should not happen */
+ parm->err = USB_ERR_INVAL;
+ return;
+ }
+
+ /* align data */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ for (n = 0; n != ntd; n++) {
+ struct atmegadci_td *td;
+
+ if (parm->buf) {
+ td = USB_ADD_BYTES(parm->buf, parm->size[0]);
+
+ /* init TD */
+ td->max_packet_size = xfer->max_packet_size;
+ td->ep_no = ep_no;
+ if (pf->support_multi_buffer) {
+ td->support_multi_buffer = 1;
+ }
+ td->obj_next = last_obj;
+
+ last_obj = td;
+ }
+ parm->size[0] += sizeof(*td);
+ }
+
+ xfer->td_start[0] = last_obj;
+}
+
+static void
+atmegadci_xfer_unsetup(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+atmegadci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
+ struct usb_endpoint *ep)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d,%d)\n",
+ ep, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb_mode,
+ sc->sc_rt_addr, udev->device_index);
+
+ if (udev->device_index != sc->sc_rt_addr) {
+ if (udev->speed != USB_SPEED_FULL) {
+ /* not supported */
+ return;
+ }
+ if ((edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS)
+ ep->methods = &atmegadci_device_isoc_fs_methods;
+ else
+ ep->methods = &atmegadci_device_non_isoc_methods;
+ }
+}
+
+static void
+atmegadci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state)
+{
+ struct atmegadci_softc *sc = ATMEGA_BUS2SC(bus);
+
+ switch (state) {
+ case USB_HW_POWER_SUSPEND:
+ atmegadci_suspend(sc);
+ break;
+ case USB_HW_POWER_SHUTDOWN:
+ atmegadci_uninit(sc);
+ break;
+ case USB_HW_POWER_RESUME:
+ atmegadci_resume(sc);
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct usb_bus_methods atmegadci_bus_methods =
+{
+ .endpoint_init = &atmegadci_ep_init,
+ .xfer_setup = &atmegadci_xfer_setup,
+ .xfer_unsetup = &atmegadci_xfer_unsetup,
+ .get_hw_ep_profile = &atmegadci_get_hw_ep_profile,
+ .xfer_stall = &atmegadci_xfer_stall,
+ .set_stall = &atmegadci_set_stall,
+ .clear_stall = &atmegadci_clear_stall,
+ .roothub_exec = &atmegadci_roothub_exec,
+ .xfer_poll = &atmegadci_do_poll,
+ .set_hw_power_sleep = &atmegadci_set_hw_power_sleep,
+};
diff --git a/sys/dev/usb/controller/atmegadci.h b/sys/dev/usb/controller/atmegadci.h
new file mode 100644
index 000000000000..55db69e123f4
--- /dev/null
+++ b/sys/dev/usb/controller/atmegadci.h
@@ -0,0 +1,284 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2009 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * USB Device Port register definitions, copied from ATMEGA documentation
+ * provided by ATMEL.
+ */
+
+#ifndef _ATMEGADCI_H_
+#define _ATMEGADCI_H_
+
+#define ATMEGA_MAX_DEVICES (USB_MIN_DEVICES + 1)
+
+#define ATMEGA_OTGTCON 0xF9
+#define ATMEGA_OTGTCON_VALUE(x) ((x) << 0)
+#define ATMEGA_OTGTCON_PAGE(x) ((x) << 5)
+
+#define ATMEGA_UEINT 0xF4
+#define ATMEGA_UEINT_MASK(n) (1 << (n)) /* endpoint interrupt mask */
+
+#define ATMEGA_UEBCHX 0xF3 /* FIFO byte count high */
+#define ATMEGA_UEBCLX 0xF2 /* FIFO byte count low */
+#define ATMEGA_UEDATX 0xF1 /* FIFO data */
+
+#define ATMEGA_UEIENX 0xF0 /* interrupt enable register */
+#define ATMEGA_UEIENX_TXINE (1 << 0)
+#define ATMEGA_UEIENX_STALLEDE (1 << 1)
+#define ATMEGA_UEIENX_RXOUTE (1 << 2)
+#define ATMEGA_UEIENX_RXSTPE (1 << 3) /* received SETUP packet */
+#define ATMEGA_UEIENX_NAKOUTE (1 << 4)
+#define ATMEGA_UEIENX_NAKINE (1 << 6)
+#define ATMEGA_UEIENX_FLERRE (1 << 7)
+
+#define ATMEGA_UESTA1X 0xEF
+#define ATMEGA_UESTA1X_CURRBK (3 << 0) /* current bank */
+#define ATMEGA_UESTA1X_CTRLDIR (1 << 2) /* control endpoint direction */
+
+#define ATMEGA_UESTA0X 0xEE
+#define ATMEGA_UESTA0X_NBUSYBK (3 << 0)
+#define ATMEGA_UESTA0X_DTSEQ (3 << 2)
+#define ATMEGA_UESTA0X_UNDERFI (1 << 5) /* underflow */
+#define ATMEGA_UESTA0X_OVERFI (1 << 6) /* overflow */
+#define ATMEGA_UESTA0X_CFGOK (1 << 7)
+
+#define ATMEGA_UECFG1X 0xED /* endpoint config register */
+#define ATMEGA_UECFG1X_ALLOC (1 << 1)
+#define ATMEGA_UECFG1X_EPBK0 (0 << 2)
+#define ATMEGA_UECFG1X_EPBK1 (1 << 2)
+#define ATMEGA_UECFG1X_EPBK2 (2 << 2)
+#define ATMEGA_UECFG1X_EPBK3 (3 << 2)
+#define ATMEGA_UECFG1X_EPSIZE(n) ((n) << 4)
+
+#define ATMEGA_UECFG0X 0xEC
+#define ATMEGA_UECFG0X_EPDIR (1 << 0) /* endpoint direction */
+#define ATMEGA_UECFG0X_EPTYPE0 (0 << 6)
+#define ATMEGA_UECFG0X_EPTYPE1 (1 << 6)
+#define ATMEGA_UECFG0X_EPTYPE2 (2 << 6)
+#define ATMEGA_UECFG0X_EPTYPE3 (3 << 6)
+
+#define ATMEGA_UECONX 0xEB
+#define ATMEGA_UECONX_EPEN (1 << 0)
+#define ATMEGA_UECONX_RSTDT (1 << 3)
+#define ATMEGA_UECONX_STALLRQC (1 << 4) /* stall request clear */
+#define ATMEGA_UECONX_STALLRQ (1 << 5) /* stall request set */
+
+#define ATMEGA_UERST 0xEA /* endpoint reset register */
+#define ATMEGA_UERST_MASK(n) (1 << (n))
+
+#define ATMEGA_UENUM 0xE9 /* endpoint number */
+
+#define ATMEGA_UEINTX 0xE8 /* interrupt register */
+#define ATMEGA_UEINTX_TXINI (1 << 0)
+#define ATMEGA_UEINTX_STALLEDI (1 << 1)
+#define ATMEGA_UEINTX_RXOUTI (1 << 2)
+#define ATMEGA_UEINTX_RXSTPI (1 << 3) /* received setup packet */
+#define ATMEGA_UEINTX_NAKOUTI (1 << 4)
+#define ATMEGA_UEINTX_RWAL (1 << 5)
+#define ATMEGA_UEINTX_NAKINI (1 << 6)
+#define ATMEGA_UEINTX_FIFOCON (1 << 7)
+
+#define ATMEGA_UDMFN 0xE6
+#define ATMEGA_UDMFN_FNCERR (1 << 4)
+
+#define ATMEGA_UDFNUMH 0xE5 /* frame number high */
+#define ATMEGA_UDFNUMH_MASK 7
+
+#define ATMEGA_UDFNUML 0xE4 /* frame number low */
+#define ATMEGA_UDFNUML_MASK 0xFF
+
+#define ATMEGA_FRAME_MASK 0x7FF
+
+#define ATMEGA_UDADDR 0xE3 /* USB address */
+#define ATMEGA_UDADDR_MASK 0x7F
+#define ATMEGA_UDADDR_ADDEN (1 << 7)
+
+#define ATMEGA_UDIEN 0xE2 /* USB device interrupt enable */
+#define ATMEGA_UDINT_SUSPE (1 << 0)
+#define ATMEGA_UDINT_MSOFE (1 << 1)
+#define ATMEGA_UDINT_SOFE (1 << 2)
+#define ATMEGA_UDINT_EORSTE (1 << 3)
+#define ATMEGA_UDINT_WAKEUPE (1 << 4)
+#define ATMEGA_UDINT_EORSME (1 << 5)
+#define ATMEGA_UDINT_UPRSME (1 << 6)
+
+#define ATMEGA_UDINT 0xE1 /* USB device interrupt status */
+#define ATMEGA_UDINT_SUSPI (1 << 0)
+#define ATMEGA_UDINT_MSOFI (1 << 1)
+#define ATMEGA_UDINT_SOFI (1 << 2)
+#define ATMEGA_UDINT_EORSTI (1 << 3)
+#define ATMEGA_UDINT_WAKEUPI (1 << 4)
+#define ATMEGA_UDINT_EORSMI (1 << 5)
+#define ATMEGA_UDINT_UPRSMI (1 << 6)
+
+#define ATMEGA_UDCON 0xE0 /* USB device connection register */
+#define ATMEGA_UDCON_DETACH (1 << 0)
+#define ATMEGA_UDCON_RMWKUP (1 << 1)
+#define ATMEGA_UDCON_LSM (1 << 2)
+#define ATMEGA_UDCON_RSTCPU (1 << 3)
+
+#define ATMEGA_OTGINT 0xDF
+
+#define ATMEGA_OTGCON 0xDD
+#define ATMEGA_OTGCON_VBUSRQC (1 << 0)
+#define ATMEGA_OTGCON_VBUSREQ (1 << 1)
+#define ATMEGA_OTGCON_VBUSHWC (1 << 2)
+#define ATMEGA_OTGCON_SRPSEL (1 << 3)
+#define ATMEGA_OTGCON_SRPREQ (1 << 4)
+#define ATMEGA_OTGCON_HNPREQ (1 << 5)
+
+#define ATMEGA_USBINT 0xDA
+#define ATMEGA_USBINT_VBUSTI (1 << 0) /* USB VBUS interrupt */
+#define ATMEGA_USBINT_IDI (1 << 1) /* USB ID interrupt */
+
+#define ATMEGA_USBSTA 0xD9
+#define ATMEGA_USBSTA_VBUS (1 << 0)
+#define ATMEGA_USBSTA_ID (1 << 1)
+
+#define ATMEGA_USBCON 0xD8
+#define ATMEGA_USBCON_VBUSTE (1 << 0)
+#define ATMEGA_USBCON_IDE (1 << 1)
+#define ATMEGA_USBCON_OTGPADE (1 << 4)
+#define ATMEGA_USBCON_FRZCLK (1 << 5)
+#define ATMEGA_USBCON_USBE (1 << 7)
+
+#define ATMEGA_UHWCON 0xD7
+#define ATMEGA_UHWCON_UVREGE (1 << 0)
+#define ATMEGA_UHWCON_UVCONE (1 << 4)
+#define ATMEGA_UHWCON_UIDE (1 << 6)
+#define ATMEGA_UHWCON_UIMOD (1 << 7)
+
+#define ATMEGA_READ_1(sc, reg) \
+ bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg)
+
+#define ATMEGA_WRITE_1(sc, reg, data) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data)
+
+#define ATMEGA_WRITE_MULTI_1(sc, reg, ptr, len) \
+ bus_space_write_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, ptr, len)
+
+#define ATMEGA_READ_MULTI_1(sc, reg, ptr, len) \
+ bus_space_read_multi_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, ptr, len)
+
+/*
+ * Maximum number of endpoints supported:
+ */
+#define ATMEGA_EP_MAX 7
+
+struct atmegadci_td;
+
+typedef uint8_t (atmegadci_cmd_t)(struct atmegadci_td *td);
+typedef void (atmegadci_clocks_t)(struct usb_bus *);
+
+struct atmegadci_td {
+ struct atmegadci_td *obj_next;
+ atmegadci_cmd_t *func;
+ struct usb_page_cache *pc;
+ uint32_t offset;
+ uint32_t remainder;
+ uint16_t max_packet_size;
+ uint8_t error:1;
+ uint8_t alt_next:1;
+ uint8_t short_pkt:1;
+ uint8_t support_multi_buffer:1;
+ uint8_t did_stall:1;
+ uint8_t ep_no:3;
+};
+
+struct atmegadci_std_temp {
+ atmegadci_cmd_t *func;
+ struct usb_page_cache *pc;
+ struct atmegadci_td *td;
+ struct atmegadci_td *td_next;
+ uint32_t len;
+ uint32_t offset;
+ uint16_t max_frame_size;
+ uint8_t short_pkt;
+ /*
+ * short_pkt = 0: transfer should be short terminated
+ * short_pkt = 1: transfer should not be short terminated
+ */
+ uint8_t setup_alt_next;
+ uint8_t did_stall;
+};
+
+struct atmegadci_config_desc {
+ struct usb_config_descriptor confd;
+ struct usb_interface_descriptor ifcd;
+ struct usb_endpoint_descriptor endpd;
+} __packed;
+
+union atmegadci_hub_temp {
+ uWord wValue;
+ struct usb_port_status ps;
+};
+
+struct atmegadci_flags {
+ uint8_t change_connect:1;
+ uint8_t change_suspend:1;
+ uint8_t status_suspend:1; /* set if suspended */
+ uint8_t status_vbus:1; /* set if present */
+ uint8_t status_bus_reset:1; /* set if reset complete */
+ uint8_t remote_wakeup:1;
+ uint8_t self_powered:1;
+ uint8_t clocks_off:1;
+ uint8_t port_powered:1;
+ uint8_t port_enabled:1;
+ uint8_t d_pulled_up:1;
+};
+
+struct atmegadci_softc {
+ struct usb_bus sc_bus;
+ union atmegadci_hub_temp sc_hub_temp;
+
+ /* must be set by by the bus interface layer */
+ atmegadci_clocks_t *sc_clocks_on;
+ atmegadci_clocks_t *sc_clocks_off;
+
+ struct usb_device *sc_devices[ATMEGA_MAX_DEVICES];
+ struct resource *sc_irq_res;
+ void *sc_intr_hdl;
+ struct resource *sc_io_res;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ uint8_t sc_rt_addr; /* root hub address */
+ uint8_t sc_dv_addr; /* device address */
+ uint8_t sc_conf; /* root hub config */
+
+ uint8_t sc_hub_idata[1];
+
+ struct atmegadci_flags sc_flags;
+};
+
+/* prototypes */
+
+usb_error_t atmegadci_init(struct atmegadci_softc *sc);
+void atmegadci_uninit(struct atmegadci_softc *sc);
+void atmegadci_interrupt(struct atmegadci_softc *sc);
+
+#endif /* _ATMEGADCI_H_ */
diff --git a/sys/dev/usb/controller/avr32dci.c b/sys/dev/usb/controller/avr32dci.c
new file mode 100644
index 000000000000..e968d799e37f
--- /dev/null
+++ b/sys/dev/usb/controller/avr32dci.c
@@ -0,0 +1,2053 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2009 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the driver for the AVR32 series USB Device
+ * Controller
+ */
+
+/*
+ * NOTE: When the chip detects BUS-reset it will also reset the
+ * endpoints, Function-address and more.
+ */
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#define USB_DEBUG_VAR avr32dci_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#include <dev/usb/controller/avr32dci.h>
+
+#define AVR32_BUS2SC(bus) \
+ __containerof(bus, struct avr32dci_softc, sc_bus)
+
+#define AVR32_PC2SC(pc) \
+ AVR32_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus)
+
+#ifdef USB_DEBUG
+static int avr32dci_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, avr32dci,
+ CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB AVR32 DCI");
+SYSCTL_INT(_hw_usb_avr32dci, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &avr32dci_debug, 0, "AVR32 DCI debug level");
+#endif
+
+#define AVR32_INTR_ENDPT 1
+
+/* prototypes */
+
+static const struct usb_bus_methods avr32dci_bus_methods;
+static const struct usb_pipe_methods avr32dci_device_non_isoc_methods;
+static const struct usb_pipe_methods avr32dci_device_isoc_fs_methods;
+
+static avr32dci_cmd_t avr32dci_setup_rx;
+static avr32dci_cmd_t avr32dci_data_rx;
+static avr32dci_cmd_t avr32dci_data_tx;
+static avr32dci_cmd_t avr32dci_data_tx_sync;
+static void avr32dci_device_done(struct usb_xfer *, usb_error_t);
+static void avr32dci_do_poll(struct usb_bus *);
+static void avr32dci_standard_done(struct usb_xfer *);
+static void avr32dci_root_intr(struct avr32dci_softc *sc);
+
+/*
+ * Here is a list of what the chip supports:
+ */
+static const struct usb_hw_ep_profile
+ avr32dci_ep_profile[4] = {
+ [0] = {
+ .max_in_frame_size = 64,
+ .max_out_frame_size = 64,
+ .is_simplex = 1,
+ .support_control = 1,
+ },
+
+ [1] = {
+ .max_in_frame_size = 512,
+ .max_out_frame_size = 512,
+ .is_simplex = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_isochronous = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+
+ [2] = {
+ .max_in_frame_size = 64,
+ .max_out_frame_size = 64,
+ .is_simplex = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+
+ [3] = {
+ .max_in_frame_size = 1024,
+ .max_out_frame_size = 1024,
+ .is_simplex = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_isochronous = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+};
+
+static void
+avr32dci_get_hw_ep_profile(struct usb_device *udev,
+ const struct usb_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+ if (ep_addr == 0)
+ *ppf = avr32dci_ep_profile;
+ else if (ep_addr < 3)
+ *ppf = avr32dci_ep_profile + 1;
+ else if (ep_addr < 5)
+ *ppf = avr32dci_ep_profile + 2;
+ else if (ep_addr < 7)
+ *ppf = avr32dci_ep_profile + 3;
+ else
+ *ppf = NULL;
+}
+
+static void
+avr32dci_mod_ctrl(struct avr32dci_softc *sc, uint32_t set, uint32_t clear)
+{
+ uint32_t temp;
+
+ temp = AVR32_READ_4(sc, AVR32_CTRL);
+ temp |= set;
+ temp &= ~clear;
+ AVR32_WRITE_4(sc, AVR32_CTRL, temp);
+}
+
+static void
+avr32dci_mod_ien(struct avr32dci_softc *sc, uint32_t set, uint32_t clear)
+{
+ uint32_t temp;
+
+ temp = AVR32_READ_4(sc, AVR32_IEN);
+ temp |= set;
+ temp &= ~clear;
+ AVR32_WRITE_4(sc, AVR32_IEN, temp);
+}
+
+static void
+avr32dci_clocks_on(struct avr32dci_softc *sc)
+{
+ if (sc->sc_flags.clocks_off &&
+ sc->sc_flags.port_powered) {
+ DPRINTFN(5, "\n");
+
+ /* turn on clocks */
+ (sc->sc_clocks_on) (&sc->sc_bus);
+
+ avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_EN_USBA, 0);
+
+ sc->sc_flags.clocks_off = 0;
+ }
+}
+
+static void
+avr32dci_clocks_off(struct avr32dci_softc *sc)
+{
+ if (!sc->sc_flags.clocks_off) {
+ DPRINTFN(5, "\n");
+
+ avr32dci_mod_ctrl(sc, 0, AVR32_CTRL_DEV_EN_USBA);
+
+ /* turn clocks off */
+ (sc->sc_clocks_off) (&sc->sc_bus);
+
+ sc->sc_flags.clocks_off = 1;
+ }
+}
+
+static void
+avr32dci_pull_up(struct avr32dci_softc *sc)
+{
+ /* pullup D+, if possible */
+
+ if (!sc->sc_flags.d_pulled_up &&
+ sc->sc_flags.port_powered) {
+ sc->sc_flags.d_pulled_up = 1;
+ avr32dci_mod_ctrl(sc, 0, AVR32_CTRL_DEV_DETACH);
+ }
+}
+
+static void
+avr32dci_pull_down(struct avr32dci_softc *sc)
+{
+ /* pulldown D+, if possible */
+
+ if (sc->sc_flags.d_pulled_up) {
+ sc->sc_flags.d_pulled_up = 0;
+ avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_DETACH, 0);
+ }
+}
+
+static void
+avr32dci_wakeup_peer(struct avr32dci_softc *sc)
+{
+ if (!sc->sc_flags.status_suspend) {
+ return;
+ }
+ avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_REWAKEUP, 0);
+
+ /* wait 8 milliseconds */
+ /* Wait for reset to complete. */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+
+ /* hardware should have cleared RMWKUP bit */
+}
+
+static void
+avr32dci_set_address(struct avr32dci_softc *sc, uint8_t addr)
+{
+ DPRINTFN(5, "addr=%d\n", addr);
+
+ avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_FADDR_EN | addr, 0);
+}
+
+static uint8_t
+avr32dci_setup_rx(struct avr32dci_td *td)
+{
+ struct avr32dci_softc *sc;
+ struct usb_device_request req;
+ uint16_t count;
+ uint32_t temp;
+
+ /* get pointer to softc */
+ sc = AVR32_PC2SC(td->pc);
+
+ /* check endpoint status */
+ temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no));
+
+ DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp);
+
+ if (!(temp & AVR32_EPTSTA_RX_SETUP)) {
+ goto not_complete;
+ }
+ /* clear did stall */
+ td->did_stall = 0;
+ /* get the packet byte count */
+ count = AVR32_EPTSTA_BYTE_COUNT(temp);
+
+ /* verify data length */
+ if (count != td->remainder) {
+ DPRINTFN(0, "Invalid SETUP packet "
+ "length, %d bytes\n", count);
+ goto not_complete;
+ }
+ if (count != sizeof(req)) {
+ DPRINTFN(0, "Unsupported SETUP packet "
+ "length, %d bytes\n", count);
+ goto not_complete;
+ }
+ /* receive data */
+ memcpy(&req, sc->physdata, sizeof(req));
+
+ /* copy data into real buffer */
+ usbd_copy_in(td->pc, 0, &req, sizeof(req));
+
+ td->offset = sizeof(req);
+ td->remainder = 0;
+
+ /* sneak peek the set address */
+ if ((req.bmRequestType == UT_WRITE_DEVICE) &&
+ (req.bRequest == UR_SET_ADDRESS)) {
+ sc->sc_dv_addr = req.wValue[0] & 0x7F;
+ /* must write address before ZLP */
+ avr32dci_mod_ctrl(sc, 0, AVR32_CTRL_DEV_FADDR_EN |
+ AVR32_CTRL_DEV_ADDR);
+ avr32dci_mod_ctrl(sc, sc->sc_dv_addr, 0);
+ } else {
+ sc->sc_dv_addr = 0xFF;
+ }
+
+ /* clear SETUP packet interrupt */
+ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(td->ep_no), AVR32_EPTSTA_RX_SETUP);
+ return (0); /* complete */
+
+not_complete:
+ if (temp & AVR32_EPTSTA_RX_SETUP) {
+ /* clear SETUP packet interrupt */
+ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(td->ep_no), AVR32_EPTSTA_RX_SETUP);
+ }
+ /* abort any ongoing transfer */
+ if (!td->did_stall) {
+ DPRINTFN(5, "stalling\n");
+ AVR32_WRITE_4(sc, AVR32_EPTSETSTA(td->ep_no),
+ AVR32_EPTSTA_FRCESTALL);
+ td->did_stall = 1;
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+avr32dci_data_rx(struct avr32dci_td *td)
+{
+ struct avr32dci_softc *sc;
+ struct usb_page_search buf_res;
+ uint16_t count;
+ uint32_t temp;
+ uint8_t to;
+ uint8_t got_short;
+
+ to = 4; /* don't loop forever! */
+ got_short = 0;
+
+ /* get pointer to softc */
+ sc = AVR32_PC2SC(td->pc);
+
+repeat:
+ /* check if any of the FIFO banks have data */
+ /* check endpoint status */
+ temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no));
+
+ DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp);
+
+ if (temp & AVR32_EPTSTA_RX_SETUP) {
+ if (td->remainder == 0) {
+ /*
+ * We are actually complete and have
+ * received the next SETUP
+ */
+ DPRINTFN(5, "faking complete\n");
+ return (0); /* complete */
+ }
+ /*
+ * USB Host Aborted the transfer.
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ /* check status */
+ if (!(temp & AVR32_EPTSTA_RX_BK_RDY)) {
+ /* no data */
+ goto not_complete;
+ }
+ /* get the packet byte count */
+ count = AVR32_EPTSTA_BYTE_COUNT(temp);
+
+ /* verify the packet byte count */
+ if (count != td->max_packet_size) {
+ if (count < td->max_packet_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ }
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ while (count > 0) {
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* receive data */
+ memcpy(buf_res.buffer, sc->physdata +
+ (AVR32_EPTSTA_CURRENT_BANK(temp) << td->bank_shift) +
+ (td->ep_no << 16) + (td->offset % td->max_packet_size), buf_res.length);
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* clear OUT packet interrupt */
+ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(td->ep_no), AVR32_EPTSTA_RX_BK_RDY);
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+not_complete:
+ return (1); /* not complete */
+}
+
+static uint8_t
+avr32dci_data_tx(struct avr32dci_td *td)
+{
+ struct avr32dci_softc *sc;
+ struct usb_page_search buf_res;
+ uint16_t count;
+ uint8_t to;
+ uint32_t temp;
+
+ to = 4; /* don't loop forever! */
+
+ /* get pointer to softc */
+ sc = AVR32_PC2SC(td->pc);
+
+repeat:
+
+ /* check endpoint status */
+ temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no));
+
+ DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp);
+
+ if (temp & AVR32_EPTSTA_RX_SETUP) {
+ /*
+ * The current transfer was aborted
+ * by the USB Host
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ if (temp & AVR32_EPTSTA_TX_PK_RDY) {
+ /* cannot write any data - all banks are busy */
+ goto not_complete;
+ }
+ count = td->max_packet_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ while (count > 0) {
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* transmit data */
+ memcpy(sc->physdata +
+ (AVR32_EPTSTA_CURRENT_BANK(temp) << td->bank_shift) +
+ (td->ep_no << 16) + (td->offset % td->max_packet_size),
+ buf_res.buffer, buf_res.length);
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* allocate FIFO bank */
+ AVR32_WRITE_4(sc, AVR32_EPTCTL(td->ep_no), AVR32_EPTCTL_TX_PK_RDY);
+
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+not_complete:
+ return (1); /* not complete */
+}
+
+static uint8_t
+avr32dci_data_tx_sync(struct avr32dci_td *td)
+{
+ struct avr32dci_softc *sc;
+ uint32_t temp;
+
+ /* get pointer to softc */
+ sc = AVR32_PC2SC(td->pc);
+
+ /* check endpoint status */
+ temp = AVR32_READ_4(sc, AVR32_EPTSTA(td->ep_no));
+
+ DPRINTFN(5, "EPTSTA(%u)=0x%08x\n", td->ep_no, temp);
+
+ if (temp & AVR32_EPTSTA_RX_SETUP) {
+ DPRINTFN(5, "faking complete\n");
+ /* Race condition */
+ return (0); /* complete */
+ }
+ /*
+ * The control endpoint has only got one bank, so if that bank
+ * is free the packet has been transferred!
+ */
+ if (AVR32_EPTSTA_BUSY_BANK_STA(temp) != 0) {
+ /* cannot write any data - a bank is busy */
+ goto not_complete;
+ }
+ if (sc->sc_dv_addr != 0xFF) {
+ /* set new address */
+ avr32dci_set_address(sc, sc->sc_dv_addr);
+ }
+ return (0); /* complete */
+
+not_complete:
+ return (1); /* not complete */
+}
+
+static uint8_t
+avr32dci_xfer_do_fifo(struct usb_xfer *xfer)
+{
+ struct avr32dci_td *td;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+ while (1) {
+ if ((td->func) (td)) {
+ /* operation in progress */
+ break;
+ }
+ if (((void *)td) == xfer->td_transfer_last) {
+ goto done;
+ }
+ if (td->error) {
+ goto done;
+ } else if (td->remainder > 0) {
+ /*
+ * We had a short transfer. If there is no alternate
+ * next, stop processing !
+ */
+ if (!td->alt_next) {
+ goto done;
+ }
+ }
+ /*
+ * Fetch the next transfer descriptor and transfer
+ * some flags to the next transfer descriptor
+ */
+ td = td->obj_next;
+ xfer->td_transfer_cache = td;
+ }
+ return (1); /* not complete */
+
+done:
+ /* compute all actual lengths */
+
+ avr32dci_standard_done(xfer);
+ return (0); /* complete */
+}
+
+static void
+avr32dci_interrupt_poll(struct avr32dci_softc *sc)
+{
+ struct usb_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (!avr32dci_xfer_do_fifo(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+void
+avr32dci_vbus_interrupt(struct avr32dci_softc *sc, uint8_t is_on)
+{
+ DPRINTFN(5, "vbus = %u\n", is_on);
+
+ if (is_on) {
+ if (!sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 1;
+
+ /* complete root HUB interrupt endpoint */
+
+ avr32dci_root_intr(sc);
+ }
+ } else {
+ if (sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* complete root HUB interrupt endpoint */
+
+ avr32dci_root_intr(sc);
+ }
+ }
+}
+
+void
+avr32dci_interrupt(struct avr32dci_softc *sc)
+{
+ uint32_t status;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* read interrupt status */
+ status = AVR32_READ_4(sc, AVR32_INTSTA);
+
+ /* clear all set interrupts */
+ AVR32_WRITE_4(sc, AVR32_CLRINT, status);
+
+ DPRINTFN(14, "INTSTA=0x%08x\n", status);
+
+ /* check for any bus state change interrupts */
+ if (status & AVR32_INT_ENDRESET) {
+ DPRINTFN(5, "end of reset\n");
+
+ /* set correct state */
+ sc->sc_flags.status_bus_reset = 1;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* disable resume interrupt */
+ avr32dci_mod_ien(sc, AVR32_INT_DET_SUSPD |
+ AVR32_INT_ENDRESET, AVR32_INT_WAKE_UP);
+
+ /* complete root HUB interrupt endpoint */
+ avr32dci_root_intr(sc);
+ }
+ /*
+ * If resume and suspend is set at the same time we interpret
+ * that like RESUME. Resume is set when there is at least 3
+ * milliseconds of inactivity on the USB BUS.
+ */
+ if (status & AVR32_INT_WAKE_UP) {
+ DPRINTFN(5, "resume interrupt\n");
+
+ if (sc->sc_flags.status_suspend) {
+ /* update status bits */
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 1;
+
+ /* disable resume interrupt */
+ avr32dci_mod_ien(sc, AVR32_INT_DET_SUSPD |
+ AVR32_INT_ENDRESET, AVR32_INT_WAKE_UP);
+
+ /* complete root HUB interrupt endpoint */
+ avr32dci_root_intr(sc);
+ }
+ } else if (status & AVR32_INT_DET_SUSPD) {
+ DPRINTFN(5, "suspend interrupt\n");
+
+ if (!sc->sc_flags.status_suspend) {
+ /* update status bits */
+ sc->sc_flags.status_suspend = 1;
+ sc->sc_flags.change_suspend = 1;
+
+ /* disable suspend interrupt */
+ avr32dci_mod_ien(sc, AVR32_INT_WAKE_UP |
+ AVR32_INT_ENDRESET, AVR32_INT_DET_SUSPD);
+
+ /* complete root HUB interrupt endpoint */
+ avr32dci_root_intr(sc);
+ }
+ }
+ /* check for any endpoint interrupts */
+ if (status & -AVR32_INT_EPT_INT(0)) {
+ DPRINTFN(5, "real endpoint interrupt\n");
+
+ avr32dci_interrupt_poll(sc);
+ }
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+avr32dci_setup_standard_chain_sub(struct avr32dci_std_temp *temp)
+{
+ struct avr32dci_td *td;
+
+ /* get current Transfer Descriptor */
+ td = temp->td_next;
+ temp->td = td;
+
+ /* prepare for next TD */
+ temp->td_next = td->obj_next;
+
+ /* fill out the Transfer Descriptor */
+ td->func = temp->func;
+ td->pc = temp->pc;
+ td->offset = temp->offset;
+ td->remainder = temp->len;
+ td->error = 0;
+ td->did_stall = temp->did_stall;
+ td->short_pkt = temp->short_pkt;
+ td->alt_next = temp->setup_alt_next;
+}
+
+static void
+avr32dci_setup_standard_chain(struct usb_xfer *xfer)
+{
+ struct avr32dci_std_temp temp;
+ struct avr32dci_softc *sc;
+ struct avr32dci_td *td;
+ uint32_t x;
+ uint8_t ep_no;
+ uint8_t need_sync;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpointno),
+ xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
+
+ temp.max_frame_size = xfer->max_frame_size;
+
+ td = xfer->td_start[0];
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ /* setup temp */
+
+ temp.pc = NULL;
+ temp.td = NULL;
+ temp.td_next = xfer->td_start[0];
+ temp.offset = 0;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok ||
+ xfer->flags_int.isochronous_xfr;
+ temp.did_stall = !xfer->flags_int.control_stall;
+
+ sc = AVR32_BUS2SC(xfer->xroot->bus);
+ ep_no = (xfer->endpointno & UE_ADDR);
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ temp.func = &avr32dci_setup_rx;
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.short_pkt = temp.len ? 1 : 0;
+ /* check for last frame */
+ if (xfer->nframes == 1) {
+ /* no STATUS stage yet, SETUP is last */
+ if (xfer->flags_int.control_act)
+ temp.setup_alt_next = 0;
+ }
+ avr32dci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ if (x != xfer->nframes) {
+ if (xfer->endpointno & UE_DIR_IN) {
+ temp.func = &avr32dci_data_tx;
+ need_sync = 1;
+ } else {
+ temp.func = &avr32dci_data_rx;
+ need_sync = 0;
+ }
+
+ /* setup "pc" pointer */
+ temp.pc = xfer->frbuffers + x;
+ } else {
+ need_sync = 0;
+ }
+ while (x != xfer->nframes) {
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+
+ x++;
+
+ if (x == xfer->nframes) {
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_act) {
+ temp.setup_alt_next = 0;
+ }
+ } else {
+ temp.setup_alt_next = 0;
+ }
+ }
+ if (temp.len == 0) {
+ /* make sure that we send an USB packet */
+
+ temp.short_pkt = 0;
+
+ } else {
+ /* regular data transfer */
+
+ temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ avr32dci_setup_standard_chain_sub(&temp);
+
+ if (xfer->flags_int.isochronous_xfr) {
+ temp.offset += temp.len;
+ } else {
+ /* get next Page Cache pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr) {
+ /* always setup a valid "pc" pointer for status and sync */
+ temp.pc = xfer->frbuffers + 0;
+ temp.len = 0;
+ temp.short_pkt = 0;
+ temp.setup_alt_next = 0;
+
+ /* check if we need to sync */
+ if (need_sync) {
+ /* we need a SYNC point after TX */
+ temp.func = &avr32dci_data_tx_sync;
+ avr32dci_setup_standard_chain_sub(&temp);
+ }
+ /* check if we should append a status stage */
+ if (!xfer->flags_int.control_act) {
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ if (xfer->endpointno & UE_DIR_IN) {
+ temp.func = &avr32dci_data_rx;
+ need_sync = 0;
+ } else {
+ temp.func = &avr32dci_data_tx;
+ need_sync = 1;
+ }
+
+ avr32dci_setup_standard_chain_sub(&temp);
+ if (need_sync) {
+ /* we need a SYNC point after TX */
+ temp.func = &avr32dci_data_tx_sync;
+ avr32dci_setup_standard_chain_sub(&temp);
+ }
+ }
+ }
+ /* must have at least one frame! */
+ td = temp.td;
+ xfer->td_transfer_last = td;
+}
+
+static void
+avr32dci_timeout(void *arg)
+{
+ struct usb_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ avr32dci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+avr32dci_start_standard_chain(struct usb_xfer *xfer)
+{
+ DPRINTFN(9, "\n");
+
+ /* poll one time - will turn on interrupts */
+ if (avr32dci_xfer_do_fifo(xfer)) {
+ uint8_t ep_no = xfer->endpointno & UE_ADDR;
+ struct avr32dci_softc *sc = AVR32_BUS2SC(xfer->xroot->bus);
+
+ avr32dci_mod_ien(sc, AVR32_INT_EPT_INT(ep_no), 0);
+
+ /* put transfer on interrupt queue */
+ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usbd_transfer_timeout_ms(xfer,
+ &avr32dci_timeout, xfer->timeout);
+ }
+ }
+}
+
+static void
+avr32dci_root_intr(struct avr32dci_softc *sc)
+{
+ DPRINTFN(9, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* set port bit */
+ sc->sc_hub_idata[0] = 0x02; /* we only have one port */
+
+ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
+ sizeof(sc->sc_hub_idata));
+}
+
+static usb_error_t
+avr32dci_standard_done_sub(struct usb_xfer *xfer)
+{
+ struct avr32dci_td *td;
+ uint32_t len;
+ uint8_t error;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+
+ do {
+ len = td->remainder;
+
+ if (xfer->aframes != xfer->nframes) {
+ /*
+ * Verify the length and subtract
+ * the remainder from "frlengths[]":
+ */
+ if (len > xfer->frlengths[xfer->aframes]) {
+ td->error = 1;
+ } else {
+ xfer->frlengths[xfer->aframes] -= len;
+ }
+ }
+ /* Check for transfer error */
+ if (td->error) {
+ /* the transfer is finished */
+ error = 1;
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len > 0) {
+ if (xfer->flags_int.short_frames_ok ||
+ xfer->flags_int.isochronous_xfr) {
+ /* follow alt next */
+ if (td->alt_next) {
+ td = td->obj_next;
+ } else {
+ td = NULL;
+ }
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ error = 0;
+ break;
+ }
+ td = td->obj_next;
+
+ /* this USB frame is complete */
+ error = 0;
+ break;
+
+ } while (0);
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ return (error ?
+ USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+avr32dci_standard_done(struct usb_xfer *xfer)
+{
+ usb_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p pipe=%p transfer done\n",
+ xfer, xfer->endpoint);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ err = avr32dci_standard_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+ err = avr32dci_standard_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+ err = avr32dci_standard_done_sub(xfer);
+ }
+done:
+ avr32dci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * avr32dci_device_done
+ *
+ * NOTE: this function can be called more than one time on the
+ * same USB transfer!
+ *------------------------------------------------------------------------*/
+static void
+avr32dci_device_done(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct avr32dci_softc *sc = AVR32_BUS2SC(xfer->xroot->bus);
+ uint8_t ep_no;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTFN(9, "xfer=%p, pipe=%p, error=%d\n",
+ xfer, xfer->endpoint, error);
+
+ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
+ ep_no = (xfer->endpointno & UE_ADDR);
+
+ /* disable endpoint interrupt */
+ avr32dci_mod_ien(sc, 0, AVR32_INT_EPT_INT(ep_no));
+
+ DPRINTFN(15, "disabled interrupts!\n");
+ }
+ /* dequeue transfer and start next transfer */
+ usbd_transfer_done(xfer, error);
+}
+
+static void
+avr32dci_xfer_stall(struct usb_xfer *xfer)
+{
+ avr32dci_device_done(xfer, USB_ERR_STALLED);
+}
+
+static void
+avr32dci_set_stall(struct usb_device *udev,
+ struct usb_endpoint *pipe, uint8_t *did_stall)
+{
+ struct avr32dci_softc *sc;
+ uint8_t ep_no;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ DPRINTFN(5, "pipe=%p\n", pipe);
+
+ sc = AVR32_BUS2SC(udev->bus);
+ /* get endpoint number */
+ ep_no = (pipe->edesc->bEndpointAddress & UE_ADDR);
+ /* set stall */
+ AVR32_WRITE_4(sc, AVR32_EPTSETSTA(ep_no), AVR32_EPTSTA_FRCESTALL);
+}
+
+static void
+avr32dci_clear_stall_sub(struct avr32dci_softc *sc, uint8_t ep_no,
+ uint8_t ep_type, uint8_t ep_dir)
+{
+ const struct usb_hw_ep_profile *pf;
+ uint32_t temp;
+ uint32_t epsize;
+ uint8_t n;
+
+ if (ep_type == UE_CONTROL) {
+ /* clearing stall is not needed */
+ return;
+ }
+ /* set endpoint reset */
+ AVR32_WRITE_4(sc, AVR32_EPTRST, AVR32_EPTRST_MASK(ep_no));
+
+ /* set stall */
+ AVR32_WRITE_4(sc, AVR32_EPTSETSTA(ep_no), AVR32_EPTSTA_FRCESTALL);
+
+ /* reset data toggle */
+ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(ep_no), AVR32_EPTSTA_TOGGLESQ);
+
+ /* clear stall */
+ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(ep_no), AVR32_EPTSTA_FRCESTALL);
+
+ if (ep_type == UE_BULK) {
+ temp = AVR32_EPTCFG_TYPE_BULK;
+ } else if (ep_type == UE_INTERRUPT) {
+ temp = AVR32_EPTCFG_TYPE_INTR;
+ } else {
+ temp = AVR32_EPTCFG_TYPE_ISOC |
+ AVR32_EPTCFG_NB_TRANS(1);
+ }
+ if (ep_dir & UE_DIR_IN) {
+ temp |= AVR32_EPTCFG_EPDIR_IN;
+ }
+ avr32dci_get_hw_ep_profile(NULL, &pf, ep_no);
+
+ /* compute endpoint size (use maximum) */
+ epsize = pf->max_in_frame_size | pf->max_out_frame_size;
+ n = 0;
+ while ((epsize /= 2))
+ n++;
+ temp |= AVR32_EPTCFG_EPSIZE(n);
+
+ /* use the maximum number of banks supported */
+ if (ep_no < 1)
+ temp |= AVR32_EPTCFG_NBANK(1);
+ else if (ep_no < 3)
+ temp |= AVR32_EPTCFG_NBANK(2);
+ else
+ temp |= AVR32_EPTCFG_NBANK(3);
+
+ AVR32_WRITE_4(sc, AVR32_EPTCFG(ep_no), temp);
+
+ temp = AVR32_READ_4(sc, AVR32_EPTCFG(ep_no));
+
+ if (!(temp & AVR32_EPTCFG_EPT_MAPD)) {
+ device_printf(sc->sc_bus.bdev, "Chip rejected configuration\n");
+ } else {
+ AVR32_WRITE_4(sc, AVR32_EPTCTLENB(ep_no),
+ AVR32_EPTCTL_EPT_ENABL);
+ }
+}
+
+static void
+avr32dci_clear_stall(struct usb_device *udev, struct usb_endpoint *pipe)
+{
+ struct avr32dci_softc *sc;
+ struct usb_endpoint_descriptor *ed;
+
+ DPRINTFN(5, "pipe=%p\n", pipe);
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ /* check mode */
+ if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ /* get softc */
+ sc = AVR32_BUS2SC(udev->bus);
+
+ /* get endpoint descriptor */
+ ed = pipe->edesc;
+
+ /* reset endpoint */
+ avr32dci_clear_stall_sub(sc,
+ (ed->bEndpointAddress & UE_ADDR),
+ (ed->bmAttributes & UE_XFERTYPE),
+ (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+}
+
+usb_error_t
+avr32dci_init(struct avr32dci_softc *sc)
+{
+ uint8_t n;
+
+ DPRINTF("start\n");
+
+ /* set up the bus structure */
+ sc->sc_bus.usbrev = USB_REV_1_1;
+ sc->sc_bus.methods = &avr32dci_bus_methods;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* make sure USB is enabled */
+ avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_EN_USBA, 0);
+
+ /* turn on clocks */
+ (sc->sc_clocks_on) (&sc->sc_bus);
+
+ /* make sure device is re-enumerated */
+ avr32dci_mod_ctrl(sc, AVR32_CTRL_DEV_DETACH, 0);
+
+ /* wait a little for things to stabilise */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 20);
+
+ /* disable interrupts */
+ avr32dci_mod_ien(sc, 0, 0xFFFFFFFF);
+
+ /* enable interrupts */
+ avr32dci_mod_ien(sc, AVR32_INT_DET_SUSPD |
+ AVR32_INT_ENDRESET, 0);
+
+ /* reset all endpoints */
+ AVR32_WRITE_4(sc, AVR32_EPTRST, (1 << AVR32_EP_MAX) - 1);
+
+ /* disable all endpoints */
+ for (n = 0; n != AVR32_EP_MAX; n++) {
+ /* disable endpoint */
+ AVR32_WRITE_4(sc, AVR32_EPTCTLDIS(n), AVR32_EPTCTL_EPT_ENABL);
+ }
+
+ /* turn off clocks */
+
+ avr32dci_clocks_off(sc);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch any lost interrupts */
+
+ avr32dci_do_poll(&sc->sc_bus);
+
+ return (0); /* success */
+}
+
+void
+avr32dci_uninit(struct avr32dci_softc *sc)
+{
+ uint8_t n;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* turn on clocks */
+ (sc->sc_clocks_on) (&sc->sc_bus);
+
+ /* disable interrupts */
+ avr32dci_mod_ien(sc, 0, 0xFFFFFFFF);
+
+ /* reset all endpoints */
+ AVR32_WRITE_4(sc, AVR32_EPTRST, (1 << AVR32_EP_MAX) - 1);
+
+ /* disable all endpoints */
+ for (n = 0; n != AVR32_EP_MAX; n++) {
+ /* disable endpoint */
+ AVR32_WRITE_4(sc, AVR32_EPTCTLDIS(n), AVR32_EPTCTL_EPT_ENABL);
+ }
+
+ sc->sc_flags.port_powered = 0;
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ avr32dci_pull_down(sc);
+ avr32dci_clocks_off(sc);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+avr32dci_suspend(struct avr32dci_softc *sc)
+{
+ /* TODO */
+}
+
+static void
+avr32dci_resume(struct avr32dci_softc *sc)
+{
+ /* TODO */
+}
+
+static void
+avr32dci_do_poll(struct usb_bus *bus)
+{
+ struct avr32dci_softc *sc = AVR32_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ avr32dci_interrupt_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * avr32dci bulk support
+ * avr32dci control support
+ * avr32dci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+avr32dci_device_non_isoc_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+avr32dci_device_non_isoc_close(struct usb_xfer *xfer)
+{
+ avr32dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+avr32dci_device_non_isoc_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+avr32dci_device_non_isoc_start(struct usb_xfer *xfer)
+{
+ /* setup TDs */
+ avr32dci_setup_standard_chain(xfer);
+ avr32dci_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods avr32dci_device_non_isoc_methods =
+{
+ .open = avr32dci_device_non_isoc_open,
+ .close = avr32dci_device_non_isoc_close,
+ .enter = avr32dci_device_non_isoc_enter,
+ .start = avr32dci_device_non_isoc_start,
+};
+
+/*------------------------------------------------------------------------*
+ * avr32dci full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+avr32dci_device_isoc_fs_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+avr32dci_device_isoc_fs_close(struct usb_xfer *xfer)
+{
+ avr32dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+avr32dci_device_isoc_fs_enter(struct usb_xfer *xfer)
+{
+ struct avr32dci_softc *sc = AVR32_BUS2SC(xfer->xroot->bus);
+ uint32_t nframes;
+ uint8_t ep_no;
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->endpoint->isoc_next, xfer->nframes);
+
+ /* get the current frame index */
+ ep_no = xfer->endpointno & UE_ADDR;
+ nframes = (AVR32_READ_4(sc, AVR32_FNUM) / 8);
+
+ if (usbd_xfer_get_isochronous_start_frame(
+ xfer, nframes, 0, 1, AVR32_FRAME_MASK, NULL))
+ DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
+
+ /* setup TDs */
+ avr32dci_setup_standard_chain(xfer);
+}
+
+static void
+avr32dci_device_isoc_fs_start(struct usb_xfer *xfer)
+{
+ /* start TD chain */
+ avr32dci_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods avr32dci_device_isoc_fs_methods =
+{
+ .open = avr32dci_device_isoc_fs_open,
+ .close = avr32dci_device_isoc_fs_close,
+ .enter = avr32dci_device_isoc_fs_enter,
+ .start = avr32dci_device_isoc_fs_start,
+};
+
+/*------------------------------------------------------------------------*
+ * avr32dci root control support
+ *------------------------------------------------------------------------*
+ * Simulate a hardware HUB by handling all the necessary requests.
+ *------------------------------------------------------------------------*/
+
+static const struct usb_device_descriptor avr32dci_devd = {
+ .bLength = sizeof(struct usb_device_descriptor),
+ .bDescriptorType = UDESC_DEVICE,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_HSHUBSTT,
+ .bMaxPacketSize = 64,
+ .bcdDevice = {0x00, 0x01},
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .bNumConfigurations = 1,
+};
+
+static const struct usb_device_qualifier avr32dci_odevd = {
+ .bLength = sizeof(struct usb_device_qualifier),
+ .bDescriptorType = UDESC_DEVICE_QUALIFIER,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_FSHUB,
+ .bMaxPacketSize0 = 0,
+ .bNumConfigurations = 0,
+};
+
+static const struct avr32dci_config_desc avr32dci_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(avr32dci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0,
+ },
+ .ifcd = {
+ .bLength = sizeof(struct usb_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = 0,
+ },
+ .endpd = {
+ .bLength = sizeof(struct usb_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = (UE_DIR_IN | AVR32_INTR_ENDPT),
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8,
+ .bInterval = 255,
+ },
+};
+#define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) }
+
+static const struct usb_hub_descriptor_min avr32dci_hubd = {
+ .bDescLength = sizeof(avr32dci_hubd),
+ .bDescriptorType = UDESC_HUB,
+ .bNbrPorts = 1,
+ HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)),
+ .bPwrOn2PwrGood = 50,
+ .bHubContrCurrent = 0,
+ .DeviceRemovable = {0}, /* port is removable */
+};
+
+#define STRING_VENDOR \
+ "A\0V\0R\0003\0002"
+
+#define STRING_PRODUCT \
+ "D\0C\0I\0 \0R\0o\0o\0t\0 \0H\0U\0B"
+
+USB_MAKE_STRING_DESC(STRING_VENDOR, avr32dci_vendor);
+USB_MAKE_STRING_DESC(STRING_PRODUCT, avr32dci_product);
+
+static usb_error_t
+avr32dci_roothub_exec(struct usb_device *udev,
+ struct usb_device_request *req, const void **pptr, uint16_t *plength)
+{
+ struct avr32dci_softc *sc = AVR32_BUS2SC(udev->bus);
+ const void *ptr;
+ uint16_t len;
+ uint16_t value;
+ uint16_t index;
+ uint32_t temp;
+ usb_error_t err;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* buffer reset */
+ ptr = (const void *)&sc->sc_hub_temp;
+ len = 0;
+ err = 0;
+
+ value = UGETW(req->wValue);
+ index = UGETW(req->wIndex);
+
+ /* demultiplex the control request */
+
+ switch (req->bmRequestType) {
+ case UT_READ_DEVICE:
+ switch (req->bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_descriptor;
+ case UR_GET_CONFIG:
+ goto tr_handle_get_config;
+ case UR_GET_STATUS:
+ goto tr_handle_get_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_DEVICE:
+ switch (req->bRequest) {
+ case UR_SET_ADDRESS:
+ goto tr_handle_set_address;
+ case UR_SET_CONFIG:
+ goto tr_handle_set_config;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_DESCRIPTOR:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_ENDPOINT:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ switch (UGETW(req->wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_clear_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_clear_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SET_FEATURE:
+ switch (UGETW(req->wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_set_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_set_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SYNCH_FRAME:
+ goto tr_valid; /* nop */
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_ENDPOINT:
+ switch (req->bRequest) {
+ case UR_GET_STATUS:
+ goto tr_handle_get_ep_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_INTERFACE:
+ switch (req->bRequest) {
+ case UR_SET_INTERFACE:
+ goto tr_handle_set_interface;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_INTERFACE:
+ switch (req->bRequest) {
+ case UR_GET_INTERFACE:
+ goto tr_handle_get_interface;
+ case UR_GET_STATUS:
+ goto tr_handle_get_iface_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_INTERFACE:
+ case UT_WRITE_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_READ_CLASS_INTERFACE:
+ case UT_READ_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_WRITE_CLASS_DEVICE:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_valid;
+ case UR_SET_DESCRIPTOR:
+ case UR_SET_FEATURE:
+ break;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_OTHER:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_handle_clear_port_feature;
+ case UR_SET_FEATURE:
+ goto tr_handle_set_port_feature;
+ case UR_CLEAR_TT_BUFFER:
+ case UR_RESET_TT:
+ case UR_STOP_TT:
+ goto tr_valid;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_OTHER:
+ switch (req->bRequest) {
+ case UR_GET_TT_STATE:
+ goto tr_handle_get_tt_state;
+ case UR_GET_STATUS:
+ goto tr_handle_get_port_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_DEVICE:
+ switch (req->bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_class_descriptor;
+ case UR_GET_STATUS:
+ goto tr_handle_get_class_status;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_valid;
+
+tr_handle_get_descriptor:
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ len = sizeof(avr32dci_devd);
+ ptr = (const void *)&avr32dci_devd;
+ goto tr_valid;
+ case UDESC_DEVICE_QUALIFIER:
+ if (value & 0xff)
+ goto tr_stalled;
+ len = sizeof(avr32dci_odevd);
+ ptr = (const void *)&avr32dci_odevd;
+ goto tr_valid;
+ case UDESC_CONFIG:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ len = sizeof(avr32dci_confd);
+ ptr = (const void *)&avr32dci_confd;
+ goto tr_valid;
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ len = sizeof(usb_string_lang_en);
+ ptr = (const void *)&usb_string_lang_en;
+ goto tr_valid;
+
+ case 1: /* Vendor */
+ len = sizeof(avr32dci_vendor);
+ ptr = (const void *)&avr32dci_vendor;
+ goto tr_valid;
+
+ case 2: /* Product */
+ len = sizeof(avr32dci_product);
+ ptr = (const void *)&avr32dci_product;
+ goto tr_valid;
+ default:
+ break;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_stalled;
+
+tr_handle_get_config:
+ len = 1;
+ sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+ goto tr_valid;
+
+tr_handle_get_status:
+ len = 2;
+ USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED);
+ goto tr_valid;
+
+tr_handle_set_address:
+ if (value & 0xFF00) {
+ goto tr_stalled;
+ }
+ sc->sc_rt_addr = value;
+ goto tr_valid;
+
+tr_handle_set_config:
+ if (value >= 2) {
+ goto tr_stalled;
+ }
+ sc->sc_conf = value;
+ goto tr_valid;
+
+tr_handle_get_interface:
+ len = 1;
+ sc->sc_hub_temp.wValue[0] = 0;
+ goto tr_valid;
+
+tr_handle_get_tt_state:
+tr_handle_get_class_status:
+tr_handle_get_iface_status:
+tr_handle_get_ep_status:
+ len = 2;
+ USETW(sc->sc_hub_temp.wValue, 0);
+ goto tr_valid;
+
+tr_handle_set_halt:
+tr_handle_set_interface:
+tr_handle_set_wakeup:
+tr_handle_clear_wakeup:
+tr_handle_clear_halt:
+ goto tr_valid;
+
+tr_handle_clear_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
+
+ switch (value) {
+ case UHF_PORT_SUSPEND:
+ avr32dci_wakeup_peer(sc);
+ break;
+
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 0;
+ break;
+
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ case UHF_C_PORT_ENABLE:
+ case UHF_C_PORT_OVER_CURRENT:
+ case UHF_C_PORT_RESET:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 0;
+ avr32dci_pull_down(sc);
+ avr32dci_clocks_off(sc);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ /* clear connect change flag */
+ sc->sc_flags.change_connect = 0;
+
+ if (!sc->sc_flags.status_bus_reset) {
+ /* we are not connected */
+ break;
+ }
+ /* configure the control endpoint */
+ /* set endpoint reset */
+ AVR32_WRITE_4(sc, AVR32_EPTRST, AVR32_EPTRST_MASK(0));
+
+ /* set stall */
+ AVR32_WRITE_4(sc, AVR32_EPTSETSTA(0), AVR32_EPTSTA_FRCESTALL);
+
+ /* reset data toggle */
+ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(0), AVR32_EPTSTA_TOGGLESQ);
+
+ /* clear stall */
+ AVR32_WRITE_4(sc, AVR32_EPTCLRSTA(0), AVR32_EPTSTA_FRCESTALL);
+
+ /* configure */
+ AVR32_WRITE_4(sc, AVR32_EPTCFG(0), AVR32_EPTCFG_TYPE_CTRL |
+ AVR32_EPTCFG_NBANK(1) | AVR32_EPTCFG_EPSIZE(6));
+
+ temp = AVR32_READ_4(sc, AVR32_EPTCFG(0));
+
+ if (!(temp & AVR32_EPTCFG_EPT_MAPD)) {
+ device_printf(sc->sc_bus.bdev,
+ "Chip rejected configuration\n");
+ } else {
+ AVR32_WRITE_4(sc, AVR32_EPTCTLENB(0),
+ AVR32_EPTCTL_EPT_ENABL);
+ }
+ break;
+ case UHF_C_PORT_SUSPEND:
+ sc->sc_flags.change_suspend = 0;
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_set_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(9, "UR_SET_PORT_FEATURE\n");
+
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 1;
+ break;
+ case UHF_PORT_SUSPEND:
+ case UHF_PORT_RESET:
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 1;
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_get_port_status:
+
+ DPRINTFN(9, "UR_GET_PORT_STATUS\n");
+
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ if (sc->sc_flags.status_vbus) {
+ avr32dci_clocks_on(sc);
+ avr32dci_pull_up(sc);
+ } else {
+ avr32dci_pull_down(sc);
+ avr32dci_clocks_off(sc);
+ }
+
+ /* Select Device Side Mode */
+
+ value = UPS_PORT_MODE_DEVICE;
+
+ /* Check for High Speed */
+ if (AVR32_READ_4(sc, AVR32_INTSTA) & AVR32_INT_SPEED)
+ value |= UPS_HIGH_SPEED;
+
+ if (sc->sc_flags.port_powered) {
+ value |= UPS_PORT_POWER;
+ }
+ if (sc->sc_flags.port_enabled) {
+ value |= UPS_PORT_ENABLED;
+ }
+ if (sc->sc_flags.status_vbus &&
+ sc->sc_flags.status_bus_reset) {
+ value |= UPS_CURRENT_CONNECT_STATUS;
+ }
+ if (sc->sc_flags.status_suspend) {
+ value |= UPS_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortStatus, value);
+
+ value = 0;
+
+ if (sc->sc_flags.change_connect) {
+ value |= UPS_C_CONNECT_STATUS;
+ }
+ if (sc->sc_flags.change_suspend) {
+ value |= UPS_C_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortChange, value);
+ len = sizeof(sc->sc_hub_temp.ps);
+ goto tr_valid;
+
+tr_handle_get_class_descriptor:
+ if (value & 0xFF) {
+ goto tr_stalled;
+ }
+ ptr = (const void *)&avr32dci_hubd;
+ len = sizeof(avr32dci_hubd);
+ goto tr_valid;
+
+tr_stalled:
+ err = USB_ERR_STALLED;
+tr_valid:
+done:
+ *plength = len;
+ *pptr = ptr;
+ return (err);
+}
+
+static void
+avr32dci_xfer_setup(struct usb_setup_params *parm)
+{
+ const struct usb_hw_ep_profile *pf;
+ struct avr32dci_softc *sc;
+ struct usb_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t n;
+ uint8_t ep_no;
+
+ sc = AVR32_BUS2SC(parm->udev->bus);
+ xfer = parm->curr_xfer;
+
+ /*
+ * NOTE: This driver does not use any of the parameters that
+ * are computed from the following values. Just set some
+ * reasonable dummies:
+ */
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x400;
+
+ usbd_transfer_setup_sub(parm);
+
+ /*
+ * compute maximum number of TDs
+ */
+ if ((xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL) {
+ ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */
+ + 1 /* SYNC 2 */ ;
+ } else {
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+ }
+
+ /*
+ * check if "usbd_transfer_setup_sub" set an error
+ */
+ if (parm->err)
+ return;
+
+ /*
+ * allocate transfer descriptors
+ */
+ last_obj = NULL;
+
+ /*
+ * get profile stuff
+ */
+ ep_no = xfer->endpointno & UE_ADDR;
+ avr32dci_get_hw_ep_profile(parm->udev, &pf, ep_no);
+
+ if (pf == NULL) {
+ /* should not happen */
+ parm->err = USB_ERR_INVAL;
+ return;
+ }
+ /* align data */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ for (n = 0; n != ntd; n++) {
+ struct avr32dci_td *td;
+
+ if (parm->buf) {
+ uint32_t temp;
+
+ td = USB_ADD_BYTES(parm->buf, parm->size[0]);
+
+ /* init TD */
+ td->max_packet_size = xfer->max_packet_size;
+ td->ep_no = ep_no;
+ temp = pf->max_in_frame_size | pf->max_out_frame_size;
+ td->bank_shift = 0;
+ while ((temp /= 2))
+ td->bank_shift++;
+ if (pf->support_multi_buffer) {
+ td->support_multi_buffer = 1;
+ }
+ td->obj_next = last_obj;
+
+ last_obj = td;
+ }
+ parm->size[0] += sizeof(*td);
+ }
+
+ xfer->td_start[0] = last_obj;
+}
+
+static void
+avr32dci_xfer_unsetup(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+avr32dci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
+ struct usb_endpoint *pipe)
+{
+ struct avr32dci_softc *sc = AVR32_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "pipe=%p, addr=%d, endpt=%d, mode=%d (%d,%d)\n",
+ pipe, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb_mode,
+ sc->sc_rt_addr, udev->device_index);
+
+ if (udev->device_index != sc->sc_rt_addr) {
+ if ((udev->speed != USB_SPEED_FULL) &&
+ (udev->speed != USB_SPEED_HIGH)) {
+ /* not supported */
+ return;
+ }
+ if ((edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS)
+ pipe->methods = &avr32dci_device_isoc_fs_methods;
+ else
+ pipe->methods = &avr32dci_device_non_isoc_methods;
+ }
+}
+
+static void
+avr32dci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state)
+{
+ struct avr32dci_softc *sc = AVR32_BUS2SC(bus);
+
+ switch (state) {
+ case USB_HW_POWER_SUSPEND:
+ avr32dci_suspend(sc);
+ break;
+ case USB_HW_POWER_SHUTDOWN:
+ avr32dci_uninit(sc);
+ break;
+ case USB_HW_POWER_RESUME:
+ avr32dci_resume(sc);
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct usb_bus_methods avr32dci_bus_methods =
+{
+ .endpoint_init = &avr32dci_ep_init,
+ .xfer_setup = &avr32dci_xfer_setup,
+ .xfer_unsetup = &avr32dci_xfer_unsetup,
+ .get_hw_ep_profile = &avr32dci_get_hw_ep_profile,
+ .xfer_stall = &avr32dci_xfer_stall,
+ .set_stall = &avr32dci_set_stall,
+ .clear_stall = &avr32dci_clear_stall,
+ .roothub_exec = &avr32dci_roothub_exec,
+ .xfer_poll = &avr32dci_do_poll,
+ .set_hw_power_sleep = &avr32dci_set_hw_power_sleep,
+};
diff --git a/sys/dev/usb/controller/avr32dci.h b/sys/dev/usb/controller/avr32dci.h
new file mode 100644
index 000000000000..bade769c65a5
--- /dev/null
+++ b/sys/dev/usb/controller/avr32dci.h
@@ -0,0 +1,254 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2009 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _AVR32DCI_H_
+#define _AVR32DCI_H_
+
+#define AVR32_MAX_DEVICES (USB_MIN_DEVICES + 1)
+
+/* Register definitions */
+
+#define AVR32_CTRL 0x00 /* Control */
+#define AVR32_CTRL_DEV_ADDR 0x7F
+#define AVR32_CTRL_DEV_FADDR_EN 0x80
+#define AVR32_CTRL_DEV_EN_USBA 0x100
+#define AVR32_CTRL_DEV_DETACH 0x200
+#define AVR32_CTRL_DEV_REWAKEUP 0x400
+
+#define AVR32_FNUM 0x04 /* Frame Number */
+#define AVR32_FNUM_MASK 0x3FFF
+#define AVR32_FRAME_MASK 0x7FF
+
+/* 0x08 - 0x0C Reserved */
+#define AVR32_IEN 0x10 /* Interrupt Enable */
+#define AVR32_INTSTA 0x14 /* Interrupt Status */
+#define AVR32_CLRINT 0x18 /* Clear Interrupt */
+
+#define AVR32_INT_SPEED 0x00000001 /* set if High Speed else Full Speed */
+#define AVR32_INT_DET_SUSPD 0x00000002
+#define AVR32_INT_MICRO_SOF 0x00000004
+#define AVR32_INT_INT_SOF 0x00000008
+#define AVR32_INT_ENDRESET 0x00000010
+#define AVR32_INT_WAKE_UP 0x00000020
+#define AVR32_INT_ENDOFRSM 0x00000040
+#define AVR32_INT_UPSTR_RES 0x00000080
+#define AVR32_INT_EPT_INT(n) (0x00000100 << (n))
+#define AVR32_INT_DMA_INT(n) (0x01000000 << (n))
+
+#define AVR32_EPTRST 0x1C /* Endpoints Reset */
+#define AVR32_EPTRST_MASK(n) (0x00000001 << (n))
+
+/* 0x20 - 0xCC Reserved */
+#define AVR32_TSTSOFCNT 0xD0 /* Test SOF Counter */
+#define AVR32_TSTCNTA 0xD4 /* Test A Counter */
+#define AVR32_TSTCNTB 0xD8 /* Test B Counter */
+#define AVR32_TSTMODEREG 0xDC /* Test Mode */
+#define AVR32_TST 0xE0 /* Test */
+#define AVR32_TST_NORMAL 0x00000000
+#define AVR32_TST_HS_ONLY 0x00000002
+#define AVR32_TST_FS_ONLY 0x00000003
+
+/* 0xE4 - 0xE8 Reserved */
+#define AVR32_IPPADDRSIZE 0xEC /* PADDRSIZE */
+#define AVR32_IPNAME1 0xF0 /* Name1 */
+#define AVR32_IPNAME2 0xF4 /* Name2 */
+#define AVR32_IPFEATURES 0xF8 /* Features */
+#define AVR32_IPFEATURES_NEP(x) (((x) & 0xF) ? ((x) & 0xF) : 0x10)
+
+#define AVR32_IPVERSION 0xFC /* IP Version */
+
+#define _A(base,n) ((base) + (0x20*(n)))
+#define AVR32_EPTCFG(n) _A(0x100, n) /* Endpoint Configuration */
+#define AVR32_EPTCFG_EPSIZE(n) ((n)-3) /* power of two */
+#define AVR32_EPTCFG_EPDIR_OUT 0x00000000
+#define AVR32_EPTCFG_EPDIR_IN 0x00000008
+#define AVR32_EPTCFG_TYPE_CTRL 0x00000000
+#define AVR32_EPTCFG_TYPE_ISOC 0x00000100
+#define AVR32_EPTCFG_TYPE_BULK 0x00000200
+#define AVR32_EPTCFG_TYPE_INTR 0x00000300
+#define AVR32_EPTCFG_NBANK(n) (0x00000400*(n))
+#define AVR32_EPTCFG_NB_TRANS(n) (0x00001000*(n))
+#define AVR32_EPTCFG_EPT_MAPD 0x80000000
+
+#define AVR32_EPTCTLENB(n) _A(0x104, n) /* Endpoint Control Enable */
+#define AVR32_EPTCTLDIS(n) _A(0x108, n) /* Endpoint Control Disable */
+#define AVR32_EPTCTL(n) _A(0x10C, n) /* Endpoint Control */
+#define AVR32_EPTCTL_EPT_ENABL 0x00000001
+#define AVR32_EPTCTL_AUTO_VALID 0x00000002
+#define AVR32_EPTCTL_INTDIS_DMA 0x00000008
+#define AVR32_EPTCTL_NYET_DIS 0x00000010
+#define AVR32_EPTCTL_DATAX_RX 0x00000040
+#define AVR32_EPTCTL_MDATA_RX 0x00000080
+#define AVR32_EPTCTL_ERR_OVFLW 0x00000100
+#define AVR32_EPTCTL_RX_BK_RDY 0x00000200
+#define AVR32_EPTCTL_TX_COMPLT 0x00000400
+#define AVR32_EPTCTL_TX_PK_RDY 0x00000800
+#define AVR32_EPTCTL_RX_SETUP 0x00001000
+#define AVR32_EPTCTL_STALL_SNT 0x00002000
+#define AVR32_EPTCTL_NAK_IN 0x00004000
+#define AVR32_EPTCTL_NAK_OUT 0x00008000
+#define AVR32_EPTCTL_BUSY_BANK 0x00040000
+#define AVR32_EPTCTL_SHORT_PCKT 0x80000000
+
+/* 0x110 Reserved */
+#define AVR32_EPTSETSTA(n) _A(0x114, n) /* Endpoint Set Status */
+#define AVR32_EPTCLRSTA(n) _A(0x118, n) /* Endpoint Clear Status */
+#define AVR32_EPTSTA(n) _A(0x11C, n) /* Endpoint Status */
+#define AVR32_EPTSTA_FRCESTALL 0x00000020
+#define AVR32_EPTSTA_TOGGLESQ_STA(x) (((x) & 0xC0) >> 6)
+#define AVR32_EPTSTA_TOGGLESQ 0x00000040
+#define AVR32_EPTSTA_ERR_OVFLW 0x00000100
+#define AVR32_EPTSTA_RX_BK_RDY 0x00000200
+#define AVR32_EPTSTA_TX_COMPLT 0x00000400
+#define AVR32_EPTSTA_TX_PK_RDY 0x00000800
+#define AVR32_EPTSTA_RX_SETUP 0x00001000
+#define AVR32_EPTSTA_STALL_SNT 0x00002000
+#define AVR32_EPTSTA_NAK_IN 0x00004000
+#define AVR32_EPTSTA_NAK_OUT 0x00008000
+#define AVR32_EPTSTA_CURRENT_BANK(x) (((x) & 0x00030000) >> 16)
+#define AVR32_EPTSTA_BUSY_BANK_STA(x) (((x) & 0x000C0000) >> 18)
+#define AVR32_EPTSTA_BYTE_COUNT(x) (((x) & 0x7FF00000) >> 20)
+#define AVR32_EPTSTA_SHRT_PCKT 0x80000000
+
+/* 0x300 - 0x30C Reserved */
+#define AVR32_DMANXTDSC 0x310 /* DMA Next Descriptor Address */
+#define AVR32_DMAADDRESS 0x314 /* DMA Channel Address */
+
+#define AVR32_READ_4(sc, reg) \
+ bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg)
+
+#define AVR32_WRITE_4(sc, reg, data) \
+ bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data)
+
+#define AVR32_WRITE_MULTI_4(sc, reg, ptr, len) \
+ bus_space_write_multi_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, ptr, len)
+
+#define AVR32_READ_MULTI_4(sc, reg, ptr, len) \
+ bus_space_read_multi_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, ptr, len)
+
+/*
+ * Maximum number of endpoints supported:
+ */
+#define AVR32_EP_MAX 7
+
+struct avr32dci_td;
+
+typedef uint8_t (avr32dci_cmd_t)(struct avr32dci_td *td);
+typedef void (avr32dci_clocks_t)(struct usb_bus *);
+
+struct avr32dci_td {
+ struct avr32dci_td *obj_next;
+ avr32dci_cmd_t *func;
+ struct usb_page_cache *pc;
+ uint32_t offset;
+ uint32_t remainder;
+ uint16_t max_packet_size;
+ uint8_t bank_shift;
+ uint8_t error:1;
+ uint8_t alt_next:1;
+ uint8_t short_pkt:1;
+ uint8_t support_multi_buffer:1;
+ uint8_t did_stall:1;
+ uint8_t ep_no:3;
+};
+
+struct avr32dci_std_temp {
+ avr32dci_cmd_t *func;
+ struct usb_page_cache *pc;
+ struct avr32dci_td *td;
+ struct avr32dci_td *td_next;
+ uint32_t len;
+ uint32_t offset;
+ uint16_t max_frame_size;
+ uint8_t bank_shift;
+ uint8_t short_pkt;
+ /*
+ * short_pkt = 0: transfer should be short terminated
+ * short_pkt = 1: transfer should not be short terminated
+ */
+ uint8_t setup_alt_next;
+ uint8_t did_stall;
+};
+
+struct avr32dci_config_desc {
+ struct usb_config_descriptor confd;
+ struct usb_interface_descriptor ifcd;
+ struct usb_endpoint_descriptor endpd;
+} __packed;
+
+union avr32dci_hub_temp {
+ uWord wValue;
+ struct usb_port_status ps;
+};
+
+struct avr32dci_flags {
+ uint8_t change_connect:1;
+ uint8_t change_suspend:1;
+ uint8_t status_suspend:1; /* set if suspended */
+ uint8_t status_vbus:1; /* set if present */
+ uint8_t status_bus_reset:1; /* set if reset complete */
+ uint8_t remote_wakeup:1;
+ uint8_t self_powered:1;
+ uint8_t clocks_off:1;
+ uint8_t port_powered:1;
+ uint8_t port_enabled:1;
+ uint8_t d_pulled_up:1;
+};
+
+struct avr32dci_softc {
+ struct usb_bus sc_bus;
+ union avr32dci_hub_temp sc_hub_temp;
+
+ /* must be set by by the bus interface layer */
+ avr32dci_clocks_t *sc_clocks_on;
+ avr32dci_clocks_t *sc_clocks_off;
+
+ struct usb_device *sc_devices[AVR32_MAX_DEVICES];
+ struct resource *sc_irq_res;
+ void *sc_intr_hdl;
+ struct resource *sc_io_res;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+ uint8_t *physdata;
+
+ uint8_t sc_rt_addr; /* root hub address */
+ uint8_t sc_dv_addr; /* device address */
+ uint8_t sc_conf; /* root hub config */
+
+ uint8_t sc_hub_idata[1];
+
+ struct avr32dci_flags sc_flags;
+};
+
+/* prototypes */
+
+usb_error_t avr32dci_init(struct avr32dci_softc *sc);
+void avr32dci_uninit(struct avr32dci_softc *sc);
+void avr32dci_interrupt(struct avr32dci_softc *sc);
+void avr32dci_vbus_interrupt(struct avr32dci_softc *sc, uint8_t is_on);
+
+#endif /* _AVR32DCI_H_ */
diff --git a/sys/dev/usb/controller/dwc3/aw_dwc3.c b/sys/dev/usb/controller/dwc3/aw_dwc3.c
new file mode 100644
index 000000000000..be941ca2148f
--- /dev/null
+++ b/sys/dev/usb/controller/dwc3/aw_dwc3.c
@@ -0,0 +1,142 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Emmanuel Vadot <manu@FreeBSD.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.
+ *
+ * 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.
+ */
+
+/*
+ * Rockchip DWC3 glue
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/gpio.h>
+#include <machine/bus.h>
+
+#include <dev/fdt/simplebus.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/ofw/ofw_subr.h>
+
+#include <dev/clk/clk.h>
+#include <dev/hwreset/hwreset.h>
+#include <dev/phy/phy_usb.h>
+
+static struct ofw_compat_data compat_data[] = {
+ { "allwinner,sun50i-h6-dwc3", 1 },
+ { NULL, 0 }
+};
+
+struct aw_dwc3_softc {
+ struct simplebus_softc sc;
+ device_t dev;
+ clk_t clk_bus;
+ hwreset_t rst_bus;
+};
+
+static int
+aw_dwc3_probe(device_t dev)
+{
+ phandle_t node;
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
+ return (ENXIO);
+
+ /* Binding says that we need a child node for the actual dwc3 controller */
+ node = ofw_bus_get_node(dev);
+ if (OF_child(node) <= 0)
+ return (ENXIO);
+
+ device_set_desc(dev, "Allwinner H6 DWC3");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+aw_dwc3_attach(device_t dev)
+{
+ struct aw_dwc3_softc *sc;
+ device_t cdev;
+ phandle_t node, child;
+ int err;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ node = ofw_bus_get_node(dev);
+
+ /* Enable the clocks */
+ if (clk_get_by_ofw_name(dev, 0, "bus", &sc->clk_bus) != 0) {
+ device_printf(dev, "Cannot get bus clock\n");
+ return (ENXIO);
+ }
+ err = clk_enable(sc->clk_bus);
+ if (err != 0) {
+ device_printf(dev, "Could not enable clock %s\n",
+ clk_get_name(sc->clk_bus));
+ return (ENXIO);
+ }
+
+ /* Put module out of reset */
+ if (hwreset_get_by_ofw_name(dev, node, "bus", &sc->rst_bus) == 0) {
+ if (hwreset_deassert(sc->rst_bus) != 0) {
+ device_printf(dev, "Cannot deassert reset\n");
+ return (ENXIO);
+ }
+ }
+
+ simplebus_init(dev, node);
+ if (simplebus_fill_ranges(node, &sc->sc) < 0) {
+ device_printf(dev, "could not get ranges\n");
+ return (ENXIO);
+ }
+
+ for (child = OF_child(node); child > 0; child = OF_peer(child)) {
+ cdev = simplebus_add_device(dev, child, 0, NULL, -1, NULL);
+ if (cdev != NULL)
+ device_probe_and_attach(cdev);
+ }
+
+ bus_attach_children(dev);
+ return (0);
+}
+
+static device_method_t aw_dwc3_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, aw_dwc3_probe),
+ DEVMETHOD(device_attach, aw_dwc3_attach),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(aw_dwc3, aw_dwc3_driver, aw_dwc3_methods,
+ sizeof(struct aw_dwc3_softc), simplebus_driver);
+DRIVER_MODULE(aw_dwc3, simplebus, aw_dwc3_driver, 0, 0);
diff --git a/sys/dev/usb/controller/dwc3/dwc3.c b/sys/dev/usb/controller/dwc3/dwc3.c
new file mode 100644
index 000000000000..39b5d3ae4cb1
--- /dev/null
+++ b/sys/dev/usb/controller/dwc3/dwc3.c
@@ -0,0 +1,619 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Emmanuel Vadot <manu@FreeBSD.Org>
+ * Copyright (c) 2021-2022 Bjoern A. Zeeb <bz@FreeBSD.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.
+ *
+ * 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 "opt_platform.h"
+#include "opt_acpi.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#ifdef FDT
+#include <sys/gpio.h>
+#endif
+
+#include <machine/bus.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/generic_xhci.h>
+#include <dev/usb/controller/xhci.h>
+#include <dev/usb/controller/dwc3/dwc3.h>
+
+#ifdef FDT
+#include <dev/fdt/simplebus.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/ofw/ofw_subr.h>
+
+#include <dev/clk/clk.h>
+#include <dev/phy/phy_usb.h>
+#endif
+
+#ifdef DEV_ACPI
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <dev/acpica/acpivar.h>
+#endif
+
+struct snps_dwc3_softc {
+ struct xhci_softc sc;
+ device_t dev;
+ struct resource * mem_res;
+ bus_space_tag_t bst;
+ bus_space_handle_t bsh;
+ uint32_t snpsid;
+ uint32_t snpsversion;
+ uint32_t snpsrevision;
+ uint32_t snpsversion_type;
+#ifdef FDT
+ clk_t clk_ref;
+ clk_t clk_suspend;
+ clk_t clk_bus;
+#endif
+};
+
+#define DWC3_WRITE(_sc, _off, _val) \
+ bus_space_write_4(_sc->bst, _sc->bsh, _off, _val)
+#define DWC3_READ(_sc, _off) \
+ bus_space_read_4(_sc->bst, _sc->bsh, _off)
+
+#define IS_DMA_32B 1
+
+static void
+xhci_interrupt_poll(void *_sc)
+{
+ struct xhci_softc *sc = _sc;
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ xhci_interrupt(sc);
+ USB_BUS_LOCK(&sc->sc_bus);
+ usb_callout_reset(&sc->sc_callout, 1, (void *)&xhci_interrupt_poll, sc);
+}
+
+static int
+snps_dwc3_attach_xhci(device_t dev)
+{
+ struct snps_dwc3_softc *snps_sc = device_get_softc(dev);
+ struct xhci_softc *sc = &snps_sc->sc;
+ int err = 0, rid = 0;
+
+ sc->sc_io_res = snps_sc->mem_res;
+ sc->sc_io_tag = snps_sc->bst;
+ sc->sc_io_hdl = snps_sc->bsh;
+ sc->sc_io_size = rman_get_size(snps_sc->mem_res);
+
+ sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (sc->sc_irq_res == NULL) {
+ device_printf(dev, "Failed to allocate IRQ\n");
+ return (ENXIO);
+ }
+
+ sc->sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY);
+ if (sc->sc_bus.bdev == NULL) {
+ device_printf(dev, "Failed to add USB device\n");
+ return (ENXIO);
+ }
+
+ device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
+
+ sprintf(sc->sc_vendor, "Synopsys");
+ device_set_desc(sc->sc_bus.bdev, "Synopsys");
+
+ if (xhci_use_polling() == 0) {
+ err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (driver_intr_t *)xhci_interrupt, sc, &sc->sc_intr_hdl);
+ if (err != 0) {
+ device_printf(dev, "Failed to setup IRQ, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ return (err);
+ }
+ }
+
+ err = xhci_init(sc, dev, IS_DMA_32B);
+ if (err != 0) {
+ device_printf(dev, "Failed to init XHCI, with error %d\n", err);
+ return (ENXIO);
+ }
+
+ usb_callout_init_mtx(&sc->sc_callout, &sc->sc_bus.bus_mtx, 0);
+
+ if (xhci_use_polling() != 0) {
+ device_printf(dev, "Interrupt polling at %dHz\n", hz);
+ USB_BUS_LOCK(&sc->sc_bus);
+ xhci_interrupt_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ }
+
+ err = xhci_start_controller(sc);
+ if (err != 0) {
+ device_printf(dev, "Failed to start XHCI controller, with error %d\n", err);
+ return (ENXIO);
+ }
+
+ device_printf(sc->sc_bus.bdev, "trying to attach\n");
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ if (err != 0) {
+ device_printf(dev, "Failed to initialize USB, with error %d\n", err);
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+#ifdef DWC3_DEBUG
+static void
+snsp_dwc3_dump_regs(struct snps_dwc3_softc *sc, const char *msg)
+{
+ struct xhci_softc *xsc;
+ uint32_t reg;
+
+ if (!bootverbose)
+ return;
+
+ device_printf(sc->dev, "%s: %s:\n", __func__, msg ? msg : "");
+
+ reg = DWC3_READ(sc, DWC3_GCTL);
+ device_printf(sc->dev, "GCTL: %#012x\n", reg);
+ reg = DWC3_READ(sc, DWC3_GUCTL);
+ device_printf(sc->dev, "GUCTL: %#012x\n", reg);
+ reg = DWC3_READ(sc, DWC3_GUCTL1);
+ device_printf(sc->dev, "GUCTL1: %#012x\n", reg);
+ reg = DWC3_READ(sc, DWC3_GUSB2PHYCFG0);
+ device_printf(sc->dev, "GUSB2PHYCFG0: %#012x\n", reg);
+ reg = DWC3_READ(sc, DWC3_GUSB3PIPECTL0);
+ device_printf(sc->dev, "GUSB3PIPECTL0: %#012x\n", reg);
+ reg = DWC3_READ(sc, DWC3_DCFG);
+ device_printf(sc->dev, "DCFG: %#012x\n", reg);
+
+ xsc = &sc->sc;
+ device_printf(sc->dev, "xhci quirks: %#012x\n", xsc->sc_quirks);
+}
+
+static void
+snps_dwc3_dump_ctrlparams(struct snps_dwc3_softc *sc)
+{
+ const bus_size_t offs[] = {
+ DWC3_GHWPARAMS0, DWC3_GHWPARAMS1, DWC3_GHWPARAMS2, DWC3_GHWPARAMS3,
+ DWC3_GHWPARAMS4, DWC3_GHWPARAMS5, DWC3_GHWPARAMS6, DWC3_GHWPARAMS7,
+ DWC3_GHWPARAMS8,
+ };
+ uint32_t reg;
+ int i;
+
+ for (i = 0; i < nitems(offs); i++) {
+ reg = DWC3_READ(sc, offs[i]);
+ if (bootverbose)
+ device_printf(sc->dev, "hwparams[%d]: %#012x\n", i, reg);
+ }
+}
+#endif
+
+static void
+snps_dwc3_reset(struct snps_dwc3_softc *sc)
+{
+ uint32_t gctl, ghwp0, phy2, phy3;
+
+ ghwp0 = DWC3_READ(sc, DWC3_GHWPARAMS0);
+
+ gctl = DWC3_READ(sc, DWC3_GCTL);
+ gctl |= DWC3_GCTL_CORESOFTRESET;
+ DWC3_WRITE(sc, DWC3_GCTL, gctl);
+
+ phy2 = DWC3_READ(sc, DWC3_GUSB2PHYCFG0);
+ phy2 |= DWC3_GUSB2PHYCFG0_PHYSOFTRST;
+ if ((ghwp0 & DWC3_GHWPARAMS0_MODE_MASK) ==
+ DWC3_GHWPARAMS0_MODE_DUALROLEDEVICE)
+ phy2 &= ~DWC3_GUSB2PHYCFG0_SUSPENDUSB20;
+ DWC3_WRITE(sc, DWC3_GUSB2PHYCFG0, phy2);
+
+ phy3 = DWC3_READ(sc, DWC3_GUSB3PIPECTL0);
+ phy3 |= DWC3_GUSB3PIPECTL0_PHYSOFTRST;
+ if ((ghwp0 & DWC3_GHWPARAMS0_MODE_MASK) ==
+ DWC3_GHWPARAMS0_MODE_DUALROLEDEVICE)
+ phy3 &= ~DWC3_GUSB3PIPECTL0_SUSPENDUSB3;
+ DWC3_WRITE(sc, DWC3_GUSB3PIPECTL0, phy3);
+
+ DELAY(1000);
+
+ phy2 &= ~DWC3_GUSB2PHYCFG0_PHYSOFTRST;
+ DWC3_WRITE(sc, DWC3_GUSB2PHYCFG0, phy2);
+
+ phy3 &= ~DWC3_GUSB3PIPECTL0_PHYSOFTRST;
+ DWC3_WRITE(sc, DWC3_GUSB3PIPECTL0, phy3);
+
+ gctl &= ~DWC3_GCTL_CORESOFTRESET;
+ DWC3_WRITE(sc, DWC3_GCTL, gctl);
+
+}
+
+static void
+snps_dwc3_configure_host(struct snps_dwc3_softc *sc)
+{
+ uint32_t reg;
+
+ reg = DWC3_READ(sc, DWC3_GCTL);
+ reg &= ~DWC3_GCTL_PRTCAPDIR_MASK;
+ reg |= DWC3_GCTL_PRTCAPDIR_HOST;
+ DWC3_WRITE(sc, DWC3_GCTL, reg);
+
+ /*
+ * Enable the Host IN Auto Retry feature, making the
+ * host respond with a non-terminating retry ACK.
+ * XXX If we ever support more than host mode this needs a dr_mode check.
+ */
+ reg = DWC3_READ(sc, DWC3_GUCTL);
+ reg |= DWC3_GUCTL_HOST_AUTO_RETRY;
+ DWC3_WRITE(sc, DWC3_GUCTL, reg);
+}
+
+#ifdef FDT
+static void
+snps_dwc3_configure_phy(struct snps_dwc3_softc *sc, phandle_t node)
+{
+ char *phy_type;
+ uint32_t reg;
+ int nphy_types;
+
+ phy_type = NULL;
+ nphy_types = OF_getprop_alloc(node, "phy_type", (void **)&phy_type);
+ if (nphy_types <= 0)
+ return;
+
+ reg = DWC3_READ(sc, DWC3_GUSB2PHYCFG0);
+ if (strncmp(phy_type, "utmi_wide", 9) == 0) {
+ reg &= ~(DWC3_GUSB2PHYCFG0_PHYIF | DWC3_GUSB2PHYCFG0_USBTRDTIM(0xf));
+ reg |= DWC3_GUSB2PHYCFG0_PHYIF |
+ DWC3_GUSB2PHYCFG0_USBTRDTIM(DWC3_GUSB2PHYCFG0_USBTRDTIM_16BITS);
+ } else {
+ reg &= ~(DWC3_GUSB2PHYCFG0_PHYIF | DWC3_GUSB2PHYCFG0_USBTRDTIM(0xf));
+ reg |= DWC3_GUSB2PHYCFG0_PHYIF |
+ DWC3_GUSB2PHYCFG0_USBTRDTIM(DWC3_GUSB2PHYCFG0_USBTRDTIM_8BITS);
+ }
+ DWC3_WRITE(sc, DWC3_GUSB2PHYCFG0, reg);
+ OF_prop_free(phy_type);
+}
+#endif
+
+static void
+snps_dwc3_do_quirks(struct snps_dwc3_softc *sc)
+{
+ struct xhci_softc *xsc;
+ uint32_t ghwp0, reg;
+
+ ghwp0 = DWC3_READ(sc, DWC3_GHWPARAMS0);
+ reg = DWC3_READ(sc, DWC3_GUSB2PHYCFG0);
+ if (device_has_property(sc->dev, "snps,dis-u2-freeclk-exists-quirk"))
+ reg &= ~DWC3_GUSB2PHYCFG0_U2_FREECLK_EXISTS;
+ else
+ reg |= DWC3_GUSB2PHYCFG0_U2_FREECLK_EXISTS;
+ if (device_has_property(sc->dev, "snps,dis_u2_susphy_quirk"))
+ reg &= ~DWC3_GUSB2PHYCFG0_SUSPENDUSB20;
+ else if ((ghwp0 & DWC3_GHWPARAMS0_MODE_MASK) ==
+ DWC3_GHWPARAMS0_MODE_DUALROLEDEVICE)
+ reg |= DWC3_GUSB2PHYCFG0_SUSPENDUSB20;
+ if (device_has_property(sc->dev, "snps,dis_enblslpm_quirk"))
+ reg &= ~DWC3_GUSB2PHYCFG0_ENBLSLPM;
+ else
+ reg |= DWC3_GUSB2PHYCFG0_ENBLSLPM;
+ DWC3_WRITE(sc, DWC3_GUSB2PHYCFG0, reg);
+
+ reg = DWC3_READ(sc, DWC3_GUCTL1);
+ if (device_has_property(sc->dev, "snps,dis-tx-ipgap-linecheck-quirk"))
+ reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS;
+ DWC3_WRITE(sc, DWC3_GUCTL1, reg);
+
+ reg = DWC3_READ(sc, DWC3_GUSB3PIPECTL0);
+ if (device_has_property(sc->dev, "snps,dis-del-phy-power-chg-quirk"))
+ reg &= ~DWC3_GUSB3PIPECTL0_DELAYP1TRANS;
+ if (device_has_property(sc->dev, "snps,dis_rxdet_inp3_quirk"))
+ reg |= DWC3_GUSB3PIPECTL0_DISRXDETINP3;
+ if (device_has_property(sc->dev, "snps,dis_u3_susphy_quirk"))
+ reg &= ~DWC3_GUSB3PIPECTL0_SUSPENDUSB3;
+ else if ((ghwp0 & DWC3_GHWPARAMS0_MODE_MASK) ==
+ DWC3_GHWPARAMS0_MODE_DUALROLEDEVICE)
+ reg |= DWC3_GUSB3PIPECTL0_SUSPENDUSB3;
+ DWC3_WRITE(sc, DWC3_GUSB3PIPECTL0, reg);
+
+ /* Port Disable does not work on <= 3.00a. Disable PORT_PED. */
+ if ((sc->snpsid & 0xffff) <= 0x300a) {
+ xsc = &sc->sc;
+ xsc->sc_quirks |= XHCI_QUIRK_DISABLE_PORT_PED;
+ }
+}
+
+static int
+snps_dwc3_probe_common(device_t dev)
+{
+ char dr_mode[16] = { 0 };
+ ssize_t s;
+
+ s = device_get_property(dev, "dr_mode", dr_mode, sizeof(dr_mode),
+ DEVICE_PROP_BUFFER);
+ if (s == -1) {
+ device_printf(dev, "Cannot determine dr_mode\n");
+ return (ENXIO);
+ }
+ if (strcmp(dr_mode, "host") != 0) {
+ device_printf(dev,
+ "Found dr_mode '%s' but only 'host' supported. s=%zd\n",
+ dr_mode, s);
+ return (ENXIO);
+ }
+
+ device_set_desc(dev, "Synopsys Designware DWC3");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+snps_dwc3_common_attach(device_t dev, bool is_fdt)
+{
+ struct snps_dwc3_softc *sc;
+#ifdef FDT
+ phandle_t node;
+ phy_t usb2_phy, usb3_phy;
+ uint32_t reg;
+#endif
+ int error, rid;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+
+ rid = 0;
+ sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->mem_res == NULL) {
+ device_printf(dev, "Failed to map memory\n");
+ return (ENXIO);
+ }
+ sc->bst = rman_get_bustag(sc->mem_res);
+ sc->bsh = rman_get_bushandle(sc->mem_res);
+
+ sc->snpsid = DWC3_READ(sc, DWC3_GSNPSID);
+ sc->snpsversion = DWC3_VERSION(sc->snpsid);
+ sc->snpsrevision = DWC3_REVISION(sc->snpsid);
+ if (sc->snpsversion == DWC3_1_IP_ID ||
+ sc->snpsversion == DWC3_2_IP_ID) {
+ sc->snpsrevision = DWC3_READ(sc, DWC3_1_VER_NUMBER);
+ sc->snpsversion_type = DWC3_READ(sc, DWC3_1_VER_TYPE);
+ }
+ if (bootverbose) {
+ switch (sc->snpsversion) {
+ case DWC3_IP_ID:
+ device_printf(sc->dev, "SNPS Version: DWC3 (%x %x)\n",
+ sc->snpsversion, sc->snpsrevision);
+ break;
+ case DWC3_1_IP_ID:
+ device_printf(sc->dev, "SNPS Version: DWC3.1 (%x %x %x)\n",
+ sc->snpsversion, sc->snpsrevision,
+ sc->snpsversion_type);
+ break;
+ case DWC3_2_IP_ID:
+ device_printf(sc->dev, "SNPS Version: DWC3.2 (%x %x %x)\n",
+ sc->snpsversion, sc->snpsrevision,
+ sc->snpsversion_type);
+ break;
+ }
+ }
+#ifdef DWC3_DEBUG
+ snps_dwc3_dump_ctrlparams(sc);
+#endif
+
+#ifdef FDT
+ if (!is_fdt)
+ goto skip_phys;
+
+ node = ofw_bus_get_node(dev);
+
+ /* Get the clocks if any */
+ if (ofw_bus_is_compatible(dev, "rockchip,rk3328-dwc3") == 1 ||
+ ofw_bus_is_compatible(dev, "rockchip,rk3568-dwc3") == 1) {
+ if (clk_get_by_ofw_name(dev, node, "ref_clk", &sc->clk_ref) != 0)
+ device_printf(dev, "Cannot get ref_clk\n");
+ if (clk_get_by_ofw_name(dev, node, "suspend_clk", &sc->clk_suspend) != 0)
+ device_printf(dev, "Cannot get suspend_clk\n");
+ if (clk_get_by_ofw_name(dev, node, "bus_clk", &sc->clk_bus) != 0)
+ device_printf(dev, "Cannot get bus_clk\n");
+ }
+
+ if (sc->clk_ref != NULL) {
+ if (clk_enable(sc->clk_ref) != 0)
+ device_printf(dev, "Cannot enable ref_clk\n");
+ }
+ if (sc->clk_suspend != NULL) {
+ if (clk_enable(sc->clk_suspend) != 0)
+ device_printf(dev, "Cannot enable suspend_clk\n");
+ }
+ if (sc->clk_bus != NULL) {
+ if (clk_enable(sc->clk_bus) != 0)
+ device_printf(dev, "Cannot enable bus_clk\n");
+ }
+
+ /* Get the phys */
+ usb2_phy = usb3_phy = NULL;
+ error = phy_get_by_ofw_name(dev, node, "usb2-phy", &usb2_phy);
+ if (error == 0 && usb2_phy != NULL)
+ phy_enable(usb2_phy);
+ error = phy_get_by_ofw_name(dev, node, "usb3-phy", &usb3_phy);
+ if (error == 0 && usb3_phy != NULL)
+ phy_enable(usb3_phy);
+ if (sc->snpsversion == DWC3_IP_ID) {
+ if (sc->snpsrevision >= 0x290A) {
+ uint32_t hwparams3;
+
+ hwparams3 = DWC3_READ(sc, DWC3_GHWPARAMS3);
+ if (DWC3_HWPARAMS3_SSPHY(hwparams3) == DWC3_HWPARAMS3_SSPHY_DISABLE) {
+ reg = DWC3_READ(sc, DWC3_GUCTL1);
+ if (bootverbose)
+ device_printf(dev, "Forcing USB2 clock only\n");
+ reg |= DWC3_GUCTL1_DEV_FORCE_20_CLK_FOR_30_CLK;
+ DWC3_WRITE(sc, DWC3_GUCTL1, reg);
+ }
+ }
+ }
+ snps_dwc3_configure_phy(sc, node);
+skip_phys:
+#endif
+
+ snps_dwc3_reset(sc);
+ snps_dwc3_configure_host(sc);
+ snps_dwc3_do_quirks(sc);
+
+#ifdef DWC3_DEBUG
+ snsp_dwc3_dump_regs(sc, "Pre XHCI init");
+#endif
+ error = snps_dwc3_attach_xhci(dev);
+#ifdef DWC3_DEBUG
+ snsp_dwc3_dump_regs(sc, "Post XHCI init");
+#endif
+
+#ifdef FDT
+ if (error) {
+ if (sc->clk_ref != NULL)
+ clk_disable(sc->clk_ref);
+ if (sc->clk_suspend != NULL)
+ clk_disable(sc->clk_suspend);
+ if (sc->clk_bus != NULL)
+ clk_disable(sc->clk_bus);
+ }
+#endif
+ return (error);
+}
+
+#ifdef FDT
+static struct ofw_compat_data compat_data[] = {
+ { "snps,dwc3", 1 },
+ { NULL, 0 }
+};
+
+static int
+snps_dwc3_fdt_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
+ return (ENXIO);
+
+ return (snps_dwc3_probe_common(dev));
+}
+
+static int
+snps_dwc3_fdt_attach(device_t dev)
+{
+
+ return (snps_dwc3_common_attach(dev, true));
+}
+
+static device_method_t snps_dwc3_fdt_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, snps_dwc3_fdt_probe),
+ DEVMETHOD(device_attach, snps_dwc3_fdt_attach),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(snps_dwc3_fdt, snps_dwc3_fdt_driver, snps_dwc3_fdt_methods,
+ sizeof(struct snps_dwc3_softc), generic_xhci_driver);
+
+DRIVER_MODULE(snps_dwc3_fdt, simplebus, snps_dwc3_fdt_driver, 0, 0);
+MODULE_DEPEND(snps_dwc3_fdt, xhci, 1, 1, 1);
+#endif
+
+#ifdef DEV_ACPI
+static char *dwc3_acpi_ids[] = {
+ "808622B7", /* This was an Intel PCI Vendor/Device ID used. */
+ "PNP0D10", /* The generic XHCI PNP ID needing extra probe checks. */
+ NULL
+};
+
+static int
+snps_dwc3_acpi_probe(device_t dev)
+{
+ char *match;
+ int error;
+
+ if (acpi_disabled("snps_dwc3"))
+ return (ENXIO);
+
+ error = ACPI_ID_PROBE(device_get_parent(dev), dev, dwc3_acpi_ids, &match);
+ if (error > 0)
+ return (ENXIO);
+
+ /*
+ * If we found the Generic XHCI PNP ID we can only attach if we have
+ * some other means to identify the device as dwc3.
+ */
+ if (strcmp(match, "PNP0D10") == 0) {
+ /* This is needed in SolidRun's HoneyComb. */
+ if (device_has_property(dev, "snps,dis_rxdet_inp3_quirk"))
+ goto is_dwc3;
+
+ return (ENXIO);
+ }
+
+is_dwc3:
+ return (snps_dwc3_probe_common(dev));
+}
+
+static int
+snps_dwc3_acpi_attach(device_t dev)
+{
+
+ return (snps_dwc3_common_attach(dev, false));
+}
+
+static device_method_t snps_dwc3_acpi_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, snps_dwc3_acpi_probe),
+ DEVMETHOD(device_attach, snps_dwc3_acpi_attach),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(snps_dwc3_acpi, snps_dwc3_acpi_driver, snps_dwc3_acpi_methods,
+ sizeof(struct snps_dwc3_softc), generic_xhci_driver);
+
+DRIVER_MODULE(snps_dwc3_acpi, acpi, snps_dwc3_acpi_driver, 0, 0);
+MODULE_DEPEND(snps_dwc3_acpi, usb, 1, 1, 1);
+#endif
diff --git a/sys/dev/usb/controller/dwc3/dwc3.h b/sys/dev/usb/controller/dwc3/dwc3.h
new file mode 100644
index 000000000000..a2063d8cff31
--- /dev/null
+++ b/sys/dev/usb/controller/dwc3/dwc3.h
@@ -0,0 +1,137 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Emmanuel Vadot <manu@FreeBSD.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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#ifndef _DWC3_H_
+#define _DWC3_H_
+
+#define DWC3_IP_ID 0x5533
+#define DWC3_1_IP_ID 0x3331
+#define DWC3_2_IP_ID 0x3332
+
+#define DWC3_VERSION_MASK 0xFFFF0000
+#define DWC3_REVISION_MASK 0xFFFF
+#define DWC3_VERSION(x) (((x) & DWC3_VERSION_MASK) >> 16)
+#define DWC3_REVISION(x) ((x) & DWC3_REVISION_MASK)
+
+#define DWC3_GSBUSCFG0 0xc100
+#define DWC3_GSBUSCFG1 0xc104
+#define DWC3_GTXTHRCFG 0xc108
+#define DWC3_GRXTHRCFG 0xc10C
+
+/* Global Core Control Register */
+#define DWC3_GCTL 0xc110
+#define DWC3_GCTL_PRTCAPDIR_MASK (0x3 << 12)
+#define DWC3_GCTL_PRTCAPDIR_HOST (0x1 << 12)
+#define DWC3_GCTL_PRTCAPDIR_DEVICE (0x2 << 12)
+#define DWC3_GCTL_CORESOFTRESET (1 << 11)
+#define DWC3_GCTL_DSBLCLKGTNG (1 << 0)
+
+#define DWC3_GPMSTS 0xc114
+#define DWC3_GSTS 0xc118
+
+#define DWC3_GUCTL1 0xc11c
+#define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS (1 << 28)
+#define DWC3_GUCTL1_DEV_FORCE_20_CLK_FOR_30_CLK (1 << 26)
+
+#define DWC3_GSNPSID 0xc120
+#define DWC3_GGPIO 0xc124
+#define DWC3_GUID 0xc128
+#define DWC3_GUCTL 0xc12C
+#define DWC3_GUCTL_HOST_AUTO_RETRY (1 << 14)
+#define DWC3_GBUSERRADDRLO 0xc130
+#define DWC3_GBUSERRADDRHI 0xc134
+#define DWC3_GPRTBIMAPLO 0xc138
+#define DWC3_GHWPARAMS0 0xc140
+#define DWC3_GHWPARAMS0_MODE_DUALROLEDEVICE 0x2
+#define DWC3_GHWPARAMS0_MODE_MASK 0x3
+#define DWC3_GHWPARAMS1 0xc144
+#define DWC3_GHWPARAMS2 0xc148
+#define DWC3_GHWPARAMS3 0xc14C
+#define DWC3_GHWPARAMS4 0xc150
+#define DWC3_GHWPARAMS5 0xc154
+#define DWC3_GHWPARAMS6 0xc158
+#define DWC3_GHWPARAMS7 0xc15C
+#define DWC3_GDBGFIFOSPACE 0xc160
+#define DWC3_GDBGLTSSM 0xc164
+#define DWC3_GDBGLNMCC 0xc168
+#define DWC3_GDBGBMU 0xc16C
+#define DWC3_GDBGLSPMUX 0xc170
+#define DWC3_GDBGLSP 0xc174
+#define DWC3_GDBGEPINFO0 0xc178
+#define DWC3_GDBGEPINFO1 0xc17C
+#define DWC3_GPRTBIMAP_HSLO 0xc180
+#define DWC3_GPRTBIMAP_FSLO 0xc188
+
+#define DWC3_1_VER_NUMBER 0xc1a0
+#define DWC3_1_VER_TYPE 0xc1a4
+
+#define DWC3_GUSB2PHYCFG0 0xc200
+#define DWC3_GUSB2PHYCFG0_PHYSOFTRST (1 << 31)
+#define DWC3_GUSB2PHYCFG0_U2_FREECLK_EXISTS (1 << 30)
+#define DWC3_GUSB2PHYCFG0_USBTRDTIM(n) ((n) << 10)
+#define DWC3_GUSB2PHYCFG0_USBTRDTIM_8BITS 9
+#define DWC3_GUSB2PHYCFG0_USBTRDTIM_16BITS 5
+#define DWC3_GUSB2PHYCFG0_ENBLSLPM (1 << 8)
+#define DWC3_GUSB2PHYCFG0_PHYSEL(x) (((x) >> 7) & 0x1) /* 0 = USB2.0, 1 = USB1.1 */
+#define DWC3_GUSB2PHYCFG0_SUSPENDUSB20 (1 << 6)
+#define DWC3_GUSB2PHYCFG0_ULPI_UTMI_SEL (1 << 4)
+#define DWC3_GUSB2PHYCFG0_PHYIF (1 << 3)
+
+#define DWC3_GUSB3PIPECTL0 0xc2c0
+#define DWC3_GUSB3PIPECTL0_PHYSOFTRST (1 << 31)
+#define DWC3_GUSB3PIPECTL0_DISRXDETINP3 (1 << 28)
+#define DWC3_GUSB3PIPECTL0_DELAYP1TRANS (1 << 18)
+#define DWC3_GUSB3PIPECTL0_SUSPENDUSB3 (1 << 17)
+
+#define DWC3_HWPARAMS3_SSPHY(x) ((x) & 0x3)
+#define DWC3_HWPARAMS3_SSPHY_DISABLE 0
+#define DWC3_HWPARAMS3_SSPHY_GEN1 1
+#define DWC3_HWPARAMS3_SSPHY_GEN2 2
+
+#define DWC3_GTXFIFOSIZ(x) (0xc300 + 0x4 * (x))
+#define DWC3_GRXFIFOSIZ(x) (0xc380 + 0x4 * (x))
+#define DWC3_GEVNTADRLO0 0xc400
+#define DWC3_GEVNTADRHI0 0xc404
+#define DWC3_GEVNTSIZ0 0xc408
+#define DWC3_GEVNTCOUNT0 0xc40C
+#define DWC3_GHWPARAMS8 0xc600
+#define DWC3_GTXFIFOPRIDEV 0xc610
+#define DWC3_GTXFIFOPRIHST 0xc618
+#define DWC3_GRXFIFOPRIHST 0xc61c
+#define DWC3_GFIFOPRIDBC 0xc620
+#define DWC3_GDMAHLRATIO 0xc624
+#define DWC3_GFLADJ 0xc630
+#define DWC3_DCFG 0xc700
+#define DWC3_DCTL 0xc704
+#define DWC3_DEVTEN 0xc708
+#define DWC3_DSTS 0xc70C
+#define DWC3_DGCMDPAR 0xc710
+#define DWC3_DGCMD 0xc714
+#define DWC3_DALEPENA 0xc720
+
+#endif /* _DWC3_H_ */
diff --git a/sys/dev/usb/controller/dwc3/rk_dwc3.c b/sys/dev/usb/controller/dwc3/rk_dwc3.c
new file mode 100644
index 000000000000..16fc5f73f922
--- /dev/null
+++ b/sys/dev/usb/controller/dwc3/rk_dwc3.c
@@ -0,0 +1,199 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Emmanuel Vadot <manu@FreeBSD.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.
+ *
+ * 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.
+ */
+
+/*
+ * Rockchip DWC3 glue
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/gpio.h>
+#include <machine/bus.h>
+
+#include <dev/fdt/simplebus.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/ofw/ofw_subr.h>
+
+#include <dev/clk/clk.h>
+#include <dev/hwreset/hwreset.h>
+#include <dev/phy/phy_usb.h>
+#include <dev/syscon/syscon.h>
+
+enum rk_dwc3_type {
+ RK3399 = 1,
+};
+
+static struct ofw_compat_data compat_data[] = {
+ { "rockchip,rk3399-dwc3", RK3399 },
+ { NULL, 0 }
+};
+
+struct rk_dwc3_softc {
+ struct simplebus_softc sc;
+ device_t dev;
+ clk_t clk_ref;
+ clk_t clk_suspend;
+ clk_t clk_bus;
+ clk_t clk_axi_perf;
+ clk_t clk_usb3;
+ clk_t clk_grf;
+ hwreset_t rst_usb3;
+ enum rk_dwc3_type type;
+};
+
+static int
+rk_dwc3_probe(device_t dev)
+{
+ phandle_t node;
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
+ return (ENXIO);
+
+ /* Binding says that we need a child node for the actual dwc3 controller */
+ node = ofw_bus_get_node(dev);
+ if (OF_child(node) <= 0)
+ return (ENXIO);
+
+ device_set_desc(dev, "Rockchip RK3399 DWC3");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+rk_dwc3_attach(device_t dev)
+{
+ struct rk_dwc3_softc *sc;
+ device_t cdev;
+ phandle_t node, child;
+ int err;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ node = ofw_bus_get_node(dev);
+ sc->type = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
+
+ /* Mandatory clocks */
+ if (clk_get_by_ofw_name(dev, 0, "ref_clk", &sc->clk_ref) != 0) {
+ device_printf(dev, "Cannot get ref_clk clock\n");
+ return (ENXIO);
+ }
+ err = clk_enable(sc->clk_ref);
+ if (err != 0) {
+ device_printf(dev, "Could not enable clock %s\n",
+ clk_get_name(sc->clk_ref));
+ return (ENXIO);
+ }
+ if (clk_get_by_ofw_name(dev, 0, "suspend_clk", &sc->clk_suspend) != 0) {
+ device_printf(dev, "Cannot get suspend_clk clock\n");
+ return (ENXIO);
+ }
+ err = clk_enable(sc->clk_suspend);
+ if (err != 0) {
+ device_printf(dev, "Could not enable clock %s\n",
+ clk_get_name(sc->clk_suspend));
+ return (ENXIO);
+ }
+ if (clk_get_by_ofw_name(dev, 0, "bus_clk", &sc->clk_bus) != 0) {
+ device_printf(dev, "Cannot get bus_clk clock\n");
+ return (ENXIO);
+ }
+ err = clk_enable(sc->clk_bus);
+ if (err != 0) {
+ device_printf(dev, "Could not enable clock %s\n",
+ clk_get_name(sc->clk_bus));
+ return (ENXIO);
+ }
+ if (clk_get_by_ofw_name(dev, 0, "grf_clk", &sc->clk_grf) == 0) {
+ err = clk_enable(sc->clk_grf);
+ if (err != 0) {
+ device_printf(dev, "Could not enable clock %s\n",
+ clk_get_name(sc->clk_grf));
+ return (ENXIO);
+ }
+ }
+ /* Optional clocks */
+ if (clk_get_by_ofw_name(dev, 0, "aclk_usb3_rksoc_axi_perf", &sc->clk_axi_perf) == 0) {
+ err = clk_enable(sc->clk_axi_perf);
+ if (err != 0) {
+ device_printf(dev, "Could not enable clock %s\n",
+ clk_get_name(sc->clk_axi_perf));
+ return (ENXIO);
+ }
+ }
+ if (clk_get_by_ofw_name(dev, 0, "aclk_usb3", &sc->clk_usb3) == 0) {
+ err = clk_enable(sc->clk_usb3);
+ if (err != 0) {
+ device_printf(dev, "Could not enable clock %s\n",
+ clk_get_name(sc->clk_usb3));
+ return (ENXIO);
+ }
+ }
+
+ /* Put module out of reset */
+ if (hwreset_get_by_ofw_name(dev, node, "usb3-otg", &sc->rst_usb3) == 0) {
+ if (hwreset_deassert(sc->rst_usb3) != 0) {
+ device_printf(dev, "Cannot deassert reset\n");
+ return (ENXIO);
+ }
+ }
+
+ simplebus_init(dev, node);
+ if (simplebus_fill_ranges(node, &sc->sc) < 0) {
+ device_printf(dev, "could not get ranges\n");
+ return (ENXIO);
+ }
+
+ for (child = OF_child(node); child > 0; child = OF_peer(child)) {
+ cdev = simplebus_add_device(dev, child, 0, NULL, -1, NULL);
+ if (cdev != NULL)
+ device_probe_and_attach(cdev);
+ }
+
+ bus_attach_children(dev);
+ return (0);
+}
+
+static device_method_t rk_dwc3_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, rk_dwc3_probe),
+ DEVMETHOD(device_attach, rk_dwc3_attach),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(rk_dwc3, rk_dwc3_driver, rk_dwc3_methods,
+ sizeof(struct rk_dwc3_softc), simplebus_driver);
+DRIVER_MODULE(rk_dwc3, simplebus, rk_dwc3_driver, 0, 0);
diff --git a/sys/dev/usb/controller/dwc_otg.c b/sys/dev/usb/controller/dwc_otg.c
new file mode 100644
index 000000000000..6c44eebd0616
--- /dev/null
+++ b/sys/dev/usb/controller/dwc_otg.c
@@ -0,0 +1,4970 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2015 Daisuke Aoyama. All rights reserved.
+ * Copyright (c) 2012-2015 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 2010-2011 Aleksandr Rybalko. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the driver for the DesignWare series USB 2.0 OTG
+ * Controller.
+ */
+
+/*
+ * LIMITATION: Drivers must be bound to all OUT endpoints in the
+ * active configuration for this driver to work properly. Blocking any
+ * OUT endpoint will block all OUT endpoints including the control
+ * endpoint. Usually this is not a problem.
+ */
+
+/*
+ * NOTE: Writing to non-existing registers appears to cause an
+ * internal reset.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/rman.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#define USB_DEBUG_VAR dwc_otg_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#include <dev/usb/controller/dwc_otg.h>
+#include <dev/usb/controller/dwc_otgreg.h>
+
+#define DWC_OTG_BUS2SC(bus) \
+ __containerof(bus, struct dwc_otg_softc, sc_bus)
+
+#define DWC_OTG_PC2UDEV(pc) \
+ (USB_DMATAG_TO_XROOT((pc)->tag_parent)->udev)
+
+#define DWC_OTG_MSK_GINT_THREAD_IRQ \
+ (GINTSTS_USBRST | GINTSTS_ENUMDONE | GINTSTS_PRTINT | \
+ GINTSTS_WKUPINT | GINTSTS_USBSUSP | GINTMSK_OTGINTMSK | \
+ GINTSTS_SESSREQINT)
+
+#ifndef DWC_OTG_PHY_DEFAULT
+#define DWC_OTG_PHY_DEFAULT DWC_OTG_PHY_ULPI
+#endif
+
+static int dwc_otg_phy_type = DWC_OTG_PHY_DEFAULT;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, dwc_otg, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB DWC OTG");
+SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, phy_type, CTLFLAG_RDTUN,
+ &dwc_otg_phy_type, 0, "DWC OTG PHY TYPE - 0/1/2/3 - ULPI/HSIC/INTERNAL/UTMI+");
+
+#ifdef USB_DEBUG
+static int dwc_otg_debug = 0;
+
+SYSCTL_INT(_hw_usb_dwc_otg, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &dwc_otg_debug, 0, "DWC OTG debug level");
+#endif
+
+#define DWC_OTG_INTR_ENDPT 1
+
+/* prototypes */
+
+static const struct usb_bus_methods dwc_otg_bus_methods;
+static const struct usb_pipe_methods dwc_otg_device_non_isoc_methods;
+static const struct usb_pipe_methods dwc_otg_device_isoc_methods;
+
+static dwc_otg_cmd_t dwc_otg_setup_rx;
+static dwc_otg_cmd_t dwc_otg_data_rx;
+static dwc_otg_cmd_t dwc_otg_data_tx;
+static dwc_otg_cmd_t dwc_otg_data_tx_sync;
+
+static dwc_otg_cmd_t dwc_otg_host_setup_tx;
+static dwc_otg_cmd_t dwc_otg_host_data_tx;
+static dwc_otg_cmd_t dwc_otg_host_data_rx;
+
+static void dwc_otg_device_done(struct usb_xfer *, usb_error_t);
+static void dwc_otg_do_poll(struct usb_bus *);
+static void dwc_otg_standard_done(struct usb_xfer *);
+static void dwc_otg_root_intr(struct dwc_otg_softc *);
+static void dwc_otg_interrupt_poll_locked(struct dwc_otg_softc *);
+
+/*
+ * Here is a configuration that the chip supports.
+ */
+static const struct usb_hw_ep_profile dwc_otg_ep_profile[1] = {
+ [0] = {
+ .max_in_frame_size = 64,/* fixed */
+ .max_out_frame_size = 64, /* fixed */
+ .is_simplex = 1,
+ .support_control = 1,
+ }
+};
+
+static void
+dwc_otg_get_hw_ep_profile(struct usb_device *udev,
+ const struct usb_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+ struct dwc_otg_softc *sc;
+
+ sc = DWC_OTG_BUS2SC(udev->bus);
+
+ if (ep_addr < sc->sc_dev_ep_max)
+ *ppf = &sc->sc_hw_ep_profile[ep_addr].usb;
+ else
+ *ppf = NULL;
+}
+
+static void
+dwc_otg_write_fifo(struct dwc_otg_softc *sc, struct usb_page_cache *pc,
+ uint32_t offset, uint32_t fifo, uint32_t count)
+{
+ uint32_t temp;
+
+ /* round down length to nearest 4-bytes */
+ temp = count & ~3;
+
+ /* check if we can write the data directly */
+ if (temp != 0 && usb_pc_buffer_is_aligned(pc, offset, temp, 3)) {
+ struct usb_page_search buf_res;
+
+ /* pre-subtract length */
+ count -= temp;
+
+ /* iterate buffer list */
+ do {
+ /* get current buffer pointer */
+ usbd_get_page(pc, offset, &buf_res);
+
+ if (buf_res.length > temp)
+ buf_res.length = temp;
+
+ /* transfer data into FIFO */
+ bus_space_write_region_4(sc->sc_io_tag, sc->sc_io_hdl,
+ fifo, buf_res.buffer, buf_res.length / 4);
+
+ offset += buf_res.length;
+ fifo += buf_res.length;
+ temp -= buf_res.length;
+ } while (temp != 0);
+ }
+
+ /* check for remainder */
+ if (count != 0) {
+ /* clear topmost word before copy */
+ sc->sc_bounce_buffer[(count - 1) / 4] = 0;
+
+ /* copy out data */
+ usbd_copy_out(pc, offset,
+ sc->sc_bounce_buffer, count);
+
+ /* transfer data into FIFO */
+ bus_space_write_region_4(sc->sc_io_tag,
+ sc->sc_io_hdl, fifo, sc->sc_bounce_buffer,
+ (count + 3) / 4);
+ }
+}
+
+static void
+dwc_otg_read_fifo(struct dwc_otg_softc *sc, struct usb_page_cache *pc,
+ uint32_t offset, uint32_t count)
+{
+ uint32_t temp;
+
+ /* round down length to nearest 4-bytes */
+ temp = count & ~3;
+
+ /* check if we can read the data directly */
+ if (temp != 0 && usb_pc_buffer_is_aligned(pc, offset, temp, 3)) {
+ struct usb_page_search buf_res;
+
+ /* pre-subtract length */
+ count -= temp;
+
+ /* iterate buffer list */
+ do {
+ /* get current buffer pointer */
+ usbd_get_page(pc, offset, &buf_res);
+
+ if (buf_res.length > temp)
+ buf_res.length = temp;
+
+ /* transfer data from FIFO */
+ bus_space_read_region_4(sc->sc_io_tag, sc->sc_io_hdl,
+ sc->sc_current_rx_fifo, buf_res.buffer, buf_res.length / 4);
+
+ offset += buf_res.length;
+ sc->sc_current_rx_fifo += buf_res.length;
+ sc->sc_current_rx_bytes -= buf_res.length;
+ temp -= buf_res.length;
+ } while (temp != 0);
+ }
+
+ /* check for remainder */
+ if (count != 0) {
+ /* read data into bounce buffer */
+ bus_space_read_region_4(sc->sc_io_tag, sc->sc_io_hdl,
+ sc->sc_current_rx_fifo,
+ sc->sc_bounce_buffer, (count + 3) / 4);
+
+ /* store data into proper buffer */
+ usbd_copy_in(pc, offset, sc->sc_bounce_buffer, count);
+
+ /* round length up to nearest 4 bytes */
+ count = (count + 3) & ~3;
+
+ /* update counters */
+ sc->sc_current_rx_bytes -= count;
+ sc->sc_current_rx_fifo += count;
+ }
+}
+
+static void
+dwc_otg_tx_fifo_reset(struct dwc_otg_softc *sc, uint32_t value)
+{
+ uint32_t temp;
+
+ /* reset FIFO */
+ DWC_OTG_WRITE_4(sc, DOTG_GRSTCTL, value);
+
+ /* wait for reset to complete */
+ for (temp = 0; temp != 16; temp++) {
+ value = DWC_OTG_READ_4(sc, DOTG_GRSTCTL);
+ if (!(value & (GRSTCTL_TXFFLSH | GRSTCTL_RXFFLSH)))
+ break;
+ }
+}
+
+static int
+dwc_otg_init_fifo(struct dwc_otg_softc *sc, uint8_t mode)
+{
+ struct dwc_otg_profile *pf;
+ uint32_t fifo_size;
+ uint32_t fifo_regs;
+ uint32_t tx_start;
+ uint8_t x;
+
+ fifo_size = sc->sc_fifo_size;
+
+ /*
+ * NOTE: Reserved fixed size area at end of RAM, which must
+ * not be allocated to the FIFOs:
+ */
+ fifo_regs = 4 * 16;
+
+ if (fifo_size < fifo_regs) {
+ DPRINTF("Too little FIFO\n");
+ return (EINVAL);
+ }
+
+ /* subtract FIFO regs from total once */
+ fifo_size -= fifo_regs;
+
+ /* split equally for IN and OUT */
+ fifo_size /= 2;
+
+ /* Align to 4 bytes boundary (refer to PGM) */
+ fifo_size &= ~3;
+
+ /* set global receive FIFO size */
+ DWC_OTG_WRITE_4(sc, DOTG_GRXFSIZ, fifo_size / 4);
+
+ tx_start = fifo_size;
+
+ if (fifo_size < 64) {
+ DPRINTFN(-1, "Not enough data space for EP0 FIFO.\n");
+ return (EINVAL);
+ }
+
+ if (mode == DWC_MODE_HOST) {
+ /* reset active endpoints */
+ sc->sc_active_rx_ep = 0;
+
+ /* split equally for periodic and non-periodic */
+ fifo_size /= 2;
+
+ DPRINTF("PTX/NPTX FIFO=%u\n", fifo_size);
+
+ /* align to 4 bytes boundary */
+ fifo_size &= ~3;
+
+ DWC_OTG_WRITE_4(sc, DOTG_GNPTXFSIZ,
+ ((fifo_size / 4) << 16) |
+ (tx_start / 4));
+
+ tx_start += fifo_size;
+
+ for (x = 0; x != sc->sc_host_ch_max; x++) {
+ /* enable all host interrupts */
+ DWC_OTG_WRITE_4(sc, DOTG_HCINTMSK(x),
+ HCINT_DEFAULT_MASK);
+ }
+
+ DWC_OTG_WRITE_4(sc, DOTG_HPTXFSIZ,
+ ((fifo_size / 4) << 16) |
+ (tx_start / 4));
+
+ /* reset host channel state */
+ memset(sc->sc_chan_state, 0, sizeof(sc->sc_chan_state));
+
+ /* enable all host channel interrupts */
+ DWC_OTG_WRITE_4(sc, DOTG_HAINTMSK,
+ (1U << sc->sc_host_ch_max) - 1U);
+
+ /* enable proper host channel interrupts */
+ sc->sc_irq_mask |= GINTMSK_HCHINTMSK;
+ sc->sc_irq_mask &= ~GINTMSK_IEPINTMSK;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+ }
+
+ if (mode == DWC_MODE_DEVICE) {
+ DWC_OTG_WRITE_4(sc, DOTG_GNPTXFSIZ,
+ (0x10 << 16) | (tx_start / 4));
+ fifo_size -= 0x40;
+ tx_start += 0x40;
+
+ /* setup control endpoint profile */
+ sc->sc_hw_ep_profile[0].usb = dwc_otg_ep_profile[0];
+
+ /* reset active endpoints */
+ sc->sc_active_rx_ep = 1;
+
+ for (x = 1; x != sc->sc_dev_ep_max; x++) {
+ pf = sc->sc_hw_ep_profile + x;
+
+ pf->usb.max_out_frame_size = 1024 * 3;
+ pf->usb.is_simplex = 0; /* assume duplex */
+ pf->usb.support_bulk = 1;
+ pf->usb.support_interrupt = 1;
+ pf->usb.support_isochronous = 1;
+ pf->usb.support_out = 1;
+
+ if (x < sc->sc_dev_in_ep_max) {
+ uint32_t limit;
+
+ limit = (x == 1) ? MIN(DWC_OTG_TX_MAX_FIFO_SIZE,
+ DWC_OTG_MAX_TXN) : MIN(DWC_OTG_MAX_TXN / 2,
+ DWC_OTG_TX_MAX_FIFO_SIZE);
+
+ /* see if there is enough FIFO space */
+ if (limit <= fifo_size) {
+ pf->max_buffer = limit;
+ pf->usb.support_in = 1;
+ } else {
+ limit = MIN(DWC_OTG_TX_MAX_FIFO_SIZE, 0x40);
+ if (limit <= fifo_size) {
+ pf->usb.support_in = 1;
+ } else {
+ pf->usb.is_simplex = 1;
+ limit = 0;
+ }
+ }
+ /* set FIFO size */
+ DWC_OTG_WRITE_4(sc, DOTG_DIEPTXF(x),
+ ((limit / 4) << 16) | (tx_start / 4));
+ tx_start += limit;
+ fifo_size -= limit;
+ pf->usb.max_in_frame_size = limit;
+ } else {
+ pf->usb.is_simplex = 1;
+ }
+
+ DPRINTF("FIFO%d = IN:%d / OUT:%d\n", x,
+ pf->usb.max_in_frame_size,
+ pf->usb.max_out_frame_size);
+ }
+
+ /* enable proper device channel interrupts */
+ sc->sc_irq_mask &= ~GINTMSK_HCHINTMSK;
+ sc->sc_irq_mask |= GINTMSK_IEPINTMSK;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+ }
+
+ /* reset RX FIFO */
+ dwc_otg_tx_fifo_reset(sc, GRSTCTL_RXFFLSH);
+
+ if (mode != DWC_MODE_OTG) {
+ /* reset all TX FIFOs */
+ dwc_otg_tx_fifo_reset(sc,
+ GRSTCTL_TXFIFO(0x10) |
+ GRSTCTL_TXFFLSH);
+ } else {
+ /* reset active endpoints */
+ sc->sc_active_rx_ep = 0;
+
+ /* reset host channel state */
+ memset(sc->sc_chan_state, 0, sizeof(sc->sc_chan_state));
+ }
+ return (0);
+}
+
+static uint8_t
+dwc_otg_uses_split(struct usb_device *udev)
+{
+ /*
+ * When a LOW or FULL speed device is connected directly to
+ * the USB port we don't use split transactions:
+ */
+ return (udev->speed != USB_SPEED_HIGH &&
+ udev->parent_hs_hub != NULL &&
+ udev->parent_hs_hub->parent_hub != NULL);
+}
+
+static void
+dwc_otg_update_host_frame_interval(struct dwc_otg_softc *sc)
+{
+
+ /*
+ * Disabled until further. Assuming that the register is already
+ * programmed correctly by the boot loader.
+ */
+#if 0
+ uint32_t temp;
+
+ /* setup HOST frame interval register, based on existing value */
+ temp = DWC_OTG_READ_4(sc, DOTG_HFIR) & HFIR_FRINT_MASK;
+ if (temp >= 10000)
+ temp /= 1000;
+ else
+ temp /= 125;
+
+ /* figure out nearest X-tal value */
+ if (temp >= 54)
+ temp = 60; /* MHz */
+ else if (temp >= 39)
+ temp = 48; /* MHz */
+ else
+ temp = 30; /* MHz */
+
+ if (sc->sc_flags.status_high_speed)
+ temp *= 125;
+ else
+ temp *= 1000;
+
+ DPRINTF("HFIR=0x%08x\n", temp);
+
+ DWC_OTG_WRITE_4(sc, DOTG_HFIR, temp);
+#endif
+}
+
+static void
+dwc_otg_clocks_on(struct dwc_otg_softc *sc)
+{
+ if (sc->sc_flags.clocks_off &&
+ sc->sc_flags.port_powered) {
+ DPRINTFN(5, "\n");
+
+ /* TODO - platform specific */
+
+ sc->sc_flags.clocks_off = 0;
+ }
+}
+
+static void
+dwc_otg_clocks_off(struct dwc_otg_softc *sc)
+{
+ if (!sc->sc_flags.clocks_off) {
+ DPRINTFN(5, "\n");
+
+ /* TODO - platform specific */
+
+ sc->sc_flags.clocks_off = 1;
+ }
+}
+
+static void
+dwc_otg_pull_up(struct dwc_otg_softc *sc)
+{
+ uint32_t temp;
+
+ /* pullup D+, if possible */
+
+ if (!sc->sc_flags.d_pulled_up &&
+ sc->sc_flags.port_powered) {
+ sc->sc_flags.d_pulled_up = 1;
+
+ temp = DWC_OTG_READ_4(sc, DOTG_DCTL);
+ temp &= ~DCTL_SFTDISCON;
+ DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp);
+ }
+}
+
+static void
+dwc_otg_pull_down(struct dwc_otg_softc *sc)
+{
+ uint32_t temp;
+
+ /* pulldown D+, if possible */
+
+ if (sc->sc_flags.d_pulled_up) {
+ sc->sc_flags.d_pulled_up = 0;
+
+ temp = DWC_OTG_READ_4(sc, DOTG_DCTL);
+ temp |= DCTL_SFTDISCON;
+ DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp);
+ }
+}
+
+static void
+dwc_otg_enable_sof_irq(struct dwc_otg_softc *sc)
+{
+ /* In device mode we don't use the SOF interrupt */
+ if (sc->sc_flags.status_device_mode != 0)
+ return;
+ /* Ensure the SOF interrupt is not disabled */
+ sc->sc_needsof = 1;
+ /* Check if the SOF interrupt is already enabled */
+ if ((sc->sc_irq_mask & GINTMSK_SOFMSK) != 0)
+ return;
+ sc->sc_irq_mask |= GINTMSK_SOFMSK;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+}
+
+static void
+dwc_otg_resume_irq(struct dwc_otg_softc *sc)
+{
+ if (sc->sc_flags.status_suspend) {
+ /* update status bits */
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 1;
+
+ if (sc->sc_flags.status_device_mode) {
+ /*
+ * Disable resume interrupt and enable suspend
+ * interrupt:
+ */
+ sc->sc_irq_mask &= ~GINTMSK_WKUPINTMSK;
+ sc->sc_irq_mask |= GINTMSK_USBSUSPMSK;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+ }
+
+ /* complete root HUB interrupt endpoint */
+ dwc_otg_root_intr(sc);
+ }
+}
+
+static void
+dwc_otg_suspend_irq(struct dwc_otg_softc *sc)
+{
+ if (!sc->sc_flags.status_suspend) {
+ /* update status bits */
+ sc->sc_flags.status_suspend = 1;
+ sc->sc_flags.change_suspend = 1;
+
+ if (sc->sc_flags.status_device_mode) {
+ /*
+ * Disable suspend interrupt and enable resume
+ * interrupt:
+ */
+ sc->sc_irq_mask &= ~GINTMSK_USBSUSPMSK;
+ sc->sc_irq_mask |= GINTMSK_WKUPINTMSK;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+ }
+
+ /* complete root HUB interrupt endpoint */
+ dwc_otg_root_intr(sc);
+ }
+}
+
+static void
+dwc_otg_wakeup_peer(struct dwc_otg_softc *sc)
+{
+ if (!sc->sc_flags.status_suspend)
+ return;
+
+ DPRINTFN(5, "Remote wakeup\n");
+
+ if (sc->sc_flags.status_device_mode) {
+ uint32_t temp;
+
+ /* enable remote wakeup signalling */
+ temp = DWC_OTG_READ_4(sc, DOTG_DCTL);
+ temp |= DCTL_RMTWKUPSIG;
+ DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp);
+
+ /* Wait 8ms for remote wakeup to complete. */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+
+ temp &= ~DCTL_RMTWKUPSIG;
+ DWC_OTG_WRITE_4(sc, DOTG_DCTL, temp);
+ } else {
+ /* enable USB port */
+ DWC_OTG_WRITE_4(sc, DOTG_PCGCCTL, 0);
+
+ /* wait 10ms */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+
+ /* resume port */
+ sc->sc_hprt_val |= HPRT_PRTRES;
+ DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val);
+
+ /* Wait 100ms for resume signalling to complete. */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 10);
+
+ /* clear suspend and resume */
+ sc->sc_hprt_val &= ~(HPRT_PRTSUSP | HPRT_PRTRES);
+ DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val);
+
+ /* Wait 4ms */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250);
+ }
+
+ /* need to fake resume IRQ */
+ dwc_otg_resume_irq(sc);
+}
+
+static void
+dwc_otg_set_address(struct dwc_otg_softc *sc, uint8_t addr)
+{
+ uint32_t temp;
+
+ DPRINTFN(5, "addr=%d\n", addr);
+
+ temp = DWC_OTG_READ_4(sc, DOTG_DCFG);
+ temp &= ~DCFG_DEVADDR_SET(0x7F);
+ temp |= DCFG_DEVADDR_SET(addr);
+ DWC_OTG_WRITE_4(sc, DOTG_DCFG, temp);
+}
+
+static void
+dwc_otg_common_rx_ack(struct dwc_otg_softc *sc)
+{
+ DPRINTFN(5, "RX status clear\n");
+
+ /* enable RX FIFO level interrupt */
+ sc->sc_irq_mask |= GINTMSK_RXFLVLMSK;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+
+ if (sc->sc_current_rx_bytes != 0) {
+ /* need to dump remaining data */
+ bus_space_read_region_4(sc->sc_io_tag, sc->sc_io_hdl,
+ sc->sc_current_rx_fifo, sc->sc_bounce_buffer,
+ sc->sc_current_rx_bytes / 4);
+ /* clear number of active bytes to receive */
+ sc->sc_current_rx_bytes = 0;
+ }
+ /* clear cached status */
+ sc->sc_last_rx_status = 0;
+}
+
+static void
+dwc_otg_clear_hcint(struct dwc_otg_softc *sc, uint8_t x)
+{
+ uint32_t hcint;
+
+ /* clear all pending interrupts */
+ hcint = DWC_OTG_READ_4(sc, DOTG_HCINT(x));
+ DWC_OTG_WRITE_4(sc, DOTG_HCINT(x), hcint);
+
+ /* clear buffered interrupts */
+ sc->sc_chan_state[x].hcint = 0;
+}
+
+static uint8_t
+dwc_otg_host_check_tx_fifo_empty(struct dwc_otg_softc *sc, struct dwc_otg_td *td)
+{
+ uint32_t temp;
+
+ temp = DWC_OTG_READ_4(sc, DOTG_GINTSTS);
+
+ if (td->ep_type == UE_ISOCHRONOUS) {
+ /*
+ * NOTE: USB INTERRUPT transactions are executed like
+ * USB CONTROL transactions! See the setup standard
+ * chain function for more information.
+ */
+ if (!(temp & GINTSTS_PTXFEMP)) {
+ DPRINTF("Periodic TX FIFO is not empty\n");
+ if (!(sc->sc_irq_mask & GINTMSK_PTXFEMPMSK)) {
+ sc->sc_irq_mask |= GINTMSK_PTXFEMPMSK;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+ }
+ return (1); /* busy */
+ }
+ } else {
+ if (!(temp & GINTSTS_NPTXFEMP)) {
+ DPRINTF("Non-periodic TX FIFO is not empty\n");
+ if (!(sc->sc_irq_mask & GINTMSK_NPTXFEMPMSK)) {
+ sc->sc_irq_mask |= GINTMSK_NPTXFEMPMSK;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+ }
+ return (1); /* busy */
+ }
+ }
+ return (0); /* ready for transmit */
+}
+
+static uint8_t
+dwc_otg_host_channel_alloc(struct dwc_otg_softc *sc,
+ struct dwc_otg_td *td, uint8_t is_out)
+{
+ uint8_t x;
+ uint8_t y;
+ uint8_t z;
+
+ if (td->channel[0] < DWC_OTG_MAX_CHANNELS)
+ return (0); /* already allocated */
+
+ /* check if device is suspended */
+ if (DWC_OTG_PC2UDEV(td->pc)->flags.self_suspended != 0)
+ return (1); /* busy - cannot transfer data */
+
+ /* compute needed TX FIFO size */
+ if (is_out != 0) {
+ if (dwc_otg_host_check_tx_fifo_empty(sc, td) != 0)
+ return (1); /* busy - cannot transfer data */
+ }
+ z = td->max_packet_count;
+ for (x = y = 0; x != sc->sc_host_ch_max; x++) {
+ /* check if channel is allocated */
+ if (sc->sc_chan_state[x].allocated != 0)
+ continue;
+ /* check if channel is still enabled */
+ if (sc->sc_chan_state[x].wait_halted != 0)
+ continue;
+ /* store channel number */
+ td->channel[y++] = x;
+ /* check if we got all channels */
+ if (y == z)
+ break;
+ }
+ if (y != z) {
+ /* reset channel variable */
+ td->channel[0] = DWC_OTG_MAX_CHANNELS;
+ td->channel[1] = DWC_OTG_MAX_CHANNELS;
+ td->channel[2] = DWC_OTG_MAX_CHANNELS;
+ /* wait a bit */
+ dwc_otg_enable_sof_irq(sc);
+ return (1); /* busy - not enough channels */
+ }
+
+ for (y = 0; y != z; y++) {
+ x = td->channel[y];
+
+ /* set allocated */
+ sc->sc_chan_state[x].allocated = 1;
+
+ /* set wait halted */
+ sc->sc_chan_state[x].wait_halted = 1;
+
+ /* clear interrupts */
+ dwc_otg_clear_hcint(sc, x);
+
+ DPRINTF("CH=%d HCCHAR=0x%08x "
+ "HCSPLT=0x%08x\n", x, td->hcchar, td->hcsplt);
+
+ /* set active channel */
+ sc->sc_active_rx_ep |= (1 << x);
+ }
+ return (0); /* allocated */
+}
+
+static void
+dwc_otg_host_channel_free_sub(struct dwc_otg_softc *sc, struct dwc_otg_td *td, uint8_t index)
+{
+ uint32_t hcchar;
+ uint8_t x;
+
+ if (td->channel[index] >= DWC_OTG_MAX_CHANNELS)
+ return; /* already freed */
+
+ /* free channel */
+ x = td->channel[index];
+ td->channel[index] = DWC_OTG_MAX_CHANNELS;
+
+ DPRINTF("CH=%d\n", x);
+
+ /*
+ * We need to let programmed host channels run till complete
+ * else the host channel will stop functioning.
+ */
+ sc->sc_chan_state[x].allocated = 0;
+
+ /* ack any pending messages */
+ if (sc->sc_last_rx_status != 0 &&
+ GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) == x) {
+ dwc_otg_common_rx_ack(sc);
+ }
+
+ /* clear active channel */
+ sc->sc_active_rx_ep &= ~(1 << x);
+
+ /* check if already halted */
+ if (sc->sc_chan_state[x].wait_halted == 0)
+ return;
+
+ /* disable host channel */
+ hcchar = DWC_OTG_READ_4(sc, DOTG_HCCHAR(x));
+ if (hcchar & HCCHAR_CHENA) {
+ DPRINTF("Halting channel %d\n", x);
+ DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(x),
+ hcchar | HCCHAR_CHDIS);
+ /* don't write HCCHAR until the channel is halted */
+ } else {
+ sc->sc_chan_state[x].wait_halted = 0;
+ }
+}
+
+static void
+dwc_otg_host_channel_free(struct dwc_otg_softc *sc, struct dwc_otg_td *td)
+{
+ uint8_t x;
+ for (x = 0; x != td->max_packet_count; x++)
+ dwc_otg_host_channel_free_sub(sc, td, x);
+}
+
+static void
+dwc_otg_host_dump_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td)
+{
+ uint8_t x;
+ /* dump any pending messages */
+ if (sc->sc_last_rx_status == 0)
+ return;
+ for (x = 0; x != td->max_packet_count; x++) {
+ if (td->channel[x] >= DWC_OTG_MAX_CHANNELS ||
+ td->channel[x] != GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status))
+ continue;
+ dwc_otg_common_rx_ack(sc);
+ break;
+ }
+}
+
+static uint8_t
+dwc_otg_host_setup_tx(struct dwc_otg_softc *sc, struct dwc_otg_td *td)
+{
+ struct usb_device_request req __aligned(4);
+ uint32_t hcint;
+ uint32_t hcchar;
+ uint8_t delta;
+
+ dwc_otg_host_dump_rx(sc, td);
+
+ if (td->channel[0] < DWC_OTG_MAX_CHANNELS) {
+ hcint = sc->sc_chan_state[td->channel[0]].hcint;
+
+ DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n",
+ td->channel[0], td->state, hcint,
+ DWC_OTG_READ_4(sc, DOTG_HCCHAR(td->channel[0])),
+ DWC_OTG_READ_4(sc, DOTG_HCTSIZ(td->channel[0])));
+ } else {
+ hcint = 0;
+ goto check_state;
+ }
+
+ if (hcint & (HCINT_RETRY |
+ HCINT_ACK | HCINT_NYET)) {
+ /* give success bits priority over failure bits */
+ } else if (hcint & HCINT_STALL) {
+ DPRINTF("CH=%d STALL\n", td->channel[0]);
+ td->error_stall = 1;
+ td->error_any = 1;
+ goto complete;
+ } else if (hcint & HCINT_ERRORS) {
+ DPRINTF("CH=%d ERROR\n", td->channel[0]);
+ td->errcnt++;
+ if (td->hcsplt != 0 || td->errcnt >= 3) {
+ td->error_any = 1;
+ goto complete;
+ }
+ }
+
+ if (hcint & (HCINT_ERRORS | HCINT_RETRY |
+ HCINT_ACK | HCINT_NYET)) {
+ if (!(hcint & HCINT_ERRORS))
+ td->errcnt = 0;
+ }
+
+check_state:
+ switch (td->state) {
+ case DWC_CHAN_ST_START:
+ goto send_pkt;
+
+ case DWC_CHAN_ST_WAIT_ANE:
+ if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+ td->did_nak = 1;
+ td->tt_scheduled = 0;
+ goto send_pkt;
+ } else if (hcint & (HCINT_ACK | HCINT_NYET)) {
+ td->offset += td->tx_bytes;
+ td->remainder -= td->tx_bytes;
+ td->toggle = 1;
+ td->tt_scheduled = 0;
+ goto complete;
+ }
+ break;
+
+ case DWC_CHAN_ST_WAIT_S_ANE:
+ if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+ td->did_nak = 1;
+ td->tt_scheduled = 0;
+ goto send_pkt;
+ } else if (hcint & (HCINT_ACK | HCINT_NYET)) {
+ goto send_cpkt;
+ }
+ break;
+
+ case DWC_CHAN_ST_WAIT_C_ANE:
+ if (hcint & HCINT_NYET) {
+ goto send_cpkt;
+ } else if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+ td->did_nak = 1;
+ td->tt_scheduled = 0;
+ goto send_pkt;
+ } else if (hcint & HCINT_ACK) {
+ td->offset += td->tx_bytes;
+ td->remainder -= td->tx_bytes;
+ td->toggle = 1;
+ goto complete;
+ }
+ break;
+
+ case DWC_CHAN_ST_WAIT_C_PKT:
+ goto send_cpkt;
+
+ default:
+ break;
+ }
+ goto busy;
+
+send_pkt:
+ /* free existing channel, if any */
+ dwc_otg_host_channel_free(sc, td);
+
+ if (sizeof(req) != td->remainder) {
+ td->error_any = 1;
+ goto complete;
+ }
+
+ if (td->hcsplt != 0) {
+ delta = td->tt_start_slot - sc->sc_last_frame_num - 1;
+ if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) {
+ td->state = DWC_CHAN_ST_START;
+ goto busy;
+ }
+ delta = sc->sc_last_frame_num - td->tt_start_slot;
+ if (delta > 5) {
+ /* missed it */
+ td->tt_scheduled = 0;
+ td->state = DWC_CHAN_ST_START;
+ goto busy;
+ }
+ }
+
+ /* allocate a new channel */
+ if (dwc_otg_host_channel_alloc(sc, td, 1)) {
+ td->state = DWC_CHAN_ST_START;
+ goto busy;
+ }
+
+ if (td->hcsplt != 0) {
+ td->hcsplt &= ~HCSPLT_COMPSPLT;
+ td->state = DWC_CHAN_ST_WAIT_S_ANE;
+ } else {
+ td->state = DWC_CHAN_ST_WAIT_ANE;
+ }
+
+ /* copy out control request */
+ usbd_copy_out(td->pc, 0, &req, sizeof(req));
+
+ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel[0]),
+ (sizeof(req) << HCTSIZ_XFERSIZE_SHIFT) |
+ (1 << HCTSIZ_PKTCNT_SHIFT) |
+ (HCTSIZ_PID_SETUP << HCTSIZ_PID_SHIFT));
+
+ DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel[0]), td->hcsplt);
+
+ hcchar = td->hcchar;
+ hcchar &= ~(HCCHAR_EPDIR_IN | HCCHAR_EPTYPE_MASK);
+ hcchar |= UE_CONTROL << HCCHAR_EPTYPE_SHIFT;
+
+ /* must enable channel before writing data to FIFO */
+ DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel[0]), hcchar);
+
+ /* transfer data into FIFO */
+ bus_space_write_region_4(sc->sc_io_tag, sc->sc_io_hdl,
+ DOTG_DFIFO(td->channel[0]), (uint32_t *)&req, sizeof(req) / 4);
+
+ /* wait until next slot before trying complete split */
+ td->tt_complete_slot = sc->sc_last_frame_num + 1;
+
+ /* store number of bytes transmitted */
+ td->tx_bytes = sizeof(req);
+ goto busy;
+
+send_cpkt:
+ /* free existing channel, if any */
+ dwc_otg_host_channel_free(sc, td);
+
+ delta = td->tt_complete_slot - sc->sc_last_frame_num - 1;
+ if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) {
+ td->state = DWC_CHAN_ST_WAIT_C_PKT;
+ goto busy;
+ }
+ delta = sc->sc_last_frame_num - td->tt_start_slot;
+ if (delta > DWC_OTG_TT_SLOT_MAX) {
+ /* we missed the service interval */
+ if (td->ep_type != UE_ISOCHRONOUS)
+ td->error_any = 1;
+ goto complete;
+ }
+ /* allocate a new channel */
+ if (dwc_otg_host_channel_alloc(sc, td, 0)) {
+ td->state = DWC_CHAN_ST_WAIT_C_PKT;
+ goto busy;
+ }
+
+ /* wait until next slot before trying complete split */
+ td->tt_complete_slot = sc->sc_last_frame_num + 1;
+
+ td->hcsplt |= HCSPLT_COMPSPLT;
+ td->state = DWC_CHAN_ST_WAIT_C_ANE;
+
+ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(td->channel[0]),
+ (HCTSIZ_PID_SETUP << HCTSIZ_PID_SHIFT));
+
+ DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(td->channel[0]), td->hcsplt);
+
+ hcchar = td->hcchar;
+ hcchar &= ~(HCCHAR_EPDIR_IN | HCCHAR_EPTYPE_MASK);
+ hcchar |= UE_CONTROL << HCCHAR_EPTYPE_SHIFT;
+
+ /* must enable channel before writing data to FIFO */
+ DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(td->channel[0]), hcchar);
+
+busy:
+ return (1); /* busy */
+
+complete:
+ dwc_otg_host_channel_free(sc, td);
+ return (0); /* complete */
+}
+
+static uint8_t
+dwc_otg_setup_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td)
+{
+ struct usb_device_request req __aligned(4);
+ uint32_t temp;
+ uint16_t count;
+
+ /* check endpoint status */
+
+ if (sc->sc_last_rx_status == 0)
+ goto not_complete;
+
+ if (GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) != 0)
+ goto not_complete;
+
+ if ((sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) !=
+ GRXSTSRD_STP_DATA) {
+ if ((sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) !=
+ GRXSTSRD_STP_COMPLETE || td->remainder != 0) {
+ /* release FIFO */
+ dwc_otg_common_rx_ack(sc);
+ goto not_complete;
+ }
+ /* release FIFO */
+ dwc_otg_common_rx_ack(sc);
+ return (0); /* complete */
+ }
+
+ if ((sc->sc_last_rx_status & GRXSTSRD_DPID_MASK) !=
+ GRXSTSRD_DPID_DATA0) {
+ /* release FIFO */
+ dwc_otg_common_rx_ack(sc);
+ goto not_complete;
+ }
+
+ DPRINTFN(5, "GRXSTSR=0x%08x\n", sc->sc_last_rx_status);
+
+ /* clear did stall */
+ td->did_stall = 0;
+
+ /* get the packet byte count */
+ count = GRXSTSRD_BCNT_GET(sc->sc_last_rx_status);
+
+ if (count != sizeof(req)) {
+ DPRINTFN(0, "Unsupported SETUP packet "
+ "length, %d bytes\n", count);
+ /* release FIFO */
+ dwc_otg_common_rx_ack(sc);
+ goto not_complete;
+ }
+
+ /* read FIFO */
+ dwc_otg_read_fifo(sc, td->pc, 0, sizeof(req));
+
+ /* copy out control request */
+ usbd_copy_out(td->pc, 0, &req, sizeof(req));
+
+ td->offset = sizeof(req);
+ td->remainder = 0;
+
+ /* sneak peek the set address */
+ if ((req.bmRequestType == UT_WRITE_DEVICE) &&
+ (req.bRequest == UR_SET_ADDRESS)) {
+ /* must write address before ZLP */
+ dwc_otg_set_address(sc, req.wValue[0] & 0x7F);
+ }
+
+ /* don't send any data by default */
+ DWC_OTG_WRITE_4(sc, DOTG_DIEPTSIZ(0), DIEPCTL_EPDIS);
+ DWC_OTG_WRITE_4(sc, DOTG_DOEPTSIZ(0), DOEPCTL_EPDIS);
+
+ /* reset IN endpoint buffer */
+ dwc_otg_tx_fifo_reset(sc,
+ GRSTCTL_TXFIFO(0) |
+ GRSTCTL_TXFFLSH);
+
+ /* acknowledge RX status */
+ dwc_otg_common_rx_ack(sc);
+ td->did_stall = 1;
+
+not_complete:
+ /* abort any ongoing transfer, before enabling again */
+ if (!td->did_stall) {
+ td->did_stall = 1;
+
+ DPRINTFN(5, "stalling IN and OUT direction\n");
+
+ temp = sc->sc_out_ctl[0];
+
+ /* set stall after enabling endpoint */
+ DWC_OTG_WRITE_4(sc, DOTG_DOEPCTL(0),
+ temp | DOEPCTL_STALL);
+
+ temp = sc->sc_in_ctl[0];
+
+ /* set stall assuming endpoint is enabled */
+ DWC_OTG_WRITE_4(sc, DOTG_DIEPCTL(0),
+ temp | DIEPCTL_STALL);
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+dwc_otg_host_rate_check_interrupt(struct dwc_otg_softc *sc, struct dwc_otg_td *td)
+{
+ uint8_t delta;
+
+ delta = sc->sc_tmr_val - td->tmr_val;
+ if (delta >= 128)
+ return (1); /* busy */
+
+ td->tmr_val = sc->sc_tmr_val + td->tmr_res;
+
+ /* set toggle, if any */
+ if (td->set_toggle) {
+ td->set_toggle = 0;
+ td->toggle = 1;
+ }
+ return (0);
+}
+
+static uint8_t
+dwc_otg_host_rate_check(struct dwc_otg_softc *sc, struct dwc_otg_td *td)
+{
+ uint8_t frame_num = (uint8_t)sc->sc_last_frame_num;
+
+ if (td->ep_type == UE_ISOCHRONOUS) {
+ /* non TT isochronous traffic */
+ if (frame_num & (td->tmr_res - 1))
+ goto busy;
+ if ((frame_num ^ td->tmr_val) & td->tmr_res)
+ goto busy;
+ td->tmr_val = td->tmr_res + sc->sc_last_frame_num;
+ td->toggle = 0;
+ return (0);
+ } else if (td->ep_type == UE_INTERRUPT) {
+ if (!td->tt_scheduled)
+ goto busy;
+ td->tt_scheduled = 0;
+ return (0);
+ } else if (td->did_nak != 0) {
+ /* check if we should pause sending queries for 125us */
+ if (td->tmr_res == frame_num) {
+ /* wait a bit */
+ dwc_otg_enable_sof_irq(sc);
+ goto busy;
+ }
+ } else if (td->set_toggle) {
+ td->set_toggle = 0;
+ td->toggle = 1;
+ }
+ /* query for data one more time */
+ td->tmr_res = frame_num;
+ td->did_nak = 0;
+ return (0);
+busy:
+ return (1);
+}
+
+static uint8_t
+dwc_otg_host_data_rx_sub(struct dwc_otg_softc *sc, struct dwc_otg_td *td,
+ uint8_t channel)
+{
+ uint32_t count;
+
+ /* check endpoint status */
+ if (sc->sc_last_rx_status == 0)
+ goto busy;
+
+ if (channel >= DWC_OTG_MAX_CHANNELS)
+ goto busy;
+
+ if (GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) != channel)
+ goto busy;
+
+ switch (sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) {
+ case GRXSTSRH_IN_DATA:
+
+ DPRINTF("DATA ST=%d STATUS=0x%08x\n",
+ (int)td->state, (int)sc->sc_last_rx_status);
+
+ if (sc->sc_chan_state[channel].hcint & HCINT_SOFTWARE_ONLY) {
+ /*
+ * When using SPLIT transactions on interrupt
+ * endpoints, sometimes data occurs twice.
+ */
+ DPRINTF("Data already received\n");
+ break;
+ }
+
+ /* get the packet byte count */
+ count = GRXSTSRD_BCNT_GET(sc->sc_last_rx_status);
+
+ /* check for ISOCHRONOUS endpoint */
+ if (td->ep_type == UE_ISOCHRONOUS) {
+ if ((sc->sc_last_rx_status & GRXSTSRD_DPID_MASK) !=
+ GRXSTSRD_DPID_DATA0) {
+ /* more data to be received */
+ td->tt_xactpos = HCSPLT_XACTPOS_MIDDLE;
+ } else {
+ /* all data received */
+ td->tt_xactpos = HCSPLT_XACTPOS_BEGIN;
+ /* verify the packet byte count */
+ if (count != td->remainder) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ td->got_short = 1;
+ }
+ }
+ } else {
+ /* verify the packet byte count */
+ if (count != td->max_packet_size) {
+ if (count < td->max_packet_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ td->got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error_any = 1;
+
+ /* release FIFO */
+ dwc_otg_common_rx_ack(sc);
+ goto complete;
+ }
+ }
+ td->toggle ^= 1;
+ td->tt_scheduled = 0;
+ }
+
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error_any = 1;
+
+ /* release FIFO */
+ dwc_otg_common_rx_ack(sc);
+ goto complete;
+ }
+
+ /* read data from FIFO */
+ dwc_otg_read_fifo(sc, td->pc, td->offset, count);
+
+ td->remainder -= count;
+ td->offset += count;
+ sc->sc_chan_state[channel].hcint |= HCINT_SOFTWARE_ONLY;
+ break;
+ default:
+ break;
+ }
+ /* release FIFO */
+ dwc_otg_common_rx_ack(sc);
+busy:
+ return (0);
+complete:
+ return (1);
+}
+
+static uint8_t
+dwc_otg_host_data_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td)
+{
+ uint32_t hcint = 0;
+ uint32_t hcchar;
+ uint8_t delta;
+ uint8_t channel;
+ uint8_t x;
+
+ for (x = 0; x != td->max_packet_count; x++) {
+ channel = td->channel[x];
+ if (channel >= DWC_OTG_MAX_CHANNELS)
+ continue;
+ hcint |= sc->sc_chan_state[channel].hcint;
+
+ DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n",
+ channel, td->state, hcint,
+ DWC_OTG_READ_4(sc, DOTG_HCCHAR(channel)),
+ DWC_OTG_READ_4(sc, DOTG_HCTSIZ(channel)));
+
+ /* check interrupt bits */
+ if (hcint & (HCINT_RETRY |
+ HCINT_ACK | HCINT_NYET)) {
+ /* give success bits priority over failure bits */
+ } else if (hcint & HCINT_STALL) {
+ DPRINTF("CH=%d STALL\n", channel);
+ td->error_stall = 1;
+ td->error_any = 1;
+ goto complete;
+ } else if (hcint & HCINT_ERRORS) {
+ DPRINTF("CH=%d ERROR\n", channel);
+ td->errcnt++;
+ if (td->hcsplt != 0 || td->errcnt >= 3) {
+ if (td->ep_type != UE_ISOCHRONOUS) {
+ td->error_any = 1;
+ goto complete;
+ }
+ }
+ }
+
+ /* check channels for data, if any */
+ if (dwc_otg_host_data_rx_sub(sc, td, channel))
+ goto complete;
+
+ /* refresh interrupt status */
+ hcint |= sc->sc_chan_state[channel].hcint;
+
+ if (hcint & (HCINT_ERRORS | HCINT_RETRY |
+ HCINT_ACK | HCINT_NYET)) {
+ if (!(hcint & HCINT_ERRORS))
+ td->errcnt = 0;
+ }
+ }
+
+ switch (td->state) {
+ case DWC_CHAN_ST_START:
+ if (td->hcsplt != 0)
+ goto receive_spkt;
+ else
+ goto receive_pkt;
+
+ case DWC_CHAN_ST_WAIT_ANE:
+ if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+ if (td->ep_type == UE_INTERRUPT) {
+ /*
+ * The USB specification does not
+ * mandate a particular data toggle
+ * value for USB INTERRUPT
+ * transfers. Switch the data toggle
+ * value to receive the packet
+ * correctly:
+ */
+ if (hcint & HCINT_DATATGLERR) {
+ DPRINTF("Retrying packet due to "
+ "data toggle error\n");
+ td->toggle ^= 1;
+ goto receive_pkt;
+ }
+ } else if (td->ep_type == UE_ISOCHRONOUS) {
+ if (td->hcsplt != 0) {
+ /*
+ * Sometimes the complete
+ * split packet may be queued
+ * too early and the
+ * transaction translator will
+ * return a NAK. Ignore
+ * this message and retry the
+ * complete split instead.
+ */
+ DPRINTF("Retrying complete split\n");
+ goto receive_pkt;
+ }
+ goto complete;
+ }
+ td->did_nak = 1;
+ td->tt_scheduled = 0;
+ if (td->hcsplt != 0)
+ goto receive_spkt;
+ else
+ goto receive_pkt;
+ } else if (hcint & HCINT_NYET) {
+ if (td->hcsplt != 0) {
+ /* try again */
+ goto receive_pkt;
+ } else {
+ /* not a valid token for IN endpoints */
+ td->error_any = 1;
+ goto complete;
+ }
+ } else if (hcint & HCINT_ACK) {
+ /* wait for data - ACK arrived first */
+ if (!(hcint & HCINT_SOFTWARE_ONLY))
+ goto busy;
+
+ if (td->ep_type == UE_ISOCHRONOUS) {
+ /* check if we are complete */
+ if (td->tt_xactpos == HCSPLT_XACTPOS_BEGIN) {
+ goto complete;
+ } else if (td->hcsplt != 0) {
+ goto receive_pkt;
+ } else {
+ /* get more packets */
+ goto busy;
+ }
+ } else {
+ /* check if we are complete */
+ if ((td->remainder == 0) || (td->got_short != 0)) {
+ if (td->short_pkt)
+ goto complete;
+
+ /*
+ * Else need to receive a zero length
+ * packet.
+ */
+ }
+ td->tt_scheduled = 0;
+ td->did_nak = 0;
+ if (td->hcsplt != 0)
+ goto receive_spkt;
+ else
+ goto receive_pkt;
+ }
+ }
+ break;
+
+ case DWC_CHAN_ST_WAIT_S_ANE:
+ /*
+ * NOTE: The DWC OTG hardware provides a fake ACK in
+ * case of interrupt and isochronous transfers:
+ */
+ if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+ td->did_nak = 1;
+ td->tt_scheduled = 0;
+ goto receive_spkt;
+ } else if (hcint & HCINT_NYET) {
+ td->tt_scheduled = 0;
+ goto receive_spkt;
+ } else if (hcint & HCINT_ACK) {
+ td->did_nak = 0;
+ goto receive_pkt;
+ }
+ break;
+
+ case DWC_CHAN_ST_WAIT_C_PKT:
+ goto receive_pkt;
+
+ default:
+ break;
+ }
+ goto busy;
+
+receive_pkt:
+ /* free existing channel, if any */
+ dwc_otg_host_channel_free(sc, td);
+
+ if (td->hcsplt != 0) {
+ delta = td->tt_complete_slot - sc->sc_last_frame_num - 1;
+ if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) {
+ if (td->ep_type != UE_ISOCHRONOUS) {
+ td->state = DWC_CHAN_ST_WAIT_C_PKT;
+ goto busy;
+ }
+ }
+ delta = sc->sc_last_frame_num - td->tt_start_slot;
+ if (delta > DWC_OTG_TT_SLOT_MAX) {
+ if (td->ep_type != UE_ISOCHRONOUS) {
+ /* we missed the service interval */
+ td->error_any = 1;
+ }
+ goto complete;
+ }
+ /* complete split */
+ td->hcsplt |= HCSPLT_COMPSPLT;
+ } else if (dwc_otg_host_rate_check(sc, td)) {
+ td->state = DWC_CHAN_ST_WAIT_C_PKT;
+ goto busy;
+ }
+
+ /* allocate a new channel */
+ if (dwc_otg_host_channel_alloc(sc, td, 0)) {
+ td->state = DWC_CHAN_ST_WAIT_C_PKT;
+ goto busy;
+ }
+
+ /* set toggle, if any */
+ if (td->set_toggle) {
+ td->set_toggle = 0;
+ td->toggle = 1;
+ }
+
+ td->state = DWC_CHAN_ST_WAIT_ANE;
+
+ for (x = 0; x != td->max_packet_count; x++) {
+ channel = td->channel[x];
+
+ /* receive one packet */
+ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel),
+ (td->max_packet_size << HCTSIZ_XFERSIZE_SHIFT) |
+ (1 << HCTSIZ_PKTCNT_SHIFT) |
+ (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) :
+ (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)));
+
+ DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(channel), td->hcsplt);
+
+ hcchar = td->hcchar;
+ hcchar |= HCCHAR_EPDIR_IN;
+
+ if (td->ep_type == UE_ISOCHRONOUS) {
+ if (td->hcsplt != 0) {
+ /* continously buffer */
+ if (sc->sc_last_frame_num & 1)
+ hcchar &= ~HCCHAR_ODDFRM;
+ else
+ hcchar |= HCCHAR_ODDFRM;
+ } else {
+ /* multi buffer, if any */
+ if (sc->sc_last_frame_num & 1)
+ hcchar |= HCCHAR_ODDFRM;
+ else
+ hcchar &= ~HCCHAR_ODDFRM;
+ }
+ } else {
+ hcchar &= ~HCCHAR_ODDFRM;
+ }
+
+ /* must enable channel before data can be received */
+ DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar);
+ }
+ /* wait until next slot before trying complete split */
+ td->tt_complete_slot = sc->sc_last_frame_num + 1;
+
+ goto busy;
+
+receive_spkt:
+ /* free existing channel(s), if any */
+ dwc_otg_host_channel_free(sc, td);
+
+ delta = td->tt_start_slot - sc->sc_last_frame_num - 1;
+ if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) {
+ td->state = DWC_CHAN_ST_START;
+ goto busy;
+ }
+ delta = sc->sc_last_frame_num - td->tt_start_slot;
+ if (delta > 5) {
+ /* missed it */
+ td->tt_scheduled = 0;
+ td->state = DWC_CHAN_ST_START;
+ goto busy;
+ }
+
+ /* allocate a new channel */
+ if (dwc_otg_host_channel_alloc(sc, td, 0)) {
+ td->state = DWC_CHAN_ST_START;
+ goto busy;
+ }
+
+ channel = td->channel[0];
+
+ td->hcsplt &= ~HCSPLT_COMPSPLT;
+ td->state = DWC_CHAN_ST_WAIT_S_ANE;
+
+ /* receive one packet */
+ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel),
+ (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT));
+
+ DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(channel), td->hcsplt);
+
+ /* send after next SOF event */
+ if ((sc->sc_last_frame_num & 1) == 0 &&
+ td->ep_type == UE_ISOCHRONOUS)
+ td->hcchar |= HCCHAR_ODDFRM;
+ else
+ td->hcchar &= ~HCCHAR_ODDFRM;
+
+ hcchar = td->hcchar;
+ hcchar |= HCCHAR_EPDIR_IN;
+
+ /* wait until next slot before trying complete split */
+ td->tt_complete_slot = sc->sc_last_frame_num + 1;
+
+ /* must enable channel before data can be received */
+ DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar);
+busy:
+ return (1); /* busy */
+
+complete:
+ dwc_otg_host_channel_free(sc, td);
+ return (0); /* complete */
+}
+
+static uint8_t
+dwc_otg_data_rx(struct dwc_otg_softc *sc, struct dwc_otg_td *td)
+{
+ uint32_t temp;
+ uint16_t count;
+ uint8_t got_short;
+
+ got_short = 0;
+
+ /* check endpoint status */
+ if (sc->sc_last_rx_status == 0)
+ goto not_complete;
+
+ if (GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status) != td->ep_no)
+ goto not_complete;
+
+ /* check for SETUP packet */
+ if ((sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) ==
+ GRXSTSRD_STP_DATA ||
+ (sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) ==
+ GRXSTSRD_STP_COMPLETE) {
+ if (td->remainder == 0) {
+ /*
+ * We are actually complete and have
+ * received the next SETUP
+ */
+ DPRINTFN(5, "faking complete\n");
+ return (0); /* complete */
+ }
+ /*
+ * USB Host Aborted the transfer.
+ */
+ td->error_any = 1;
+ return (0); /* complete */
+ }
+
+ if ((sc->sc_last_rx_status & GRXSTSRD_PKTSTS_MASK) !=
+ GRXSTSRD_OUT_DATA) {
+ /* release FIFO */
+ dwc_otg_common_rx_ack(sc);
+ goto not_complete;
+ }
+
+ /* get the packet byte count */
+ count = GRXSTSRD_BCNT_GET(sc->sc_last_rx_status);
+
+ /* verify the packet byte count */
+ if (count != td->max_packet_size) {
+ if (count < td->max_packet_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error_any = 1;
+
+ /* release FIFO */
+ dwc_otg_common_rx_ack(sc);
+ return (0); /* we are complete */
+ }
+ }
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error_any = 1;
+
+ /* release FIFO */
+ dwc_otg_common_rx_ack(sc);
+ return (0); /* we are complete */
+ }
+
+ /* read data from FIFO */
+ dwc_otg_read_fifo(sc, td->pc, td->offset, count);
+
+ td->remainder -= count;
+ td->offset += count;
+
+ /* release FIFO */
+ dwc_otg_common_rx_ack(sc);
+
+ temp = sc->sc_out_ctl[td->ep_no];
+
+ /* check for isochronous mode */
+ if ((temp & DIEPCTL_EPTYPE_MASK) ==
+ (DIEPCTL_EPTYPE_ISOC << DIEPCTL_EPTYPE_SHIFT)) {
+ /* toggle odd or even frame bit */
+ if (temp & DIEPCTL_SETD1PID) {
+ temp &= ~DIEPCTL_SETD1PID;
+ temp |= DIEPCTL_SETD0PID;
+ } else {
+ temp &= ~DIEPCTL_SETD0PID;
+ temp |= DIEPCTL_SETD1PID;
+ }
+ sc->sc_out_ctl[td->ep_no] = temp;
+ }
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+
+not_complete:
+
+ /* enable SETUP and transfer complete interrupt */
+ if (td->ep_no == 0) {
+ DWC_OTG_WRITE_4(sc, DOTG_DOEPTSIZ(0),
+ DXEPTSIZ_SET_MULTI(3) |
+ DXEPTSIZ_SET_NPKT(1) |
+ DXEPTSIZ_SET_NBYTES(td->max_packet_size));
+ } else {
+ /* allow reception of multiple packets */
+ DWC_OTG_WRITE_4(sc, DOTG_DOEPTSIZ(td->ep_no),
+ DXEPTSIZ_SET_MULTI(1) |
+ DXEPTSIZ_SET_NPKT(4) |
+ DXEPTSIZ_SET_NBYTES(4 *
+ ((td->max_packet_size + 3) & ~3)));
+ }
+ temp = sc->sc_out_ctl[td->ep_no];
+ DWC_OTG_WRITE_4(sc, DOTG_DOEPCTL(td->ep_no), temp |
+ DOEPCTL_EPENA | DOEPCTL_CNAK);
+
+ return (1); /* not complete */
+}
+
+static uint8_t
+dwc_otg_host_data_tx(struct dwc_otg_softc *sc, struct dwc_otg_td *td)
+{
+ uint32_t count;
+ uint32_t hcint;
+ uint32_t hcchar;
+ uint8_t delta;
+ uint8_t channel;
+ uint8_t x;
+
+ dwc_otg_host_dump_rx(sc, td);
+
+ /* check that last channel is complete */
+ channel = td->channel[td->npkt];
+
+ if (channel < DWC_OTG_MAX_CHANNELS) {
+ hcint = sc->sc_chan_state[channel].hcint;
+
+ DPRINTF("CH=%d ST=%d HCINT=0x%08x HCCHAR=0x%08x HCTSIZ=0x%08x\n",
+ channel, td->state, hcint,
+ DWC_OTG_READ_4(sc, DOTG_HCCHAR(channel)),
+ DWC_OTG_READ_4(sc, DOTG_HCTSIZ(channel)));
+
+ if (hcint & (HCINT_RETRY |
+ HCINT_ACK | HCINT_NYET)) {
+ /* give success bits priority over failure bits */
+ } else if (hcint & HCINT_STALL) {
+ DPRINTF("CH=%d STALL\n", channel);
+ td->error_stall = 1;
+ td->error_any = 1;
+ goto complete;
+ } else if (hcint & HCINT_ERRORS) {
+ DPRINTF("CH=%d ERROR\n", channel);
+ td->errcnt++;
+ if (td->hcsplt != 0 || td->errcnt >= 3) {
+ td->error_any = 1;
+ goto complete;
+ }
+ }
+
+ if (hcint & (HCINT_ERRORS | HCINT_RETRY |
+ HCINT_ACK | HCINT_NYET)) {
+ if (!(hcint & HCINT_ERRORS))
+ td->errcnt = 0;
+ }
+ } else {
+ hcint = 0;
+ }
+
+ switch (td->state) {
+ case DWC_CHAN_ST_START:
+ goto send_pkt;
+
+ case DWC_CHAN_ST_WAIT_ANE:
+ if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+ td->did_nak = 1;
+ td->tt_scheduled = 0;
+ goto send_pkt;
+ } else if (hcint & (HCINT_ACK | HCINT_NYET)) {
+ td->offset += td->tx_bytes;
+ td->remainder -= td->tx_bytes;
+ td->toggle ^= 1;
+ /* check if next response will be a NAK */
+ if (hcint & HCINT_NYET)
+ td->did_nak = 1;
+ else
+ td->did_nak = 0;
+ td->tt_scheduled = 0;
+
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt)
+ goto complete;
+
+ /*
+ * Else we need to transmit a short
+ * packet:
+ */
+ }
+ goto send_pkt;
+ }
+ break;
+
+ case DWC_CHAN_ST_WAIT_S_ANE:
+ if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+ td->did_nak = 1;
+ td->tt_scheduled = 0;
+ goto send_pkt;
+ } else if (hcint & (HCINT_ACK | HCINT_NYET)) {
+ td->did_nak = 0;
+ goto send_cpkt;
+ }
+ break;
+
+ case DWC_CHAN_ST_WAIT_C_ANE:
+ if (hcint & HCINT_NYET) {
+ goto send_cpkt;
+ } else if (hcint & (HCINT_RETRY | HCINT_ERRORS)) {
+ td->did_nak = 1;
+ td->tt_scheduled = 0;
+ goto send_pkt;
+ } else if (hcint & HCINT_ACK) {
+ td->offset += td->tx_bytes;
+ td->remainder -= td->tx_bytes;
+ td->toggle ^= 1;
+ td->did_nak = 0;
+ td->tt_scheduled = 0;
+
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt)
+ goto complete;
+
+ /* else we need to transmit a short packet */
+ }
+ goto send_pkt;
+ }
+ break;
+
+ case DWC_CHAN_ST_WAIT_C_PKT:
+ goto send_cpkt;
+
+ case DWC_CHAN_ST_TX_WAIT_ISOC:
+ /* Check if ISOCHRONOUS OUT traffic is complete */
+ if ((hcint & HCINT_HCH_DONE_MASK) == 0)
+ break;
+
+ td->offset += td->tx_bytes;
+ td->remainder -= td->tx_bytes;
+ goto complete;
+ default:
+ break;
+ }
+ goto busy;
+
+send_pkt:
+ /* free existing channel(s), if any */
+ dwc_otg_host_channel_free(sc, td);
+
+ if (td->hcsplt != 0) {
+ delta = td->tt_start_slot - sc->sc_last_frame_num - 1;
+ if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) {
+ td->state = DWC_CHAN_ST_START;
+ goto busy;
+ }
+ delta = sc->sc_last_frame_num - td->tt_start_slot;
+ if (delta > 5) {
+ /* missed it */
+ td->tt_scheduled = 0;
+ td->state = DWC_CHAN_ST_START;
+ goto busy;
+ }
+ } else if (dwc_otg_host_rate_check(sc, td)) {
+ td->state = DWC_CHAN_ST_START;
+ goto busy;
+ }
+
+ /* allocate a new channel */
+ if (dwc_otg_host_channel_alloc(sc, td, 1)) {
+ td->state = DWC_CHAN_ST_START;
+ goto busy;
+ }
+
+ /* set toggle, if any */
+ if (td->set_toggle) {
+ td->set_toggle = 0;
+ td->toggle = 1;
+ }
+
+ if (td->ep_type == UE_ISOCHRONOUS) {
+ /* ISOCHRONOUS OUT transfers don't have any ACKs */
+ td->state = DWC_CHAN_ST_TX_WAIT_ISOC;
+ td->hcsplt &= ~HCSPLT_COMPSPLT;
+ if (td->hcsplt != 0) {
+ /* get maximum transfer length */
+ count = td->remainder;
+ if (count > HCSPLT_XACTLEN_BURST) {
+ DPRINTF("TT overflow\n");
+ td->error_any = 1;
+ goto complete;
+ }
+ /* Update transaction position */
+ td->hcsplt &= ~HCSPLT_XACTPOS_MASK;
+ td->hcsplt |= (HCSPLT_XACTPOS_ALL << HCSPLT_XACTPOS_SHIFT);
+ }
+ } else if (td->hcsplt != 0) {
+ td->hcsplt &= ~HCSPLT_COMPSPLT;
+ /* Wait for ACK/NAK/ERR from TT */
+ td->state = DWC_CHAN_ST_WAIT_S_ANE;
+ } else {
+ /* Wait for ACK/NAK/STALL from device */
+ td->state = DWC_CHAN_ST_WAIT_ANE;
+ }
+
+ td->tx_bytes = 0;
+
+ for (x = 0; x != td->max_packet_count; x++) {
+ uint32_t rem_bytes;
+
+ channel = td->channel[x];
+
+ /* send one packet at a time */
+ count = td->max_packet_size;
+ rem_bytes = td->remainder - td->tx_bytes;
+ if (rem_bytes < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = rem_bytes;
+ }
+ if (count == rem_bytes) {
+ /* last packet */
+ switch (x) {
+ case 0:
+ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel),
+ (count << HCTSIZ_XFERSIZE_SHIFT) |
+ (1 << HCTSIZ_PKTCNT_SHIFT) |
+ (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) :
+ (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)));
+ break;
+ case 1:
+ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel),
+ (count << HCTSIZ_XFERSIZE_SHIFT) |
+ (1 << HCTSIZ_PKTCNT_SHIFT) |
+ (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT));
+ break;
+ default:
+ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel),
+ (count << HCTSIZ_XFERSIZE_SHIFT) |
+ (1 << HCTSIZ_PKTCNT_SHIFT) |
+ (HCTSIZ_PID_DATA2 << HCTSIZ_PID_SHIFT));
+ break;
+ }
+ } else if (td->ep_type == UE_ISOCHRONOUS &&
+ td->max_packet_count > 1) {
+ /* ISOCHRONOUS multi packet */
+ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel),
+ (count << HCTSIZ_XFERSIZE_SHIFT) |
+ (1 << HCTSIZ_PKTCNT_SHIFT) |
+ (HCTSIZ_PID_MDATA << HCTSIZ_PID_SHIFT));
+ } else {
+ /* TODO: HCTSIZ_DOPNG */
+ /* standard BULK/INTERRUPT/CONTROL packet */
+ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel),
+ (count << HCTSIZ_XFERSIZE_SHIFT) |
+ (1 << HCTSIZ_PKTCNT_SHIFT) |
+ (td->toggle ? (HCTSIZ_PID_DATA1 << HCTSIZ_PID_SHIFT) :
+ (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT)));
+ }
+
+ DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(channel), td->hcsplt);
+
+ hcchar = td->hcchar;
+ hcchar &= ~HCCHAR_EPDIR_IN;
+
+ /* send after next SOF event */
+ if ((sc->sc_last_frame_num & 1) == 0 &&
+ td->ep_type == UE_ISOCHRONOUS)
+ hcchar |= HCCHAR_ODDFRM;
+ else
+ hcchar &= ~HCCHAR_ODDFRM;
+
+ /* must enable before writing data to FIFO */
+ DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar);
+
+ if (count != 0) {
+ /* write data into FIFO */
+ dwc_otg_write_fifo(sc, td->pc, td->offset +
+ td->tx_bytes, DOTG_DFIFO(channel), count);
+ }
+
+ /* store number of bytes transmitted */
+ td->tx_bytes += count;
+
+ /* store last packet index */
+ td->npkt = x;
+
+ /* check for last packet */
+ if (count == rem_bytes)
+ break;
+ }
+ goto busy;
+
+send_cpkt:
+ /* free existing channel, if any */
+ dwc_otg_host_channel_free(sc, td);
+
+ delta = td->tt_complete_slot - sc->sc_last_frame_num - 1;
+ if (td->tt_scheduled == 0 || delta < DWC_OTG_TT_SLOT_MAX) {
+ td->state = DWC_CHAN_ST_WAIT_C_PKT;
+ goto busy;
+ }
+ delta = sc->sc_last_frame_num - td->tt_start_slot;
+ if (delta > DWC_OTG_TT_SLOT_MAX) {
+ /* we missed the service interval */
+ if (td->ep_type != UE_ISOCHRONOUS)
+ td->error_any = 1;
+ goto complete;
+ }
+
+ /* allocate a new channel */
+ if (dwc_otg_host_channel_alloc(sc, td, 0)) {
+ td->state = DWC_CHAN_ST_WAIT_C_PKT;
+ goto busy;
+ }
+
+ channel = td->channel[0];
+
+ td->hcsplt |= HCSPLT_COMPSPLT;
+ td->state = DWC_CHAN_ST_WAIT_C_ANE;
+
+ DWC_OTG_WRITE_4(sc, DOTG_HCTSIZ(channel),
+ (HCTSIZ_PID_DATA0 << HCTSIZ_PID_SHIFT));
+
+ DWC_OTG_WRITE_4(sc, DOTG_HCSPLT(channel), td->hcsplt);
+
+ hcchar = td->hcchar;
+ hcchar &= ~HCCHAR_EPDIR_IN;
+
+ /* receive complete split ASAP */
+ if ((sc->sc_last_frame_num & 1) != 0 &&
+ td->ep_type == UE_ISOCHRONOUS)
+ hcchar |= HCCHAR_ODDFRM;
+ else
+ hcchar &= ~HCCHAR_ODDFRM;
+
+ /* must enable channel before data can be received */
+ DWC_OTG_WRITE_4(sc, DOTG_HCCHAR(channel), hcchar);
+
+ /* wait until next slot before trying complete split */
+ td->tt_complete_slot = sc->sc_last_frame_num + 1;
+busy:
+ return (1); /* busy */
+
+complete:
+ dwc_otg_host_channel_free(sc, td);
+ return (0); /* complete */
+}
+
+static uint8_t
+dwc_otg_data_tx(struct dwc_otg_softc *sc, struct dwc_otg_td *td)
+{
+ uint32_t max_buffer;
+ uint32_t count;
+ uint32_t fifo_left;
+ uint32_t mpkt;
+ uint32_t temp;
+ uint8_t to;
+
+ to = 3; /* don't loop forever! */
+
+ max_buffer = sc->sc_hw_ep_profile[td->ep_no].max_buffer;
+
+repeat:
+ /* check for endpoint 0 data */
+
+ temp = sc->sc_last_rx_status;
+
+ if ((td->ep_no == 0) && (temp != 0) &&
+ (GRXSTSRD_CHNUM_GET(temp) == 0)) {
+ if ((temp & GRXSTSRD_PKTSTS_MASK) !=
+ GRXSTSRD_STP_DATA &&
+ (temp & GRXSTSRD_PKTSTS_MASK) !=
+ GRXSTSRD_STP_COMPLETE) {
+ /* dump data - wrong direction */
+ dwc_otg_common_rx_ack(sc);
+ } else {
+ /*
+ * The current transfer was cancelled
+ * by the USB Host:
+ */
+ td->error_any = 1;
+ return (0); /* complete */
+ }
+ }
+
+ /* fill in more TX data, if possible */
+ if (td->tx_bytes != 0) {
+ uint16_t cpkt;
+
+ /* check if packets have been transferred */
+ temp = DWC_OTG_READ_4(sc, DOTG_DIEPTSIZ(td->ep_no));
+
+ /* get current packet number */
+ cpkt = DXEPTSIZ_GET_NPKT(temp);
+
+ if (cpkt >= td->npkt) {
+ fifo_left = 0;
+ } else {
+ if (max_buffer != 0) {
+ fifo_left = (td->npkt - cpkt) *
+ td->max_packet_size;
+
+ if (fifo_left > max_buffer)
+ fifo_left = max_buffer;
+ } else {
+ fifo_left = td->max_packet_size;
+ }
+ }
+
+ count = td->tx_bytes;
+ if (count > fifo_left)
+ count = fifo_left;
+
+ if (count != 0) {
+ /* write data into FIFO */
+ dwc_otg_write_fifo(sc, td->pc, td->offset,
+ DOTG_DFIFO(td->ep_no), count);
+
+ td->tx_bytes -= count;
+ td->remainder -= count;
+ td->offset += count;
+ td->npkt = cpkt;
+ }
+ if (td->tx_bytes != 0)
+ goto not_complete;
+
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt)
+ return (0); /* complete */
+
+ /* else we need to transmit a short packet */
+ }
+ }
+
+ if (!to--)
+ goto not_complete;
+
+ /* check if not all packets have been transferred */
+ temp = DWC_OTG_READ_4(sc, DOTG_DIEPTSIZ(td->ep_no));
+
+ if (DXEPTSIZ_GET_NPKT(temp) != 0) {
+ DPRINTFN(5, "busy ep=%d npkt=%d DIEPTSIZ=0x%08x "
+ "DIEPCTL=0x%08x\n", td->ep_no,
+ DXEPTSIZ_GET_NPKT(temp),
+ temp, DWC_OTG_READ_4(sc, DOTG_DIEPCTL(td->ep_no)));
+
+ goto not_complete;
+ }
+
+ DPRINTFN(5, "rem=%u ep=%d\n", td->remainder, td->ep_no);
+
+ /* try to optimise by sending more data */
+ if ((max_buffer != 0) && ((td->max_packet_size & 3) == 0)) {
+ /* send multiple packets at the same time */
+ mpkt = max_buffer / td->max_packet_size;
+
+ if (mpkt > 0x3FE)
+ mpkt = 0x3FE;
+
+ count = td->remainder;
+ if (count > 0x7FFFFF)
+ count = 0x7FFFFF - (0x7FFFFF % td->max_packet_size);
+
+ td->npkt = count / td->max_packet_size;
+
+ /*
+ * NOTE: We could use 0x3FE instead of "mpkt" in the
+ * check below to get more throughput, but then we
+ * have a dependency towards non-generic chip features
+ * to disable the TX-FIFO-EMPTY interrupts on a per
+ * endpoint basis. Increase the maximum buffer size of
+ * the IN endpoint to increase the performance.
+ */
+ if (td->npkt > mpkt) {
+ td->npkt = mpkt;
+ count = td->max_packet_size * mpkt;
+ } else if ((count == 0) || (count % td->max_packet_size)) {
+ /* we are transmitting a short packet */
+ td->npkt++;
+ td->short_pkt = 1;
+ }
+ } else {
+ /* send one packet at a time */
+ mpkt = 1;
+ count = td->max_packet_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ td->npkt = 1;
+ }
+ DWC_OTG_WRITE_4(sc, DOTG_DIEPTSIZ(td->ep_no),
+ DXEPTSIZ_SET_MULTI(1) |
+ DXEPTSIZ_SET_NPKT(td->npkt) |
+ DXEPTSIZ_SET_NBYTES(count));
+
+ /* make room for buffering */
+ td->npkt += mpkt;
+
+ temp = sc->sc_in_ctl[td->ep_no];
+
+ /* check for isochronous mode */
+ if ((temp & DIEPCTL_EPTYPE_MASK) ==
+ (DIEPCTL_EPTYPE_ISOC << DIEPCTL_EPTYPE_SHIFT)) {
+ /* toggle odd or even frame bit */
+ if (temp & DIEPCTL_SETD1PID) {
+ temp &= ~DIEPCTL_SETD1PID;
+ temp |= DIEPCTL_SETD0PID;
+ } else {
+ temp &= ~DIEPCTL_SETD0PID;
+ temp |= DIEPCTL_SETD1PID;
+ }
+ sc->sc_in_ctl[td->ep_no] = temp;
+ }
+
+ /* must enable before writing data to FIFO */
+ DWC_OTG_WRITE_4(sc, DOTG_DIEPCTL(td->ep_no), temp |
+ DIEPCTL_EPENA | DIEPCTL_CNAK);
+
+ td->tx_bytes = count;
+
+ /* check remainder */
+ if (td->tx_bytes == 0 &&
+ td->remainder == 0) {
+ if (td->short_pkt)
+ return (0); /* complete */
+
+ /* else we need to transmit a short packet */
+ }
+ goto repeat;
+
+not_complete:
+ return (1); /* not complete */
+}
+
+static uint8_t
+dwc_otg_data_tx_sync(struct dwc_otg_softc *sc, struct dwc_otg_td *td)
+{
+ uint32_t temp;
+
+ /*
+ * If all packets are transferred we are complete:
+ */
+ temp = DWC_OTG_READ_4(sc, DOTG_DIEPTSIZ(td->ep_no));
+
+ /* check that all packets have been transferred */
+ if (DXEPTSIZ_GET_NPKT(temp) != 0) {
+ DPRINTFN(5, "busy ep=%d\n", td->ep_no);
+ goto not_complete;
+ }
+ return (0);
+
+not_complete:
+
+ /* we only want to know if there is a SETUP packet or free IN packet */
+
+ temp = sc->sc_last_rx_status;
+
+ if ((td->ep_no == 0) && (temp != 0) &&
+ (GRXSTSRD_CHNUM_GET(temp) == 0)) {
+ if ((temp & GRXSTSRD_PKTSTS_MASK) ==
+ GRXSTSRD_STP_DATA ||
+ (temp & GRXSTSRD_PKTSTS_MASK) ==
+ GRXSTSRD_STP_COMPLETE) {
+ DPRINTFN(5, "faking complete\n");
+ /*
+ * Race condition: We are complete!
+ */
+ return (0);
+ } else {
+ /* dump data - wrong direction */
+ dwc_otg_common_rx_ack(sc);
+ }
+ }
+ return (1); /* not complete */
+}
+
+static void
+dwc_otg_xfer_do_fifo(struct dwc_otg_softc *sc, struct usb_xfer *xfer)
+{
+ struct dwc_otg_td *td;
+ uint8_t toggle;
+ uint8_t tmr_val;
+ uint8_t tmr_res;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+ if (td == NULL)
+ return;
+
+ while (1) {
+ if ((td->func) (sc, td)) {
+ /* operation in progress */
+ break;
+ }
+ if (((void *)td) == xfer->td_transfer_last) {
+ goto done;
+ }
+ if (td->error_any) {
+ goto done;
+ } else if (td->remainder > 0) {
+ /*
+ * We had a short transfer. If there is no alternate
+ * next, stop processing !
+ */
+ if (!td->alt_next)
+ goto done;
+ }
+
+ /*
+ * Fetch the next transfer descriptor and transfer
+ * some flags to the next transfer descriptor
+ */
+ tmr_res = td->tmr_res;
+ tmr_val = td->tmr_val;
+ toggle = td->toggle;
+ td = td->obj_next;
+ xfer->td_transfer_cache = td;
+ td->toggle = toggle; /* transfer toggle */
+ td->tmr_res = tmr_res;
+ td->tmr_val = tmr_val;
+ }
+ return;
+
+done:
+ xfer->td_transfer_cache = NULL;
+ sc->sc_xfer_complete = 1;
+}
+
+static uint8_t
+dwc_otg_xfer_do_complete_locked(struct dwc_otg_softc *sc, struct usb_xfer *xfer)
+{
+ struct dwc_otg_td *td;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+ if (td == NULL) {
+ /* compute all actual lengths */
+ dwc_otg_standard_done(xfer);
+ return (1);
+ }
+ return (0);
+}
+
+static void
+dwc_otg_timer(void *_sc)
+{
+ struct dwc_otg_softc *sc = _sc;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTF("\n");
+
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+
+ /* increment timer value */
+ sc->sc_tmr_val++;
+
+ /* enable SOF interrupt, which will poll jobs */
+ dwc_otg_enable_sof_irq(sc);
+
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+
+ if (sc->sc_timer_active) {
+ /* restart timer */
+ usb_callout_reset(&sc->sc_timer,
+ hz / (1000 / DWC_OTG_HOST_TIMER_RATE),
+ &dwc_otg_timer, sc);
+ }
+}
+
+static void
+dwc_otg_timer_start(struct dwc_otg_softc *sc)
+{
+ if (sc->sc_timer_active != 0)
+ return;
+
+ sc->sc_timer_active = 1;
+
+ /* restart timer */
+ usb_callout_reset(&sc->sc_timer,
+ hz / (1000 / DWC_OTG_HOST_TIMER_RATE),
+ &dwc_otg_timer, sc);
+}
+
+static void
+dwc_otg_timer_stop(struct dwc_otg_softc *sc)
+{
+ if (sc->sc_timer_active == 0)
+ return;
+
+ sc->sc_timer_active = 0;
+
+ /* stop timer */
+ usb_callout_stop(&sc->sc_timer);
+}
+
+static uint16_t
+dwc_otg_compute_isoc_rx_tt_slot(struct dwc_otg_tt_info *pinfo)
+{
+ if (pinfo->slot_index < DWC_OTG_TT_SLOT_MAX)
+ pinfo->slot_index++;
+ return (pinfo->slot_index);
+}
+
+static uint8_t
+dwc_otg_update_host_transfer_schedule_locked(struct dwc_otg_softc *sc)
+{
+ TAILQ_HEAD(, usb_xfer) head;
+ struct usb_xfer *xfer;
+ struct usb_xfer *xfer_next;
+ struct dwc_otg_td *td;
+ uint16_t temp;
+ uint16_t slot;
+
+ temp = DWC_OTG_READ_4(sc, DOTG_HFNUM) & DWC_OTG_FRAME_MASK;
+
+ if (sc->sc_last_frame_num == temp)
+ return (0);
+
+ sc->sc_last_frame_num = temp;
+
+ TAILQ_INIT(&head);
+
+ if ((temp & 7) == 0) {
+ /* reset the schedule */
+ memset(sc->sc_tt_info, 0, sizeof(sc->sc_tt_info));
+
+ TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) {
+ td = xfer->td_transfer_cache;
+ if (td == NULL || td->ep_type != UE_ISOCHRONOUS)
+ continue;
+
+ /* check for IN direction */
+ if ((td->hcchar & HCCHAR_EPDIR_IN) != 0)
+ continue;
+
+ sc->sc_needsof = 1;
+
+ if (td->hcsplt == 0 || td->tt_scheduled != 0)
+ continue;
+
+ /* compute slot */
+ slot = dwc_otg_compute_isoc_rx_tt_slot(
+ sc->sc_tt_info + td->tt_index);
+ if (slot > 3) {
+ /*
+ * Not enough time to get complete
+ * split executed.
+ */
+ continue;
+ }
+ /* Delayed start */
+ td->tt_start_slot = temp + slot;
+ td->tt_scheduled = 1;
+ TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry);
+ TAILQ_INSERT_TAIL(&head, xfer, wait_entry);
+ }
+
+ TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) {
+ td = xfer->td_transfer_cache;
+ if (td == NULL || td->ep_type != UE_ISOCHRONOUS)
+ continue;
+
+ /* check for OUT direction */
+ if ((td->hcchar & HCCHAR_EPDIR_IN) == 0)
+ continue;
+
+ sc->sc_needsof = 1;
+
+ if (td->hcsplt == 0 || td->tt_scheduled != 0)
+ continue;
+
+ /* Start ASAP */
+ td->tt_start_slot = temp;
+ td->tt_scheduled = 1;
+ TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry);
+ TAILQ_INSERT_TAIL(&head, xfer, wait_entry);
+ }
+
+ TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) {
+ td = xfer->td_transfer_cache;
+ if (td == NULL || td->ep_type != UE_INTERRUPT)
+ continue;
+
+ if (td->tt_scheduled != 0) {
+ sc->sc_needsof = 1;
+ continue;
+ }
+
+ if (dwc_otg_host_rate_check_interrupt(sc, td))
+ continue;
+
+ if (td->hcsplt == 0) {
+ sc->sc_needsof = 1;
+ td->tt_scheduled = 1;
+ continue;
+ }
+
+ /* start ASAP */
+ td->tt_start_slot = temp;
+ sc->sc_needsof = 1;
+ td->tt_scheduled = 1;
+ TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry);
+ TAILQ_INSERT_TAIL(&head, xfer, wait_entry);
+ }
+
+ TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) {
+ td = xfer->td_transfer_cache;
+ if (td == NULL ||
+ td->ep_type != UE_CONTROL) {
+ continue;
+ }
+
+ sc->sc_needsof = 1;
+
+ if (td->hcsplt == 0 || td->tt_scheduled != 0)
+ continue;
+
+ /* start ASAP */
+ td->tt_start_slot = temp;
+ td->tt_scheduled = 1;
+ TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry);
+ TAILQ_INSERT_TAIL(&head, xfer, wait_entry);
+ }
+ }
+ if ((temp & 7) < 6) {
+ TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) {
+ td = xfer->td_transfer_cache;
+ if (td == NULL ||
+ td->ep_type != UE_BULK) {
+ continue;
+ }
+
+ sc->sc_needsof = 1;
+
+ if (td->hcsplt == 0 || td->tt_scheduled != 0)
+ continue;
+
+ /* start ASAP */
+ td->tt_start_slot = temp;
+ td->tt_scheduled = 1;
+ TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry);
+ TAILQ_INSERT_TAIL(&head, xfer, wait_entry);
+ }
+ }
+
+ /* Put TT transfers in execution order at the end */
+ TAILQ_CONCAT(&sc->sc_bus.intr_q.head, &head, wait_entry);
+
+ /* move all TT transfers in front, keeping the current order */
+ TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) {
+ td = xfer->td_transfer_cache;
+ if (td == NULL || td->hcsplt == 0)
+ continue;
+ TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry);
+ TAILQ_INSERT_TAIL(&head, xfer, wait_entry);
+ }
+ TAILQ_CONCAT(&head, &sc->sc_bus.intr_q.head, wait_entry);
+ TAILQ_CONCAT(&sc->sc_bus.intr_q.head, &head, wait_entry);
+
+ /* put non-TT non-ISOCHRONOUS transfers last */
+ TAILQ_FOREACH_SAFE(xfer, &sc->sc_bus.intr_q.head, wait_entry, xfer_next) {
+ td = xfer->td_transfer_cache;
+ if (td == NULL || td->hcsplt != 0 || td->ep_type == UE_ISOCHRONOUS)
+ continue;
+ TAILQ_REMOVE(&sc->sc_bus.intr_q.head, xfer, wait_entry);
+ TAILQ_INSERT_TAIL(&head, xfer, wait_entry);
+ }
+ TAILQ_CONCAT(&sc->sc_bus.intr_q.head, &head, wait_entry);
+
+ if ((temp & 7) == 0) {
+ DPRINTFN(12, "SOF interrupt #%d, needsof=%d\n",
+ (int)temp, (int)sc->sc_needsof);
+
+ /* update SOF IRQ mask */
+ if (sc->sc_irq_mask & GINTMSK_SOFMSK) {
+ if (sc->sc_needsof == 0) {
+ sc->sc_irq_mask &= ~GINTMSK_SOFMSK;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+ }
+ } else {
+ if (sc->sc_needsof != 0) {
+ sc->sc_irq_mask |= GINTMSK_SOFMSK;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+ }
+ }
+
+ /* clear need SOF flag */
+ sc->sc_needsof = 0;
+ }
+ return (1);
+}
+
+static void
+dwc_otg_interrupt_poll_locked(struct dwc_otg_softc *sc)
+{
+ struct usb_xfer *xfer;
+ uint32_t count;
+ uint32_t temp;
+ uint32_t haint;
+ uint8_t got_rx_status;
+ uint8_t x;
+
+ if (sc->sc_flags.status_device_mode == 0) {
+ /*
+ * Update host transfer schedule, so that new
+ * transfers can be issued:
+ */
+ dwc_otg_update_host_transfer_schedule_locked(sc);
+ }
+ count = 0;
+repeat:
+ if (++count == 16) {
+ /* give other interrupts a chance */
+ DPRINTF("Yield\n");
+ return;
+ }
+
+ /* get all host channel interrupts */
+ haint = DWC_OTG_READ_4(sc, DOTG_HAINT);
+ while (1) {
+ x = ffs(haint) - 1;
+ if (x >= sc->sc_host_ch_max)
+ break;
+ temp = DWC_OTG_READ_4(sc, DOTG_HCINT(x));
+ DWC_OTG_WRITE_4(sc, DOTG_HCINT(x), temp);
+ temp &= ~HCINT_SOFTWARE_ONLY;
+ sc->sc_chan_state[x].hcint |= temp;
+ haint &= ~(1U << x);
+ }
+
+ if (sc->sc_last_rx_status == 0) {
+ temp = DWC_OTG_READ_4(sc, DOTG_GINTSTS);
+ if (temp & GINTSTS_RXFLVL) {
+ /* pop current status */
+ sc->sc_last_rx_status =
+ DWC_OTG_READ_4(sc, DOTG_GRXSTSPD);
+ }
+
+ if (sc->sc_last_rx_status != 0) {
+ uint8_t ep_no;
+
+ temp = sc->sc_last_rx_status &
+ GRXSTSRD_PKTSTS_MASK;
+
+ /* non-data messages we simply skip */
+ if (temp != GRXSTSRD_STP_DATA &&
+ temp != GRXSTSRD_STP_COMPLETE &&
+ temp != GRXSTSRD_OUT_DATA) {
+ /* check for halted channel */
+ if (temp == GRXSTSRH_HALTED) {
+ ep_no = GRXSTSRD_CHNUM_GET(sc->sc_last_rx_status);
+ sc->sc_chan_state[ep_no].wait_halted = 0;
+ DPRINTFN(5, "channel halt complete ch=%u\n", ep_no);
+ }
+ /* store bytes and FIFO offset */
+ sc->sc_current_rx_bytes = 0;
+ sc->sc_current_rx_fifo = 0;
+
+ /* acknowledge status */
+ dwc_otg_common_rx_ack(sc);
+ goto repeat;
+ }
+
+ temp = GRXSTSRD_BCNT_GET(
+ sc->sc_last_rx_status);
+ ep_no = GRXSTSRD_CHNUM_GET(
+ sc->sc_last_rx_status);
+
+ /* store bytes and FIFO offset */
+ sc->sc_current_rx_bytes = (temp + 3) & ~3;
+ sc->sc_current_rx_fifo = DOTG_DFIFO(ep_no);
+
+ DPRINTF("Reading %d bytes from ep %d\n", temp, ep_no);
+
+ /* check if we should dump the data */
+ if (!(sc->sc_active_rx_ep & (1U << ep_no))) {
+ dwc_otg_common_rx_ack(sc);
+ goto repeat;
+ }
+
+ got_rx_status = 1;
+
+ DPRINTFN(5, "RX status = 0x%08x: ch=%d pid=%d bytes=%d sts=%d\n",
+ sc->sc_last_rx_status, ep_no,
+ (sc->sc_last_rx_status >> 15) & 3,
+ GRXSTSRD_BCNT_GET(sc->sc_last_rx_status),
+ (sc->sc_last_rx_status >> 17) & 15);
+ } else {
+ got_rx_status = 0;
+ }
+ } else {
+ uint8_t ep_no;
+
+ ep_no = GRXSTSRD_CHNUM_GET(
+ sc->sc_last_rx_status);
+
+ /* check if we should dump the data */
+ if (!(sc->sc_active_rx_ep & (1U << ep_no))) {
+ dwc_otg_common_rx_ack(sc);
+ goto repeat;
+ }
+
+ got_rx_status = 1;
+ }
+
+ /* execute FIFOs */
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry)
+ dwc_otg_xfer_do_fifo(sc, xfer);
+
+ if (got_rx_status) {
+ /* check if data was consumed */
+ if (sc->sc_last_rx_status == 0)
+ goto repeat;
+
+ /* disable RX FIFO level interrupt */
+ sc->sc_irq_mask &= ~GINTMSK_RXFLVLMSK;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+ }
+}
+
+static void
+dwc_otg_interrupt_complete_locked(struct dwc_otg_softc *sc)
+{
+ struct usb_xfer *xfer;
+repeat:
+ /* scan for completion events */
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (dwc_otg_xfer_do_complete_locked(sc, xfer))
+ goto repeat;
+ }
+}
+
+static void
+dwc_otg_vbus_interrupt(struct dwc_otg_softc *sc, uint8_t is_on)
+{
+ DPRINTFN(5, "vbus = %u\n", is_on);
+
+ /*
+ * If the USB host mode is forced, then assume VBUS is always
+ * present else rely on the input to this function:
+ */
+ if ((is_on != 0) || (sc->sc_mode == DWC_MODE_HOST)) {
+ if (!sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 1;
+
+ /* complete root HUB interrupt endpoint */
+
+ dwc_otg_root_intr(sc);
+ }
+ } else {
+ if (sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* complete root HUB interrupt endpoint */
+
+ dwc_otg_root_intr(sc);
+ }
+ }
+}
+
+int
+dwc_otg_filter_interrupt(void *arg)
+{
+ struct dwc_otg_softc *sc = arg;
+ int retval = FILTER_HANDLED;
+ uint32_t status;
+
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+
+ /* read and clear interrupt status */
+ status = DWC_OTG_READ_4(sc, DOTG_GINTSTS);
+
+ /* clear interrupts we are handling here */
+ DWC_OTG_WRITE_4(sc, DOTG_GINTSTS, status & ~DWC_OTG_MSK_GINT_THREAD_IRQ);
+
+ /* check for USB state change interrupts */
+ if ((status & DWC_OTG_MSK_GINT_THREAD_IRQ) != 0)
+ retval = FILTER_SCHEDULE_THREAD;
+
+ /* clear FIFO empty interrupts */
+ if (status & sc->sc_irq_mask &
+ (GINTSTS_PTXFEMP | GINTSTS_NPTXFEMP)) {
+ sc->sc_irq_mask &= ~(GINTSTS_PTXFEMP | GINTSTS_NPTXFEMP);
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+ }
+ /* clear all IN endpoint interrupts */
+ if (status & GINTSTS_IEPINT) {
+ uint32_t temp;
+ uint8_t x;
+
+ for (x = 0; x != sc->sc_dev_in_ep_max; x++) {
+ temp = DWC_OTG_READ_4(sc, DOTG_DIEPINT(x));
+ /*
+ * NOTE: Need to clear all interrupt bits,
+ * because some appears to be unmaskable and
+ * can cause an interrupt loop:
+ */
+ if (temp != 0)
+ DWC_OTG_WRITE_4(sc, DOTG_DIEPINT(x), temp);
+ }
+ }
+
+ /* poll FIFOs, if any */
+ dwc_otg_interrupt_poll_locked(sc);
+
+ if (sc->sc_xfer_complete != 0)
+ retval = FILTER_SCHEDULE_THREAD;
+
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+
+ return (retval);
+}
+
+void
+dwc_otg_interrupt(void *arg)
+{
+ struct dwc_otg_softc *sc = arg;
+ uint32_t status;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+
+ /* read and clear interrupt status */
+ status = DWC_OTG_READ_4(sc, DOTG_GINTSTS);
+
+ /* clear interrupts we are handling here */
+ DWC_OTG_WRITE_4(sc, DOTG_GINTSTS, status & DWC_OTG_MSK_GINT_THREAD_IRQ);
+
+ DPRINTFN(14, "GINTSTS=0x%08x HAINT=0x%08x HFNUM=0x%08x\n",
+ status, DWC_OTG_READ_4(sc, DOTG_HAINT),
+ DWC_OTG_READ_4(sc, DOTG_HFNUM));
+
+ if (status & GINTSTS_USBRST) {
+ /* set correct state */
+ sc->sc_flags.status_device_mode = 1;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* Disable SOF interrupt */
+ sc->sc_irq_mask &= ~GINTMSK_SOFMSK;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+
+ /* complete root HUB interrupt endpoint */
+ dwc_otg_root_intr(sc);
+ }
+
+ /* check for any bus state change interrupts */
+ if (status & GINTSTS_ENUMDONE) {
+ uint32_t temp;
+
+ DPRINTFN(5, "end of reset\n");
+
+ /* set correct state */
+ sc->sc_flags.status_device_mode = 1;
+ sc->sc_flags.status_bus_reset = 1;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+ sc->sc_flags.status_low_speed = 0;
+ sc->sc_flags.port_enabled = 1;
+
+ /* reset FIFOs */
+ (void) dwc_otg_init_fifo(sc, DWC_MODE_DEVICE);
+
+ /* reset function address */
+ dwc_otg_set_address(sc, 0);
+
+ /* figure out enumeration speed */
+ temp = DWC_OTG_READ_4(sc, DOTG_DSTS);
+ if (DSTS_ENUMSPD_GET(temp) == DSTS_ENUMSPD_HI)
+ sc->sc_flags.status_high_speed = 1;
+ else
+ sc->sc_flags.status_high_speed = 0;
+
+ /*
+ * Disable resume and SOF interrupt, and enable
+ * suspend and RX frame interrupt:
+ */
+ sc->sc_irq_mask &= ~(GINTMSK_WKUPINTMSK | GINTMSK_SOFMSK);
+ sc->sc_irq_mask |= GINTMSK_USBSUSPMSK;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+
+ /* complete root HUB interrupt endpoint */
+ dwc_otg_root_intr(sc);
+ }
+
+ if (status & GINTSTS_PRTINT) {
+ uint32_t hprt;
+
+ hprt = DWC_OTG_READ_4(sc, DOTG_HPRT);
+
+ /* clear change bits */
+ DWC_OTG_WRITE_4(sc, DOTG_HPRT, (hprt & (
+ HPRT_PRTPWR | HPRT_PRTENCHNG |
+ HPRT_PRTCONNDET | HPRT_PRTOVRCURRCHNG)) |
+ sc->sc_hprt_val);
+
+ DPRINTFN(12, "GINTSTS=0x%08x, HPRT=0x%08x\n", status, hprt);
+
+ sc->sc_flags.status_device_mode = 0;
+
+ if (hprt & HPRT_PRTCONNSTS)
+ sc->sc_flags.status_bus_reset = 1;
+ else
+ sc->sc_flags.status_bus_reset = 0;
+
+ if ((hprt & HPRT_PRTENCHNG) &&
+ (hprt & HPRT_PRTENA) == 0)
+ sc->sc_flags.change_enabled = 1;
+
+ if (hprt & HPRT_PRTENA)
+ sc->sc_flags.port_enabled = 1;
+ else
+ sc->sc_flags.port_enabled = 0;
+
+ if (hprt & HPRT_PRTOVRCURRCHNG)
+ sc->sc_flags.change_over_current = 1;
+
+ if (hprt & HPRT_PRTOVRCURRACT)
+ sc->sc_flags.port_over_current = 1;
+ else
+ sc->sc_flags.port_over_current = 0;
+
+ if (hprt & HPRT_PRTPWR)
+ sc->sc_flags.port_powered = 1;
+ else
+ sc->sc_flags.port_powered = 0;
+
+ if (((hprt & HPRT_PRTSPD_MASK)
+ >> HPRT_PRTSPD_SHIFT) == HPRT_PRTSPD_LOW)
+ sc->sc_flags.status_low_speed = 1;
+ else
+ sc->sc_flags.status_low_speed = 0;
+
+ if (((hprt & HPRT_PRTSPD_MASK)
+ >> HPRT_PRTSPD_SHIFT) == HPRT_PRTSPD_HIGH)
+ sc->sc_flags.status_high_speed = 1;
+ else
+ sc->sc_flags.status_high_speed = 0;
+
+ if (hprt & HPRT_PRTCONNDET)
+ sc->sc_flags.change_connect = 1;
+
+ if (hprt & HPRT_PRTSUSP)
+ dwc_otg_suspend_irq(sc);
+ else
+ dwc_otg_resume_irq(sc);
+
+ /* complete root HUB interrupt endpoint */
+ dwc_otg_root_intr(sc);
+
+ /* update host frame interval */
+ dwc_otg_update_host_frame_interval(sc);
+ }
+
+ /*
+ * If resume and suspend is set at the same time we interpret
+ * that like RESUME. Resume is set when there is at least 3
+ * milliseconds of inactivity on the USB BUS.
+ */
+ if (status & GINTSTS_WKUPINT) {
+ DPRINTFN(5, "resume interrupt\n");
+
+ dwc_otg_resume_irq(sc);
+
+ } else if (status & GINTSTS_USBSUSP) {
+ DPRINTFN(5, "suspend interrupt\n");
+
+ dwc_otg_suspend_irq(sc);
+ }
+ /* check VBUS */
+ if (status & (GINTSTS_USBSUSP |
+ GINTSTS_USBRST |
+ GINTMSK_OTGINTMSK |
+ GINTSTS_SESSREQINT)) {
+ uint32_t temp;
+
+ temp = DWC_OTG_READ_4(sc, DOTG_GOTGCTL);
+
+ DPRINTFN(5, "GOTGCTL=0x%08x\n", temp);
+
+ dwc_otg_vbus_interrupt(sc,
+ (temp & (GOTGCTL_ASESVLD | GOTGCTL_BSESVLD)) ? 1 : 0);
+ }
+
+ if (sc->sc_xfer_complete != 0) {
+ sc->sc_xfer_complete = 0;
+
+ /* complete FIFOs, if any */
+ dwc_otg_interrupt_complete_locked(sc);
+ }
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+dwc_otg_setup_standard_chain_sub(struct dwc_otg_std_temp *temp)
+{
+ struct dwc_otg_td *td;
+
+ /* get current Transfer Descriptor */
+ td = temp->td_next;
+ temp->td = td;
+
+ /* prepare for next TD */
+ temp->td_next = td->obj_next;
+
+ /* fill out the Transfer Descriptor */
+ td->func = temp->func;
+ td->pc = temp->pc;
+ td->offset = temp->offset;
+ td->remainder = temp->len;
+ td->tx_bytes = 0;
+ td->error_any = 0;
+ td->error_stall = 0;
+ td->npkt = 0;
+ td->did_stall = temp->did_stall;
+ td->short_pkt = temp->short_pkt;
+ td->alt_next = temp->setup_alt_next;
+ td->set_toggle = 0;
+ td->got_short = 0;
+ td->did_nak = 0;
+ td->channel[0] = DWC_OTG_MAX_CHANNELS;
+ td->channel[1] = DWC_OTG_MAX_CHANNELS;
+ td->channel[2] = DWC_OTG_MAX_CHANNELS;
+ td->state = 0;
+ td->errcnt = 0;
+ td->tt_scheduled = 0;
+ td->tt_xactpos = HCSPLT_XACTPOS_BEGIN;
+}
+
+static void
+dwc_otg_setup_standard_chain(struct usb_xfer *xfer)
+{
+ struct dwc_otg_std_temp temp;
+ struct dwc_otg_td *td;
+ uint32_t x;
+ uint8_t need_sync;
+ uint8_t is_host;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpointno),
+ xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
+
+ temp.max_frame_size = xfer->max_frame_size;
+
+ td = xfer->td_start[0];
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ /* setup temp */
+
+ temp.pc = NULL;
+ temp.td = NULL;
+ temp.td_next = xfer->td_start[0];
+ temp.offset = 0;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok ||
+ xfer->flags_int.isochronous_xfr;
+ temp.did_stall = !xfer->flags_int.control_stall;
+
+ is_host = (xfer->xroot->udev->flags.usb_mode == USB_MODE_HOST);
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ if (is_host)
+ temp.func = &dwc_otg_host_setup_tx;
+ else
+ temp.func = &dwc_otg_setup_rx;
+
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.short_pkt = temp.len ? 1 : 0;
+
+ /* check for last frame */
+ if (xfer->nframes == 1) {
+ /* no STATUS stage yet, SETUP is last */
+ if (xfer->flags_int.control_act)
+ temp.setup_alt_next = 0;
+ }
+
+ dwc_otg_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ if (x != xfer->nframes) {
+ if (xfer->endpointno & UE_DIR_IN) {
+ if (is_host) {
+ temp.func = &dwc_otg_host_data_rx;
+ need_sync = 0;
+ } else {
+ temp.func = &dwc_otg_data_tx;
+ need_sync = 1;
+ }
+ } else {
+ if (is_host) {
+ temp.func = &dwc_otg_host_data_tx;
+ need_sync = 0;
+ } else {
+ temp.func = &dwc_otg_data_rx;
+ need_sync = 0;
+ }
+ }
+
+ /* setup "pc" pointer */
+ temp.pc = xfer->frbuffers + x;
+ } else {
+ need_sync = 0;
+ }
+ while (x != xfer->nframes) {
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+
+ x++;
+
+ if (x == xfer->nframes) {
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_act) {
+ temp.setup_alt_next = 0;
+ }
+ } else {
+ temp.setup_alt_next = 0;
+ }
+ }
+ if (temp.len == 0) {
+ /* make sure that we send an USB packet */
+
+ temp.short_pkt = 0;
+
+ } else {
+ /* regular data transfer */
+
+ temp.short_pkt = (xfer->flags.force_short_xfer ? 0 : 1);
+ }
+
+ dwc_otg_setup_standard_chain_sub(&temp);
+
+ if (xfer->flags_int.isochronous_xfr) {
+ temp.offset += temp.len;
+ } else {
+ /* get next Page Cache pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr) {
+ /* always setup a valid "pc" pointer for status and sync */
+ temp.pc = xfer->frbuffers + 0;
+ temp.len = 0;
+ temp.short_pkt = 0;
+ temp.setup_alt_next = 0;
+
+ /* check if we need to sync */
+ if (need_sync) {
+ /* we need a SYNC point after TX */
+ temp.func = &dwc_otg_data_tx_sync;
+ dwc_otg_setup_standard_chain_sub(&temp);
+ }
+
+ /* check if we should append a status stage */
+ if (!xfer->flags_int.control_act) {
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ if (xfer->endpointno & UE_DIR_IN) {
+ if (is_host) {
+ temp.func = &dwc_otg_host_data_tx;
+ need_sync = 0;
+ } else {
+ temp.func = &dwc_otg_data_rx;
+ need_sync = 0;
+ }
+ } else {
+ if (is_host) {
+ temp.func = &dwc_otg_host_data_rx;
+ need_sync = 0;
+ } else {
+ temp.func = &dwc_otg_data_tx;
+ need_sync = 1;
+ }
+ }
+
+ dwc_otg_setup_standard_chain_sub(&temp);
+
+ /* data toggle should be DATA1 */
+ td = temp.td;
+ td->set_toggle = 1;
+
+ if (need_sync) {
+ /* we need a SYNC point after TX */
+ temp.func = &dwc_otg_data_tx_sync;
+ dwc_otg_setup_standard_chain_sub(&temp);
+ }
+ }
+ } else {
+ /* check if we need to sync */
+ if (need_sync) {
+ temp.pc = xfer->frbuffers + 0;
+ temp.len = 0;
+ temp.short_pkt = 0;
+ temp.setup_alt_next = 0;
+
+ /* we need a SYNC point after TX */
+ temp.func = &dwc_otg_data_tx_sync;
+ dwc_otg_setup_standard_chain_sub(&temp);
+ }
+ }
+
+ /* must have at least one frame! */
+ td = temp.td;
+ xfer->td_transfer_last = td;
+
+ if (is_host) {
+ struct dwc_otg_softc *sc;
+ uint32_t hcchar;
+ uint32_t hcsplt;
+
+ sc = DWC_OTG_BUS2SC(xfer->xroot->bus);
+
+ /* get first again */
+ td = xfer->td_transfer_first;
+ td->toggle = (xfer->endpoint->toggle_next ? 1 : 0);
+
+ hcchar =
+ (xfer->address << HCCHAR_DEVADDR_SHIFT) |
+ ((xfer->endpointno & UE_ADDR) << HCCHAR_EPNUM_SHIFT) |
+ (xfer->max_packet_size << HCCHAR_MPS_SHIFT) |
+ HCCHAR_CHENA;
+
+ /*
+ * We are not always able to meet the timing
+ * requirements of the USB interrupt endpoint's
+ * complete split token, when doing transfers going
+ * via a transaction translator. Use the CONTROL
+ * transfer type instead of the INTERRUPT transfer
+ * type in general, as a means to workaround
+ * that. This trick should work for both FULL and LOW
+ * speed USB traffic going through a TT. For non-TT
+ * traffic it works as well. The reason for using
+ * CONTROL type instead of BULK is that some TTs might
+ * reject LOW speed BULK traffic.
+ */
+ if (td->ep_type == UE_INTERRUPT)
+ hcchar |= (UE_CONTROL << HCCHAR_EPTYPE_SHIFT);
+ else
+ hcchar |= (td->ep_type << HCCHAR_EPTYPE_SHIFT);
+
+ if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN)
+ hcchar |= HCCHAR_EPDIR_IN;
+
+ switch (xfer->xroot->udev->speed) {
+ case USB_SPEED_LOW:
+ hcchar |= HCCHAR_LSPDDEV;
+ /* FALLTHROUGH */
+ case USB_SPEED_FULL:
+ /* check if root HUB port is running High Speed */
+ if (dwc_otg_uses_split(xfer->xroot->udev)) {
+ hcsplt = HCSPLT_SPLTENA |
+ (xfer->xroot->udev->hs_port_no <<
+ HCSPLT_PRTADDR_SHIFT) |
+ (xfer->xroot->udev->hs_hub_addr <<
+ HCSPLT_HUBADDR_SHIFT);
+ } else {
+ hcsplt = 0;
+ }
+ if (td->ep_type == UE_INTERRUPT) {
+ uint32_t ival;
+ ival = xfer->interval / DWC_OTG_HOST_TIMER_RATE;
+ if (ival == 0)
+ ival = 1;
+ else if (ival > 127)
+ ival = 127;
+ td->tmr_val = sc->sc_tmr_val + ival;
+ td->tmr_res = ival;
+ } else if (td->ep_type == UE_ISOCHRONOUS) {
+ td->tmr_res = 1;
+ td->tmr_val = sc->sc_last_frame_num;
+ if (td->hcchar & HCCHAR_EPDIR_IN)
+ td->tmr_val++;
+ } else {
+ td->tmr_val = 0;
+ td->tmr_res = (uint8_t)sc->sc_last_frame_num;
+ }
+ break;
+ case USB_SPEED_HIGH:
+ hcsplt = 0;
+ if (td->ep_type == UE_INTERRUPT) {
+ uint32_t ival;
+ hcchar |= ((xfer->max_packet_count & 3)
+ << HCCHAR_MC_SHIFT);
+ ival = xfer->interval / DWC_OTG_HOST_TIMER_RATE;
+ if (ival == 0)
+ ival = 1;
+ else if (ival > 127)
+ ival = 127;
+ td->tmr_val = sc->sc_tmr_val + ival;
+ td->tmr_res = ival;
+ } else if (td->ep_type == UE_ISOCHRONOUS) {
+ hcchar |= ((xfer->max_packet_count & 3)
+ << HCCHAR_MC_SHIFT);
+ td->tmr_res = 1 << usbd_xfer_get_fps_shift(xfer);
+ td->tmr_val = sc->sc_last_frame_num;
+ if (td->hcchar & HCCHAR_EPDIR_IN)
+ td->tmr_val += td->tmr_res;
+
+ } else {
+ td->tmr_val = 0;
+ td->tmr_res = (uint8_t)sc->sc_last_frame_num;
+ }
+ break;
+ default:
+ hcsplt = 0;
+ td->tmr_val = 0;
+ td->tmr_res = 0;
+ break;
+ }
+
+ /* store configuration in all TD's */
+ while (1) {
+ td->hcchar = hcchar;
+ td->hcsplt = hcsplt;
+
+ if (((void *)td) == xfer->td_transfer_last)
+ break;
+
+ td = td->obj_next;
+ }
+ }
+}
+
+static void
+dwc_otg_timeout(void *arg)
+{
+ struct usb_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ dwc_otg_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+dwc_otg_start_standard_chain(struct usb_xfer *xfer)
+{
+ struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(xfer->xroot->bus);
+
+ DPRINTFN(9, "\n");
+
+ /*
+ * Poll one time in device mode, which will turn on the
+ * endpoint interrupts. Else wait for SOF interrupt in host
+ * mode.
+ */
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+
+ if (sc->sc_flags.status_device_mode != 0) {
+ dwc_otg_xfer_do_fifo(sc, xfer);
+ if (dwc_otg_xfer_do_complete_locked(sc, xfer))
+ goto done;
+ } else {
+ struct dwc_otg_td *td = xfer->td_transfer_cache;
+ if (td->ep_type == UE_ISOCHRONOUS &&
+ (td->hcchar & HCCHAR_EPDIR_IN) == 0) {
+ /*
+ * Need to start ISOCHRONOUS OUT transfer ASAP
+ * because execution is delayed by one 125us
+ * microframe:
+ */
+ dwc_otg_xfer_do_fifo(sc, xfer);
+ if (dwc_otg_xfer_do_complete_locked(sc, xfer))
+ goto done;
+ }
+ }
+
+ /* put transfer on interrupt queue */
+ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usbd_transfer_timeout_ms(xfer,
+ &dwc_otg_timeout, xfer->timeout);
+ }
+
+ if (sc->sc_flags.status_device_mode != 0)
+ goto done;
+
+ /* enable SOF interrupt, if any */
+ dwc_otg_enable_sof_irq(sc);
+done:
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+}
+
+static void
+dwc_otg_root_intr(struct dwc_otg_softc *sc)
+{
+ DPRINTFN(9, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* set port bit */
+ sc->sc_hub_idata[0] = 0x02; /* we only have one port */
+
+ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
+ sizeof(sc->sc_hub_idata));
+}
+
+static usb_error_t
+dwc_otg_standard_done_sub(struct usb_xfer *xfer)
+{
+ struct dwc_otg_td *td;
+ uint32_t len;
+ usb_error_t error;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+
+ do {
+ len = td->remainder;
+
+ /* store last data toggle */
+ xfer->endpoint->toggle_next = td->toggle;
+
+ if (xfer->aframes != xfer->nframes) {
+ /*
+ * Verify the length and subtract
+ * the remainder from "frlengths[]":
+ */
+ if (len > xfer->frlengths[xfer->aframes]) {
+ td->error_any = 1;
+ } else {
+ xfer->frlengths[xfer->aframes] -= len;
+ }
+ }
+ /* Check for transfer error */
+ if (td->error_any) {
+ /* the transfer is finished */
+ error = (td->error_stall ?
+ USB_ERR_STALLED : USB_ERR_IOERROR);
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len > 0) {
+ if (xfer->flags_int.short_frames_ok ||
+ xfer->flags_int.isochronous_xfr) {
+ /* follow alt next */
+ if (td->alt_next) {
+ td = td->obj_next;
+ } else {
+ td = NULL;
+ }
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ error = 0;
+ break;
+ }
+ td = td->obj_next;
+
+ /* this USB frame is complete */
+ error = 0;
+ break;
+
+ } while (0);
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ return (error);
+}
+
+static void
+dwc_otg_standard_done(struct usb_xfer *xfer)
+{
+ usb_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
+ xfer, xfer->endpoint);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ err = dwc_otg_standard_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+ err = dwc_otg_standard_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+ err = dwc_otg_standard_done_sub(xfer);
+ }
+done:
+ dwc_otg_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * dwc_otg_device_done
+ *
+ * NOTE: this function can be called more than one time on the
+ * same USB transfer!
+ *------------------------------------------------------------------------*/
+static void
+dwc_otg_device_done(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(xfer->xroot->bus);
+
+ DPRINTFN(9, "xfer=%p, endpoint=%p, error=%d\n",
+ xfer, xfer->endpoint, error);
+
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+
+ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
+ /* Interrupts are cleared by the interrupt handler */
+ } else {
+ struct dwc_otg_td *td;
+
+ td = xfer->td_transfer_cache;
+ if (td != NULL)
+ dwc_otg_host_channel_free(sc, td);
+ }
+ /* dequeue transfer and start next transfer */
+ usbd_transfer_done(xfer, error);
+
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+}
+
+static void
+dwc_otg_xfer_stall(struct usb_xfer *xfer)
+{
+ dwc_otg_device_done(xfer, USB_ERR_STALLED);
+}
+
+static void
+dwc_otg_set_stall(struct usb_device *udev,
+ struct usb_endpoint *ep, uint8_t *did_stall)
+{
+ struct dwc_otg_softc *sc;
+ uint32_t temp;
+ uint32_t reg;
+ uint8_t ep_no;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ /* check mode */
+ if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+
+ sc = DWC_OTG_BUS2SC(udev->bus);
+
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+
+ /* get endpoint address */
+ ep_no = ep->edesc->bEndpointAddress;
+
+ DPRINTFN(5, "endpoint=0x%x\n", ep_no);
+
+ if (ep_no & UE_DIR_IN) {
+ reg = DOTG_DIEPCTL(ep_no & UE_ADDR);
+ temp = sc->sc_in_ctl[ep_no & UE_ADDR];
+ } else {
+ reg = DOTG_DOEPCTL(ep_no & UE_ADDR);
+ temp = sc->sc_out_ctl[ep_no & UE_ADDR];
+ }
+
+ /* disable and stall endpoint */
+ DWC_OTG_WRITE_4(sc, reg, temp | DOEPCTL_EPDIS);
+ DWC_OTG_WRITE_4(sc, reg, temp | DOEPCTL_STALL);
+
+ /* clear active OUT ep */
+ if (!(ep_no & UE_DIR_IN)) {
+ sc->sc_active_rx_ep &= ~(1U << (ep_no & UE_ADDR));
+
+ if (sc->sc_last_rx_status != 0 &&
+ (ep_no & UE_ADDR) == GRXSTSRD_CHNUM_GET(
+ sc->sc_last_rx_status)) {
+ /* dump data */
+ dwc_otg_common_rx_ack(sc);
+ /* poll interrupt */
+ dwc_otg_interrupt_poll_locked(sc);
+ dwc_otg_interrupt_complete_locked(sc);
+ }
+ }
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+}
+
+static void
+dwc_otg_clear_stall_sub_locked(struct dwc_otg_softc *sc, uint32_t mps,
+ uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir)
+{
+ uint32_t reg;
+ uint32_t temp;
+
+ if (ep_type == UE_CONTROL) {
+ /* clearing stall is not needed */
+ return;
+ }
+
+ if (ep_dir) {
+ reg = DOTG_DIEPCTL(ep_no);
+ } else {
+ reg = DOTG_DOEPCTL(ep_no);
+ sc->sc_active_rx_ep |= (1U << ep_no);
+ }
+
+ /* round up and mask away the multiplier count */
+ mps = (mps + 3) & 0x7FC;
+
+ if (ep_type == UE_BULK) {
+ temp = DIEPCTL_EPTYPE_SET(
+ DIEPCTL_EPTYPE_BULK) |
+ DIEPCTL_USBACTEP;
+ } else if (ep_type == UE_INTERRUPT) {
+ temp = DIEPCTL_EPTYPE_SET(
+ DIEPCTL_EPTYPE_INTERRUPT) |
+ DIEPCTL_USBACTEP;
+ } else {
+ temp = DIEPCTL_EPTYPE_SET(
+ DIEPCTL_EPTYPE_ISOC) |
+ DIEPCTL_USBACTEP;
+ }
+
+ temp |= DIEPCTL_MPS_SET(mps);
+ temp |= DIEPCTL_TXFNUM_SET(ep_no);
+
+ if (ep_dir)
+ sc->sc_in_ctl[ep_no] = temp;
+ else
+ sc->sc_out_ctl[ep_no] = temp;
+
+ DWC_OTG_WRITE_4(sc, reg, temp | DOEPCTL_EPDIS);
+ DWC_OTG_WRITE_4(sc, reg, temp | DOEPCTL_SETD0PID);
+ DWC_OTG_WRITE_4(sc, reg, temp | DIEPCTL_SNAK);
+
+ /* we only reset the transmit FIFO */
+ if (ep_dir) {
+ dwc_otg_tx_fifo_reset(sc,
+ GRSTCTL_TXFIFO(ep_no) |
+ GRSTCTL_TXFFLSH);
+
+ DWC_OTG_WRITE_4(sc,
+ DOTG_DIEPTSIZ(ep_no), 0);
+ }
+
+ /* poll interrupt */
+ dwc_otg_interrupt_poll_locked(sc);
+ dwc_otg_interrupt_complete_locked(sc);
+}
+
+static void
+dwc_otg_clear_stall(struct usb_device *udev, struct usb_endpoint *ep)
+{
+ struct dwc_otg_softc *sc;
+ struct usb_endpoint_descriptor *ed;
+
+ DPRINTFN(5, "endpoint=%p\n", ep);
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ /* check mode */
+ if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ /* get softc */
+ sc = DWC_OTG_BUS2SC(udev->bus);
+
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+
+ /* get endpoint descriptor */
+ ed = ep->edesc;
+
+ /* reset endpoint */
+ dwc_otg_clear_stall_sub_locked(sc,
+ UGETW(ed->wMaxPacketSize),
+ (ed->bEndpointAddress & UE_ADDR),
+ (ed->bmAttributes & UE_XFERTYPE),
+ (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+}
+
+static void
+dwc_otg_device_state_change(struct usb_device *udev)
+{
+ struct dwc_otg_softc *sc;
+ uint8_t x;
+
+ /* check mode */
+ if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+
+ /* get softc */
+ sc = DWC_OTG_BUS2SC(udev->bus);
+
+ /* deactivate all other endpoint but the control endpoint */
+ if (udev->state == USB_STATE_CONFIGURED ||
+ udev->state == USB_STATE_ADDRESSED) {
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ for (x = 1; x != sc->sc_dev_ep_max; x++) {
+ if (x < sc->sc_dev_in_ep_max) {
+ DWC_OTG_WRITE_4(sc, DOTG_DIEPCTL(x),
+ DIEPCTL_EPDIS);
+ DWC_OTG_WRITE_4(sc, DOTG_DIEPCTL(x), 0);
+ }
+
+ DWC_OTG_WRITE_4(sc, DOTG_DOEPCTL(x),
+ DOEPCTL_EPDIS);
+ DWC_OTG_WRITE_4(sc, DOTG_DOEPCTL(x), 0);
+ }
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ }
+}
+
+int
+dwc_otg_init(struct dwc_otg_softc *sc)
+{
+ uint32_t temp;
+ int err;
+
+ DPRINTF("start\n");
+
+ sc->sc_io_tag = rman_get_bustag(sc->sc_io_res);
+ sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = rman_get_size(sc->sc_io_res);
+
+ /* set up the bus structure */
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = DWC_OTG_MAX_DEVICES;
+ sc->sc_bus.dma_bits = 32;
+ sc->sc_bus.usbrev = USB_REV_2_0;
+ sc->sc_bus.methods = &dwc_otg_bus_methods;
+
+ /* get all DMA memory */
+ if (usb_bus_mem_alloc_all(&sc->sc_bus,
+ USB_GET_DMA_TAG(sc->sc_bus.parent), NULL)) {
+ return (ENOMEM);
+ }
+
+ sc->sc_bus.bdev = device_add_child(sc->sc_bus.parent, "usbus", DEVICE_UNIT_ANY);
+ if (sc->sc_bus.bdev == NULL)
+ return (ENXIO);
+
+ device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
+
+ err = bus_setup_intr(sc->sc_bus.parent, sc->sc_irq_res,
+ INTR_TYPE_TTY | INTR_MPSAFE, &dwc_otg_filter_interrupt,
+ &dwc_otg_interrupt, sc, &sc->sc_intr_hdl);
+ if (err) {
+ sc->sc_intr_hdl = NULL;
+ return (ENXIO);
+ }
+
+ usb_callout_init_mtx(&sc->sc_timer,
+ &sc->sc_bus.bus_mtx, 0);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* turn on clocks */
+ dwc_otg_clocks_on(sc);
+
+ temp = DWC_OTG_READ_4(sc, DOTG_GSNPSID);
+ DPRINTF("Version = 0x%08x\n", temp);
+
+ /* disconnect */
+ DWC_OTG_WRITE_4(sc, DOTG_DCTL,
+ DCTL_SFTDISCON);
+
+ /* wait for host to detect disconnect */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 32);
+
+ DWC_OTG_WRITE_4(sc, DOTG_GRSTCTL,
+ GRSTCTL_CSFTRST);
+
+ /* wait a little bit for block to reset */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 128);
+
+ switch (sc->sc_mode) {
+ case DWC_MODE_DEVICE:
+ temp = GUSBCFG_FORCEDEVMODE;
+ break;
+ case DWC_MODE_HOST:
+ temp = GUSBCFG_FORCEHOSTMODE;
+ break;
+ default:
+ temp = 0;
+ break;
+ }
+
+ if (sc->sc_phy_type == 0)
+ sc->sc_phy_type = dwc_otg_phy_type + 1;
+ if (sc->sc_phy_bits == 0)
+ sc->sc_phy_bits = 16;
+
+ /* select HSIC, ULPI, UTMI+ or internal PHY mode */
+ switch (sc->sc_phy_type) {
+ case DWC_OTG_PHY_HSIC:
+ DWC_OTG_WRITE_4(sc, DOTG_GUSBCFG,
+ GUSBCFG_PHYIF |
+ GUSBCFG_TRD_TIM_SET(5) | temp);
+ DWC_OTG_WRITE_4(sc, DOTG_GOTGCTL,
+ 0x000000EC);
+
+ temp = DWC_OTG_READ_4(sc, DOTG_GLPMCFG);
+ DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG,
+ temp & ~GLPMCFG_HSIC_CONN);
+ DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG,
+ temp | GLPMCFG_HSIC_CONN);
+ break;
+ case DWC_OTG_PHY_ULPI:
+ DWC_OTG_WRITE_4(sc, DOTG_GUSBCFG,
+ GUSBCFG_ULPI_UTMI_SEL |
+ GUSBCFG_TRD_TIM_SET(5) | temp);
+ DWC_OTG_WRITE_4(sc, DOTG_GOTGCTL, 0);
+
+ temp = DWC_OTG_READ_4(sc, DOTG_GLPMCFG);
+ DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG,
+ temp & ~GLPMCFG_HSIC_CONN);
+ break;
+ case DWC_OTG_PHY_UTMI:
+ DWC_OTG_WRITE_4(sc, DOTG_GUSBCFG,
+ (sc->sc_phy_bits == 16 ? GUSBCFG_PHYIF : 0) |
+ GUSBCFG_TRD_TIM_SET(5) | temp);
+ DWC_OTG_WRITE_4(sc, DOTG_GOTGCTL, 0);
+
+ temp = DWC_OTG_READ_4(sc, DOTG_GLPMCFG);
+ DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG,
+ temp & ~GLPMCFG_HSIC_CONN);
+ break;
+ case DWC_OTG_PHY_INTERNAL:
+ DWC_OTG_WRITE_4(sc, DOTG_GUSBCFG,
+ GUSBCFG_PHYSEL |
+ GUSBCFG_TRD_TIM_SET(5) | temp);
+ DWC_OTG_WRITE_4(sc, DOTG_GOTGCTL, 0);
+
+ temp = DWC_OTG_READ_4(sc, DOTG_GLPMCFG);
+ DWC_OTG_WRITE_4(sc, DOTG_GLPMCFG,
+ temp & ~GLPMCFG_HSIC_CONN);
+
+ temp = DWC_OTG_READ_4(sc, DOTG_GGPIO);
+ temp &= ~(DOTG_GGPIO_NOVBUSSENS | DOTG_GGPIO_I2CPADEN);
+ temp |= (DOTG_GGPIO_VBUSASEN | DOTG_GGPIO_VBUSBSEN |
+ DOTG_GGPIO_PWRDWN);
+ DWC_OTG_WRITE_4(sc, DOTG_GGPIO, temp);
+ break;
+ default:
+ break;
+ }
+
+ /* clear global nak */
+ DWC_OTG_WRITE_4(sc, DOTG_DCTL,
+ DCTL_CGOUTNAK |
+ DCTL_CGNPINNAK);
+
+ /* disable USB port */
+ DWC_OTG_WRITE_4(sc, DOTG_PCGCCTL, 0xFFFFFFFF);
+
+ /* wait 10ms */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+
+ /* enable USB port */
+ DWC_OTG_WRITE_4(sc, DOTG_PCGCCTL, 0);
+
+ /* wait 10ms */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+
+ temp = DWC_OTG_READ_4(sc, DOTG_GHWCFG3);
+
+ sc->sc_fifo_size = 4 * GHWCFG3_DFIFODEPTH_GET(temp);
+
+ temp = DWC_OTG_READ_4(sc, DOTG_GHWCFG2);
+
+ sc->sc_dev_ep_max = GHWCFG2_NUMDEVEPS_GET(temp);
+
+ if (sc->sc_dev_ep_max > DWC_OTG_MAX_ENDPOINTS)
+ sc->sc_dev_ep_max = DWC_OTG_MAX_ENDPOINTS;
+
+ sc->sc_host_ch_max = GHWCFG2_NUMHSTCHNL_GET(temp);
+
+ if (sc->sc_host_ch_max > DWC_OTG_MAX_CHANNELS)
+ sc->sc_host_ch_max = DWC_OTG_MAX_CHANNELS;
+
+ temp = DWC_OTG_READ_4(sc, DOTG_GHWCFG4);
+
+ sc->sc_dev_in_ep_max = GHWCFG4_NUM_IN_EP_GET(temp);
+
+ DPRINTF("Total FIFO size = %d bytes, Device EPs = %d/%d Host CHs = %d\n",
+ sc->sc_fifo_size, sc->sc_dev_ep_max, sc->sc_dev_in_ep_max,
+ sc->sc_host_ch_max);
+
+ /* setup FIFO */
+ if (dwc_otg_init_fifo(sc, sc->sc_mode)) {
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ return (EINVAL);
+ }
+
+ /* enable interrupts */
+ sc->sc_irq_mask |= DWC_OTG_MSK_GINT_THREAD_IRQ;
+ DWC_OTG_WRITE_4(sc, DOTG_GINTMSK, sc->sc_irq_mask);
+
+ if (sc->sc_mode == DWC_MODE_OTG || sc->sc_mode == DWC_MODE_DEVICE) {
+ /* enable all endpoint interrupts */
+ temp = DWC_OTG_READ_4(sc, DOTG_GHWCFG2);
+ if (temp & GHWCFG2_MPI) {
+ uint8_t x;
+
+ DPRINTF("Disable Multi Process Interrupts\n");
+
+ for (x = 0; x != sc->sc_dev_in_ep_max; x++) {
+ DWC_OTG_WRITE_4(sc, DOTG_DIEPEACHINTMSK(x), 0);
+ DWC_OTG_WRITE_4(sc, DOTG_DOEPEACHINTMSK(x), 0);
+ }
+ DWC_OTG_WRITE_4(sc, DOTG_DEACHINTMSK, 0);
+ }
+ DWC_OTG_WRITE_4(sc, DOTG_DIEPMSK,
+ DIEPMSK_XFERCOMPLMSK);
+ DWC_OTG_WRITE_4(sc, DOTG_DOEPMSK, 0);
+ DWC_OTG_WRITE_4(sc, DOTG_DAINTMSK, 0xFFFF);
+ }
+
+ if (sc->sc_mode == DWC_MODE_OTG || sc->sc_mode == DWC_MODE_HOST) {
+ /* setup clocks */
+ temp = DWC_OTG_READ_4(sc, DOTG_HCFG);
+ temp &= ~(HCFG_FSLSSUPP | HCFG_FSLSPCLKSEL_MASK);
+ temp |= (1 << HCFG_FSLSPCLKSEL_SHIFT);
+ DWC_OTG_WRITE_4(sc, DOTG_HCFG, temp);
+ }
+
+ /* only enable global IRQ */
+ DWC_OTG_WRITE_4(sc, DOTG_GAHBCFG,
+ GAHBCFG_GLBLINTRMSK);
+
+ /* turn off clocks */
+ dwc_otg_clocks_off(sc);
+
+ /* read initial VBUS state */
+
+ temp = DWC_OTG_READ_4(sc, DOTG_GOTGCTL);
+
+ DPRINTFN(5, "GOTCTL=0x%08x\n", temp);
+
+ dwc_otg_vbus_interrupt(sc,
+ (temp & (GOTGCTL_ASESVLD | GOTGCTL_BSESVLD)) ? 1 : 0);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch any lost interrupts */
+
+ dwc_otg_do_poll(&sc->sc_bus);
+
+ return (0); /* success */
+}
+
+void
+dwc_otg_uninit(struct dwc_otg_softc *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* stop host timer */
+ dwc_otg_timer_stop(sc);
+
+ /* set disconnect */
+ DWC_OTG_WRITE_4(sc, DOTG_DCTL,
+ DCTL_SFTDISCON);
+
+ /* turn off global IRQ */
+ DWC_OTG_WRITE_4(sc, DOTG_GAHBCFG, 0);
+
+ sc->sc_flags.port_enabled = 0;
+ sc->sc_flags.port_powered = 0;
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ dwc_otg_pull_down(sc);
+ dwc_otg_clocks_off(sc);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ usb_callout_drain(&sc->sc_timer);
+}
+
+static void
+dwc_otg_suspend(struct dwc_otg_softc *sc)
+{
+ return;
+}
+
+static void
+dwc_otg_resume(struct dwc_otg_softc *sc)
+{
+ return;
+}
+
+static void
+dwc_otg_do_poll(struct usb_bus *bus)
+{
+ struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+ dwc_otg_interrupt_poll_locked(sc);
+ dwc_otg_interrupt_complete_locked(sc);
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * DWC OTG bulk support
+ * DWC OTG control support
+ * DWC OTG interrupt support
+ *------------------------------------------------------------------------*/
+static void
+dwc_otg_device_non_isoc_open(struct usb_xfer *xfer)
+{
+}
+
+static void
+dwc_otg_device_non_isoc_close(struct usb_xfer *xfer)
+{
+ dwc_otg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+dwc_otg_device_non_isoc_enter(struct usb_xfer *xfer)
+{
+}
+
+static void
+dwc_otg_device_non_isoc_start(struct usb_xfer *xfer)
+{
+ /* setup TDs */
+ dwc_otg_setup_standard_chain(xfer);
+ dwc_otg_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods dwc_otg_device_non_isoc_methods =
+{
+ .open = dwc_otg_device_non_isoc_open,
+ .close = dwc_otg_device_non_isoc_close,
+ .enter = dwc_otg_device_non_isoc_enter,
+ .start = dwc_otg_device_non_isoc_start,
+};
+
+/*------------------------------------------------------------------------*
+ * DWC OTG full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+dwc_otg_device_isoc_open(struct usb_xfer *xfer)
+{
+}
+
+static void
+dwc_otg_device_isoc_close(struct usb_xfer *xfer)
+{
+ dwc_otg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+dwc_otg_device_isoc_enter(struct usb_xfer *xfer)
+{
+}
+
+static void
+dwc_otg_device_isoc_start(struct usb_xfer *xfer)
+{
+ struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(xfer->xroot->bus);
+ uint32_t temp;
+ uint32_t framenum;
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->endpoint->isoc_next, xfer->nframes);
+
+ if (xfer->xroot->udev->flags.usb_mode == USB_MODE_HOST) {
+ temp = DWC_OTG_READ_4(sc, DOTG_HFNUM);
+
+ /* get the current frame index */
+ framenum = (temp & HFNUM_FRNUM_MASK);
+ } else {
+ temp = DWC_OTG_READ_4(sc, DOTG_DSTS);
+
+ /* get the current frame index */
+ framenum = DSTS_SOFFN_GET(temp);
+ }
+
+ /*
+ * Check if port is doing 8000 or 1000 frames per second:
+ */
+ if (sc->sc_flags.status_high_speed)
+ framenum /= 8;
+
+ if (usbd_xfer_get_isochronous_start_frame(
+ xfer, framenum, 0, 1, DWC_OTG_FRAME_MASK, NULL))
+ DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
+
+ /* setup TDs */
+ dwc_otg_setup_standard_chain(xfer);
+
+ /* start TD chain */
+ dwc_otg_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods dwc_otg_device_isoc_methods =
+{
+ .open = dwc_otg_device_isoc_open,
+ .close = dwc_otg_device_isoc_close,
+ .enter = dwc_otg_device_isoc_enter,
+ .start = dwc_otg_device_isoc_start,
+};
+
+/*------------------------------------------------------------------------*
+ * DWC OTG root control support
+ *------------------------------------------------------------------------*
+ * Simulate a hardware HUB by handling all the necessary requests.
+ *------------------------------------------------------------------------*/
+
+static const struct usb_device_descriptor dwc_otg_devd = {
+ .bLength = sizeof(struct usb_device_descriptor),
+ .bDescriptorType = UDESC_DEVICE,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_HSHUBSTT,
+ .bMaxPacketSize = 64,
+ .bcdDevice = {0x00, 0x01},
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .bNumConfigurations = 1,
+};
+
+static const struct dwc_otg_config_desc dwc_otg_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(dwc_otg_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0,
+ },
+ .ifcd = {
+ .bLength = sizeof(struct usb_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = 0,
+ },
+ .endpd = {
+ .bLength = sizeof(struct usb_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = (UE_DIR_IN | DWC_OTG_INTR_ENDPT),
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8,
+ .bInterval = 255,
+ },
+};
+#define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) }
+
+static const struct usb_hub_descriptor_min dwc_otg_hubd = {
+ .bDescLength = sizeof(dwc_otg_hubd),
+ .bDescriptorType = UDESC_HUB,
+ .bNbrPorts = 1,
+ HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)),
+ .bPwrOn2PwrGood = 50,
+ .bHubContrCurrent = 0,
+ .DeviceRemovable = {0}, /* port is removable */
+};
+
+#define STRING_VENDOR \
+ "D\0W\0C\0O\0T\0G"
+
+#define STRING_PRODUCT \
+ "O\0T\0G\0 \0R\0o\0o\0t\0 \0H\0U\0B"
+
+USB_MAKE_STRING_DESC(STRING_VENDOR, dwc_otg_vendor);
+USB_MAKE_STRING_DESC(STRING_PRODUCT, dwc_otg_product);
+
+static usb_error_t
+dwc_otg_roothub_exec(struct usb_device *udev,
+ struct usb_device_request *req, const void **pptr, uint16_t *plength)
+{
+ struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(udev->bus);
+ const void *ptr;
+ uint16_t len;
+ uint16_t value;
+ uint16_t index;
+ usb_error_t err;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* buffer reset */
+ ptr = (const void *)&sc->sc_hub_temp;
+ len = 0;
+ err = 0;
+
+ value = UGETW(req->wValue);
+ index = UGETW(req->wIndex);
+
+ /* demultiplex the control request */
+
+ switch (req->bmRequestType) {
+ case UT_READ_DEVICE:
+ switch (req->bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_descriptor;
+ case UR_GET_CONFIG:
+ goto tr_handle_get_config;
+ case UR_GET_STATUS:
+ goto tr_handle_get_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_DEVICE:
+ switch (req->bRequest) {
+ case UR_SET_ADDRESS:
+ goto tr_handle_set_address;
+ case UR_SET_CONFIG:
+ goto tr_handle_set_config;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_DESCRIPTOR:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_ENDPOINT:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ switch (UGETW(req->wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_clear_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_clear_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SET_FEATURE:
+ switch (UGETW(req->wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_set_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_set_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SYNCH_FRAME:
+ goto tr_valid; /* nop */
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_ENDPOINT:
+ switch (req->bRequest) {
+ case UR_GET_STATUS:
+ goto tr_handle_get_ep_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_INTERFACE:
+ switch (req->bRequest) {
+ case UR_SET_INTERFACE:
+ goto tr_handle_set_interface;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_INTERFACE:
+ switch (req->bRequest) {
+ case UR_GET_INTERFACE:
+ goto tr_handle_get_interface;
+ case UR_GET_STATUS:
+ goto tr_handle_get_iface_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_INTERFACE:
+ case UT_WRITE_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_READ_CLASS_INTERFACE:
+ case UT_READ_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_WRITE_CLASS_DEVICE:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_valid;
+ case UR_SET_DESCRIPTOR:
+ case UR_SET_FEATURE:
+ break;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_OTHER:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_handle_clear_port_feature;
+ case UR_SET_FEATURE:
+ goto tr_handle_set_port_feature;
+ case UR_CLEAR_TT_BUFFER:
+ case UR_RESET_TT:
+ case UR_STOP_TT:
+ goto tr_valid;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_OTHER:
+ switch (req->bRequest) {
+ case UR_GET_TT_STATE:
+ goto tr_handle_get_tt_state;
+ case UR_GET_STATUS:
+ goto tr_handle_get_port_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_DEVICE:
+ switch (req->bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_class_descriptor;
+ case UR_GET_STATUS:
+ goto tr_handle_get_class_status;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_valid;
+
+tr_handle_get_descriptor:
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ len = sizeof(dwc_otg_devd);
+ ptr = (const void *)&dwc_otg_devd;
+ goto tr_valid;
+ case UDESC_CONFIG:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ len = sizeof(dwc_otg_confd);
+ ptr = (const void *)&dwc_otg_confd;
+ goto tr_valid;
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ len = sizeof(usb_string_lang_en);
+ ptr = (const void *)&usb_string_lang_en;
+ goto tr_valid;
+
+ case 1: /* Vendor */
+ len = sizeof(dwc_otg_vendor);
+ ptr = (const void *)&dwc_otg_vendor;
+ goto tr_valid;
+
+ case 2: /* Product */
+ len = sizeof(dwc_otg_product);
+ ptr = (const void *)&dwc_otg_product;
+ goto tr_valid;
+ default:
+ break;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_stalled;
+
+tr_handle_get_config:
+ len = 1;
+ sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+ goto tr_valid;
+
+tr_handle_get_status:
+ len = 2;
+ USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED);
+ goto tr_valid;
+
+tr_handle_set_address:
+ if (value & 0xFF00) {
+ goto tr_stalled;
+ }
+ sc->sc_rt_addr = value;
+ goto tr_valid;
+
+tr_handle_set_config:
+ if (value >= 2) {
+ goto tr_stalled;
+ }
+ sc->sc_conf = value;
+ goto tr_valid;
+
+tr_handle_get_interface:
+ len = 1;
+ sc->sc_hub_temp.wValue[0] = 0;
+ goto tr_valid;
+
+tr_handle_get_tt_state:
+tr_handle_get_class_status:
+tr_handle_get_iface_status:
+tr_handle_get_ep_status:
+ len = 2;
+ USETW(sc->sc_hub_temp.wValue, 0);
+ goto tr_valid;
+
+tr_handle_set_halt:
+tr_handle_set_interface:
+tr_handle_set_wakeup:
+tr_handle_clear_wakeup:
+tr_handle_clear_halt:
+ goto tr_valid;
+
+tr_handle_clear_port_feature:
+ if (index != 1)
+ goto tr_stalled;
+
+ DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
+
+ switch (value) {
+ case UHF_PORT_SUSPEND:
+ dwc_otg_wakeup_peer(sc);
+ break;
+
+ case UHF_PORT_ENABLE:
+ if (sc->sc_flags.status_device_mode == 0) {
+ DWC_OTG_WRITE_4(sc, DOTG_HPRT,
+ sc->sc_hprt_val | HPRT_PRTENA);
+ }
+ sc->sc_flags.port_enabled = 0;
+ break;
+
+ case UHF_C_PORT_RESET:
+ sc->sc_flags.change_reset = 0;
+ break;
+
+ case UHF_C_PORT_ENABLE:
+ sc->sc_flags.change_enabled = 0;
+ break;
+
+ case UHF_C_PORT_OVER_CURRENT:
+ sc->sc_flags.change_over_current = 0;
+ break;
+
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ /* nops */
+ break;
+
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 0;
+ if (sc->sc_mode == DWC_MODE_HOST || sc->sc_mode == DWC_MODE_OTG) {
+ sc->sc_hprt_val = 0;
+ DWC_OTG_WRITE_4(sc, DOTG_HPRT, HPRT_PRTENA);
+ }
+ dwc_otg_pull_down(sc);
+ dwc_otg_clocks_off(sc);
+ break;
+
+ case UHF_C_PORT_CONNECTION:
+ /* clear connect change flag */
+ sc->sc_flags.change_connect = 0;
+ break;
+
+ case UHF_C_PORT_SUSPEND:
+ sc->sc_flags.change_suspend = 0;
+ break;
+
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_set_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(9, "UR_SET_PORT_FEATURE\n");
+
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ break;
+
+ case UHF_PORT_SUSPEND:
+ if (sc->sc_flags.status_device_mode == 0) {
+ /* set suspend BIT */
+ sc->sc_hprt_val |= HPRT_PRTSUSP;
+ DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val);
+
+ /* generate HUB suspend event */
+ dwc_otg_suspend_irq(sc);
+ }
+ break;
+
+ case UHF_PORT_RESET:
+ if (sc->sc_flags.status_device_mode == 0) {
+ DPRINTF("PORT RESET\n");
+
+ /* enable PORT reset */
+ DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val | HPRT_PRTRST);
+
+ /* Wait 62.5ms for reset to complete */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 16);
+
+ DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val);
+
+ /* Wait 62.5ms for reset to complete */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 16);
+
+ /* reset FIFOs */
+ (void) dwc_otg_init_fifo(sc, DWC_MODE_HOST);
+
+ sc->sc_flags.change_reset = 1;
+ } else {
+ err = USB_ERR_IOERROR;
+ }
+ break;
+
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 1;
+ if (sc->sc_mode == DWC_MODE_HOST || sc->sc_mode == DWC_MODE_OTG) {
+ sc->sc_hprt_val |= HPRT_PRTPWR;
+ DWC_OTG_WRITE_4(sc, DOTG_HPRT, sc->sc_hprt_val);
+ }
+ if (sc->sc_mode == DWC_MODE_DEVICE || sc->sc_mode == DWC_MODE_OTG) {
+ /* pull up D+, if any */
+ dwc_otg_pull_up(sc);
+ }
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_get_port_status:
+
+ DPRINTFN(9, "UR_GET_PORT_STATUS\n");
+
+ if (index != 1)
+ goto tr_stalled;
+
+ if (sc->sc_flags.status_vbus)
+ dwc_otg_clocks_on(sc);
+ else
+ dwc_otg_clocks_off(sc);
+
+ /* Select Device Side Mode */
+
+ if (sc->sc_flags.status_device_mode) {
+ value = UPS_PORT_MODE_DEVICE;
+ dwc_otg_timer_stop(sc);
+ } else {
+ value = 0;
+ dwc_otg_timer_start(sc);
+ }
+
+ if (sc->sc_flags.status_high_speed)
+ value |= UPS_HIGH_SPEED;
+ else if (sc->sc_flags.status_low_speed)
+ value |= UPS_LOW_SPEED;
+
+ if (sc->sc_flags.port_powered)
+ value |= UPS_PORT_POWER;
+
+ if (sc->sc_flags.port_enabled)
+ value |= UPS_PORT_ENABLED;
+
+ if (sc->sc_flags.port_over_current)
+ value |= UPS_OVERCURRENT_INDICATOR;
+
+ if (sc->sc_flags.status_vbus &&
+ sc->sc_flags.status_bus_reset)
+ value |= UPS_CURRENT_CONNECT_STATUS;
+
+ if (sc->sc_flags.status_suspend)
+ value |= UPS_SUSPEND;
+
+ USETW(sc->sc_hub_temp.ps.wPortStatus, value);
+
+ value = 0;
+
+ if (sc->sc_flags.change_enabled)
+ value |= UPS_C_PORT_ENABLED;
+ if (sc->sc_flags.change_connect)
+ value |= UPS_C_CONNECT_STATUS;
+ if (sc->sc_flags.change_suspend)
+ value |= UPS_C_SUSPEND;
+ if (sc->sc_flags.change_reset)
+ value |= UPS_C_PORT_RESET;
+ if (sc->sc_flags.change_over_current)
+ value |= UPS_C_OVERCURRENT_INDICATOR;
+
+ USETW(sc->sc_hub_temp.ps.wPortChange, value);
+ len = sizeof(sc->sc_hub_temp.ps);
+ goto tr_valid;
+
+tr_handle_get_class_descriptor:
+ if (value & 0xFF) {
+ goto tr_stalled;
+ }
+ ptr = (const void *)&dwc_otg_hubd;
+ len = sizeof(dwc_otg_hubd);
+ goto tr_valid;
+
+tr_stalled:
+ err = USB_ERR_STALLED;
+tr_valid:
+done:
+ *plength = len;
+ *pptr = ptr;
+ return (err);
+}
+
+static void
+dwc_otg_xfer_setup(struct usb_setup_params *parm)
+{
+ struct usb_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t n;
+ uint8_t ep_no;
+ uint8_t ep_type;
+
+ xfer = parm->curr_xfer;
+
+ /*
+ * NOTE: This driver does not use any of the parameters that
+ * are computed from the following values. Just set some
+ * reasonable dummies:
+ */
+ parm->hc_max_packet_size = 0x500;
+ parm->hc_max_packet_count = 3;
+ parm->hc_max_frame_size = 3 * 0x500;
+
+ usbd_transfer_setup_sub(parm);
+
+ /*
+ * compute maximum number of TDs
+ */
+ ep_type = (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE);
+
+ if (ep_type == UE_CONTROL) {
+ ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC 1 */
+ + 1 /* SYNC 2 */ + 1 /* SYNC 3 */;
+ } else {
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+ }
+
+ /*
+ * check if "usbd_transfer_setup_sub" set an error
+ */
+ if (parm->err)
+ return;
+
+ /*
+ * allocate transfer descriptors
+ */
+ last_obj = NULL;
+
+ ep_no = xfer->endpointno & UE_ADDR;
+
+ /*
+ * Check for a valid endpoint profile in USB device mode:
+ */
+ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
+ const struct usb_hw_ep_profile *pf;
+
+ dwc_otg_get_hw_ep_profile(parm->udev, &pf, ep_no);
+
+ if (pf == NULL) {
+ /* should not happen */
+ parm->err = USB_ERR_INVAL;
+ return;
+ }
+ }
+
+ /* align data */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ for (n = 0; n != ntd; n++) {
+ struct dwc_otg_td *td;
+
+ if (parm->buf) {
+ td = USB_ADD_BYTES(parm->buf, parm->size[0]);
+
+ /* compute shared bandwidth resource index for TT */
+ if (dwc_otg_uses_split(parm->udev)) {
+ if (parm->udev->parent_hs_hub->ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT)
+ td->tt_index = parm->udev->device_index;
+ else
+ td->tt_index = parm->udev->parent_hs_hub->device_index;
+ } else {
+ td->tt_index = parm->udev->device_index;
+ }
+
+ /* init TD */
+ td->max_packet_size = xfer->max_packet_size;
+ td->max_packet_count = xfer->max_packet_count;
+ /* range check */
+ if (td->max_packet_count == 0 || td->max_packet_count > 3)
+ td->max_packet_count = 1;
+ td->ep_no = ep_no;
+ td->ep_type = ep_type;
+ td->obj_next = last_obj;
+
+ last_obj = td;
+ }
+ parm->size[0] += sizeof(*td);
+ }
+
+ xfer->td_start[0] = last_obj;
+}
+
+static void
+dwc_otg_xfer_unsetup(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+dwc_otg_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
+ struct usb_endpoint *ep)
+{
+ struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d,%d)\n",
+ ep, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb_mode,
+ sc->sc_rt_addr, udev->device_index);
+
+ if (udev->device_index != sc->sc_rt_addr) {
+ if (udev->flags.usb_mode == USB_MODE_DEVICE) {
+ if (udev->speed != USB_SPEED_FULL &&
+ udev->speed != USB_SPEED_HIGH) {
+ /* not supported */
+ return;
+ }
+ } else {
+ if (udev->speed == USB_SPEED_HIGH &&
+ (edesc->wMaxPacketSize[1] & 0x18) != 0 &&
+ (edesc->bmAttributes & UE_XFERTYPE) != UE_ISOCHRONOUS) {
+ /* not supported */
+ DPRINTFN(-1, "Non-isochronous high bandwidth "
+ "endpoint not supported\n");
+ return;
+ }
+ }
+ if ((edesc->bmAttributes & UE_XFERTYPE) == UE_ISOCHRONOUS)
+ ep->methods = &dwc_otg_device_isoc_methods;
+ else
+ ep->methods = &dwc_otg_device_non_isoc_methods;
+ }
+}
+
+static void
+dwc_otg_set_hw_power_sleep(struct usb_bus *bus, uint32_t state)
+{
+ struct dwc_otg_softc *sc = DWC_OTG_BUS2SC(bus);
+
+ switch (state) {
+ case USB_HW_POWER_SUSPEND:
+ dwc_otg_suspend(sc);
+ break;
+ case USB_HW_POWER_SHUTDOWN:
+ dwc_otg_uninit(sc);
+ break;
+ case USB_HW_POWER_RESUME:
+ dwc_otg_resume(sc);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+dwc_otg_get_dma_delay(struct usb_device *udev, uint32_t *pus)
+{
+ /* DMA delay - wait until any use of memory is finished */
+ *pus = (2125); /* microseconds */
+}
+
+static void
+dwc_otg_device_resume(struct usb_device *udev)
+{
+ DPRINTF("\n");
+
+ /* poll all transfers again to restart resumed ones */
+ dwc_otg_do_poll(udev->bus);
+}
+
+static void
+dwc_otg_device_suspend(struct usb_device *udev)
+{
+ DPRINTF("\n");
+}
+
+static const struct usb_bus_methods dwc_otg_bus_methods =
+{
+ .endpoint_init = &dwc_otg_ep_init,
+ .xfer_setup = &dwc_otg_xfer_setup,
+ .xfer_unsetup = &dwc_otg_xfer_unsetup,
+ .get_hw_ep_profile = &dwc_otg_get_hw_ep_profile,
+ .xfer_stall = &dwc_otg_xfer_stall,
+ .set_stall = &dwc_otg_set_stall,
+ .clear_stall = &dwc_otg_clear_stall,
+ .roothub_exec = &dwc_otg_roothub_exec,
+ .xfer_poll = &dwc_otg_do_poll,
+ .device_state_change = &dwc_otg_device_state_change,
+ .set_hw_power_sleep = &dwc_otg_set_hw_power_sleep,
+ .get_dma_delay = &dwc_otg_get_dma_delay,
+ .device_resume = &dwc_otg_device_resume,
+ .device_suspend = &dwc_otg_device_suspend,
+};
diff --git a/sys/dev/usb/controller/dwc_otg.h b/sys/dev/usb/controller/dwc_otg.h
new file mode 100644
index 000000000000..fe0aef6d6b75
--- /dev/null
+++ b/sys/dev/usb/controller/dwc_otg.h
@@ -0,0 +1,226 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _DWC_OTG_H_
+#define _DWC_OTG_H_
+
+#define DWC_OTG_MAX_DEVICES MIN(USB_MAX_DEVICES, 32)
+#define DWC_OTG_FRAME_MASK 0x7FF
+#define DWC_OTG_MAX_TXP 4
+#define DWC_OTG_MAX_TXN (0x200 * DWC_OTG_MAX_TXP)
+#define DWC_OTG_MAX_CHANNELS 16
+#define DWC_OTG_MAX_ENDPOINTS 16
+#define DWC_OTG_HOST_TIMER_RATE 10 /* ms */
+#define DWC_OTG_TT_SLOT_MAX 8
+#define DWC_OTG_SLOT_IDLE_MAX 3
+#define DWC_OTG_SLOT_IDLE_MIN 2
+#ifndef DWC_OTG_TX_MAX_FIFO_SIZE
+#define DWC_OTG_TX_MAX_FIFO_SIZE DWC_OTG_MAX_TXN
+#endif
+
+#define DWC_OTG_READ_4(sc, reg) \
+ bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg)
+
+#define DWC_OTG_WRITE_4(sc, reg, data) \
+ bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data)
+
+struct dwc_otg_td;
+struct dwc_otg_softc;
+
+typedef uint8_t (dwc_otg_cmd_t)(struct dwc_otg_softc *sc, struct dwc_otg_td *td);
+
+struct dwc_otg_td {
+ struct dwc_otg_td *obj_next;
+ dwc_otg_cmd_t *func;
+ struct usb_page_cache *pc;
+ uint32_t tx_bytes;
+ uint32_t offset;
+ uint32_t remainder;
+ uint32_t hcchar; /* HOST CFG */
+ uint32_t hcsplt; /* HOST CFG */
+ uint16_t max_packet_size; /* packet_size */
+ uint16_t npkt;
+ uint8_t max_packet_count; /* packet_count */
+ uint8_t errcnt;
+ uint8_t tmr_res;
+ uint8_t tmr_val;
+ uint8_t ep_no;
+ uint8_t ep_type;
+ uint8_t channel[3];
+ uint8_t tt_index; /* TT data */
+ uint8_t tt_start_slot; /* TT data */
+ uint8_t tt_complete_slot; /* TT data */
+ uint8_t tt_xactpos; /* TT data */
+ uint8_t state;
+#define DWC_CHAN_ST_START 0
+#define DWC_CHAN_ST_WAIT_ANE 1
+#define DWC_CHAN_ST_WAIT_S_ANE 2
+#define DWC_CHAN_ST_WAIT_C_ANE 3
+#define DWC_CHAN_ST_WAIT_C_PKT 4
+#define DWC_CHAN_ST_TX_WAIT_ISOC 5
+ uint8_t error_any:1;
+ uint8_t error_stall:1;
+ uint8_t alt_next:1;
+ uint8_t short_pkt:1;
+ uint8_t did_stall:1;
+ uint8_t toggle:1;
+ uint8_t set_toggle:1;
+ uint8_t got_short:1;
+ uint8_t tt_scheduled:1;
+ uint8_t did_nak:1;
+};
+
+struct dwc_otg_tt_info {
+ uint8_t slot_index;
+};
+
+struct dwc_otg_std_temp {
+ dwc_otg_cmd_t *func;
+ struct usb_page_cache *pc;
+ struct dwc_otg_td *td;
+ struct dwc_otg_td *td_next;
+ uint32_t len;
+ uint32_t offset;
+ uint16_t max_frame_size;
+ uint8_t short_pkt;
+
+ /*
+ * short_pkt = 0: transfer should be short terminated
+ * short_pkt = 1: transfer should not be short terminated
+ */
+ uint8_t setup_alt_next;
+ uint8_t did_stall;
+ uint8_t bulk_or_control;
+};
+
+struct dwc_otg_config_desc {
+ struct usb_config_descriptor confd;
+ struct usb_interface_descriptor ifcd;
+ struct usb_endpoint_descriptor endpd;
+} __packed;
+
+union dwc_otg_hub_temp {
+ uWord wValue;
+ struct usb_port_status ps;
+};
+
+struct dwc_otg_flags {
+ uint8_t change_connect:1;
+ uint8_t change_suspend:1;
+ uint8_t change_reset:1;
+ uint8_t change_enabled:1;
+ uint8_t change_over_current:1;
+ uint8_t status_suspend:1; /* set if suspended */
+ uint8_t status_vbus:1; /* set if present */
+ uint8_t status_bus_reset:1; /* set if reset complete */
+ uint8_t status_high_speed:1; /* set if High Speed is selected */
+ uint8_t status_low_speed:1; /* set if Low Speed is selected */
+ uint8_t status_device_mode:1; /* set if device mode */
+ uint8_t self_powered:1;
+ uint8_t clocks_off:1;
+ uint8_t port_powered:1;
+ uint8_t port_enabled:1;
+ uint8_t port_over_current:1;
+ uint8_t d_pulled_up:1;
+};
+
+struct dwc_otg_profile {
+ struct usb_hw_ep_profile usb;
+ uint16_t max_buffer;
+};
+
+struct dwc_otg_chan_state {
+ uint16_t allocated;
+ uint16_t wait_halted;
+ uint32_t hcint;
+};
+
+struct dwc_otg_softc {
+ struct usb_bus sc_bus;
+ union dwc_otg_hub_temp sc_hub_temp;
+ struct dwc_otg_profile sc_hw_ep_profile[DWC_OTG_MAX_ENDPOINTS];
+ struct dwc_otg_tt_info sc_tt_info[DWC_OTG_MAX_DEVICES];
+ struct usb_callout sc_timer;
+
+ struct usb_device *sc_devices[DWC_OTG_MAX_DEVICES];
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ void *sc_intr_hdl;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ uint32_t sc_bounce_buffer[MAX(512 * DWC_OTG_MAX_TXP, 1024) / 4];
+
+ uint32_t sc_fifo_size;
+ uint32_t sc_irq_mask;
+ uint32_t sc_last_rx_status;
+ uint32_t sc_out_ctl[DWC_OTG_MAX_ENDPOINTS];
+ uint32_t sc_in_ctl[DWC_OTG_MAX_ENDPOINTS];
+ struct dwc_otg_chan_state sc_chan_state[DWC_OTG_MAX_CHANNELS];
+ uint32_t sc_tmr_val;
+ uint32_t sc_hprt_val;
+ uint32_t sc_xfer_complete;
+
+ uint16_t sc_current_rx_bytes;
+ uint16_t sc_current_rx_fifo;
+
+ uint16_t sc_active_rx_ep;
+ uint16_t sc_last_frame_num;
+
+ uint8_t sc_phy_type;
+ uint8_t sc_phy_bits;
+#define DWC_OTG_PHY_ULPI 1
+#define DWC_OTG_PHY_HSIC 2
+#define DWC_OTG_PHY_INTERNAL 3
+#define DWC_OTG_PHY_UTMI 4
+
+ uint8_t sc_timer_active;
+ uint8_t sc_dev_ep_max;
+ uint8_t sc_dev_in_ep_max;
+ uint8_t sc_host_ch_max;
+ uint8_t sc_needsof;
+ uint8_t sc_rt_addr; /* root HUB address */
+ uint8_t sc_conf; /* root HUB config */
+ uint8_t sc_mode; /* mode of operation */
+#define DWC_MODE_OTG 0 /* both modes */
+#define DWC_MODE_DEVICE 1 /* device only */
+#define DWC_MODE_HOST 2 /* host only */
+
+ uint8_t sc_hub_idata[1];
+
+ struct dwc_otg_flags sc_flags;
+};
+
+/* prototypes */
+
+driver_filter_t dwc_otg_filter_interrupt;
+driver_intr_t dwc_otg_interrupt;
+int dwc_otg_init(struct dwc_otg_softc *);
+void dwc_otg_uninit(struct dwc_otg_softc *);
+
+#endif /* _DWC_OTG_H_ */
diff --git a/sys/dev/usb/controller/dwc_otg_acpi.c b/sys/dev/usb/controller/dwc_otg_acpi.c
new file mode 100644
index 000000000000..d8deaa80e94e
--- /dev/null
+++ b/sys/dev/usb/controller/dwc_otg_acpi.c
@@ -0,0 +1,182 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012 Hans Petter Selasky.
+ *
+ * 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 "opt_acpi.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+
+#include <dev/acpica/acpivar.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+
+#include <dev/usb/controller/dwc_otg.h>
+
+static device_probe_t dwc_otg_probe;
+static device_attach_t dwc_otg_attach;
+static device_attach_t dwc_otg_detach;
+
+static char *dwc_otg_ids[] = {
+ "BCM2848",
+ NULL
+};
+
+static int
+dwc_otg_probe(device_t dev)
+{
+ int rv;
+
+ if (acpi_disabled("dwc_otg"))
+ return (ENXIO);
+
+ rv = ACPI_ID_PROBE(device_get_parent(dev), dev, dwc_otg_ids, NULL);
+ if (rv > 0)
+ return (rv);
+
+ device_set_desc(dev, "DWC OTG 2.0 integrated USB controller");
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+dwc_otg_attach(device_t dev)
+{
+ struct dwc_otg_softc *sc = device_get_softc(dev);
+ int err;
+ int rid;
+
+ sc->sc_bus.parent = dev;
+
+ /* assume device mode (this is only used for the Raspberry Pi 4's
+ * USB-C port, which only works in device mode) */
+ sc->sc_mode = DWC_MODE_DEVICE;
+
+ rid = 0;
+ sc->sc_io_res =
+ bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+
+ if (sc->sc_io_res == NULL)
+ goto error;
+
+ rid = 0;
+ sc->sc_irq_res =
+ bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
+ if (sc->sc_irq_res == NULL)
+ goto error;
+
+ err = dwc_otg_init(sc);
+ if (err == 0) {
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ }
+ if (err)
+ goto error;
+
+ return (0);
+
+error:
+ dwc_otg_detach(dev);
+ return (ENXIO);
+}
+
+static int
+dwc_otg_detach(device_t dev)
+{
+ struct dwc_otg_softc *sc = device_get_softc(dev);
+ int error;
+
+ /* during module unload there are lots of children leftover */
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ /*
+ * only call dwc_otg_uninit() after dwc_otg_init()
+ */
+ dwc_otg_uninit(sc);
+
+ bus_teardown_intr(dev, sc->sc_irq_res,
+ sc->sc_intr_hdl);
+ sc->sc_intr_hdl = NULL;
+ }
+ /* free IRQ channel, if any */
+ if (sc->sc_irq_res) {
+ bus_release_resource(dev, SYS_RES_IRQ, 0,
+ sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ }
+ /* free memory resource, if any */
+ if (sc->sc_io_res) {
+ bus_release_resource(dev, SYS_RES_MEMORY, 0,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+ usb_bus_mem_free_all(&sc->sc_bus, NULL);
+
+ return (0);
+}
+
+static device_method_t dwc_otg_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, dwc_otg_probe),
+ DEVMETHOD(device_attach, dwc_otg_attach),
+ DEVMETHOD(device_detach, dwc_otg_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ DEVMETHOD_END
+};
+
+static driver_t dwc_otg_driver = {
+ .name = "dwcotg",
+ .methods = dwc_otg_methods,
+ .size = sizeof(struct dwc_otg_softc),
+};
+
+DRIVER_MODULE(dwcotg, acpi, dwc_otg_driver, 0, 0);
+MODULE_DEPEND(dwcotg, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/dwc_otg_fdt.c b/sys/dev/usb/controller/dwc_otg_fdt.c
new file mode 100644
index 000000000000..2ed94b23212c
--- /dev/null
+++ b/sys/dev/usb/controller/dwc_otg_fdt.c
@@ -0,0 +1,214 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012 Hans Petter Selasky.
+ *
+ * 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/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/rman.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+
+#include <dev/usb/controller/dwc_otg.h>
+#include <dev/usb/controller/dwc_otg_fdt.h>
+
+static device_probe_t dwc_otg_probe;
+
+static struct ofw_compat_data compat_data[] = {
+ { "synopsys,designware-hs-otg2", 1 },
+ { "snps,dwc2", 1 },
+ { NULL, 0 }
+};
+
+static int
+dwc_otg_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
+ return (ENXIO);
+
+ device_set_desc(dev, "DWC OTG 2.0 integrated USB controller");
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+dwc_otg_irq_index(device_t dev, int *rid)
+{
+ int idx, rv;
+ phandle_t node;
+
+ node = ofw_bus_get_node(dev);
+ rv = ofw_bus_find_string_index(node, "interrupt-names", "usb", &idx);
+ if (rv != 0)
+ return (rv);
+ *rid = idx;
+ return (0);
+}
+
+int
+dwc_otg_attach(device_t dev)
+{
+ struct dwc_otg_fdt_softc *sc = device_get_softc(dev);
+ char usb_mode[24];
+ int err;
+ int rid;
+
+ sc->sc_otg.sc_bus.parent = dev;
+
+ /* get USB mode, if any */
+ if (OF_getprop(ofw_bus_get_node(dev), "dr_mode",
+ &usb_mode, sizeof(usb_mode)) > 0) {
+ /* ensure proper zero termination */
+ usb_mode[sizeof(usb_mode) - 1] = 0;
+
+ if (strcasecmp(usb_mode, "host") == 0)
+ sc->sc_otg.sc_mode = DWC_MODE_HOST;
+ else if (strcasecmp(usb_mode, "peripheral") == 0)
+ sc->sc_otg.sc_mode = DWC_MODE_DEVICE;
+ else if (strcasecmp(usb_mode, "otg") != 0) {
+ device_printf(dev, "Invalid FDT dr_mode: %s\n",
+ usb_mode);
+ }
+ }
+
+ rid = 0;
+ sc->sc_otg.sc_io_res =
+ bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+
+ if (!(sc->sc_otg.sc_io_res))
+ goto error;
+
+ /*
+ * brcm,bcm2708-usb FDT provides two interrupts, we need only the USB
+ * interrupt (VC_USB). The latest FDT for it provides an
+ * interrupt-names property and swapped them around, while older ones
+ * did not have interrupt-names and put the usb interrupt in the second
+ * position. We'll attempt to use interrupt-names first with a fallback
+ * to the old method of assuming the index based on the compatible
+ * string.
+ */
+ if (dwc_otg_irq_index(dev, &rid) != 0)
+ rid = ofw_bus_is_compatible(dev, "brcm,bcm2708-usb") ? 1 : 0;
+ sc->sc_otg.sc_irq_res =
+ bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
+ if (sc->sc_otg.sc_irq_res == NULL)
+ goto error;
+
+ err = dwc_otg_init(&sc->sc_otg);
+ if (err == 0) {
+ err = device_probe_and_attach(sc->sc_otg.sc_bus.bdev);
+ }
+ if (err)
+ goto error;
+
+ return (0);
+
+error:
+ dwc_otg_detach(dev);
+ return (ENXIO);
+}
+
+int
+dwc_otg_detach(device_t dev)
+{
+ struct dwc_otg_fdt_softc *sc = device_get_softc(dev);
+ int error;
+
+ /* during module unload there are lots of children leftover */
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ if (sc->sc_otg.sc_irq_res && sc->sc_otg.sc_intr_hdl) {
+ /*
+ * only call dwc_otg_uninit() after dwc_otg_init()
+ */
+ dwc_otg_uninit(&sc->sc_otg);
+
+ bus_teardown_intr(dev, sc->sc_otg.sc_irq_res,
+ sc->sc_otg.sc_intr_hdl);
+ sc->sc_otg.sc_intr_hdl = NULL;
+ }
+ /* free IRQ channel, if any */
+ if (sc->sc_otg.sc_irq_res) {
+ bus_release_resource(dev, SYS_RES_IRQ, 0,
+ sc->sc_otg.sc_irq_res);
+ sc->sc_otg.sc_irq_res = NULL;
+ }
+ /* free memory resource, if any */
+ if (sc->sc_otg.sc_io_res) {
+ bus_release_resource(dev, SYS_RES_MEMORY, 0,
+ sc->sc_otg.sc_io_res);
+ sc->sc_otg.sc_io_res = NULL;
+ }
+ usb_bus_mem_free_all(&sc->sc_otg.sc_bus, NULL);
+
+ return (0);
+}
+
+static device_method_t dwc_otg_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, dwc_otg_probe),
+ DEVMETHOD(device_attach, dwc_otg_attach),
+ DEVMETHOD(device_detach, dwc_otg_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ DEVMETHOD_END
+};
+
+driver_t dwc_otg_driver = {
+ .name = "dwcotg",
+ .methods = dwc_otg_methods,
+ .size = sizeof(struct dwc_otg_fdt_softc),
+};
+
+DRIVER_MODULE(dwcotg, simplebus, dwc_otg_driver, 0, 0);
+MODULE_DEPEND(dwcotg, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/dwc_otg_fdt.h b/sys/dev/usb/controller/dwc_otg_fdt.h
new file mode 100644
index 000000000000..2559360255a8
--- /dev/null
+++ b/sys/dev/usb/controller/dwc_otg_fdt.h
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 2012 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _DWC_OTG_FDT_H_
+#define _DWC_OTG_FDT_H_
+
+struct dwc_otg_fdt_softc {
+ struct dwc_otg_softc sc_otg; /* must be first */
+};
+
+extern driver_t dwc_otg_driver;
+
+device_attach_t dwc_otg_attach;
+device_attach_t dwc_otg_detach;
+
+#endif
diff --git a/sys/dev/usb/controller/dwc_otg_hisi.c b/sys/dev/usb/controller/dwc_otg_hisi.c
new file mode 100644
index 000000000000..994ab3b6e80e
--- /dev/null
+++ b/sys/dev/usb/controller/dwc_otg_hisi.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2015 Andrew Turner.
+ * 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/param.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/callout.h>
+#include <sys/condvar.h>
+#include <sys/module.h>
+
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+
+#include <dev/usb/controller/dwc_otg.h>
+#include <dev/usb/controller/dwc_otg_fdt.h>
+
+static device_probe_t hisi_dwc_otg_probe;
+static device_attach_t hisi_dwc_otg_attach;
+
+static int
+hisi_dwc_otg_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (!ofw_bus_is_compatible(dev, "huawei,hisi-usb"))
+ return (ENXIO);
+
+ device_set_desc(dev, "DWC OTG 2.0 integrated USB controller (hisilicon)");
+
+ return (BUS_PROBE_VENDOR);
+}
+
+static int
+hisi_dwc_otg_attach(device_t dev)
+{
+ struct dwc_otg_fdt_softc *sc;
+
+ /* Set the default to host mode. */
+ /* TODO: Use vbus to detect this. */
+ sc = device_get_softc(dev);
+ sc->sc_otg.sc_mode = DWC_MODE_HOST;
+
+ return (dwc_otg_attach(dev));
+}
+
+static device_method_t hisi_dwc_otg_methods[] = {
+ /* bus interface */
+ DEVMETHOD(device_probe, hisi_dwc_otg_probe),
+ DEVMETHOD(device_attach, hisi_dwc_otg_attach),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(hisi_dwcotg, hisi_dwc_otg_driver, hisi_dwc_otg_methods,
+ sizeof(struct dwc_otg_fdt_softc), dwc_otg_driver);
+DRIVER_MODULE(hisi_dwcotg, simplebus, hisi_dwc_otg_driver, 0, 0);
+MODULE_DEPEND(hisi_dwcotg, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/dwc_otgreg.h b/sys/dev/usb/controller/dwc_otgreg.h
new file mode 100644
index 000000000000..e012de28c548
--- /dev/null
+++ b/sys/dev/usb/controller/dwc_otgreg.h
@@ -0,0 +1,803 @@
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010,2011 Aleksandr Rybalko. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _DWC_OTGREG_H_
+#define _DWC_OTGREG_H_
+
+#define DOTG_GOTGCTL 0x0000
+#define DOTG_GOTGINT 0x0004
+#define DOTG_GAHBCFG 0x0008
+#define DOTG_GUSBCFG 0x000C
+#define DOTG_GRSTCTL 0x0010
+#define DOTG_GINTSTS 0x0014
+#define DOTG_GINTMSK 0x0018
+#define DOTG_GRXSTSRD 0x001C
+#define DOTG_GRXSTSRH 0x001C
+#define DOTG_GRXSTSPD 0x0020
+#define DOTG_GRXSTSPH 0x0020
+#define DOTG_GRXFSIZ 0x0024
+#define DOTG_GNPTXFSIZ 0x0028
+#define DOTG_GNPTXSTS 0x002C
+#define DOTG_GI2CCTL 0x0030
+#define DOTG_GPVNDCTL 0x0034
+#define DOTG_GGPIO 0x0038
+#define DOTG_GUID 0x003C
+#define DOTG_GSNPSID 0x0040
+#define DOTG_GSNPSID_REV_2_80a 0x4f54280a /* RPi model B/RPi2 */
+#define DOTG_GSNPSID_REV_3_10a 0x4f54310a /* ODROID-C1 */
+#define DOTG_GHWCFG1 0x0044
+#define DOTG_GHWCFG2 0x0048
+#define DOTG_GHWCFG3 0x004C
+#define DOTG_GHWCFG4 0x0050
+#define DOTG_GLPMCFG 0x0054
+#define DOTG_GPWRDN 0x0058
+#define DOTG_GDFIFOCFG 0x005C
+#define DOTG_GADPCTL 0x0060
+
+#define DOTG_HPTXFSIZ 0x0100
+/* start from 0x104, but fifo0 not exists */
+#define DOTG_DPTXFSIZ(fifo) (0x0100 + (4*(fifo)))
+#define DOTG_DIEPTXF(fifo) (0x0100 + (4*(fifo)))
+
+#define DOTG_HCFG 0x0400
+#define DOTG_HFIR 0x0404
+#define DOTG_HFNUM 0x0408
+#define DOTG_HPTXSTS 0x0410
+#define DOTG_HAINT 0x0414
+#define DOTG_HAINTMSK 0x0418
+#define DOTG_HPRT 0x0440
+
+#define DOTG_HCCHAR(ch) (0x0500 + (32*(ch)))
+#define DOTG_HCSPLT(ch) (0x0504 + (32*(ch)))
+#define DOTG_HCINT(ch) (0x0508 + (32*(ch)))
+#define DOTG_HCINTMSK(ch) (0x050C + (32*(ch)))
+#define DOTG_HCTSIZ(ch) (0x0510 + (32*(ch)))
+#define DOTG_HCDMA(ch) (0x0514 + (32*(ch)))
+#define DOTG_HCDMAI(ch) (0x0514 + (32*(ch)))
+#define DOTG_HCDMAO(ch) (0x0514 + (32*(ch)))
+#define DOTG_HCDMAB(ch) (0x051C + (32*(ch)))
+
+/* Device Mode */
+#define DOTG_DCFG 0x0800
+#define DOTG_DCTL 0x0804
+#define DOTG_DSTS 0x0808
+#define DOTG_DIEPMSK 0x0810
+#define DOTG_DOEPMSK 0x0814
+#define DOTG_DAINT 0x0818
+#define DOTG_DAINTMSK 0x081C
+#define DOTG_DTKNQR1 0x0820
+#define DOTG_DTKNQR2 0x0824
+#define DOTG_DVBUSDIS 0x0828
+#define DOTG_DVBUSPULSE 0x082C
+#define DOTG_DTHRCTL 0x0830
+#define DOTG_DTKNQR4 0x0834
+#define DOTG_DIEPEMPMSK 0x0834
+#define DOTG_DEACHINT 0x0838
+#define DOTG_DEACHINTMSK 0x083C
+#define DOTG_DIEPEACHINTMSK(ch) (0x0840 + (4*(ch)))
+#define DOTG_DOEPEACHINTMSK(ch) (0x0880 + (4*(ch)))
+
+#define DOTG_DIEPCTL(ep) (0x0900 + (32*(ep)))
+#define DOTG_DIEPINT(ep) (0x0908 + (32*(ep)))
+#define DOTG_DIEPTSIZ(ep) (0x0910 + (32*(ep)))
+#define DOTG_DIEPDMA(ep) (0x0914 + (32*(ep)))
+#define DOTG_DTXFSTS(ep) (0x0918 + (32*(ep)))
+#define DOTG_DIEPDMAB(ep) (0x091c + (32*(ep)))
+
+#define DOTG_DOEPCTL(ep) (0x0B00 + (32*(ep)))
+#define DOTG_DOEPFN(ep) (0x0B04 + (32*(ep)))
+#define DOTG_DOEPINT(ep) (0x0B08 + (32*(ep)))
+#define DOTG_DOEPTSIZ(ep) (0x0B10 + (32*(ep)))
+#define DOTG_DOEPDMA(ep) (0x0B14 + (32*(ep)))
+#define DOTG_DOEPDMAB(ep) (0x0B1c + (32*(ep)))
+/* End Device Mode */
+
+/* Host Mode
+#define DOTG_CTL_STATUS 0x0800
+#define DOTG_DMA0_INB_CHN0 0x0818
+#define DOTG_DMA0_INB_CHN1 0x0820
+#define DOTG_DMA0_INB_CHN2 0x0828
+#define DOTG_DVBUSDIS 0x0828
+#define DOTG_DVBUSPULSE 0x082c
+#define DOTG_DMA0_INB_CHN3 0x0830
+#define DOTG_DMA0_INB_CHN4 0x0838
+#define DOTG_DMA0_INB_CHN5 0x0840
+#define DOTG_DMA0_INB_CHN6 0x0848
+#define DOTG_DMA0_INB_CHN7 0x0850
+#define DOTG_DMA0_OUTB_CHN0 0x0858
+#define DOTG_DMA0_OUTB_CHN1 0x0860
+#define DOTG_DMA0_OUTB_CHN2 0x0868
+#define DOTG_DMA0_OUTB_CHN3 0x0870
+#define DOTG_DMA0_OUTB_CHN4 0x0878
+#define DOTG_DMA0_OUTB_CHN5 0x0880
+#define DOTG_DMA0_OUTB_CHN6 0x0888
+#define DOTG_DMA0_OUTB_CHN7 0x0890
+ End Host Mode */
+
+/* Power and clock gating CSR */
+
+#define DOTG_PCGCCTL 0x0E00
+
+/* FIFO access registers (PIO-mode) */
+
+#define DOTG_DFIFO(n) (0x1000 + (0x1000 * (n)))
+
+#define GOTGCTL_CHIRP_ON (1<<27)
+#define GOTGCTL_BSESVLD (1<<19)
+#define GOTGCTL_ASESVLD (1<<18)
+#define GOTGCTL_DBNCTIME (1<<17)
+#define GOTGCTL_CONIDSTS (1<<16)
+#define GOTGCTL_DEVHNPEN (1<<11)
+#define GOTGCTL_HSTSETHNPEN (1<<10)
+#define GOTGCTL_HNPREQ (1<<9)
+#define GOTGCTL_HSTNEGSCS (1<<8)
+#define GOTGCTL_SESREQ (1<<1)
+#define GOTGCTL_SESREQSCS (1<<0)
+
+#define GOTGCTL_DBNCEDONE (1<<19)
+#define GOTGCTL_ADEVTOUTCHG (1<<18)
+#define GOTGCTL_HSTNEGDET (1<<17)
+#define GOTGCTL_HSTNEGSUCSTSCHG (1<<9)
+#define GOTGCTL_SESREQSUCSTSCHG (1<<8)
+#define GOTGCTL_SESENDDET (1<<2)
+
+#define GAHBCFG_PTXFEMPLVL (1<<8)
+#define GAHBCFG_NPTXFEMPLVL (1<<7)
+#define GAHBCFG_DMAEN (1<<5)
+#define GAHBCFG_HBSTLEN_MASK 0x0000001e
+#define GAHBCFG_HBSTLEN_SHIFT 1
+#define GAHBCFG_GLBLINTRMSK (1<<0)
+
+#define GUSBCFG_CORRUPTTXPACKET (1<<31)
+#define GUSBCFG_FORCEDEVMODE (1<<30)
+#define GUSBCFG_FORCEHOSTMODE (1<<29)
+#define GUSBCFG_NO_PULLUP (1<<27)
+#define GUSBCFG_IC_USB_CAP (1<<26)
+#define GUSBCFG_TERMSELDLPULSE (1<<22)
+#define GUSBCFG_ULPIEXTVBUSINDICATOR (1<<21)
+#define GUSBCFG_ULPIEXTVBUSDRV (1<<20)
+#define GUSBCFG_ULPICLKSUSM (1<<19)
+#define GUSBCFG_ULPIAUTORES (1<<18)
+#define GUSBCFG_ULPIFSLS (1<<17)
+#define GUSBCFG_OTGI2CSEL (1<<16)
+#define GUSBCFG_PHYLPWRCLKSEL (1<<15)
+#define GUSBCFG_USBTRDTIM_MASK 0x00003c00
+#define GUSBCFG_USBTRDTIM_SHIFT 10
+#define GUSBCFG_TRD_TIM_SET(x) (((x) & 15) << 10)
+#define GUSBCFG_HNPCAP (1<<9)
+#define GUSBCFG_SRPCAP (1<<8)
+#define GUSBCFG_DDRSEL (1<<7)
+#define GUSBCFG_PHYSEL (1<<6)
+#define GUSBCFG_FSINTF (1<<5)
+#define GUSBCFG_ULPI_UTMI_SEL (1<<4)
+#define GUSBCFG_PHYIF (1<<3)
+#define GUSBCFG_TOUTCAL_MASK 0x00000007
+#define GUSBCFG_TOUTCAL_SHIFT 0
+
+/* STM32F4 */
+#define DOTG_GGPIO_NOVBUSSENS (1 << 21)
+#define DOTG_GGPIO_SOFOUTEN (1 << 20)
+#define DOTG_GGPIO_VBUSBSEN (1 << 19)
+#define DOTG_GGPIO_VBUSASEN (1 << 18)
+#define DOTG_GGPIO_I2CPADEN (1 << 17)
+#define DOTG_GGPIO_PWRDWN (1 << 16)
+
+#define GRSTCTL_AHBIDLE (1<<31)
+#define GRSTCTL_DMAREQ (1<<30)
+#define GRSTCTL_TXFNUM_MASK 0x000007c0
+#define GRSTCTL_TXFNUM_SHIFT 6
+#define GRSTCTL_TXFIFO(n) (((n) & 31) << 6)
+#define GRSTCTL_TXFFLSH (1<<5)
+#define GRSTCTL_RXFFLSH (1<<4)
+#define GRSTCTL_INTKNQFLSH (1<<3)
+#define GRSTCTL_FRMCNTRRST (1<<2)
+#define GRSTCTL_HSFTRST (1<<1)
+#define GRSTCTL_CSFTRST (1<<0)
+
+#define GINTSTS_WKUPINT (1<<31)
+#define GINTSTS_SESSREQINT (1<<30)
+#define GINTSTS_DISCONNINT (1<<29)
+#define GINTSTS_CONIDSTSCHNG (1<<28)
+#define GINTSTS_LPM (1<<27)
+#define GINTSTS_PTXFEMP (1<<26)
+#define GINTSTS_HCHINT (1<<25)
+#define GINTSTS_PRTINT (1<<24)
+#define GINTSTS_RESETDET (1<<23)
+#define GINTSTS_FETSUSP (1<<22)
+#define GINTSTS_INCOMPLP (1<<21)
+#define GINTSTS_INCOMPISOIN (1<<20)
+#define GINTSTS_OEPINT (1<<19)
+#define GINTSTS_IEPINT (1<<18)
+#define GINTSTS_EPMIS (1<<17)
+#define GINTSTS_RESTORE_DONE (1<<16)
+#define GINTSTS_EOPF (1<<15)
+#define GINTSTS_ISOOUTDROP (1<<14)
+#define GINTSTS_ENUMDONE (1<<13)
+#define GINTSTS_USBRST (1<<12)
+#define GINTSTS_USBSUSP (1<<11)
+#define GINTSTS_ERLYSUSP (1<<10)
+#define GINTSTS_I2CINT (1<<9)
+#define GINTSTS_ULPICKINT (1<<8)
+#define GINTSTS_GOUTNAKEFF (1<<7)
+#define GINTSTS_GINNAKEFF (1<<6)
+#define GINTSTS_NPTXFEMP (1<<5)
+#define GINTSTS_RXFLVL (1<<4)
+#define GINTSTS_SOF (1<<3)
+#define GINTSTS_OTGINT (1<<2)
+#define GINTSTS_MODEMIS (1<<1)
+#define GINTSTS_CURMOD (1<<0)
+
+#define GINTMSK_WKUPINTMSK (1<<31)
+#define GINTMSK_SESSREQINTMSK (1<<30)
+#define GINTMSK_DISCONNINTMSK (1<<29)
+#define GINTMSK_CONIDSTSCHNGMSK (1<<28)
+#define GINTMSK_PTXFEMPMSK (1<<26)
+#define GINTMSK_HCHINTMSK (1<<25)
+#define GINTMSK_PRTINTMSK (1<<24)
+#define GINTMSK_FETSUSPMSK (1<<22)
+#define GINTMSK_INCOMPLPMSK (1<<21)
+#define GINTMSK_INCOMPISOINMSK (1<<20)
+#define GINTMSK_OEPINTMSK (1<<19)
+#define GINTMSK_IEPINTMSK (1<<18)
+#define GINTMSK_EPMISMSK (1<<17)
+#define GINTMSK_EOPFMSK (1<<15)
+#define GINTMSK_ISOOUTDROPMSK (1<<14)
+#define GINTMSK_ENUMDONEMSK (1<<13)
+#define GINTMSK_USBRSTMSK (1<<12)
+#define GINTMSK_USBSUSPMSK (1<<11)
+#define GINTMSK_ERLYSUSPMSK (1<<10)
+#define GINTMSK_I2CINTMSK (1<<9)
+#define GINTMSK_ULPICKINTMSK (1<<8)
+#define GINTMSK_GOUTNAKEFFMSK (1<<7)
+#define GINTMSK_GINNAKEFFMSK (1<<6)
+#define GINTMSK_NPTXFEMPMSK (1<<5)
+#define GINTMSK_RXFLVLMSK (1<<4)
+#define GINTMSK_SOFMSK (1<<3)
+#define GINTMSK_OTGINTMSK (1<<2)
+#define GINTMSK_MODEMISMSK (1<<1)
+#define GINTMSK_CURMODMSK (1<<0)
+
+#define GRXSTSRH_PKTSTS_MASK 0x001e0000
+#define GRXSTSRH_PKTSTS_SHIFT 17
+#define GRXSTSRH_DPID_MASK 0x00018000
+#define GRXSTSRH_DPID_SHIFT 15
+#define GRXSTSRH_BCNT_MASK 0x00007ff0
+#define GRXSTSRH_BCNT_SHIFT 4
+#define GRXSTSRH_CHNUM_MASK 0x0000000f
+#define GRXSTSRH_CHNUM_SHIFT 0
+
+#define GRXSTSRD_FN_MASK 0x01e00000
+#define GRXSTSRD_FN_GET(x) (((x) >> 21) & 15)
+#define GRXSTSRD_FN_SHIFT 21
+#define GRXSTSRD_PKTSTS_MASK 0x001e0000
+#define GRXSTSRD_PKTSTS_SHIFT 17
+#define GRXSTSRH_IN_DATA (2<<17)
+#define GRXSTSRH_IN_COMPLETE (3<<17)
+#define GRXSTSRH_DT_ERROR (5<<17)
+#define GRXSTSRH_HALTED (7<<17)
+#define GRXSTSRD_GLOB_OUT_NAK (1<<17)
+#define GRXSTSRD_OUT_DATA (2<<17)
+#define GRXSTSRD_OUT_COMPLETE (3<<17)
+#define GRXSTSRD_STP_COMPLETE (4<<17)
+#define GRXSTSRD_STP_DATA (6<<17)
+#define GRXSTSRD_DPID_MASK 0x00018000
+#define GRXSTSRD_DPID_SHIFT 15
+#define GRXSTSRD_DPID_DATA0 (0<<15)
+#define GRXSTSRD_DPID_DATA1 (2<<15)
+#define GRXSTSRD_DPID_DATA2 (1<<15)
+#define GRXSTSRD_DPID_MDATA (3<<15)
+#define GRXSTSRD_BCNT_MASK 0x00007ff0
+#define GRXSTSRD_BCNT_GET(x) (((x) >> 4) & 0x7FF)
+#define GRXSTSRD_BCNT_SHIFT 4
+#define GRXSTSRD_CHNUM_MASK 0x0000000f
+#define GRXSTSRD_CHNUM_GET(x) ((x) & 15)
+#define GRXSTSRD_CHNUM_SHIFT 0
+
+#define GRXFSIZ_RXFDEP_MASK 0x0000ffff
+#define GRXFSIZ_RXFDEP_SHIFT 0
+
+#define GNPTXFSIZ_NPTXFDEP_MASK 0xffff0000
+#define GNPTXFSIZ_NPTXFDEP_SHIFT 0
+#define GNPTXFSIZ_NPTXFSTADDR_MASK 0x0000ffff
+#define GNPTXFSIZ_NPTXFSTADDR_SHIFT 16
+
+#define GNPTXSTS_NPTXQTOP_SHIFT 24
+#define GNPTXSTS_NPTXQTOP_MASK 0x7f000000
+#define GNPTXSTS_NPTXQSPCAVAIL_SHIFT 16
+#define GNPTXSTS_NPTXQSPCAVAIL_MASK 0x00ff0000
+#define GNPTXSTS_NPTXFSPCAVAIL_SHIFT 0
+#define GNPTXSTS_NPTXFSPCAVAIL_MASK 0x0000ffff
+
+#define GI2CCTL_BSYDNE_SC (1<<31)
+#define GI2CCTL_RW (1<<30)
+#define GI2CCTL_I2CDATSE0 (1<<28)
+#define GI2CCTL_I2CDEVADR_SHIFT 26
+#define GI2CCTL_I2CDEVADR_MASK 0x0c000000
+#define GI2CCTL_I2CSUSPCTL (1<<25)
+#define GI2CCTL_ACK (1<<24)
+#define GI2CCTL_I2CEN (1<<23)
+#define GI2CCTL_ADDR_SHIFT 16
+#define GI2CCTL_ADDR_MASK 0x007f0000
+#define GI2CCTL_REGADDR_SHIFT 8
+#define GI2CCTL_REGADDR_MASK 0x0000ff00
+#define GI2CCTL_RWDATA_SHIFT 0
+#define GI2CCTL_RWDATA_MASK 0x000000ff
+
+#define GPVNDCTL_DISULPIDRVR (1<<31)
+#define GPVNDCTL_VSTSDONE (1<<27)
+#define GPVNDCTL_VSTSBSY (1<<26)
+#define GPVNDCTL_NEWREGREQ (1<<25)
+#define GPVNDCTL_REGWR (1<<22)
+#define GPVNDCTL_REGADDR_SHIFT 16
+#define GPVNDCTL_REGADDR_MASK 0x003f0000
+#define GPVNDCTL_VCTRL_SHIFT 8
+#define GPVNDCTL_VCTRL_MASK 0x0000ff00
+#define GPVNDCTL_REGDATA_SHIFT 0
+#define GPVNDCTL_REGDATA_MASK 0x000000ff
+
+#define GGPIO_GPO_SHIFT 16
+#define GGPIO_GPO_MASK 0xffff0000
+#define GGPIO_GPI_SHIFT 0
+#define GGPIO_GPI_MASK 0x0000ffff
+
+#define GHWCFG1_GET_DIR(x, n) (((x) >> (2 * (n))) & 3)
+#define GHWCFG1_BIDIR 0
+#define GHWCFG1_IN 1
+#define GHWCFG1_OUT 2
+
+#define GHWCFG2_TKNQDEPTH_SHIFT 26
+#define GHWCFG2_TKNQDEPTH_MASK 0x7c000000
+#define GHWCFG2_PTXQDEPTH_SHIFT 24
+#define GHWCFG2_PTXQDEPTH_MASK 0x03000000
+#define GHWCFG2_NPTXQDEPTH_SHIFT 22
+#define GHWCFG2_NPTXQDEPTH_MASK 0x00c00000
+#define GHWCFG2_MPI (1<<20)
+#define GHWCFG2_DYNFIFOSIZING (1<<19)
+#define GHWCFG2_PERIOSUPPORT (1<<18)
+#define GHWCFG2_NUMHSTCHNL_SHIFT 14
+#define GHWCFG2_NUMHSTCHNL_MASK 0x0003c000
+#define GHWCFG2_NUMHSTCHNL_GET(x) ((((x) >> 14) & 15) + 1)
+#define GHWCFG2_NUMDEVEPS_SHIFT 10
+#define GHWCFG2_NUMDEVEPS_MASK 0x00003c00
+#define GHWCFG2_NUMDEVEPS_GET(x) ((((x) >> 10) & 15) + 1)
+#define GHWCFG2_FSPHYTYPE_SHIFT 8
+#define GHWCFG2_FSPHYTYPE_MASK 0x00000300
+#define GHWCFG2_HSPHYTYPE_SHIFT 6
+#define GHWCFG2_HSPHYTYPE_MASK 0x000000c0
+#define GHWCFG2_SINGPNT (1<<5)
+#define GHWCFG2_OTGARCH_SHIFT 3
+#define GHWCFG2_OTGARCH_MASK 0x00000018
+#define GHWCFG2_OTGMODE_SHIFT 0
+#define GHWCFG2_OTGMODE_MASK 0x00000007
+
+#define GHWCFG3_DFIFODEPTH_SHIFT 16
+#define GHWCFG3_DFIFODEPTH_MASK 0xffff0000
+#define GHWCFG3_DFIFODEPTH_GET(x) ((x) >> 16)
+#define GHWCFG3_RSTTYPE (1<<11)
+#define GHWCFG3_OPTFEATURE (1<<10)
+#define GHWCFG3_VNDCTLSUPT (1<<9)
+#define GHWCFG3_I2CINTSEL (1<<8)
+#define GHWCFG3_OTGEN (1<<7)
+#define GHWCFG3_PKTSIZEWIDTH_SHIFT 4
+#define GHWCFG3_PKTSIZEWIDTH_MASK 0x00000070
+#define GHWCFG3_PKTSIZE_GET(x) (0x10<<(((x) >> 4) & 7))
+#define GHWCFG3_XFERSIZEWIDTH_SHIFT 0
+#define GHWCFG3_XFERSIZEWIDTH_MASK 0x0000000f
+#define GHWCFG3_XFRRSIZE_GET(x) (0x400<<(((x) >> 0) & 15))
+
+#define GHWCFG4_NUM_IN_EP_GET(x) ((((x) >> 26) & 15) + 1)
+#define GHWCFG4_SESSENDFLTR (1<<24)
+#define GHWCFG4_BVALIDFLTR (1<<23)
+#define GHWCFG4_AVALIDFLTR (1<<22)
+#define GHWCFG4_VBUSVALIDFLTR (1<<21)
+#define GHWCFG4_IDDGFLTR (1<<20)
+#define GHWCFG4_NUMCTLEPS_SHIFT 16
+#define GHWCFG4_NUMCTLEPS_MASK 0x000f0000
+#define GHWCFG4_NUMCTLEPS_GET(x) (((x) >> 16) & 15)
+#define GHWCFG4_PHYDATAWIDTH_SHIFT 14
+#define GHWCFG4_PHYDATAWIDTH_MASK 0x0000c000
+#define GHWCFG4_AHBFREQ (1<<5)
+#define GHWCFG4_ENABLEPWROPT (1<<4)
+#define GHWCFG4_NUMDEVPERIOEPS_SHIFT 0
+#define GHWCFG4_NUMDEVPERIOEPS_MASK 0x0000000f
+#define GHWCFG4_NUMDEVPERIOEPS_GET(x) (((x) >> 0) & 15)
+
+#define GLPMCFG_HSIC_CONN (1<<30)
+
+#define GPWRDN_BVALID (1<<22)
+#define GPWRDN_IDDIG (1<<21)
+#define GPWRDN_CONNDET_INT (1<<14)
+#define GPWRDN_CONNDET (1<<13)
+#define GPWRDN_DISCONN_INT (1<<12)
+#define GPWRDN_DISCONN (1<<11)
+#define GPWRDN_RESETDET_INT (1<<10)
+#define GPWRDN_RESETDET (1<<9)
+#define GPWRDN_LINESTATE_INT (1<<8)
+#define GPWRDN_LINESTATE (1<<7)
+#define GPWRDN_DISABLE_VBUS (1<<6)
+#define GPWRDN_POWER_DOWN (1<<5)
+#define GPWRDN_POWER_DOWN_RST (1<<4)
+#define GPWRDN_POWER_DOWN_CLAMP (1<<3)
+#define GPWRDN_RESTORE (1<<2)
+#define GPWRDN_PMU_ACTIVE (1<<1)
+#define GPWRDN_PMU_IRQ_SEL (1<<0)
+
+#define HPTXFSIZ_PTXFSIZE_SHIFT 16
+#define HPTXFSIZ_PTXFSIZE_MASK 0xffff0000
+#define HPTXFSIZ_PTXFSTADDR_SHIFT 0
+#define HPTXFSIZ_PTXFSTADDR_MASK 0x0000ffff
+
+#define DPTXFSIZN_DPTXFSIZE_SHIFT 16
+#define DPTXFSIZN_DPTXFSIZE_MASK 0xffff0000
+#define DPTXFSIZN_PTXFSTADDR_SHIFT 0
+#define DPTXFSIZN_PTXFSTADDR_MASK 0x0000ffff
+
+#define DIEPTXFN_INEPNTXFDEP_SHIFT 16
+#define DIEPTXFN_INEPNTXFDEP_MASK 0xffff0000
+#define DIEPTXFN_INEPNTXFSTADDR_SHIFT 0
+#define DIEPTXFN_INEPNTXFSTADDR_MASK 0x0000ffff
+
+#define HCFG_MODECHANGERDY (1<<31)
+#define HCFG_PERSCHEDENABLE (1<<26)
+#define HCFG_FLENTRIES_SHIFT 24
+#define HCFG_FLENTRIES_MASK 0x03000000
+#define HCFG_FLENTRIES_8 (0)
+#define HCFG_FLENTRIES_16 (1)
+#define HCFG_FLENTRIES_32 (2)
+#define HCFG_FLENTRIES_64 (3)
+#define HCFG_MULTISEGDMA (1<<23)
+#define HCFG_32KHZSUSPEND (1<<7)
+#define HCFG_FSLSSUPP (1<<2)
+#define HCFG_FSLSPCLKSEL_SHIFT 0
+#define HCFG_FSLSPCLKSEL_MASK 0x00000003
+
+#define HFIR_RELOADCTRL (1<<16)
+#define HFIR_FRINT_SHIFT 0
+#define HFIR_FRINT_MASK 0x0000ffff
+
+#define HFNUM_FRREM_SHIFT 16
+#define HFNUM_FRREM_MASK 0xffff0000
+#define HFNUM_FRNUM_SHIFT 0
+#define HFNUM_FRNUM_MASK 0x0000ffff
+
+#define HPTXSTS_ODD (1<<31)
+#define HPTXSTS_CHAN_SHIFT 27
+#define HPTXSTS_CHAN_MASK 0x78000000
+#define HPTXSTS_TOKEN_SHIFT 25
+#define HPTXSTS_TOKEN_MASK 0x06000000
+#define HPTXSTS_TOKEN_ZL 0
+#define HPTXSTS_TOKEN_PING 1
+#define HPTXSTS_TOKEN_DISABLE 2
+#define HPTXSTS_TERMINATE (1<<24)
+#define HPTXSTS_PTXQSPCAVAIL_SHIFT 16
+#define HPTXSTS_PTXQSPCAVAIL_MASK 0x00ff0000
+#define HPTXSTS_PTXFSPCAVAIL_SHIFT 0
+#define HPTXSTS_PTXFSPCAVAIL_MASK 0x0000ffff
+
+#define HAINT_HAINT_SHIFT 0
+#define HAINT_HAINT_MASK 0x0000ffff
+#define HAINTMSK_HAINTMSK_SHIFT 0
+#define HAINTMSK_HAINTMSK_MASK 0x0000ffff
+
+#define HPRT_PRTSPD_SHIFT 17
+#define HPRT_PRTSPD_MASK 0x00060000
+#define HPRT_PRTSPD_HIGH 0
+#define HPRT_PRTSPD_FULL 1
+#define HPRT_PRTSPD_LOW 2
+#define HPRT_PRTSPD_MASK 0x00060000
+#define HPRT_PRTTSTCTL_SHIFT 13
+#define HPRT_PRTTSTCTL_MASK 0x0001e000
+#define HPRT_PRTPWR (1<<12)
+#define HPRT_PRTLNSTS_SHIFT 10
+#define HPRT_PRTLNSTS_MASK 0x00000c00
+#define HPRT_PRTRST (1<<8)
+#define HPRT_PRTSUSP (1<<7)
+#define HPRT_PRTRES (1<<6)
+#define HPRT_PRTOVRCURRCHNG (1<<5)
+#define HPRT_PRTOVRCURRACT (1<<4)
+#define HPRT_PRTENCHNG (1<<3)
+#define HPRT_PRTENA (1<<2)
+#define HPRT_PRTCONNDET (1<<1)
+#define HPRT_PRTCONNSTS (1<<0)
+
+#define HCCHAR_CHENA (1<<31)
+#define HCCHAR_CHDIS (1<<30)
+#define HCCHAR_ODDFRM (1<<29)
+#define HCCHAR_DEVADDR_SHIFT 22
+#define HCCHAR_DEVADDR_MASK 0x1fc00000
+#define HCCHAR_MC_SHIFT 20
+#define HCCHAR_MC_MASK 0x00300000
+#define HCCHAR_EPTYPE_SHIFT 18
+#define HCCHAR_EPTYPE_MASK 0x000c0000
+#define HCCHAR_LSPDDEV (1<<17)
+#define HCCHAR_EPDIR (1<<15)
+#define HCCHAR_EPDIR_IN (1<<15)
+#define HCCHAR_EPDIR_OUT 0
+#define HCCHAR_EPNUM_SHIFT 11
+#define HCCHAR_EPNUM_MASK 0x00007800
+#define HCCHAR_MPS_SHIFT 0
+#define HCCHAR_MPS_MASK 0x000007ff
+
+#define HCSPLT_SPLTENA (1<<31)
+#define HCSPLT_COMPSPLT (1<<16)
+#define HCSPLT_XACTPOS_SHIFT 14
+#define HCSPLT_XACTPOS_MASK 0x0000c000
+#define HCSPLT_XACTPOS_MIDDLE 0
+#define HCSPLT_XACTPOS_LAST 1
+#define HCSPLT_XACTPOS_BEGIN 2
+#define HCSPLT_XACTPOS_ALL 3
+#define HCSPLT_XACTLEN_BURST 1023 /* bytes */
+#define HCSPLT_HUBADDR_SHIFT 7
+#define HCSPLT_HUBADDR_MASK 0x00003f80
+#define HCSPLT_PRTADDR_SHIFT 0
+#define HCSPLT_PRTADDR_MASK 0x0000007f
+
+#define HCINT_ERRORS \
+ (HCINT_BBLERR | HCINT_XACTERR)
+#define HCINT_RETRY \
+ (HCINT_DATATGLERR | HCINT_FRMOVRUN | HCINT_NAK)
+#define HCINT_DEFAULT_MASK \
+ (HCINT_STALL | HCINT_BBLERR | \
+ HCINT_XACTERR | HCINT_NAK | HCINT_ACK | HCINT_NYET | \
+ HCINT_CHHLTD | HCINT_FRMOVRUN | \
+ HCINT_DATATGLERR)
+#define HCINT_HCH_DONE_MASK \
+ (HCINT_ACK | HCINT_RETRY | HCINT_NYET | \
+ HCINT_ERRORS | HCINT_STALL | HCINT_SOFTWARE_ONLY)
+
+#define HCINT_SOFTWARE_ONLY (1<<20) /* BSD only */
+#define HCINT_DATATGLERR (1<<10)
+#define HCINT_FRMOVRUN (1<<9)
+#define HCINT_BBLERR (1<<8)
+#define HCINT_XACTERR (1<<7)
+#define HCINT_NYET (1<<6)
+#define HCINT_ACK (1<<5)
+#define HCINT_NAK (1<<4)
+#define HCINT_STALL (1<<3)
+#define HCINT_AHBERR (1<<2)
+#define HCINT_CHHLTD (1<<1)
+#define HCINT_XFERCOMPL (1<<0)
+
+#define HCINTMSK_DATATGLERRMSK (1<<10)
+#define HCINTMSK_FRMOVRUNMSK (1<<9)
+#define HCINTMSK_BBLERRMSK (1<<8)
+#define HCINTMSK_XACTERRMSK (1<<7)
+#define HCINTMSK_NYETMSK (1<<6)
+#define HCINTMSK_ACKMSK (1<<5)
+#define HCINTMSK_NAKMSK (1<<4)
+#define HCINTMSK_STALLMSK (1<<3)
+#define HCINTMSK_AHBERRMSK (1<<2)
+#define HCINTMSK_CHHLTDMSK (1<<1)
+#define HCINTMSK_XFERCOMPLMSK (1<<0)
+
+#define HCTSIZ_DOPNG (1<<31)
+#define HCTSIZ_PID_SHIFT 29
+#define HCTSIZ_PID_MASK 0x60000000
+#define HCTSIZ_PID_DATA0 0
+#define HCTSIZ_PID_DATA2 1
+#define HCTSIZ_PID_DATA1 2
+#define HCTSIZ_PID_MDATA 3
+#define HCTSIZ_PID_SETUP 3
+#define HCTSIZ_PKTCNT_SHIFT 19
+#define HCTSIZ_PKTCNT_MASK 0x1ff80000
+#define HCTSIZ_XFERSIZE_SHIFT 0
+#define HCTSIZ_XFERSIZE_MASK 0x0007ffff
+
+#define DCFG_EPMISCNT_SHIFT 18
+#define DCFG_EPMISCNT_MASK 0x007c0000
+#define DCFG_PERFRINT_SHIFT 11
+#define DCFG_PERFRINT_MASK 0x00001800
+#define DCFG_DEVADDR_SHIFT 4
+#define DCFG_DEVADDR_MASK 0x000007f0
+#define DCFG_DEVADDR_SET(x) (((x) & 0x7F) << 4)
+#define DCFG_NZSTSOUTHSHK (1<<2)
+#define DCFG_DEVSPD_SHIFT 0
+#define DCFG_DEVSPD_MASK 0x00000003
+#define DCFG_DEVSPD_SET(x) ((x) & 0x3)
+#define DCFG_DEVSPD_HI 0
+#define DCFG_DEVSPD_FULL20 1
+#define DCFG_DEVSPD_FULL10 3
+
+#define DCTL_PWRONPRGDONE (1<<11)
+#define DCTL_CGOUTNAK (1<<10)
+#define DCTL_SGOUTNAK (1<<9)
+#define DCTL_CGNPINNAK (1<<8)
+#define DCTL_SGNPINNAK (1<<7)
+#define DCTL_TSTCTL_SHIFT 4
+#define DCTL_TSTCTL_MASK 0x00000070
+#define DCTL_GOUTNAKSTS (1<<3)
+#define DCTL_GNPINNAKSTS (1<<2)
+#define DCTL_SFTDISCON (1<<1)
+#define DCTL_RMTWKUPSIG (1<<0)
+
+#define DSTS_SOFFN_SHIFT 8
+#define DSTS_SOFFN_MASK 0x003fff00
+#define DSTS_SOFFN_GET(x) (((x) >> 8) & 0x3FFF)
+#define DSTS_ERRTICERR (1<<3)
+#define DSTS_ENUMSPD_SHIFT 1
+#define DSTS_ENUMSPD_MASK 0x00000006
+#define DSTS_ENUMSPD_GET(x) (((x) >> 1) & 3)
+#define DSTS_ENUMSPD_HI 0
+#define DSTS_ENUMSPD_FULL20 1
+#define DSTS_ENUMSPD_LOW10 2
+#define DSTS_ENUMSPD_FULL10 3
+#define DSTS_SUSPSTS (1<<0)
+
+#define DIEPMSK_TXFIFOUNDRNMSK (1<<8)
+#define DIEPMSK_INEPNAKEFFMSK (1<<6)
+#define DIEPMSK_INTKNEPMISMSK (1<<5)
+#define DIEPMSK_INTKNTXFEMPMSK (1<<4)
+#define DIEPMSK_FIFOEMPTY (1<<4)
+#define DIEPMSK_TIMEOUTMSK (1<<3)
+#define DIEPMSK_AHBERRMSK (1<<2)
+#define DIEPMSK_EPDISBLDMSK (1<<1)
+#define DIEPMSK_XFERCOMPLMSK (1<<0)
+
+#define DOEPMSK_OUTPKTERRMSK (1<<8)
+#define DOEPMSK_BACK2BACKSETUP (1<<6)
+#define DOEPMSK_OUTTKNEPDISMSK (1<<4)
+#define DOEPMSK_FIFOEMPTY (1<<4)
+#define DOEPMSK_SETUPMSK (1<<3)
+#define DOEPMSK_AHBERRMSK (1<<2)
+#define DOEPMSK_EPDISBLDMSK (1<<1)
+#define DOEPMSK_XFERCOMPLMSK (1<<0)
+
+#define DIEPINT_TXFIFOUNDRN (1<<8)
+#define DIEPINT_INEPNAKEFF (1<<6)
+#define DIEPINT_INTKNEPMIS (1<<5)
+#define DIEPINT_INTKNTXFEMP (1<<4)
+#define DIEPINT_TIMEOUT (1<<3)
+#define DIEPINT_AHBERR (1<<2)
+#define DIEPINT_EPDISBLD (1<<1)
+#define DIEPINT_XFERCOMPL (1<<0)
+
+#define DOEPINT_OUTPKTERR (1<<8)
+#define DOEPINT_BACK2BACKSETUP (1<<6)
+#define DOEPINT_OUTTKNEPDIS (1<<4)
+#define DOEPINT_SETUP (1<<3)
+#define DOEPINT_AHBERR (1<<2)
+#define DOEPINT_EPDISBLD (1<<1)
+#define DOEPINT_XFERCOMPL (1<<0)
+
+#define DAINT_INEPINT_MASK 0xffff0000
+#define DAINT_INEPINT_SHIFT 0
+#define DAINT_OUTEPINT_MASK 0x0000ffff
+#define DAINT_OUTEPINT_SHIFT 16
+
+#define DAINTMSK_INEPINT_MASK 0xffff0000
+#define DAINTMSK_INEPINT_SHIFT 0
+#define DAINTMSK_OUTEPINT_MASK 0x0000ffff
+#define DAINTMSK_OUTEPINT_SHIFT 16
+
+#define DTKNQR1_EPTKN_SHIFT 8
+#define DTKNQR1_EPTKN_MASK 0xffffff00
+#define DTKNQR1_WRAPBIT (1<<7)
+#define DTKNQR1_INTKNWPTR_SHIFT 0
+#define DTKNQR1_INTKNWPTR_MASK 0x0000001f
+
+#define DVBUSDIS_DVBUSDIS_SHIFT 0
+#define DVBUSDIS_DVBUSDIS_MASK 0x0000ffff
+
+#define DVBUSPULSE_DVBUSPULSE_SHIFT 0
+#define DVBUSPULSE_DVBUSPULSE_MASK 0x00000fff
+
+#define DTHRCTL_ARBPRKEN (1<<27)
+#define DTHRCTL_RXTHRLEN_SHIFT 17
+#define DTHRCTL_RXTHRLEN_MASK 0x03fe0000
+#define DTHRCTL_RXTHREN (1<<16)
+#define DTHRCTL_TXTHRLEN_SHIFT 2
+#define DTHRCTL_TXTHRLEN_MASK 0x000007fc
+#define DTHRCTL_ISOTHREN (1<<1)
+#define DTHRCTL_NONISOTHREN (1<<0)
+
+#define DIEPEMPMSK_INEPTXFEMPMSK_SHIFT 0
+#define DIEPEMPMSK_INEPTXFEMPMSK_MASK 0x0000ffff
+
+#define DIEPCTL_EPENA (1<<31)
+#define DIEPCTL_EPDIS (1<<30)
+#define DIEPCTL_SETD1PID (1<<29)
+#define DIEPCTL_SETD0PID (1<<28)
+#define DIEPCTL_SNAK (1<<27)
+#define DIEPCTL_CNAK (1<<26)
+#define DIEPCTL_TXFNUM_SHIFT 22
+#define DIEPCTL_TXFNUM_MASK 0x03c00000
+#define DIEPCTL_TXFNUM_SET(n) (((n) & 15) << 22)
+#define DIEPCTL_STALL (1<<21)
+#define DIEPCTL_EPTYPE_SHIFT 18
+#define DIEPCTL_EPTYPE_MASK 0x000c0000
+#define DIEPCTL_EPTYPE_SET(n) (((n) & 3) << 18)
+#define DIEPCTL_EPTYPE_CONTROL 0
+#define DIEPCTL_EPTYPE_ISOC 1
+#define DIEPCTL_EPTYPE_BULK 2
+#define DIEPCTL_EPTYPE_INTERRUPT 3
+#define DIEPCTL_NAKSTS (1<<17)
+#define DIEPCTL_USBACTEP (1<<15)
+#define DIEPCTL_NEXTEP_SHIFT 11
+#define DIEPCTL_NEXTEP_MASK 0x00007800
+#define DIEPCTL_MPS_SHIFT 0
+#define DIEPCTL_MPS_MASK 0x000007ff
+#define DIEPCTL_MPS_SET(n) ((n) & 0x7FF)
+#define DIEPCTL_MPS_64 (0<<0)
+#define DIEPCTL_MPS_32 (1<<0)
+#define DIEPCTL_MPS_16 (2<<0)
+#define DIEPCTL_MPS_8 (3<<0)
+
+#define DOEPCTL_EPENA (1<<31)
+#define DOEPCTL_EPDIS (1<<30)
+#define DOEPCTL_SETD1PID (1<<29)
+#define DOEPCTL_SETD0PID (1<<28)
+#define DOEPCTL_SNAK (1<<27)
+#define DOEPCTL_CNAK (1<<26)
+#define DOEPCTL_FNUM_SET(n) (((n) & 15) << 22)
+#define DOEPCTL_STALL (1<<21)
+#define DOEPCTL_EPTYPE_SHIFT 18
+#define DOEPCTL_EPTYPE_MASK 0x000c0000
+#define DOEPCTL_EPTYPE_SET(n) (((n) & 3) << 18)
+#define DOEPCTL_NAKSTS (1<<17)
+#define DOEPCTL_USBACTEP (1<<15)
+#define DOEPCTL_MPS_SHIFT 0
+#define DOEPCTL_MPS_MASK 0x000007ff
+#define DOEPCTL_MPS_SET(n) ((n) & 0x7FF)
+#define DOEPCTL_MPS_64 (0<<0)
+#define DOEPCTL_MPS_32 (1<<0)
+#define DOEPCTL_MPS_16 (2<<0)
+#define DOEPCTL_MPS_8 (3<<0)
+
+/* common bits */
+#define DXEPINT_TXFEMP (1<<7)
+#define DXEPINT_SETUP (1<<3)
+#define DXEPINT_XFER_COMPL (1<<0)
+
+#define DIEPTSIZ_XFERSIZE_MASK 0x0007ffff
+#define DIEPTSIZ_XFERSIZE_SHIFT 0
+#define DIEPTSIZ_PKTCNT_MASK 0x1ff80000
+#define DIEPTSIZ_PKTCNT_SHIFT 19
+#define DIEPTSIZ_MC_MASK 0x60000000
+#define DIEPTSIZ_MC_SHIFT 29
+
+#define DOEPTSIZ_XFERSIZE_MASK 0x0007ffff
+#define DOEPTSIZ_XFERSIZE_SHIFT 0
+#define DOEPTSIZ_PKTCNT_MASK 0x1ff80000
+#define DOEPTSIZ_PKTCNT_SHIFT 19
+#define DOEPTSIZ_MC_MASK 0x60000000
+#define DOEPTSIZ_MC_SHIFT 29
+
+/* common bits */
+#define DXEPTSIZ_SET_MULTI(n) (((n) & 3) << 29)
+#define DXEPTSIZ_SET_NPKT(n) (((n) & 0x3FF) << 19)
+#define DXEPTSIZ_GET_NPKT(n) (((n) >> 19) & 0x3FF)
+#define DXEPTSIZ_SET_NBYTES(n) (((n) & 0x7FFFFF) << 0)
+#define DXEPTSIZ_GET_NBYTES(n) (((n) >> 0) & 0x7FFFFF)
+
+/* generic endpoint mask */
+
+#define ENDPOINT_MASK(x,in) \
+ ((in) ? (1U << ((x) & 15U)) : \
+ (0x10000U << ((x) & 15U)))
+
+#endif /* _DWC_OTGREG_H_ */
diff --git a/sys/dev/usb/controller/ehci.c b/sys/dev/usb/controller/ehci.c
new file mode 100644
index 000000000000..c75ebd1b2e46
--- /dev/null
+++ b/sys/dev/usb/controller/ehci.c
@@ -0,0 +1,3862 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 2004 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 2004 Lennart Augustsson. All rights reserved.
+ * Copyright (c) 2004 Charles M. Hannum. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * USB Enhanced Host Controller Driver, a.k.a. USB 2.0 controller.
+ *
+ * The EHCI 0.96 spec can be found at
+ * http://developer.intel.com/technology/usb/download/ehci-r096.pdf
+ * The EHCI 1.0 spec can be found at
+ * http://developer.intel.com/technology/usb/download/ehci-r10.pdf
+ * and the USB 2.0 spec at
+ * http://www.usb.org/developers/docs/usb_20.zip
+ *
+ */
+
+/*
+ * TODO:
+ * 1) command failures are not recovered correctly
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#define USB_DEBUG_VAR ehcidebug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#include <dev/usb/controller/ehci.h>
+#include <dev/usb/controller/ehcireg.h>
+
+#define EHCI_BUS2SC(bus) \
+ __containerof(bus, ehci_softc_t, sc_bus)
+
+#ifdef USB_DEBUG
+static int ehcidebug = 0;
+static int ehcinohighspeed = 0;
+static int ehciiaadbug = 0;
+static int ehcilostintrbug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ehci, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB ehci");
+SYSCTL_INT(_hw_usb_ehci, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &ehcidebug, 0, "Debug level");
+SYSCTL_INT(_hw_usb_ehci, OID_AUTO, no_hs, CTLFLAG_RWTUN,
+ &ehcinohighspeed, 0, "Disable High Speed USB");
+SYSCTL_INT(_hw_usb_ehci, OID_AUTO, iaadbug, CTLFLAG_RWTUN,
+ &ehciiaadbug, 0, "Enable doorbell bug workaround");
+SYSCTL_INT(_hw_usb_ehci, OID_AUTO, lostintrbug, CTLFLAG_RWTUN,
+ &ehcilostintrbug, 0, "Enable lost interrupt bug workaround");
+
+static void ehci_dump_regs(ehci_softc_t *sc);
+static void ehci_dump_sqh(ehci_softc_t *sc, ehci_qh_t *sqh);
+
+#endif
+
+#define EHCI_INTR_ENDPT 1
+
+static const struct usb_bus_methods ehci_bus_methods;
+static const struct usb_pipe_methods ehci_device_bulk_methods;
+static const struct usb_pipe_methods ehci_device_ctrl_methods;
+static const struct usb_pipe_methods ehci_device_intr_methods;
+static const struct usb_pipe_methods ehci_device_isoc_fs_methods;
+static const struct usb_pipe_methods ehci_device_isoc_hs_methods;
+
+static void ehci_do_poll(struct usb_bus *);
+static void ehci_device_done(struct usb_xfer *, usb_error_t);
+static uint8_t ehci_check_transfer(struct usb_xfer *);
+static void ehci_timeout(void *);
+static void ehci_poll_timeout(void *);
+
+static void ehci_root_intr(ehci_softc_t *sc);
+
+struct ehci_std_temp {
+ ehci_softc_t *sc;
+ struct usb_page_cache *pc;
+ ehci_qtd_t *td;
+ ehci_qtd_t *td_next;
+ uint32_t average;
+ uint32_t qtd_status;
+ uint32_t len;
+ uint16_t max_frame_size;
+ uint8_t shortpkt;
+ uint8_t auto_data_toggle;
+ uint8_t setup_alt_next;
+ uint8_t last_frame;
+};
+
+void
+ehci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(bus);
+ uint32_t i;
+
+ cb(bus, &sc->sc_hw.pframes_pc, &sc->sc_hw.pframes_pg,
+ sizeof(uint32_t) * EHCI_FRAMELIST_COUNT, EHCI_FRAMELIST_ALIGN);
+
+ cb(bus, &sc->sc_hw.terminate_pc, &sc->sc_hw.terminate_pg,
+ sizeof(struct ehci_qh_sub), EHCI_QH_ALIGN);
+
+ cb(bus, &sc->sc_hw.async_start_pc, &sc->sc_hw.async_start_pg,
+ sizeof(ehci_qh_t), EHCI_QH_ALIGN);
+
+ for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ cb(bus, sc->sc_hw.intr_start_pc + i,
+ sc->sc_hw.intr_start_pg + i,
+ sizeof(ehci_qh_t), EHCI_QH_ALIGN);
+ }
+
+ for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ cb(bus, sc->sc_hw.isoc_hs_start_pc + i,
+ sc->sc_hw.isoc_hs_start_pg + i,
+ sizeof(ehci_itd_t), EHCI_ITD_ALIGN);
+ }
+
+ for (i = 0; i != EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ cb(bus, sc->sc_hw.isoc_fs_start_pc + i,
+ sc->sc_hw.isoc_fs_start_pg + i,
+ sizeof(ehci_sitd_t), EHCI_SITD_ALIGN);
+ }
+}
+
+usb_error_t
+ehci_reset(ehci_softc_t *sc)
+{
+ uint32_t hcr;
+ int i;
+
+ EOWRITE4(sc, EHCI_USBCMD, EHCI_CMD_HCRESET);
+ for (i = 0; i < 100; i++) {
+ usb_pause_mtx(NULL, hz / 128);
+ hcr = EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_HCRESET;
+ if (!hcr) {
+ if (sc->sc_vendor_post_reset != NULL)
+ sc->sc_vendor_post_reset(sc);
+ return (0);
+ }
+ }
+ device_printf(sc->sc_bus.bdev, "reset timeout\n");
+ return (USB_ERR_IOERROR);
+}
+
+static usb_error_t
+ehci_hcreset(ehci_softc_t *sc)
+{
+ uint32_t hcr;
+ int i;
+
+ EOWRITE4(sc, EHCI_USBCMD, 0); /* Halt controller */
+ for (i = 0; i < 100; i++) {
+ usb_pause_mtx(NULL, hz / 128);
+ 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));
+}
+
+static int
+ehci_init_sub(struct ehci_softc *sc)
+{
+ struct usb_page_search buf_res;
+ uint32_t cparams;
+ uint32_t hcr;
+ uint8_t i;
+
+ cparams = EREAD4(sc, EHCI_HCCPARAMS);
+
+ DPRINTF("cparams=0x%x\n", cparams);
+
+ if (EHCI_HCC_64BIT(cparams)) {
+ DPRINTF("HCC uses 64-bit structures\n");
+
+ /* MUST clear segment register if 64 bit capable */
+ EOWRITE4(sc, EHCI_CTRLDSSEGMENT, 0);
+ }
+
+ usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res);
+ EOWRITE4(sc, EHCI_PERIODICLISTBASE, buf_res.physaddr);
+
+ usbd_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res);
+ EOWRITE4(sc, EHCI_ASYNCLISTADDR, buf_res.physaddr | EHCI_LINK_QH);
+
+ /* enable interrupts */
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ /* turn on controller */
+ EOWRITE4(sc, EHCI_USBCMD,
+ EHCI_CMD_ITC_1 | /* 1 microframes interrupt delay */
+ (EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_FLS_M) |
+ EHCI_CMD_ASE |
+ EHCI_CMD_PSE |
+ EHCI_CMD_RS);
+
+ /* Take over port ownership */
+ EOWRITE4(sc, EHCI_CONFIGFLAG, EHCI_CONF_CF);
+
+ for (i = 0; i < 100; i++) {
+ usb_pause_mtx(NULL, hz / 128);
+ hcr = EOREAD4(sc, EHCI_USBSTS) & EHCI_STS_HCH;
+ if (!hcr) {
+ break;
+ }
+ }
+ if (hcr) {
+ device_printf(sc->sc_bus.bdev, "run timeout\n");
+ return (USB_ERR_IOERROR);
+ }
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+usb_error_t
+ehci_init(ehci_softc_t *sc)
+{
+ struct usb_page_search buf_res;
+ uint32_t version;
+ uint32_t sparams;
+ uint16_t i;
+ uint16_t x;
+ uint16_t y;
+ uint16_t bit;
+ usb_error_t err = 0;
+
+ DPRINTF("start\n");
+
+ usb_callout_init_mtx(&sc->sc_tmo_pcd, &sc->sc_bus.bus_mtx, 0);
+ usb_callout_init_mtx(&sc->sc_tmo_poll, &sc->sc_bus.bus_mtx, 0);
+
+ sc->sc_offs = EHCI_CAPLENGTH(EREAD4(sc, EHCI_CAPLEN_HCIVERSION));
+
+#ifdef USB_DEBUG
+ if (ehciiaadbug)
+ sc->sc_flags |= EHCI_SCFLG_IAADBUG;
+ if (ehcilostintrbug)
+ sc->sc_flags |= EHCI_SCFLG_LOSTINTRBUG;
+ if (ehcidebug > 2) {
+ ehci_dump_regs(sc);
+ }
+#endif
+
+ version = EHCI_HCIVERSION(EREAD4(sc, EHCI_CAPLEN_HCIVERSION));
+ device_printf(sc->sc_bus.bdev, "EHCI version %x.%x\n",
+ version >> 8, version & 0xff);
+
+ sparams = EREAD4(sc, EHCI_HCSPARAMS);
+ DPRINTF("sparams=0x%x\n", sparams);
+
+ sc->sc_noport = EHCI_HCS_N_PORTS(sparams);
+ sc->sc_bus.usbrev = USB_REV_2_0;
+
+ if (!(sc->sc_flags & EHCI_SCFLG_DONTRESET)) {
+ /* Reset the controller */
+ DPRINTF("%s: resetting\n",
+ device_get_nameunit(sc->sc_bus.bdev));
+
+ err = ehci_hcreset(sc);
+ if (err) {
+ device_printf(sc->sc_bus.bdev, "reset timeout\n");
+ return (err);
+ }
+ }
+
+ /*
+ * use current frame-list-size selection 0: 1024*4 bytes 1: 512*4
+ * bytes 2: 256*4 bytes 3: unknown
+ */
+ if (EHCI_CMD_FLS(EOREAD4(sc, EHCI_USBCMD)) == 3) {
+ device_printf(sc->sc_bus.bdev, "invalid frame-list-size\n");
+ return (USB_ERR_IOERROR);
+ }
+ /* set up the bus struct */
+ sc->sc_bus.methods = &ehci_bus_methods;
+
+ sc->sc_eintrs = EHCI_NORMAL_INTRS;
+
+ if (1) {
+ struct ehci_qh_sub *qh;
+
+ usbd_get_page(&sc->sc_hw.terminate_pc, 0, &buf_res);
+
+ qh = buf_res.buffer;
+
+ sc->sc_terminate_self = htohc32(sc, buf_res.physaddr);
+
+ /* init terminate TD */
+ qh->qtd_next =
+ htohc32(sc, EHCI_LINK_TERMINATE);
+ qh->qtd_altnext =
+ htohc32(sc, EHCI_LINK_TERMINATE);
+ qh->qtd_status =
+ htohc32(sc, EHCI_QTD_HALTED);
+ }
+
+ for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ ehci_qh_t *qh;
+
+ usbd_get_page(sc->sc_hw.intr_start_pc + i, 0, &buf_res);
+
+ qh = buf_res.buffer;
+
+ /* initialize page cache pointer */
+
+ qh->page_cache = sc->sc_hw.intr_start_pc + i;
+
+ /* store a pointer to queue head */
+
+ sc->sc_intr_p_last[i] = qh;
+
+ qh->qh_self =
+ htohc32(sc, buf_res.physaddr) |
+ htohc32(sc, EHCI_LINK_QH);
+
+ qh->qh_endp =
+ htohc32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH));
+ qh->qh_endphub =
+ htohc32(sc, EHCI_QH_SET_MULT(1));
+ qh->qh_curqtd = 0;
+
+ qh->qh_qtd.qtd_next =
+ htohc32(sc, EHCI_LINK_TERMINATE);
+ qh->qh_qtd.qtd_altnext =
+ htohc32(sc, EHCI_LINK_TERMINATE);
+ qh->qh_qtd.qtd_status =
+ htohc32(sc, EHCI_QTD_HALTED);
+ }
+
+ /*
+ * the QHs are arranged to give poll intervals that are
+ * powers of 2 times 1ms
+ */
+ bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 2;
+ while (bit) {
+ x = bit;
+ while (x & bit) {
+ ehci_qh_t *qh_x;
+ ehci_qh_t *qh_y;
+
+ y = (x ^ bit) | (bit / 2);
+
+ qh_x = sc->sc_intr_p_last[x];
+ qh_y = sc->sc_intr_p_last[y];
+
+ /*
+ * the next QH has half the poll interval
+ */
+ qh_x->qh_link = qh_y->qh_self;
+
+ x++;
+ }
+ bit >>= 1;
+ }
+
+ if (1) {
+ ehci_qh_t *qh;
+
+ qh = sc->sc_intr_p_last[0];
+
+ /* the last (1ms) QH terminates */
+ qh->qh_link = htohc32(sc, EHCI_LINK_TERMINATE);
+ }
+ for (i = 0; i < EHCI_VIRTUAL_FRAMELIST_COUNT; i++) {
+ ehci_sitd_t *sitd;
+ ehci_itd_t *itd;
+
+ usbd_get_page(sc->sc_hw.isoc_fs_start_pc + i, 0, &buf_res);
+
+ sitd = buf_res.buffer;
+
+ /* initialize page cache pointer */
+
+ sitd->page_cache = sc->sc_hw.isoc_fs_start_pc + i;
+
+ /* store a pointer to the transfer descriptor */
+
+ sc->sc_isoc_fs_p_last[i] = sitd;
+
+ /* initialize full speed isochronous */
+
+ sitd->sitd_self =
+ htohc32(sc, buf_res.physaddr) |
+ htohc32(sc, EHCI_LINK_SITD);
+
+ sitd->sitd_back =
+ htohc32(sc, EHCI_LINK_TERMINATE);
+
+ sitd->sitd_next =
+ sc->sc_intr_p_last[i | (EHCI_VIRTUAL_FRAMELIST_COUNT / 2)]->qh_self;
+
+ usbd_get_page(sc->sc_hw.isoc_hs_start_pc + i, 0, &buf_res);
+
+ itd = buf_res.buffer;
+
+ /* initialize page cache pointer */
+
+ itd->page_cache = sc->sc_hw.isoc_hs_start_pc + i;
+
+ /* store a pointer to the transfer descriptor */
+
+ sc->sc_isoc_hs_p_last[i] = itd;
+
+ /* initialize high speed isochronous */
+
+ itd->itd_self =
+ htohc32(sc, buf_res.physaddr) |
+ htohc32(sc, EHCI_LINK_ITD);
+
+ itd->itd_next =
+ sitd->sitd_self;
+ }
+
+ usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res);
+
+ if (1) {
+ uint32_t *pframes;
+
+ pframes = buf_res.buffer;
+
+ /*
+ * execution order:
+ * pframes -> high speed isochronous ->
+ * full speed isochronous -> interrupt QH's
+ */
+ for (i = 0; i < EHCI_FRAMELIST_COUNT; i++) {
+ pframes[i] = sc->sc_isoc_hs_p_last
+ [i & (EHCI_VIRTUAL_FRAMELIST_COUNT - 1)]->itd_self;
+ }
+ }
+ usbd_get_page(&sc->sc_hw.async_start_pc, 0, &buf_res);
+
+ if (1) {
+ ehci_qh_t *qh;
+
+ qh = buf_res.buffer;
+
+ /* initialize page cache pointer */
+
+ qh->page_cache = &sc->sc_hw.async_start_pc;
+
+ /* store a pointer to the queue head */
+
+ sc->sc_async_p_last = qh;
+
+ /* init dummy QH that starts the async list */
+
+ qh->qh_self =
+ htohc32(sc, buf_res.physaddr) |
+ htohc32(sc, EHCI_LINK_QH);
+
+ /* fill the QH */
+ qh->qh_endp =
+ htohc32(sc, EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH) | EHCI_QH_HRECL);
+ qh->qh_endphub = htohc32(sc, EHCI_QH_SET_MULT(1));
+ qh->qh_link = qh->qh_self;
+ qh->qh_curqtd = 0;
+
+ /* fill the overlay qTD */
+ qh->qh_qtd.qtd_next = htohc32(sc, EHCI_LINK_TERMINATE);
+ qh->qh_qtd.qtd_altnext = htohc32(sc, EHCI_LINK_TERMINATE);
+ qh->qh_qtd.qtd_status = htohc32(sc, EHCI_QTD_HALTED);
+ }
+ /* flush all cache into memory */
+
+ usb_bus_mem_flush_all(&sc->sc_bus, &ehci_iterate_hw_softc);
+
+#ifdef USB_DEBUG
+ if (ehcidebug) {
+ ehci_dump_sqh(sc, sc->sc_async_p_last);
+ }
+#endif
+
+ /* finial setup */
+ err = ehci_init_sub(sc);
+
+ if (!err) {
+ /* catch any lost interrupts */
+ ehci_do_poll(&sc->sc_bus);
+ }
+ return (err);
+}
+
+/*
+ * shut down the controller when the system is going down
+ */
+void
+ehci_detach(ehci_softc_t *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ usb_callout_stop(&sc->sc_tmo_pcd);
+ usb_callout_stop(&sc->sc_tmo_poll);
+
+ EOWRITE4(sc, EHCI_USBINTR, 0);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ if (ehci_hcreset(sc)) {
+ DPRINTF("reset failed!\n");
+ }
+
+ /* XXX let stray task complete */
+ usb_pause_mtx(NULL, hz / 20);
+
+ usb_callout_drain(&sc->sc_tmo_pcd);
+ usb_callout_drain(&sc->sc_tmo_poll);
+}
+
+static void
+ehci_suspend(ehci_softc_t *sc)
+{
+ DPRINTF("stopping the HC\n");
+
+ /* reset HC */
+ ehci_hcreset(sc);
+}
+
+static void
+ehci_resume(ehci_softc_t *sc)
+{
+ /* reset HC */
+ ehci_hcreset(sc);
+
+ /* setup HC */
+ ehci_init_sub(sc);
+
+ /* catch any lost interrupts */
+ ehci_do_poll(&sc->sc_bus);
+}
+
+#ifdef USB_DEBUG
+static void
+ehci_dump_regs(ehci_softc_t *sc)
+{
+ uint32_t i;
+
+ i = EOREAD4(sc, EHCI_USBCMD);
+ printf("cmd=0x%08x\n", i);
+
+ if (i & EHCI_CMD_ITC_1)
+ printf(" EHCI_CMD_ITC_1\n");
+ if (i & EHCI_CMD_ITC_2)
+ printf(" EHCI_CMD_ITC_2\n");
+ if (i & EHCI_CMD_ITC_4)
+ printf(" EHCI_CMD_ITC_4\n");
+ if (i & EHCI_CMD_ITC_8)
+ printf(" EHCI_CMD_ITC_8\n");
+ if (i & EHCI_CMD_ITC_16)
+ printf(" EHCI_CMD_ITC_16\n");
+ if (i & EHCI_CMD_ITC_32)
+ printf(" EHCI_CMD_ITC_32\n");
+ if (i & EHCI_CMD_ITC_64)
+ printf(" EHCI_CMD_ITC_64\n");
+ if (i & EHCI_CMD_ASPME)
+ printf(" EHCI_CMD_ASPME\n");
+ if (i & EHCI_CMD_ASPMC)
+ printf(" EHCI_CMD_ASPMC\n");
+ if (i & EHCI_CMD_LHCR)
+ printf(" EHCI_CMD_LHCR\n");
+ if (i & EHCI_CMD_IAAD)
+ printf(" EHCI_CMD_IAAD\n");
+ if (i & EHCI_CMD_ASE)
+ printf(" EHCI_CMD_ASE\n");
+ if (i & EHCI_CMD_PSE)
+ printf(" EHCI_CMD_PSE\n");
+ if (i & EHCI_CMD_FLS_M)
+ printf(" EHCI_CMD_FLS_M\n");
+ if (i & EHCI_CMD_HCRESET)
+ printf(" EHCI_CMD_HCRESET\n");
+ if (i & EHCI_CMD_RS)
+ printf(" EHCI_CMD_RS\n");
+
+ i = EOREAD4(sc, EHCI_USBSTS);
+
+ printf("sts=0x%08x\n", i);
+
+ if (i & EHCI_STS_ASS)
+ printf(" EHCI_STS_ASS\n");
+ if (i & EHCI_STS_PSS)
+ printf(" EHCI_STS_PSS\n");
+ if (i & EHCI_STS_REC)
+ printf(" EHCI_STS_REC\n");
+ if (i & EHCI_STS_HCH)
+ printf(" EHCI_STS_HCH\n");
+ if (i & EHCI_STS_IAA)
+ printf(" EHCI_STS_IAA\n");
+ if (i & EHCI_STS_HSE)
+ printf(" EHCI_STS_HSE\n");
+ if (i & EHCI_STS_FLR)
+ printf(" EHCI_STS_FLR\n");
+ if (i & EHCI_STS_PCD)
+ printf(" EHCI_STS_PCD\n");
+ if (i & EHCI_STS_ERRINT)
+ printf(" EHCI_STS_ERRINT\n");
+ if (i & EHCI_STS_INT)
+ printf(" EHCI_STS_INT\n");
+
+ printf("ien=0x%08x\n",
+ EOREAD4(sc, EHCI_USBINTR));
+ printf("frindex=0x%08x ctrdsegm=0x%08x periodic=0x%08x async=0x%08x\n",
+ EOREAD4(sc, EHCI_FRINDEX),
+ EOREAD4(sc, EHCI_CTRLDSSEGMENT),
+ EOREAD4(sc, EHCI_PERIODICLISTBASE),
+ EOREAD4(sc, EHCI_ASYNCLISTADDR));
+ for (i = 1; i <= sc->sc_noport; i++) {
+ printf("port %d status=0x%08x\n", i,
+ EOREAD4(sc, EHCI_PORTSC(i)));
+ }
+}
+
+static void
+ehci_dump_link(ehci_softc_t *sc, uint32_t link, int type)
+{
+ link = hc32toh(sc, link);
+ printf("0x%08x", link);
+ if (link & EHCI_LINK_TERMINATE)
+ printf("<T>");
+ else {
+ printf("<");
+ if (type) {
+ switch (EHCI_LINK_TYPE(link)) {
+ case EHCI_LINK_ITD:
+ printf("ITD");
+ break;
+ case EHCI_LINK_QH:
+ printf("QH");
+ break;
+ case EHCI_LINK_SITD:
+ printf("SITD");
+ break;
+ case EHCI_LINK_FSTN:
+ printf("FSTN");
+ break;
+ }
+ }
+ printf(">");
+ }
+}
+
+static void
+ehci_dump_qtd(ehci_softc_t *sc, ehci_qtd_t *qtd)
+{
+ uint32_t s;
+
+ printf(" next=");
+ ehci_dump_link(sc, qtd->qtd_next, 0);
+ printf(" altnext=");
+ ehci_dump_link(sc, qtd->qtd_altnext, 0);
+ printf("\n");
+ s = hc32toh(sc, qtd->qtd_status);
+ printf(" status=0x%08x: toggle=%d bytes=0x%x ioc=%d c_page=0x%x\n",
+ s, EHCI_QTD_GET_TOGGLE(s), EHCI_QTD_GET_BYTES(s),
+ EHCI_QTD_GET_IOC(s), EHCI_QTD_GET_C_PAGE(s));
+ printf(" cerr=%d pid=%d stat=%s%s%s%s%s%s%s%s\n",
+ EHCI_QTD_GET_CERR(s), EHCI_QTD_GET_PID(s),
+ (s & EHCI_QTD_ACTIVE) ? "ACTIVE" : "NOT_ACTIVE",
+ (s & EHCI_QTD_HALTED) ? "-HALTED" : "",
+ (s & EHCI_QTD_BUFERR) ? "-BUFERR" : "",
+ (s & EHCI_QTD_BABBLE) ? "-BABBLE" : "",
+ (s & EHCI_QTD_XACTERR) ? "-XACTERR" : "",
+ (s & EHCI_QTD_MISSEDMICRO) ? "-MISSED" : "",
+ (s & EHCI_QTD_SPLITXSTATE) ? "-SPLIT" : "",
+ (s & EHCI_QTD_PINGSTATE) ? "-PING" : "");
+
+ for (s = 0; s < 5; s++) {
+ printf(" buffer[%d]=0x%08x\n", s,
+ hc32toh(sc, qtd->qtd_buffer[s]));
+ }
+ for (s = 0; s < 5; s++) {
+ printf(" buffer_hi[%d]=0x%08x\n", s,
+ hc32toh(sc, qtd->qtd_buffer_hi[s]));
+ }
+}
+
+static uint8_t
+ehci_dump_sqtd(ehci_softc_t *sc, ehci_qtd_t *sqtd)
+{
+ uint8_t temp;
+
+ usb_pc_cpu_invalidate(sqtd->page_cache);
+ printf("QTD(%p) at 0x%08x:\n", sqtd, hc32toh(sc, sqtd->qtd_self));
+ ehci_dump_qtd(sc, sqtd);
+ temp = (sqtd->qtd_next & htohc32(sc, EHCI_LINK_TERMINATE)) ? 1 : 0;
+ return (temp);
+}
+
+static void
+ehci_dump_sqtds(ehci_softc_t *sc, ehci_qtd_t *sqtd)
+{
+ uint16_t i;
+ uint8_t stop;
+
+ stop = 0;
+ for (i = 0; sqtd && (i < 20) && !stop; sqtd = sqtd->obj_next, i++) {
+ stop = ehci_dump_sqtd(sc, sqtd);
+ }
+ if (sqtd) {
+ printf("dump aborted, too many TDs\n");
+ }
+}
+
+static void
+ehci_dump_sqh(ehci_softc_t *sc, ehci_qh_t *qh)
+{
+ uint32_t endp;
+ uint32_t endphub;
+
+ usb_pc_cpu_invalidate(qh->page_cache);
+ printf("QH(%p) at 0x%08x:\n", qh, hc32toh(sc, qh->qh_self) & ~0x1F);
+ printf(" link=");
+ ehci_dump_link(sc, qh->qh_link, 1);
+ printf("\n");
+ endp = hc32toh(sc, qh->qh_endp);
+ printf(" endp=0x%08x\n", endp);
+ printf(" addr=0x%02x inact=%d endpt=%d eps=%d dtc=%d hrecl=%d\n",
+ EHCI_QH_GET_ADDR(endp), EHCI_QH_GET_INACT(endp),
+ EHCI_QH_GET_ENDPT(endp), EHCI_QH_GET_EPS(endp),
+ EHCI_QH_GET_DTC(endp), EHCI_QH_GET_HRECL(endp));
+ printf(" mpl=0x%x ctl=%d nrl=%d\n",
+ EHCI_QH_GET_MPL(endp), EHCI_QH_GET_CTL(endp),
+ EHCI_QH_GET_NRL(endp));
+ endphub = hc32toh(sc, qh->qh_endphub);
+ printf(" endphub=0x%08x\n", endphub);
+ printf(" smask=0x%02x cmask=0x%02x huba=0x%02x port=%d mult=%d\n",
+ EHCI_QH_GET_SMASK(endphub), EHCI_QH_GET_CMASK(endphub),
+ EHCI_QH_GET_HUBA(endphub), EHCI_QH_GET_PORT(endphub),
+ EHCI_QH_GET_MULT(endphub));
+ printf(" curqtd=");
+ ehci_dump_link(sc, qh->qh_curqtd, 0);
+ printf("\n");
+ printf("Overlay qTD:\n");
+ ehci_dump_qtd(sc, (void *)&qh->qh_qtd);
+}
+
+static void
+ehci_dump_sitd(ehci_softc_t *sc, ehci_sitd_t *sitd)
+{
+ usb_pc_cpu_invalidate(sitd->page_cache);
+ printf("SITD(%p) at 0x%08x\n", sitd, hc32toh(sc, sitd->sitd_self) & ~0x1F);
+ printf(" next=0x%08x\n", hc32toh(sc, sitd->sitd_next));
+ printf(" portaddr=0x%08x dir=%s addr=%d endpt=0x%x port=0x%x huba=0x%x\n",
+ hc32toh(sc, sitd->sitd_portaddr),
+ (sitd->sitd_portaddr & htohc32(sc, EHCI_SITD_SET_DIR_IN))
+ ? "in" : "out",
+ EHCI_SITD_GET_ADDR(hc32toh(sc, sitd->sitd_portaddr)),
+ EHCI_SITD_GET_ENDPT(hc32toh(sc, sitd->sitd_portaddr)),
+ EHCI_SITD_GET_PORT(hc32toh(sc, sitd->sitd_portaddr)),
+ EHCI_SITD_GET_HUBA(hc32toh(sc, sitd->sitd_portaddr)));
+ printf(" mask=0x%08x\n", hc32toh(sc, sitd->sitd_mask));
+ printf(" status=0x%08x <%s> len=0x%x\n", hc32toh(sc, sitd->sitd_status),
+ (sitd->sitd_status & htohc32(sc, EHCI_SITD_ACTIVE)) ? "ACTIVE" : "",
+ EHCI_SITD_GET_LEN(hc32toh(sc, sitd->sitd_status)));
+ printf(" back=0x%08x, bp=0x%08x,0x%08x,0x%08x,0x%08x\n",
+ hc32toh(sc, sitd->sitd_back),
+ hc32toh(sc, sitd->sitd_bp[0]),
+ hc32toh(sc, sitd->sitd_bp[1]),
+ hc32toh(sc, sitd->sitd_bp_hi[0]),
+ hc32toh(sc, sitd->sitd_bp_hi[1]));
+}
+
+static void
+ehci_dump_itd(ehci_softc_t *sc, ehci_itd_t *itd)
+{
+ usb_pc_cpu_invalidate(itd->page_cache);
+ printf("ITD(%p) at 0x%08x\n", itd, hc32toh(sc, itd->itd_self) & ~0x1F);
+ printf(" next=0x%08x\n", hc32toh(sc, itd->itd_next));
+ printf(" status[0]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[0]),
+ (itd->itd_status[0] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[1]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[1]),
+ (itd->itd_status[1] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[2]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[2]),
+ (itd->itd_status[2] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[3]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[3]),
+ (itd->itd_status[3] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[4]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[4]),
+ (itd->itd_status[4] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[5]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[5]),
+ (itd->itd_status[5] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[6]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[6]),
+ (itd->itd_status[6] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" status[7]=0x%08x; <%s>\n", hc32toh(sc, itd->itd_status[7]),
+ (itd->itd_status[7] & htohc32(sc, EHCI_ITD_ACTIVE)) ? "ACTIVE" : "");
+ printf(" bp[0]=0x%08x\n", hc32toh(sc, itd->itd_bp[0]));
+ printf(" addr=0x%02x; endpt=0x%01x\n",
+ EHCI_ITD_GET_ADDR(hc32toh(sc, itd->itd_bp[0])),
+ EHCI_ITD_GET_ENDPT(hc32toh(sc, itd->itd_bp[0])));
+ printf(" bp[1]=0x%08x\n", hc32toh(sc, itd->itd_bp[1]));
+ printf(" dir=%s; mpl=0x%02x\n",
+ (hc32toh(sc, itd->itd_bp[1]) & EHCI_ITD_SET_DIR_IN) ? "in" : "out",
+ EHCI_ITD_GET_MPL(hc32toh(sc, itd->itd_bp[1])));
+ printf(" bp[2..6]=0x%08x,0x%08x,0x%08x,0x%08x,0x%08x\n",
+ hc32toh(sc, itd->itd_bp[2]),
+ hc32toh(sc, itd->itd_bp[3]),
+ hc32toh(sc, itd->itd_bp[4]),
+ hc32toh(sc, itd->itd_bp[5]),
+ hc32toh(sc, itd->itd_bp[6]));
+ printf(" bp_hi=0x%08x,0x%08x,0x%08x,0x%08x,\n"
+ " 0x%08x,0x%08x,0x%08x\n",
+ hc32toh(sc, itd->itd_bp_hi[0]),
+ hc32toh(sc, itd->itd_bp_hi[1]),
+ hc32toh(sc, itd->itd_bp_hi[2]),
+ hc32toh(sc, itd->itd_bp_hi[3]),
+ hc32toh(sc, itd->itd_bp_hi[4]),
+ hc32toh(sc, itd->itd_bp_hi[5]),
+ hc32toh(sc, itd->itd_bp_hi[6]));
+}
+
+static void
+ehci_dump_isoc(ehci_softc_t *sc)
+{
+ ehci_itd_t *itd;
+ ehci_sitd_t *sitd;
+ uint16_t max = 1000;
+ uint16_t pos;
+
+ pos = (EOREAD4(sc, EHCI_FRINDEX) / 8) &
+ (EHCI_VIRTUAL_FRAMELIST_COUNT - 1);
+
+ printf("%s: isochronous dump from frame 0x%03x:\n",
+ __FUNCTION__, pos);
+
+ itd = sc->sc_isoc_hs_p_last[pos];
+ sitd = sc->sc_isoc_fs_p_last[pos];
+
+ while (itd && max && max--) {
+ ehci_dump_itd(sc, itd);
+ itd = itd->prev;
+ }
+
+ while (sitd && max && max--) {
+ ehci_dump_sitd(sc, sitd);
+ sitd = sitd->prev;
+ }
+}
+
+#endif
+
+static void
+ehci_transfer_intr_enqueue(struct usb_xfer *xfer)
+{
+ /* check for early completion */
+ if (ehci_check_transfer(xfer)) {
+ return;
+ }
+ /* put transfer on interrupt queue */
+ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usbd_transfer_timeout_ms(xfer, &ehci_timeout, xfer->timeout);
+ }
+}
+
+#define EHCI_APPEND_FS_TD(std,last) (last) = _ehci_append_fs_td(std,last)
+static ehci_sitd_t *
+_ehci_append_fs_td(ehci_sitd_t *std, ehci_sitd_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->next = last->next;
+ std->sitd_next = last->sitd_next;
+
+ std->prev = last;
+
+ usb_pc_cpu_flush(std->page_cache);
+
+ /*
+ * the last->next->prev is never followed: std->next->prev = std;
+ */
+ last->next = std;
+ last->sitd_next = std->sitd_self;
+
+ usb_pc_cpu_flush(last->page_cache);
+
+ return (std);
+}
+
+#define EHCI_APPEND_HS_TD(std,last) (last) = _ehci_append_hs_td(std,last)
+static ehci_itd_t *
+_ehci_append_hs_td(ehci_itd_t *std, ehci_itd_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->next = last->next;
+ std->itd_next = last->itd_next;
+
+ std->prev = last;
+
+ usb_pc_cpu_flush(std->page_cache);
+
+ /*
+ * the last->next->prev is never followed: std->next->prev = std;
+ */
+ last->next = std;
+ last->itd_next = std->itd_self;
+
+ usb_pc_cpu_flush(last->page_cache);
+
+ return (std);
+}
+
+#define EHCI_APPEND_QH(sqh,last) (last) = _ehci_append_qh(sqh,last)
+static ehci_qh_t *
+_ehci_append_qh(ehci_qh_t *sqh, ehci_qh_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", sqh, last);
+
+ if (sqh->prev != NULL) {
+ /* should not happen */
+ DPRINTFN(0, "QH already linked!\n");
+ return (last);
+ }
+ /* (sc->sc_bus.mtx) must be locked */
+
+ sqh->next = last->next;
+ sqh->qh_link = last->qh_link;
+
+ sqh->prev = last;
+
+ usb_pc_cpu_flush(sqh->page_cache);
+
+ /*
+ * the last->next->prev is never followed: sqh->next->prev = sqh;
+ */
+
+ last->next = sqh;
+ last->qh_link = sqh->qh_self;
+
+ usb_pc_cpu_flush(last->page_cache);
+
+ return (sqh);
+}
+
+#define EHCI_REMOVE_FS_TD(std,last) (last) = _ehci_remove_fs_td(std,last)
+static ehci_sitd_t *
+_ehci_remove_fs_td(ehci_sitd_t *std, ehci_sitd_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->prev->next = std->next;
+ std->prev->sitd_next = std->sitd_next;
+
+ usb_pc_cpu_flush(std->prev->page_cache);
+
+ if (std->next) {
+ std->next->prev = std->prev;
+ usb_pc_cpu_flush(std->next->page_cache);
+ }
+ return ((last == std) ? std->prev : last);
+}
+
+#define EHCI_REMOVE_HS_TD(std,last) (last) = _ehci_remove_hs_td(std,last)
+static ehci_itd_t *
+_ehci_remove_hs_td(ehci_itd_t *std, ehci_itd_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->prev->next = std->next;
+ std->prev->itd_next = std->itd_next;
+
+ usb_pc_cpu_flush(std->prev->page_cache);
+
+ if (std->next) {
+ std->next->prev = std->prev;
+ usb_pc_cpu_flush(std->next->page_cache);
+ }
+ return ((last == std) ? std->prev : last);
+}
+
+#define EHCI_REMOVE_QH(sqh,last) (last) = _ehci_remove_qh(sqh,last)
+static ehci_qh_t *
+_ehci_remove_qh(ehci_qh_t *sqh, ehci_qh_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", sqh, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ /* only remove if not removed from a queue */
+ if (sqh->prev) {
+ sqh->prev->next = sqh->next;
+ sqh->prev->qh_link = sqh->qh_link;
+
+ usb_pc_cpu_flush(sqh->prev->page_cache);
+
+ if (sqh->next) {
+ sqh->next->prev = sqh->prev;
+ usb_pc_cpu_flush(sqh->next->page_cache);
+ }
+ last = ((last == sqh) ? sqh->prev : last);
+
+ sqh->prev = 0;
+
+ usb_pc_cpu_flush(sqh->page_cache);
+ }
+ return (last);
+}
+
+static void
+ehci_data_toggle_update(struct usb_xfer *xfer, uint16_t actlen, uint16_t xlen)
+{
+ uint16_t rem;
+ uint8_t dt;
+
+ /* count number of full packets */
+ dt = (actlen / xfer->max_packet_size) & 1;
+
+ /* compute remainder */
+ rem = actlen % xfer->max_packet_size;
+
+ if (rem > 0)
+ dt ^= 1; /* short packet at the end */
+ else if (actlen != xlen)
+ dt ^= 1; /* zero length packet at the end */
+ else if (xlen == 0)
+ dt ^= 1; /* zero length transfer */
+
+ xfer->endpoint->toggle_next ^= dt;
+}
+
+static usb_error_t
+ehci_non_isoc_done_sub(struct usb_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ ehci_qtd_t *td;
+ ehci_qtd_t *td_alt_next;
+ uint32_t status;
+ uint16_t len;
+
+ td = xfer->td_transfer_cache;
+ td_alt_next = td->alt_next;
+
+ if (xfer->aframes != xfer->nframes) {
+ usbd_xfer_set_frame_len(xfer, xfer->aframes, 0);
+ }
+ while (1) {
+ usb_pc_cpu_invalidate(td->page_cache);
+ status = hc32toh(sc, td->qtd_status);
+
+ len = EHCI_QTD_GET_BYTES(status);
+
+ /*
+ * Verify the status length and
+ * add the length to "frlengths[]":
+ */
+ if (len > td->len) {
+ /* should not happen */
+ DPRINTF("Invalid status length, "
+ "0x%04x/0x%04x bytes\n", len, td->len);
+ status |= EHCI_QTD_HALTED;
+ } else if (xfer->aframes != xfer->nframes) {
+ xfer->frlengths[xfer->aframes] += td->len - len;
+ /* manually update data toggle */
+ ehci_data_toggle_update(xfer, td->len - len, td->len);
+ }
+
+ /* Check for last transfer */
+ if (((void *)td) == xfer->td_transfer_last) {
+ td = NULL;
+ break;
+ }
+ /* Check for transfer error */
+ if (status & EHCI_QTD_HALTED) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len > 0) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ td = td->alt_next;
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ break;
+ }
+ td = td->obj_next;
+
+ if (td->alt_next != td_alt_next) {
+ /* this USB frame is complete */
+ break;
+ }
+ }
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+#ifdef USB_DEBUG
+ if (status & EHCI_QTD_STATERRS) {
+ DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x"
+ "status=%s%s%s%s%s%s%s%s\n",
+ xfer->address, xfer->endpointno, xfer->aframes,
+ (status & EHCI_QTD_ACTIVE) ? "[ACTIVE]" : "[NOT_ACTIVE]",
+ (status & EHCI_QTD_HALTED) ? "[HALTED]" : "",
+ (status & EHCI_QTD_BUFERR) ? "[BUFERR]" : "",
+ (status & EHCI_QTD_BABBLE) ? "[BABBLE]" : "",
+ (status & EHCI_QTD_XACTERR) ? "[XACTERR]" : "",
+ (status & EHCI_QTD_MISSEDMICRO) ? "[MISSED]" : "",
+ (status & EHCI_QTD_SPLITXSTATE) ? "[SPLIT]" : "",
+ (status & EHCI_QTD_PINGSTATE) ? "[PING]" : "");
+ }
+#endif
+ if (status & EHCI_QTD_HALTED) {
+ if ((xfer->xroot->udev->parent_hs_hub != NULL) ||
+ (xfer->xroot->udev->address != 0)) {
+ /* try to separate I/O errors from STALL */
+ if (EHCI_QTD_GET_CERR(status) == 0)
+ return (USB_ERR_IOERROR);
+ }
+ return (USB_ERR_STALLED);
+ }
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+ehci_non_isoc_done(struct usb_xfer *xfer)
+{
+ ehci_qh_t *qh;
+ usb_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
+ xfer, xfer->endpoint);
+
+#ifdef USB_DEBUG
+ if (ehcidebug > 10) {
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ ehci_dump_sqtds(sc, xfer->td_transfer_first);
+ }
+#endif
+
+ /* extract data toggle directly from the QH's overlay area */
+
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ usb_pc_cpu_invalidate(qh->page_cache);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ err = ehci_non_isoc_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+ err = ehci_non_isoc_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+ err = ehci_non_isoc_done_sub(xfer);
+ }
+done:
+ ehci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * ehci_check_transfer
+ *
+ * Return values:
+ * 0: USB transfer is not finished
+ * Else: USB transfer is finished
+ *------------------------------------------------------------------------*/
+static uint8_t
+ehci_check_transfer(struct usb_xfer *xfer)
+{
+ const struct usb_pipe_methods *methods = xfer->endpoint->methods;
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ uint32_t status;
+
+ DPRINTFN(13, "xfer=%p checking transfer\n", xfer);
+
+ if (methods == &ehci_device_isoc_fs_methods) {
+ ehci_sitd_t *td;
+
+ /* isochronous full speed transfer */
+
+ td = xfer->td_transfer_last;
+ usb_pc_cpu_invalidate(td->page_cache);
+ status = hc32toh(sc, td->sitd_status);
+
+ /* also check if first is complete */
+
+ td = xfer->td_transfer_first;
+ usb_pc_cpu_invalidate(td->page_cache);
+ status |= hc32toh(sc, td->sitd_status);
+
+ if (!(status & EHCI_SITD_ACTIVE)) {
+ ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION);
+ goto transferred;
+ }
+ } else if (methods == &ehci_device_isoc_hs_methods) {
+ ehci_itd_t *td;
+
+ /* isochronous high speed transfer */
+
+ /* check last transfer */
+ td = xfer->td_transfer_last;
+ usb_pc_cpu_invalidate(td->page_cache);
+ status = td->itd_status[0];
+ status |= td->itd_status[1];
+ status |= td->itd_status[2];
+ status |= td->itd_status[3];
+ status |= td->itd_status[4];
+ status |= td->itd_status[5];
+ status |= td->itd_status[6];
+ status |= td->itd_status[7];
+
+ /* also check first transfer */
+ td = xfer->td_transfer_first;
+ usb_pc_cpu_invalidate(td->page_cache);
+ status |= td->itd_status[0];
+ status |= td->itd_status[1];
+ status |= td->itd_status[2];
+ status |= td->itd_status[3];
+ status |= td->itd_status[4];
+ status |= td->itd_status[5];
+ status |= td->itd_status[6];
+ status |= td->itd_status[7];
+
+ /* if no transactions are active we continue */
+ if (!(status & htohc32(sc, EHCI_ITD_ACTIVE))) {
+ ehci_device_done(xfer, USB_ERR_NORMAL_COMPLETION);
+ goto transferred;
+ }
+ } else {
+ ehci_qtd_t *td;
+ ehci_qh_t *qh;
+
+ /* non-isochronous transfer */
+
+ /*
+ * check whether there is an error somewhere in the middle,
+ * or whether there was a short packet (SPD and not ACTIVE)
+ */
+ td = xfer->td_transfer_cache;
+
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ usb_pc_cpu_invalidate(qh->page_cache);
+
+ status = hc32toh(sc, qh->qh_qtd.qtd_status);
+ if (status & EHCI_QTD_ACTIVE) {
+ /* transfer is pending */
+ goto done;
+ }
+
+ while (1) {
+ usb_pc_cpu_invalidate(td->page_cache);
+ status = hc32toh(sc, td->qtd_status);
+
+ /*
+ * Check if there is an active TD which
+ * indicates that the transfer isn't done.
+ */
+ if (status & EHCI_QTD_ACTIVE) {
+ /* update cache */
+ xfer->td_transfer_cache = td;
+ goto done;
+ }
+ /*
+ * last transfer descriptor makes the transfer done
+ */
+ if (((void *)td) == xfer->td_transfer_last) {
+ break;
+ }
+ /*
+ * any kind of error makes the transfer done
+ */
+ if (status & EHCI_QTD_HALTED) {
+ break;
+ }
+ /*
+ * if there is no alternate next transfer, a short
+ * packet also makes the transfer done
+ */
+ if (EHCI_QTD_GET_BYTES(status)) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ if (td->alt_next) {
+ td = td->alt_next;
+ continue;
+ }
+ }
+ /* transfer is done */
+ break;
+ }
+ td = td->obj_next;
+ }
+ ehci_non_isoc_done(xfer);
+ goto transferred;
+ }
+
+done:
+ DPRINTFN(13, "xfer=%p is still active\n", xfer);
+ return (0);
+
+transferred:
+ return (1);
+}
+
+static void
+ehci_pcd_enable(ehci_softc_t *sc)
+{
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ sc->sc_eintrs |= EHCI_STS_PCD;
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ /* acknowledge any PCD interrupt */
+ EOWRITE4(sc, EHCI_USBSTS, EHCI_STS_PCD);
+
+ ehci_root_intr(sc);
+}
+
+static void
+ehci_interrupt_poll(ehci_softc_t *sc)
+{
+ struct usb_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ /*
+ * check if transfer is transferred
+ */
+ if (ehci_check_transfer(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+/*
+ * Some EHCI chips from VIA / ATI seem to trigger interrupts before
+ * writing back the qTD status, or miss signalling occasionally under
+ * heavy load. If the host machine is too fast, we can miss
+ * transaction completion - when we scan the active list the
+ * transaction still seems to be active. This generally exhibits
+ * itself as a umass stall that never recovers.
+ *
+ * We work around this behaviour by setting up this callback after any
+ * softintr that completes with transactions still pending, giving us
+ * another chance to check for completion after the writeback has
+ * taken place.
+ */
+static void
+ehci_poll_timeout(void *arg)
+{
+ ehci_softc_t *sc = arg;
+
+ DPRINTFN(3, "\n");
+ ehci_interrupt_poll(sc);
+}
+
+/*------------------------------------------------------------------------*
+ * ehci_interrupt - EHCI interrupt handler
+ *
+ * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler,
+ * hence the interrupt handler will be setup before "sc->sc_bus.bdev"
+ * is present !
+ *------------------------------------------------------------------------*/
+void
+ehci_interrupt(ehci_softc_t *sc)
+{
+ uint32_t status;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ DPRINTFN(16, "real interrupt\n");
+
+#ifdef USB_DEBUG
+ if (ehcidebug > 15) {
+ ehci_dump_regs(sc);
+ }
+#endif
+
+ status = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS));
+ if (status == 0) {
+ /* the interrupt was not for us */
+ goto done;
+ }
+ if (!(status & sc->sc_eintrs)) {
+ goto done;
+ }
+ EOWRITE4(sc, EHCI_USBSTS, status); /* acknowledge */
+
+ status &= sc->sc_eintrs;
+
+ if (status & EHCI_STS_HSE) {
+ printf("%s: unrecoverable error, "
+ "controller halted\n", __FUNCTION__);
+#ifdef USB_DEBUG
+ ehci_dump_regs(sc);
+ ehci_dump_isoc(sc);
+#endif
+ }
+ if (status & EHCI_STS_PCD) {
+ /*
+ * Disable PCD interrupt for now, because it will be
+ * on until the port has been reset.
+ */
+ sc->sc_eintrs &= ~EHCI_STS_PCD;
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ ehci_root_intr(sc);
+
+ /* do not allow RHSC interrupts > 1 per second */
+ usb_callout_reset(&sc->sc_tmo_pcd, hz,
+ (void *)&ehci_pcd_enable, sc);
+ }
+ status &= ~(EHCI_STS_INT | EHCI_STS_ERRINT | EHCI_STS_PCD | EHCI_STS_IAA);
+
+ if (status != 0) {
+ /* block unprocessed interrupts */
+ sc->sc_eintrs &= ~status;
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+ printf("%s: blocking interrupts 0x%x\n", __FUNCTION__, status);
+ }
+ /* poll all the USB transfers */
+ ehci_interrupt_poll(sc);
+
+ if (sc->sc_flags & EHCI_SCFLG_LOSTINTRBUG) {
+ usb_callout_reset(&sc->sc_tmo_poll, hz / 128,
+ (void *)&ehci_poll_timeout, sc);
+ }
+
+done:
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*
+ * called when a request does not complete
+ */
+static void
+ehci_timeout(void *arg)
+{
+ struct usb_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ ehci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+ehci_do_poll(struct usb_bus *bus)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ ehci_interrupt_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+ehci_setup_standard_chain_sub(struct ehci_std_temp *temp)
+{
+ struct usb_page_search buf_res;
+ ehci_qtd_t *td;
+ ehci_qtd_t *td_next;
+ ehci_qtd_t *td_alt_next;
+ uint32_t buf_offset;
+ uint32_t average;
+ uint32_t len_old;
+ uint32_t terminate;
+ uint32_t qtd_altnext;
+ uint8_t shortpkt_old;
+ uint8_t precompute;
+
+ terminate = temp->sc->sc_terminate_self;
+ qtd_altnext = temp->sc->sc_terminate_self;
+ td_alt_next = NULL;
+ buf_offset = 0;
+ shortpkt_old = temp->shortpkt;
+ len_old = temp->len;
+ precompute = 1;
+
+restart:
+
+ td = temp->td;
+ td_next = temp->td_next;
+
+ while (1) {
+ if (temp->len == 0) {
+ if (temp->shortpkt) {
+ break;
+ }
+ /* send a Zero Length Packet, ZLP, last */
+
+ temp->shortpkt = 1;
+ average = 0;
+
+ } else {
+ average = temp->average;
+
+ if (temp->len < average) {
+ if (temp->len % temp->max_frame_size) {
+ temp->shortpkt = 1;
+ }
+ average = temp->len;
+ }
+ }
+
+ if (td_next == NULL) {
+ panic("%s: out of EHCI transfer descriptors!", __FUNCTION__);
+ }
+ /* get next TD */
+
+ td = td_next;
+ td_next = td->obj_next;
+
+ /* check if we are pre-computing */
+
+ if (precompute) {
+ /* update remaining length */
+
+ temp->len -= average;
+
+ continue;
+ }
+ /* fill out current TD */
+
+ td->qtd_status =
+ temp->qtd_status |
+ htohc32(temp->sc, EHCI_QTD_IOC |
+ EHCI_QTD_SET_BYTES(average));
+
+ if (average == 0) {
+ if (temp->auto_data_toggle == 0) {
+ /* update data toggle, ZLP case */
+
+ temp->qtd_status ^=
+ htohc32(temp->sc, EHCI_QTD_TOGGLE_MASK);
+ }
+ td->len = 0;
+
+ /* properly reset reserved fields */
+ td->qtd_buffer[0] = 0;
+ td->qtd_buffer[1] = 0;
+ td->qtd_buffer[2] = 0;
+ td->qtd_buffer[3] = 0;
+ td->qtd_buffer[4] = 0;
+ td->qtd_buffer_hi[0] = 0;
+ td->qtd_buffer_hi[1] = 0;
+ td->qtd_buffer_hi[2] = 0;
+ td->qtd_buffer_hi[3] = 0;
+ td->qtd_buffer_hi[4] = 0;
+ } else {
+ uint8_t x;
+
+ if (temp->auto_data_toggle == 0) {
+ /* update data toggle */
+
+ if (howmany(average, temp->max_frame_size) & 1) {
+ temp->qtd_status ^=
+ htohc32(temp->sc, EHCI_QTD_TOGGLE_MASK);
+ }
+ }
+ td->len = average;
+
+ /* update remaining length */
+
+ temp->len -= average;
+
+ /* fill out buffer pointers */
+
+ usbd_get_page(temp->pc, buf_offset, &buf_res);
+ td->qtd_buffer[0] =
+ htohc32(temp->sc, buf_res.physaddr);
+ td->qtd_buffer_hi[0] = 0;
+
+ x = 1;
+
+ while (average > EHCI_PAGE_SIZE) {
+ average -= EHCI_PAGE_SIZE;
+ buf_offset += EHCI_PAGE_SIZE;
+ usbd_get_page(temp->pc, buf_offset, &buf_res);
+ td->qtd_buffer[x] =
+ htohc32(temp->sc,
+ buf_res.physaddr & (~0xFFF));
+ td->qtd_buffer_hi[x] = 0;
+ x++;
+ }
+
+ /*
+ * NOTE: The "average" variable is never zero after
+ * exiting the loop above !
+ *
+ * NOTE: We have to subtract one from the offset to
+ * ensure that we are computing the physical address
+ * of a valid page !
+ */
+ buf_offset += average;
+ usbd_get_page(temp->pc, buf_offset - 1, &buf_res);
+ td->qtd_buffer[x] =
+ htohc32(temp->sc,
+ buf_res.physaddr & (~0xFFF));
+ td->qtd_buffer_hi[x] = 0;
+
+ /* properly reset reserved fields */
+ while (++x < EHCI_QTD_NBUFFERS) {
+ td->qtd_buffer[x] = 0;
+ td->qtd_buffer_hi[x] = 0;
+ }
+ }
+
+ if (td_next) {
+ /* link the current TD with the next one */
+ td->qtd_next = td_next->qtd_self;
+ }
+ td->qtd_altnext = qtd_altnext;
+ td->alt_next = td_alt_next;
+
+ usb_pc_cpu_flush(td->page_cache);
+ }
+
+ if (precompute) {
+ precompute = 0;
+
+ /* setup alt next pointer, if any */
+ if (temp->last_frame) {
+ td_alt_next = NULL;
+ qtd_altnext = terminate;
+ } else {
+ /* we use this field internally */
+ td_alt_next = td_next;
+ if (temp->setup_alt_next) {
+ qtd_altnext = td_next->qtd_self;
+ } else {
+ qtd_altnext = terminate;
+ }
+ }
+
+ /* restore */
+ temp->shortpkt = shortpkt_old;
+ temp->len = len_old;
+ goto restart;
+ }
+ temp->td = td;
+ temp->td_next = td_next;
+}
+
+static void
+ehci_setup_standard_chain(struct usb_xfer *xfer, ehci_qh_t **qh_last)
+{
+ struct ehci_std_temp temp;
+ const struct usb_pipe_methods *methods;
+ ehci_qh_t *qh;
+ ehci_qtd_t *td;
+ uint32_t qh_endp;
+ uint32_t qh_endphub;
+ uint32_t x;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpointno),
+ xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
+
+ temp.average = xfer->max_hc_frame_size;
+ temp.max_frame_size = xfer->max_frame_size;
+ temp.sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ temp.td = NULL;
+ temp.td_next = td;
+ temp.qtd_status = 0;
+ temp.last_frame = 0;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->endpoint->toggle_next) {
+ /* DATA1 is next */
+ temp.qtd_status |=
+ htohc32(temp.sc, EHCI_QTD_SET_TOGGLE(1));
+ }
+ temp.auto_data_toggle = 0;
+ } else {
+ temp.auto_data_toggle = 1;
+ }
+
+ if ((xfer->xroot->udev->parent_hs_hub != NULL) ||
+ (xfer->xroot->udev->address != 0)) {
+ /* max 3 retries */
+ temp.qtd_status |=
+ htohc32(temp.sc, EHCI_QTD_SET_CERR(3));
+ }
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ xfer->endpoint->toggle_next = 0;
+
+ temp.qtd_status &=
+ htohc32(temp.sc, EHCI_QTD_SET_CERR(3));
+ temp.qtd_status |= htohc32(temp.sc,
+ EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_SETUP) |
+ EHCI_QTD_SET_TOGGLE(0));
+
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.shortpkt = temp.len ? 1 : 0;
+ /* check for last frame */
+ if (xfer->nframes == 1) {
+ /* no STATUS stage yet, SETUP is last */
+ if (xfer->flags_int.control_act) {
+ temp.last_frame = 1;
+ temp.setup_alt_next = 0;
+ }
+ }
+ ehci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ while (x != xfer->nframes) {
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+ temp.pc = xfer->frbuffers + x;
+
+ x++;
+
+ if (x == xfer->nframes) {
+ if (xfer->flags_int.control_xfr) {
+ /* no STATUS stage yet, DATA is last */
+ if (xfer->flags_int.control_act) {
+ temp.last_frame = 1;
+ temp.setup_alt_next = 0;
+ }
+ } else {
+ temp.last_frame = 1;
+ temp.setup_alt_next = 0;
+ }
+ }
+ /* keep previous data toggle and error count */
+
+ temp.qtd_status &=
+ htohc32(temp.sc, EHCI_QTD_SET_CERR(3) |
+ EHCI_QTD_SET_TOGGLE(1));
+
+ if (temp.len == 0) {
+ /* make sure that we send an USB packet */
+
+ temp.shortpkt = 0;
+
+ } else {
+ /* regular data transfer */
+
+ temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ /* set endpoint direction */
+
+ temp.qtd_status |=
+ (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ?
+ htohc32(temp.sc, EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_IN)) :
+ htohc32(temp.sc, EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT));
+
+ ehci_setup_standard_chain_sub(&temp);
+ }
+
+ /* check if we should append a status stage */
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+ /*
+ * Send a DATA1 message and invert the current endpoint
+ * direction.
+ */
+
+ temp.qtd_status &= htohc32(temp.sc, EHCI_QTD_SET_CERR(3) |
+ EHCI_QTD_SET_TOGGLE(1));
+ temp.qtd_status |=
+ (UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT) ?
+ htohc32(temp.sc, EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_IN) |
+ EHCI_QTD_SET_TOGGLE(1)) :
+ htohc32(temp.sc, EHCI_QTD_ACTIVE |
+ EHCI_QTD_SET_PID(EHCI_QTD_PID_OUT) |
+ EHCI_QTD_SET_TOGGLE(1));
+
+ temp.len = 0;
+ temp.pc = NULL;
+ temp.shortpkt = 0;
+ temp.last_frame = 1;
+ temp.setup_alt_next = 0;
+
+ ehci_setup_standard_chain_sub(&temp);
+ }
+ td = temp.td;
+
+ /* the last TD terminates the transfer: */
+ td->qtd_next = htohc32(temp.sc, EHCI_LINK_TERMINATE);
+ td->qtd_altnext = htohc32(temp.sc, EHCI_LINK_TERMINATE);
+
+ usb_pc_cpu_flush(td->page_cache);
+
+ /* must have at least one frame! */
+
+ xfer->td_transfer_last = td;
+
+#ifdef USB_DEBUG
+ if (ehcidebug > 8) {
+ DPRINTF("nexttog=%d; data before transfer:\n",
+ xfer->endpoint->toggle_next);
+ ehci_dump_sqtds(temp.sc,
+ xfer->td_transfer_first);
+ }
+#endif
+
+ methods = xfer->endpoint->methods;
+
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ /* the "qh_link" field is filled when the QH is added */
+
+ qh_endp =
+ (EHCI_QH_SET_ADDR(xfer->address) |
+ EHCI_QH_SET_ENDPT(UE_GET_ADDR(xfer->endpointno)) |
+ EHCI_QH_SET_MPL(xfer->max_packet_size));
+
+ if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_HIGH) {
+ qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_HIGH);
+ if (methods != &ehci_device_intr_methods)
+ qh_endp |= EHCI_QH_SET_NRL(8);
+ } else {
+ if (usbd_get_speed(xfer->xroot->udev) == USB_SPEED_FULL) {
+ qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_FULL);
+ } else {
+ qh_endp |= EHCI_QH_SET_EPS(EHCI_QH_SPEED_LOW);
+ }
+
+ if (methods == &ehci_device_ctrl_methods) {
+ qh_endp |= EHCI_QH_CTL;
+ }
+ if (methods != &ehci_device_intr_methods) {
+ /* Only try one time per microframe! */
+ qh_endp |= EHCI_QH_SET_NRL(1);
+ }
+ }
+
+ if (temp.auto_data_toggle == 0) {
+ /* software computes the data toggle */
+ qh_endp |= EHCI_QH_DTC;
+ }
+
+ qh->qh_endp = htohc32(temp.sc, qh_endp);
+
+ qh_endphub =
+ (EHCI_QH_SET_MULT(xfer->max_packet_count & 3) |
+ EHCI_QH_SET_CMASK(xfer->endpoint->usb_cmask) |
+ EHCI_QH_SET_SMASK(xfer->endpoint->usb_smask) |
+ EHCI_QH_SET_HUBA(xfer->xroot->udev->hs_hub_addr) |
+ EHCI_QH_SET_PORT(xfer->xroot->udev->hs_port_no));
+
+ qh->qh_endphub = htohc32(temp.sc, qh_endphub);
+ qh->qh_curqtd = 0;
+
+ /* fill the overlay qTD */
+
+ if (temp.auto_data_toggle && xfer->endpoint->toggle_next) {
+ /* DATA1 is next */
+ qh->qh_qtd.qtd_status = htohc32(temp.sc, EHCI_QTD_SET_TOGGLE(1));
+ } else {
+ qh->qh_qtd.qtd_status = 0;
+ }
+
+ td = xfer->td_transfer_first;
+
+ qh->qh_qtd.qtd_next = td->qtd_self;
+ qh->qh_qtd.qtd_altnext =
+ htohc32(temp.sc, EHCI_LINK_TERMINATE);
+
+ /* properly reset reserved fields */
+ qh->qh_qtd.qtd_buffer[0] = 0;
+ qh->qh_qtd.qtd_buffer[1] = 0;
+ qh->qh_qtd.qtd_buffer[2] = 0;
+ qh->qh_qtd.qtd_buffer[3] = 0;
+ qh->qh_qtd.qtd_buffer[4] = 0;
+ qh->qh_qtd.qtd_buffer_hi[0] = 0;
+ qh->qh_qtd.qtd_buffer_hi[1] = 0;
+ qh->qh_qtd.qtd_buffer_hi[2] = 0;
+ qh->qh_qtd.qtd_buffer_hi[3] = 0;
+ qh->qh_qtd.qtd_buffer_hi[4] = 0;
+
+ usb_pc_cpu_flush(qh->page_cache);
+
+ if (xfer->xroot->udev->flags.self_suspended == 0) {
+ EHCI_APPEND_QH(qh, *qh_last);
+ }
+}
+
+static void
+ehci_root_intr(ehci_softc_t *sc)
+{
+ uint16_t i;
+ uint16_t m;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* clear any old interrupt data */
+ memset(sc->sc_hub_idata, 0, sizeof(sc->sc_hub_idata));
+
+ /* set bits */
+ m = (sc->sc_noport + 1);
+ if (m > (8 * sizeof(sc->sc_hub_idata))) {
+ m = (8 * sizeof(sc->sc_hub_idata));
+ }
+ for (i = 1; i < m; i++) {
+ /* pick out CHANGE bits from the status register */
+ if (EOREAD4(sc, EHCI_PORTSC(i)) & EHCI_PS_CLEAR) {
+ sc->sc_hub_idata[i / 8] |= 1 << (i % 8);
+ DPRINTF("port %d changed\n", i);
+ }
+ }
+ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
+ sizeof(sc->sc_hub_idata));
+}
+
+static void
+ehci_isoc_fs_done(ehci_softc_t *sc, struct usb_xfer *xfer)
+{
+ uint32_t nframes = xfer->nframes;
+ uint32_t status;
+ uint32_t *plen = xfer->frlengths;
+ uint16_t len = 0;
+ ehci_sitd_t *td = xfer->td_transfer_first;
+ ehci_sitd_t **pp_last = &sc->sc_isoc_fs_p_last[xfer->qh_pos];
+
+ DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
+ xfer, xfer->endpoint);
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_fs_p_last[0];
+ }
+#ifdef USB_DEBUG
+ if (ehcidebug > 15) {
+ DPRINTF("isoc FS-TD\n");
+ ehci_dump_sitd(sc, td);
+ }
+#endif
+ usb_pc_cpu_invalidate(td->page_cache);
+ status = hc32toh(sc, td->sitd_status);
+
+ len = EHCI_SITD_GET_LEN(status);
+
+ DPRINTFN(2, "status=0x%08x, rem=%u\n", status, len);
+
+ if (*plen >= len) {
+ len = *plen - len;
+ } else {
+ len = 0;
+ }
+
+ *plen = len;
+
+ /* remove FS-TD from schedule */
+ EHCI_REMOVE_FS_TD(td, *pp_last);
+
+ pp_last++;
+ plen++;
+ td = td->obj_next;
+ }
+
+ xfer->aframes = xfer->nframes;
+}
+
+static void
+ehci_isoc_hs_done(ehci_softc_t *sc, struct usb_xfer *xfer)
+{
+ uint32_t nframes = xfer->nframes;
+ uint32_t status;
+ uint32_t *plen = xfer->frlengths;
+ uint16_t len = 0;
+ uint8_t td_no = 0;
+ ehci_itd_t *td = xfer->td_transfer_first;
+ ehci_itd_t **pp_last = &sc->sc_isoc_hs_p_last[xfer->qh_pos];
+
+ DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
+ xfer, xfer->endpoint);
+
+ while (nframes) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_hs_p_last[0];
+ }
+#ifdef USB_DEBUG
+ if (ehcidebug > 15) {
+ DPRINTF("isoc HS-TD\n");
+ ehci_dump_itd(sc, td);
+ }
+#endif
+
+ usb_pc_cpu_invalidate(td->page_cache);
+ status = hc32toh(sc, td->itd_status[td_no]);
+
+ len = EHCI_ITD_GET_LEN(status);
+
+ DPRINTFN(2, "status=0x%08x, len=%u\n", status, len);
+
+ if (xfer->endpoint->usb_smask & (1 << td_no)) {
+ if (*plen >= len) {
+ /*
+ * The length is valid. NOTE: The
+ * complete length is written back
+ * into the status field, and not the
+ * remainder like with other transfer
+ * descriptor types.
+ */
+ } else {
+ /* Invalid length - truncate */
+ len = 0;
+ }
+
+ *plen = len;
+ plen++;
+ nframes--;
+ }
+
+ td_no++;
+
+ if ((td_no == 8) || (nframes == 0)) {
+ /* remove HS-TD from schedule */
+ EHCI_REMOVE_HS_TD(td, *pp_last);
+ pp_last++;
+
+ td_no = 0;
+ td = td->obj_next;
+ }
+ }
+ xfer->aframes = xfer->nframes;
+}
+
+/* NOTE: "done" can be run two times in a row,
+ * from close and from interrupt
+ */
+static void
+ehci_device_done(struct usb_xfer *xfer, usb_error_t error)
+{
+ const struct usb_pipe_methods *methods = xfer->endpoint->methods;
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n",
+ xfer, xfer->endpoint, error);
+
+ if ((methods == &ehci_device_bulk_methods) ||
+ (methods == &ehci_device_ctrl_methods)) {
+#ifdef USB_DEBUG
+ if (ehcidebug > 8) {
+ DPRINTF("nexttog=%d; data after transfer:\n",
+ xfer->endpoint->toggle_next);
+ ehci_dump_sqtds(sc,
+ xfer->td_transfer_first);
+ }
+#endif
+
+ EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_async_p_last);
+ }
+ if (methods == &ehci_device_intr_methods) {
+ EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ /*
+ * Only finish isochronous transfers once which will update
+ * "xfer->frlengths".
+ */
+ if (xfer->td_transfer_first &&
+ xfer->td_transfer_last) {
+ if (methods == &ehci_device_isoc_fs_methods) {
+ ehci_isoc_fs_done(sc, xfer);
+ }
+ if (methods == &ehci_device_isoc_hs_methods) {
+ ehci_isoc_hs_done(sc, xfer);
+ }
+ xfer->td_transfer_first = NULL;
+ xfer->td_transfer_last = NULL;
+ }
+ /* dequeue transfer and start next transfer */
+ usbd_transfer_done(xfer, error);
+}
+
+/*------------------------------------------------------------------------*
+ * ehci bulk support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_bulk_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_bulk_close(struct usb_xfer *xfer)
+{
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_device_bulk_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_doorbell_async(struct ehci_softc *sc)
+{
+ uint32_t temp;
+
+ /*
+ * XXX Performance quirk: Some Host Controllers have a too low
+ * interrupt rate. Issue an IAAD to stimulate the Host
+ * Controller after queueing the BULK transfer.
+ *
+ * XXX Force the host controller to refresh any QH caches.
+ */
+ temp = EOREAD4(sc, EHCI_USBCMD);
+ if (!(temp & EHCI_CMD_IAAD))
+ EOWRITE4(sc, EHCI_USBCMD, temp | EHCI_CMD_IAAD);
+}
+
+static void
+ehci_device_bulk_start(struct usb_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ehci_setup_standard_chain(xfer, &sc->sc_async_p_last);
+
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+
+ /*
+ * XXX Certain nVidia chipsets choke when using the IAAD
+ * feature too frequently.
+ */
+ if (sc->sc_flags & EHCI_SCFLG_IAADBUG)
+ return;
+
+ ehci_doorbell_async(sc);
+}
+
+static const struct usb_pipe_methods ehci_device_bulk_methods =
+{
+ .open = ehci_device_bulk_open,
+ .close = ehci_device_bulk_close,
+ .enter = ehci_device_bulk_enter,
+ .start = ehci_device_bulk_start,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci control support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_ctrl_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_ctrl_close(struct usb_xfer *xfer)
+{
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_device_ctrl_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_ctrl_start(struct usb_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ehci_setup_standard_chain(xfer, &sc->sc_async_p_last);
+
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+static const struct usb_pipe_methods ehci_device_ctrl_methods =
+{
+ .open = ehci_device_ctrl_open,
+ .close = ehci_device_ctrl_close,
+ .enter = ehci_device_ctrl_enter,
+ .start = ehci_device_ctrl_start,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_intr_open(struct usb_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ uint16_t best;
+ uint16_t bit;
+ uint16_t x;
+
+ usb_hs_bandwidth_alloc(xfer);
+
+ /*
+ * Find the best QH position corresponding to the given interval:
+ */
+
+ best = 0;
+ bit = EHCI_VIRTUAL_FRAMELIST_COUNT / 2;
+ while (bit) {
+ if (xfer->interval >= bit) {
+ x = bit;
+ best = bit;
+ while (x & bit) {
+ if (sc->sc_intr_stat[x] <
+ sc->sc_intr_stat[best]) {
+ best = x;
+ }
+ x++;
+ }
+ break;
+ }
+ bit >>= 1;
+ }
+
+ sc->sc_intr_stat[best]++;
+ xfer->qh_pos = best;
+
+ DPRINTFN(3, "best=%d interval=%d\n",
+ best, xfer->interval);
+}
+
+static void
+ehci_device_intr_close(struct usb_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_intr_stat[xfer->qh_pos]--;
+
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+
+ /* bandwidth must be freed after device done */
+ usb_hs_bandwidth_free(xfer);
+}
+
+static void
+ehci_device_intr_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_device_intr_start(struct usb_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ehci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]);
+
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+static const struct usb_pipe_methods ehci_device_intr_methods =
+{
+ .open = ehci_device_intr_open,
+ .close = ehci_device_intr_close,
+ .enter = ehci_device_intr_enter,
+ .start = ehci_device_intr_start,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_isoc_fs_open(struct usb_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ ehci_sitd_t *td;
+ uint32_t sitd_portaddr;
+ uint8_t ds;
+
+ sitd_portaddr =
+ EHCI_SITD_SET_ADDR(xfer->address) |
+ EHCI_SITD_SET_ENDPT(UE_GET_ADDR(xfer->endpointno)) |
+ EHCI_SITD_SET_HUBA(xfer->xroot->udev->hs_hub_addr) |
+ EHCI_SITD_SET_PORT(xfer->xroot->udev->hs_port_no);
+
+ if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN)
+ sitd_portaddr |= EHCI_SITD_SET_DIR_IN;
+
+ sitd_portaddr = htohc32(sc, sitd_portaddr);
+
+ /* initialize all TD's */
+
+ for (ds = 0; ds != 2; ds++) {
+ for (td = xfer->td_start[ds]; td; td = td->obj_next) {
+ td->sitd_portaddr = sitd_portaddr;
+
+ /*
+ * TODO: make some kind of automatic
+ * SMASK/CMASK selection based on micro-frame
+ * usage
+ *
+ * micro-frame usage (8 microframes per 1ms)
+ */
+ td->sitd_back = htohc32(sc, EHCI_LINK_TERMINATE);
+
+ usb_pc_cpu_flush(td->page_cache);
+ }
+ }
+}
+
+static void
+ehci_device_isoc_fs_close(struct usb_xfer *xfer)
+{
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ehci_device_isoc_fs_enter(struct usb_xfer *xfer)
+{
+ struct usb_page_search buf_res;
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ ehci_sitd_t *td;
+ ehci_sitd_t *td_last = NULL;
+ ehci_sitd_t **pp_last;
+ uint32_t *plen;
+ uint32_t buf_offset;
+ uint32_t nframes;
+ uint32_t startframe;
+ uint32_t temp;
+ uint32_t sitd_mask;
+ uint16_t tlen;
+ uint8_t sa;
+ uint8_t sb;
+
+#ifdef USB_DEBUG
+ uint8_t once = 1;
+
+#endif
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->endpoint->isoc_next, xfer->nframes);
+
+ /* get the current frame index */
+
+ nframes = EOREAD4(sc, EHCI_FRINDEX) / 8;
+
+ if (usbd_xfer_get_isochronous_start_frame(
+ xfer, nframes, 0, 1, EHCI_VIRTUAL_FRAMELIST_COUNT - 1, &startframe))
+ DPRINTFN(3, "start next=%d\n", startframe);
+
+ /* get the real number of frames */
+
+ nframes = xfer->nframes;
+
+ buf_offset = 0;
+
+ plen = xfer->frlengths;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+ xfer->td_transfer_first = td;
+
+ pp_last = &sc->sc_isoc_fs_p_last[startframe];
+
+ /* store starting position */
+
+ xfer->qh_pos = startframe;
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT])
+ pp_last = &sc->sc_isoc_fs_p_last[0];
+
+ /* reuse sitd_portaddr and sitd_back from last transfer */
+
+ if (*plen > xfer->max_frame_size) {
+#ifdef USB_DEBUG
+ if (once) {
+ once = 0;
+ printf("%s: frame length(%d) exceeds %d "
+ "bytes (frame truncated)\n",
+ __FUNCTION__, *plen,
+ xfer->max_frame_size);
+ }
+#endif
+ *plen = xfer->max_frame_size;
+ }
+
+ /* allocate a slot */
+
+ sa = usbd_fs_isoc_schedule_alloc_slot(xfer,
+ xfer->isoc_time_complete - nframes - 1);
+
+ if (sa == 255) {
+ /*
+ * Schedule is FULL, set length to zero:
+ */
+
+ *plen = 0;
+ sa = USB_FS_ISOC_UFRAME_MAX - 1;
+ }
+ if (*plen) {
+ /*
+ * only call "usbd_get_page()" when we have a
+ * non-zero length
+ */
+ usbd_get_page(xfer->frbuffers, buf_offset, &buf_res);
+ td->sitd_bp[0] = htohc32(sc, buf_res.physaddr);
+ buf_offset += *plen;
+ /*
+ * NOTE: We need to subtract one from the offset so
+ * that we are on a valid page!
+ */
+ usbd_get_page(xfer->frbuffers, buf_offset - 1,
+ &buf_res);
+ temp = buf_res.physaddr & ~0xFFF;
+ } else {
+ td->sitd_bp[0] = 0;
+ temp = 0;
+ }
+
+ if (UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT) {
+ tlen = *plen;
+ if (tlen <= 188) {
+ temp |= 1; /* T-count = 1, TP = ALL */
+ tlen = 1;
+ } else {
+ tlen += 187;
+ tlen /= 188;
+ temp |= tlen; /* T-count = [1..6] */
+ temp |= 8; /* TP = Begin */
+ }
+
+ tlen += sa;
+
+ if (tlen >= 8) {
+ sb = 0;
+ } else {
+ sb = (1 << tlen);
+ }
+
+ sa = (1 << sa);
+ sa = (sb - sa) & 0x3F;
+ sb = 0;
+ } else {
+ sb = (-(4 << sa)) & 0xFE;
+ sa = (1 << sa) & 0x3F;
+ }
+
+ sitd_mask = (EHCI_SITD_SET_SMASK(sa) |
+ EHCI_SITD_SET_CMASK(sb));
+
+ td->sitd_bp[1] = htohc32(sc, temp);
+
+ td->sitd_mask = htohc32(sc, sitd_mask);
+
+ if (nframes == 0) {
+ td->sitd_status = htohc32(sc,
+ EHCI_SITD_IOC |
+ EHCI_SITD_ACTIVE |
+ EHCI_SITD_SET_LEN(*plen));
+ } else {
+ td->sitd_status = htohc32(sc,
+ EHCI_SITD_ACTIVE |
+ EHCI_SITD_SET_LEN(*plen));
+ }
+ usb_pc_cpu_flush(td->page_cache);
+
+#ifdef USB_DEBUG
+ if (ehcidebug > 15) {
+ DPRINTF("FS-TD %d\n", nframes);
+ ehci_dump_sitd(sc, td);
+ }
+#endif
+ /* insert TD into schedule */
+ EHCI_APPEND_FS_TD(td, *pp_last);
+ pp_last++;
+
+ plen++;
+ td_last = td;
+ td = td->obj_next;
+ }
+
+ xfer->td_transfer_last = td_last;
+
+ /*
+ * We don't allow cancelling of the SPLIT transaction USB FULL
+ * speed transfer, because it disturbs the bandwidth
+ * computation algorithm.
+ */
+ xfer->flags_int.can_cancel_immed = 0;
+}
+
+static void
+ehci_device_isoc_fs_start(struct usb_xfer *xfer)
+{
+ /*
+ * We don't allow cancelling of the SPLIT transaction USB FULL
+ * speed transfer, because it disturbs the bandwidth
+ * computation algorithm.
+ */
+ xfer->flags_int.can_cancel_immed = 0;
+
+ /* set a default timeout */
+ if (xfer->timeout == 0)
+ xfer->timeout = 500; /* ms */
+
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+static const struct usb_pipe_methods ehci_device_isoc_fs_methods =
+{
+ .open = ehci_device_isoc_fs_open,
+ .close = ehci_device_isoc_fs_close,
+ .enter = ehci_device_isoc_fs_enter,
+ .start = ehci_device_isoc_fs_start,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci high speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+ehci_device_isoc_hs_open(struct usb_xfer *xfer)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ ehci_itd_t *td;
+ uint32_t temp;
+ uint8_t ds;
+
+ usb_hs_bandwidth_alloc(xfer);
+
+ /* initialize all TD's */
+
+ for (ds = 0; ds != 2; ds++) {
+ for (td = xfer->td_start[ds]; td; td = td->obj_next) {
+ /* set TD inactive */
+ td->itd_status[0] = 0;
+ td->itd_status[1] = 0;
+ td->itd_status[2] = 0;
+ td->itd_status[3] = 0;
+ td->itd_status[4] = 0;
+ td->itd_status[5] = 0;
+ td->itd_status[6] = 0;
+ td->itd_status[7] = 0;
+
+ /* set endpoint and address */
+ td->itd_bp[0] = htohc32(sc,
+ EHCI_ITD_SET_ADDR(xfer->address) |
+ EHCI_ITD_SET_ENDPT(UE_GET_ADDR(xfer->endpointno)));
+
+ temp =
+ EHCI_ITD_SET_MPL(xfer->max_packet_size & 0x7FF);
+
+ /* set direction */
+ if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) {
+ temp |= EHCI_ITD_SET_DIR_IN;
+ }
+ /* set maximum packet size */
+ td->itd_bp[1] = htohc32(sc, temp);
+
+ /* set transfer multiplier */
+ td->itd_bp[2] = htohc32(sc, xfer->max_packet_count & 3);
+
+ usb_pc_cpu_flush(td->page_cache);
+ }
+ }
+}
+
+static void
+ehci_device_isoc_hs_close(struct usb_xfer *xfer)
+{
+ ehci_device_done(xfer, USB_ERR_CANCELLED);
+
+ /* bandwidth must be freed after device done */
+ usb_hs_bandwidth_free(xfer);
+}
+
+static void
+ehci_device_isoc_hs_enter(struct usb_xfer *xfer)
+{
+ struct usb_page_search buf_res;
+ ehci_softc_t *sc = EHCI_BUS2SC(xfer->xroot->bus);
+ ehci_itd_t *td;
+ ehci_itd_t *td_last = NULL;
+ ehci_itd_t **pp_last;
+ bus_size_t page_addr;
+ uint32_t *plen;
+ uint32_t status;
+ uint32_t buf_offset;
+ uint32_t nframes;
+ uint32_t startframe;
+ uint32_t itd_offset[8 + 1];
+ uint8_t x;
+ uint8_t td_no;
+ uint8_t page_no;
+
+#ifdef USB_DEBUG
+ uint8_t once = 1;
+
+#endif
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d shift=%d\n",
+ xfer, xfer->endpoint->isoc_next, xfer->nframes,
+ usbd_xfer_get_fps_shift(xfer));
+
+ /* get the current frame index */
+
+ nframes = EOREAD4(sc, EHCI_FRINDEX) / 8;
+
+ if (usbd_xfer_get_isochronous_start_frame(
+ xfer, nframes, 0, 1, EHCI_VIRTUAL_FRAMELIST_COUNT - 1, &startframe))
+ DPRINTFN(3, "start next=%d\n", startframe);
+
+ nframes = xfer->nframes;
+
+ buf_offset = 0;
+ td_no = 0;
+
+ plen = xfer->frlengths;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+ xfer->td_transfer_first = td;
+
+ pp_last = &sc->sc_isoc_hs_p_last[startframe];
+
+ /* store starting position */
+
+ xfer->qh_pos = startframe;
+
+ while (nframes) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_hs_p_last[0];
+ }
+ /* range check */
+ if (*plen > xfer->max_frame_size) {
+#ifdef USB_DEBUG
+ if (once) {
+ once = 0;
+ printf("%s: frame length(%d) exceeds %d bytes "
+ "(frame truncated)\n",
+ __FUNCTION__, *plen, xfer->max_frame_size);
+ }
+#endif
+ *plen = xfer->max_frame_size;
+ }
+
+ if (xfer->endpoint->usb_smask & (1 << td_no)) {
+ status = (EHCI_ITD_SET_LEN(*plen) |
+ EHCI_ITD_ACTIVE |
+ EHCI_ITD_SET_PG(0));
+ td->itd_status[td_no] = htohc32(sc, status);
+ itd_offset[td_no] = buf_offset;
+ buf_offset += *plen;
+ plen++;
+ nframes --;
+ } else {
+ td->itd_status[td_no] = 0; /* not active */
+ itd_offset[td_no] = buf_offset;
+ }
+
+ td_no++;
+
+ if ((td_no == 8) || (nframes == 0)) {
+ /* the rest of the transfers are not active, if any */
+ for (x = td_no; x != 8; x++) {
+ td->itd_status[x] = 0; /* not active */
+ }
+
+ /* check if there is any data to be transferred */
+ if (itd_offset[0] != buf_offset) {
+ page_no = 0;
+ itd_offset[td_no] = buf_offset;
+
+ /* get first page offset */
+ usbd_get_page(xfer->frbuffers, itd_offset[0], &buf_res);
+ /* get page address */
+ page_addr = buf_res.physaddr & ~0xFFF;
+ /* update page address */
+ td->itd_bp[0] &= htohc32(sc, 0xFFF);
+ td->itd_bp[0] |= htohc32(sc, page_addr);
+
+ for (x = 0; x != td_no; x++) {
+ /* set page number and page offset */
+ status = (EHCI_ITD_SET_PG(page_no) |
+ (buf_res.physaddr & 0xFFF));
+ td->itd_status[x] |= htohc32(sc, status);
+
+ /* get next page offset */
+ if (itd_offset[x + 1] == buf_offset) {
+ /*
+ * We subtract one so that
+ * we don't go off the last
+ * page!
+ */
+ usbd_get_page(xfer->frbuffers, buf_offset - 1, &buf_res);
+ } else {
+ usbd_get_page(xfer->frbuffers, itd_offset[x + 1], &buf_res);
+ }
+
+ /* check if we need a new page */
+ if ((buf_res.physaddr ^ page_addr) & ~0xFFF) {
+ /* new page needed */
+ page_addr = buf_res.physaddr & ~0xFFF;
+ if (page_no == 6) {
+ panic("%s: too many pages\n", __FUNCTION__);
+ }
+ page_no++;
+ /* update page address */
+ td->itd_bp[page_no] &= htohc32(sc, 0xFFF);
+ td->itd_bp[page_no] |= htohc32(sc, page_addr);
+ }
+ }
+ }
+ /* set IOC bit if we are complete */
+ if (nframes == 0) {
+ td->itd_status[td_no - 1] |= htohc32(sc, EHCI_ITD_IOC);
+ }
+ usb_pc_cpu_flush(td->page_cache);
+#ifdef USB_DEBUG
+ if (ehcidebug > 15) {
+ DPRINTF("HS-TD %d\n", nframes);
+ ehci_dump_itd(sc, td);
+ }
+#endif
+ /* insert TD into schedule */
+ EHCI_APPEND_HS_TD(td, *pp_last);
+ pp_last++;
+
+ td_no = 0;
+ td_last = td;
+ td = td->obj_next;
+ }
+ }
+
+ xfer->td_transfer_last = td_last;
+}
+
+static void
+ehci_device_isoc_hs_start(struct usb_xfer *xfer)
+{
+ /* put transfer on interrupt queue */
+ ehci_transfer_intr_enqueue(xfer);
+}
+
+static const struct usb_pipe_methods ehci_device_isoc_hs_methods =
+{
+ .open = ehci_device_isoc_hs_open,
+ .close = ehci_device_isoc_hs_close,
+ .enter = ehci_device_isoc_hs_enter,
+ .start = ehci_device_isoc_hs_start,
+};
+
+/*------------------------------------------------------------------------*
+ * ehci root control support
+ *------------------------------------------------------------------------*
+ * Simulate a hardware hub by handling all the necessary requests.
+ *------------------------------------------------------------------------*/
+
+static const
+struct usb_device_descriptor ehci_devd =
+{
+ sizeof(struct usb_device_descriptor),
+ UDESC_DEVICE, /* type */
+ {0x00, 0x02}, /* USB version */
+ UDCLASS_HUB, /* class */
+ UDSUBCLASS_HUB, /* subclass */
+ UDPROTO_HSHUBSTT, /* protocol */
+ 64, /* max packet */
+ {0}, {0}, {0x00, 0x01}, /* device id */
+ 1, 2, 0, /* string indexes */
+ 1 /* # of configurations */
+};
+
+static const
+struct usb_device_qualifier ehci_odevd =
+{
+ sizeof(struct usb_device_qualifier),
+ UDESC_DEVICE_QUALIFIER, /* type */
+ {0x00, 0x02}, /* USB version */
+ UDCLASS_HUB, /* class */
+ UDSUBCLASS_HUB, /* subclass */
+ UDPROTO_FSHUB, /* protocol */
+ 0, /* max packet */
+ 0, /* # of configurations */
+ 0
+};
+
+static const struct ehci_config_desc ehci_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(ehci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0 /* max power */
+ },
+ .ifcd = {
+ .bLength = sizeof(struct usb_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = 0,
+ },
+ .endpd = {
+ .bLength = sizeof(struct usb_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = UE_DIR_IN | EHCI_INTR_ENDPT,
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8, /* max packet (63 ports) */
+ .bInterval = 255,
+ },
+};
+
+static const
+struct usb_hub_descriptor ehci_hubd =
+{
+ .bDescLength = 0, /* dynamic length */
+ .bDescriptorType = UDESC_HUB,
+};
+
+uint16_t
+ehci_get_port_speed_portsc(struct ehci_softc *sc, uint16_t index)
+{
+ uint32_t v;
+
+ v = EOREAD4(sc, EHCI_PORTSC(index));
+ v = (v >> EHCI_PORTSC_PSPD_SHIFT) & EHCI_PORTSC_PSPD_MASK;
+
+ if (v == EHCI_PORT_SPEED_HIGH)
+ return (UPS_HIGH_SPEED);
+ if (v == EHCI_PORT_SPEED_LOW)
+ return (UPS_LOW_SPEED);
+ return (0);
+}
+
+uint16_t
+ehci_get_port_speed_hostc(struct ehci_softc *sc, uint16_t index)
+{
+ uint32_t v;
+
+ v = EOREAD4(sc, EHCI_HOSTC(index));
+ v = (v >> EHCI_HOSTC_PSPD_SHIFT) & EHCI_HOSTC_PSPD_MASK;
+
+ if (v == EHCI_PORT_SPEED_HIGH)
+ return (UPS_HIGH_SPEED);
+ if (v == EHCI_PORT_SPEED_LOW)
+ return (UPS_LOW_SPEED);
+ return (0);
+}
+
+static void
+ehci_disown(ehci_softc_t *sc, uint16_t index, uint8_t lowspeed)
+{
+ uint32_t port;
+ uint32_t v;
+
+ DPRINTF("index=%d lowspeed=%d\n", index, lowspeed);
+
+ port = EHCI_PORTSC(index);
+ v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR;
+ EOWRITE4(sc, port, v | EHCI_PS_PO);
+}
+
+static usb_error_t
+ehci_roothub_exec(struct usb_device *udev,
+ struct usb_device_request *req, const void **pptr, uint16_t *plength)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(udev->bus);
+ const char *str_ptr;
+ const void *ptr;
+ uint32_t port;
+ uint32_t v;
+ uint16_t len;
+ uint16_t i;
+ uint16_t value;
+ uint16_t index;
+ usb_error_t err;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* buffer reset */
+ ptr = (const void *)&sc->sc_hub_desc;
+ len = 0;
+ err = 0;
+
+ value = UGETW(req->wValue);
+ index = UGETW(req->wIndex);
+
+ DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x "
+ "wValue=0x%04x wIndex=0x%04x\n",
+ req->bmRequestType, req->bRequest,
+ UGETW(req->wLength), value, index);
+
+#define C(x,y) ((x) | ((y) << 8))
+ switch (C(req->bRequest, req->bmRequestType)) {
+ case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
+ /*
+ * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops
+ * for the integrated root hub.
+ */
+ break;
+ case C(UR_GET_CONFIG, UT_READ_DEVICE):
+ len = 1;
+ sc->sc_hub_desc.temp[0] = sc->sc_conf;
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE):
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ len = sizeof(ehci_devd);
+ ptr = (const void *)&ehci_devd;
+ break;
+ /*
+ * We can't really operate at another speed,
+ * but the specification says we need this
+ * descriptor:
+ */
+ case UDESC_DEVICE_QUALIFIER:
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ len = sizeof(ehci_odevd);
+ ptr = (const void *)&ehci_odevd;
+ break;
+
+ case UDESC_CONFIG:
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ len = sizeof(ehci_confd);
+ ptr = (const void *)&ehci_confd;
+ break;
+
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ str_ptr = "\001";
+ break;
+
+ case 1: /* Vendor */
+ str_ptr = sc->sc_vendor;
+ break;
+
+ case 2: /* Product */
+ str_ptr = "EHCI root HUB";
+ break;
+
+ default:
+ str_ptr = "";
+ break;
+ }
+
+ len = usb_make_str_desc(
+ sc->sc_hub_desc.temp,
+ sizeof(sc->sc_hub_desc.temp),
+ str_ptr);
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_GET_INTERFACE, UT_READ_INTERFACE):
+ len = 1;
+ sc->sc_hub_desc.temp[0] = 0;
+ break;
+ case C(UR_GET_STATUS, UT_READ_DEVICE):
+ len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED);
+ break;
+ case C(UR_GET_STATUS, UT_READ_INTERFACE):
+ case C(UR_GET_STATUS, UT_READ_ENDPOINT):
+ len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, 0);
+ break;
+ case C(UR_SET_ADDRESS, UT_WRITE_DEVICE):
+ if (value >= EHCI_MAX_DEVICES) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_addr = value;
+ break;
+ case C(UR_SET_CONFIG, UT_WRITE_DEVICE):
+ if ((value != 0) && (value != 1)) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_conf = value;
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_SET_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT):
+ err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
+ break;
+ case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT):
+ break;
+ /* Hub requests */
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER):
+ DPRINTFN(9, "UR_CLEAR_PORT_FEATURE\n");
+
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ port = EHCI_PORTSC(index);
+ v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR;
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ EOWRITE4(sc, port, v & ~EHCI_PS_PE);
+ break;
+ case UHF_PORT_SUSPEND:
+ if ((v & EHCI_PS_SUSP) && (!(v & EHCI_PS_FPR))) {
+ /*
+ * waking up a High Speed device is rather
+ * complicated if
+ */
+ EOWRITE4(sc, port, v | EHCI_PS_FPR);
+ }
+ /* wait 20ms for resume sequence to complete */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50);
+
+ EOWRITE4(sc, port, v & ~(EHCI_PS_SUSP |
+ EHCI_PS_FPR | (3 << 10) /* High Speed */ ));
+
+ /* 4ms settle time */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250);
+ break;
+ case UHF_PORT_POWER:
+ EOWRITE4(sc, port, v & ~EHCI_PS_PP);
+ break;
+ case UHF_PORT_TEST:
+ DPRINTFN(3, "clear port test "
+ "%d\n", index);
+ break;
+ case UHF_PORT_INDICATOR:
+ DPRINTFN(3, "clear port ind "
+ "%d\n", index);
+ EOWRITE4(sc, port, v & ~EHCI_PS_PIC);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ EOWRITE4(sc, port, v | EHCI_PS_CSC);
+ break;
+ case UHF_C_PORT_ENABLE:
+ EOWRITE4(sc, port, v | EHCI_PS_PEC);
+ break;
+ case UHF_C_PORT_SUSPEND:
+ EOWRITE4(sc, port, v | EHCI_PS_SUSP);
+ break;
+ case UHF_C_PORT_OVER_CURRENT:
+ EOWRITE4(sc, port, v | EHCI_PS_OCC);
+ break;
+ case UHF_C_PORT_RESET:
+ sc->sc_isreset = 0;
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE):
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ v = EREAD4(sc, EHCI_HCSPARAMS);
+
+ sc->sc_hub_desc.hubd = ehci_hubd;
+ sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport;
+
+ if (EHCI_HCS_PPC(v))
+ i = UHD_PWR_INDIVIDUAL;
+ else
+ i = UHD_PWR_NO_SWITCH;
+
+ if (EHCI_HCS_P_INDICATOR(v))
+ i |= UHD_PORT_IND;
+
+ USETW(sc->sc_hub_desc.hubd.wHubCharacteristics, i);
+ /* XXX can't find out? */
+ sc->sc_hub_desc.hubd.bPwrOn2PwrGood = 200;
+ /* XXX don't know if ports are removable or not */
+ sc->sc_hub_desc.hubd.bDescLength =
+ 8 + ((sc->sc_noport + 7) / 8);
+ len = sc->sc_hub_desc.hubd.bDescLength;
+ break;
+ case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE):
+ len = 16;
+ memset(sc->sc_hub_desc.temp, 0, 16);
+ break;
+ case C(UR_GET_STATUS, UT_READ_CLASS_OTHER):
+ DPRINTFN(9, "get port status i=%d\n",
+ index);
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ v = EOREAD4(sc, EHCI_PORTSC(index));
+ DPRINTFN(9, "port status=0x%04x\n", v);
+ if (sc->sc_flags & EHCI_SCFLG_TT) {
+ if (sc->sc_vendor_get_port_speed != NULL) {
+ i = sc->sc_vendor_get_port_speed(sc, index);
+ } else {
+ device_printf(sc->sc_bus.bdev,
+ "EHCI_SCFLG_TT quirk is set but "
+ "sc_vendor_get_hub_speed() is NULL\n");
+ i = UPS_HIGH_SPEED;
+ }
+ } else {
+ i = UPS_HIGH_SPEED;
+ }
+ if (v & EHCI_PS_CS)
+ i |= UPS_CURRENT_CONNECT_STATUS;
+ if (v & EHCI_PS_PE)
+ i |= UPS_PORT_ENABLED;
+ if ((v & EHCI_PS_SUSP) && !(v & EHCI_PS_FPR))
+ i |= UPS_SUSPEND;
+ if (v & EHCI_PS_OCA)
+ i |= UPS_OVERCURRENT_INDICATOR;
+ if (v & EHCI_PS_PR)
+ i |= UPS_RESET;
+ if (v & EHCI_PS_PP)
+ i |= UPS_PORT_POWER;
+ USETW(sc->sc_hub_desc.ps.wPortStatus, i);
+ i = 0;
+ if (v & EHCI_PS_CSC)
+ i |= UPS_C_CONNECT_STATUS;
+ if (v & EHCI_PS_PEC)
+ i |= UPS_C_PORT_ENABLED;
+ if (v & EHCI_PS_OCC)
+ i |= UPS_C_OVERCURRENT_INDICATOR;
+ if (v & EHCI_PS_FPR)
+ i |= UPS_C_SUSPEND;
+ if (sc->sc_isreset)
+ i |= UPS_C_PORT_RESET;
+ USETW(sc->sc_hub_desc.ps.wPortChange, i);
+ len = sizeof(sc->sc_hub_desc.ps);
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE):
+ err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER):
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ port = EHCI_PORTSC(index);
+ v = EOREAD4(sc, port) & ~EHCI_PS_CLEAR;
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ EOWRITE4(sc, port, v | EHCI_PS_PE);
+ break;
+ case UHF_PORT_SUSPEND:
+ EOWRITE4(sc, port, v | EHCI_PS_SUSP);
+ break;
+ case UHF_PORT_RESET:
+ DPRINTFN(6, "reset port %d\n", index);
+#ifdef USB_DEBUG
+ if (ehcinohighspeed) {
+ /*
+ * Connect USB device to companion
+ * controller.
+ */
+ ehci_disown(sc, index, 1);
+ break;
+ }
+#endif
+ if (EHCI_PS_IS_LOWSPEED(v) &&
+ (sc->sc_flags & EHCI_SCFLG_TT) == 0) {
+ /* Low speed device, give up ownership. */
+ ehci_disown(sc, index, 1);
+ break;
+ }
+ /* Start reset sequence. */
+ v &= ~(EHCI_PS_PE | EHCI_PS_PR);
+ EOWRITE4(sc, port, v | EHCI_PS_PR);
+
+ /* Wait for reset to complete. */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(usb_port_root_reset_delay));
+
+ /* Terminate reset sequence. */
+ if (!(sc->sc_flags & EHCI_SCFLG_NORESTERM))
+ EOWRITE4(sc, port, v);
+
+ /* Wait for HC to complete reset. */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(EHCI_PORT_RESET_COMPLETE));
+
+ v = EOREAD4(sc, port);
+ DPRINTF("ehci after reset, status=0x%08x\n", v);
+ if (v & EHCI_PS_PR) {
+ device_printf(sc->sc_bus.bdev,
+ "port reset timeout\n");
+ err = USB_ERR_TIMEOUT;
+ goto done;
+ }
+ if (!(v & EHCI_PS_PE) &&
+ (sc->sc_flags & EHCI_SCFLG_TT) == 0) {
+ /* Not a high speed device, give up ownership.*/
+ ehci_disown(sc, index, 0);
+ break;
+ }
+ sc->sc_isreset = 1;
+ DPRINTF("ehci port %d reset, status = 0x%08x\n",
+ index, v);
+ break;
+
+ case UHF_PORT_POWER:
+ DPRINTFN(3, "set port power %d\n", index);
+ EOWRITE4(sc, port, v | EHCI_PS_PP);
+ break;
+
+ case UHF_PORT_TEST:
+ DPRINTFN(3, "set port test %d\n", index);
+ break;
+
+ case UHF_PORT_INDICATOR:
+ DPRINTFN(3, "set port ind %d\n", index);
+ EOWRITE4(sc, port, v | EHCI_PS_PIC);
+ break;
+
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER):
+ case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER):
+ case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER):
+ case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER):
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+done:
+ *plength = len;
+ *pptr = ptr;
+ return (err);
+}
+
+static void
+ehci_xfer_setup(struct usb_setup_params *parm)
+{
+ struct usb_page_search page_info;
+ struct usb_page_cache *pc;
+ ehci_softc_t *sc;
+ struct usb_xfer *xfer;
+ void *last_obj;
+ uint32_t nqtd;
+ uint32_t nqh;
+ uint32_t nsitd;
+ uint32_t nitd;
+ uint32_t n;
+
+ sc = EHCI_BUS2SC(parm->udev->bus);
+ xfer = parm->curr_xfer;
+
+ nqtd = 0;
+ nqh = 0;
+ nsitd = 0;
+ nitd = 0;
+
+ /*
+ * compute maximum number of some structures
+ */
+ if (parm->methods == &ehci_device_ctrl_methods) {
+ /*
+ * The proof for the "nqtd" formula is illustrated like
+ * this:
+ *
+ * +------------------------------------+
+ * | |
+ * | |remainder -> |
+ * | +-----+---+ |
+ * | | xxx | x | frm 0 |
+ * | +-----+---++ |
+ * | | xxx | xx | frm 1 |
+ * | +-----+----+ |
+ * | ... |
+ * +------------------------------------+
+ *
+ * "xxx" means a completely full USB transfer descriptor
+ *
+ * "x" and "xx" means a short USB packet
+ *
+ * For the remainder of an USB transfer modulo
+ * "max_data_length" we need two USB transfer descriptors.
+ * One to transfer the remaining data and one to finalise
+ * with a zero length packet in case the "force_short_xfer"
+ * flag is set. We only need two USB transfer descriptors in
+ * the case where the transfer length of the first one is a
+ * factor of "max_frame_size". The rest of the needed USB
+ * transfer descriptors is given by the buffer size divided
+ * by the maximum data payload.
+ */
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX;
+ xfer->flags_int.bdma_enable = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ nqh = 1;
+ nqtd = ((2 * xfer->nframes) + 1 /* STATUS */
+ + (xfer->max_data_length / xfer->max_hc_frame_size));
+
+ } else if (parm->methods == &ehci_device_bulk_methods) {
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX;
+ xfer->flags_int.bdma_enable = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ nqh = 1;
+ nqtd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_hc_frame_size));
+
+ } else if (parm->methods == &ehci_device_intr_methods) {
+ if (parm->speed == USB_SPEED_HIGH) {
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 3;
+ } else if (parm->speed == USB_SPEED_FULL) {
+ parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME;
+ parm->hc_max_packet_count = 1;
+ } else {
+ parm->hc_max_packet_size = USB_FS_BYTES_PER_HS_UFRAME / 8;
+ parm->hc_max_packet_count = 1;
+ }
+
+ parm->hc_max_frame_size = EHCI_QTD_PAYLOAD_MAX;
+ xfer->flags_int.bdma_enable = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ nqh = 1;
+ nqtd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_hc_frame_size));
+
+ } else if (parm->methods == &ehci_device_isoc_fs_methods) {
+ parm->hc_max_packet_size = 0x3FF;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x3FF;
+ xfer->flags_int.bdma_enable = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ nsitd = xfer->nframes;
+
+ } else if (parm->methods == &ehci_device_isoc_hs_methods) {
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 3;
+ parm->hc_max_frame_size = 0xC00;
+ xfer->flags_int.bdma_enable = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ nitd = ((xfer->nframes + 7) / 8) <<
+ usbd_xfer_get_fps_shift(xfer);
+
+ } else {
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x400;
+
+ usbd_transfer_setup_sub(parm);
+ }
+
+alloc_dma_set:
+
+ if (parm->err) {
+ return;
+ }
+ /*
+ * Allocate queue heads and transfer descriptors
+ */
+ last_obj = NULL;
+
+ if (usbd_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ehci_itd_t),
+ EHCI_ITD_ALIGN, nitd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nitd; n++) {
+ ehci_itd_t *td;
+
+ usbd_get_page(pc + n, 0, &page_info);
+
+ td = page_info.buffer;
+
+ /* init TD */
+ td->itd_self = htohc32(sc, page_info.physaddr | EHCI_LINK_ITD);
+ td->obj_next = last_obj;
+ td->page_cache = pc + n;
+
+ last_obj = td;
+
+ usb_pc_cpu_flush(pc + n);
+ }
+ }
+ if (usbd_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ehci_sitd_t),
+ EHCI_SITD_ALIGN, nsitd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nsitd; n++) {
+ ehci_sitd_t *td;
+
+ usbd_get_page(pc + n, 0, &page_info);
+
+ td = page_info.buffer;
+
+ /* init TD */
+ td->sitd_self = htohc32(sc, page_info.physaddr | EHCI_LINK_SITD);
+ td->obj_next = last_obj;
+ td->page_cache = pc + n;
+
+ last_obj = td;
+
+ usb_pc_cpu_flush(pc + n);
+ }
+ }
+ if (usbd_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ehci_qtd_t),
+ EHCI_QTD_ALIGN, nqtd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nqtd; n++) {
+ ehci_qtd_t *qtd;
+
+ usbd_get_page(pc + n, 0, &page_info);
+
+ qtd = page_info.buffer;
+
+ /* init TD */
+ qtd->qtd_self = htohc32(sc, page_info.physaddr);
+ qtd->obj_next = last_obj;
+ qtd->page_cache = pc + n;
+
+ last_obj = qtd;
+
+ usb_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ last_obj = NULL;
+
+ if (usbd_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ehci_qh_t),
+ EHCI_QH_ALIGN, nqh)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nqh; n++) {
+ ehci_qh_t *qh;
+
+ usbd_get_page(pc + n, 0, &page_info);
+
+ qh = page_info.buffer;
+
+ /* init QH */
+ qh->qh_self = htohc32(sc, page_info.physaddr | EHCI_LINK_QH);
+ qh->obj_next = last_obj;
+ qh->page_cache = pc + n;
+
+ last_obj = qh;
+
+ usb_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ if (!xfer->flags_int.curr_dma_set) {
+ xfer->flags_int.curr_dma_set = 1;
+ goto alloc_dma_set;
+ }
+}
+
+static void
+ehci_xfer_unsetup(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+ehci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
+ struct usb_endpoint *ep)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ ep, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb_mode,
+ sc->sc_addr);
+
+ if (udev->device_index != sc->sc_addr) {
+ if ((udev->speed != USB_SPEED_HIGH) &&
+ ((udev->hs_hub_addr == 0) ||
+ (udev->hs_port_no == 0) ||
+ (udev->parent_hs_hub == NULL) ||
+ (udev->parent_hs_hub->hub == NULL))) {
+ /* We need a transaction translator */
+ goto done;
+ }
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ ep->methods = &ehci_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ ep->methods = &ehci_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ if (udev->speed == USB_SPEED_HIGH) {
+ ep->methods = &ehci_device_isoc_hs_methods;
+ } else if (udev->speed == USB_SPEED_FULL) {
+ ep->methods = &ehci_device_isoc_fs_methods;
+ }
+ break;
+ case UE_BULK:
+ ep->methods = &ehci_device_bulk_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+done:
+ return;
+}
+
+static void
+ehci_get_dma_delay(struct usb_device *udev, uint32_t *pus)
+{
+ /*
+ * Wait until the hardware has finished any possible use of
+ * the transfer descriptor(s) and QH
+ */
+ *pus = (1125); /* microseconds */
+}
+
+static void
+ehci_device_resume(struct usb_device *udev)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(udev->bus);
+ struct usb_xfer *xfer;
+ const struct usb_pipe_methods *methods;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (xfer->xroot->udev == udev) {
+ methods = xfer->endpoint->methods;
+
+ if ((methods == &ehci_device_bulk_methods) ||
+ (methods == &ehci_device_ctrl_methods)) {
+ EHCI_APPEND_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_async_p_last);
+ }
+ if (methods == &ehci_device_intr_methods) {
+ EHCI_APPEND_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ return;
+}
+
+static void
+ehci_device_suspend(struct usb_device *udev)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(udev->bus);
+ struct usb_xfer *xfer;
+ const struct usb_pipe_methods *methods;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (xfer->xroot->udev == udev) {
+ methods = xfer->endpoint->methods;
+
+ if ((methods == &ehci_device_bulk_methods) ||
+ (methods == &ehci_device_ctrl_methods)) {
+ EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_async_p_last);
+ }
+ if (methods == &ehci_device_intr_methods) {
+ EHCI_REMOVE_QH(xfer->qh_start[xfer->flags_int.curr_dma_set],
+ sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+}
+
+static void
+ehci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state)
+{
+ struct ehci_softc *sc = EHCI_BUS2SC(bus);
+
+ switch (state) {
+ case USB_HW_POWER_SUSPEND:
+ case USB_HW_POWER_SHUTDOWN:
+ ehci_suspend(sc);
+ break;
+ case USB_HW_POWER_RESUME:
+ ehci_resume(sc);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+ehci_set_hw_power(struct usb_bus *bus)
+{
+ ehci_softc_t *sc = EHCI_BUS2SC(bus);
+ uint32_t temp;
+ uint32_t flags;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(bus);
+
+ flags = bus->hw_power_state;
+
+ temp = EOREAD4(sc, EHCI_USBCMD);
+
+ temp &= ~(EHCI_CMD_ASE | EHCI_CMD_PSE);
+
+ if (flags & (USB_HW_POWER_CONTROL |
+ USB_HW_POWER_BULK)) {
+ DPRINTF("Async is active\n");
+ temp |= EHCI_CMD_ASE;
+ }
+ if (flags & (USB_HW_POWER_INTERRUPT |
+ USB_HW_POWER_ISOC)) {
+ DPRINTF("Periodic is active\n");
+ temp |= EHCI_CMD_PSE;
+ }
+ EOWRITE4(sc, EHCI_USBCMD, temp);
+
+ USB_BUS_UNLOCK(bus);
+
+ return;
+}
+
+static void
+ehci_start_dma_delay_second(struct usb_xfer *xfer)
+{
+ struct ehci_softc *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ DPRINTF("\n");
+
+ /* trigger doorbell */
+ ehci_doorbell_async(sc);
+
+ /* give the doorbell 4ms */
+ usbd_transfer_timeout_ms(xfer,
+ (void (*)(void *))&usb_dma_delay_done_cb, 4);
+}
+
+/*
+ * Ring the doorbell twice before freeing any DMA descriptors. Some host
+ * controllers apparently cache the QH descriptors and need a message
+ * that the cache needs to be discarded.
+ */
+static void
+ehci_start_dma_delay(struct usb_xfer *xfer)
+{
+ struct ehci_softc *sc = EHCI_BUS2SC(xfer->xroot->bus);
+
+ DPRINTF("\n");
+
+ /* trigger doorbell */
+ ehci_doorbell_async(sc);
+
+ /* give the doorbell 4ms */
+ usbd_transfer_timeout_ms(xfer,
+ (void (*)(void *))&ehci_start_dma_delay_second, 4);
+}
+
+static const struct usb_bus_methods ehci_bus_methods =
+{
+ .endpoint_init = ehci_ep_init,
+ .xfer_setup = ehci_xfer_setup,
+ .xfer_unsetup = ehci_xfer_unsetup,
+ .get_dma_delay = ehci_get_dma_delay,
+ .device_resume = ehci_device_resume,
+ .device_suspend = ehci_device_suspend,
+ .set_hw_power = ehci_set_hw_power,
+ .set_hw_power_sleep = ehci_set_hw_power_sleep,
+ .roothub_exec = ehci_roothub_exec,
+ .xfer_poll = ehci_do_poll,
+ .start_dma_delay = ehci_start_dma_delay,
+};
diff --git a/sys/dev/usb/controller/ehci.h b/sys/dev/usb/controller/ehci.h
new file mode 100644
index 000000000000..4576457712a3
--- /dev/null
+++ b/sys/dev/usb/controller/ehci.h
@@ -0,0 +1,453 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2001 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Lennart Augustsson (lennart@augustsson.net).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _EHCI_H_
+#define _EHCI_H_
+
+#define EHCI_MAX_DEVICES MIN(USB_MAX_DEVICES, 128)
+
+/*
+ * Alignment NOTE: structures must be aligned so that the hardware can index
+ * without performing addition.
+ */
+#define EHCI_FRAMELIST_ALIGN 0x1000 /* bytes */
+#define EHCI_FRAMELIST_COUNT 1024 /* units */
+#define EHCI_VIRTUAL_FRAMELIST_COUNT 128 /* units */
+
+#if ((8*EHCI_VIRTUAL_FRAMELIST_COUNT) < USB_MAX_HS_ISOC_FRAMES_PER_XFER)
+#error "maximum number of high-speed isochronous frames is higher than supported!"
+#endif
+
+#if (EHCI_VIRTUAL_FRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER)
+#error "maximum number of full-speed isochronous frames is higher than supported!"
+#endif
+
+/* Link types */
+#define EHCI_LINK_TERMINATE 0x00000001
+#define EHCI_LINK_TYPE(x) ((x) & 0x00000006)
+#define EHCI_LINK_ITD 0x0
+#define EHCI_LINK_QH 0x2
+#define EHCI_LINK_SITD 0x4
+#define EHCI_LINK_FSTN 0x6
+#define EHCI_LINK_ADDR(x) ((x) &~ 0x1f)
+
+/* Structures alignment (bytes) */
+#define EHCI_ITD_ALIGN 128
+#define EHCI_SITD_ALIGN 64
+#define EHCI_QTD_ALIGN 64
+#define EHCI_QH_ALIGN 128
+#define EHCI_FSTN_ALIGN 32
+/* Data buffers are divided into one or more pages */
+#define EHCI_PAGE_SIZE 0x1000
+#if ((USB_PAGE_SIZE < EHCI_PAGE_SIZE) || (EHCI_PAGE_SIZE == 0) || \
+ (USB_PAGE_SIZE < EHCI_ITD_ALIGN) || (EHCI_ITD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < EHCI_SITD_ALIGN) || (EHCI_SITD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < EHCI_QTD_ALIGN) || (EHCI_QTD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < EHCI_QH_ALIGN) || (EHCI_QH_ALIGN == 0) || \
+ (USB_PAGE_SIZE < EHCI_FSTN_ALIGN) || (EHCI_FSTN_ALIGN == 0))
+#error "Invalid USB page size!"
+#endif
+
+/*
+ * Isochronous Transfer Descriptor. This descriptor is used for high speed
+ * transfers only.
+ */
+struct ehci_itd {
+ volatile uint32_t itd_next;
+ volatile uint32_t itd_status[8];
+#define EHCI_ITD_SET_LEN(x) ((x) << 16)
+#define EHCI_ITD_GET_LEN(x) (((x) >> 16) & 0xFFF)
+#define EHCI_ITD_IOC (1 << 15)
+#define EHCI_ITD_SET_PG(x) ((x) << 12)
+#define EHCI_ITD_GET_PG(x) (((x) >> 12) & 0x7)
+#define EHCI_ITD_SET_OFFS(x) (x)
+#define EHCI_ITD_GET_OFFS(x) (((x) >> 0) & 0xFFF)
+#define EHCI_ITD_ACTIVE (1U << 31)
+#define EHCI_ITD_DATABUFERR (1 << 30)
+#define EHCI_ITD_BABBLE (1 << 29)
+#define EHCI_ITD_XACTERR (1 << 28)
+ volatile uint32_t itd_bp[7];
+ /* itd_bp[0] */
+#define EHCI_ITD_SET_ADDR(x) (x)
+#define EHCI_ITD_GET_ADDR(x) (((x) >> 0) & 0x7F)
+#define EHCI_ITD_SET_ENDPT(x) ((x) << 8)
+#define EHCI_ITD_GET_ENDPT(x) (((x) >> 8) & 0xF)
+ /* itd_bp[1] */
+#define EHCI_ITD_SET_DIR_IN (1 << 11)
+#define EHCI_ITD_SET_DIR_OUT (0 << 11)
+#define EHCI_ITD_SET_MPL(x) (x)
+#define EHCI_ITD_GET_MPL(x) (((x) >> 0) & 0x7FF)
+ volatile uint32_t itd_bp_hi[7];
+/*
+ * Extra information needed:
+ */
+ uint32_t itd_self;
+ struct ehci_itd *next;
+ struct ehci_itd *prev;
+ struct ehci_itd *obj_next;
+ struct usb_page_cache *page_cache;
+} __aligned(EHCI_ITD_ALIGN);
+
+typedef struct ehci_itd ehci_itd_t;
+
+/*
+ * Split Transaction Isochronous Transfer Descriptor. This descriptor is used
+ * for full speed transfers only.
+ */
+struct ehci_sitd {
+ volatile uint32_t sitd_next;
+ volatile uint32_t sitd_portaddr;
+#define EHCI_SITD_SET_DIR_OUT (0 << 31)
+#define EHCI_SITD_SET_DIR_IN (1U << 31)
+#define EHCI_SITD_SET_ADDR(x) (x)
+#define EHCI_SITD_GET_ADDR(x) ((x) & 0x7F)
+#define EHCI_SITD_SET_ENDPT(x) ((x) << 8)
+#define EHCI_SITD_GET_ENDPT(x) (((x) >> 8) & 0xF)
+#define EHCI_SITD_GET_DIR(x) ((x) >> 31)
+#define EHCI_SITD_SET_PORT(x) ((x) << 24)
+#define EHCI_SITD_GET_PORT(x) (((x) >> 24) & 0x7F)
+#define EHCI_SITD_SET_HUBA(x) ((x) << 16)
+#define EHCI_SITD_GET_HUBA(x) (((x) >> 16) & 0x7F)
+ volatile uint32_t sitd_mask;
+#define EHCI_SITD_SET_SMASK(x) (x)
+#define EHCI_SITD_SET_CMASK(x) ((x) << 8)
+ volatile uint32_t sitd_status;
+#define EHCI_SITD_COMPLETE_SPLIT (1<<1)
+#define EHCI_SITD_START_SPLIT (0<<1)
+#define EHCI_SITD_MISSED_MICRO_FRAME (1<<2)
+#define EHCI_SITD_XACTERR (1<<3)
+#define EHCI_SITD_BABBLE (1<<4)
+#define EHCI_SITD_DATABUFERR (1<<5)
+#define EHCI_SITD_ERROR (1<<6)
+#define EHCI_SITD_ACTIVE (1<<7)
+#define EHCI_SITD_IOC (1<<31)
+#define EHCI_SITD_SET_LEN(len) ((len)<<16)
+#define EHCI_SITD_GET_LEN(x) (((x)>>16) & 0x3FF)
+ volatile uint32_t sitd_bp[2];
+ volatile uint32_t sitd_back;
+ volatile uint32_t sitd_bp_hi[2];
+/*
+ * Extra information needed:
+ */
+ uint32_t sitd_self;
+ struct ehci_sitd *next;
+ struct ehci_sitd *prev;
+ struct ehci_sitd *obj_next;
+ struct usb_page_cache *page_cache;
+} __aligned(EHCI_SITD_ALIGN);
+
+typedef struct ehci_sitd ehci_sitd_t;
+
+/* Queue Element Transfer Descriptor */
+struct ehci_qtd {
+ volatile uint32_t qtd_next;
+ volatile uint32_t qtd_altnext;
+ volatile uint32_t qtd_status;
+#define EHCI_QTD_GET_STATUS(x) (((x) >> 0) & 0xff)
+#define EHCI_QTD_SET_STATUS(x) ((x) << 0)
+#define EHCI_QTD_ACTIVE 0x80
+#define EHCI_QTD_HALTED 0x40
+#define EHCI_QTD_BUFERR 0x20
+#define EHCI_QTD_BABBLE 0x10
+#define EHCI_QTD_XACTERR 0x08
+#define EHCI_QTD_MISSEDMICRO 0x04
+#define EHCI_QTD_SPLITXSTATE 0x02
+#define EHCI_QTD_PINGSTATE 0x01
+#define EHCI_QTD_STATERRS 0x74
+#define EHCI_QTD_GET_PID(x) (((x) >> 8) & 0x3)
+#define EHCI_QTD_SET_PID(x) ((x) << 8)
+#define EHCI_QTD_PID_OUT 0x0
+#define EHCI_QTD_PID_IN 0x1
+#define EHCI_QTD_PID_SETUP 0x2
+#define EHCI_QTD_GET_CERR(x) (((x) >> 10) & 0x3)
+#define EHCI_QTD_SET_CERR(x) ((x) << 10)
+#define EHCI_QTD_GET_C_PAGE(x) (((x) >> 12) & 0x7)
+#define EHCI_QTD_SET_C_PAGE(x) ((x) << 12)
+#define EHCI_QTD_GET_IOC(x) (((x) >> 15) & 0x1)
+#define EHCI_QTD_IOC 0x00008000
+#define EHCI_QTD_GET_BYTES(x) (((x) >> 16) & 0x7fff)
+#define EHCI_QTD_SET_BYTES(x) ((x) << 16)
+#define EHCI_QTD_GET_TOGGLE(x) (((x) >> 31) & 0x1)
+#define EHCI_QTD_SET_TOGGLE(x) ((x) << 31)
+#define EHCI_QTD_TOGGLE_MASK 0x80000000
+#define EHCI_QTD_NBUFFERS 5
+#define EHCI_QTD_PAYLOAD_MAX ((EHCI_QTD_NBUFFERS-1)*EHCI_PAGE_SIZE)
+ volatile uint32_t qtd_buffer[EHCI_QTD_NBUFFERS];
+ volatile uint32_t qtd_buffer_hi[EHCI_QTD_NBUFFERS];
+/*
+ * Extra information needed:
+ */
+ struct ehci_qtd *alt_next;
+ struct ehci_qtd *obj_next;
+ struct usb_page_cache *page_cache;
+ uint32_t qtd_self;
+ uint16_t len;
+} __aligned(EHCI_QTD_ALIGN);
+
+typedef struct ehci_qtd ehci_qtd_t;
+
+/* Queue Head Sub Structure */
+struct ehci_qh_sub {
+ volatile uint32_t qtd_next;
+ volatile uint32_t qtd_altnext;
+ volatile uint32_t qtd_status;
+ volatile uint32_t qtd_buffer[EHCI_QTD_NBUFFERS];
+ volatile uint32_t qtd_buffer_hi[EHCI_QTD_NBUFFERS];
+} __aligned(4);
+
+/* Queue Head */
+struct ehci_qh {
+ volatile uint32_t qh_link;
+ volatile uint32_t qh_endp;
+#define EHCI_QH_GET_ADDR(x) (((x) >> 0) & 0x7f) /* endpoint addr */
+#define EHCI_QH_SET_ADDR(x) (x)
+#define EHCI_QH_ADDRMASK 0x0000007f
+#define EHCI_QH_GET_INACT(x) (((x) >> 7) & 0x01) /* inactivate on next */
+#define EHCI_QH_INACT 0x00000080
+#define EHCI_QH_GET_ENDPT(x) (((x) >> 8) & 0x0f) /* endpoint no */
+#define EHCI_QH_SET_ENDPT(x) ((x) << 8)
+#define EHCI_QH_GET_EPS(x) (((x) >> 12) & 0x03) /* endpoint speed */
+#define EHCI_QH_SET_EPS(x) ((x) << 12)
+#define EHCI_QH_SPEED_FULL 0x0
+#define EHCI_QH_SPEED_LOW 0x1
+#define EHCI_QH_SPEED_HIGH 0x2
+#define EHCI_QH_GET_DTC(x) (((x) >> 14) & 0x01) /* data toggle control */
+#define EHCI_QH_DTC 0x00004000
+#define EHCI_QH_GET_HRECL(x) (((x) >> 15) & 0x01) /* head of reclamation */
+#define EHCI_QH_HRECL 0x00008000
+#define EHCI_QH_GET_MPL(x) (((x) >> 16) & 0x7ff) /* max packet len */
+#define EHCI_QH_SET_MPL(x) ((x) << 16)
+#define EHCI_QH_MPLMASK 0x07ff0000
+#define EHCI_QH_GET_CTL(x) (((x) >> 27) & 0x01) /* control endpoint */
+#define EHCI_QH_CTL 0x08000000
+#define EHCI_QH_GET_NRL(x) (((x) >> 28) & 0x0f) /* NAK reload */
+#define EHCI_QH_SET_NRL(x) ((x) << 28)
+ volatile uint32_t qh_endphub;
+#define EHCI_QH_GET_SMASK(x) (((x) >> 0) & 0xff) /* intr sched mask */
+#define EHCI_QH_SET_SMASK(x) ((x) << 0)
+#define EHCI_QH_GET_CMASK(x) (((x) >> 8) & 0xff) /* split completion mask */
+#define EHCI_QH_SET_CMASK(x) ((x) << 8)
+#define EHCI_QH_GET_HUBA(x) (((x) >> 16) & 0x7f) /* hub address */
+#define EHCI_QH_SET_HUBA(x) ((x) << 16)
+#define EHCI_QH_GET_PORT(x) (((x) >> 23) & 0x7f) /* hub port */
+#define EHCI_QH_SET_PORT(x) ((x) << 23)
+#define EHCI_QH_GET_MULT(x) (((x) >> 30) & 0x03) /* pipe multiplier */
+#define EHCI_QH_SET_MULT(x) ((x) << 30)
+ volatile uint32_t qh_curqtd;
+ struct ehci_qh_sub qh_qtd;
+/*
+ * Extra information needed:
+ */
+ struct ehci_qh *next;
+ struct ehci_qh *prev;
+ struct ehci_qh *obj_next;
+ struct usb_page_cache *page_cache;
+ uint32_t qh_self;
+} __aligned(EHCI_QH_ALIGN);
+
+typedef struct ehci_qh ehci_qh_t;
+
+/* Periodic Frame Span Traversal Node */
+struct ehci_fstn {
+ volatile uint32_t fstn_link;
+ volatile uint32_t fstn_back;
+} __aligned(EHCI_FSTN_ALIGN);
+
+typedef struct ehci_fstn ehci_fstn_t;
+
+struct ehci_hw_softc {
+ struct usb_page_cache pframes_pc;
+ struct usb_page_cache terminate_pc;
+ struct usb_page_cache async_start_pc;
+ struct usb_page_cache intr_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct usb_page_cache isoc_hs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct usb_page_cache isoc_fs_start_pc[EHCI_VIRTUAL_FRAMELIST_COUNT];
+
+ struct usb_page pframes_pg;
+ struct usb_page terminate_pg;
+ struct usb_page async_start_pg;
+ struct usb_page intr_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct usb_page isoc_hs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct usb_page isoc_fs_start_pg[EHCI_VIRTUAL_FRAMELIST_COUNT];
+};
+
+struct ehci_config_desc {
+ struct usb_config_descriptor confd;
+ struct usb_interface_descriptor ifcd;
+ struct usb_endpoint_descriptor endpd;
+} __packed;
+
+union ehci_hub_desc {
+ struct usb_status stat;
+ struct usb_port_status ps;
+ struct usb_hub_descriptor hubd;
+ uint8_t temp[128];
+};
+
+typedef struct ehci_softc {
+ struct ehci_hw_softc sc_hw;
+ struct usb_bus sc_bus; /* base device */
+ struct usb_callout sc_tmo_pcd;
+ struct usb_callout sc_tmo_poll;
+ union ehci_hub_desc sc_hub_desc;
+
+ struct usb_device *sc_devices[EHCI_MAX_DEVICES];
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ struct ehci_qh *sc_async_p_last;
+ struct ehci_qh *sc_intr_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct ehci_sitd *sc_isoc_fs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ struct ehci_itd *sc_isoc_hs_p_last[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ void *sc_intr_hdl;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ uint32_t sc_terminate_self; /* TD short packet termination pointer */
+ uint32_t sc_eintrs;
+
+ uint16_t sc_intr_stat[EHCI_VIRTUAL_FRAMELIST_COUNT];
+ uint16_t sc_id_vendor; /* vendor ID for root hub */
+ uint16_t sc_flags; /* chip specific flags */
+#define EHCI_SCFLG_NORESTERM 0x0004 /* don't terminate reset sequence */
+#define EHCI_SCFLG_BIGEDESC 0x0008 /* big-endian byte order descriptors */
+#define EHCI_SCFLG_TT 0x0020 /* transaction translator present */
+#define EHCI_SCFLG_LOSTINTRBUG 0x0040 /* workaround for VIA / ATI chipsets */
+#define EHCI_SCFLG_IAADBUG 0x0080 /* workaround for nVidia chipsets */
+#define EHCI_SCFLG_DONTRESET 0x0100 /* don't reset ctrl. in ehci_init() */
+#define EHCI_SCFLG_DONEINIT 0x1000 /* ehci_init() has been called. */
+
+ uint8_t sc_offs; /* offset to operational registers */
+ uint8_t sc_doorbell_disable; /* set on doorbell failure */
+ uint8_t sc_noport;
+ uint8_t sc_addr; /* device address */
+ uint8_t sc_conf; /* device configuration */
+ uint8_t sc_isreset;
+ uint8_t sc_hub_idata[8];
+
+ char sc_vendor[16]; /* vendor string for root hub */
+
+ void (*sc_vendor_post_reset)(struct ehci_softc *sc);
+ uint16_t (*sc_vendor_get_port_speed)(struct ehci_softc *sc,
+ uint16_t index);
+
+} ehci_softc_t;
+
+#define EREAD1(sc, a) bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (a))
+#define EREAD2(sc, a) bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a))
+#define EREAD4(sc, a) bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a))
+#define EWRITE1(sc, a, x) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x))
+#define EWRITE2(sc, a, x) \
+ bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x))
+#define EWRITE4(sc, a, x) \
+ bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (a), (x))
+#define EOREAD1(sc, a) \
+ bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a))
+#define EOREAD2(sc, a) \
+ bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a))
+#define EOREAD4(sc, a) \
+ bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a))
+#define EOWRITE1(sc, a, x) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x))
+#define EOWRITE2(sc, a, x) \
+ bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x))
+#define EOWRITE4(sc, a, x) \
+ bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (sc)->sc_offs+(a), (x))
+
+#ifdef USB_EHCI_BIG_ENDIAN_DESC
+/*
+ * Handle byte order conversion between host and ``host controller''.
+ * Typically the latter is little-endian but some controllers require
+ * big-endian in which case we may need to manually swap.
+ */
+static __inline uint32_t
+htohc32(const struct ehci_softc *sc, const uint32_t v)
+{
+ return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? htobe32(v) : htole32(v);
+}
+
+static __inline uint16_t
+htohc16(const struct ehci_softc *sc, const uint16_t v)
+{
+ return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? htobe16(v) : htole16(v);
+}
+
+static __inline uint32_t
+hc32toh(const struct ehci_softc *sc, const uint32_t v)
+{
+ return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? be32toh(v) : le32toh(v);
+}
+
+static __inline uint16_t
+hc16toh(const struct ehci_softc *sc, const uint16_t v)
+{
+ return sc->sc_flags & EHCI_SCFLG_BIGEDESC ? be16toh(v) : le16toh(v);
+}
+#else
+/*
+ * Normal little-endian only conversion routines.
+ */
+static __inline uint32_t
+htohc32(const struct ehci_softc *sc, const uint32_t v)
+{
+ return htole32(v);
+}
+
+static __inline uint16_t
+htohc16(const struct ehci_softc *sc, const uint16_t v)
+{
+ return htole16(v);
+}
+
+static __inline uint32_t
+hc32toh(const struct ehci_softc *sc, const uint32_t v)
+{
+ return le32toh(v);
+}
+
+static __inline uint16_t
+hc16toh(const struct ehci_softc *sc, const uint16_t v)
+{
+ return le16toh(v);
+}
+#endif
+
+usb_bus_mem_cb_t ehci_iterate_hw_softc;
+
+usb_error_t ehci_reset(ehci_softc_t *sc);
+usb_error_t ehci_init(ehci_softc_t *sc);
+void ehci_detach(struct ehci_softc *sc);
+void ehci_interrupt(ehci_softc_t *sc);
+uint16_t ehci_get_port_speed_portsc(struct ehci_softc *sc, uint16_t index);
+uint16_t ehci_get_port_speed_hostc(struct ehci_softc *sc, uint16_t index);
+
+#endif /* _EHCI_H_ */
diff --git a/sys/dev/usb/controller/ehci_fsl.c b/sys/dev/usb/controller/ehci_fsl.c
new file mode 100644
index 000000000000..ce1749775ab2
--- /dev/null
+++ b/sys/dev/usb/controller/ehci_fsl.c
@@ -0,0 +1,418 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010-2012 Semihalf
+ * 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 "opt_bus.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/queue.h>
+#include <sys/lock.h>
+#include <sys/lockmgr.h>
+#include <sys/condvar.h>
+#include <sys/rman.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ehci.h>
+#include <dev/usb/controller/ehcireg.h>
+
+#include <machine/bus.h>
+#include <machine/clock.h>
+#include <machine/resource.h>
+
+#include <powerpc/include/tlb.h>
+
+#include "opt_platform.h"
+
+/*
+ * Register the driver
+ */
+/* Forward declarations */
+static int fsl_ehci_attach(device_t self);
+static int fsl_ehci_detach(device_t self);
+static int fsl_ehci_probe(device_t self);
+
+static device_method_t ehci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, fsl_ehci_probe),
+ DEVMETHOD(device_attach, fsl_ehci_attach),
+ DEVMETHOD(device_detach, fsl_ehci_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+ { 0, 0 }
+};
+
+/* kobj_class definition */
+static driver_t ehci_driver = {
+ "ehci",
+ ehci_methods,
+ sizeof(struct ehci_softc)
+};
+
+DRIVER_MODULE(ehci, simplebus, ehci_driver, 0, 0);
+MODULE_DEPEND(ehci, usb, 1, 1, 1);
+
+/*
+ * Private defines
+ */
+#define FSL_EHCI_REG_OFF 0x100
+#define FSL_EHCI_REG_SIZE 0x300
+
+/*
+ * Internal interface registers' offsets.
+ * Offsets from 0x000 ehci dev space, big-endian access.
+ */
+enum internal_reg {
+ SNOOP1 = 0x400,
+ SNOOP2 = 0x404,
+ AGE_CNT_THRESH = 0x408,
+ SI_CTRL = 0x410,
+ CONTROL = 0x500
+};
+
+/* CONTROL register bit flags */
+enum control_flags {
+ USB_EN = 0x00000004,
+ UTMI_PHY_EN = 0x00000200,
+ ULPI_INT_EN = 0x00000001
+};
+
+/* SI_CTRL register bit flags */
+enum si_ctrl_flags {
+ FETCH_32 = 1,
+ FETCH_64 = 0
+};
+
+#define SNOOP_RANGE_2GB 0x1E
+
+/*
+ * Operational registers' offsets.
+ * Offsets from USBCMD register, little-endian access.
+ */
+enum special_op_reg {
+ USBMODE = 0x0A8,
+ PORTSC = 0x084,
+ ULPI_VIEWPORT = 0x70
+};
+
+/* USBMODE register bit flags */
+enum usbmode_flags {
+ HOST_MODE = 0x3,
+ DEVICE_MODE = 0x2
+};
+
+#define PORT_POWER_MASK 0x00001000
+
+/*
+ * Private methods
+ */
+
+static void
+set_to_host_mode(ehci_softc_t *sc)
+{
+ int tmp;
+
+ tmp = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl, USBMODE);
+ bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, USBMODE, tmp | HOST_MODE);
+}
+
+static void
+enable_usb(device_t dev, bus_space_tag_t iot, bus_space_handle_t ioh)
+{
+ int tmp;
+ phandle_t node;
+ char *phy_type;
+
+ phy_type = NULL;
+ tmp = bus_space_read_4(iot, ioh, CONTROL) | USB_EN;
+
+ node = ofw_bus_get_node(dev);
+ if ((node != 0) &&
+ (OF_getprop_alloc(node, "phy_type", (void **)&phy_type) > 0)) {
+ if (strncasecmp(phy_type, "utmi", strlen("utmi")) == 0)
+ tmp |= UTMI_PHY_EN;
+ OF_prop_free(phy_type);
+ }
+ bus_space_write_4(iot, ioh, CONTROL, tmp);
+}
+
+static void
+set_32b_prefetch(bus_space_tag_t iot, bus_space_handle_t ioh)
+{
+
+ bus_space_write_4(iot, ioh, SI_CTRL, FETCH_32);
+}
+
+static void
+set_snooping(bus_space_tag_t iot, bus_space_handle_t ioh)
+{
+
+ bus_space_write_4(iot, ioh, SNOOP1, SNOOP_RANGE_2GB);
+ bus_space_write_4(iot, ioh, SNOOP2, 0x80000000 | SNOOP_RANGE_2GB);
+}
+
+static void
+clear_port_power(ehci_softc_t *sc)
+{
+ int tmp;
+
+ tmp = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl, PORTSC);
+ bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, PORTSC, tmp & ~PORT_POWER_MASK);
+}
+
+/*
+ * Public methods
+ */
+static int
+fsl_ehci_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (((ofw_bus_is_compatible(dev, "fsl-usb2-dr")) == 0) &&
+ ((ofw_bus_is_compatible(dev, "fsl-usb2-mph")) == 0))
+ return (ENXIO);
+
+ device_set_desc(dev, "Freescale integrated EHCI controller");
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+fsl_ehci_attach(device_t self)
+{
+ ehci_softc_t *sc;
+ int rid;
+ int err;
+ bus_space_handle_t ioh;
+ bus_space_tag_t iot;
+
+ sc = device_get_softc(self);
+ rid = 0;
+
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
+ sc->sc_bus.dma_bits = 32;
+
+ if (usb_bus_mem_alloc_all(&sc->sc_bus,
+ USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc))
+ return (ENOMEM);
+
+ /* Allocate io resource for EHCI */
+ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->sc_io_res == NULL) {
+ err = fsl_ehci_detach(self);
+ if (err) {
+ device_printf(self,
+ "Detach of the driver failed with error %d\n",
+ err);
+ }
+ return (ENXIO);
+ }
+ iot = rman_get_bustag(sc->sc_io_res);
+
+ /*
+ * Set handle to USB related registers subregion used by generic
+ * EHCI driver
+ */
+ ioh = rman_get_bushandle(sc->sc_io_res);
+
+ err = bus_space_subregion(iot, ioh, FSL_EHCI_REG_OFF, FSL_EHCI_REG_SIZE,
+ &sc->sc_io_hdl);
+ if (err != 0) {
+ err = fsl_ehci_detach(self);
+ if (err) {
+ device_printf(self,
+ "Detach of the driver failed with error %d\n",
+ err);
+ }
+ return (ENXIO);
+ }
+
+ /* Set little-endian tag for use by the generic EHCI driver */
+ sc->sc_io_tag = &bs_le_tag;
+
+ /* Allocate irq */
+ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid,
+ RF_ACTIVE);
+ if (sc->sc_irq_res == NULL) {
+ err = fsl_ehci_detach(self);
+ if (err) {
+ device_printf(self,
+ "Detach of the driver failed with error %d\n",
+ err);
+ }
+ return (ENXIO);
+ }
+
+ /* Setup interrupt handler */
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl);
+ if (err) {
+ device_printf(self, "Could not setup irq, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ err = fsl_ehci_detach(self);
+ if (err) {
+ device_printf(self,
+ "Detach of the driver failed with error %d\n",
+ err);
+ }
+ return (ENXIO);
+ }
+
+ /* Add USB device */
+ sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY);
+ if (!sc->sc_bus.bdev) {
+ device_printf(self, "Could not add USB device\n");
+ err = fsl_ehci_detach(self);
+ if (err) {
+ device_printf(self,
+ "Detach of the driver failed with error %d\n",
+ err);
+ }
+ return (ENOMEM);
+ }
+ device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
+
+ sc->sc_id_vendor = 0x1234;
+ strlcpy(sc->sc_vendor, "Freescale", sizeof(sc->sc_vendor));
+
+ /* Enable USB */
+ err = ehci_reset(sc);
+ if (err) {
+ device_printf(self, "Could not reset the controller\n");
+ err = fsl_ehci_detach(self);
+ if (err) {
+ device_printf(self,
+ "Detach of the driver failed with error %d\n",
+ err);
+ }
+ return (ENXIO);
+ }
+
+ enable_usb(self, iot, ioh);
+ set_snooping(iot, ioh);
+ set_to_host_mode(sc);
+ set_32b_prefetch(iot, ioh);
+
+ /*
+ * If usb subsystem is enabled in U-Boot, port power has to be turned
+ * off to allow proper discovery of devices during boot up.
+ */
+ clear_port_power(sc);
+
+ /* Set flags */
+ sc->sc_flags |= EHCI_SCFLG_DONTRESET | 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);
+ err = fsl_ehci_detach(self);
+ if (err) {
+ device_printf(self,
+ "Detach of the driver failed with error %d\n",
+ err);
+ }
+ return (EIO);
+ }
+
+ return (0);
+}
+
+static int
+fsl_ehci_detach(device_t self)
+{
+ int err;
+ ehci_softc_t *sc;
+
+ /* During module unload there are lots of children leftover */
+ err = bus_generic_detach(self);
+ if (err != 0)
+ return (err);
+
+ sc = device_get_softc(self);
+ /*
+ * only call ehci_detach() after ehci_init()
+ */
+ if (sc->sc_flags & EHCI_SCFLG_DONEINIT) {
+ ehci_detach(sc);
+ sc->sc_flags &= ~EHCI_SCFLG_DONEINIT;
+ }
+
+ /* Disable interrupts that might have been switched on in ehci_init */
+ if (sc->sc_io_tag && sc->sc_io_hdl)
+ bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl, EHCI_USBINTR, 0);
+
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl);
+ if (err) {
+ device_printf(self, "Could not tear down irq, %d\n",
+ err);
+ return (err);
+ }
+ sc->sc_intr_hdl = NULL;
+ }
+
+ if (sc->sc_irq_res) {
+ bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ }
+
+ if (sc->sc_io_res) {
+ bus_release_resource(self, SYS_RES_MEMORY, 0, sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ sc->sc_io_tag = 0;
+ sc->sc_io_hdl = 0;
+ }
+
+ return (0);
+}
diff --git a/sys/dev/usb/controller/ehci_imx.c b/sys/dev/usb/controller/ehci_imx.c
new file mode 100644
index 000000000000..149b26f04760
--- /dev/null
+++ b/sys/dev/usb/controller/ehci_imx.c
@@ -0,0 +1,506 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010-2012 Semihalf
+ * Copyright (c) 2012 The FreeBSD Foundation
+ * Copyright (c) 2013 Ian Lepore <ian@freebsd.org>
+ * All rights reserved.
+ *
+ * Portions of this software were developed by Oleksandr Rybalko
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * 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.
+ */
+
+/*
+ * EHCI driver for Freescale i.MX SoCs which incorporate the USBOH3 controller.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/condvar.h>
+#include <sys/rman.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ehci.h>
+#include <dev/usb/controller/ehcireg.h>
+#include "usbdevs.h"
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+
+#include <arm/freescale/imx/imx_ccmvar.h>
+
+#include "opt_platform.h"
+
+/*
+ * Notes on the hardware and related FDT data seen in the wild.
+ *
+ * There are two sets of registers in the USBOH3 implementation; documentation
+ * refers to them as "core" and "non-core" registers. A set of core register
+ * exists for each OTG or EHCI device. There is a single set of non-core
+ * registers per USBOH3, and they control aspects of operation not directly
+ * related to the USB specs, such as whether interrupts from each of the core
+ * devices are able to generate a SoC wakeup event.
+ *
+ * In the FreeBSD universe we might be inclined to describe the core and
+ * non-core registers by using a pair of resource address/size values (two
+ * entries in the reg property for each core). However, we have to work with
+ * existing FDT data (which mostly comes from the linux universe), and the way
+ * they've chosen to represent this is with an entry for a "usbmisc" device
+ * whose reg property describes the non-core registers. The way we handle FDT
+ * data, this means that the resources (memory-mapped register range) for the
+ * non-core registers belongs to a device other than the echi devices.
+ *
+ * Because the main ehci device cannot access registers in a range that's
+ * defined in the fdt data as belonging to another device, we implement a teeny
+ * little "usbmisc" driver which exists only to provide access to the usbmisc
+ * control register for each of the 4 usb controller instances. That little
+ * driver is implemented here in this file, before the main driver.
+ *
+ * In addition to the single usbmisc device, the existing FDT data defines a
+ * separate device for each of the OTG or EHCI cores within the USBOH3. Each of
+ * those devices has a set of core registers described by the reg property.
+ *
+ * The core registers for each of the four cores in the USBOH3 are divided into
+ * two parts: a set of imx-specific registers at an offset of 0 from the
+ * beginning of the register range, and the standard USB (EHCI or OTG) registers
+ * at an offset of 0x100 from the beginning of the register range. The FreeBSD
+ * way of dealing with this might be to map out two ranges in the reg property,
+ * but that's not what the alternate universe has done. To work with existing
+ * FDT data, we acquire the resource that maps all the core registers, then use
+ * bus_space_subregion() to create another resource that maps just the standard
+ * USB registers, which we provide to the standard USB code in the ehci_softc.
+ *
+ * The following compat strings have been seen for the OTG and EHCI cores. The
+ * FDT compat table in this driver contains all these strings, but as of this
+ * writing, not all of these SoCs have been tested with the driver. The fact
+ * that imx27 is common to all of them gives some hope that the driver will work
+ * on all these SoCs.
+ * - "fsl,imx23-usb", "fsl,imx27-usb";
+ * - "fsl,imx25-usb", "fsl,imx27-usb";
+ * - "fsl,imx28-usb", "fsl,imx27-usb";
+ * - "fsl,imx51-usb", "fsl,imx27-usb";
+ * - "fsl,imx53-usb", "fsl,imx27-usb";
+ * - "fsl,imx6q-usb", "fsl,imx27-usb";
+ *
+ * The FDT data for some SoCs contains the following properties, which we don't
+ * currently do anything with:
+ * - fsl,usbmisc = <&usbmisc 0>;
+ * - fsl,usbphy = <&usbphy0>;
+ *
+ * Some imx SoCs have FDT data related to USB PHY, some don't. We have separate
+ * usbphy drivers where needed; this data is mentioned here just to keep all the
+ * imx-FDT-usb-related info in one place. Here are the usbphy compat strings
+ * known to exist:
+ * - "nop-usbphy"
+ * - "usb-nop-xceiv";
+ * - "fsl,imx23-usbphy"
+ * - "fsl,imx28-usbphy", "fsl,imx23-usbphy";
+ * - "fsl,imx6q-usbphy", "fsl,imx23-usbphy";
+ *
+ */
+
+/*-----------------------------------------------------------------------------
+ * imx_usbmisc driver
+ *---------------------------------------------------------------------------*/
+
+#define USBNC_OVER_CUR_POL (1u << 8)
+#define USBNC_OVER_CUR_DIS (1u << 7)
+
+struct imx_usbmisc_softc {
+ device_t dev;
+ struct resource *mmio;
+};
+
+static struct ofw_compat_data usbmisc_compat_data[] = {
+ {"fsl,imx6q-usbmisc", true},
+ {"fsl,imx51-usbmisc", true},
+ {"fsl,imx25-usbmisc", true},
+ {NULL, false},
+};
+
+static void
+imx_usbmisc_set_ctrl(device_t dev, u_int index, uint32_t bits)
+{
+ struct imx_usbmisc_softc *sc;
+ uint32_t reg;
+
+ sc = device_get_softc(dev);
+ reg = bus_read_4(sc->mmio, index * sizeof(uint32_t));
+ bus_write_4(sc->mmio, index * sizeof(uint32_t), reg | bits);
+}
+
+#ifdef notyet
+static void
+imx_usbmisc_clr_ctrl(device_t dev, u_int index, uint32_t bits)
+{
+ struct imx_usbmisc_softc *sc;
+ uint32_t reg;
+
+ sc = device_get_softc(dev);
+ reg = bus_read_4(sc->mmio, index * sizeof(uint32_t));
+ bus_write_4(sc->mmio, index * sizeof(uint32_t), reg & ~bits);
+}
+#endif
+
+static int
+imx_usbmisc_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (ofw_bus_search_compatible(dev, usbmisc_compat_data)->ocd_data) {
+ device_set_desc(dev, "i.MX USB Misc Control");
+ return (BUS_PROBE_DEFAULT);
+ }
+ return (ENXIO);
+}
+
+static int
+imx_usbmisc_detach(device_t dev)
+{
+ struct imx_usbmisc_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ if (sc->mmio != NULL)
+ bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->mmio);
+
+ return (0);
+}
+
+static int
+imx_usbmisc_attach(device_t dev)
+{
+ struct imx_usbmisc_softc *sc;
+ int rid;
+
+ sc = device_get_softc(dev);
+
+ /* Allocate bus_space resources. */
+ rid = 0;
+ sc->mmio = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->mmio == NULL) {
+ device_printf(dev, "Cannot allocate memory resources\n");
+ return (ENXIO);
+ }
+
+ OF_device_register_xref(OF_xref_from_node(ofw_bus_get_node(dev)), dev);
+
+ return (0);
+}
+
+static device_method_t imx_usbmisc_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, imx_usbmisc_probe),
+ DEVMETHOD(device_attach, imx_usbmisc_attach),
+ DEVMETHOD(device_detach, imx_usbmisc_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t imx_usbmisc_driver = {
+ "imx_usbmisc",
+ imx_usbmisc_methods,
+ sizeof(struct imx_usbmisc_softc)
+};
+
+/*
+ * This driver needs to start before the ehci driver, but later than the usual
+ * "special" drivers like clocks and cpu. Ehci starts at DEFAULT so
+ * DEFAULT-1000 seems good.
+ */
+EARLY_DRIVER_MODULE(imx_usbmisc, simplebus, imx_usbmisc_driver,
+ 0, 0, BUS_PASS_DEFAULT - 1000);
+
+/*-----------------------------------------------------------------------------
+ * imx_ehci driver...
+ *---------------------------------------------------------------------------*/
+
+/*
+ * Each EHCI device in the SoC has some SoC-specific per-device registers at an
+ * offset of 0, then the standard EHCI registers begin at an offset of 0x100.
+ */
+#define IMX_EHCI_REG_OFF 0x100
+#define IMX_EHCI_REG_SIZE 0x100
+
+struct imx_ehci_softc {
+ ehci_softc_t ehci_softc;
+ device_t dev;
+ struct resource *ehci_mem_res; /* EHCI core regs. */
+ struct resource *ehci_irq_res; /* EHCI core IRQ. */
+};
+
+static struct ofw_compat_data compat_data[] = {
+ {"fsl,imx6q-usb", 1},
+ {"fsl,imx53-usb", 1},
+ {"fsl,imx51-usb", 1},
+ {"fsl,imx28-usb", 1},
+ {"fsl,imx27-usb", 1},
+ {"fsl,imx25-usb", 1},
+ {"fsl,imx23-usb", 1},
+ {NULL, 0},
+};
+
+static void
+imx_ehci_post_reset(struct ehci_softc *ehci_softc)
+{
+ uint32_t usbmode;
+
+ /* Force HOST mode */
+ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_NOLPM);
+ usbmode &= ~EHCI_UM_CM;
+ usbmode |= EHCI_UM_CM_HOST;
+ EOWRITE4(ehci_softc, EHCI_USBMODE_NOLPM, usbmode);
+}
+
+static int
+imx_ehci_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (ofw_bus_search_compatible(dev, compat_data)->ocd_data != 0) {
+ device_set_desc(dev, "Freescale i.MX integrated USB controller");
+ return (BUS_PROBE_DEFAULT);
+ }
+ return (ENXIO);
+}
+
+static int
+imx_ehci_detach(device_t dev)
+{
+ struct imx_ehci_softc *sc;
+ ehci_softc_t *esc;
+ int err;
+
+ sc = device_get_softc(dev);
+
+ esc = &sc->ehci_softc;
+
+ /* First detach all children; we can't detach if that fails. */
+ if ((err = bus_generic_detach(dev)) != 0)
+ return (err);
+
+ if (esc->sc_flags & EHCI_SCFLG_DONEINIT)
+ ehci_detach(esc);
+ if (esc->sc_intr_hdl != NULL)
+ bus_teardown_intr(dev, esc->sc_irq_res,
+ esc->sc_intr_hdl);
+ if (sc->ehci_irq_res != NULL)
+ bus_release_resource(dev, SYS_RES_IRQ, 0,
+ sc->ehci_irq_res);
+ if (sc->ehci_mem_res != NULL)
+ bus_release_resource(dev, SYS_RES_MEMORY, 0,
+ sc->ehci_mem_res);
+
+ usb_bus_mem_free_all(&esc->sc_bus, &ehci_iterate_hw_softc);
+
+ return (0);
+}
+
+static void
+imx_ehci_disable_oc(struct imx_ehci_softc *sc)
+{
+ device_t usbmdev;
+ pcell_t usbmprops[2];
+ phandle_t node;
+ ssize_t size;
+ int index;
+
+ /* Get the reference to the usbmisc driver from the fdt data */
+ node = ofw_bus_get_node(sc->dev);
+ size = OF_getencprop(node, "fsl,usbmisc", usbmprops,
+ sizeof(usbmprops));
+ if (size < sizeof(usbmprops)) {
+ device_printf(sc->dev, "failed to retrieve fsl,usbmisc "
+ "property, cannot disable overcurrent protection");
+ return;
+ }
+ /* Retrieve the device_t via the xref handle. */
+ usbmdev = OF_device_from_xref(usbmprops[0]);
+ if (usbmdev == NULL) {
+ device_printf(sc->dev, "usbmisc device not found, "
+ "cannot disable overcurrent protection");
+ return;
+ }
+ /* Call the device routine to set the overcurrent disable bit. */
+ index = usbmprops[1];
+ imx_usbmisc_set_ctrl(usbmdev, index, USBNC_OVER_CUR_DIS);
+}
+
+static int
+imx_ehci_attach(device_t dev)
+{
+ struct imx_ehci_softc *sc;
+ ehci_softc_t *esc;
+ int err, rid;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ esc = &sc->ehci_softc;
+ err = 0;
+
+ /* Allocate bus_space resources. */
+ rid = 0;
+ sc->ehci_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->ehci_mem_res == NULL) {
+ device_printf(dev, "Cannot allocate memory resources\n");
+ err = ENXIO;
+ goto out;
+ }
+
+ rid = 0;
+ sc->ehci_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+ RF_ACTIVE);
+ if (sc->ehci_irq_res == NULL) {
+ device_printf(dev, "Cannot allocate IRQ resources\n");
+ err = ENXIO;
+ goto out;
+ }
+
+ esc->sc_io_tag = rman_get_bustag(sc->ehci_mem_res);
+ esc->sc_bus.parent = dev;
+ esc->sc_bus.devices = esc->sc_devices;
+ esc->sc_bus.devices_max = EHCI_MAX_DEVICES;
+ esc->sc_bus.dma_bits = 32;
+
+ /* allocate all DMA memory */
+ if (usb_bus_mem_alloc_all(&esc->sc_bus, USB_GET_DMA_TAG(dev),
+ &ehci_iterate_hw_softc) != 0) {
+ device_printf(dev, "usb_bus_mem_alloc_all() failed\n");
+ err = ENOMEM;
+ goto out;
+ }
+
+ /*
+ * Set handle to USB related registers subregion used by
+ * generic EHCI driver.
+ */
+ err = bus_space_subregion(esc->sc_io_tag,
+ rman_get_bushandle(sc->ehci_mem_res),
+ IMX_EHCI_REG_OFF, IMX_EHCI_REG_SIZE, &esc->sc_io_hdl);
+ if (err != 0) {
+ device_printf(dev, "bus_space_subregion() failed\n");
+ err = ENXIO;
+ goto out;
+ }
+
+ /* Setup interrupt handler. */
+ err = bus_setup_intr(dev, sc->ehci_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (driver_intr_t *)ehci_interrupt, esc, &esc->sc_intr_hdl);
+ if (err != 0) {
+ device_printf(dev, "Could not setup IRQ\n");
+ goto out;
+ }
+
+ /* Turn on clocks. */
+ imx_ccm_usb_enable(dev);
+
+ /* Disable overcurrent detection, if configured to do so. */
+ if (OF_hasprop(ofw_bus_get_node(sc->dev), "disable-over-current"))
+ imx_ehci_disable_oc(sc);
+
+ /* Add USB bus device. */
+ esc->sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY);
+ if (esc->sc_bus.bdev == NULL) {
+ device_printf(dev, "Could not add USB device\n");
+ goto out;
+ }
+ device_set_ivars(esc->sc_bus.bdev, &esc->sc_bus);
+
+ esc->sc_id_vendor = USB_VENDOR_FREESCALE;
+ strlcpy(esc->sc_vendor, "Freescale", sizeof(esc->sc_vendor));
+
+ /*
+ * Set flags that affect ehci_init() behavior, and hook our post-reset
+ * code into the standard controller code.
+ */
+ esc->sc_flags |= EHCI_SCFLG_NORESTERM | EHCI_SCFLG_TT;
+ esc->sc_vendor_post_reset = imx_ehci_post_reset;
+ esc->sc_vendor_get_port_speed = ehci_get_port_speed_portsc;
+
+ err = ehci_init(esc);
+ if (err != 0) {
+ device_printf(dev, "USB init failed, usb_err_t=%d\n",
+ err);
+ goto out;
+ }
+ esc->sc_flags |= EHCI_SCFLG_DONEINIT;
+
+ /* Probe the bus. */
+ err = device_probe_and_attach(esc->sc_bus.bdev);
+ if (err != 0) {
+ device_printf(dev,
+ "device_probe_and_attach() failed\n");
+ goto out;
+ }
+
+ err = 0;
+
+out:
+
+ if (err != 0)
+ imx_ehci_detach(dev);
+
+ return (err);
+}
+
+static device_method_t ehci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, imx_ehci_probe),
+ DEVMETHOD(device_attach, imx_ehci_attach),
+ DEVMETHOD(device_detach, imx_ehci_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+
+ DEVMETHOD_END
+};
+
+static driver_t ehci_driver = {
+ "ehci",
+ ehci_methods,
+ sizeof(struct imx_ehci_softc)
+};
+
+DRIVER_MODULE(imx_ehci, simplebus, ehci_driver, 0, 0);
+MODULE_DEPEND(imx_ehci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/ehci_msm.c b/sys/dev/usb/controller/ehci_msm.c
new file mode 100644
index 000000000000..2586df634b3c
--- /dev/null
+++ b/sys/dev/usb/controller/ehci_msm.c
@@ -0,0 +1,221 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2018 Ruslan Bukin <br@bsdpad.com>
+ * All rights reserved.
+ *
+ * This software was developed by BAE Systems, the University of Cambridge
+ * Computer Laboratory, and Memorial University under DARPA/AFRL contract
+ * FA8650-15-C-7558 ("CADETS"), as part of the DARPA Transparent Computing
+ * (TC) research program.
+ *
+ * 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 "opt_bus.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ehci.h>
+#include <dev/usb/controller/ehcireg.h>
+
+struct ehci_msm_softc {
+ ehci_softc_t base;
+ struct resource *res[3];
+};
+
+static struct resource_spec ehci_msm_spec[] = {
+ { SYS_RES_MEMORY, 0, RF_ACTIVE },
+ { SYS_RES_MEMORY, 1, RF_ACTIVE },
+ { SYS_RES_IRQ, 0, RF_ACTIVE },
+ { -1, 0 }
+};
+
+#define EHCI_HC_DEVSTR "Qualcomm USB 2.0 controller"
+
+static device_attach_t ehci_msm_attach;
+static device_detach_t ehci_msm_detach;
+
+static int
+ehci_msm_probe(device_t dev)
+{
+
+ if (!ofw_bus_is_compatible(dev, "qcom,ci-hdrc"))
+ return (ENXIO);
+
+ device_set_desc(dev, EHCI_HC_DEVSTR);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+ehci_msm_attach(device_t dev)
+{
+ struct ehci_msm_softc *esc;
+ bus_space_handle_t bsh;
+ ehci_softc_t *sc;
+ int err;
+
+ esc = device_get_softc(dev);
+ sc = &esc->base;
+ sc->sc_bus.parent = dev;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
+ sc->sc_bus.dma_bits = 32;
+
+ if (bus_alloc_resources(dev, ehci_msm_spec, esc->res)) {
+ device_printf(dev, "could not allocate resources\n");
+ return (ENXIO);
+ }
+
+ sc->sc_io_tag = rman_get_bustag(esc->res[0]);
+
+ /* Get all DMA memory */
+ if (usb_bus_mem_alloc_all(&sc->sc_bus,
+ USB_GET_DMA_TAG(dev), &ehci_iterate_hw_softc)) {
+ return (ENOMEM);
+ }
+
+ /* EHCI registers */
+ sc->sc_io_tag = rman_get_bustag(esc->res[0]);
+ bsh = rman_get_bushandle(esc->res[0]);
+ sc->sc_io_size = rman_get_size(esc->res[0]);
+
+ if (bus_space_subregion(sc->sc_io_tag, bsh, 0x100,
+ sc->sc_io_size, &sc->sc_io_hdl) != 0)
+ panic("%s: unable to subregion USB host registers",
+ device_get_name(dev));
+
+ sc->sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY);
+ if (!sc->sc_bus.bdev) {
+ device_printf(dev, "Could not add USB device\n");
+ goto error;
+ }
+ device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
+ device_set_desc(sc->sc_bus.bdev, EHCI_HC_DEVSTR);
+
+ sprintf(sc->sc_vendor, "Qualcomm");
+
+ err = bus_setup_intr(dev, esc->res[2], INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl);
+ if (err) {
+ device_printf(dev, "Could not setup irq, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ goto error;
+ }
+
+ sc->sc_flags |= EHCI_SCFLG_DONTRESET | 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(dev, "USB init failed err=%d\n", err);
+ goto error;
+ }
+ return (0);
+
+error:
+ ehci_msm_detach(dev);
+ return (ENXIO);
+}
+
+static int
+ehci_msm_detach(device_t dev)
+{
+ ehci_softc_t *sc;
+ device_t bdev;
+ int err;
+
+ sc = device_get_softc(dev);
+
+ err = bus_generic_detach(dev);
+ if (err != 0)
+ return (err);
+
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ /* only call ehci_detach() after ehci_init() */
+ ehci_detach(sc);
+
+ err = bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_hdl);
+ if (err)
+ device_printf(dev, "Could not tear down irq, %d\n",
+ err);
+ sc->sc_intr_hdl = NULL;
+ }
+
+ if (sc->sc_irq_res) {
+ bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ }
+ if (sc->sc_io_res) {
+ bus_release_resource(dev, SYS_RES_MEMORY, 0,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+
+ usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc);
+
+ return (0);
+}
+
+static device_method_t ehci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ehci_msm_probe),
+ DEVMETHOD(device_attach, ehci_msm_attach),
+ DEVMETHOD(device_detach, ehci_msm_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+ DEVMETHOD_END
+};
+
+static driver_t ehci_driver = {
+ .name = "ehci",
+ .methods = ehci_methods,
+ .size = sizeof(ehci_softc_t),
+};
+
+DRIVER_MODULE(ehci_msm, simplebus, ehci_driver, 0, 0);
+MODULE_DEPEND(ehci_msm, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/ehci_mv.c b/sys/dev/usb/controller/ehci_mv.c
new file mode 100644
index 000000000000..5dc72d4af3d8
--- /dev/null
+++ b/sys/dev/usb/controller/ehci_mv.c
@@ -0,0 +1,384 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * 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.
+ */
+
+/*
+ * FDT attachment driver for the USB Enhanced Host Controller.
+ */
+
+#include "opt_bus.h"
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ehci.h>
+#include <dev/usb/controller/ehcireg.h>
+
+#if !defined(__aarch64__)
+#include <arm/mv/mvreg.h>
+#endif
+#include <arm/mv/mvvar.h>
+
+#define EHCI_VENDORID_MRVL 0x1286
+#define EHCI_HC_DEVSTR "Marvell Integrated USB 2.0 controller"
+
+static device_attach_t mv_ehci_attach;
+static device_detach_t mv_ehci_detach;
+
+static int err_intr(void *arg);
+
+static struct resource *irq_err;
+static void *ih_err;
+
+/* EHCI HC regs start at this offset within USB range */
+#define MV_USB_HOST_OFST 0x0100
+
+#define USB_BRIDGE_INTR_CAUSE 0x210
+#define USB_BRIDGE_INTR_MASK 0x214
+#define USB_BRIDGE_ERR_ADDR 0x21C
+
+#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)
+
+enum mv_ehci_hwtype {
+ HWTYPE_NONE = 0,
+ HWTYPE_MV_EHCI_V1,
+ HWTYPE_MV_EHCI_V2,
+};
+
+static struct ofw_compat_data compat_data[] = {
+ {"mrvl,usb-ehci", HWTYPE_MV_EHCI_V1},
+ {"marvell,orion-ehci", HWTYPE_MV_EHCI_V2},
+ {"marvell,armada-3700-ehci", HWTYPE_MV_EHCI_V2},
+ {NULL, HWTYPE_NONE}
+};
+
+static void
+mv_ehci_post_reset(struct ehci_softc *ehci_softc)
+{
+ uint32_t usbmode;
+
+ /* Force HOST mode */
+ usbmode = EOREAD4(ehci_softc, EHCI_USBMODE_NOLPM);
+ usbmode &= ~EHCI_UM_CM;
+ usbmode |= EHCI_UM_CM_HOST;
+ EOWRITE4(ehci_softc, EHCI_USBMODE_NOLPM, usbmode);
+}
+
+static int
+mv_ehci_probe(device_t self)
+{
+
+ if (!ofw_bus_status_okay(self))
+ return (ENXIO);
+
+ if (!ofw_bus_search_compatible(self, compat_data)->ocd_data)
+ return (ENXIO);
+
+ device_set_desc(self, EHCI_HC_DEVSTR);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+mv_ehci_attach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ enum mv_ehci_hwtype hwtype;
+ bus_space_handle_t bsh;
+ int err;
+ int rid;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
+ sc->sc_bus.dma_bits = 32;
+
+ hwtype = ofw_bus_search_compatible(self, compat_data)->ocd_data;
+ if (hwtype == HWTYPE_NONE) {
+ device_printf(self, "Wrong HW type flag detected\n");
+ return (ENXIO);
+ }
+
+ /* get all DMA memory */
+ if (usb_bus_mem_alloc_all(&sc->sc_bus,
+ USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) {
+ return (ENOMEM);
+ }
+
+ rid = 0;
+ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+ if (!sc->sc_io_res) {
+ device_printf(self, "Could not map memory\n");
+ goto error;
+ }
+ sc->sc_io_tag = rman_get_bustag(sc->sc_io_res);
+ bsh = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = rman_get_size(sc->sc_io_res) - MV_USB_HOST_OFST;
+
+ /*
+ * 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->sc_io_tag, bsh, MV_USB_HOST_OFST,
+ sc->sc_io_size, &sc->sc_io_hdl) != 0)
+ panic("%s: unable to subregion USB host registers",
+ device_get_name(self));
+
+ rid = 0;
+ if (hwtype == HWTYPE_MV_EHCI_V1) {
+ 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");
+ mv_ehci_detach(self);
+ return (ENXIO);
+ }
+ rid = 1;
+ }
+
+ /*
+ * Notice: Marvell EHCI controller has TWO interrupt lines, so make
+ * sure to use the correct rid for the main one (controller interrupt)
+ * -- refer to DTS for the right resource number to use here.
+ */
+ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (sc->sc_irq_res == NULL) {
+ device_printf(self, "Could not allocate irq\n");
+ goto error;
+ }
+
+ sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY);
+ if (!sc->sc_bus.bdev) {
+ device_printf(self, "Could not add USB device\n");
+ goto error;
+ }
+ device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
+ device_set_desc(sc->sc_bus.bdev, EHCI_HC_DEVSTR);
+
+ sprintf(sc->sc_vendor, "Marvell");
+
+ if (hwtype == HWTYPE_MV_EHCI_V1) {
+ err = bus_setup_intr(self, irq_err, 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;
+ goto error;
+ }
+ }
+
+ 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->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl);
+ if (err) {
+ device_printf(self, "Could not setup irq, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ goto error;
+ }
+
+ /*
+ * 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_vendor_post_reset = mv_ehci_post_reset;
+ if (bootverbose)
+ device_printf(self, "5.24 GL USB-2 workaround enabled\n");
+
+ /* XXX all MV chips need it? */
+ sc->sc_flags |= EHCI_SCFLG_TT | EHCI_SCFLG_NORESTERM;
+ sc->sc_vendor_get_port_speed = ehci_get_port_speed_portsc;
+ err = ehci_init(sc);
+ if (!err) {
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ }
+ if (err) {
+ device_printf(self, "USB init failed err=%d\n", err);
+ goto error;
+ }
+ return (0);
+
+error:
+ mv_ehci_detach(self);
+ return (ENXIO);
+}
+
+static int
+mv_ehci_detach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ /* during module unload there are lots of children leftover */
+ err = bus_generic_detach(self);
+ if (err != 0)
+ return (err);
+
+ /*
+ * disable interrupts that might have been switched on in mv_ehci_attach
+ */
+ if (sc->sc_io_res) {
+ EWRITE4(sc, USB_BRIDGE_INTR_MASK, 0);
+ }
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ /*
+ * only call ehci_detach() after ehci_init()
+ */
+ ehci_detach(sc);
+
+ err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl);
+
+ if (err)
+ /* XXX or should we panic? */
+ device_printf(self, "Could not tear down irq, %d\n",
+ err);
+ sc->sc_intr_hdl = NULL;
+ }
+ 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 (irq_err) {
+ bus_release_resource(self, SYS_RES_IRQ, 0, irq_err);
+ irq_err = NULL;
+ }
+ if (sc->sc_irq_res) {
+ bus_release_resource(self, SYS_RES_IRQ, 1, sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ }
+ if (sc->sc_io_res) {
+ bus_release_resource(self, SYS_RES_MEMORY, 0,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+ usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc);
+
+ return (0);
+}
+
+static int
+err_intr(void *arg)
+{
+ ehci_softc_t *sc = arg;
+ unsigned cause;
+
+ cause = EREAD4(sc, USB_BRIDGE_INTR_CAUSE);
+ if (cause) {
+ printf("USB error: ");
+ if (cause & MV_USB_ADDR_DECODE_ERR) {
+ uint32_t addr;
+
+ addr = EREAD4(sc, USB_BRIDGE_ERR_ADDR);
+ printf("address decoding error (addr=%#x)\n", addr);
+ }
+ if (cause & MV_USB_HOST_UNDERFLOW)
+ printf("host underflow\n");
+ if (cause & MV_USB_HOST_OVERFLOW)
+ printf("host overflow\n");
+ if (cause & MV_USB_DEVICE_UNDERFLOW)
+ printf("device underflow\n");
+ if (cause & ~(MV_USB_ADDR_DECODE_ERR | MV_USB_HOST_UNDERFLOW |
+ MV_USB_HOST_OVERFLOW | MV_USB_DEVICE_UNDERFLOW))
+ printf("unknown cause (cause=%#x)\n", cause);
+
+ EWRITE4(sc, USB_BRIDGE_INTR_CAUSE, 0);
+ }
+ return (FILTER_HANDLED);
+}
+
+static device_method_t ehci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, mv_ehci_probe),
+ DEVMETHOD(device_attach, mv_ehci_attach),
+ DEVMETHOD(device_detach, mv_ehci_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ DEVMETHOD_END
+};
+
+static driver_t ehci_driver = {
+ "ehci",
+ ehci_methods,
+ sizeof(ehci_softc_t),
+};
+
+DRIVER_MODULE(ehci_mv, simplebus, ehci_driver, 0, 0);
+MODULE_DEPEND(ehci_mv, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/ehci_pci.c b/sys/dev/usb/controller/ehci_pci.c
new file mode 100644
index 000000000000..d7298ab89df7
--- /dev/null
+++ b/sys/dev/usb/controller/ehci_pci.c
@@ -0,0 +1,610 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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
+ */
+
+/* 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/usb_pci.h>
+#include <dev/usb/controller/ehci.h>
+#include <dev/usb/controller/ehcireg.h>
+#include "usb_if.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_HYGON 0x1d94
+#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
+#define PCI_EHCI_VENDORID_VMWARE 0x15ad
+#define PCI_EHCI_VENDORID_ZHAOXIN 0x1d17
+
+static device_probe_t ehci_pci_probe;
+static device_attach_t ehci_pci_attach;
+static device_detach_t ehci_pci_detach;
+static usb_take_controller_t ehci_pci_take_controller;
+
+static const char *
+ehci_pci_match(device_t self)
+{
+ uint32_t device_id = pci_get_devid(self);
+
+ switch (device_id) {
+ case 0x523910b9:
+ return "ALi M5239 USB 2.0 controller";
+
+ case 0x10227463:
+ return "AMD 8111 USB 2.0 controller";
+
+ case 0x20951022:
+ return ("AMD CS5536 (Geode) USB 2.0 controller");
+ case 0x78081022:
+ return ("AMD FCH USB 2.0 controller");
+ case 0x79081022:
+ return ("AMD FCH USB 2.0 controller");
+
+ case 0x43451002:
+ return "ATI SB200 USB 2.0 controller";
+ case 0x43731002:
+ return "ATI SB400 USB 2.0 controller";
+ case 0x43961002:
+ return ("AMD SB7x0/SB8x0/SB9x0 USB 2.0 controller");
+
+ case 0x0f348086:
+ return ("Intel BayTrail USB 2.0 controller");
+ case 0x1c268086:
+ return ("Intel Cougar Point USB 2.0 controller");
+ case 0x1c2d8086:
+ return ("Intel Cougar Point USB 2.0 controller");
+ case 0x1d268086:
+ return ("Intel Patsburg USB 2.0 controller");
+ case 0x1d2d8086:
+ return ("Intel Patsburg USB 2.0 controller");
+ case 0x1e268086:
+ return ("Intel Panther Point USB 2.0 controller");
+ case 0x1e2d8086:
+ return ("Intel Panther Point USB 2.0 controller");
+ case 0x1f2c8086:
+ return ("Intel Avoton USB 2.0 controller");
+ case 0x25ad8086:
+ return "Intel 6300ESB USB 2.0 controller";
+ case 0x24cd8086:
+ return "Intel 82801DB/L/M (ICH4) USB 2.0 controller";
+ case 0x24dd8086:
+ return "Intel 82801EB/R (ICH5) USB 2.0 controller";
+ case 0x265c8086:
+ return "Intel 82801FB (ICH6) USB 2.0 controller";
+ case 0x268c8086:
+ return ("Intel 63XXESB USB 2.0 controller");
+ case 0x27cc8086:
+ return "Intel 82801GB/R (ICH7) USB 2.0 controller";
+ case 0x28368086:
+ return "Intel 82801H (ICH8) USB 2.0 controller USB2-A";
+ case 0x283a8086:
+ return "Intel 82801H (ICH8) USB 2.0 controller USB2-B";
+ case 0x293a8086:
+ return "Intel 82801I (ICH9) USB 2.0 controller";
+ case 0x293c8086:
+ return "Intel 82801I (ICH9) USB 2.0 controller";
+ case 0x3a3a8086:
+ return "Intel 82801JI (ICH10) USB 2.0 controller USB-A";
+ case 0x3a3c8086:
+ return "Intel 82801JI (ICH10) USB 2.0 controller USB-B";
+ case 0x3a6c8086:
+ return "Intel 82801JD (ICH10) USB 2.0 controller USB-A";
+ case 0x3a6a8086:
+ return "Intel 82801JD (ICH10) USB 2.0 controller USB-B";
+ case 0x3b348086:
+ return ("Intel PCH USB 2.0 controller USB-A");
+ case 0x3b3c8086:
+ return ("Intel PCH USB 2.0 controller USB-B");
+ case 0x8c268086:
+ return ("Intel Lynx Point USB 2.0 controller USB-A");
+ case 0x8c2d8086:
+ return ("Intel Lynx Point USB 2.0 controller USB-B");
+ case 0x8ca68086:
+ return ("Intel Wildcat Point USB 2.0 controller USB-A");
+ case 0x8cad8086:
+ return ("Intel Wildcat Point USB 2.0 controller USB-B");
+ case 0x8d268086:
+ return ("Intel Wellsburg USB 2.0 controller");
+ case 0x8d2d8086:
+ return ("Intel Wellsburg USB 2.0 controller");
+ case 0x9c268086:
+ return ("Intel Lynx Point-LP USB 2.0 controller");
+ case 0x9ca68086:
+ return ("Intel Wildcat Point-LP USB 2.0 controller");
+
+ case 0x00e01033:
+ return ("NEC uPD 72010x USB 2.0 controller");
+
+ case 0x006810de:
+ return "NVIDIA nForce2 USB 2.0 controller";
+ case 0x008810de:
+ return "NVIDIA nForce2 Ultra 400 USB 2.0 controller";
+ case 0x00d810de:
+ return "NVIDIA nForce3 USB 2.0 controller";
+ case 0x00e810de:
+ return "NVIDIA nForce3 250 USB 2.0 controller";
+ case 0x005b10de:
+ return "NVIDIA nForce CK804 USB 2.0 controller";
+ case 0x036d10de:
+ return "NVIDIA nForce MCP55 USB 2.0 controller";
+ case 0x03f210de:
+ return "NVIDIA nForce MCP61 USB 2.0 controller";
+ case 0x0aa610de:
+ return "NVIDIA nForce MCP79 USB 2.0 controller";
+ case 0x0aa910de:
+ return "NVIDIA nForce MCP79 USB 2.0 controller";
+ case 0x0aaa10de:
+ return "NVIDIA nForce MCP79 USB 2.0 controller";
+
+ case 0x15621131:
+ return "Philips ISP156x USB 2.0 controller";
+
+ case 0x70021039:
+ return "SiS 968 USB 2.0 controller";
+
+ case 0x31041106:
+ return ("VIA VT6202 USB 2.0 controller");
+
+ case 0x077015ad:
+ return ("VMware USB 2.0 controller");
+
+ case 0x31041d17:
+ return ("Zhaoxin ZX-100/ZX-200/ZX-E USB 2.0 controller");
+
+ default:
+ break;
+ }
+
+ if ((pci_get_class(self) == PCIC_SERIALBUS)
+ && (pci_get_subclass(self) == PCIS_SERIALBUS_USB)
+ && (pci_get_progif(self) == PCI_INTERFACE_EHCI)) {
+ return ("EHCI (generic) USB 2.0 controller");
+ }
+ 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 void
+ehci_pci_ati_quirk(device_t self, uint8_t is_sb700)
+{
+ device_t smbdev;
+ uint32_t val;
+
+ if (is_sb700) {
+ /* Lookup SMBUS PCI device */
+ smbdev = pci_find_device(PCI_EHCI_VENDORID_ATI, 0x4385);
+ if (smbdev == NULL)
+ return;
+ val = pci_get_revid(smbdev);
+ if (val != 0x3a && val != 0x3b)
+ return;
+ }
+
+ /*
+ * Note: this bit is described as reserved in SB700
+ * Register Reference Guide.
+ */
+ val = pci_read_config(self, 0x53, 1);
+ if (!(val & 0x8)) {
+ val |= 0x8;
+ pci_write_config(self, 0x53, val, 1);
+ device_printf(self, "AMD SB600/700 quirk applied\n");
+ }
+}
+
+static void
+ehci_pci_via_quirk(device_t self)
+{
+ uint32_t val;
+
+ if ((pci_get_device(self) == 0x3104) &&
+ ((pci_get_revid(self) & 0xf0) == 0x60)) {
+ /* Correct schedule sleep time to 10us */
+ val = pci_read_config(self, 0x4b, 1);
+ if (val & 0x20)
+ return;
+ val |= 0x20;
+ pci_write_config(self, 0x4b, val, 1);
+ device_printf(self, "VIA-quirk applied\n");
+ }
+}
+
+static int
+ehci_pci_attach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+ int rid;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
+ sc->sc_bus.dma_bits = 32;
+
+ /* get all DMA memory */
+ if (usb_bus_mem_alloc_all(&sc->sc_bus,
+ USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) {
+ return (ENOMEM);
+ }
+
+ pci_enable_busmaster(self);
+
+ switch (pci_read_config(self, PCI_USBREV, 1) & PCI_USB_REV_MASK) {
+ case PCI_USB_REV_PRE_1_0:
+ case PCI_USB_REV_1_0:
+ case PCI_USB_REV_1_1:
+ /*
+ * NOTE: some EHCI USB controllers have the wrong USB
+ * revision number. It appears those controllers are
+ * fully compliant so we just ignore this value in
+ * some common cases.
+ */
+ device_printf(self, "pre-2.0 USB revision (ignored)\n");
+ /* fallthrough */
+ case PCI_USB_REV_2_0:
+ break;
+ default:
+ /* Quirk for Parallels Desktop 4.0 */
+ device_printf(self, "USB revision is unknown. Assuming v2.0.\n");
+ break;
+ }
+
+ rid = PCI_CBMEM;
+ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (!sc->sc_io_res) {
+ device_printf(self, "Could not map memory\n");
+ goto error;
+ }
+ sc->sc_io_tag = rman_get_bustag(sc->sc_io_res);
+ sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = rman_get_size(sc->sc_io_res);
+
+ rid = 0;
+ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (sc->sc_irq_res == NULL) {
+ device_printf(self, "Could not allocate irq\n");
+ goto error;
+ }
+ sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY);
+ if (!sc->sc_bus.bdev) {
+ device_printf(self, "Could not add USB device\n");
+ goto error;
+ }
+ 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_HYGON:
+ sprintf(sc->sc_vendor, "Hygon");
+ 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_PHILIPS:
+ sprintf(sc->sc_vendor, "Philips");
+ 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;
+ case PCI_EHCI_VENDORID_VMWARE:
+ sprintf(sc->sc_vendor, "VMware");
+ break;
+ case PCI_EHCI_VENDORID_ZHAOXIN:
+ sprintf(sc->sc_vendor, "Zhaoxin");
+ 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->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl);
+ if (err) {
+ device_printf(self, "Could not setup irq, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ goto error;
+ }
+ ehci_pci_take_controller(self);
+
+ /* Undocumented quirks taken from Linux */
+
+ switch (pci_get_vendor(self)) {
+ case PCI_EHCI_VENDORID_ATI:
+ /* SB600 and SB700 EHCI quirk */
+ switch (pci_get_device(self)) {
+ case 0x4386:
+ ehci_pci_ati_quirk(self, 0);
+ break;
+ case 0x4396:
+ ehci_pci_ati_quirk(self, 1);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case PCI_EHCI_VENDORID_VIA:
+ ehci_pci_via_quirk(self);
+ break;
+
+ default:
+ break;
+ }
+
+ /* Dropped interrupts workaround */
+ 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;
+ }
+
+ /* Doorbell feature workaround */
+ switch (pci_get_vendor(self)) {
+ case PCI_EHCI_VENDORID_NVIDIA:
+ case PCI_EHCI_VENDORID_NVIDIA2:
+ sc->sc_flags |= EHCI_SCFLG_IAADBUG;
+ if (bootverbose)
+ device_printf(self,
+ "Doorbell workaround enabled\n");
+ break;
+ default:
+ break;
+ }
+
+ err = ehci_init(sc);
+ if (!err) {
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ }
+ if (err) {
+ device_printf(self, "USB init failed err=%d\n", err);
+ goto error;
+ }
+ return (0);
+
+error:
+ ehci_pci_detach(self);
+ return (ENXIO);
+}
+
+static int
+ehci_pci_detach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int error;
+
+ /* during module unload there are lots of children leftover */
+ error = bus_generic_detach(self);
+ if (error != 0)
+ return (error);
+
+ pci_disable_busmaster(self);
+
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ /*
+ * only call ehci_detach() after ehci_init()
+ */
+ ehci_detach(sc);
+
+ int err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl);
+
+ if (err)
+ /* XXX or should we panic? */
+ device_printf(self, "Could not tear down irq, %d\n",
+ err);
+ sc->sc_intr_hdl = NULL;
+ }
+ if (sc->sc_irq_res) {
+ bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ }
+ if (sc->sc_io_res) {
+ bus_release_resource(self, SYS_RES_MEMORY, PCI_CBMEM,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+ usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc);
+
+ return (0);
+}
+
+static int
+ehci_pci_take_controller(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ uint32_t cparams;
+ uint32_t eec;
+ uint16_t to;
+ uint8_t eecp;
+ uint8_t bios_sem;
+
+ 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 == 0) {
+ continue;
+ }
+ device_printf(sc->sc_bus.bdev, "waiting for BIOS "
+ "to give up control\n");
+ pci_write_config(self, eecp +
+ EHCI_LEGSUP_OS_SEM, 1, 1);
+ to = 500;
+ while (1) {
+ bios_sem = pci_read_config(self, eecp +
+ EHCI_LEGSUP_BIOS_SEM, 1);
+ if (bios_sem == 0)
+ break;
+
+ if (--to == 0) {
+ device_printf(sc->sc_bus.bdev,
+ "timed out waiting for BIOS\n");
+ break;
+ }
+ usb_pause_mtx(NULL, hz / 100); /* wait 10ms */
+ }
+ }
+ return (0);
+}
+
+static device_method_t ehci_pci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ehci_pci_probe),
+ DEVMETHOD(device_attach, ehci_pci_attach),
+ DEVMETHOD(device_detach, ehci_pci_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+ DEVMETHOD(usb_take_controller, ehci_pci_take_controller),
+
+ DEVMETHOD_END
+};
+
+static driver_t ehci_driver = {
+ .name = "ehci",
+ .methods = ehci_pci_methods,
+ .size = sizeof(struct ehci_softc),
+};
+
+DRIVER_MODULE(ehci, pci, ehci_driver, 0, 0);
+MODULE_DEPEND(ehci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/ehcireg.h b/sys/dev/usb/controller/ehcireg.h
new file mode 100644
index 000000000000..90c006103987
--- /dev/null
+++ b/sys/dev/usb/controller/ehcireg.h
@@ -0,0 +1,194 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2001 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Lennart Augustsson (lennart@augustsson.net).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _EHCIREG_H_
+#define _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_USB_REV_MASK 0xff
+#define PCI_USB_REV_PRE_1_0 0x00
+#define PCI_USB_REV_1_0 0x10
+#define PCI_USB_REV_1_1 0x11
+#define PCI_USB_REV_2_0 0x20
+#define PCI_EHCI_FLADJ 0x61 /* RW Frame len adj, SOF=59488+6*fladj */
+#define PCI_EHCI_PORTWAKECAP 0x62 /* RW Port wake caps (opt) */
+
+/* EHCI Extended Capabilities */
+#define EHCI_EC_LEGSUP 0x01
+#define EHCI_EECP_NEXT(x) (((x) >> 8) & 0xff)
+#define EHCI_EECP_ID(x) ((x) & 0xff)
+
+/* Legacy support extended capability */
+#define EHCI_LEGSUP_BIOS_SEM 0x02
+#define EHCI_LEGSUP_OS_SEM 0x03
+#define EHCI_LEGSUP_USBLEGCTLSTS 0x04
+
+/* EHCI capability registers */
+#define EHCI_CAPLEN_HCIVERSION 0x00 /* RO Capability register length
+ * (least-significant byte) and
+ * interface version number (two
+ * most significant)
+ */
+#define EHCI_CAPLENGTH(x) ((x) & 0xff)
+#define EHCI_HCIVERSION(x) (((x) >> 16) & 0xffff)
+#define EHCI_HCSPARAMS 0x04 /* RO Structural parameters */
+#define EHCI_HCS_DEBUGPORT(x) (((x) >> 20) & 0xf)
+#define EHCI_HCS_P_INDICATOR(x) ((x) & 0x10000)
+#define EHCI_HCS_N_CC(x) (((x) >> 12) & 0xf) /* # of companion ctlrs */
+#define EHCI_HCS_N_PCC(x) (((x) >> 8) & 0xf) /* # of ports per comp. */
+#define EHCI_HCS_PPC(x) ((x) & 0x10) /* port power control */
+#define EHCI_HCS_N_PORTS(x) ((x) & 0xf) /* # of ports */
+#define EHCI_HCCPARAMS 0x08 /* RO Capability parameters */
+#define EHCI_HCC_EECP(x) (((x) >> 8) & 0xff) /* extended ports caps */
+#define EHCI_HCC_IST(x) (((x) >> 4) & 0xf) /* isoc sched threshold */
+#define EHCI_HCC_ASPC(x) ((x) & 0x4) /* async sched park cap */
+#define EHCI_HCC_PFLF(x) ((x) & 0x2) /* prog frame list flag */
+#define EHCI_HCC_64BIT(x) ((x) & 0x1) /* 64 bit address cap */
+#define EHCI_HCSP_PORTROUTE 0x0c /* RO Companion port route description */
+
+/* EHCI operational registers. Offset given by EHCI_CAPLENGTH register */
+#define EHCI_USBCMD 0x00 /* RO, RW, WO Command register */
+#define EHCI_CMD_ITC_M 0x00ff0000 /* RW interrupt threshold ctrl */
+#define EHCI_CMD_ITC_1 0x00010000
+#define EHCI_CMD_ITC_2 0x00020000
+#define EHCI_CMD_ITC_4 0x00040000
+#define EHCI_CMD_ITC_8 0x00080000
+#define EHCI_CMD_ITC_16 0x00100000
+#define EHCI_CMD_ITC_32 0x00200000
+#define EHCI_CMD_ITC_64 0x00400000
+#define EHCI_CMD_ASPME 0x00000800 /* RW/RO async park enable */
+#define EHCI_CMD_ASPMC 0x00000300 /* RW/RO async park count */
+#define EHCI_CMD_LHCR 0x00000080 /* RW light host ctrl reset */
+#define EHCI_CMD_IAAD 0x00000040 /* RW intr on async adv door
+ * bell */
+#define EHCI_CMD_ASE 0x00000020 /* RW async sched enable */
+#define EHCI_CMD_PSE 0x00000010 /* RW periodic sched enable */
+#define EHCI_CMD_FLS_M 0x0000000c /* RW/RO frame list size */
+#define EHCI_CMD_FLS(x) (((x) >> 2) & 3) /* RW/RO frame list size */
+#define EHCI_CMD_HCRESET 0x00000002 /* RW reset */
+#define EHCI_CMD_RS 0x00000001 /* RW run/stop */
+#define EHCI_USBSTS 0x04 /* RO, RW, RWC Status register */
+#define EHCI_STS_ASS 0x00008000 /* RO async sched status */
+#define EHCI_STS_PSS 0x00004000 /* RO periodic sched status */
+#define EHCI_STS_REC 0x00002000 /* RO reclamation */
+#define EHCI_STS_HCH 0x00001000 /* RO host controller halted */
+#define EHCI_STS_IAA 0x00000020 /* RWC interrupt on async adv */
+#define EHCI_STS_HSE 0x00000010 /* RWC host system error */
+#define EHCI_STS_FLR 0x00000008 /* RWC frame list rollover */
+#define EHCI_STS_PCD 0x00000004 /* RWC port change detect */
+#define EHCI_STS_ERRINT 0x00000002 /* RWC error interrupt */
+#define EHCI_STS_INT 0x00000001 /* RWC interrupt */
+#define EHCI_STS_INTRS(x) ((x) & 0x3f)
+
+/*
+ * NOTE: the doorbell interrupt is enabled, but the doorbell is never
+ * used! SiS chipsets require this.
+ */
+#define EHCI_NORMAL_INTRS (EHCI_STS_IAA | EHCI_STS_HSE | \
+ EHCI_STS_PCD | EHCI_STS_ERRINT | EHCI_STS_INT)
+
+#define EHCI_USBINTR 0x08 /* RW Interrupt register */
+#define EHCI_INTR_IAAE 0x00000020 /* interrupt on async advance
+ * ena */
+#define EHCI_INTR_HSEE 0x00000010 /* host system error ena */
+#define EHCI_INTR_FLRE 0x00000008 /* frame list rollover ena */
+#define EHCI_INTR_PCIE 0x00000004 /* port change ena */
+#define EHCI_INTR_UEIE 0x00000002 /* USB error intr ena */
+#define EHCI_INTR_UIE 0x00000001 /* USB intr ena */
+
+#define EHCI_FRINDEX 0x0c /* RW Frame Index register */
+
+#define EHCI_CTRLDSSEGMENT 0x10 /* RW Control Data Structure Segment */
+
+#define EHCI_PERIODICLISTBASE 0x14 /* RW Periodic List Base */
+#define EHCI_ASYNCLISTADDR 0x18 /* RW Async List Base */
+
+#define EHCI_CONFIGFLAG 0x40 /* RW Configure Flag register */
+#define EHCI_CONF_CF 0x00000001 /* RW configure flag */
+
+#define EHCI_PORTSC(n) (0x40+(4*(n))) /* RO, RW, RWC Port Status reg */
+#define EHCI_PS_WKOC_E 0x00400000 /* RW wake on over current ena */
+#define EHCI_PS_WKDSCNNT_E 0x00200000 /* RW wake on disconnect ena */
+#define EHCI_PS_WKCNNT_E 0x00100000 /* RW wake on connect ena */
+#define EHCI_PS_PTC 0x000f0000 /* RW port test control */
+#define EHCI_PS_PIC 0x0000c000 /* RW port indicator control */
+#define EHCI_PS_PO 0x00002000 /* RW port owner */
+#define EHCI_PS_PP 0x00001000 /* RW,RO port power */
+#define EHCI_PS_LS 0x00000c00 /* RO line status */
+#define EHCI_PS_IS_LOWSPEED(x) (((x) & EHCI_PS_LS) == 0x00000400)
+#define EHCI_PS_PR 0x00000100 /* RW port reset */
+#define EHCI_PS_SUSP 0x00000080 /* RW suspend */
+#define EHCI_PS_FPR 0x00000040 /* RW force port resume */
+#define EHCI_PS_OCC 0x00000020 /* RWC over current change */
+#define EHCI_PS_OCA 0x00000010 /* RO over current active */
+#define EHCI_PS_PEC 0x00000008 /* RWC port enable change */
+#define EHCI_PS_PE 0x00000004 /* RW port enable */
+#define EHCI_PS_CSC 0x00000002 /* RWC connect status change */
+#define EHCI_PS_CS 0x00000001 /* RO connect status */
+#define EHCI_PS_CLEAR (EHCI_PS_OCC | EHCI_PS_PEC | EHCI_PS_CSC)
+
+#define EHCI_PORT_RESET_COMPLETE 2 /* ms */
+
+/*
+ * Registers not covered by EHCI specification
+ *
+ *
+ * EHCI_USBMODE register offset is different for cores with LPM support,
+ * bits are equal
+ */
+#define EHCI_USBMODE_NOLPM 0x68 /* RW USB Device mode reg (no LPM) */
+#define EHCI_USBMODE_LPM 0xC8 /* RW USB Device mode reg (LPM) */
+#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 */
+
+/*
+ * Actual port speed bits depends on EHCI_HOSTC(n) registers presence,
+ * speed encoding is equal
+ */
+#define EHCI_HOSTC(n) (0x80+(4*(n))) /* RO, RW Host mode control reg */
+#define EHCI_HOSTC_PSPD_SHIFT 25
+#define EHCI_HOSTC_PSPD_MASK 0x3
+
+#define EHCI_PORTSC_PSPD_SHIFT 26
+#define EHCI_PORTSC_PSPD_MASK 0x3
+
+#define EHCI_PORT_SPEED_FULL 0
+#define EHCI_PORT_SPEED_LOW 1
+#define EHCI_PORT_SPEED_HIGH 2
+#endif /* _EHCIREG_H_ */
diff --git a/sys/dev/usb/controller/generic_ehci.c b/sys/dev/usb/controller/generic_ehci.c
new file mode 100644
index 000000000000..bd7dc32b1ea8
--- /dev/null
+++ b/sys/dev/usb/controller/generic_ehci.c
@@ -0,0 +1,189 @@
+/*-
+ * Copyright (c) 2012 Ganbold Tsagaankhuu <ganbold@freebsd.org>
+ * Copyright (c) 2016 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Andrew Turner under
+ * sponsorship from the FreeBSD Foundation.
+ *
+ * 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.
+ */
+
+/*
+ * Generic EHCI driver based on the Allwinner A10 EHCI driver
+ */
+
+#include "opt_bus.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+
+#include <machine/bus.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ehci.h>
+#include <dev/usb/controller/ehcireg.h>
+
+#include "generic_ehci.h"
+
+int
+generic_ehci_attach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+ int rid;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = EHCI_MAX_DEVICES;
+ sc->sc_bus.dma_bits = 32;
+
+ /* get all DMA memory */
+ if (usb_bus_mem_alloc_all(&sc->sc_bus,
+ USB_GET_DMA_TAG(self), &ehci_iterate_hw_softc)) {
+ return (ENOMEM);
+ }
+
+ sc->sc_bus.usbrev = USB_REV_2_0;
+
+ rid = 0;
+ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (!sc->sc_io_res) {
+ device_printf(self, "Could not map memory\n");
+ goto error;
+ }
+
+ sc->sc_io_tag = rman_get_bustag(sc->sc_io_res);
+ sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = rman_get_size(sc->sc_io_res);
+
+ rid = 0;
+ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (sc->sc_irq_res == NULL) {
+ device_printf(self, "Could not allocate irq\n");
+ goto error;
+ }
+ sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY);
+ if (!sc->sc_bus.bdev) {
+ device_printf(self, "Could not add USB device\n");
+ goto error;
+ }
+ device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
+
+ strlcpy(sc->sc_vendor, "Generic", sizeof(sc->sc_vendor));
+
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (driver_intr_t *)ehci_interrupt, sc, &sc->sc_intr_hdl);
+ if (err) {
+ device_printf(self, "Could not setup irq, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ goto error;
+ }
+
+ sc->sc_flags |= EHCI_SCFLG_DONTRESET;
+
+ err = ehci_init(sc);
+ if (!err)
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ if (err)
+ goto error;
+
+ return (0);
+
+error:
+ generic_ehci_detach(self);
+ return (ENXIO);
+}
+
+int
+generic_ehci_detach(device_t self)
+{
+ ehci_softc_t *sc = device_get_softc(self);
+ int err;
+
+ /* during module unload there are lots of children leftover */
+ err = bus_generic_detach(self);
+ if (err != 0)
+ return (err);
+
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ /*
+ * only call ehci_detach() after ehci_init()
+ */
+ ehci_detach(sc);
+
+ err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl);
+
+ if (err)
+ /* XXX or should we panic? */
+ device_printf(self, "Could not tear down irq, %d\n",
+ err);
+ sc->sc_intr_hdl = NULL;
+ }
+
+ if (sc->sc_irq_res) {
+ bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ }
+ if (sc->sc_io_res) {
+ bus_release_resource(self, SYS_RES_MEMORY, 0,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+ usb_bus_mem_free_all(&sc->sc_bus, &ehci_iterate_hw_softc);
+
+ return (0);
+}
+
+static device_method_t ehci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_attach, generic_ehci_attach),
+ DEVMETHOD(device_detach, generic_ehci_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ DEVMETHOD_END
+};
+
+driver_t generic_ehci_driver = {
+ .name = "ehci",
+ .methods = ehci_methods,
+ .size = sizeof(ehci_softc_t),
+};
diff --git a/sys/dev/usb/controller/generic_ehci.h b/sys/dev/usb/controller/generic_ehci.h
new file mode 100644
index 000000000000..567bcefeddf1
--- /dev/null
+++ b/sys/dev/usb/controller/generic_ehci.h
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 2012 Ganbold Tsagaankhuu <ganbold@freebsd.org>
+ * Copyright (c) 2016 The FreeBSD Foundation
+ * Copyright (c) 2019 Andrew Turner <andrew@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by Andrew Turner under
+ * sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _GENERIC_EHCI_H_
+#define _GENERIC_EHCI_H_
+
+extern driver_t generic_ehci_driver;
+
+device_attach_t generic_ehci_attach;
+device_detach_t generic_ehci_detach;
+
+#endif /* !_GENERIC_EHCI_H_ */
diff --git a/sys/dev/usb/controller/generic_ehci_acpi.c b/sys/dev/usb/controller/generic_ehci_acpi.c
new file mode 100644
index 000000000000..d947215ad355
--- /dev/null
+++ b/sys/dev/usb/controller/generic_ehci_acpi.c
@@ -0,0 +1,81 @@
+/*-
+ * Copyright (c) 2012 Ganbold Tsagaankhuu <ganbold@freebsd.org>
+ * Copyright (c) 2016 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Andrew Turner under
+ * sponsorship from the FreeBSD Foundation.
+ *
+ * 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 "opt_bus.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ehci.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <dev/acpica/acpivar.h>
+
+#include "generic_ehci.h"
+
+static int
+generic_ehci_acpi_probe(device_t self)
+{
+ ACPI_HANDLE h;
+
+ if ((h = acpi_get_handle(self)) == NULL ||
+ !acpi_MatchHid(h, "PNP0D20"))
+ return (ENXIO);
+
+ device_set_desc(self, "Generic EHCI Controller");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static device_method_t ehci_acpi_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, generic_ehci_acpi_probe),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(ehci, ehci_acpi_driver, ehci_acpi_methods,
+ sizeof(ehci_softc_t), generic_ehci_driver);
+
+DRIVER_MODULE(ehci, acpi, ehci_acpi_driver, 0, 0);
+MODULE_DEPEND(ehci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/generic_ehci_fdt.c b/sys/dev/usb/controller/generic_ehci_fdt.c
new file mode 100644
index 000000000000..df2dc7fba4b9
--- /dev/null
+++ b/sys/dev/usb/controller/generic_ehci_fdt.c
@@ -0,0 +1,238 @@
+/*-
+ * Copyright (c) 2012 Ganbold Tsagaankhuu <ganbold@freebsd.org>
+ * Copyright (c) 2016 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by Andrew Turner under
+ * sponsorship from the FreeBSD Foundation.
+ *
+ * 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 "opt_bus.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ehci.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/clk/clk.h>
+#include <dev/hwreset/hwreset.h>
+#include <dev/phy/phy.h>
+#include <dev/phy/phy_usb.h>
+
+#include "generic_ehci.h"
+
+struct clk_list {
+ TAILQ_ENTRY(clk_list) next;
+ clk_t clk;
+};
+
+struct hwrst_list {
+ TAILQ_ENTRY(hwrst_list) next;
+ hwreset_t rst;
+};
+
+struct phy_list {
+ TAILQ_ENTRY(phy_list) next;
+ phy_t phy;
+};
+
+struct generic_ehci_fdt_softc {
+ ehci_softc_t ehci_sc;
+
+ TAILQ_HEAD(, clk_list) clk_list;
+ TAILQ_HEAD(, hwrst_list) rst_list;
+ TAILQ_HEAD(, phy_list) phy_list;
+};
+
+static device_probe_t generic_ehci_fdt_probe;
+static device_attach_t generic_ehci_fdt_attach;
+static device_detach_t generic_ehci_fdt_detach;
+
+static int
+generic_ehci_fdt_probe(device_t self)
+{
+
+ if (!ofw_bus_status_okay(self))
+ return (ENXIO);
+
+ if (!ofw_bus_is_compatible(self, "generic-ehci"))
+ return (ENXIO);
+
+ device_set_desc(self, "Generic EHCI Controller");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+generic_ehci_fdt_attach(device_t dev)
+{
+ int err;
+ struct generic_ehci_fdt_softc *sc;
+ struct clk_list *clkp;
+ clk_t clk;
+ struct hwrst_list *rstp;
+ hwreset_t rst;
+ struct phy_list *phyp;
+ phy_t phy;
+ int off;
+
+ sc = device_get_softc(dev);
+
+ TAILQ_INIT(&sc->clk_list);
+ /* Enable clock */
+ for (off = 0; clk_get_by_ofw_index(dev, 0, off, &clk) == 0; off++) {
+ err = clk_enable(clk);
+ if (err != 0) {
+ device_printf(dev, "Could not enable clock %s\n",
+ clk_get_name(clk));
+ goto error;
+ }
+ clkp = malloc(sizeof(*clkp), M_DEVBUF, M_WAITOK | M_ZERO);
+ clkp->clk = clk;
+ TAILQ_INSERT_TAIL(&sc->clk_list, clkp, next);
+ }
+
+ /* De-assert reset */
+ TAILQ_INIT(&sc->rst_list);
+ for (off = 0; hwreset_get_by_ofw_idx(dev, 0, off, &rst) == 0; off++) {
+ err = hwreset_deassert(rst);
+ if (err != 0) {
+ device_printf(dev, "Could not de-assert reset\n");
+ goto error;
+ }
+ rstp = malloc(sizeof(*rstp), M_DEVBUF, M_WAITOK | M_ZERO);
+ rstp->rst = rst;
+ TAILQ_INSERT_TAIL(&sc->rst_list, rstp, next);
+ }
+
+ /* Enable USB PHY */
+ TAILQ_INIT(&sc->phy_list);
+ for (off = 0; phy_get_by_ofw_idx(dev, 0, off, &phy) == 0; off++) {
+ err = phy_usb_set_mode(phy, PHY_USB_MODE_HOST);
+ if (err != 0) {
+ device_printf(dev, "Could not set phy to host mode\n");
+ goto error;
+ }
+ err = phy_enable(phy);
+ if (err != 0) {
+ device_printf(dev, "Could not enable phy\n");
+ goto error;
+ }
+ phyp = malloc(sizeof(*phyp), M_DEVBUF, M_WAITOK | M_ZERO);
+ phyp->phy = phy;
+ TAILQ_INSERT_TAIL(&sc->phy_list, phyp, next);
+ }
+
+ err = generic_ehci_attach(dev);
+ if (err != 0)
+ goto error;
+
+ return (0);
+
+error:
+ generic_ehci_fdt_detach(dev);
+ return (err);
+}
+
+static int
+generic_ehci_fdt_detach(device_t dev)
+{
+ struct generic_ehci_fdt_softc *sc;
+ struct clk_list *clk, *clk_tmp;
+ struct hwrst_list *rst, *rst_tmp;
+ struct phy_list *phy, *phy_tmp;
+ int err;
+
+ err = generic_ehci_detach(dev);
+ if (err != 0)
+ return (err);
+
+ sc = device_get_softc(dev);
+
+ /* Disable clock */
+ TAILQ_FOREACH_SAFE(clk, &sc->clk_list, next, clk_tmp) {
+ err = clk_disable(clk->clk);
+ if (err != 0)
+ device_printf(dev, "Could not disable clock %s\n",
+ clk_get_name(clk->clk));
+ err = clk_release(clk->clk);
+ if (err != 0)
+ device_printf(dev, "Could not release clock %s\n",
+ clk_get_name(clk->clk));
+ TAILQ_REMOVE(&sc->clk_list, clk, next);
+ free(clk, M_DEVBUF);
+ }
+
+ /* Assert reset */
+ TAILQ_FOREACH_SAFE(rst, &sc->rst_list, next, rst_tmp) {
+ hwreset_assert(rst->rst);
+ hwreset_release(rst->rst);
+ TAILQ_REMOVE(&sc->rst_list, rst, next);
+ free(rst, M_DEVBUF);
+ }
+
+ /* Disable phys */
+ TAILQ_FOREACH_SAFE(phy, &sc->phy_list, next, phy_tmp) {
+ err = phy_disable(phy->phy);
+ if (err != 0)
+ device_printf(dev, "Could not disable phy\n");
+ phy_release(phy->phy);
+ TAILQ_REMOVE(&sc->phy_list, phy, next);
+ free(phy, M_DEVBUF);
+ }
+
+ return (0);
+}
+
+static device_method_t ehci_fdt_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, generic_ehci_fdt_probe),
+ DEVMETHOD(device_attach, generic_ehci_fdt_attach),
+ DEVMETHOD(device_detach, generic_ehci_fdt_detach),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(ehci, ehci_fdt_driver, ehci_fdt_methods,
+ sizeof(ehci_softc_t), generic_ehci_driver);
+
+DRIVER_MODULE(generic_ehci, simplebus, ehci_fdt_driver, 0, 0);
+MODULE_DEPEND(generic_ehci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/generic_ohci.c b/sys/dev/usb/controller/generic_ohci.c
new file mode 100644
index 000000000000..5c0de59074d2
--- /dev/null
+++ b/sys/dev/usb/controller/generic_ohci.c
@@ -0,0 +1,330 @@
+/*-
+ * Copyright (c) 2016 Emmanuel Vadot <manu@freebsd.org> All rights reserved.
+ * Copyright (c) 2006 M. Warner Losh <imp@FreeBSD.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.
+ *
+ * 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.
+ */
+
+/*
+ * Generic OHCI driver based on AT91 OHCI
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+
+#include <machine/bus.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/ohci.h>
+#include <dev/usb/controller/ohcireg.h>
+
+#include <dev/clk/clk.h>
+#include <dev/hwreset/hwreset.h>
+#include <dev/phy/phy.h>
+#include <dev/phy/phy_usb.h>
+
+#include "generic_usb_if.h"
+
+struct clk_list {
+ TAILQ_ENTRY(clk_list) next;
+ clk_t clk;
+};
+struct phy_list {
+ TAILQ_ENTRY(phy_list) next;
+ phy_t phy;
+};
+struct hwrst_list {
+ TAILQ_ENTRY(hwrst_list) next;
+ hwreset_t rst;
+};
+
+struct generic_ohci_softc {
+ ohci_softc_t ohci_sc;
+
+ TAILQ_HEAD(, clk_list) clk_list;
+ TAILQ_HEAD(, phy_list) phy_list;
+ TAILQ_HEAD(, hwrst_list) rst_list;
+};
+
+static int generic_ohci_detach(device_t);
+
+static int
+generic_ohci_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (!ofw_bus_is_compatible(dev, "generic-ohci"))
+ return (ENXIO);
+
+ device_set_desc(dev, "Generic OHCI Controller");
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+generic_ohci_attach(device_t dev)
+{
+ struct generic_ohci_softc *sc = device_get_softc(dev);
+ int err, rid;
+ int off;
+ struct clk_list *clkp;
+ struct phy_list *phyp;
+ struct hwrst_list *rstp;
+ clk_t clk;
+ phy_t phy;
+ hwreset_t rst;
+
+ sc->ohci_sc.sc_bus.parent = dev;
+ sc->ohci_sc.sc_bus.devices = sc->ohci_sc.sc_devices;
+ sc->ohci_sc.sc_bus.devices_max = OHCI_MAX_DEVICES;
+ sc->ohci_sc.sc_bus.dma_bits = 32;
+
+ /* get all DMA memory */
+ if (usb_bus_mem_alloc_all(&sc->ohci_sc.sc_bus,
+ USB_GET_DMA_TAG(dev), &ohci_iterate_hw_softc)) {
+ return (ENOMEM);
+ }
+
+ rid = 0;
+ sc->ohci_sc.sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
+ &rid, RF_ACTIVE);
+ if (sc->ohci_sc.sc_io_res == 0) {
+ err = ENOMEM;
+ goto error;
+ }
+
+ sc->ohci_sc.sc_io_tag = rman_get_bustag(sc->ohci_sc.sc_io_res);
+ sc->ohci_sc.sc_io_hdl = rman_get_bushandle(sc->ohci_sc.sc_io_res);
+ sc->ohci_sc.sc_io_size = rman_get_size(sc->ohci_sc.sc_io_res);
+
+ rid = 0;
+ sc->ohci_sc.sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+ RF_ACTIVE);
+ if (sc->ohci_sc.sc_irq_res == 0) {
+ err = ENXIO;
+ goto error;
+ }
+ sc->ohci_sc.sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY);
+ if (sc->ohci_sc.sc_bus.bdev == 0) {
+ err = ENXIO;
+ goto error;
+ }
+ device_set_ivars(sc->ohci_sc.sc_bus.bdev, &sc->ohci_sc.sc_bus);
+
+ strlcpy(sc->ohci_sc.sc_vendor, "Generic",
+ sizeof(sc->ohci_sc.sc_vendor));
+
+ err = bus_setup_intr(dev, sc->ohci_sc.sc_irq_res,
+ INTR_TYPE_BIO | INTR_MPSAFE, NULL,
+ (driver_intr_t *)ohci_interrupt, sc, &sc->ohci_sc.sc_intr_hdl);
+ if (err) {
+ sc->ohci_sc.sc_intr_hdl = NULL;
+ goto error;
+ }
+
+ TAILQ_INIT(&sc->clk_list);
+ /* Enable clock */
+ for (off = 0; clk_get_by_ofw_index(dev, 0, off, &clk) == 0; off++) {
+ err = clk_enable(clk);
+ if (err != 0) {
+ device_printf(dev, "Could not enable clock %s\n",
+ clk_get_name(clk));
+ goto error;
+ }
+ clkp = malloc(sizeof(*clkp), M_DEVBUF, M_WAITOK | M_ZERO);
+ clkp->clk = clk;
+ TAILQ_INSERT_TAIL(&sc->clk_list, clkp, next);
+ }
+
+ /* De-assert reset */
+ TAILQ_INIT(&sc->rst_list);
+ for (off = 0; hwreset_get_by_ofw_idx(dev, 0, off, &rst) == 0; off++) {
+ err = hwreset_deassert(rst);
+ if (err != 0) {
+ device_printf(dev, "Could not de-assert reset\n");
+ goto error;
+ }
+ rstp = malloc(sizeof(*rstp), M_DEVBUF, M_WAITOK | M_ZERO);
+ rstp->rst = rst;
+ TAILQ_INSERT_TAIL(&sc->rst_list, rstp, next);
+ }
+
+ /* Enable phy */
+ TAILQ_INIT(&sc->phy_list);
+ for (off = 0; phy_get_by_ofw_idx(dev, 0, off, &phy) == 0; off++) {
+ err = phy_usb_set_mode(phy, PHY_USB_MODE_HOST);
+ if (err != 0) {
+ device_printf(dev, "Could not set phy to host mode\n");
+ goto error;
+ }
+ err = phy_enable(phy);
+ if (err != 0) {
+ device_printf(dev, "Could not enable phy\n");
+ goto error;
+ }
+ phyp = malloc(sizeof(*phyp), M_DEVBUF, M_WAITOK | M_ZERO);
+ phyp->phy = phy;
+ TAILQ_INSERT_TAIL(&sc->phy_list, phyp, next);
+ }
+
+ if (GENERIC_USB_INIT(dev) != 0) {
+ err = ENXIO;
+ goto error;
+ }
+
+ err = ohci_init(&sc->ohci_sc);
+ if (err == 0)
+ err = device_probe_and_attach(sc->ohci_sc.sc_bus.bdev);
+ if (err)
+ goto error;
+
+ return (0);
+error:
+ generic_ohci_detach(dev);
+ return (err);
+}
+
+static int
+generic_ohci_detach(device_t dev)
+{
+ struct generic_ohci_softc *sc = device_get_softc(dev);
+ int err;
+ struct clk_list *clk, *clk_tmp;
+ struct phy_list *phy, *phy_tmp;
+ struct hwrst_list *rst, *rst_tmp;
+
+ /* during module unload there are lots of children leftover */
+ err = bus_generic_detach(dev);
+ if (err != 0)
+ return (err);
+
+ /*
+ * Put the controller into reset, then disable clocks and do
+ * the MI tear down. We have to disable the clocks/hardware
+ * after we do the rest of the teardown. We also disable the
+ * clocks in the opposite order we acquire them, but that
+ * doesn't seem to be absolutely necessary. We free up the
+ * clocks after we disable them, so the system could, in
+ * theory, reuse them.
+ */
+ bus_space_write_4(sc->ohci_sc.sc_io_tag, sc->ohci_sc.sc_io_hdl,
+ OHCI_CONTROL, 0);
+
+ if (sc->ohci_sc.sc_irq_res && sc->ohci_sc.sc_intr_hdl) {
+ /*
+ * only call ohci_detach() after ohci_init()
+ */
+ ohci_detach(&sc->ohci_sc);
+
+ err = bus_teardown_intr(dev, sc->ohci_sc.sc_irq_res,
+ sc->ohci_sc.sc_intr_hdl);
+ sc->ohci_sc.sc_intr_hdl = NULL;
+ }
+ if (sc->ohci_sc.sc_irq_res) {
+ bus_release_resource(dev, SYS_RES_IRQ, 0,
+ sc->ohci_sc.sc_irq_res);
+ sc->ohci_sc.sc_irq_res = NULL;
+ }
+ if (sc->ohci_sc.sc_io_res) {
+ bus_release_resource(dev, SYS_RES_MEMORY, 0,
+ sc->ohci_sc.sc_io_res);
+ sc->ohci_sc.sc_io_res = NULL;
+ }
+ usb_bus_mem_free_all(&sc->ohci_sc.sc_bus, &ohci_iterate_hw_softc);
+
+ /* Disable phy */
+ TAILQ_FOREACH_SAFE(phy, &sc->phy_list, next, phy_tmp) {
+ err = phy_disable(phy->phy);
+ if (err != 0)
+ device_printf(dev, "Could not disable phy\n");
+ phy_release(phy->phy);
+ TAILQ_REMOVE(&sc->phy_list, phy, next);
+ free(phy, M_DEVBUF);
+ }
+
+ /* Assert reset */
+ TAILQ_FOREACH_SAFE(rst, &sc->rst_list, next, rst_tmp) {
+ hwreset_assert(rst->rst);
+ hwreset_release(rst->rst);
+ TAILQ_REMOVE(&sc->rst_list, rst, next);
+ free(rst, M_DEVBUF);
+ }
+
+ /* Disable clock */
+ TAILQ_FOREACH_SAFE(clk, &sc->clk_list, next, clk_tmp) {
+ err = clk_disable(clk->clk);
+ if (err != 0)
+ device_printf(dev, "Could not disable clock %s\n",
+ clk_get_name(clk->clk));
+ err = clk_release(clk->clk);
+ if (err != 0)
+ device_printf(dev, "Could not release clock %s\n",
+ clk_get_name(clk->clk));
+ TAILQ_REMOVE(&sc->clk_list, clk, next);
+ free(clk, M_DEVBUF);
+ }
+
+ if (GENERIC_USB_DEINIT(dev) != 0)
+ return (ENXIO);
+
+ return (0);
+}
+
+static device_method_t generic_ohci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, generic_ohci_probe),
+ DEVMETHOD(device_attach, generic_ohci_attach),
+ DEVMETHOD(device_detach, generic_ohci_detach),
+
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ DEVMETHOD_END
+};
+
+driver_t generic_ohci_driver = {
+ .name = "ohci",
+ .methods = generic_ohci_methods,
+ .size = sizeof(struct generic_ohci_softc),
+};
+
+DRIVER_MODULE(ohci, simplebus, generic_ohci_driver, 0, 0);
+MODULE_DEPEND(ohci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/generic_usb_if.m b/sys/dev/usb/controller/generic_usb_if.m
new file mode 100644
index 000000000000..22969a0b71b3
--- /dev/null
+++ b/sys/dev/usb/controller/generic_usb_if.m
@@ -0,0 +1,58 @@
+#-
+# Copyright (c) 2016 Emmanuel Vadot <manu@freebsd.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.
+#
+# 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.
+#
+#
+
+INTERFACE generic_usb;
+
+CODE {
+ static int
+ generic_usb_default_init(device_t dev)
+ {
+ return (0);
+ }
+
+ static int
+ generic_usb_default_deinit(device_t dev)
+ {
+ return (0);
+ }
+};
+
+HEADER {
+};
+
+#
+# Initialize the SoC bits
+#
+METHOD int init {
+ device_t dev;
+} DEFAULT generic_usb_default_init;
+
+#
+# Deinitialize the SoC bits
+#
+METHOD int deinit {
+ device_t dev;
+} DEFAULT generic_usb_default_deinit;
diff --git a/sys/dev/usb/controller/generic_xhci.c b/sys/dev/usb/controller/generic_xhci.c
new file mode 100644
index 000000000000..16bda77e043d
--- /dev/null
+++ b/sys/dev/usb/controller/generic_xhci.c
@@ -0,0 +1,198 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2015 Semihalf.
+ * Copyright (c) 2015 Stormshield.
+ * 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/priv.h>
+#include <sys/rman.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/xhci.h>
+#include <dev/usb/controller/xhcireg.h>
+
+#include "generic_xhci.h"
+
+#if __SIZEOF_LONG__ == 8
+#define IS_DMA_32B 0
+#elif __SIZEOF_LONG__ == 4
+#define IS_DMA_32B 1
+#else
+#error unsupported long size
+#endif
+
+int
+generic_xhci_attach(device_t dev)
+{
+ struct xhci_softc *sc = device_get_softc(dev);
+ int err = 0, rid = 0;
+
+ sc->sc_bus.parent = dev;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = XHCI_MAX_DEVICES;
+
+ sc->sc_io_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (sc->sc_io_res == NULL) {
+ device_printf(dev, "Failed to map memory\n");
+ generic_xhci_detach(dev);
+ return (ENXIO);
+ }
+
+ sc->sc_io_tag = rman_get_bustag(sc->sc_io_res);
+ sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = rman_get_size(sc->sc_io_res);
+
+ sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (sc->sc_irq_res == NULL) {
+ device_printf(dev, "Failed to allocate IRQ\n");
+ generic_xhci_detach(dev);
+ return (ENXIO);
+ }
+
+ sc->sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY);
+ if (sc->sc_bus.bdev == NULL) {
+ device_printf(dev, "Failed to add USB device\n");
+ generic_xhci_detach(dev);
+ return (ENXIO);
+ }
+
+ device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
+
+ sprintf(sc->sc_vendor, XHCI_HC_VENDOR);
+ device_set_desc(sc->sc_bus.bdev, XHCI_HC_DEVSTR);
+
+ err = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (driver_intr_t *)xhci_interrupt, sc, &sc->sc_intr_hdl);
+ if (err != 0) {
+ device_printf(dev, "Failed to setup error IRQ, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ generic_xhci_detach(dev);
+ return (err);
+ }
+
+ err = xhci_init(sc, dev,
+ (sc->sc_quirks & XHCI_QUIRK_DMA_32B) == 0 ? IS_DMA_32B : 1);
+ if (err != 0) {
+ device_printf(dev, "Failed to init XHCI, with error %d\n", err);
+ generic_xhci_detach(dev);
+ return (ENXIO);
+ }
+
+ err = xhci_start_controller(sc);
+ if (err != 0) {
+ device_printf(dev, "Failed to start XHCI controller, with error %d\n", err);
+ generic_xhci_detach(dev);
+ return (ENXIO);
+ }
+
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ if (err != 0) {
+ device_printf(dev, "Failed to initialize USB, with error %d\n", err);
+ generic_xhci_detach(dev);
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+int
+generic_xhci_detach(device_t dev)
+{
+ struct xhci_softc *sc = device_get_softc(dev);
+ int err;
+
+ /* during module unload there are lots of children leftover */
+ err = bus_generic_detach(dev);
+ if (err != 0)
+ return (err);
+
+ if (sc->sc_irq_res != NULL && sc->sc_intr_hdl != NULL) {
+ err = bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intr_hdl);
+ if (err != 0)
+ device_printf(dev, "Could not tear down irq, %d\n",
+ err);
+ sc->sc_intr_hdl = NULL;
+ }
+
+ if (sc->sc_irq_res != NULL) {
+ bus_release_resource(dev, SYS_RES_IRQ,
+ rman_get_rid(sc->sc_irq_res), sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ }
+
+ if (sc->sc_io_res != NULL) {
+ bus_release_resource(dev, SYS_RES_MEMORY,
+ rman_get_rid(sc->sc_io_res), sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+
+ xhci_uninit(sc);
+
+ return (0);
+}
+
+static device_method_t xhci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_attach, generic_xhci_attach),
+ DEVMETHOD(device_detach, generic_xhci_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ DEVMETHOD_END
+};
+
+driver_t generic_xhci_driver = {
+ "xhci",
+ xhci_methods,
+ sizeof(struct xhci_softc),
+};
diff --git a/sys/dev/usb/controller/generic_xhci.h b/sys/dev/usb/controller/generic_xhci.h
new file mode 100644
index 000000000000..6753140e111f
--- /dev/null
+++ b/sys/dev/usb/controller/generic_xhci.h
@@ -0,0 +1,43 @@
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2015 Semihalf.
+ * Copyright (c) 2015 Stormshield.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#ifndef _GENERIC_XHCI_H_
+#define _GENERIC_XHCI_H_
+
+#define XHCI_HC_DEVSTR "Generic USB 3.0 controller"
+#define XHCI_HC_VENDOR "Generic"
+
+extern driver_t generic_xhci_driver;
+
+device_attach_t generic_xhci_attach;
+device_detach_t generic_xhci_detach;
+
+#endif /* !_GENERIC_XHCI_H_ */
diff --git a/sys/dev/usb/controller/generic_xhci_acpi.c b/sys/dev/usb/controller/generic_xhci_acpi.c
new file mode 100644
index 000000000000..e24fe1b1bcc3
--- /dev/null
+++ b/sys/dev/usb/controller/generic_xhci_acpi.c
@@ -0,0 +1,81 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Val Packett <val@packett.cool>
+ *
+ * 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 "opt_acpi.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/xhci.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <dev/acpica/acpivar.h>
+
+#include "generic_xhci.h"
+
+static char *xhci_ids[] = {
+ "PNP0D10",
+ "PNP0D15",
+ NULL,
+};
+
+static int
+generic_xhci_acpi_probe(device_t dev)
+{
+ if (ACPI_ID_PROBE(device_get_parent(dev), dev, xhci_ids, NULL) >= 0)
+ return (ENXIO);
+
+ device_set_desc(dev, XHCI_HC_DEVSTR);
+
+ return (BUS_PROBE_GENERIC);
+}
+
+static device_method_t xhci_acpi_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, generic_xhci_acpi_probe),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(xhci, xhci_acpi_driver, xhci_acpi_methods,
+ sizeof(struct xhci_softc), generic_xhci_driver);
+
+DRIVER_MODULE(xhci, acpi, xhci_acpi_driver, 0, 0);
+MODULE_DEPEND(xhci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/generic_xhci_fdt.c b/sys/dev/usb/controller/generic_xhci_fdt.c
new file mode 100644
index 000000000000..8aab938cbc77
--- /dev/null
+++ b/sys/dev/usb/controller/generic_xhci_fdt.c
@@ -0,0 +1,135 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2015 Semihalf.
+ * Copyright (c) 2015 Stormshield.
+ * 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 "opt_bus.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/xhci.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/phy/phy.h>
+
+#include "generic_xhci.h"
+
+/* Flags for the OFW compat data table */
+#define XHCI_FDT_MATCH 0x01
+#define XHCI_FDT_32BIT_DMA 0x02 /* Controller needs 32-bit DMA */
+
+static struct ofw_compat_data compat_data[] = {
+ {"marvell,armada-380-xhci", XHCI_FDT_MATCH},
+ {"marvell,armada3700-xhci", XHCI_FDT_MATCH},
+ {"marvell,armada-8k-xhci", XHCI_FDT_MATCH},
+ {"generic-xhci", XHCI_FDT_MATCH},
+ {NULL, 0}
+};
+
+static int
+generic_xhci_fdt_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
+ return (ENXIO);
+
+ device_set_desc(dev, XHCI_HC_DEVSTR);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+generic_xhci_fdt_attach(device_t dev)
+{
+ struct xhci_softc *sc = device_get_softc(dev);
+ phandle_t node;
+ phy_t phy;
+ int flags;
+
+ node = ofw_bus_get_node(dev);
+ if (phy_get_by_ofw_property(dev, node, "usb-phy", &phy) == 0)
+ if (phy_enable(phy) != 0)
+ device_printf(dev, "Cannot enable phy\n");
+
+ flags = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
+ if ((flags & XHCI_FDT_32BIT_DMA) != 0)
+ sc->sc_quirks |= XHCI_QUIRK_DMA_32B;
+
+ return (generic_xhci_attach(dev));
+}
+
+static int
+generic_xhci_fdt_detach(device_t dev)
+{
+ phandle_t node;
+ phy_t phy;
+ int err;
+
+ err = generic_xhci_detach(dev);
+ if (err != 0)
+ return (err);
+
+ node = ofw_bus_get_node(dev);
+ if (phy_get_by_ofw_property(dev, node, "usb-phy", &phy) == 0)
+ phy_release(phy);
+
+ return (0);
+}
+
+static device_method_t xhci_fdt_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, generic_xhci_fdt_probe),
+ DEVMETHOD(device_attach, generic_xhci_fdt_attach),
+ DEVMETHOD(device_detach, generic_xhci_fdt_detach),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(xhci, xhci_fdt_driver, xhci_fdt_methods,
+ sizeof(struct xhci_softc), generic_xhci_driver);
+
+DRIVER_MODULE(xhci, simplebus, xhci_fdt_driver, 0, 0);
+MODULE_DEPEND(xhci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/musb_otg.c b/sys/dev/usb/controller/musb_otg.c
new file mode 100644
index 000000000000..caede049593b
--- /dev/null
+++ b/sys/dev/usb/controller/musb_otg.c
@@ -0,0 +1,4188 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Thanks to Mentor Graphics for providing a reference driver for this USB chip
+ * at their homepage.
+ */
+
+/*
+ * This file contains the driver for the Mentor Graphics Inventra USB
+ * 2.0 High Speed Dual-Role controller.
+ *
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#define USB_DEBUG_VAR musbotgdebug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#include <dev/usb/controller/musb_otg.h>
+
+#define MUSBOTG_INTR_ENDPT 1
+
+#define MUSBOTG_BUS2SC(bus) \
+ __containerof(bus, struct musbotg_softc, sc_bus)
+
+#define MUSBOTG_PC2SC(pc) \
+ MUSBOTG_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus)
+
+#ifdef USB_DEBUG
+static int musbotgdebug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, musbotg, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB musbotg");
+SYSCTL_INT(_hw_usb_musbotg, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &musbotgdebug, 0, "Debug level");
+#endif
+
+#define MAX_NAK_TO 16
+
+/* prototypes */
+
+static const struct usb_bus_methods musbotg_bus_methods;
+static const struct usb_pipe_methods musbotg_device_bulk_methods;
+static const struct usb_pipe_methods musbotg_device_ctrl_methods;
+static const struct usb_pipe_methods musbotg_device_intr_methods;
+static const struct usb_pipe_methods musbotg_device_isoc_methods;
+
+/* Control transfers: Device mode */
+static musbotg_cmd_t musbotg_dev_ctrl_setup_rx;
+static musbotg_cmd_t musbotg_dev_ctrl_data_rx;
+static musbotg_cmd_t musbotg_dev_ctrl_data_tx;
+static musbotg_cmd_t musbotg_dev_ctrl_status;
+
+/* Control transfers: Host mode */
+static musbotg_cmd_t musbotg_host_ctrl_setup_tx;
+static musbotg_cmd_t musbotg_host_ctrl_data_rx;
+static musbotg_cmd_t musbotg_host_ctrl_data_tx;
+static musbotg_cmd_t musbotg_host_ctrl_status_rx;
+static musbotg_cmd_t musbotg_host_ctrl_status_tx;
+
+/* Bulk, Interrupt, Isochronous: Device mode */
+static musbotg_cmd_t musbotg_dev_data_rx;
+static musbotg_cmd_t musbotg_dev_data_tx;
+
+/* Bulk, Interrupt, Isochronous: Host mode */
+static musbotg_cmd_t musbotg_host_data_rx;
+static musbotg_cmd_t musbotg_host_data_tx;
+
+static void musbotg_device_done(struct usb_xfer *, usb_error_t);
+static void musbotg_do_poll(struct usb_bus *);
+static void musbotg_standard_done(struct usb_xfer *);
+static void musbotg_interrupt_poll(struct musbotg_softc *);
+static void musbotg_root_intr(struct musbotg_softc *);
+static int musbotg_channel_alloc(struct musbotg_softc *, struct musbotg_td *td, uint8_t);
+static void musbotg_channel_free(struct musbotg_softc *, struct musbotg_td *td);
+static void musbotg_ep_int_set(struct musbotg_softc *sc, int channel, int on);
+
+/*
+ * Here is a configuration that the chip supports.
+ */
+static const struct usb_hw_ep_profile musbotg_ep_profile[1] = {
+ [0] = {
+ .max_in_frame_size = 64,/* fixed */
+ .max_out_frame_size = 64, /* fixed */
+ .is_simplex = 1,
+ .support_control = 1,
+ }
+};
+
+static const struct musb_otg_ep_cfg musbotg_ep_default[] = {
+ {
+ .ep_end = 1,
+ .ep_fifosz_shift = 12,
+ .ep_fifosz_reg = MUSB2_VAL_FIFOSZ_4096 | MUSB2_MASK_FIFODB,
+ },
+ {
+ .ep_end = 7,
+ .ep_fifosz_shift = 10,
+ .ep_fifosz_reg = MUSB2_VAL_FIFOSZ_512 | MUSB2_MASK_FIFODB,
+ },
+ {
+ .ep_end = 15,
+ .ep_fifosz_shift = 7,
+ .ep_fifosz_reg = MUSB2_VAL_FIFOSZ_128,
+ },
+ {
+ .ep_end = -1,
+ },
+};
+
+static int
+musbotg_channel_alloc(struct musbotg_softc *sc, struct musbotg_td *td, uint8_t is_tx)
+{
+ int ch;
+ int ep;
+
+ ep = td->ep_no;
+
+ /* In device mode each EP got its own channel */
+ if (sc->sc_mode == MUSB2_DEVICE_MODE) {
+ musbotg_ep_int_set(sc, ep, 1);
+ return (ep);
+ }
+
+ /*
+ * All control transactions go through EP0
+ */
+ if (ep == 0) {
+ if (sc->sc_channel_mask & (1 << 0))
+ return (-1);
+ sc->sc_channel_mask |= (1 << 0);
+ musbotg_ep_int_set(sc, ep, 1);
+ return (0);
+ }
+
+ for (ch = sc->sc_ep_max; ch != 0; ch--) {
+ if (sc->sc_channel_mask & (1 << ch))
+ continue;
+
+ /* check FIFO size requirement */
+ if (is_tx) {
+ if (td->max_frame_size >
+ sc->sc_hw_ep_profile[ch].max_in_frame_size)
+ continue;
+ } else {
+ if (td->max_frame_size >
+ sc->sc_hw_ep_profile[ch].max_out_frame_size)
+ continue;
+ }
+ sc->sc_channel_mask |= (1 << ch);
+ musbotg_ep_int_set(sc, ch, 1);
+ return (ch);
+ }
+
+ DPRINTFN(-1, "No available channels. Mask: %04x\n", sc->sc_channel_mask);
+
+ return (-1);
+}
+
+static void
+musbotg_channel_free(struct musbotg_softc *sc, struct musbotg_td *td)
+{
+
+ DPRINTFN(1, "ep_no=%d\n", td->channel);
+
+ if (sc->sc_mode == MUSB2_DEVICE_MODE)
+ return;
+
+ if (td == NULL)
+ return;
+ if (td->channel == -1)
+ return;
+
+ musbotg_ep_int_set(sc, td->channel, 0);
+ sc->sc_channel_mask &= ~(1 << td->channel);
+
+ td->channel = -1;
+}
+
+static void
+musbotg_get_hw_ep_profile(struct usb_device *udev,
+ const struct usb_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+ struct musbotg_softc *sc;
+
+ sc = MUSBOTG_BUS2SC(udev->bus);
+
+ if (ep_addr == 0) {
+ /* control endpoint */
+ *ppf = musbotg_ep_profile;
+ } else if (ep_addr <= sc->sc_ep_max) {
+ /* other endpoints */
+ *ppf = sc->sc_hw_ep_profile + ep_addr;
+ } else {
+ *ppf = NULL;
+ }
+}
+
+static void
+musbotg_clocks_on(struct musbotg_softc *sc)
+{
+ if (sc->sc_flags.clocks_off &&
+ sc->sc_flags.port_powered) {
+ DPRINTFN(4, "\n");
+
+ if (sc->sc_clocks_on) {
+ (sc->sc_clocks_on) (sc->sc_clocks_arg);
+ }
+ sc->sc_flags.clocks_off = 0;
+
+ /* XXX enable Transceiver */
+ }
+}
+
+static void
+musbotg_clocks_off(struct musbotg_softc *sc)
+{
+ if (!sc->sc_flags.clocks_off) {
+ DPRINTFN(4, "\n");
+
+ /* XXX disable Transceiver */
+
+ if (sc->sc_clocks_off) {
+ (sc->sc_clocks_off) (sc->sc_clocks_arg);
+ }
+ sc->sc_flags.clocks_off = 1;
+ }
+}
+
+static void
+musbotg_pull_common(struct musbotg_softc *sc, uint8_t on)
+{
+ uint8_t temp;
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ if (on)
+ temp |= MUSB2_MASK_SOFTC;
+ else
+ temp &= ~MUSB2_MASK_SOFTC;
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp);
+}
+
+static void
+musbotg_pull_up(struct musbotg_softc *sc)
+{
+ /* pullup D+, if possible */
+
+ if (!sc->sc_flags.d_pulled_up &&
+ sc->sc_flags.port_powered) {
+ sc->sc_flags.d_pulled_up = 1;
+ musbotg_pull_common(sc, 1);
+ }
+}
+
+static void
+musbotg_pull_down(struct musbotg_softc *sc)
+{
+ /* pulldown D+, if possible */
+
+ if (sc->sc_flags.d_pulled_up) {
+ sc->sc_flags.d_pulled_up = 0;
+ musbotg_pull_common(sc, 0);
+ }
+}
+
+static void
+musbotg_suspend_host(struct musbotg_softc *sc)
+{
+ uint8_t temp;
+
+ if (sc->sc_flags.status_suspend) {
+ return;
+ }
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ temp |= MUSB2_MASK_SUSPMODE;
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp);
+ sc->sc_flags.status_suspend = 1;
+}
+
+static void
+musbotg_wakeup_host(struct musbotg_softc *sc)
+{
+ uint8_t temp;
+
+ if (!(sc->sc_flags.status_suspend)) {
+ return;
+ }
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ temp &= ~MUSB2_MASK_SUSPMODE;
+ temp |= MUSB2_MASK_RESUME;
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp);
+
+ /* wait 20 milliseconds */
+ /* Wait for reset to complete. */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50);
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ temp &= ~MUSB2_MASK_RESUME;
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp);
+
+ sc->sc_flags.status_suspend = 0;
+}
+
+static void
+musbotg_wakeup_peer(struct musbotg_softc *sc)
+{
+ uint8_t temp;
+
+ if (!(sc->sc_flags.status_suspend)) {
+ return;
+ }
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ temp |= MUSB2_MASK_RESUME;
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp);
+
+ /* wait 8 milliseconds */
+ /* Wait for reset to complete. */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 125);
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ temp &= ~MUSB2_MASK_RESUME;
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, temp);
+}
+
+static void
+musbotg_set_address(struct musbotg_softc *sc, uint8_t addr)
+{
+ DPRINTFN(4, "addr=%d\n", addr);
+ addr &= 0x7F;
+ MUSB2_WRITE_1(sc, MUSB2_REG_FADDR, addr);
+}
+
+static uint8_t
+musbotg_dev_ctrl_setup_rx(struct musbotg_td *td)
+{
+ struct musbotg_softc *sc;
+ struct usb_device_request req;
+ uint16_t count;
+ uint8_t csr;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ if (td->channel == -1)
+ td->channel = musbotg_channel_alloc(sc, td, 0);
+
+ /* EP0 is busy, wait */
+ if (td->channel == -1)
+ return (1);
+
+ DPRINTFN(1, "ep_no=%d\n", td->channel);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ /*
+ * NOTE: If DATAEND is set we should not call the
+ * callback, hence the status stage is not complete.
+ */
+ if (csr & MUSB2_MASK_CSR0L_DATAEND) {
+ /* do not stall at this point */
+ td->did_stall = 1;
+ /* wait for interrupt */
+ DPRINTFN(1, "CSR0 DATAEND\n");
+ goto not_complete;
+ }
+
+ if (csr & MUSB2_MASK_CSR0L_SENTSTALL) {
+ /* clear SENTSTALL */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ /* get latest status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ /* update EP0 state */
+ sc->sc_ep0_busy = 0;
+ }
+ if (csr & MUSB2_MASK_CSR0L_SETUPEND) {
+ /* clear SETUPEND */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_SETUPEND_CLR);
+ /* get latest status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ /* update EP0 state */
+ sc->sc_ep0_busy = 0;
+ }
+ if (sc->sc_ep0_busy) {
+ DPRINTFN(1, "EP0 BUSY\n");
+ goto not_complete;
+ }
+ if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) {
+ goto not_complete;
+ }
+ /* get the packet byte count */
+ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT);
+
+ /* verify data length */
+ if (count != td->remainder) {
+ DPRINTFN(1, "Invalid SETUP packet "
+ "length, %d bytes\n", count);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_RXPKTRDY_CLR);
+ /* don't clear stall */
+ td->did_stall = 1;
+ goto not_complete;
+ }
+ if (count != sizeof(req)) {
+ DPRINTFN(1, "Unsupported SETUP packet "
+ "length, %d bytes\n", count);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_RXPKTRDY_CLR);
+ /* don't clear stall */
+ td->did_stall = 1;
+ goto not_complete;
+ }
+ /* clear did stall flag */
+ td->did_stall = 0;
+
+ /* receive data */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), (void *)&req, sizeof(req));
+
+ /* copy data into real buffer */
+ usbd_copy_in(td->pc, 0, &req, sizeof(req));
+
+ td->offset = sizeof(req);
+ td->remainder = 0;
+
+ /* set pending command */
+ sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR;
+
+ /* we need set stall or dataend after this */
+ sc->sc_ep0_busy = 1;
+
+ /* sneak peek the set address */
+ if ((req.bmRequestType == UT_WRITE_DEVICE) &&
+ (req.bRequest == UR_SET_ADDRESS)) {
+ sc->sc_dv_addr = req.wValue[0] & 0x7F;
+ } else {
+ sc->sc_dv_addr = 0xFF;
+ }
+
+ musbotg_channel_free(sc, td);
+ return (0); /* complete */
+
+not_complete:
+ /* abort any ongoing transfer */
+ if (!td->did_stall) {
+ DPRINTFN(4, "stalling\n");
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_SENDSTALL);
+ td->did_stall = 1;
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_host_ctrl_setup_tx(struct musbotg_td *td)
+{
+ struct musbotg_softc *sc;
+ struct usb_device_request req;
+ uint8_t csr, csrh;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ if (td->channel == -1)
+ td->channel = musbotg_channel_alloc(sc, td, 1);
+
+ /* EP0 is busy, wait */
+ if (td->channel == -1)
+ return (1);
+
+ DPRINTFN(1, "ep_no=%d\n", td->channel);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ /* Not ready yet yet */
+ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY)
+ return (1);
+
+ /* Failed */
+ if (csr & (MUSB2_MASK_CSR0L_RXSTALL |
+ MUSB2_MASK_CSR0L_ERROR))
+ {
+ /* Clear status bit */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ DPRINTFN(1, "error bit set, csr=0x%02x\n", csr);
+ td->error = 1;
+ }
+
+ if (csr & MUSB2_MASK_CSR0L_NAKTIMO) {
+ DPRINTFN(1, "NAK timeout\n");
+
+ if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) {
+ csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH);
+ csrh |= MUSB2_MASK_CSR0H_FFLUSH;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) {
+ csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH);
+ csrh |= MUSB2_MASK_CSR0H_FFLUSH;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ }
+ }
+
+ csr &= ~MUSB2_MASK_CSR0L_NAKTIMO;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr);
+
+ td->error = 1;
+ }
+
+ if (td->error) {
+ musbotg_channel_free(sc, td);
+ return (0);
+ }
+
+ /* Fifo is not empty and there is no NAK timeout */
+ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY)
+ return (1);
+
+ /* check if we are complete */
+ if (td->remainder == 0) {
+ /* we are complete */
+ musbotg_channel_free(sc, td);
+ return (0);
+ }
+
+ /* copy data into real buffer */
+ usbd_copy_out(td->pc, 0, &req, sizeof(req));
+
+ /* send data */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), (void *)&req, sizeof(req));
+
+ /* update offset and remainder */
+ td->offset += sizeof(req);
+ td->remainder -= sizeof(req);
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, MAX_NAK_TO);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXFADDR(0), td->dev_addr);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXHADDR(0), td->haddr);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXHUBPORT(0), td->hport);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXTI, td->transfer_type);
+
+ /* write command */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_TXPKTRDY |
+ MUSB2_MASK_CSR0L_SETUPPKT);
+
+ /* Just to be consistent, not used above */
+ td->transaction_started = 1;
+
+ return (1); /* in progress */
+}
+
+/* Control endpoint only data handling functions (RX/TX/SYNC) */
+
+static uint8_t
+musbotg_dev_ctrl_data_rx(struct musbotg_td *td)
+{
+ struct usb_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr;
+ uint8_t got_short;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* check if a command is pending */
+ if (sc->sc_ep0_cmd) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd);
+ sc->sc_ep0_cmd = 0;
+ }
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ got_short = 0;
+
+ if (csr & (MUSB2_MASK_CSR0L_SETUPEND |
+ MUSB2_MASK_CSR0L_SENTSTALL)) {
+ if (td->remainder == 0) {
+ /*
+ * We are actually complete and have
+ * received the next SETUP
+ */
+ DPRINTFN(4, "faking complete\n");
+ return (0); /* complete */
+ }
+ /*
+ * USB Host Aborted the transfer.
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY)) {
+ return (1); /* not complete */
+ }
+ /* get the packet byte count */
+ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT);
+
+ /* verify the packet byte count */
+ if (count != td->max_frame_size) {
+ if (count < td->max_frame_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ }
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ while (count > 0) {
+ uint32_t temp;
+
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+ temp = count & ~3;
+
+ if (temp) {
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf,
+ temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0),
+ (void *)(&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ usbd_copy_in(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* receive data */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+ sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_RXPKTRDY_CLR;
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+ /* write command - need more data */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_RXPKTRDY_CLR);
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_dev_ctrl_data_tx(struct musbotg_td *td)
+{
+ struct usb_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* check if a command is pending */
+ if (sc->sc_ep0_cmd) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd);
+ sc->sc_ep0_cmd = 0;
+ }
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ if (csr & (MUSB2_MASK_CSR0L_SETUPEND |
+ MUSB2_MASK_CSR0L_SENTSTALL)) {
+ /*
+ * The current transfer was aborted
+ * by the USB Host
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY) {
+ return (1); /* not complete */
+ }
+ count = td->max_frame_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ while (count > 0) {
+ uint32_t temp;
+
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+ usbd_copy_out(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ temp = count & ~3;
+
+ if (temp) {
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf,
+ temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0),
+ ((void *)&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* transmit data */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ sc->sc_ep0_cmd = MUSB2_MASK_CSR0L_TXPKTRDY;
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+ /* write command */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_TXPKTRDY);
+
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_host_ctrl_data_rx(struct musbotg_td *td)
+{
+ struct usb_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr;
+ uint8_t got_short;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ if (td->channel == -1)
+ td->channel = musbotg_channel_alloc(sc, td, 0);
+
+ /* EP0 is busy, wait */
+ if (td->channel == -1)
+ return (1);
+
+ DPRINTFN(1, "ep_no=%d\n", td->channel);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ got_short = 0;
+ if (!td->transaction_started) {
+ td->transaction_started = 1;
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXNAKLIMIT, MAX_NAK_TO);
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXFADDR(0),
+ td->dev_addr);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXHADDR(0), td->haddr);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXHUBPORT(0), td->hport);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXTI, td->transfer_type);
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_REQPKT);
+
+ return (1);
+ }
+
+ if (csr & MUSB2_MASK_CSR0L_NAKTIMO) {
+ csr &= ~MUSB2_MASK_CSR0L_REQPKT;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr);
+
+ csr &= ~MUSB2_MASK_CSR0L_NAKTIMO;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr);
+
+ td->error = 1;
+ }
+
+ /* Failed */
+ if (csr & (MUSB2_MASK_CSR0L_RXSTALL |
+ MUSB2_MASK_CSR0L_ERROR))
+ {
+ /* Clear status bit */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ DPRINTFN(1, "error bit set, csr=0x%02x\n", csr);
+ td->error = 1;
+ }
+
+ if (td->error) {
+ musbotg_channel_free(sc, td);
+ return (0); /* we are complete */
+ }
+
+ if (!(csr & MUSB2_MASK_CSR0L_RXPKTRDY))
+ return (1); /* not yet */
+
+ /* get the packet byte count */
+ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT);
+
+ /* verify the packet byte count */
+ if (count != td->max_frame_size) {
+ if (count < td->max_frame_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error = 1;
+ musbotg_channel_free(sc, td);
+ return (0); /* we are complete */
+ }
+ }
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error = 1;
+ musbotg_channel_free(sc, td);
+ return (0); /* we are complete */
+ }
+ while (count > 0) {
+ uint32_t temp;
+
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+ temp = count & ~3;
+
+ if (temp) {
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), sc->sc_bounce_buf,
+ temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0),
+ (void *)(&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ usbd_copy_in(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* receive data */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ csr &= ~MUSB2_MASK_CSR0L_RXPKTRDY;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr);
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+
+ musbotg_channel_free(sc, td);
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+
+ td->transaction_started = 1;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_REQPKT);
+
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_host_ctrl_data_tx(struct musbotg_td *td)
+{
+ struct usb_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr, csrh;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ if (td->channel == -1)
+ td->channel = musbotg_channel_alloc(sc, td, 1);
+
+ /* No free EPs */
+ if (td->channel == -1)
+ return (1);
+
+ DPRINTFN(1, "ep_no=%d\n", td->channel);
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ if (csr & (MUSB2_MASK_CSR0L_RXSTALL |
+ MUSB2_MASK_CSR0L_ERROR)) {
+ /* clear status bits */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ td->error = 1;
+ }
+
+ if (csr & MUSB2_MASK_CSR0L_NAKTIMO ) {
+ if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) {
+ csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH);
+ csrh |= MUSB2_MASK_CSR0H_FFLUSH;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY) {
+ csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH);
+ csrh |= MUSB2_MASK_CSR0H_FFLUSH;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ }
+ }
+
+ csr &= ~MUSB2_MASK_CSR0L_NAKTIMO;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr);
+
+ td->error = 1;
+ }
+
+ if (td->error) {
+ musbotg_channel_free(sc, td);
+ return (0); /* complete */
+ }
+
+ /*
+ * Wait while FIFO is empty.
+ * Do not flush it because it will cause transactions
+ * with size more then packet size. It might upset
+ * some devices
+ */
+ if (csr & MUSB2_MASK_CSR0L_TXFIFONEMPTY)
+ return (1);
+
+ /* Packet still being processed */
+ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY)
+ return (1);
+
+ if (td->transaction_started) {
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ musbotg_channel_free(sc, td);
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+
+ /* We're not complete - more transactions required */
+ td->transaction_started = 0;
+ }
+
+ /* check for short packet */
+ count = td->max_frame_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+
+ while (count > 0) {
+ uint32_t temp;
+
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+ usbd_copy_out(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ temp = count & ~3;
+
+ if (temp) {
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag,
+ sc->sc_io_hdl, MUSB2_REG_EPFIFO(0),
+ sc->sc_bounce_buf, temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0),
+ ((void *)&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* transmit data */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(0), buf_res.buffer,
+ buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* Function address */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXFADDR(0), td->dev_addr);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXHADDR(0), td->haddr);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXHUBPORT(0), td->hport);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXTI, td->transfer_type);
+
+ /* TX NAK timeout */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, MAX_NAK_TO);
+
+ /* write command */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_TXPKTRDY);
+
+ td->transaction_started = 1;
+
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_dev_ctrl_status(struct musbotg_td *td)
+{
+ struct musbotg_softc *sc;
+ uint8_t csr;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ if (sc->sc_ep0_busy) {
+ sc->sc_ep0_busy = 0;
+ sc->sc_ep0_cmd |= MUSB2_MASK_CSR0L_DATAEND;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, sc->sc_ep0_cmd);
+ sc->sc_ep0_cmd = 0;
+ }
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ if (csr & MUSB2_MASK_CSR0L_DATAEND) {
+ /* wait for interrupt */
+ return (1); /* not complete */
+ }
+ if (sc->sc_dv_addr != 0xFF) {
+ /* write function address */
+ musbotg_set_address(sc, sc->sc_dv_addr);
+ }
+
+ musbotg_channel_free(sc, td);
+ return (0); /* complete */
+}
+
+static uint8_t
+musbotg_host_ctrl_status_rx(struct musbotg_td *td)
+{
+ struct musbotg_softc *sc;
+ uint8_t csr, csrh;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ if (td->channel == -1)
+ td->channel = musbotg_channel_alloc(sc, td, 0);
+
+ /* EP0 is busy, wait */
+ if (td->channel == -1)
+ return (1);
+
+ DPRINTFN(1, "ep_no=%d\n", td->channel);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ if (!td->transaction_started) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXFADDR(0),
+ td->dev_addr);
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXHADDR(0), td->haddr);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXHUBPORT(0), td->hport);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXTI, td->transfer_type);
+
+ /* RX NAK timeout */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXNAKLIMIT, MAX_NAK_TO);
+
+ td->transaction_started = 1;
+
+ /* Disable PING */
+ csrh = MUSB2_READ_1(sc, MUSB2_REG_RXCSRH);
+ csrh |= MUSB2_MASK_CSR0H_PING_DIS;
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, csrh);
+
+ /* write command */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_STATUSPKT |
+ MUSB2_MASK_CSR0L_REQPKT);
+
+ return (1); /* Just started */
+ }
+
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "IN STATUS csr=0x%02x\n", csr);
+
+ if (csr & MUSB2_MASK_CSR0L_RXPKTRDY) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_RXPKTRDY_CLR);
+ musbotg_channel_free(sc, td);
+ return (0); /* complete */
+ }
+
+ if (csr & MUSB2_MASK_CSR0L_NAKTIMO) {
+ csr &= ~ (MUSB2_MASK_CSR0L_STATUSPKT |
+ MUSB2_MASK_CSR0L_REQPKT);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr);
+
+ csr &= ~MUSB2_MASK_CSR0L_NAKTIMO;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr);
+ td->error = 1;
+ }
+
+ /* Failed */
+ if (csr & (MUSB2_MASK_CSR0L_RXSTALL |
+ MUSB2_MASK_CSR0L_ERROR))
+ {
+ /* Clear status bit */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ DPRINTFN(1, "error bit set, csr=0x%02x\n", csr);
+ td->error = 1;
+ }
+
+ if (td->error) {
+ musbotg_channel_free(sc, td);
+ return (0);
+ }
+
+ return (1); /* Not ready yet */
+}
+
+static uint8_t
+musbotg_host_ctrl_status_tx(struct musbotg_td *td)
+{
+ struct musbotg_softc *sc;
+ uint8_t csr;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ if (td->channel == -1)
+ td->channel = musbotg_channel_alloc(sc, td, 1);
+
+ /* EP0 is busy, wait */
+ if (td->channel == -1)
+ return (1);
+
+ DPRINTFN(1, "ep_no=%d/%d [%d@%d.%d/%02x]\n", td->channel, td->transaction_started,
+ td->dev_addr,td->haddr,td->hport, td->transfer_type);
+
+ /* select endpoint 0 */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ /* Not yet */
+ if (csr & MUSB2_MASK_CSR0L_TXPKTRDY)
+ return (1);
+
+ /* Failed */
+ if (csr & (MUSB2_MASK_CSR0L_RXSTALL |
+ MUSB2_MASK_CSR0L_ERROR))
+ {
+ /* Clear status bit */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ DPRINTFN(1, "error bit set, csr=0x%02x\n", csr);
+ td->error = 1;
+ musbotg_channel_free(sc, td);
+ return (0); /* complete */
+ }
+
+ if (td->transaction_started) {
+ musbotg_channel_free(sc, td);
+ return (0); /* complete */
+ }
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, MUSB2_MASK_CSR0H_PING_DIS);
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXFADDR(0), td->dev_addr);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXHADDR(0), td->haddr);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXHUBPORT(0), td->hport);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXTI, td->transfer_type);
+
+ /* TX NAK timeout */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, MAX_NAK_TO);
+
+ td->transaction_started = 1;
+
+ /* write command */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSR0L_STATUSPKT |
+ MUSB2_MASK_CSR0L_TXPKTRDY);
+
+ return (1); /* wait for interrupt */
+}
+
+static uint8_t
+musbotg_dev_data_rx(struct musbotg_td *td)
+{
+ struct usb_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr;
+ uint8_t to;
+ uint8_t got_short;
+
+ to = 8; /* don't loop forever! */
+ got_short = 0;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ if (td->channel == -1)
+ td->channel = musbotg_channel_alloc(sc, td, 0);
+
+ /* EP0 is busy, wait */
+ if (td->channel == -1)
+ return (1);
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->channel);
+
+repeat:
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ /* clear overrun */
+ if (csr & MUSB2_MASK_CSRL_RXOVERRUN) {
+ /* make sure we don't clear "RXPKTRDY" */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXPKTRDY);
+ }
+
+ /* check status */
+ if (!(csr & MUSB2_MASK_CSRL_RXPKTRDY))
+ return (1); /* not complete */
+
+ /* get the packet byte count */
+ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT);
+
+ DPRINTFN(4, "count=0x%04x\n", count);
+
+ /*
+ * Check for short or invalid packet:
+ */
+ if (count != td->max_frame_size) {
+ if (count < td->max_frame_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error = 1;
+ musbotg_channel_free(sc, td);
+ return (0); /* we are complete */
+ }
+ }
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error = 1;
+ musbotg_channel_free(sc, td);
+ return (0); /* we are complete */
+ }
+ while (count > 0) {
+ uint32_t temp;
+
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+ temp = count & ~3;
+
+ if (temp) {
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->channel), sc->sc_bounce_buf,
+ temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_read_multi_1(sc->sc_io_tag,
+ sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel),
+ ((void *)&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ usbd_copy_in(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->channel), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* receive data */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->channel), buf_res.buffer,
+ buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* clear status bits */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0);
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+ musbotg_channel_free(sc, td);
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_dev_data_tx(struct musbotg_td *td)
+{
+ struct usb_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr;
+ uint8_t to;
+
+ to = 8; /* don't loop forever! */
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ if (td->channel == -1)
+ td->channel = musbotg_channel_alloc(sc, td, 1);
+
+ /* EP0 is busy, wait */
+ if (td->channel == -1)
+ return (1);
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->channel);
+
+repeat:
+
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ if (csr & (MUSB2_MASK_CSRL_TXINCOMP |
+ MUSB2_MASK_CSRL_TXUNDERRUN)) {
+ /* clear status bits */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ }
+ if (csr & MUSB2_MASK_CSRL_TXPKTRDY) {
+ return (1); /* not complete */
+ }
+ /* check for short packet */
+ count = td->max_frame_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ while (count > 0) {
+ uint32_t temp;
+
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+ usbd_copy_out(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ temp = count & ~3;
+
+ if (temp) {
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag,
+ sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel),
+ sc->sc_bounce_buf, temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->channel),
+ ((void *)&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->channel), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* transmit data */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->channel), buf_res.buffer,
+ buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* Max packet size */
+ MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, td->reg_max_packet);
+
+ /* write command */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXPKTRDY);
+
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ musbotg_channel_free(sc, td);
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_host_data_rx(struct musbotg_td *td)
+{
+ struct usb_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr, csrh;
+ uint8_t to;
+ uint8_t got_short;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ if (td->channel == -1)
+ td->channel = musbotg_channel_alloc(sc, td, 0);
+
+ /* No free EPs */
+ if (td->channel == -1)
+ return (1);
+
+ DPRINTFN(1, "ep_no=%d\n", td->channel);
+
+ to = 8; /* don't loop forever! */
+ got_short = 0;
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->channel);
+
+repeat:
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ if (!td->transaction_started) {
+ /* Function address */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXFADDR(td->channel),
+ td->dev_addr);
+
+ /* SPLIT transaction */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXHADDR(td->channel),
+ td->haddr);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXHUBPORT(td->channel),
+ td->hport);
+
+ /* RX NAK timeout */
+ if (td->transfer_type & MUSB2_MASK_TI_PROTO_ISOC)
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXNAKLIMIT, 0);
+ else
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXNAKLIMIT, MAX_NAK_TO);
+
+ /* Protocol, speed, device endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXTI, td->transfer_type);
+
+ /* Max packet size */
+ MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, td->reg_max_packet);
+
+ /* Data Toggle */
+ csrh = MUSB2_READ_1(sc, MUSB2_REG_RXCSRH);
+ DPRINTFN(4, "csrh=0x%02x\n", csrh);
+
+ csrh |= MUSB2_MASK_CSRH_RXDT_WREN;
+ if (td->toggle)
+ csrh |= MUSB2_MASK_CSRH_RXDT_VAL;
+ else
+ csrh &= ~MUSB2_MASK_CSRH_RXDT_VAL;
+
+ /* Set data toggle */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, csrh);
+
+ /* write command */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXREQPKT);
+
+ td->transaction_started = 1;
+ return (1);
+ }
+
+ /* clear NAK timeout */
+ if (csr & MUSB2_MASK_CSRL_RXNAKTO) {
+ DPRINTFN(4, "NAK Timeout\n");
+ if (csr & MUSB2_MASK_CSRL_RXREQPKT) {
+ csr &= ~MUSB2_MASK_CSRL_RXREQPKT;
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, csr);
+
+ csr &= ~MUSB2_MASK_CSRL_RXNAKTO;
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, csr);
+ }
+
+ td->error = 1;
+ }
+
+ if (csr & MUSB2_MASK_CSRL_RXERROR) {
+ DPRINTFN(4, "RXERROR\n");
+ td->error = 1;
+ }
+
+ if (csr & MUSB2_MASK_CSRL_RXSTALL) {
+ DPRINTFN(4, "RXSTALL\n");
+ td->error = 1;
+ }
+
+ if (td->error) {
+ musbotg_channel_free(sc, td);
+ return (0); /* we are complete */
+ }
+
+ if (!(csr & MUSB2_MASK_CSRL_RXPKTRDY)) {
+ /* No data available yet */
+ return (1);
+ }
+
+ td->toggle ^= 1;
+ /* get the packet byte count */
+ count = MUSB2_READ_2(sc, MUSB2_REG_RXCOUNT);
+ DPRINTFN(4, "count=0x%04x\n", count);
+
+ /*
+ * Check for short or invalid packet:
+ */
+ if (count != td->max_frame_size) {
+ if (count < td->max_frame_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error = 1;
+ musbotg_channel_free(sc, td);
+ return (0); /* we are complete */
+ }
+ }
+
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error = 1;
+ musbotg_channel_free(sc, td);
+ return (0); /* we are complete */
+ }
+
+ while (count > 0) {
+ uint32_t temp;
+
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+ temp = count & ~3;
+
+ if (temp) {
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->channel), sc->sc_bounce_buf,
+ temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_read_multi_1(sc->sc_io_tag,
+ sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel),
+ ((void *)&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ usbd_copy_in(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+ /* receive data 4 bytes at a time */
+ bus_space_read_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->channel), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* receive data */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->channel), buf_res.buffer,
+ buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* clear status bits */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0);
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+ musbotg_channel_free(sc, td);
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+
+ /* Reset transaction state and restart */
+ td->transaction_started = 0;
+
+ if (--to)
+ goto repeat;
+
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_host_data_tx(struct musbotg_td *td)
+{
+ struct usb_page_search buf_res;
+ struct musbotg_softc *sc;
+ uint16_t count;
+ uint8_t csr, csrh;
+
+ /* get pointer to softc */
+ sc = MUSBOTG_PC2SC(td->pc);
+
+ if (td->channel == -1)
+ td->channel = musbotg_channel_alloc(sc, td, 1);
+
+ /* No free EPs */
+ if (td->channel == -1)
+ return (1);
+
+ DPRINTFN(1, "ep_no=%d\n", td->channel);
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, td->channel);
+
+ /* read out FIFO status */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ DPRINTFN(4, "csr=0x%02x\n", csr);
+
+ if (csr & (MUSB2_MASK_CSRL_TXSTALLED |
+ MUSB2_MASK_CSRL_TXERROR)) {
+ /* clear status bits */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ td->error = 1;
+ musbotg_channel_free(sc, td);
+ return (0); /* complete */
+ }
+
+ if (csr & MUSB2_MASK_CSRL_TXNAKTO) {
+ /*
+ * Flush TX FIFO before clearing NAK TO
+ */
+ if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) {
+ csr |= MUSB2_MASK_CSRL_TXFFLUSH;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) {
+ csr |= MUSB2_MASK_CSRL_TXFFLUSH;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ }
+ }
+
+ csr &= ~MUSB2_MASK_CSRL_TXNAKTO;
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, csr);
+
+ td->error = 1;
+ musbotg_channel_free(sc, td);
+ return (0); /* complete */
+ }
+
+ /*
+ * Wait while FIFO is empty.
+ * Do not flush it because it will cause transactions
+ * with size more then packet size. It might upset
+ * some devices
+ */
+ if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY)
+ return (1);
+
+ /* Packet still being processed */
+ if (csr & MUSB2_MASK_CSRL_TXPKTRDY)
+ return (1);
+
+ if (td->transaction_started) {
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ musbotg_channel_free(sc, td);
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+
+ /* We're not complete - more transactions required */
+ td->transaction_started = 0;
+ }
+
+ /* check for short packet */
+ count = td->max_frame_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+
+ while (count > 0) {
+ uint32_t temp;
+
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* check for unaligned memory address */
+ if (USB_P2U(buf_res.buffer) & 3) {
+ usbd_copy_out(td->pc, td->offset,
+ sc->sc_bounce_buf, count);
+
+ temp = count & ~3;
+
+ if (temp) {
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag,
+ sc->sc_io_hdl, MUSB2_REG_EPFIFO(td->channel),
+ sc->sc_bounce_buf, temp / 4);
+ }
+ temp = count & 3;
+ if (temp) {
+ /* receive data 1 byte at a time */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->channel),
+ ((void *)&sc->sc_bounce_buf[count / 4]), temp);
+ }
+ /* update offset and remainder */
+ td->offset += count;
+ td->remainder -= count;
+ break;
+ }
+ /* check if we can optimise */
+ if (buf_res.length >= 4) {
+ /* transmit data 4 bytes at a time */
+ bus_space_write_multi_4(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->channel), buf_res.buffer,
+ buf_res.length / 4);
+
+ temp = buf_res.length & ~3;
+
+ /* update counters */
+ count -= temp;
+ td->offset += temp;
+ td->remainder -= temp;
+ continue;
+ }
+ /* transmit data */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ MUSB2_REG_EPFIFO(td->channel), buf_res.buffer,
+ buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* Function address */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXFADDR(td->channel),
+ td->dev_addr);
+
+ /* SPLIT transaction */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXHADDR(td->channel),
+ td->haddr);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXHUBPORT(td->channel),
+ td->hport);
+
+ /* TX NAK timeout */
+ if (td->transfer_type & MUSB2_MASK_TI_PROTO_ISOC)
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, 0);
+ else
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXNAKLIMIT, MAX_NAK_TO);
+
+ /* Protocol, speed, device endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXTI, td->transfer_type);
+
+ /* Max packet size */
+ MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, td->reg_max_packet);
+
+ if (!td->transaction_started) {
+ csrh = MUSB2_READ_1(sc, MUSB2_REG_TXCSRH);
+ DPRINTFN(4, "csrh=0x%02x\n", csrh);
+
+ csrh |= MUSB2_MASK_CSRH_TXDT_WREN;
+ if (td->toggle)
+ csrh |= MUSB2_MASK_CSRH_TXDT_VAL;
+ else
+ csrh &= ~MUSB2_MASK_CSRH_TXDT_VAL;
+
+ /* Set data toggle */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH, csrh);
+ }
+
+ /* write command */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXPKTRDY);
+
+ /* Update Data Toggle */
+ td->toggle ^= 1;
+ td->transaction_started = 1;
+
+ return (1); /* not complete */
+}
+
+static uint8_t
+musbotg_xfer_do_fifo(struct usb_xfer *xfer)
+{
+ struct musbotg_td *td;
+
+ DPRINTFN(8, "\n");
+ td = xfer->td_transfer_cache;
+ while (1) {
+ if ((td->func) (td)) {
+ /* operation in progress */
+ break;
+ }
+
+ if (((void *)td) == xfer->td_transfer_last) {
+ goto done;
+ }
+ if (td->error) {
+ goto done;
+ } else if (td->remainder > 0) {
+ /*
+ * We had a short transfer. If there is no alternate
+ * next, stop processing !
+ */
+ if (!td->alt_next) {
+ goto done;
+ }
+ }
+ /*
+ * Fetch the next transfer descriptor and transfer
+ * some flags to the next transfer descriptor
+ */
+ td = td->obj_next;
+ xfer->td_transfer_cache = td;
+ }
+
+ return (1); /* not complete */
+done:
+ /* compute all actual lengths */
+ musbotg_standard_done(xfer);
+
+ return (0); /* complete */
+}
+
+static void
+musbotg_interrupt_poll(struct musbotg_softc *sc)
+{
+ struct usb_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (!musbotg_xfer_do_fifo(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+void
+musbotg_vbus_interrupt(struct musbotg_softc *sc, uint8_t is_on)
+{
+ DPRINTFN(4, "vbus = %u\n", is_on);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ if (is_on) {
+ if (!sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 1;
+
+ /* complete root HUB interrupt endpoint */
+ musbotg_root_intr(sc);
+ }
+ } else {
+ if (sc->sc_flags.status_vbus) {
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* complete root HUB interrupt endpoint */
+ musbotg_root_intr(sc);
+ }
+ }
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+musbotg_connect_interrupt(struct musbotg_softc *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+ sc->sc_flags.change_connect = 1;
+
+ /* complete root HUB interrupt endpoint */
+ musbotg_root_intr(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+void
+musbotg_interrupt(struct musbotg_softc *sc,
+ uint16_t rxstat, uint16_t txstat, uint8_t stat)
+{
+ uint16_t rx_status;
+ uint16_t tx_status;
+ uint8_t usb_status;
+ uint8_t temp;
+ uint8_t to = 2;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+repeat:
+
+ /* read all interrupt registers */
+ usb_status = MUSB2_READ_1(sc, MUSB2_REG_INTUSB);
+
+ /* read all FIFO interrupts */
+ rx_status = MUSB2_READ_2(sc, MUSB2_REG_INTRX);
+ tx_status = MUSB2_READ_2(sc, MUSB2_REG_INTTX);
+ rx_status |= rxstat;
+ tx_status |= txstat;
+ usb_status |= stat;
+
+ /* Clear platform flags after first time */
+ rxstat = 0;
+ txstat = 0;
+ stat = 0;
+
+ /* check for any bus state change interrupts */
+
+ if (usb_status & (MUSB2_MASK_IRESET |
+ MUSB2_MASK_IRESUME | MUSB2_MASK_ISUSP |
+ MUSB2_MASK_ICONN | MUSB2_MASK_IDISC |
+ MUSB2_MASK_IVBUSERR)) {
+ DPRINTFN(4, "real bus interrupt 0x%08x\n", usb_status);
+
+ if (usb_status & MUSB2_MASK_IRESET) {
+ /* set correct state */
+ sc->sc_flags.status_bus_reset = 1;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* determine line speed */
+ temp = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ if (temp & MUSB2_MASK_HSMODE)
+ sc->sc_flags.status_high_speed = 1;
+ else
+ sc->sc_flags.status_high_speed = 0;
+
+ /*
+ * After reset all interrupts are on and we need to
+ * turn them off!
+ */
+ temp = MUSB2_MASK_IRESET;
+ /* disable resume interrupt */
+ temp &= ~MUSB2_MASK_IRESUME;
+ /* enable suspend interrupt */
+ temp |= MUSB2_MASK_ISUSP;
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp);
+ /* disable TX and RX interrupts */
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0);
+ }
+ /*
+ * If RXRSM and RXSUSP is set at the same time we interpret
+ * that like RESUME. Resume is set when there is at least 3
+ * milliseconds of inactivity on the USB BUS.
+ */
+ if (usb_status & MUSB2_MASK_IRESUME) {
+ if (sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 1;
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE);
+ /* disable resume interrupt */
+ temp &= ~MUSB2_MASK_IRESUME;
+ /* enable suspend interrupt */
+ temp |= MUSB2_MASK_ISUSP;
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp);
+ }
+ } else if (usb_status & MUSB2_MASK_ISUSP) {
+ if (!sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 1;
+ sc->sc_flags.change_suspend = 1;
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_INTUSBE);
+ /* disable suspend interrupt */
+ temp &= ~MUSB2_MASK_ISUSP;
+ /* enable resume interrupt */
+ temp |= MUSB2_MASK_IRESUME;
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, temp);
+ }
+ }
+ if (usb_status &
+ (MUSB2_MASK_ICONN | MUSB2_MASK_IDISC))
+ sc->sc_flags.change_connect = 1;
+
+ /*
+ * Host Mode: There is no IRESET so assume bus is
+ * always in reset state once device is connected.
+ */
+ if (sc->sc_mode == MUSB2_HOST_MODE) {
+ /* check for VBUS error in USB host mode */
+ if (usb_status & MUSB2_MASK_IVBUSERR) {
+ temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL);
+ temp |= MUSB2_MASK_SESS;
+ MUSB2_WRITE_1(sc, MUSB2_REG_DEVCTL, temp);
+ }
+ if (usb_status & MUSB2_MASK_ICONN)
+ sc->sc_flags.status_bus_reset = 1;
+ if (usb_status & MUSB2_MASK_IDISC)
+ sc->sc_flags.status_bus_reset = 0;
+ }
+
+ /* complete root HUB interrupt endpoint */
+ musbotg_root_intr(sc);
+ }
+ /* check for any endpoint interrupts */
+
+ if (rx_status || tx_status) {
+ DPRINTFN(4, "real endpoint interrupt "
+ "rx=0x%04x, tx=0x%04x\n", rx_status, tx_status);
+ }
+ /* poll one time regardless of FIFO status */
+
+ musbotg_interrupt_poll(sc);
+
+ if (--to)
+ goto repeat;
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+musbotg_setup_standard_chain_sub(struct musbotg_std_temp *temp)
+{
+ struct musbotg_td *td;
+
+ /* get current Transfer Descriptor */
+ td = temp->td_next;
+ temp->td = td;
+
+ /* prepare for next TD */
+ temp->td_next = td->obj_next;
+
+ /* fill out the Transfer Descriptor */
+ td->func = temp->func;
+ td->pc = temp->pc;
+ td->offset = temp->offset;
+ td->remainder = temp->len;
+ td->error = 0;
+ td->transaction_started = 0;
+ td->did_stall = temp->did_stall;
+ td->short_pkt = temp->short_pkt;
+ td->alt_next = temp->setup_alt_next;
+ td->channel = temp->channel;
+ td->dev_addr = temp->dev_addr;
+ td->haddr = temp->haddr;
+ td->hport = temp->hport;
+ td->transfer_type = temp->transfer_type;
+}
+
+static void
+musbotg_setup_standard_chain(struct usb_xfer *xfer)
+{
+ struct musbotg_std_temp temp;
+ struct musbotg_softc *sc;
+ struct musbotg_td *td;
+ uint32_t x;
+ uint8_t ep_no;
+ uint8_t xfer_type;
+ enum usb_dev_speed speed;
+ int tx;
+ int dev_addr;
+
+ DPRINTFN(8, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpointno),
+ xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
+
+ sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+ ep_no = (xfer->endpointno & UE_ADDR);
+
+ temp.max_frame_size = xfer->max_frame_size;
+
+ td = xfer->td_start[0];
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ /* setup temp */
+ dev_addr = xfer->address;
+
+ xfer_type = xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE;
+
+ temp.pc = NULL;
+ temp.td = NULL;
+ temp.td_next = xfer->td_start[0];
+ temp.offset = 0;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok ||
+ xfer->flags_int.isochronous_xfr;
+ temp.did_stall = !xfer->flags_int.control_stall;
+ temp.channel = -1;
+ temp.dev_addr = dev_addr;
+ temp.haddr = xfer->xroot->udev->hs_hub_addr;
+ temp.hport = xfer->xroot->udev->hs_port_no;
+
+ if (xfer->flags_int.usb_mode == USB_MODE_HOST) {
+ speed = usbd_get_speed(xfer->xroot->udev);
+
+ switch (speed) {
+ case USB_SPEED_LOW:
+ temp.transfer_type = MUSB2_MASK_TI_SPEED_LO;
+ break;
+ case USB_SPEED_FULL:
+ temp.transfer_type = MUSB2_MASK_TI_SPEED_FS;
+ break;
+ case USB_SPEED_HIGH:
+ temp.transfer_type = MUSB2_MASK_TI_SPEED_HS;
+ break;
+ default:
+ temp.transfer_type = 0;
+ DPRINTFN(-1, "Invalid USB speed: %d\n", speed);
+ break;
+ }
+
+ switch (xfer_type) {
+ case UE_CONTROL:
+ temp.transfer_type |= MUSB2_MASK_TI_PROTO_CTRL;
+ break;
+ case UE_ISOCHRONOUS:
+ temp.transfer_type |= MUSB2_MASK_TI_PROTO_ISOC;
+ break;
+ case UE_BULK:
+ temp.transfer_type |= MUSB2_MASK_TI_PROTO_BULK;
+ break;
+ case UE_INTERRUPT:
+ temp.transfer_type |= MUSB2_MASK_TI_PROTO_INTR;
+ break;
+ default:
+ DPRINTFN(-1, "Invalid USB transfer type: %d\n",
+ xfer_type);
+ break;
+ }
+
+ temp.transfer_type |= ep_no;
+ td->toggle = xfer->endpoint->toggle_next;
+ }
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE)
+ temp.func = &musbotg_dev_ctrl_setup_rx;
+ else
+ temp.func = &musbotg_host_ctrl_setup_tx;
+
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.short_pkt = temp.len ? 1 : 0;
+
+ musbotg_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ tx = 0;
+
+ if (x != xfer->nframes) {
+ if (xfer->endpointno & UE_DIR_IN)
+ tx = 1;
+
+ if (xfer->flags_int.usb_mode == USB_MODE_HOST) {
+ tx = !tx;
+
+ if (tx) {
+ if (xfer->flags_int.control_xfr)
+ temp.func = &musbotg_host_ctrl_data_tx;
+ else
+ temp.func = &musbotg_host_data_tx;
+ } else {
+ if (xfer->flags_int.control_xfr)
+ temp.func = &musbotg_host_ctrl_data_rx;
+ else
+ temp.func = &musbotg_host_data_rx;
+ }
+
+ } else {
+ if (tx) {
+ if (xfer->flags_int.control_xfr)
+ temp.func = &musbotg_dev_ctrl_data_tx;
+ else
+ temp.func = &musbotg_dev_data_tx;
+ } else {
+ if (xfer->flags_int.control_xfr)
+ temp.func = &musbotg_dev_ctrl_data_rx;
+ else
+ temp.func = &musbotg_dev_data_rx;
+ }
+ }
+
+ /* setup "pc" pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ while (x != xfer->nframes) {
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+
+ x++;
+
+ if (x == xfer->nframes) {
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_act) {
+ temp.setup_alt_next = 0;
+ }
+ } else {
+ temp.setup_alt_next = 0;
+ }
+ }
+ if (temp.len == 0) {
+ /* make sure that we send an USB packet */
+
+ temp.short_pkt = 0;
+
+ } else {
+ if (xfer->flags_int.isochronous_xfr) {
+ /* isochronous data transfer */
+ /* don't force short */
+ temp.short_pkt = 1;
+ } else {
+ /* regular data transfer */
+ temp.short_pkt = (xfer->flags.force_short_xfer ? 0 : 1);
+ }
+ }
+
+ musbotg_setup_standard_chain_sub(&temp);
+
+ if (xfer->flags_int.isochronous_xfr) {
+ temp.offset += temp.len;
+ } else {
+ /* get next Page Cache pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ }
+
+ /* check for control transfer */
+ if (xfer->flags_int.control_xfr) {
+ /* always setup a valid "pc" pointer for status and sync */
+ temp.pc = xfer->frbuffers + 0;
+ temp.len = 0;
+ temp.short_pkt = 0;
+ temp.setup_alt_next = 0;
+
+ /* check if we should append a status stage */
+ if (!xfer->flags_int.control_act) {
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ if (sc->sc_mode == MUSB2_DEVICE_MODE)
+ temp.func = &musbotg_dev_ctrl_status;
+ else {
+ if (xfer->endpointno & UE_DIR_IN)
+ temp.func = musbotg_host_ctrl_status_tx;
+ else
+ temp.func = musbotg_host_ctrl_status_rx;
+ }
+ musbotg_setup_standard_chain_sub(&temp);
+ }
+ }
+ /* must have at least one frame! */
+ td = temp.td;
+ xfer->td_transfer_last = td;
+}
+
+static void
+musbotg_timeout(void *arg)
+{
+ struct usb_xfer *xfer = arg;
+
+ DPRINTFN(1, "xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ musbotg_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+musbotg_ep_int_set(struct musbotg_softc *sc, int channel, int on)
+{
+ uint16_t temp;
+
+ /*
+ * Only enable the endpoint interrupt when we are
+ * actually waiting for data, hence we are dealing
+ * with level triggered interrupts !
+ */
+ DPRINTFN(1, "ep_no=%d, on=%d\n", channel, on);
+
+ if (channel == -1)
+ return;
+
+ if (channel == 0) {
+ temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE);
+ if (on)
+ temp |= MUSB2_MASK_EPINT(0);
+ else
+ temp &= ~MUSB2_MASK_EPINT(0);
+
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp);
+ } else {
+ temp = MUSB2_READ_2(sc, MUSB2_REG_INTRXE);
+ if (on)
+ temp |= MUSB2_MASK_EPINT(channel);
+ else
+ temp &= ~MUSB2_MASK_EPINT(channel);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, temp);
+
+ temp = MUSB2_READ_2(sc, MUSB2_REG_INTTXE);
+ if (on)
+ temp |= MUSB2_MASK_EPINT(channel);
+ else
+ temp &= ~MUSB2_MASK_EPINT(channel);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, temp);
+ }
+
+ if (sc->sc_ep_int_set)
+ sc->sc_ep_int_set(sc, channel, on);
+}
+
+static void
+musbotg_start_standard_chain(struct usb_xfer *xfer)
+{
+ DPRINTFN(8, "\n");
+
+ /* poll one time */
+ if (musbotg_xfer_do_fifo(xfer)) {
+ DPRINTFN(14, "enabled interrupts on endpoint\n");
+
+ /* put transfer on interrupt queue */
+ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usbd_transfer_timeout_ms(xfer,
+ &musbotg_timeout, xfer->timeout);
+ }
+ }
+}
+
+static void
+musbotg_root_intr(struct musbotg_softc *sc)
+{
+ DPRINTFN(8, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* set port bit */
+ sc->sc_hub_idata[0] = 0x02; /* we only have one port */
+
+ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
+ sizeof(sc->sc_hub_idata));
+}
+
+static usb_error_t
+musbotg_standard_done_sub(struct usb_xfer *xfer)
+{
+ struct musbotg_td *td;
+ uint32_t len;
+ uint8_t error;
+
+ DPRINTFN(8, "\n");
+
+ td = xfer->td_transfer_cache;
+
+ do {
+ len = td->remainder;
+
+ xfer->endpoint->toggle_next = td->toggle;
+
+ if (xfer->aframes != xfer->nframes) {
+ /*
+ * Verify the length and subtract
+ * the remainder from "frlengths[]":
+ */
+ if (len > xfer->frlengths[xfer->aframes]) {
+ td->error = 1;
+ } else {
+ xfer->frlengths[xfer->aframes] -= len;
+ }
+ }
+ /* Check for transfer error */
+ if (td->error) {
+ /* the transfer is finished */
+ error = 1;
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len > 0) {
+ if (xfer->flags_int.short_frames_ok ||
+ xfer->flags_int.isochronous_xfr) {
+ /* follow alt next */
+ if (td->alt_next) {
+ td = td->obj_next;
+ } else {
+ td = NULL;
+ }
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ error = 0;
+ break;
+ }
+ td = td->obj_next;
+
+ /* this USB frame is complete */
+ error = 0;
+ break;
+
+ } while (0);
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ return (error ?
+ USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+musbotg_standard_done(struct usb_xfer *xfer)
+{
+ usb_error_t err = 0;
+
+ DPRINTFN(12, "xfer=%p endpoint=%p transfer done\n",
+ xfer, xfer->endpoint);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ err = musbotg_standard_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+ err = musbotg_standard_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+ err = musbotg_standard_done_sub(xfer);
+ }
+done:
+ musbotg_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * musbotg_device_done
+ *
+ * NOTE: this function can be called more than one time on the
+ * same USB transfer!
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_done(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct musbotg_td *td;
+ struct musbotg_softc *sc;
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ DPRINTFN(1, "xfer=%p, endpoint=%p, error=%d\n",
+ xfer, xfer->endpoint, error);
+
+ DPRINTFN(14, "disabled interrupts on endpoint\n");
+
+ sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+ td = xfer->td_transfer_cache;
+
+ if (td && (td->channel != -1))
+ musbotg_channel_free(sc, td);
+
+ /* dequeue transfer and start next transfer */
+ usbd_transfer_done(xfer, error);
+}
+
+static void
+musbotg_xfer_stall(struct usb_xfer *xfer)
+{
+ musbotg_device_done(xfer, USB_ERR_STALLED);
+}
+
+static void
+musbotg_set_stall(struct usb_device *udev,
+ struct usb_endpoint *ep, uint8_t *did_stall)
+{
+ struct musbotg_softc *sc;
+ uint8_t ep_no;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ DPRINTFN(4, "endpoint=%p\n", ep);
+
+ /* set FORCESTALL */
+ sc = MUSBOTG_BUS2SC(udev->bus);
+
+ ep_no = (ep->edesc->bEndpointAddress & UE_ADDR);
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no);
+
+ if (ep->edesc->bEndpointAddress & UE_DIR_IN) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXSENDSTALL);
+ } else {
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXSENDSTALL);
+ }
+}
+
+static void
+musbotg_clear_stall_sub(struct musbotg_softc *sc, uint16_t wMaxPacket,
+ uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir)
+{
+ uint16_t mps;
+ uint16_t temp;
+ uint8_t csr;
+
+ if (ep_type == UE_CONTROL) {
+ /* clearing stall is not needed */
+ return;
+ }
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, ep_no);
+
+ /* compute max frame size */
+ mps = wMaxPacket & 0x7FF;
+ switch ((wMaxPacket >> 11) & 3) {
+ case 1:
+ mps *= 2;
+ break;
+ case 2:
+ mps *= 3;
+ break;
+ default:
+ break;
+ }
+
+ if (ep_dir == UE_DIR_IN) {
+ temp = 0;
+
+ /* Configure endpoint */
+ switch (ep_type) {
+ case UE_INTERRUPT:
+ MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH,
+ MUSB2_MASK_CSRH_TXMODE | temp);
+ break;
+ case UE_ISOCHRONOUS:
+ MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH,
+ MUSB2_MASK_CSRH_TXMODE |
+ MUSB2_MASK_CSRH_TXISO | temp);
+ break;
+ case UE_BULK:
+ MUSB2_WRITE_2(sc, MUSB2_REG_TXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRH,
+ MUSB2_MASK_CSRH_TXMODE | temp);
+ break;
+ default:
+ break;
+ }
+
+ /* Need to flush twice in case of double bufring */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXFFLUSH);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ if (csr & MUSB2_MASK_CSRL_TXFIFONEMPTY) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXFFLUSH);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ }
+ }
+ /* reset data toggle */
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL,
+ MUSB2_MASK_CSRL_TXDT_CLR);
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+
+ /* set double/single buffering */
+ temp = MUSB2_READ_2(sc, MUSB2_REG_TXDBDIS);
+ if (mps <= (sc->sc_hw_ep_profile[ep_no].
+ max_in_frame_size / 2)) {
+ /* double buffer */
+ temp &= ~(1 << ep_no);
+ } else {
+ /* single buffer */
+ temp |= (1 << ep_no);
+ }
+ MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, temp);
+
+ /* clear sent stall */
+ if (csr & MUSB2_MASK_CSRL_TXSENTSTALL) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_TXCSRL, 0);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_TXCSRL);
+ }
+ } else {
+ temp = 0;
+
+ /* Configure endpoint */
+ switch (ep_type) {
+ case UE_INTERRUPT:
+ MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH,
+ MUSB2_MASK_CSRH_RXNYET | temp);
+ break;
+ case UE_ISOCHRONOUS:
+ MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH,
+ MUSB2_MASK_CSRH_RXNYET |
+ MUSB2_MASK_CSRH_RXISO | temp);
+ break;
+ case UE_BULK:
+ MUSB2_WRITE_2(sc, MUSB2_REG_RXMAXP, wMaxPacket);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRH, temp);
+ break;
+ default:
+ break;
+ }
+
+ /* Need to flush twice in case of double bufring */
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+ if (csr & MUSB2_MASK_CSRL_RXPKTRDY) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXFFLUSH);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+ if (csr & MUSB2_MASK_CSRL_RXPKTRDY) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXFFLUSH);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+ }
+ }
+ /* reset data toggle */
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL,
+ MUSB2_MASK_CSRL_RXDT_CLR);
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0);
+ csr = MUSB2_READ_1(sc, MUSB2_REG_RXCSRL);
+
+ /* set double/single buffering */
+ temp = MUSB2_READ_2(sc, MUSB2_REG_RXDBDIS);
+ if (mps <= (sc->sc_hw_ep_profile[ep_no].
+ max_out_frame_size / 2)) {
+ /* double buffer */
+ temp &= ~(1 << ep_no);
+ } else {
+ /* single buffer */
+ temp |= (1 << ep_no);
+ }
+ MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, temp);
+
+ /* clear sent stall */
+ if (csr & MUSB2_MASK_CSRL_RXSENTSTALL) {
+ MUSB2_WRITE_1(sc, MUSB2_REG_RXCSRL, 0);
+ }
+ }
+}
+
+static void
+musbotg_clear_stall(struct usb_device *udev, struct usb_endpoint *ep)
+{
+ struct musbotg_softc *sc;
+ struct usb_endpoint_descriptor *ed;
+
+ DPRINTFN(4, "endpoint=%p\n", ep);
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ /* check mode */
+ if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ /* get softc */
+ sc = MUSBOTG_BUS2SC(udev->bus);
+
+ /* get endpoint descriptor */
+ ed = ep->edesc;
+
+ /* reset endpoint */
+ musbotg_clear_stall_sub(sc,
+ UGETW(ed->wMaxPacketSize),
+ (ed->bEndpointAddress & UE_ADDR),
+ (ed->bmAttributes & UE_XFERTYPE),
+ (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+}
+
+usb_error_t
+musbotg_init(struct musbotg_softc *sc)
+{
+ const struct musb_otg_ep_cfg *cfg;
+ struct usb_hw_ep_profile *pf;
+ int i;
+ uint16_t offset;
+ uint8_t nrx;
+ uint8_t ntx;
+ uint8_t temp;
+ uint8_t fsize;
+ uint8_t frx;
+ uint8_t ftx;
+ uint8_t dynfifo;
+
+ DPRINTFN(1, "start\n");
+
+ /* set up the bus structure */
+ sc->sc_bus.usbrev = USB_REV_2_0;
+ sc->sc_bus.methods = &musbotg_bus_methods;
+
+ /* Set a default endpoint configuration */
+ if (sc->sc_ep_cfg == NULL)
+ sc->sc_ep_cfg = musbotg_ep_default;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* turn on clocks */
+
+ if (sc->sc_clocks_on) {
+ (sc->sc_clocks_on) (sc->sc_clocks_arg);
+ }
+
+ /* wait a little for things to stabilise */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+
+ /* disable all interrupts */
+
+ temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL);
+ DPRINTF("pre-DEVCTL=0x%02x\n", temp);
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0);
+
+ /* disable pullup */
+
+ musbotg_pull_common(sc, 0);
+
+ /* wait a little bit (10ms) */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+
+ /* disable double packet buffering */
+ MUSB2_WRITE_2(sc, MUSB2_REG_RXDBDIS, 0xFFFF);
+ MUSB2_WRITE_2(sc, MUSB2_REG_TXDBDIS, 0xFFFF);
+
+ /* enable HighSpeed and ISO Update flags */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER,
+ MUSB2_MASK_HSENAB | MUSB2_MASK_ISOUPD);
+
+ if (sc->sc_mode == MUSB2_DEVICE_MODE) {
+ /* clear Session bit, if set */
+ temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL);
+ temp &= ~MUSB2_MASK_SESS;
+ MUSB2_WRITE_1(sc, MUSB2_REG_DEVCTL, temp);
+ } else {
+ /* Enter session for Host mode */
+ temp = MUSB2_READ_1(sc, MUSB2_REG_DEVCTL);
+ temp |= MUSB2_MASK_SESS;
+ MUSB2_WRITE_1(sc, MUSB2_REG_DEVCTL, temp);
+ }
+
+ /* wait a little for things to stabilise */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 10);
+
+ DPRINTF("DEVCTL=0x%02x\n", temp);
+
+ /* disable testmode */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_TESTMODE, 0);
+
+ /* set default value */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_MISC, 0);
+
+ /* select endpoint index 0 */
+
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, 0);
+
+ if (sc->sc_ep_max == 0) {
+ /* read out number of endpoints */
+
+ nrx =
+ (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) / 16);
+
+ ntx =
+ (MUSB2_READ_1(sc, MUSB2_REG_EPINFO) % 16);
+
+ sc->sc_ep_max = (nrx > ntx) ? nrx : ntx;
+ } else {
+ nrx = ntx = sc->sc_ep_max;
+ }
+
+ /* these numbers exclude the control endpoint */
+
+ DPRINTFN(2, "RX/TX endpoints: %u/%u\n", nrx, ntx);
+
+ if (sc->sc_ep_max == 0) {
+ DPRINTFN(2, "ERROR: Looks like the clocks are off!\n");
+ }
+ /* read out configuration data */
+
+ sc->sc_conf_data = MUSB2_READ_1(sc, MUSB2_REG_CONFDATA);
+
+ DPRINTFN(2, "Config Data: 0x%02x\n",
+ sc->sc_conf_data);
+
+ dynfifo = (sc->sc_conf_data & MUSB2_MASK_CD_DYNFIFOSZ) ? 1 : 0;
+
+ if (dynfifo) {
+ device_printf(sc->sc_bus.bdev, "Dynamic FIFO sizing detected, "
+ "assuming 16Kbytes of FIFO RAM\n");
+ }
+
+ DPRINTFN(2, "HW version: 0x%04x\n",
+ MUSB2_READ_1(sc, MUSB2_REG_HWVERS));
+
+ /* initialise endpoint profiles */
+
+ offset = 0;
+
+ for (temp = 1; temp <= sc->sc_ep_max; temp++) {
+ pf = sc->sc_hw_ep_profile + temp;
+
+ /* select endpoint */
+ MUSB2_WRITE_1(sc, MUSB2_REG_EPINDEX, temp);
+
+ fsize = MUSB2_READ_1(sc, MUSB2_REG_FSIZE);
+ frx = (fsize & MUSB2_MASK_RX_FSIZE) / 16;
+ ftx = (fsize & MUSB2_MASK_TX_FSIZE);
+
+ DPRINTF("Endpoint %u FIFO size: IN=%u, OUT=%u, DYN=%d\n",
+ temp, ftx, frx, dynfifo);
+
+ if (dynfifo) {
+ if (frx && (temp <= nrx)) {
+ for (i = 0; sc->sc_ep_cfg[i].ep_end >= 0; i++) {
+ cfg = &sc->sc_ep_cfg[i];
+ if (temp <= cfg->ep_end) {
+ frx = cfg->ep_fifosz_shift;
+ MUSB2_WRITE_1(sc,
+ MUSB2_REG_RXFIFOSZ,
+ cfg->ep_fifosz_reg);
+ break;
+ }
+ }
+
+ MUSB2_WRITE_2(sc, MUSB2_REG_RXFIFOADD,
+ offset >> 3);
+
+ offset += (1 << frx);
+ }
+ if (ftx && (temp <= ntx)) {
+ for (i = 0; sc->sc_ep_cfg[i].ep_end >= 0; i++) {
+ cfg = &sc->sc_ep_cfg[i];
+ if (temp <= cfg->ep_end) {
+ ftx = cfg->ep_fifosz_shift;
+ MUSB2_WRITE_1(sc,
+ MUSB2_REG_TXFIFOSZ,
+ cfg->ep_fifosz_reg);
+ break;
+ }
+ }
+
+ MUSB2_WRITE_2(sc, MUSB2_REG_TXFIFOADD,
+ offset >> 3);
+
+ offset += (1 << ftx);
+ }
+ }
+
+ if (frx && ftx && (temp <= nrx) && (temp <= ntx)) {
+ pf->max_in_frame_size = 1 << ftx;
+ pf->max_out_frame_size = 1 << frx;
+ pf->is_simplex = 0; /* duplex */
+ pf->support_multi_buffer = 1;
+ pf->support_bulk = 1;
+ pf->support_interrupt = 1;
+ pf->support_isochronous = 1;
+ pf->support_in = 1;
+ pf->support_out = 1;
+ } else if (frx && (temp <= nrx)) {
+ pf->max_out_frame_size = 1 << frx;
+ pf->max_in_frame_size = 0;
+ pf->is_simplex = 1; /* simplex */
+ pf->support_multi_buffer = 1;
+ pf->support_bulk = 1;
+ pf->support_interrupt = 1;
+ pf->support_isochronous = 1;
+ pf->support_out = 1;
+ } else if (ftx && (temp <= ntx)) {
+ pf->max_in_frame_size = 1 << ftx;
+ pf->max_out_frame_size = 0;
+ pf->is_simplex = 1; /* simplex */
+ pf->support_multi_buffer = 1;
+ pf->support_bulk = 1;
+ pf->support_interrupt = 1;
+ pf->support_isochronous = 1;
+ pf->support_in = 1;
+ }
+ }
+
+ DPRINTFN(2, "Dynamic FIFO size = %d bytes\n", offset);
+
+ /* turn on default interrupts */
+
+ if (sc->sc_mode == MUSB2_HOST_MODE)
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0xff);
+ else
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE,
+ MUSB2_MASK_IRESET);
+
+ musbotg_clocks_off(sc);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch any lost interrupts */
+
+ musbotg_do_poll(&sc->sc_bus);
+
+ return (0); /* success */
+}
+
+void
+musbotg_uninit(struct musbotg_softc *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* disable all interrupts */
+ MUSB2_WRITE_1(sc, MUSB2_REG_INTUSBE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTTXE, 0);
+ MUSB2_WRITE_2(sc, MUSB2_REG_INTRXE, 0);
+
+ sc->sc_flags.port_powered = 0;
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ musbotg_pull_down(sc);
+ musbotg_clocks_off(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+musbotg_do_poll(struct usb_bus *bus)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ musbotg_interrupt_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * musbotg bulk support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_bulk_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_bulk_close(struct usb_xfer *xfer)
+{
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_device_bulk_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_bulk_start(struct usb_xfer *xfer)
+{
+ /* setup TDs */
+ musbotg_setup_standard_chain(xfer);
+ musbotg_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods musbotg_device_bulk_methods =
+{
+ .open = musbotg_device_bulk_open,
+ .close = musbotg_device_bulk_close,
+ .enter = musbotg_device_bulk_enter,
+ .start = musbotg_device_bulk_start,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg control support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_ctrl_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_ctrl_close(struct usb_xfer *xfer)
+{
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_device_ctrl_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_ctrl_start(struct usb_xfer *xfer)
+{
+ /* setup TDs */
+ musbotg_setup_standard_chain(xfer);
+ musbotg_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods musbotg_device_ctrl_methods =
+{
+ .open = musbotg_device_ctrl_open,
+ .close = musbotg_device_ctrl_close,
+ .enter = musbotg_device_ctrl_enter,
+ .start = musbotg_device_ctrl_start,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg interrupt support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_intr_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_intr_close(struct usb_xfer *xfer)
+{
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_device_intr_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_intr_start(struct usb_xfer *xfer)
+{
+ /* setup TDs */
+ musbotg_setup_standard_chain(xfer);
+ musbotg_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods musbotg_device_intr_methods =
+{
+ .open = musbotg_device_intr_open,
+ .close = musbotg_device_intr_close,
+ .enter = musbotg_device_intr_enter,
+ .start = musbotg_device_intr_start,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+musbotg_device_isoc_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_device_isoc_close(struct usb_xfer *xfer)
+{
+ musbotg_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+musbotg_device_isoc_enter(struct usb_xfer *xfer)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(xfer->xroot->bus);
+ uint32_t nframes;
+
+ DPRINTFN(5, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->endpoint->isoc_next, xfer->nframes);
+
+ /* get the current frame index */
+
+ nframes = MUSB2_READ_2(sc, MUSB2_REG_FRAME);
+
+ if (usbd_xfer_get_isochronous_start_frame(
+ xfer, nframes, 0, 1, MUSB2_MASK_FRAME, NULL))
+ DPRINTFN(2, "start next=%d\n", xfer->endpoint->isoc_next);
+
+ /* setup TDs */
+ musbotg_setup_standard_chain(xfer);
+}
+
+static void
+musbotg_device_isoc_start(struct usb_xfer *xfer)
+{
+ /* start TD chain */
+ musbotg_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods musbotg_device_isoc_methods =
+{
+ .open = musbotg_device_isoc_open,
+ .close = musbotg_device_isoc_close,
+ .enter = musbotg_device_isoc_enter,
+ .start = musbotg_device_isoc_start,
+};
+
+/*------------------------------------------------------------------------*
+ * musbotg root control support
+ *------------------------------------------------------------------------*
+ * Simulate a hardware HUB by handling all the necessary requests.
+ *------------------------------------------------------------------------*/
+
+static const struct usb_device_descriptor musbotg_devd = {
+ .bLength = sizeof(struct usb_device_descriptor),
+ .bDescriptorType = UDESC_DEVICE,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_HSHUBSTT,
+ .bMaxPacketSize = 64,
+ .bcdDevice = {0x00, 0x01},
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .bNumConfigurations = 1,
+};
+
+static const struct usb_device_qualifier musbotg_odevd = {
+ .bLength = sizeof(struct usb_device_qualifier),
+ .bDescriptorType = UDESC_DEVICE_QUALIFIER,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_FSHUB,
+ .bMaxPacketSize0 = 0,
+ .bNumConfigurations = 0,
+};
+
+static const struct musbotg_config_desc musbotg_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(musbotg_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0,
+ },
+ .ifcd = {
+ .bLength = sizeof(struct usb_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = 0,
+ },
+ .endpd = {
+ .bLength = sizeof(struct usb_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = (UE_DIR_IN | MUSBOTG_INTR_ENDPT),
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8,
+ .bInterval = 255,
+ },
+};
+#define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) }
+
+static const struct usb_hub_descriptor_min musbotg_hubd = {
+ .bDescLength = sizeof(musbotg_hubd),
+ .bDescriptorType = UDESC_HUB,
+ .bNbrPorts = 1,
+ HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)),
+ .bPwrOn2PwrGood = 50,
+ .bHubContrCurrent = 0,
+ .DeviceRemovable = {0}, /* port is removable */
+};
+
+#define STRING_VENDOR \
+ "M\0e\0n\0t\0o\0r\0 \0G\0r\0a\0p\0h\0i\0c\0s"
+
+#define STRING_PRODUCT \
+ "O\0T\0G\0 \0R\0o\0o\0t\0 \0H\0U\0B"
+
+USB_MAKE_STRING_DESC(STRING_VENDOR, musbotg_vendor);
+USB_MAKE_STRING_DESC(STRING_PRODUCT, musbotg_product);
+
+static usb_error_t
+musbotg_roothub_exec(struct usb_device *udev,
+ struct usb_device_request *req, const void **pptr, uint16_t *plength)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus);
+ const void *ptr;
+ uint16_t len;
+ uint16_t value;
+ uint16_t index;
+ uint8_t reg;
+ usb_error_t err;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* buffer reset */
+ ptr = (const void *)&sc->sc_hub_temp;
+ len = 0;
+ err = 0;
+
+ value = UGETW(req->wValue);
+ index = UGETW(req->wIndex);
+
+ /* demultiplex the control request */
+
+ switch (req->bmRequestType) {
+ case UT_READ_DEVICE:
+ switch (req->bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_descriptor;
+ case UR_GET_CONFIG:
+ goto tr_handle_get_config;
+ case UR_GET_STATUS:
+ goto tr_handle_get_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_DEVICE:
+ switch (req->bRequest) {
+ case UR_SET_ADDRESS:
+ goto tr_handle_set_address;
+ case UR_SET_CONFIG:
+ goto tr_handle_set_config;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_DESCRIPTOR:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_ENDPOINT:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ switch (UGETW(req->wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_clear_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_clear_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SET_FEATURE:
+ switch (UGETW(req->wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_set_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_set_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SYNCH_FRAME:
+ goto tr_valid; /* nop */
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_ENDPOINT:
+ switch (req->bRequest) {
+ case UR_GET_STATUS:
+ goto tr_handle_get_ep_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_INTERFACE:
+ switch (req->bRequest) {
+ case UR_SET_INTERFACE:
+ goto tr_handle_set_interface;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_INTERFACE:
+ switch (req->bRequest) {
+ case UR_GET_INTERFACE:
+ goto tr_handle_get_interface;
+ case UR_GET_STATUS:
+ goto tr_handle_get_iface_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_INTERFACE:
+ case UT_WRITE_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_READ_CLASS_INTERFACE:
+ case UT_READ_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_WRITE_CLASS_DEVICE:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_valid;
+ case UR_SET_DESCRIPTOR:
+ case UR_SET_FEATURE:
+ break;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_OTHER:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_handle_clear_port_feature;
+ case UR_SET_FEATURE:
+ goto tr_handle_set_port_feature;
+ case UR_CLEAR_TT_BUFFER:
+ case UR_RESET_TT:
+ case UR_STOP_TT:
+ goto tr_valid;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_OTHER:
+ switch (req->bRequest) {
+ case UR_GET_TT_STATE:
+ goto tr_handle_get_tt_state;
+ case UR_GET_STATUS:
+ goto tr_handle_get_port_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_DEVICE:
+ switch (req->bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_class_descriptor;
+ case UR_GET_STATUS:
+ goto tr_handle_get_class_status;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_valid;
+
+tr_handle_get_descriptor:
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ len = sizeof(musbotg_devd);
+ ptr = (const void *)&musbotg_devd;
+ goto tr_valid;
+ case UDESC_DEVICE_QUALIFIER:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ len = sizeof(musbotg_odevd);
+ ptr = (const void *)&musbotg_odevd;
+ goto tr_valid;
+ case UDESC_CONFIG:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ len = sizeof(musbotg_confd);
+ ptr = (const void *)&musbotg_confd;
+ goto tr_valid;
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ len = sizeof(usb_string_lang_en);
+ ptr = (const void *)&usb_string_lang_en;
+ goto tr_valid;
+
+ case 1: /* Vendor */
+ len = sizeof(musbotg_vendor);
+ ptr = (const void *)&musbotg_vendor;
+ goto tr_valid;
+
+ case 2: /* Product */
+ len = sizeof(musbotg_product);
+ ptr = (const void *)&musbotg_product;
+ goto tr_valid;
+ default:
+ break;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_stalled;
+
+tr_handle_get_config:
+ len = 1;
+ sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+ goto tr_valid;
+
+tr_handle_get_status:
+ len = 2;
+ USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED);
+ goto tr_valid;
+
+tr_handle_set_address:
+ if (value & 0xFF00) {
+ goto tr_stalled;
+ }
+ sc->sc_rt_addr = value;
+ goto tr_valid;
+
+tr_handle_set_config:
+ if (value >= 2) {
+ goto tr_stalled;
+ }
+ sc->sc_conf = value;
+ goto tr_valid;
+
+tr_handle_get_interface:
+ len = 1;
+ sc->sc_hub_temp.wValue[0] = 0;
+ goto tr_valid;
+
+tr_handle_get_tt_state:
+tr_handle_get_class_status:
+tr_handle_get_iface_status:
+tr_handle_get_ep_status:
+ len = 2;
+ USETW(sc->sc_hub_temp.wValue, 0);
+ goto tr_valid;
+
+tr_handle_set_halt:
+tr_handle_set_interface:
+tr_handle_set_wakeup:
+tr_handle_clear_wakeup:
+tr_handle_clear_halt:
+ goto tr_valid;
+
+tr_handle_clear_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(8, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
+
+ switch (value) {
+ case UHF_PORT_SUSPEND:
+ if (sc->sc_mode == MUSB2_HOST_MODE)
+ musbotg_wakeup_host(sc);
+ else
+ musbotg_wakeup_peer(sc);
+ break;
+
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 0;
+ break;
+
+ case UHF_C_PORT_ENABLE:
+ sc->sc_flags.change_enabled = 0;
+ break;
+
+ case UHF_C_PORT_OVER_CURRENT:
+ sc->sc_flags.change_over_current = 0;
+ break;
+
+ case UHF_C_PORT_RESET:
+ sc->sc_flags.change_reset = 0;
+ break;
+
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ /* nops */
+ break;
+
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 0;
+ musbotg_pull_down(sc);
+ musbotg_clocks_off(sc);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ sc->sc_flags.change_connect = 0;
+ break;
+ case UHF_C_PORT_SUSPEND:
+ sc->sc_flags.change_suspend = 0;
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_set_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(8, "UR_SET_PORT_FEATURE\n");
+
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 1;
+ break;
+ case UHF_PORT_SUSPEND:
+ if (sc->sc_mode == MUSB2_HOST_MODE)
+ musbotg_suspend_host(sc);
+ break;
+
+ case UHF_PORT_RESET:
+ if (sc->sc_mode == MUSB2_HOST_MODE) {
+ reg = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ reg |= MUSB2_MASK_RESET;
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, reg);
+
+ /* Wait for 20 msec */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 5);
+
+ reg = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ reg &= ~MUSB2_MASK_RESET;
+ MUSB2_WRITE_1(sc, MUSB2_REG_POWER, reg);
+
+ /* determine line speed */
+ reg = MUSB2_READ_1(sc, MUSB2_REG_POWER);
+ if (reg & MUSB2_MASK_HSMODE)
+ sc->sc_flags.status_high_speed = 1;
+ else
+ sc->sc_flags.status_high_speed = 0;
+
+ sc->sc_flags.change_reset = 1;
+ } else
+ err = USB_ERR_IOERROR;
+ break;
+
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 1;
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_get_port_status:
+
+ DPRINTFN(8, "UR_GET_PORT_STATUS\n");
+
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ if (sc->sc_flags.status_vbus) {
+ musbotg_clocks_on(sc);
+ musbotg_pull_up(sc);
+ } else {
+ musbotg_pull_down(sc);
+ musbotg_clocks_off(sc);
+ }
+
+ /* Select Device Side Mode */
+ if (sc->sc_mode == MUSB2_DEVICE_MODE)
+ value = UPS_PORT_MODE_DEVICE;
+ else
+ value = 0;
+
+ if (sc->sc_flags.status_high_speed) {
+ value |= UPS_HIGH_SPEED;
+ }
+ if (sc->sc_flags.port_powered) {
+ value |= UPS_PORT_POWER;
+ }
+ if (sc->sc_flags.port_enabled) {
+ value |= UPS_PORT_ENABLED;
+ }
+
+ if (sc->sc_flags.port_over_current)
+ value |= UPS_OVERCURRENT_INDICATOR;
+
+ if (sc->sc_flags.status_vbus &&
+ sc->sc_flags.status_bus_reset) {
+ value |= UPS_CURRENT_CONNECT_STATUS;
+ }
+ if (sc->sc_flags.status_suspend) {
+ value |= UPS_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortStatus, value);
+
+ value = 0;
+
+ if (sc->sc_flags.change_connect) {
+ value |= UPS_C_CONNECT_STATUS;
+
+ if (sc->sc_mode == MUSB2_DEVICE_MODE) {
+ if (sc->sc_flags.status_vbus &&
+ sc->sc_flags.status_bus_reset) {
+ /* reset EP0 state */
+ sc->sc_ep0_busy = 0;
+ sc->sc_ep0_cmd = 0;
+ }
+ }
+ }
+ if (sc->sc_flags.change_suspend)
+ value |= UPS_C_SUSPEND;
+ if (sc->sc_flags.change_reset)
+ value |= UPS_C_PORT_RESET;
+ if (sc->sc_flags.change_over_current)
+ value |= UPS_C_OVERCURRENT_INDICATOR;
+
+ USETW(sc->sc_hub_temp.ps.wPortChange, value);
+ len = sizeof(sc->sc_hub_temp.ps);
+ goto tr_valid;
+
+tr_handle_get_class_descriptor:
+ if (value & 0xFF) {
+ goto tr_stalled;
+ }
+ ptr = (const void *)&musbotg_hubd;
+ len = sizeof(musbotg_hubd);
+ goto tr_valid;
+
+tr_stalled:
+ err = USB_ERR_STALLED;
+tr_valid:
+done:
+ *plength = len;
+ *pptr = ptr;
+ return (err);
+}
+
+static void
+musbotg_xfer_setup(struct usb_setup_params *parm)
+{
+ struct usb_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t n;
+ uint8_t ep_no;
+
+ xfer = parm->curr_xfer;
+
+ /*
+ * NOTE: This driver does not use any of the parameters that
+ * are computed from the following values. Just set some
+ * reasonable dummies:
+ */
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_frame_size = 0xc00;
+
+ if ((parm->methods == &musbotg_device_isoc_methods) ||
+ (parm->methods == &musbotg_device_intr_methods))
+ parm->hc_max_packet_count = 3;
+ else
+ parm->hc_max_packet_count = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ /*
+ * compute maximum number of TDs
+ */
+ if (parm->methods == &musbotg_device_ctrl_methods) {
+ ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &musbotg_device_bulk_methods) {
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &musbotg_device_intr_methods) {
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &musbotg_device_isoc_methods) {
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else {
+ ntd = 0;
+ }
+
+ /*
+ * check if "usbd_transfer_setup_sub" set an error
+ */
+ if (parm->err) {
+ return;
+ }
+ /*
+ * allocate transfer descriptors
+ */
+ last_obj = NULL;
+
+ ep_no = xfer->endpointno & UE_ADDR;
+
+ /*
+ * Check for a valid endpoint profile in USB device mode:
+ */
+ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
+ const struct usb_hw_ep_profile *pf;
+
+ musbotg_get_hw_ep_profile(parm->udev, &pf, ep_no);
+
+ if (pf == NULL) {
+ /* should not happen */
+ parm->err = USB_ERR_INVAL;
+ return;
+ }
+ }
+
+ /* align data */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ for (n = 0; n != ntd; n++) {
+ struct musbotg_td *td;
+
+ if (parm->buf) {
+ td = USB_ADD_BYTES(parm->buf, parm->size[0]);
+
+ /* init TD */
+ td->max_frame_size = xfer->max_frame_size;
+ td->reg_max_packet = xfer->max_packet_size |
+ ((xfer->max_packet_count - 1) << 11);
+ td->ep_no = ep_no;
+ td->obj_next = last_obj;
+
+ last_obj = td;
+ }
+ parm->size[0] += sizeof(*td);
+ }
+
+ xfer->td_start[0] = last_obj;
+}
+
+static void
+musbotg_xfer_unsetup(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+musbotg_get_dma_delay(struct usb_device *udev, uint32_t *pus)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus);
+
+ if (sc->sc_mode == MUSB2_HOST_MODE)
+ *pus = 2000; /* microseconds */
+ else
+ *pus = 0;
+}
+
+static void
+musbotg_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
+ struct usb_endpoint *ep)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ ep, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb_mode,
+ sc->sc_rt_addr);
+
+ if (udev->device_index != sc->sc_rt_addr) {
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ ep->methods = &musbotg_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ ep->methods = &musbotg_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ ep->methods = &musbotg_device_isoc_methods;
+ break;
+ case UE_BULK:
+ ep->methods = &musbotg_device_bulk_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+static void
+musbotg_set_hw_power_sleep(struct usb_bus *bus, uint32_t state)
+{
+ struct musbotg_softc *sc = MUSBOTG_BUS2SC(bus);
+
+ switch (state) {
+ case USB_HW_POWER_SUSPEND:
+ musbotg_uninit(sc);
+ break;
+ case USB_HW_POWER_SHUTDOWN:
+ musbotg_uninit(sc);
+ break;
+ case USB_HW_POWER_RESUME:
+ musbotg_init(sc);
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct usb_bus_methods musbotg_bus_methods =
+{
+ .endpoint_init = &musbotg_ep_init,
+ .get_dma_delay = &musbotg_get_dma_delay,
+ .xfer_setup = &musbotg_xfer_setup,
+ .xfer_unsetup = &musbotg_xfer_unsetup,
+ .get_hw_ep_profile = &musbotg_get_hw_ep_profile,
+ .xfer_stall = &musbotg_xfer_stall,
+ .set_stall = &musbotg_set_stall,
+ .clear_stall = &musbotg_clear_stall,
+ .roothub_exec = &musbotg_roothub_exec,
+ .xfer_poll = &musbotg_do_poll,
+ .set_hw_power_sleep = &musbotg_set_hw_power_sleep,
+};
diff --git a/sys/dev/usb/controller/musb_otg.h b/sys/dev/usb/controller/musb_otg.h
new file mode 100644
index 000000000000..a41f7da94837
--- /dev/null
+++ b/sys/dev/usb/controller/musb_otg.h
@@ -0,0 +1,443 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This header file defines the registers of the Mentor Graphics USB OnTheGo
+ * Inventra chip.
+ */
+
+#ifndef _MUSB2_OTG_H_
+#define _MUSB2_OTG_H_
+
+#define MUSB2_MAX_DEVICES USB_MAX_DEVICES
+
+/* Common registers */
+
+#define MUSB2_REG_FADDR 0x0000 /* function address register */
+#define MUSB2_MASK_FADDR 0x7F
+
+#define MUSB2_REG_POWER 0x0001 /* power register */
+#define MUSB2_MASK_SUSPM_ENA 0x01
+#define MUSB2_MASK_SUSPMODE 0x02
+#define MUSB2_MASK_RESUME 0x04
+#define MUSB2_MASK_RESET 0x08
+#define MUSB2_MASK_HSMODE 0x10
+#define MUSB2_MASK_HSENAB 0x20
+#define MUSB2_MASK_SOFTC 0x40
+#define MUSB2_MASK_ISOUPD 0x80
+
+/* Endpoint interrupt handling */
+
+#define MUSB2_REG_INTTX 0x0002 /* transmit interrupt register */
+#define MUSB2_REG_INTRX 0x0004 /* receive interrupt register */
+#define MUSB2_REG_INTTXE 0x0006 /* transmit interrupt enable register */
+#define MUSB2_REG_INTRXE 0x0008 /* receive interrupt enable register */
+#define MUSB2_MASK_EPINT(epn) (1 << (epn)) /* epn = [0..15] */
+
+/* Common interrupt handling */
+
+#define MUSB2_REG_INTUSB 0x000A /* USB interrupt register */
+#define MUSB2_MASK_ISUSP 0x01
+#define MUSB2_MASK_IRESUME 0x02
+#define MUSB2_MASK_IRESET 0x04
+#define MUSB2_MASK_IBABBLE 0x04
+#define MUSB2_MASK_ISOF 0x08
+#define MUSB2_MASK_ICONN 0x10
+#define MUSB2_MASK_IDISC 0x20
+#define MUSB2_MASK_ISESSRQ 0x40
+#define MUSB2_MASK_IVBUSERR 0x80
+
+#define MUSB2_REG_INTUSBE 0x000B /* USB interrupt enable register */
+#define MUSB2_REG_FRAME 0x000C /* USB frame register */
+#define MUSB2_MASK_FRAME 0x3FF /* 0..1023 */
+
+#define MUSB2_REG_EPINDEX 0x000E /* endpoint index register */
+#define MUSB2_MASK_EPINDEX 0x0F
+
+#define MUSB2_REG_TESTMODE 0x000F /* test mode register */
+#define MUSB2_MASK_TSE0_NAK 0x01
+#define MUSB2_MASK_TJ 0x02
+#define MUSB2_MASK_TK 0x04
+#define MUSB2_MASK_TPACKET 0x08
+#define MUSB2_MASK_TFORCE_HS 0x10
+#define MUSB2_MASK_TFORCE_LS 0x20
+#define MUSB2_MASK_TFIFO_ACC 0x40
+#define MUSB2_MASK_TFORCE_HC 0x80
+
+#define MUSB2_REG_INDEXED_CSR 0x0010 /* EP control status register offset */
+
+#define MUSB2_REG_TXMAXP (0x0000 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_REG_RXMAXP (0x0004 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_PKTSIZE 0x03FF /* in bytes, should be even */
+#define MUSB2_MASK_PKTMULT 0xFC00 /* HS packet multiplier: 0..2 */
+
+#define MUSB2_REG_TXCSRL (0x0002 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_CSRL_TXPKTRDY 0x01
+#define MUSB2_MASK_CSRL_TXFIFONEMPTY 0x02
+#define MUSB2_MASK_CSRL_TXUNDERRUN 0x04 /* Device Mode */
+#define MUSB2_MASK_CSRL_TXERROR 0x04 /* Host Mode */
+#define MUSB2_MASK_CSRL_TXFFLUSH 0x08
+#define MUSB2_MASK_CSRL_TXSENDSTALL 0x10/* Device Mode */
+#define MUSB2_MASK_CSRL_TXSETUPPKT 0x10 /* Host Mode */
+#define MUSB2_MASK_CSRL_TXSENTSTALL 0x20/* Device Mode */
+#define MUSB2_MASK_CSRL_TXSTALLED 0x20 /* Host Mode */
+#define MUSB2_MASK_CSRL_TXDT_CLR 0x40
+#define MUSB2_MASK_CSRL_TXINCOMP 0x80 /* Device mode */
+#define MUSB2_MASK_CSRL_TXNAKTO 0x80 /* Host mode */
+
+/* Device Side Mode */
+#define MUSB2_MASK_CSR0L_RXPKTRDY 0x01
+#define MUSB2_MASK_CSR0L_TXPKTRDY 0x02
+#define MUSB2_MASK_CSR0L_SENTSTALL 0x04
+#define MUSB2_MASK_CSR0L_DATAEND 0x08
+#define MUSB2_MASK_CSR0L_SETUPEND 0x10
+#define MUSB2_MASK_CSR0L_SENDSTALL 0x20
+#define MUSB2_MASK_CSR0L_RXPKTRDY_CLR 0x40
+#define MUSB2_MASK_CSR0L_SETUPEND_CLR 0x80
+
+/* Host Side Mode */
+#define MUSB2_MASK_CSR0L_TXFIFONEMPTY 0x02
+#define MUSB2_MASK_CSR0L_RXSTALL 0x04
+#define MUSB2_MASK_CSR0L_SETUPPKT 0x08
+#define MUSB2_MASK_CSR0L_ERROR 0x10
+#define MUSB2_MASK_CSR0L_REQPKT 0x20
+#define MUSB2_MASK_CSR0L_STATUSPKT 0x40
+#define MUSB2_MASK_CSR0L_NAKTIMO 0x80
+
+#define MUSB2_REG_TXCSRH (0x0003 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_CSRH_TXDT_VAL 0x01 /* Host Mode */
+#define MUSB2_MASK_CSRH_TXDT_WREN 0x02 /* Host Mode */
+#define MUSB2_MASK_CSRH_TXDMAREQMODE 0x04
+#define MUSB2_MASK_CSRH_TXDT_SWITCH 0x08
+#define MUSB2_MASK_CSRH_TXDMAREQENA 0x10
+#define MUSB2_MASK_CSRH_RXMODE 0x00
+#define MUSB2_MASK_CSRH_TXMODE 0x20
+#define MUSB2_MASK_CSRH_TXISO 0x40 /* Device Mode */
+#define MUSB2_MASK_CSRH_TXAUTOSET 0x80
+
+#define MUSB2_MASK_CSR0H_FFLUSH 0x01 /* Device Side flush FIFO */
+#define MUSB2_MASK_CSR0H_DT 0x02 /* Host Side data toggle */
+#define MUSB2_MASK_CSR0H_DT_WREN 0x04 /* Host Side */
+#define MUSB2_MASK_CSR0H_PING_DIS 0x08 /* Host Side */
+
+#define MUSB2_REG_RXCSRL (0x0006 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_CSRL_RXPKTRDY 0x01
+#define MUSB2_MASK_CSRL_RXFIFOFULL 0x02
+#define MUSB2_MASK_CSRL_RXOVERRUN 0x04 /* Device Mode */
+#define MUSB2_MASK_CSRL_RXERROR 0x04 /* Host Mode */
+#define MUSB2_MASK_CSRL_RXDATAERR 0x08 /* Device Mode */
+#define MUSB2_MASK_CSRL_RXNAKTO 0x08 /* Host Mode */
+#define MUSB2_MASK_CSRL_RXFFLUSH 0x10
+#define MUSB2_MASK_CSRL_RXSENDSTALL 0x20/* Device Mode */
+#define MUSB2_MASK_CSRL_RXREQPKT 0x20 /* Host Mode */
+#define MUSB2_MASK_CSRL_RXSENTSTALL 0x40/* Device Mode */
+#define MUSB2_MASK_CSRL_RXSTALL 0x40 /* Host Mode */
+#define MUSB2_MASK_CSRL_RXDT_CLR 0x80
+
+#define MUSB2_REG_RXCSRH (0x0007 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_CSRH_RXINCOMP 0x01
+#define MUSB2_MASK_CSRH_RXDT_VAL 0x02 /* Host Mode */
+#define MUSB2_MASK_CSRH_RXDT_WREN 0x04 /* Host Mode */
+#define MUSB2_MASK_CSRH_RXDMAREQMODE 0x08
+#define MUSB2_MASK_CSRH_RXNYET 0x10
+#define MUSB2_MASK_CSRH_RXDMAREQENA 0x20
+#define MUSB2_MASK_CSRH_RXISO 0x40 /* Device Mode */
+#define MUSB2_MASK_CSRH_RXAUTOREQ 0x40 /* Host Mode */
+#define MUSB2_MASK_CSRH_RXAUTOCLEAR 0x80
+
+#define MUSB2_REG_RXCOUNT (0x0008 + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_RXCOUNT 0xFFFF
+
+#define MUSB2_REG_TXTI (0x000A + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_REG_RXTI (0x000C + MUSB2_REG_INDEXED_CSR)
+
+/* Host Mode */
+#define MUSB2_MASK_TI_SPEED 0xC0
+#define MUSB2_MASK_TI_SPEED_LO 0xC0
+#define MUSB2_MASK_TI_SPEED_FS 0x80
+#define MUSB2_MASK_TI_SPEED_HS 0x40
+#define MUSB2_MASK_TI_PROTO_CTRL 0x00
+#define MUSB2_MASK_TI_PROTO_ISOC 0x10
+#define MUSB2_MASK_TI_PROTO_BULK 0x20
+#define MUSB2_MASK_TI_PROTO_INTR 0x30
+#define MUSB2_MASK_TI_EP_NUM 0x0F
+
+#define MUSB2_REG_TXNAKLIMIT (0x000B /* EPN=0 */ + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_REG_RXNAKLIMIT (0x000D /* EPN=0 */ + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_NAKLIMIT 0xFF
+
+#define MUSB2_REG_FSIZE (0x000F + MUSB2_REG_INDEXED_CSR)
+#define MUSB2_MASK_RX_FSIZE 0xF0 /* 3..13, 2**n bytes */
+#define MUSB2_MASK_TX_FSIZE 0x0F /* 3..13, 2**n bytes */
+
+#define MUSB2_REG_EPFIFO(n) (0x0020 + (4*(n)))
+
+#define MUSB2_REG_CONFDATA (0x000F + MUSB2_REG_INDEXED_CSR) /* EPN=0 */
+#define MUSB2_MASK_CD_UTMI_DW 0x01
+#define MUSB2_MASK_CD_SOFTCONE 0x02
+#define MUSB2_MASK_CD_DYNFIFOSZ 0x04
+#define MUSB2_MASK_CD_HBTXE 0x08
+#define MUSB2_MASK_CD_HBRXE 0x10
+#define MUSB2_MASK_CD_BIGEND 0x20
+#define MUSB2_MASK_CD_MPTXE 0x40
+#define MUSB2_MASK_CD_MPRXE 0x80
+
+/* Various registers */
+
+#define MUSB2_REG_DEVCTL 0x0060
+#define MUSB2_MASK_SESS 0x01
+#define MUSB2_MASK_HOSTREQ 0x02
+#define MUSB2_MASK_HOSTMD 0x04
+#define MUSB2_MASK_VBUS0 0x08
+#define MUSB2_MASK_VBUS1 0x10
+#define MUSB2_MASK_LSDEV 0x20
+#define MUSB2_MASK_FSDEV 0x40
+#define MUSB2_MASK_BDEV 0x80
+
+#define MUSB2_REG_MISC 0x0061
+#define MUSB2_MASK_RXEDMA 0x01
+#define MUSB2_MASK_TXEDMA 0x02
+
+#define MUSB2_REG_TXFIFOSZ 0x0062
+#define MUSB2_REG_RXFIFOSZ 0x0063
+#define MUSB2_MASK_FIFODB 0x10 /* set if double buffering, r/w */
+#define MUSB2_MASK_FIFOSZ 0x0F
+#define MUSB2_VAL_FIFOSZ_8 0
+#define MUSB2_VAL_FIFOSZ_16 1
+#define MUSB2_VAL_FIFOSZ_32 2
+#define MUSB2_VAL_FIFOSZ_64 3
+#define MUSB2_VAL_FIFOSZ_128 4
+#define MUSB2_VAL_FIFOSZ_256 5
+#define MUSB2_VAL_FIFOSZ_512 6
+#define MUSB2_VAL_FIFOSZ_1024 7
+#define MUSB2_VAL_FIFOSZ_2048 8
+#define MUSB2_VAL_FIFOSZ_4096 9
+
+#define MUSB2_REG_TXFIFOADD 0x0064
+#define MUSB2_REG_RXFIFOADD 0x0066
+#define MUSB2_MASK_FIFOADD 0xFFF /* unit is 8-bytes */
+
+#define MUSB2_REG_VSTATUS 0x0068
+#define MUSB2_REG_VCONTROL 0x0068
+#define MUSB2_REG_HWVERS 0x006C
+#define MUSB2_REG_ULPI_BASE 0x0070
+
+#define MUSB2_REG_EPINFO 0x0078
+#define MUSB2_MASK_NRXEP 0xF0
+#define MUSB2_MASK_NTXEP 0x0F
+
+#define MUSB2_REG_RAMINFO 0x0079
+#define MUSB2_REG_LINKINFO 0x007A
+
+#define MUSB2_REG_VPLEN 0x007B
+#define MUSB2_MASK_VPLEN 0xFF
+
+#define MUSB2_REG_HS_EOF1 0x007C
+#define MUSB2_REG_FS_EOF1 0x007D
+#define MUSB2_REG_LS_EOF1 0x007E
+#define MUSB2_REG_SOFT_RST 0x007F
+#define MUSB2_MASK_SRST 0x01
+#define MUSB2_MASK_SRSTX 0x02
+
+#define MUSB2_REG_RQPKTCOUNT(n) (0x0300 + (4*(n))
+#define MUSB2_REG_RXDBDIS 0x0340
+#define MUSB2_REG_TXDBDIS 0x0342
+#define MUSB2_MASK_DB(n) (1 << (n)) /* disable double buffer, n = [0..15] */
+
+#define MUSB2_REG_CHIRPTO 0x0344
+#define MUSB2_REG_HSRESUM 0x0346
+
+/* Host Mode only registers */
+
+#define MUSB2_REG_TXFADDR(n) (0x0080 + (8*(n)))
+#define MUSB2_REG_TXHADDR(n) (0x0082 + (8*(n)))
+#define MUSB2_REG_TXHUBPORT(n) (0x0083 + (8*(n)))
+#define MUSB2_REG_RXFADDR(n) (0x0084 + (8*(n)))
+#define MUSB2_REG_RXHADDR(n) (0x0086 + (8*(n)))
+#define MUSB2_REG_RXHUBPORT(n) (0x0087 + (8*(n)))
+
+#define MUSB2_EP_MAX 16 /* maximum number of endpoints */
+
+#define MUSB2_DEVICE_MODE 0
+#define MUSB2_HOST_MODE 1
+
+#define MUSB2_READ_2(sc, reg) \
+ bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, reg)
+
+#define MUSB2_WRITE_2(sc, reg, data) \
+ bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data)
+
+#define MUSB2_READ_1(sc, reg) \
+ bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg)
+
+#define MUSB2_WRITE_1(sc, reg, data) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, reg, data)
+
+struct musbotg_td;
+struct musbotg_softc;
+
+typedef uint8_t (musbotg_cmd_t)(struct musbotg_td *td);
+
+struct musbotg_dma {
+ struct musbotg_softc *sc;
+ uint32_t dma_chan;
+ uint8_t busy:1;
+ uint8_t complete:1;
+ uint8_t error:1;
+};
+
+struct musbotg_td {
+ struct musbotg_td *obj_next;
+ musbotg_cmd_t *func;
+ struct usb_page_cache *pc;
+ uint32_t offset;
+ uint32_t remainder;
+ uint16_t max_frame_size; /* packet_size * mult */
+ uint16_t reg_max_packet;
+ uint8_t ep_no;
+ uint8_t transfer_type;
+ uint8_t error:1;
+ uint8_t alt_next:1;
+ uint8_t short_pkt:1;
+ uint8_t support_multi_buffer:1;
+ uint8_t did_stall:1;
+ uint8_t dma_enabled:1;
+ uint8_t transaction_started:1;
+ uint8_t dev_addr;
+ uint8_t toggle;
+ int8_t channel;
+ uint8_t haddr;
+ uint8_t hport;
+};
+
+struct musbotg_std_temp {
+ musbotg_cmd_t *func;
+ struct usb_page_cache *pc;
+ struct musbotg_td *td;
+ struct musbotg_td *td_next;
+ uint32_t len;
+ uint32_t offset;
+ uint16_t max_frame_size;
+ uint8_t short_pkt;
+ /*
+ * short_pkt = 0: transfer should be short terminated
+ * short_pkt = 1: transfer should not be short terminated
+ */
+ uint8_t setup_alt_next;
+ uint8_t did_stall;
+ uint8_t dev_addr;
+ int8_t channel;
+ uint8_t haddr;
+ uint8_t hport;
+ uint8_t transfer_type;
+};
+
+struct musbotg_config_desc {
+ struct usb_config_descriptor confd;
+ struct usb_interface_descriptor ifcd;
+ struct usb_endpoint_descriptor endpd;
+} __packed;
+
+union musbotg_hub_temp {
+ uWord wValue;
+ struct usb_port_status ps;
+};
+
+struct musbotg_flags {
+ uint8_t change_connect:1;
+ uint8_t change_suspend:1;
+ uint8_t change_reset:1;
+ uint8_t change_over_current:1;
+ uint8_t change_enabled:1;
+ uint8_t status_suspend:1; /* set if suspended */
+ uint8_t status_vbus:1; /* set if present */
+ uint8_t status_bus_reset:1; /* set if reset complete */
+ uint8_t status_high_speed:1; /* set if High Speed is selected */
+ uint8_t remote_wakeup:1;
+ uint8_t self_powered:1;
+ uint8_t clocks_off:1;
+ uint8_t port_powered:1;
+ uint8_t port_enabled:1;
+ uint8_t port_over_current:1;
+ uint8_t d_pulled_up:1;
+};
+
+struct musb_otg_ep_cfg {
+ int ep_end;
+ int ep_fifosz_shift;
+ uint8_t ep_fifosz_reg;
+};
+
+struct musbotg_softc {
+ struct usb_bus sc_bus;
+ union musbotg_hub_temp sc_hub_temp;
+ struct usb_hw_ep_profile sc_hw_ep_profile[MUSB2_EP_MAX];
+
+ struct usb_device *sc_devices[MUSB2_MAX_DEVICES];
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ void *sc_intr_hdl;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ void (*sc_clocks_on) (void *arg);
+ void (*sc_clocks_off) (void *arg);
+ void (*sc_ep_int_set) (struct musbotg_softc *sc, int ep, int on);
+ void *sc_clocks_arg;
+
+ uint32_t sc_bounce_buf[(1024 * 3) / 4]; /* bounce buffer */
+
+ uint8_t sc_ep_max; /* maximum number of RX and TX
+ * endpoints supported */
+ uint8_t sc_rt_addr; /* root HUB address */
+ uint8_t sc_dv_addr; /* device address */
+ uint8_t sc_conf; /* root HUB config */
+ uint8_t sc_ep0_busy; /* set if ep0 is busy */
+ uint8_t sc_ep0_cmd; /* pending commands */
+ uint8_t sc_conf_data; /* copy of hardware register */
+
+ uint8_t sc_hub_idata[1];
+ uint16_t sc_channel_mask; /* 16 endpoints */
+
+ struct musbotg_flags sc_flags;
+ uint8_t sc_id;
+ uint8_t sc_mode;
+ void *sc_platform_data;
+ const struct musb_otg_ep_cfg *sc_ep_cfg;
+};
+
+/* prototypes */
+
+usb_error_t musbotg_init(struct musbotg_softc *sc);
+void musbotg_uninit(struct musbotg_softc *sc);
+void musbotg_interrupt(struct musbotg_softc *sc,
+ uint16_t rxstat, uint16_t txstat, uint8_t stat);
+void musbotg_vbus_interrupt(struct musbotg_softc *sc, uint8_t is_on);
+void musbotg_connect_interrupt(struct musbotg_softc *sc);
+
+#endif /* _MUSB2_OTG_H_ */
diff --git a/sys/dev/usb/controller/musb_otg_allwinner.c b/sys/dev/usb/controller/musb_otg_allwinner.c
new file mode 100644
index 000000000000..781b4d7e33fa
--- /dev/null
+++ b/sys/dev/usb/controller/musb_otg_allwinner.c
@@ -0,0 +1,617 @@
+/*-
+ * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
+ * Copyright (c) 2018 Andrew Turner <andrew@FreeBSD.org>
+ * All rights reserved.
+ *
+ * This software was developed by SRI International and the University of
+ * Cambridge Computer Laboratory under DARPA/AFRL contract FA8750-10-C-0237
+ * ("CTSRD"), as part of the DARPA CRASH research programme.
+ *
+ * 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.
+ */
+
+/*
+ * Allwinner USB Dual-Role Device (DRD) controller
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+#include <sys/kernel.h>
+#include <sys/condvar.h>
+#include <sys/module.h>
+
+#include <machine/bus.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/controller/musb_otg.h>
+
+#include <dev/clk/clk.h>
+#include <dev/hwreset/hwreset.h>
+#include <dev/phy/phy.h>
+#include <dev/phy/phy_usb.h>
+
+#ifdef __arm__
+#include <arm/allwinner/aw_machdep.h>
+#include <arm/allwinner/a10_sramc.h>
+#endif
+
+#define DRD_EP_MAX 5
+#define DRD_EP_MAX_H3 4
+
+#define MUSB2_REG_AWIN_VEND0 0x0043
+#define VEND0_PIO_MODE 0
+
+#if defined(__arm__)
+#define bs_parent_space(bs) ((bs)->bs_parent)
+typedef bus_space_tag_t awusb_bs_tag;
+#elif defined(__aarch64__) || defined(__riscv)
+#define bs_parent_space(bs) (bs)
+typedef void * awusb_bs_tag;
+#endif
+
+#define AWUSB_OKAY 0x01
+#define AWUSB_NO_CONFDATA 0x02
+static struct ofw_compat_data compat_data[] = {
+ { "allwinner,sun4i-a10-musb", AWUSB_OKAY },
+ { "allwinner,sun6i-a31-musb", AWUSB_OKAY },
+ { "allwinner,sun8i-a33-musb", AWUSB_OKAY | AWUSB_NO_CONFDATA },
+ { "allwinner,sun8i-h3-musb", AWUSB_OKAY | AWUSB_NO_CONFDATA },
+ { "allwinner,sun20i-d1-musb", AWUSB_OKAY | AWUSB_NO_CONFDATA },
+ { NULL, 0 }
+};
+
+static const struct musb_otg_ep_cfg musbotg_ep_allwinner[] = {
+ {
+ .ep_end = DRD_EP_MAX,
+ .ep_fifosz_shift = 9,
+ .ep_fifosz_reg = MUSB2_VAL_FIFOSZ_512,
+ },
+ {
+ .ep_end = -1,
+ },
+};
+
+static const struct musb_otg_ep_cfg musbotg_ep_allwinner_h3[] = {
+ {
+ .ep_end = DRD_EP_MAX_H3,
+ .ep_fifosz_shift = 9,
+ .ep_fifosz_reg = MUSB2_VAL_FIFOSZ_512,
+ },
+ {
+ .ep_end = -1,
+ },
+};
+
+struct awusbdrd_softc {
+ struct musbotg_softc sc;
+ struct resource *res[2];
+ clk_t clk;
+ hwreset_t reset;
+ phy_t phy;
+ struct bus_space bs;
+ int flags;
+};
+
+static struct resource_spec awusbdrd_spec[] = {
+ { SYS_RES_MEMORY, 0, RF_ACTIVE },
+ { SYS_RES_IRQ, 0, RF_ACTIVE },
+ { -1, 0 }
+};
+
+#define REMAPFLAG 0x8000
+#define REGDECL(a, b) [(a)] = ((b) | REMAPFLAG)
+
+/* Allwinner USB DRD register mappings */
+static const uint16_t awusbdrd_regmap[] = {
+ REGDECL(MUSB2_REG_EPFIFO(0), 0x0000),
+ REGDECL(MUSB2_REG_EPFIFO(1), 0x0004),
+ REGDECL(MUSB2_REG_EPFIFO(2), 0x0008),
+ REGDECL(MUSB2_REG_EPFIFO(3), 0x000c),
+ REGDECL(MUSB2_REG_EPFIFO(4), 0x0010),
+ REGDECL(MUSB2_REG_EPFIFO(5), 0x0014),
+ REGDECL(MUSB2_REG_POWER, 0x0040),
+ REGDECL(MUSB2_REG_DEVCTL, 0x0041),
+ REGDECL(MUSB2_REG_EPINDEX, 0x0042),
+ REGDECL(MUSB2_REG_INTTX, 0x0044),
+ REGDECL(MUSB2_REG_INTRX, 0x0046),
+ REGDECL(MUSB2_REG_INTTXE, 0x0048),
+ REGDECL(MUSB2_REG_INTRXE, 0x004a),
+ REGDECL(MUSB2_REG_INTUSB, 0x004c),
+ REGDECL(MUSB2_REG_INTUSBE, 0x0050),
+ REGDECL(MUSB2_REG_FRAME, 0x0054),
+ REGDECL(MUSB2_REG_TESTMODE, 0x007c),
+ REGDECL(MUSB2_REG_TXMAXP, 0x0080),
+ REGDECL(MUSB2_REG_TXCSRL, 0x0082),
+ REGDECL(MUSB2_REG_TXCSRH, 0x0083),
+ REGDECL(MUSB2_REG_RXMAXP, 0x0084),
+ REGDECL(MUSB2_REG_RXCSRL, 0x0086),
+ REGDECL(MUSB2_REG_RXCSRH, 0x0087),
+ REGDECL(MUSB2_REG_RXCOUNT, 0x0088),
+ REGDECL(MUSB2_REG_TXTI, 0x008c),
+ REGDECL(MUSB2_REG_TXNAKLIMIT, 0x008d),
+ REGDECL(MUSB2_REG_RXNAKLIMIT, 0x008f),
+ REGDECL(MUSB2_REG_RXTI, 0x008e),
+ REGDECL(MUSB2_REG_TXFIFOSZ, 0x0090),
+ REGDECL(MUSB2_REG_TXFIFOADD, 0x0092),
+ REGDECL(MUSB2_REG_RXFIFOSZ, 0x0094),
+ REGDECL(MUSB2_REG_RXFIFOADD, 0x0096),
+ REGDECL(MUSB2_REG_FADDR, 0x0098),
+ REGDECL(MUSB2_REG_TXFADDR(0), 0x0098),
+ REGDECL(MUSB2_REG_TXHADDR(0), 0x009a),
+ REGDECL(MUSB2_REG_TXHUBPORT(0), 0x009b),
+ REGDECL(MUSB2_REG_RXFADDR(0), 0x009c),
+ REGDECL(MUSB2_REG_RXHADDR(0), 0x009e),
+ REGDECL(MUSB2_REG_RXHUBPORT(0), 0x009f),
+ REGDECL(MUSB2_REG_TXFADDR(1), 0x0098),
+ REGDECL(MUSB2_REG_TXHADDR(1), 0x009a),
+ REGDECL(MUSB2_REG_TXHUBPORT(1), 0x009b),
+ REGDECL(MUSB2_REG_RXFADDR(1), 0x009c),
+ REGDECL(MUSB2_REG_RXHADDR(1), 0x009e),
+ REGDECL(MUSB2_REG_RXHUBPORT(1), 0x009f),
+ REGDECL(MUSB2_REG_TXFADDR(2), 0x0098),
+ REGDECL(MUSB2_REG_TXHADDR(2), 0x009a),
+ REGDECL(MUSB2_REG_TXHUBPORT(2), 0x009b),
+ REGDECL(MUSB2_REG_RXFADDR(2), 0x009c),
+ REGDECL(MUSB2_REG_RXHADDR(2), 0x009e),
+ REGDECL(MUSB2_REG_RXHUBPORT(2), 0x009f),
+ REGDECL(MUSB2_REG_TXFADDR(3), 0x0098),
+ REGDECL(MUSB2_REG_TXHADDR(3), 0x009a),
+ REGDECL(MUSB2_REG_TXHUBPORT(3), 0x009b),
+ REGDECL(MUSB2_REG_RXFADDR(3), 0x009c),
+ REGDECL(MUSB2_REG_RXHADDR(3), 0x009e),
+ REGDECL(MUSB2_REG_RXHUBPORT(3), 0x009f),
+ REGDECL(MUSB2_REG_TXFADDR(4), 0x0098),
+ REGDECL(MUSB2_REG_TXHADDR(4), 0x009a),
+ REGDECL(MUSB2_REG_TXHUBPORT(4), 0x009b),
+ REGDECL(MUSB2_REG_RXFADDR(4), 0x009c),
+ REGDECL(MUSB2_REG_RXHADDR(4), 0x009e),
+ REGDECL(MUSB2_REG_RXHUBPORT(4), 0x009f),
+ REGDECL(MUSB2_REG_TXFADDR(5), 0x0098),
+ REGDECL(MUSB2_REG_TXHADDR(5), 0x009a),
+ REGDECL(MUSB2_REG_TXHUBPORT(5), 0x009b),
+ REGDECL(MUSB2_REG_RXFADDR(5), 0x009c),
+ REGDECL(MUSB2_REG_RXHADDR(5), 0x009e),
+ REGDECL(MUSB2_REG_RXHUBPORT(5), 0x009f),
+ REGDECL(MUSB2_REG_CONFDATA, 0x00c0),
+};
+
+static bus_size_t
+awusbdrd_reg(bus_size_t o)
+{
+ bus_size_t v;
+
+ KASSERT(o < nitems(awusbdrd_regmap),
+ ("%s: Invalid register %#lx", __func__, o));
+ if (o >= nitems(awusbdrd_regmap))
+ return (o);
+
+ v = awusbdrd_regmap[o];
+
+ KASSERT((v & REMAPFLAG) != 0, ("%s: reg %#lx not in regmap",
+ __func__, o));
+
+ return (v & ~REMAPFLAG);
+}
+
+static int
+awusbdrd_filt(bus_size_t o)
+{
+ switch (o) {
+ case MUSB2_REG_MISC:
+ case MUSB2_REG_RXDBDIS:
+ case MUSB2_REG_TXDBDIS:
+ return (1);
+ default:
+ return (0);
+ }
+}
+
+static uint8_t
+awusbdrd_bs_r_1(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o)
+{
+ struct bus_space *bs = t;
+
+ switch (o) {
+ case MUSB2_REG_HWVERS:
+ return (0); /* no known equivalent */
+ }
+
+ return (bus_space_read_1(bs_parent_space(bs), h, awusbdrd_reg(o)));
+}
+
+static uint8_t
+awusbdrd_bs_r_1_noconf(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o)
+{
+
+ /*
+ * There is no confdata register on some SoCs, return the same
+ * magic value as Linux.
+ */
+ if (o == MUSB2_REG_CONFDATA)
+ return (0xde);
+
+ return (awusbdrd_bs_r_1(t, h, o));
+}
+
+
+static uint16_t
+awusbdrd_bs_r_2(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o)
+{
+ struct bus_space *bs = t;
+
+ if (awusbdrd_filt(o) != 0)
+ return (0);
+ return bus_space_read_2(bs_parent_space(bs), h, awusbdrd_reg(o));
+}
+
+static void
+awusbdrd_bs_w_1(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o,
+ uint8_t v)
+{
+ struct bus_space *bs = t;
+
+ if (awusbdrd_filt(o) != 0)
+ return;
+
+ bus_space_write_1(bs_parent_space(bs), h, awusbdrd_reg(o), v);
+}
+
+static void
+awusbdrd_bs_w_2(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o,
+ uint16_t v)
+{
+ struct bus_space *bs = t;
+
+ if (awusbdrd_filt(o) != 0)
+ return;
+
+ bus_space_write_2(bs_parent_space(bs), h, awusbdrd_reg(o), v);
+}
+
+static void
+awusbdrd_bs_rm_1(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o,
+ uint8_t *d, bus_size_t c)
+{
+ struct bus_space *bs = t;
+
+ bus_space_read_multi_1(bs_parent_space(bs), h, awusbdrd_reg(o), d, c);
+}
+
+static void
+awusbdrd_bs_rm_4(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o,
+ uint32_t *d, bus_size_t c)
+{
+ struct bus_space *bs = t;
+
+ bus_space_read_multi_4(bs_parent_space(bs), h, awusbdrd_reg(o), d, c);
+}
+
+static void
+awusbdrd_bs_wm_1(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o,
+ const uint8_t *d, bus_size_t c)
+{
+ struct bus_space *bs = t;
+
+ if (awusbdrd_filt(o) != 0)
+ return;
+
+ bus_space_write_multi_1(bs_parent_space(bs), h, awusbdrd_reg(o), d, c);
+}
+
+static void
+awusbdrd_bs_wm_4(awusb_bs_tag t, bus_space_handle_t h, bus_size_t o,
+ const uint32_t *d, bus_size_t c)
+{
+ struct bus_space *bs = t;
+
+ if (awusbdrd_filt(o) != 0)
+ return;
+
+ bus_space_write_multi_4(bs_parent_space(bs), h, awusbdrd_reg(o), d, c);
+}
+
+static void
+awusbdrd_intr(void *arg)
+{
+ struct awusbdrd_softc *sc = arg;
+ uint8_t intusb;
+ uint16_t inttx, intrx;
+
+ intusb = MUSB2_READ_1(&sc->sc, MUSB2_REG_INTUSB);
+ inttx = MUSB2_READ_2(&sc->sc, MUSB2_REG_INTTX);
+ intrx = MUSB2_READ_2(&sc->sc, MUSB2_REG_INTRX);
+ if (intusb == 0 && inttx == 0 && intrx == 0)
+ return;
+
+ if (intusb)
+ MUSB2_WRITE_1(&sc->sc, MUSB2_REG_INTUSB, intusb);
+ if (inttx)
+ MUSB2_WRITE_2(&sc->sc, MUSB2_REG_INTTX, inttx);
+ if (intrx)
+ MUSB2_WRITE_2(&sc->sc, MUSB2_REG_INTRX, intrx);
+
+ musbotg_interrupt(arg, intrx, inttx, intusb);
+}
+
+static int
+awusbdrd_probe(device_t dev)
+{
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
+ return (ENXIO);
+
+ device_set_desc(dev, "Allwinner USB DRD");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+awusbdrd_attach(device_t dev)
+{
+ char usb_mode[24];
+ struct awusbdrd_softc *sc;
+ uint8_t musb_mode;
+ int phy_mode;
+ int error;
+
+ sc = device_get_softc(dev);
+ sc->flags = ofw_bus_search_compatible(dev, compat_data)->ocd_data;
+
+ error = bus_alloc_resources(dev, awusbdrd_spec, sc->res);
+ if (error != 0)
+ return (error);
+
+ musb_mode = MUSB2_HOST_MODE; /* default */
+ phy_mode = PHY_USB_MODE_HOST;
+ if (OF_getprop(ofw_bus_get_node(dev), "dr_mode",
+ &usb_mode, sizeof(usb_mode)) > 0) {
+ usb_mode[sizeof(usb_mode) - 1] = 0;
+ if (strcasecmp(usb_mode, "host") == 0) {
+ musb_mode = MUSB2_HOST_MODE;
+ phy_mode = PHY_USB_MODE_HOST;
+ } else if (strcasecmp(usb_mode, "peripheral") == 0) {
+ musb_mode = MUSB2_DEVICE_MODE;
+ phy_mode = PHY_USB_MODE_DEVICE;
+ } else if (strcasecmp(usb_mode, "otg") == 0) {
+ /*
+ * XXX phy has PHY_USB_MODE_OTG, but MUSB does not have
+ * it. It's not clear how to propagate mode changes
+ * from phy layer (that detects them) to MUSB.
+ */
+ musb_mode = MUSB2_DEVICE_MODE;
+ phy_mode = PHY_USB_MODE_DEVICE;
+ } else {
+ device_printf(dev, "Invalid FDT dr_mode: %s\n",
+ usb_mode);
+ }
+ }
+
+ /* AHB gate clock is required */
+ error = clk_get_by_ofw_index(dev, 0, 0, &sc->clk);
+ if (error != 0)
+ goto fail;
+
+ /* AHB reset is only present on some SoCs */
+ (void)hwreset_get_by_ofw_idx(dev, 0, 0, &sc->reset);
+
+ /* Enable clocks */
+ error = clk_enable(sc->clk);
+ if (error != 0) {
+ device_printf(dev, "failed to enable clock: %d\n", error);
+ goto fail;
+ }
+ if (sc->reset != NULL) {
+ error = hwreset_deassert(sc->reset);
+ if (error != 0) {
+ device_printf(dev, "failed to de-assert reset: %d\n",
+ error);
+ goto fail;
+ }
+ }
+
+ /* XXX not sure if this is universally needed. */
+ (void)phy_get_by_ofw_name(dev, 0, "usb", &sc->phy);
+ if (sc->phy != NULL) {
+ device_printf(dev, "setting phy mode %d\n", phy_mode);
+ if (musb_mode == MUSB2_HOST_MODE) {
+ error = phy_enable(sc->phy);
+ if (error != 0) {
+ device_printf(dev, "Could not enable phy\n");
+ goto fail;
+ }
+ }
+ error = phy_usb_set_mode(sc->phy, phy_mode);
+ if (error != 0) {
+ device_printf(dev, "Could not set phy mode\n");
+ goto fail;
+ }
+ }
+
+ sc->sc.sc_bus.parent = dev;
+ sc->sc.sc_bus.devices = sc->sc.sc_devices;
+ sc->sc.sc_bus.devices_max = MUSB2_MAX_DEVICES;
+ sc->sc.sc_bus.dma_bits = 32;
+
+ error = usb_bus_mem_alloc_all(&sc->sc.sc_bus, USB_GET_DMA_TAG(dev),
+ NULL);
+ if (error != 0) {
+ error = ENOMEM;
+ goto fail;
+ }
+
+#if defined(__arm__)
+ sc->bs.bs_parent = rman_get_bustag(sc->res[0]);
+#elif defined(__aarch64__) || defined(__riscv)
+ sc->bs.bs_cookie = rman_get_bustag(sc->res[0]);
+#endif
+
+ if ((sc->flags & AWUSB_NO_CONFDATA) == AWUSB_NO_CONFDATA)
+ sc->bs.bs_r_1 = awusbdrd_bs_r_1_noconf;
+ else
+ sc->bs.bs_r_1 = awusbdrd_bs_r_1;
+ sc->bs.bs_r_2 = awusbdrd_bs_r_2;
+ sc->bs.bs_w_1 = awusbdrd_bs_w_1;
+ sc->bs.bs_w_2 = awusbdrd_bs_w_2;
+ sc->bs.bs_rm_1 = awusbdrd_bs_rm_1;
+ sc->bs.bs_rm_4 = awusbdrd_bs_rm_4;
+ sc->bs.bs_wm_1 = awusbdrd_bs_wm_1;
+ sc->bs.bs_wm_4 = awusbdrd_bs_wm_4;
+
+ sc->sc.sc_io_tag = &sc->bs;
+ sc->sc.sc_io_hdl = rman_get_bushandle(sc->res[0]);
+ sc->sc.sc_io_size = rman_get_size(sc->res[0]);
+
+ sc->sc.sc_bus.bdev = device_add_child(dev, "usbus", DEVICE_UNIT_ANY);
+ if (sc->sc.sc_bus.bdev == NULL) {
+ error = ENXIO;
+ goto fail;
+ }
+ device_set_ivars(sc->sc.sc_bus.bdev, &sc->sc.sc_bus);
+ sc->sc.sc_id = 0;
+ sc->sc.sc_platform_data = sc;
+ sc->sc.sc_mode = musb_mode;
+ if (ofw_bus_is_compatible(dev, "allwinner,sun8i-h3-musb")) {
+ sc->sc.sc_ep_cfg = musbotg_ep_allwinner_h3;
+ sc->sc.sc_ep_max = DRD_EP_MAX_H3;
+ } else {
+ sc->sc.sc_ep_cfg = musbotg_ep_allwinner;
+ sc->sc.sc_ep_max = DRD_EP_MAX;
+ }
+
+ error = bus_setup_intr(dev, sc->res[1], INTR_MPSAFE | INTR_TYPE_BIO,
+ NULL, awusbdrd_intr, sc, &sc->sc.sc_intr_hdl);
+ if (error != 0)
+ goto fail;
+
+ /* Enable PIO mode */
+ bus_write_1(sc->res[0], MUSB2_REG_AWIN_VEND0, VEND0_PIO_MODE);
+
+#ifdef __arm__
+ /* Map SRAMD area to USB0 (sun4i/sun7i only) */
+ switch (allwinner_soc_family()) {
+ case ALLWINNERSOC_SUN4I:
+ case ALLWINNERSOC_SUN7I:
+ a10_map_to_otg();
+ break;
+ }
+#endif
+
+ error = musbotg_init(&sc->sc);
+ if (error != 0)
+ goto fail;
+
+ error = device_probe_and_attach(sc->sc.sc_bus.bdev);
+ if (error != 0)
+ goto fail;
+
+ musbotg_vbus_interrupt(&sc->sc, 1); /* XXX VBUS */
+
+ return (0);
+
+fail:
+ if (sc->phy != NULL) {
+ if (musb_mode == MUSB2_HOST_MODE)
+ (void)phy_disable(sc->phy);
+ phy_release(sc->phy);
+ }
+ if (sc->reset != NULL) {
+ hwreset_assert(sc->reset);
+ hwreset_release(sc->reset);
+ }
+ if (sc->clk != NULL)
+ clk_release(sc->clk);
+ bus_release_resources(dev, awusbdrd_spec, sc->res);
+ return (error);
+}
+
+static int
+awusbdrd_detach(device_t dev)
+{
+ struct awusbdrd_softc *sc;
+ int error;
+
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ sc = device_get_softc(dev);
+
+ musbotg_uninit(&sc->sc);
+ error = bus_teardown_intr(dev, sc->res[1], sc->sc.sc_intr_hdl);
+ if (error != 0)
+ return (error);
+
+ usb_bus_mem_free_all(&sc->sc.sc_bus, NULL);
+
+ if (sc->phy != NULL) {
+ if (sc->sc.sc_mode == MUSB2_HOST_MODE)
+ phy_disable(sc->phy);
+ phy_release(sc->phy);
+ }
+ if (sc->reset != NULL) {
+ if (hwreset_assert(sc->reset) != 0)
+ device_printf(dev, "failed to assert reset\n");
+ hwreset_release(sc->reset);
+ }
+ if (sc->clk != NULL)
+ clk_release(sc->clk);
+
+ bus_release_resources(dev, awusbdrd_spec, sc->res);
+
+ return (0);
+}
+
+static device_method_t awusbdrd_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, awusbdrd_probe),
+ DEVMETHOD(device_attach, awusbdrd_attach),
+ DEVMETHOD(device_detach, awusbdrd_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+
+ DEVMETHOD_END
+};
+
+static driver_t awusbdrd_driver = {
+ .name = "musbotg",
+ .methods = awusbdrd_methods,
+ .size = sizeof(struct awusbdrd_softc),
+};
+
+DRIVER_MODULE(musbotg, simplebus, awusbdrd_driver, 0, 0);
+MODULE_DEPEND(musbotg, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/ohci.c b/sys/dev/usb/controller/ohci.c
new file mode 100644
index 000000000000..6f1ec725e9bc
--- /dev/null
+++ b/sys/dev/usb/controller/ohci.c
@@ -0,0 +1,2686 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * USB Open Host Controller driver.
+ *
+ * OHCI spec: http://www.compaq.com/productinfo/development/openhci.html
+ * USB spec: http://www.usb.org/developers/docs/usbspec.zip
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#define USB_DEBUG_VAR ohcidebug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#include <dev/usb/controller/ohci.h>
+#include <dev/usb/controller/ohcireg.h>
+
+#define OHCI_BUS2SC(bus) \
+ __containerof(bus, ohci_softc_t, sc_bus)
+
+#ifdef USB_DEBUG
+static int ohcidebug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ohci, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB ohci");
+SYSCTL_INT(_hw_usb_ohci, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &ohcidebug, 0, "ohci debug level");
+
+static void ohci_dumpregs(ohci_softc_t *);
+static void ohci_dump_tds(ohci_td_t *);
+static uint8_t ohci_dump_td(ohci_td_t *);
+static void ohci_dump_ed(ohci_ed_t *);
+static uint8_t ohci_dump_itd(ohci_itd_t *);
+static void ohci_dump_itds(ohci_itd_t *);
+
+#endif
+
+#define OBARR(sc) bus_space_barrier((sc)->sc_io_tag, (sc)->sc_io_hdl, 0, (sc)->sc_io_size, \
+ BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE)
+#define OWRITE1(sc, r, x) \
+ do { OBARR(sc); bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0)
+#define OWRITE2(sc, r, x) \
+ do { OBARR(sc); bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0)
+#define OWRITE4(sc, r, x) \
+ do { OBARR(sc); bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); } while (0)
+#define OREAD1(sc, r) (OBARR(sc), bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+#define OREAD2(sc, r) (OBARR(sc), bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+#define OREAD4(sc, r) (OBARR(sc), bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+
+#define OHCI_INTR_ENDPT 1
+
+static const struct usb_bus_methods ohci_bus_methods;
+static const struct usb_pipe_methods ohci_device_bulk_methods;
+static const struct usb_pipe_methods ohci_device_ctrl_methods;
+static const struct usb_pipe_methods ohci_device_intr_methods;
+static const struct usb_pipe_methods ohci_device_isoc_methods;
+
+static void ohci_do_poll(struct usb_bus *bus);
+static void ohci_device_done(struct usb_xfer *xfer, usb_error_t error);
+static void ohci_timeout(void *arg);
+static uint8_t ohci_check_transfer(struct usb_xfer *xfer);
+static void ohci_root_intr(ohci_softc_t *sc);
+
+struct ohci_std_temp {
+ struct usb_page_cache *pc;
+ ohci_td_t *td;
+ ohci_td_t *td_next;
+ uint32_t average;
+ uint32_t td_flags;
+ uint32_t len;
+ uint16_t max_frame_size;
+ uint8_t shortpkt;
+ uint8_t setup_alt_next;
+ uint8_t last_frame;
+};
+
+static struct ohci_hcca *
+ohci_get_hcca(ohci_softc_t *sc)
+{
+ usb_pc_cpu_invalidate(&sc->sc_hw.hcca_pc);
+ return (sc->sc_hcca_p);
+}
+
+void
+ohci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb)
+{
+ struct ohci_softc *sc = OHCI_BUS2SC(bus);
+ uint32_t i;
+
+ cb(bus, &sc->sc_hw.hcca_pc, &sc->sc_hw.hcca_pg,
+ sizeof(ohci_hcca_t), OHCI_HCCA_ALIGN);
+
+ cb(bus, &sc->sc_hw.ctrl_start_pc, &sc->sc_hw.ctrl_start_pg,
+ sizeof(ohci_ed_t), OHCI_ED_ALIGN);
+
+ cb(bus, &sc->sc_hw.bulk_start_pc, &sc->sc_hw.bulk_start_pg,
+ sizeof(ohci_ed_t), OHCI_ED_ALIGN);
+
+ cb(bus, &sc->sc_hw.isoc_start_pc, &sc->sc_hw.isoc_start_pg,
+ sizeof(ohci_ed_t), OHCI_ED_ALIGN);
+
+ for (i = 0; i != OHCI_NO_EDS; i++) {
+ cb(bus, sc->sc_hw.intr_start_pc + i, sc->sc_hw.intr_start_pg + i,
+ sizeof(ohci_ed_t), OHCI_ED_ALIGN);
+ }
+}
+
+static usb_error_t
+ohci_controller_init(ohci_softc_t *sc, int do_suspend)
+{
+ struct usb_page_search buf_res;
+ uint32_t i;
+ uint32_t ctl;
+ uint32_t ival;
+ uint32_t hcr;
+ uint32_t fm;
+ uint32_t per;
+ uint32_t desca;
+
+ /* Determine in what context we are running. */
+ ctl = OREAD4(sc, OHCI_CONTROL);
+ if (ctl & OHCI_IR) {
+ /* SMM active, request change */
+ DPRINTF("SMM active, request owner change\n");
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_OCR);
+ for (i = 0; (i < 100) && (ctl & OHCI_IR); i++) {
+ usb_pause_mtx(NULL, hz / 1000);
+ ctl = OREAD4(sc, OHCI_CONTROL);
+ }
+ if (ctl & OHCI_IR) {
+ device_printf(sc->sc_bus.bdev,
+ "SMM does not respond, resetting\n");
+ OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+ goto reset;
+ }
+ } else {
+ DPRINTF("cold started\n");
+reset:
+ /* controller was cold started */
+ usb_pause_mtx(NULL,
+ USB_MS_TO_TICKS(USB_BUS_RESET_DELAY));
+ }
+
+ /*
+ * This reset should not be necessary according to the OHCI spec, but
+ * without it some controllers do not start.
+ */
+ DPRINTF("%s: resetting\n", device_get_nameunit(sc->sc_bus.bdev));
+ OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+
+ usb_pause_mtx(NULL,
+ USB_MS_TO_TICKS(USB_BUS_RESET_DELAY));
+
+ /* we now own the host controller and the bus has been reset */
+ ival = OHCI_GET_IVAL(OREAD4(sc, OHCI_FM_INTERVAL));
+
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_HCR); /* Reset HC */
+ /* nominal time for a reset is 10 us */
+ for (i = 0; i < 10; i++) {
+ DELAY(10);
+ hcr = OREAD4(sc, OHCI_COMMAND_STATUS) & OHCI_HCR;
+ if (!hcr) {
+ break;
+ }
+ }
+ if (hcr) {
+ device_printf(sc->sc_bus.bdev, "reset timeout\n");
+ return (USB_ERR_IOERROR);
+ }
+#ifdef USB_DEBUG
+ if (ohcidebug > 15) {
+ ohci_dumpregs(sc);
+ }
+#endif
+
+ if (do_suspend) {
+ OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_SUSPEND);
+ return (USB_ERR_NORMAL_COMPLETION);
+ }
+
+ /* The controller is now in SUSPEND state, we have 2ms to finish. */
+
+ /* set up HC registers */
+ usbd_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res);
+ OWRITE4(sc, OHCI_HCCA, buf_res.physaddr);
+
+ usbd_get_page(&sc->sc_hw.ctrl_start_pc, 0, &buf_res);
+ OWRITE4(sc, OHCI_CONTROL_HEAD_ED, buf_res.physaddr);
+
+ usbd_get_page(&sc->sc_hw.bulk_start_pc, 0, &buf_res);
+ OWRITE4(sc, OHCI_BULK_HEAD_ED, buf_res.physaddr);
+
+ /* disable all interrupts and then switch on all desired interrupts */
+ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS);
+ OWRITE4(sc, OHCI_INTERRUPT_ENABLE, sc->sc_eintrs | OHCI_MIE);
+ /* switch on desired functional features */
+ ctl = OREAD4(sc, OHCI_CONTROL);
+ ctl &= ~(OHCI_CBSR_MASK | OHCI_LES | OHCI_HCFS_MASK | OHCI_IR);
+ ctl |= OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE |
+ OHCI_RATIO_1_4 | OHCI_HCFS_OPERATIONAL;
+ /* And finally start it! */
+ OWRITE4(sc, OHCI_CONTROL, ctl);
+
+ /*
+ * The controller is now OPERATIONAL. Set a some final
+ * registers that should be set earlier, but that the
+ * controller ignores when in the SUSPEND state.
+ */
+ fm = (OREAD4(sc, OHCI_FM_INTERVAL) & OHCI_FIT) ^ OHCI_FIT;
+ fm |= OHCI_FSMPS(ival) | ival;
+ OWRITE4(sc, OHCI_FM_INTERVAL, fm);
+ per = OHCI_PERIODIC(ival); /* 90% periodic */
+ OWRITE4(sc, OHCI_PERIODIC_START, per);
+
+ /* Fiddle the No OverCurrent Protection bit to avoid chip bug. */
+ desca = OREAD4(sc, OHCI_RH_DESCRIPTOR_A);
+ OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca | OHCI_NOCP);
+ OWRITE4(sc, OHCI_RH_STATUS, OHCI_LPSC); /* Enable port power */
+ usb_pause_mtx(NULL,
+ USB_MS_TO_TICKS(OHCI_ENABLE_POWER_DELAY));
+ OWRITE4(sc, OHCI_RH_DESCRIPTOR_A, desca);
+
+ /*
+ * The AMD756 requires a delay before re-reading the register,
+ * otherwise it will occasionally report 0 ports.
+ */
+ sc->sc_noport = 0;
+ for (i = 0; (i < 10) && (sc->sc_noport == 0); i++) {
+ usb_pause_mtx(NULL,
+ USB_MS_TO_TICKS(OHCI_READ_DESC_DELAY));
+ sc->sc_noport = OHCI_GET_NDP(OREAD4(sc, OHCI_RH_DESCRIPTOR_A));
+ }
+
+#ifdef USB_DEBUG
+ if (ohcidebug > 5) {
+ ohci_dumpregs(sc);
+ }
+#endif
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static struct ohci_ed *
+ohci_init_ed(struct usb_page_cache *pc)
+{
+ struct usb_page_search buf_res;
+ struct ohci_ed *ed;
+
+ usbd_get_page(pc, 0, &buf_res);
+
+ ed = buf_res.buffer;
+
+ ed->ed_self = htole32(buf_res.physaddr);
+ ed->ed_flags = htole32(OHCI_ED_SKIP);
+ ed->page_cache = pc;
+
+ return (ed);
+}
+
+usb_error_t
+ohci_init(ohci_softc_t *sc)
+{
+ struct usb_page_search buf_res;
+ uint16_t i;
+ uint16_t bit;
+ uint16_t x;
+ uint16_t y;
+
+ DPRINTF("start\n");
+
+ sc->sc_eintrs = OHCI_NORMAL_INTRS;
+
+ /*
+ * Setup all ED's
+ */
+
+ sc->sc_ctrl_p_last =
+ ohci_init_ed(&sc->sc_hw.ctrl_start_pc);
+
+ sc->sc_bulk_p_last =
+ ohci_init_ed(&sc->sc_hw.bulk_start_pc);
+
+ sc->sc_isoc_p_last =
+ ohci_init_ed(&sc->sc_hw.isoc_start_pc);
+
+ for (i = 0; i != OHCI_NO_EDS; i++) {
+ sc->sc_intr_p_last[i] =
+ ohci_init_ed(sc->sc_hw.intr_start_pc + i);
+ }
+
+ /*
+ * the QHs are arranged to give poll intervals that are
+ * powers of 2 times 1ms
+ */
+ bit = OHCI_NO_EDS / 2;
+ while (bit) {
+ x = bit;
+ while (x & bit) {
+ ohci_ed_t *ed_x;
+ ohci_ed_t *ed_y;
+
+ y = (x ^ bit) | (bit / 2);
+
+ /*
+ * the next QH has half the poll interval
+ */
+ ed_x = sc->sc_intr_p_last[x];
+ ed_y = sc->sc_intr_p_last[y];
+
+ ed_x->next = NULL;
+ ed_x->ed_next = ed_y->ed_self;
+
+ x++;
+ }
+ bit >>= 1;
+ }
+
+ if (1) {
+ ohci_ed_t *ed_int;
+ ohci_ed_t *ed_isc;
+
+ ed_int = sc->sc_intr_p_last[0];
+ ed_isc = sc->sc_isoc_p_last;
+
+ /* the last (1ms) QH */
+ ed_int->next = ed_isc;
+ ed_int->ed_next = ed_isc->ed_self;
+ }
+ usbd_get_page(&sc->sc_hw.hcca_pc, 0, &buf_res);
+
+ sc->sc_hcca_p = buf_res.buffer;
+
+ /*
+ * Fill HCCA interrupt table. The bit reversal is to get
+ * the tree set up properly to spread the interrupts.
+ */
+ for (i = 0; i != OHCI_NO_INTRS; i++) {
+ sc->sc_hcca_p->hcca_interrupt_table[i] =
+ sc->sc_intr_p_last[i | (OHCI_NO_EDS / 2)]->ed_self;
+ }
+ /* flush all cache into memory */
+
+ usb_bus_mem_flush_all(&sc->sc_bus, &ohci_iterate_hw_softc);
+
+ /* set up the bus struct */
+ sc->sc_bus.methods = &ohci_bus_methods;
+
+ usb_callout_init_mtx(&sc->sc_tmo_rhsc, &sc->sc_bus.bus_mtx, 0);
+
+#ifdef USB_DEBUG
+ if (ohcidebug > 15) {
+ for (i = 0; i != OHCI_NO_EDS; i++) {
+ printf("ed#%d ", i);
+ ohci_dump_ed(sc->sc_intr_p_last[i]);
+ }
+ printf("iso ");
+ ohci_dump_ed(sc->sc_isoc_p_last);
+ }
+#endif
+
+ sc->sc_bus.usbrev = USB_REV_1_0;
+
+ if (ohci_controller_init(sc, 0) != 0)
+ return (USB_ERR_INVAL);
+
+ /* catch any lost interrupts */
+ ohci_do_poll(&sc->sc_bus);
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+/*
+ * shut down the controller when the system is going down
+ */
+void
+ohci_detach(struct ohci_softc *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ usb_callout_stop(&sc->sc_tmo_rhsc);
+
+ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS);
+ OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* XXX let stray task complete */
+ usb_pause_mtx(NULL, hz / 20);
+
+ usb_callout_drain(&sc->sc_tmo_rhsc);
+}
+
+static void
+ohci_suspend(ohci_softc_t *sc)
+{
+ DPRINTF("\n");
+
+#ifdef USB_DEBUG
+ if (ohcidebug > 2)
+ ohci_dumpregs(sc);
+#endif
+
+ /* reset HC and leave it suspended */
+ ohci_controller_init(sc, 1);
+}
+
+static void
+ohci_resume(ohci_softc_t *sc)
+{
+ DPRINTF("\n");
+
+#ifdef USB_DEBUG
+ if (ohcidebug > 2)
+ ohci_dumpregs(sc);
+#endif
+
+ /* some broken BIOSes never initialize the Controller chip */
+ ohci_controller_init(sc, 0);
+
+ /* catch any lost interrupts */
+ ohci_do_poll(&sc->sc_bus);
+}
+
+#ifdef USB_DEBUG
+static void
+ohci_dumpregs(ohci_softc_t *sc)
+{
+ struct ohci_hcca *hcca;
+
+ DPRINTF("ohci_dumpregs: rev=0x%08x control=0x%08x command=0x%08x\n",
+ OREAD4(sc, OHCI_REVISION),
+ OREAD4(sc, OHCI_CONTROL),
+ OREAD4(sc, OHCI_COMMAND_STATUS));
+ DPRINTF(" intrstat=0x%08x intre=0x%08x intrd=0x%08x\n",
+ OREAD4(sc, OHCI_INTERRUPT_STATUS),
+ OREAD4(sc, OHCI_INTERRUPT_ENABLE),
+ OREAD4(sc, OHCI_INTERRUPT_DISABLE));
+ DPRINTF(" hcca=0x%08x percur=0x%08x ctrlhd=0x%08x\n",
+ OREAD4(sc, OHCI_HCCA),
+ OREAD4(sc, OHCI_PERIOD_CURRENT_ED),
+ OREAD4(sc, OHCI_CONTROL_HEAD_ED));
+ DPRINTF(" ctrlcur=0x%08x bulkhd=0x%08x bulkcur=0x%08x\n",
+ OREAD4(sc, OHCI_CONTROL_CURRENT_ED),
+ OREAD4(sc, OHCI_BULK_HEAD_ED),
+ OREAD4(sc, OHCI_BULK_CURRENT_ED));
+ DPRINTF(" done=0x%08x fmival=0x%08x fmrem=0x%08x\n",
+ OREAD4(sc, OHCI_DONE_HEAD),
+ OREAD4(sc, OHCI_FM_INTERVAL),
+ OREAD4(sc, OHCI_FM_REMAINING));
+ DPRINTF(" fmnum=0x%08x perst=0x%08x lsthrs=0x%08x\n",
+ OREAD4(sc, OHCI_FM_NUMBER),
+ OREAD4(sc, OHCI_PERIODIC_START),
+ OREAD4(sc, OHCI_LS_THRESHOLD));
+ DPRINTF(" desca=0x%08x descb=0x%08x stat=0x%08x\n",
+ OREAD4(sc, OHCI_RH_DESCRIPTOR_A),
+ OREAD4(sc, OHCI_RH_DESCRIPTOR_B),
+ OREAD4(sc, OHCI_RH_STATUS));
+ DPRINTF(" port1=0x%08x port2=0x%08x\n",
+ OREAD4(sc, OHCI_RH_PORT_STATUS(1)),
+ OREAD4(sc, OHCI_RH_PORT_STATUS(2)));
+
+ hcca = ohci_get_hcca(sc);
+
+ DPRINTF(" HCCA: frame_number=0x%04x done_head=0x%08x\n",
+ le32toh(hcca->hcca_frame_number),
+ le32toh(hcca->hcca_done_head));
+}
+static void
+ohci_dump_tds(ohci_td_t *std)
+{
+ for (; std; std = std->obj_next) {
+ if (ohci_dump_td(std)) {
+ break;
+ }
+ }
+}
+
+static uint8_t
+ohci_dump_td(ohci_td_t *std)
+{
+ uint32_t td_flags;
+ uint8_t temp;
+
+ usb_pc_cpu_invalidate(std->page_cache);
+
+ td_flags = le32toh(std->td_flags);
+ temp = (std->td_next == 0);
+
+ printf("TD(%p) at 0x%08x: %s%s%s%s%s delay=%d ec=%d "
+ "cc=%d\ncbp=0x%08x next=0x%08x be=0x%08x\n",
+ std, le32toh(std->td_self),
+ (td_flags & OHCI_TD_R) ? "-R" : "",
+ (td_flags & OHCI_TD_OUT) ? "-OUT" : "",
+ (td_flags & OHCI_TD_IN) ? "-IN" : "",
+ ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_1) ? "-TOG1" : "",
+ ((td_flags & OHCI_TD_TOGGLE_MASK) == OHCI_TD_TOGGLE_0) ? "-TOG0" : "",
+ OHCI_TD_GET_DI(td_flags),
+ OHCI_TD_GET_EC(td_flags),
+ OHCI_TD_GET_CC(td_flags),
+ le32toh(std->td_cbp),
+ le32toh(std->td_next),
+ le32toh(std->td_be));
+
+ return (temp);
+}
+
+static uint8_t
+ohci_dump_itd(ohci_itd_t *sitd)
+{
+ uint32_t itd_flags;
+ uint16_t i;
+ uint8_t temp;
+
+ usb_pc_cpu_invalidate(sitd->page_cache);
+
+ itd_flags = le32toh(sitd->itd_flags);
+ temp = (sitd->itd_next == 0);
+
+ printf("ITD(%p) at 0x%08x: sf=%d di=%d fc=%d cc=%d\n"
+ "bp0=0x%08x next=0x%08x be=0x%08x\n",
+ sitd, le32toh(sitd->itd_self),
+ OHCI_ITD_GET_SF(itd_flags),
+ OHCI_ITD_GET_DI(itd_flags),
+ OHCI_ITD_GET_FC(itd_flags),
+ OHCI_ITD_GET_CC(itd_flags),
+ le32toh(sitd->itd_bp0),
+ le32toh(sitd->itd_next),
+ le32toh(sitd->itd_be));
+ for (i = 0; i < OHCI_ITD_NOFFSET; i++) {
+ printf("offs[%d]=0x%04x ", i,
+ (uint32_t)le16toh(sitd->itd_offset[i]));
+ }
+ printf("\n");
+
+ return (temp);
+}
+
+static void
+ohci_dump_itds(ohci_itd_t *sitd)
+{
+ for (; sitd; sitd = sitd->obj_next) {
+ if (ohci_dump_itd(sitd)) {
+ break;
+ }
+ }
+}
+
+static void
+ohci_dump_ed(ohci_ed_t *sed)
+{
+ uint32_t ed_flags;
+ uint32_t ed_headp;
+
+ usb_pc_cpu_invalidate(sed->page_cache);
+
+ ed_flags = le32toh(sed->ed_flags);
+ ed_headp = le32toh(sed->ed_headp);
+
+ printf("ED(%p) at 0x%08x: addr=%d endpt=%d maxp=%d flags=%s%s%s%s%s\n"
+ "tailp=0x%08x headflags=%s%s headp=0x%08x nexted=0x%08x\n",
+ sed, le32toh(sed->ed_self),
+ OHCI_ED_GET_FA(ed_flags),
+ OHCI_ED_GET_EN(ed_flags),
+ OHCI_ED_GET_MAXP(ed_flags),
+ (ed_flags & OHCI_ED_DIR_OUT) ? "-OUT" : "",
+ (ed_flags & OHCI_ED_DIR_IN) ? "-IN" : "",
+ (ed_flags & OHCI_ED_SPEED) ? "-LOWSPEED" : "",
+ (ed_flags & OHCI_ED_SKIP) ? "-SKIP" : "",
+ (ed_flags & OHCI_ED_FORMAT_ISO) ? "-ISO" : "",
+ le32toh(sed->ed_tailp),
+ (ed_headp & OHCI_HALTED) ? "-HALTED" : "",
+ (ed_headp & OHCI_TOGGLECARRY) ? "-CARRY" : "",
+ le32toh(sed->ed_headp),
+ le32toh(sed->ed_next));
+}
+
+#endif
+
+static void
+ohci_transfer_intr_enqueue(struct usb_xfer *xfer)
+{
+ /* check for early completion */
+ if (ohci_check_transfer(xfer)) {
+ return;
+ }
+ /* put transfer on interrupt queue */
+ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usbd_transfer_timeout_ms(xfer, &ohci_timeout, xfer->timeout);
+ }
+}
+
+#define OHCI_APPEND_QH(sed,last) (last) = _ohci_append_qh(sed,last)
+static ohci_ed_t *
+_ohci_append_qh(ohci_ed_t *sed, ohci_ed_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", sed, last);
+
+ if (sed->prev != NULL) {
+ /* should not happen */
+ DPRINTFN(0, "ED already linked!\n");
+ return (last);
+ }
+ /* (sc->sc_bus.bus_mtx) must be locked */
+
+ sed->next = last->next;
+ sed->ed_next = last->ed_next;
+ sed->ed_tailp = 0;
+
+ sed->prev = last;
+
+ usb_pc_cpu_flush(sed->page_cache);
+
+ /*
+ * the last->next->prev is never followed: sed->next->prev = sed;
+ */
+
+ last->next = sed;
+ last->ed_next = sed->ed_self;
+
+ usb_pc_cpu_flush(last->page_cache);
+
+ return (sed);
+}
+
+#define OHCI_REMOVE_QH(sed,last) (last) = _ohci_remove_qh(sed,last)
+static ohci_ed_t *
+_ohci_remove_qh(ohci_ed_t *sed, ohci_ed_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", sed, last);
+
+ /* (sc->sc_bus.bus_mtx) must be locked */
+
+ /* only remove if not removed from a queue */
+ if (sed->prev) {
+ sed->prev->next = sed->next;
+ sed->prev->ed_next = sed->ed_next;
+
+ usb_pc_cpu_flush(sed->prev->page_cache);
+
+ if (sed->next) {
+ sed->next->prev = sed->prev;
+ usb_pc_cpu_flush(sed->next->page_cache);
+ }
+ last = ((last == sed) ? sed->prev : last);
+
+ sed->prev = 0;
+
+ usb_pc_cpu_flush(sed->page_cache);
+ }
+ return (last);
+}
+
+static void
+ohci_isoc_done(struct usb_xfer *xfer)
+{
+ uint8_t nframes;
+ uint32_t *plen = xfer->frlengths;
+ volatile uint16_t *olen;
+ uint16_t len = 0;
+ ohci_itd_t *td = xfer->td_transfer_first;
+
+ while (1) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+#ifdef USB_DEBUG
+ if (ohcidebug > 5) {
+ DPRINTF("isoc TD\n");
+ ohci_dump_itd(td);
+ }
+#endif
+ usb_pc_cpu_invalidate(td->page_cache);
+
+ nframes = td->frames;
+ olen = &td->itd_offset[0];
+
+ if (nframes > 8) {
+ nframes = 8;
+ }
+ while (nframes--) {
+ len = le16toh(*olen);
+
+ if ((len >> 12) == OHCI_CC_NOT_ACCESSED) {
+ len = 0;
+ } else {
+ len &= ((1 << 12) - 1);
+ }
+
+ if (len > *plen) {
+ len = 0;/* invalid length */
+ }
+ *plen = len;
+ plen++;
+ olen++;
+ }
+
+ if (((void *)td) == xfer->td_transfer_last) {
+ break;
+ }
+ td = td->obj_next;
+ }
+
+ xfer->aframes = xfer->nframes;
+ ohci_device_done(xfer, USB_ERR_NORMAL_COMPLETION);
+}
+
+#ifdef USB_DEBUG
+static const char *const
+ ohci_cc_strs[] =
+{
+ "NO_ERROR",
+ "CRC",
+ "BIT_STUFFING",
+ "DATA_TOGGLE_MISMATCH",
+
+ "STALL",
+ "DEVICE_NOT_RESPONDING",
+ "PID_CHECK_FAILURE",
+ "UNEXPECTED_PID",
+
+ "DATA_OVERRUN",
+ "DATA_UNDERRUN",
+ "BUFFER_OVERRUN",
+ "BUFFER_UNDERRUN",
+
+ "reserved",
+ "reserved",
+ "NOT_ACCESSED",
+ "NOT_ACCESSED"
+};
+
+#endif
+
+static usb_error_t
+ohci_non_isoc_done_sub(struct usb_xfer *xfer)
+{
+ ohci_td_t *td;
+ ohci_td_t *td_alt_next;
+ uint32_t temp;
+ uint32_t phy_start;
+ uint32_t phy_end;
+ uint32_t td_flags;
+ uint16_t cc;
+
+ td = xfer->td_transfer_cache;
+ td_alt_next = td->alt_next;
+ td_flags = 0;
+
+ if (xfer->aframes != xfer->nframes) {
+ usbd_xfer_set_frame_len(xfer, xfer->aframes, 0);
+ }
+ while (1) {
+ usb_pc_cpu_invalidate(td->page_cache);
+ phy_start = le32toh(td->td_cbp);
+ td_flags = le32toh(td->td_flags);
+ cc = OHCI_TD_GET_CC(td_flags);
+
+ if (phy_start) {
+ /*
+ * short transfer - compute the number of remaining
+ * bytes in the hardware buffer:
+ */
+ phy_end = le32toh(td->td_be);
+ temp = (OHCI_PAGE(phy_start ^ phy_end) ?
+ (OHCI_PAGE_SIZE + 1) : 0x0001);
+ temp += OHCI_PAGE_OFFSET(phy_end);
+ temp -= OHCI_PAGE_OFFSET(phy_start);
+
+ if (temp > td->len) {
+ /* guard against corruption */
+ cc = OHCI_CC_STALL;
+ } else if (xfer->aframes != xfer->nframes) {
+ /*
+ * Sum up total transfer length
+ * in "frlengths[]":
+ */
+ xfer->frlengths[xfer->aframes] += td->len - temp;
+ }
+ } else {
+ if (xfer->aframes != xfer->nframes) {
+ /* transfer was complete */
+ xfer->frlengths[xfer->aframes] += td->len;
+ }
+ }
+ /* Check for last transfer */
+ if (((void *)td) == xfer->td_transfer_last) {
+ td = NULL;
+ break;
+ }
+ /* Check transfer status */
+ if (cc) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (phy_start) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ td = td->alt_next;
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ break;
+ }
+ td = td->obj_next;
+
+ if (td->alt_next != td_alt_next) {
+ /* this USB frame is complete */
+ break;
+ }
+ }
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ DPRINTFN(16, "error cc=%d (%s)\n",
+ cc, ohci_cc_strs[cc]);
+
+ return ((cc == 0) ? USB_ERR_NORMAL_COMPLETION :
+ (cc == OHCI_CC_STALL) ? USB_ERR_STALLED : USB_ERR_IOERROR);
+}
+
+static void
+ohci_non_isoc_done(struct usb_xfer *xfer)
+{
+ usb_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
+ xfer, xfer->endpoint);
+
+#ifdef USB_DEBUG
+ if (ohcidebug > 10) {
+ ohci_dump_tds(xfer->td_transfer_first);
+ }
+#endif
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ err = ohci_non_isoc_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+ err = ohci_non_isoc_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+ err = ohci_non_isoc_done_sub(xfer);
+ }
+done:
+ ohci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * ohci_check_transfer_sub
+ *------------------------------------------------------------------------*/
+static void
+ohci_check_transfer_sub(struct usb_xfer *xfer)
+{
+ ohci_td_t *td;
+ ohci_ed_t *ed;
+ uint32_t phy_start;
+ uint32_t td_flags;
+ uint32_t td_next;
+ uint16_t cc;
+
+ td = xfer->td_transfer_cache;
+
+ while (1) {
+ usb_pc_cpu_invalidate(td->page_cache);
+ phy_start = le32toh(td->td_cbp);
+ td_flags = le32toh(td->td_flags);
+ td_next = le32toh(td->td_next);
+
+ /* Check for last transfer */
+ if (((void *)td) == xfer->td_transfer_last) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /* Check transfer status */
+ cc = OHCI_TD_GET_CC(td_flags);
+ if (cc) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /*
+ * Check if we reached the last packet
+ * or if there is a short packet:
+ */
+
+ if (((td_next & (~0xF)) == OHCI_TD_NEXT_END) || phy_start) {
+ /* follow alt next */
+ td = td->alt_next;
+ break;
+ }
+ td = td->obj_next;
+ }
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ if (td) {
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ ed->ed_headp = td->td_self;
+ usb_pc_cpu_flush(ed->page_cache);
+
+ DPRINTFN(13, "xfer=%p following alt next\n", xfer);
+
+ /*
+ * Make sure that the OHCI re-scans the schedule by
+ * writing the BLF and CLF bits:
+ */
+
+ if (xfer->xroot->udev->flags.self_suspended) {
+ /* nothing to do */
+ } else if (xfer->endpoint->methods == &ohci_device_bulk_methods) {
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF);
+ } else if (xfer->endpoint->methods == &ohci_device_ctrl_methods) {
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF);
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * ohci_check_transfer
+ *
+ * Return values:
+ * 0: USB transfer is not finished
+ * Else: USB transfer is finished
+ *------------------------------------------------------------------------*/
+static uint8_t
+ohci_check_transfer(struct usb_xfer *xfer)
+{
+ ohci_ed_t *ed;
+ uint32_t ed_headp;
+ uint32_t ed_tailp;
+
+ DPRINTFN(13, "xfer=%p checking transfer\n", xfer);
+
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ usb_pc_cpu_invalidate(ed->page_cache);
+ ed_headp = le32toh(ed->ed_headp);
+ ed_tailp = le32toh(ed->ed_tailp);
+
+ if ((ed_headp & OHCI_HALTED) ||
+ (((ed_headp ^ ed_tailp) & (~0xF)) == 0)) {
+ if (xfer->endpoint->methods == &ohci_device_isoc_methods) {
+ /* isochronous transfer */
+ ohci_isoc_done(xfer);
+ } else {
+ if (xfer->flags_int.short_frames_ok) {
+ ohci_check_transfer_sub(xfer);
+ if (xfer->td_transfer_cache) {
+ /* not finished yet */
+ return (0);
+ }
+ }
+ /* store data-toggle */
+ if (ed_headp & OHCI_TOGGLECARRY) {
+ xfer->endpoint->toggle_next = 1;
+ } else {
+ xfer->endpoint->toggle_next = 0;
+ }
+
+ /* non-isochronous transfer */
+ ohci_non_isoc_done(xfer);
+ }
+ return (1);
+ }
+ DPRINTFN(13, "xfer=%p is still active\n", xfer);
+ return (0);
+}
+
+static void
+ohci_rhsc_enable(ohci_softc_t *sc)
+{
+ DPRINTFN(5, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ sc->sc_eintrs |= OHCI_RHSC;
+ OWRITE4(sc, OHCI_INTERRUPT_ENABLE, OHCI_RHSC);
+
+ /* acknowledge any RHSC interrupt */
+ OWRITE4(sc, OHCI_INTERRUPT_STATUS, OHCI_RHSC);
+
+ ohci_root_intr(sc);
+}
+
+static void
+ohci_interrupt_poll(ohci_softc_t *sc)
+{
+ struct usb_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ /*
+ * check if transfer is transferred
+ */
+ if (ohci_check_transfer(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * ohci_interrupt - OHCI interrupt handler
+ *
+ * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler,
+ * hence the interrupt handler will be setup before "sc->sc_bus.bdev"
+ * is present !
+ *------------------------------------------------------------------------*/
+void
+ohci_interrupt(ohci_softc_t *sc)
+{
+ struct ohci_hcca *hcca;
+ uint32_t status;
+ uint32_t done;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ hcca = ohci_get_hcca(sc);
+
+ DPRINTFN(16, "real interrupt\n");
+
+#ifdef USB_DEBUG
+ if (ohcidebug > 15) {
+ ohci_dumpregs(sc);
+ }
+#endif
+
+ done = le32toh(hcca->hcca_done_head);
+
+ /*
+ * The LSb of done is used to inform the HC Driver that an interrupt
+ * condition exists for both the Done list and for another event
+ * recorded in HcInterruptStatus. On an interrupt from the HC, the
+ * HC Driver checks the HccaDoneHead Value. If this value is 0, then
+ * the interrupt was caused by other than the HccaDoneHead update
+ * and the HcInterruptStatus register needs to be accessed to
+ * determine that exact interrupt cause. If HccaDoneHead is nonzero,
+ * then a Done list update interrupt is indicated and if the LSb of
+ * done is nonzero, then an additional interrupt event is indicated
+ * and HcInterruptStatus should be checked to determine its cause.
+ */
+ if (done != 0) {
+ status = 0;
+
+ if (done & ~OHCI_DONE_INTRS) {
+ status |= OHCI_WDH;
+ }
+ if (done & OHCI_DONE_INTRS) {
+ status |= OREAD4(sc, OHCI_INTERRUPT_STATUS);
+ }
+ hcca->hcca_done_head = 0;
+
+ usb_pc_cpu_flush(&sc->sc_hw.hcca_pc);
+ } else {
+ status = OREAD4(sc, OHCI_INTERRUPT_STATUS) & ~OHCI_WDH;
+ }
+
+ status &= ~OHCI_MIE;
+ if (status == 0) {
+ /*
+ * nothing to be done (PCI shared
+ * interrupt)
+ */
+ goto done;
+ }
+ OWRITE4(sc, OHCI_INTERRUPT_STATUS, status); /* Acknowledge */
+
+ status &= sc->sc_eintrs;
+ if (status == 0) {
+ goto done;
+ }
+ if (status & (OHCI_SO | OHCI_RD | OHCI_UE | OHCI_RHSC)) {
+#if 0
+ if (status & OHCI_SO) {
+ /* XXX do what */
+ }
+#endif
+ if (status & OHCI_RD) {
+ printf("%s: resume detect\n", __FUNCTION__);
+ /* XXX process resume detect */
+ }
+ if (status & OHCI_UE) {
+ printf("%s: unrecoverable error, "
+ "controller halted\n", __FUNCTION__);
+ OWRITE4(sc, OHCI_CONTROL, OHCI_HCFS_RESET);
+ /* XXX what else */
+ }
+ if (status & OHCI_RHSC) {
+ /*
+ * Disable RHSC interrupt for now, because it will be
+ * on until the port has been reset.
+ */
+ sc->sc_eintrs &= ~OHCI_RHSC;
+ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, OHCI_RHSC);
+
+ ohci_root_intr(sc);
+
+ /* do not allow RHSC interrupts > 1 per second */
+ usb_callout_reset(&sc->sc_tmo_rhsc, hz,
+ (void *)&ohci_rhsc_enable, sc);
+ }
+ }
+ status &= ~(OHCI_RHSC | OHCI_WDH | OHCI_SO);
+ if (status != 0) {
+ /* Block unprocessed interrupts. XXX */
+ OWRITE4(sc, OHCI_INTERRUPT_DISABLE, status);
+ sc->sc_eintrs &= ~status;
+ printf("%s: blocking intrs 0x%x\n",
+ __FUNCTION__, status);
+ }
+ /* poll all the USB transfers */
+ ohci_interrupt_poll(sc);
+
+done:
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*
+ * called when a request does not complete
+ */
+static void
+ohci_timeout(void *arg)
+{
+ struct usb_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ ohci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+ohci_do_poll(struct usb_bus *bus)
+{
+ struct ohci_softc *sc = OHCI_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ ohci_interrupt_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+ohci_setup_standard_chain_sub(struct ohci_std_temp *temp)
+{
+ struct usb_page_search buf_res;
+ ohci_td_t *td;
+ ohci_td_t *td_next;
+ ohci_td_t *td_alt_next;
+ uint32_t buf_offset;
+ uint32_t average;
+ uint32_t len_old;
+ uint8_t shortpkt_old;
+ uint8_t precompute;
+
+ td_alt_next = NULL;
+ buf_offset = 0;
+ shortpkt_old = temp->shortpkt;
+ len_old = temp->len;
+ precompute = 1;
+
+ /* software is used to detect short incoming transfers */
+
+ if ((temp->td_flags & htole32(OHCI_TD_DP_MASK)) == htole32(OHCI_TD_IN)) {
+ temp->td_flags |= htole32(OHCI_TD_R);
+ } else {
+ temp->td_flags &= ~htole32(OHCI_TD_R);
+ }
+
+restart:
+
+ td = temp->td;
+ td_next = temp->td_next;
+
+ while (1) {
+ if (temp->len == 0) {
+ if (temp->shortpkt) {
+ break;
+ }
+ /* send a Zero Length Packet, ZLP, last */
+
+ temp->shortpkt = 1;
+ average = 0;
+
+ } else {
+ average = temp->average;
+
+ if (temp->len < average) {
+ if (temp->len % temp->max_frame_size) {
+ temp->shortpkt = 1;
+ }
+ average = temp->len;
+ }
+ }
+
+ if (td_next == NULL) {
+ panic("%s: out of OHCI transfer descriptors!", __FUNCTION__);
+ }
+ /* get next TD */
+
+ td = td_next;
+ td_next = td->obj_next;
+
+ /* check if we are pre-computing */
+
+ if (precompute) {
+ /* update remaining length */
+
+ temp->len -= average;
+
+ continue;
+ }
+ /* fill out current TD */
+ td->td_flags = temp->td_flags;
+
+ /* the next TD uses TOGGLE_CARRY */
+ temp->td_flags &= ~htole32(OHCI_TD_TOGGLE_MASK);
+
+ if (average == 0) {
+ /*
+ * The buffer start and end phys addresses should be
+ * 0x0 for a zero length packet.
+ */
+ td->td_cbp = 0;
+ td->td_be = 0;
+ td->len = 0;
+
+ } else {
+ usbd_get_page(temp->pc, buf_offset, &buf_res);
+ td->td_cbp = htole32(buf_res.physaddr);
+ buf_offset += (average - 1);
+
+ usbd_get_page(temp->pc, buf_offset, &buf_res);
+ td->td_be = htole32(buf_res.physaddr);
+ buf_offset++;
+
+ td->len = average;
+
+ /* update remaining length */
+
+ temp->len -= average;
+ }
+
+ if ((td_next == td_alt_next) && temp->setup_alt_next) {
+ /* we need to receive these frames one by one ! */
+ td->td_flags &= htole32(~OHCI_TD_INTR_MASK);
+ td->td_flags |= htole32(OHCI_TD_SET_DI(1));
+ td->td_next = htole32(OHCI_TD_NEXT_END);
+ } else {
+ if (td_next) {
+ /* link the current TD with the next one */
+ td->td_next = td_next->td_self;
+ }
+ }
+
+ td->alt_next = td_alt_next;
+
+ usb_pc_cpu_flush(td->page_cache);
+ }
+
+ if (precompute) {
+ precompute = 0;
+
+ /* setup alt next pointer, if any */
+ if (temp->last_frame) {
+ /* no alternate next */
+ td_alt_next = NULL;
+ } else {
+ /* we use this field internally */
+ td_alt_next = td_next;
+ }
+
+ /* restore */
+ temp->shortpkt = shortpkt_old;
+ temp->len = len_old;
+ goto restart;
+ }
+ temp->td = td;
+ temp->td_next = td_next;
+}
+
+static void
+ohci_setup_standard_chain(struct usb_xfer *xfer, ohci_ed_t **ed_last)
+{
+ struct ohci_std_temp temp;
+ const struct usb_pipe_methods *methods;
+ ohci_ed_t *ed;
+ ohci_td_t *td;
+ uint32_t ed_flags;
+ uint32_t x;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpointno),
+ xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
+
+ temp.average = xfer->max_hc_frame_size;
+ temp.max_frame_size = xfer->max_frame_size;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ temp.td = NULL;
+ temp.td_next = td;
+ temp.last_frame = 0;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+
+ methods = xfer->endpoint->methods;
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ temp.td_flags = htole32(OHCI_TD_SETUP | OHCI_TD_NOCC |
+ OHCI_TD_TOGGLE_0 | OHCI_TD_NOINTR);
+
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.shortpkt = temp.len ? 1 : 0;
+ /* check for last frame */
+ if (xfer->nframes == 1) {
+ /* no STATUS stage yet, SETUP is last */
+ if (xfer->flags_int.control_act) {
+ temp.last_frame = 1;
+ temp.setup_alt_next = 0;
+ }
+ }
+ ohci_setup_standard_chain_sub(&temp);
+
+ /*
+ * XXX assume that the setup message is
+ * contained within one USB packet:
+ */
+ xfer->endpoint->toggle_next = 1;
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+ temp.td_flags = htole32(OHCI_TD_NOCC | OHCI_TD_NOINTR);
+
+ /* set data toggle */
+
+ if (xfer->endpoint->toggle_next) {
+ temp.td_flags |= htole32(OHCI_TD_TOGGLE_1);
+ } else {
+ temp.td_flags |= htole32(OHCI_TD_TOGGLE_0);
+ }
+
+ /* set endpoint direction */
+
+ if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) {
+ temp.td_flags |= htole32(OHCI_TD_IN);
+ } else {
+ temp.td_flags |= htole32(OHCI_TD_OUT);
+ }
+
+ while (x != xfer->nframes) {
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+ temp.pc = xfer->frbuffers + x;
+
+ x++;
+
+ if (x == xfer->nframes) {
+ if (xfer->flags_int.control_xfr) {
+ /* no STATUS stage yet, DATA is last */
+ if (xfer->flags_int.control_act) {
+ temp.last_frame = 1;
+ temp.setup_alt_next = 0;
+ }
+ } else {
+ temp.last_frame = 1;
+ temp.setup_alt_next = 0;
+ }
+ }
+ if (temp.len == 0) {
+ /* make sure that we send an USB packet */
+
+ temp.shortpkt = 0;
+
+ } else {
+ /* regular data transfer */
+
+ temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ ohci_setup_standard_chain_sub(&temp);
+ }
+
+ /* check if we should append a status stage */
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+ /*
+ * Send a DATA1 message and invert the current endpoint
+ * direction.
+ */
+
+ /* set endpoint direction and data toggle */
+
+ if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) {
+ temp.td_flags = htole32(OHCI_TD_OUT |
+ OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1));
+ } else {
+ temp.td_flags = htole32(OHCI_TD_IN |
+ OHCI_TD_NOCC | OHCI_TD_TOGGLE_1 | OHCI_TD_SET_DI(1));
+ }
+
+ temp.len = 0;
+ temp.pc = NULL;
+ temp.shortpkt = 0;
+ temp.last_frame = 1;
+ temp.setup_alt_next = 0;
+
+ ohci_setup_standard_chain_sub(&temp);
+ }
+ td = temp.td;
+
+ /* Ensure that last TD is terminating: */
+ td->td_next = htole32(OHCI_TD_NEXT_END);
+ td->td_flags &= ~htole32(OHCI_TD_INTR_MASK);
+ td->td_flags |= htole32(OHCI_TD_SET_DI(1));
+
+ usb_pc_cpu_flush(td->page_cache);
+
+ /* must have at least one frame! */
+
+ xfer->td_transfer_last = td;
+
+#ifdef USB_DEBUG
+ if (ohcidebug > 8) {
+ DPRINTF("nexttog=%d; data before transfer:\n",
+ xfer->endpoint->toggle_next);
+ ohci_dump_tds(xfer->td_transfer_first);
+ }
+#endif
+
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ ed_flags = (OHCI_ED_SET_FA(xfer->address) |
+ OHCI_ED_SET_EN(UE_GET_ADDR(xfer->endpointno)) |
+ OHCI_ED_SET_MAXP(xfer->max_frame_size));
+
+ ed_flags |= (OHCI_ED_FORMAT_GEN | OHCI_ED_DIR_TD);
+
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ ed_flags |= OHCI_ED_SPEED;
+ }
+ ed->ed_flags = htole32(ed_flags);
+
+ td = xfer->td_transfer_first;
+
+ ed->ed_headp = td->td_self;
+
+ if (xfer->xroot->udev->flags.self_suspended == 0) {
+ /* the append function will flush the endpoint descriptor */
+ OHCI_APPEND_QH(ed, *ed_last);
+
+ if (methods == &ohci_device_bulk_methods) {
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF);
+ }
+ if (methods == &ohci_device_ctrl_methods) {
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF);
+ }
+ } else {
+ usb_pc_cpu_flush(ed->page_cache);
+ }
+}
+
+static void
+ohci_root_intr(ohci_softc_t *sc)
+{
+ uint32_t hstatus __usbdebug_used;
+ uint16_t i;
+ uint16_t m;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* clear any old interrupt data */
+ memset(sc->sc_hub_idata, 0, sizeof(sc->sc_hub_idata));
+
+ hstatus = OREAD4(sc, OHCI_RH_STATUS);
+ DPRINTF("sc=%p hstatus=0x%08x\n",
+ sc, hstatus);
+
+ /* set bits */
+ m = (sc->sc_noport + 1);
+ if (m > (8 * sizeof(sc->sc_hub_idata))) {
+ m = (8 * sizeof(sc->sc_hub_idata));
+ }
+ for (i = 1; i < m; i++) {
+ /* pick out CHANGE bits from the status register */
+ if (OREAD4(sc, OHCI_RH_PORT_STATUS(i)) >> 16) {
+ sc->sc_hub_idata[i / 8] |= 1 << (i % 8);
+ DPRINTF("port %d changed\n", i);
+ }
+ }
+
+ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
+ sizeof(sc->sc_hub_idata));
+}
+
+/* NOTE: "done" can be run two times in a row,
+ * from close and from interrupt
+ */
+static void
+ohci_device_done(struct usb_xfer *xfer, usb_error_t error)
+{
+ const struct usb_pipe_methods *methods = xfer->endpoint->methods;
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+ ohci_ed_t *ed;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n",
+ xfer, xfer->endpoint, error);
+
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+ if (ed) {
+ usb_pc_cpu_invalidate(ed->page_cache);
+ }
+ if (methods == &ohci_device_bulk_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_bulk_p_last);
+ }
+ if (methods == &ohci_device_ctrl_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_ctrl_p_last);
+ }
+ if (methods == &ohci_device_intr_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ if (methods == &ohci_device_isoc_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_isoc_p_last);
+ }
+ xfer->td_transfer_first = NULL;
+ xfer->td_transfer_last = NULL;
+
+ /* dequeue transfer and start next transfer */
+ usbd_transfer_done(xfer, error);
+}
+
+/*------------------------------------------------------------------------*
+ * ohci bulk support
+ *------------------------------------------------------------------------*/
+static void
+ohci_device_bulk_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_bulk_close(struct usb_xfer *xfer)
+{
+ ohci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ohci_device_bulk_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_bulk_start(struct usb_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ohci_setup_standard_chain(xfer, &sc->sc_bulk_p_last);
+
+ /* put transfer on interrupt queue */
+ ohci_transfer_intr_enqueue(xfer);
+}
+
+static const struct usb_pipe_methods ohci_device_bulk_methods =
+{
+ .open = ohci_device_bulk_open,
+ .close = ohci_device_bulk_close,
+ .enter = ohci_device_bulk_enter,
+ .start = ohci_device_bulk_start,
+};
+
+/*------------------------------------------------------------------------*
+ * ohci control support
+ *------------------------------------------------------------------------*/
+static void
+ohci_device_ctrl_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_ctrl_close(struct usb_xfer *xfer)
+{
+ ohci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ohci_device_ctrl_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_ctrl_start(struct usb_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ohci_setup_standard_chain(xfer, &sc->sc_ctrl_p_last);
+
+ /* put transfer on interrupt queue */
+ ohci_transfer_intr_enqueue(xfer);
+}
+
+static const struct usb_pipe_methods ohci_device_ctrl_methods =
+{
+ .open = ohci_device_ctrl_open,
+ .close = ohci_device_ctrl_close,
+ .enter = ohci_device_ctrl_enter,
+ .start = ohci_device_ctrl_start,
+};
+
+/*------------------------------------------------------------------------*
+ * ohci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+ohci_device_intr_open(struct usb_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+ uint16_t best;
+ uint16_t bit;
+ uint16_t x;
+
+ best = 0;
+ bit = OHCI_NO_EDS / 2;
+ while (bit) {
+ if (xfer->interval >= bit) {
+ x = bit;
+ best = bit;
+ while (x & bit) {
+ if (sc->sc_intr_stat[x] <
+ sc->sc_intr_stat[best]) {
+ best = x;
+ }
+ x++;
+ }
+ break;
+ }
+ bit >>= 1;
+ }
+
+ sc->sc_intr_stat[best]++;
+ xfer->qh_pos = best;
+
+ DPRINTFN(3, "best=%d interval=%d\n",
+ best, xfer->interval);
+}
+
+static void
+ohci_device_intr_close(struct usb_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_intr_stat[xfer->qh_pos]--;
+
+ ohci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ohci_device_intr_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_intr_start(struct usb_xfer *xfer)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+
+ /* setup TD's and QH */
+ ohci_setup_standard_chain(xfer, &sc->sc_intr_p_last[xfer->qh_pos]);
+
+ /* put transfer on interrupt queue */
+ ohci_transfer_intr_enqueue(xfer);
+}
+
+static const struct usb_pipe_methods ohci_device_intr_methods =
+{
+ .open = ohci_device_intr_open,
+ .close = ohci_device_intr_close,
+ .enter = ohci_device_intr_enter,
+ .start = ohci_device_intr_start,
+};
+
+/*------------------------------------------------------------------------*
+ * ohci isochronous support
+ *------------------------------------------------------------------------*/
+static void
+ohci_device_isoc_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_device_isoc_close(struct usb_xfer *xfer)
+{
+ /**/
+ ohci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+ohci_device_isoc_enter(struct usb_xfer *xfer)
+{
+ struct usb_page_search buf_res;
+ ohci_softc_t *sc = OHCI_BUS2SC(xfer->xroot->bus);
+ struct ohci_hcca *hcca;
+ uint32_t buf_offset;
+ uint32_t nframes;
+ uint32_t startframe;
+ uint32_t ed_flags;
+ uint32_t *plen;
+ uint16_t itd_offset[OHCI_ITD_NOFFSET];
+ uint16_t length;
+ uint8_t ncur;
+ ohci_itd_t *td;
+ ohci_itd_t *td_last = NULL;
+ ohci_ed_t *ed;
+
+ hcca = ohci_get_hcca(sc);
+
+ nframes = le32toh(hcca->hcca_frame_number);
+
+ DPRINTFN(6, "xfer=%p isoc_next=%u nframes=%u hcca_fn=%u\n",
+ xfer, xfer->endpoint->isoc_next, xfer->nframes, nframes);
+
+ if (usbd_xfer_get_isochronous_start_frame(
+ xfer, nframes, 0, 1, 0xFFFF, &startframe))
+ DPRINTFN(3, "start next=%d\n", startframe);
+
+ /* get the real number of frames */
+
+ nframes = xfer->nframes;
+
+ buf_offset = 0;
+
+ plen = xfer->frlengths;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+
+ xfer->td_transfer_first = td;
+
+ ncur = 0;
+ length = 0;
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ itd_offset[ncur] = length;
+ buf_offset += *plen;
+ length += *plen;
+ plen++;
+ ncur++;
+
+ if ( /* check if the ITD is full */
+ (ncur == OHCI_ITD_NOFFSET) ||
+ /* check if we have put more than 4K into the ITD */
+ (length & 0xF000) ||
+ /* check if it is the last frame */
+ (nframes == 0)) {
+ /* fill current ITD */
+ td->itd_flags = htole32(
+ OHCI_ITD_NOCC |
+ OHCI_ITD_SET_SF(startframe) |
+ OHCI_ITD_NOINTR |
+ OHCI_ITD_SET_FC(ncur));
+
+ td->frames = ncur;
+ startframe += ncur;
+
+ if (length == 0) {
+ /* all zero */
+ td->itd_bp0 = 0;
+ td->itd_be = ~0;
+
+ while (ncur--) {
+ td->itd_offset[ncur] =
+ htole16(OHCI_ITD_MK_OFFS(0));
+ }
+ } else {
+ usbd_get_page(xfer->frbuffers, buf_offset - length, &buf_res);
+ length = OHCI_PAGE_MASK(buf_res.physaddr);
+ buf_res.physaddr =
+ OHCI_PAGE(buf_res.physaddr);
+ td->itd_bp0 = htole32(buf_res.physaddr);
+ usbd_get_page(xfer->frbuffers, buf_offset - 1, &buf_res);
+ td->itd_be = htole32(buf_res.physaddr);
+
+ while (ncur--) {
+ itd_offset[ncur] += length;
+ itd_offset[ncur] =
+ OHCI_ITD_MK_OFFS(itd_offset[ncur]);
+ td->itd_offset[ncur] =
+ htole16(itd_offset[ncur]);
+ }
+ }
+ ncur = 0;
+ length = 0;
+ td_last = td;
+ td = td->obj_next;
+
+ if (td) {
+ /* link the last TD with the next one */
+ td_last->itd_next = td->itd_self;
+ }
+ usb_pc_cpu_flush(td_last->page_cache);
+ }
+ }
+
+ /* update the last TD */
+ td_last->itd_flags &= ~htole32(OHCI_ITD_NOINTR);
+ td_last->itd_flags |= htole32(OHCI_ITD_SET_DI(0));
+ td_last->itd_next = 0;
+
+ usb_pc_cpu_flush(td_last->page_cache);
+
+ xfer->td_transfer_last = td_last;
+
+#ifdef USB_DEBUG
+ if (ohcidebug > 8) {
+ DPRINTF("data before transfer:\n");
+ ohci_dump_itds(xfer->td_transfer_first);
+ }
+#endif
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ if (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN)
+ ed_flags = (OHCI_ED_DIR_IN | OHCI_ED_FORMAT_ISO);
+ else
+ ed_flags = (OHCI_ED_DIR_OUT | OHCI_ED_FORMAT_ISO);
+
+ ed_flags |= (OHCI_ED_SET_FA(xfer->address) |
+ OHCI_ED_SET_EN(UE_GET_ADDR(xfer->endpointno)) |
+ OHCI_ED_SET_MAXP(xfer->max_frame_size));
+
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ ed_flags |= OHCI_ED_SPEED;
+ }
+ ed->ed_flags = htole32(ed_flags);
+
+ td = xfer->td_transfer_first;
+
+ ed->ed_headp = td->itd_self;
+
+ /* isochronous transfers are not affected by suspend / resume */
+ /* the append function will flush the endpoint descriptor */
+
+ OHCI_APPEND_QH(ed, sc->sc_isoc_p_last);
+}
+
+static void
+ohci_device_isoc_start(struct usb_xfer *xfer)
+{
+ /* put transfer on interrupt queue */
+ ohci_transfer_intr_enqueue(xfer);
+}
+
+static const struct usb_pipe_methods ohci_device_isoc_methods =
+{
+ .open = ohci_device_isoc_open,
+ .close = ohci_device_isoc_close,
+ .enter = ohci_device_isoc_enter,
+ .start = ohci_device_isoc_start,
+};
+
+/*------------------------------------------------------------------------*
+ * ohci root control support
+ *------------------------------------------------------------------------*
+ * Simulate a hardware hub by handling all the necessary requests.
+ *------------------------------------------------------------------------*/
+
+static const
+struct usb_device_descriptor ohci_devd =
+{
+ sizeof(struct usb_device_descriptor),
+ UDESC_DEVICE, /* type */
+ {0x00, 0x01}, /* USB version */
+ UDCLASS_HUB, /* class */
+ UDSUBCLASS_HUB, /* subclass */
+ UDPROTO_FSHUB, /* protocol */
+ 64, /* max packet */
+ {0}, {0}, {0x00, 0x01}, /* device id */
+ 1, 2, 0, /* string indexes */
+ 1 /* # of configurations */
+};
+
+static const
+struct ohci_config_desc ohci_confd =
+{
+ .confd = {
+ .bLength = sizeof(struct usb_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(ohci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0, /* max power */
+ },
+ .ifcd = {
+ .bLength = sizeof(struct usb_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = 0,
+ },
+ .endpd = {
+ .bLength = sizeof(struct usb_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = UE_DIR_IN | OHCI_INTR_ENDPT,
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 32,/* max packet (255 ports) */
+ .bInterval = 255,
+ },
+};
+
+static const
+struct usb_hub_descriptor ohci_hubd =
+{
+ .bDescLength = 0, /* dynamic length */
+ .bDescriptorType = UDESC_HUB,
+};
+
+static usb_error_t
+ohci_roothub_exec(struct usb_device *udev,
+ struct usb_device_request *req, const void **pptr, uint16_t *plength)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(udev->bus);
+ const void *ptr;
+ const char *str_ptr;
+ uint32_t port;
+ uint32_t v;
+ uint16_t len;
+ uint16_t value;
+ uint16_t index;
+ uint8_t l;
+ usb_error_t err;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* buffer reset */
+ ptr = (const void *)&sc->sc_hub_desc.temp;
+ len = 0;
+ err = 0;
+
+ value = UGETW(req->wValue);
+ index = UGETW(req->wIndex);
+
+ DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x "
+ "wValue=0x%04x wIndex=0x%04x\n",
+ req->bmRequestType, req->bRequest,
+ UGETW(req->wLength), value, index);
+
+#define C(x,y) ((x) | ((y) << 8))
+ switch (C(req->bRequest, req->bmRequestType)) {
+ case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
+ /*
+ * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops
+ * for the integrated root hub.
+ */
+ break;
+ case C(UR_GET_CONFIG, UT_READ_DEVICE):
+ len = 1;
+ sc->sc_hub_desc.temp[0] = sc->sc_conf;
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE):
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ len = sizeof(ohci_devd);
+ ptr = (const void *)&ohci_devd;
+ break;
+
+ case UDESC_CONFIG:
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ len = sizeof(ohci_confd);
+ ptr = (const void *)&ohci_confd;
+ break;
+
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ str_ptr = "\001";
+ break;
+
+ case 1: /* Vendor */
+ str_ptr = sc->sc_vendor;
+ break;
+
+ case 2: /* Product */
+ str_ptr = "OHCI root HUB";
+ break;
+
+ default:
+ str_ptr = "";
+ break;
+ }
+
+ len = usb_make_str_desc(
+ sc->sc_hub_desc.temp,
+ sizeof(sc->sc_hub_desc.temp),
+ str_ptr);
+ break;
+
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_GET_INTERFACE, UT_READ_INTERFACE):
+ len = 1;
+ sc->sc_hub_desc.temp[0] = 0;
+ break;
+ case C(UR_GET_STATUS, UT_READ_DEVICE):
+ len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED);
+ break;
+ case C(UR_GET_STATUS, UT_READ_INTERFACE):
+ case C(UR_GET_STATUS, UT_READ_ENDPOINT):
+ len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, 0);
+ break;
+ case C(UR_SET_ADDRESS, UT_WRITE_DEVICE):
+ if (value >= OHCI_MAX_DEVICES) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_addr = value;
+ break;
+ case C(UR_SET_CONFIG, UT_WRITE_DEVICE):
+ if ((value != 0) && (value != 1)) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_conf = value;
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_SET_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT):
+ err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
+ break;
+ case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT):
+ break;
+ /* Hub requests */
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER):
+ DPRINTFN(9, "UR_CLEAR_PORT_FEATURE "
+ "port=%d feature=%d\n",
+ index, value);
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ port = OHCI_RH_PORT_STATUS(index);
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ OWRITE4(sc, port, UPS_CURRENT_CONNECT_STATUS);
+ break;
+ case UHF_PORT_SUSPEND:
+ OWRITE4(sc, port, UPS_OVERCURRENT_INDICATOR);
+ break;
+ case UHF_PORT_POWER:
+ /* Yes, writing to the LOW_SPEED bit clears power. */
+ OWRITE4(sc, port, UPS_LOW_SPEED);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ OWRITE4(sc, port, UPS_C_CONNECT_STATUS << 16);
+ break;
+ case UHF_C_PORT_ENABLE:
+ OWRITE4(sc, port, UPS_C_PORT_ENABLED << 16);
+ break;
+ case UHF_C_PORT_SUSPEND:
+ OWRITE4(sc, port, UPS_C_SUSPEND << 16);
+ break;
+ case UHF_C_PORT_OVER_CURRENT:
+ OWRITE4(sc, port, UPS_C_OVERCURRENT_INDICATOR << 16);
+ break;
+ case UHF_C_PORT_RESET:
+ OWRITE4(sc, port, UPS_C_PORT_RESET << 16);
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ switch (value) {
+ case UHF_C_PORT_CONNECTION:
+ case UHF_C_PORT_ENABLE:
+ case UHF_C_PORT_SUSPEND:
+ case UHF_C_PORT_OVER_CURRENT:
+ case UHF_C_PORT_RESET:
+ /* enable RHSC interrupt if condition is cleared. */
+ if ((OREAD4(sc, port) >> 16) == 0)
+ ohci_rhsc_enable(sc);
+ break;
+ default:
+ break;
+ }
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE):
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ v = OREAD4(sc, OHCI_RH_DESCRIPTOR_A);
+
+ sc->sc_hub_desc.hubd = ohci_hubd;
+ sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport;
+ USETW(sc->sc_hub_desc.hubd.wHubCharacteristics,
+ (v & OHCI_NPS ? UHD_PWR_NO_SWITCH :
+ v & OHCI_PSM ? UHD_PWR_GANGED : UHD_PWR_INDIVIDUAL)
+ /* XXX overcurrent */
+ );
+ sc->sc_hub_desc.hubd.bPwrOn2PwrGood = OHCI_GET_POTPGT(v);
+ v = OREAD4(sc, OHCI_RH_DESCRIPTOR_B);
+
+ for (l = 0; l < sc->sc_noport; l++) {
+ if (v & 1) {
+ sc->sc_hub_desc.hubd.DeviceRemovable[l / 8] |= (1 << (l % 8));
+ }
+ v >>= 1;
+ }
+ sc->sc_hub_desc.hubd.bDescLength =
+ 8 + ((sc->sc_noport + 7) / 8);
+ len = sc->sc_hub_desc.hubd.bDescLength;
+ break;
+
+ case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE):
+ len = 16;
+ memset(sc->sc_hub_desc.temp, 0, 16);
+ break;
+ case C(UR_GET_STATUS, UT_READ_CLASS_OTHER):
+ DPRINTFN(9, "get port status i=%d\n",
+ index);
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ v = OREAD4(sc, OHCI_RH_PORT_STATUS(index));
+ DPRINTFN(9, "port status=0x%04x\n", v);
+ v &= ~UPS_PORT_MODE_DEVICE; /* force host mode */
+ USETW(sc->sc_hub_desc.ps.wPortStatus, v);
+ USETW(sc->sc_hub_desc.ps.wPortChange, v >> 16);
+ len = sizeof(sc->sc_hub_desc.ps);
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE):
+ err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER):
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ port = OHCI_RH_PORT_STATUS(index);
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ OWRITE4(sc, port, UPS_PORT_ENABLED);
+ break;
+ case UHF_PORT_SUSPEND:
+ OWRITE4(sc, port, UPS_SUSPEND);
+ break;
+ case UHF_PORT_RESET:
+ DPRINTFN(6, "reset port %d\n", index);
+ OWRITE4(sc, port, UPS_RESET);
+ for (v = 0;; v++) {
+ if (v < 12) {
+ usb_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(usb_port_root_reset_delay));
+
+ if ((OREAD4(sc, port) & UPS_RESET) == 0) {
+ break;
+ }
+ } else {
+ err = USB_ERR_TIMEOUT;
+ goto done;
+ }
+ }
+ DPRINTFN(9, "ohci port %d reset, status = 0x%04x\n",
+ index, OREAD4(sc, port));
+ break;
+ case UHF_PORT_POWER:
+ DPRINTFN(3, "set port power %d\n", index);
+ OWRITE4(sc, port, UPS_PORT_POWER);
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+done:
+ *plength = len;
+ *pptr = ptr;
+ return (err);
+}
+
+static void
+ohci_xfer_setup(struct usb_setup_params *parm)
+{
+ struct usb_page_search page_info;
+ struct usb_page_cache *pc;
+ struct usb_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t nitd;
+ uint32_t nqh;
+ uint32_t n;
+
+ xfer = parm->curr_xfer;
+
+ parm->hc_max_packet_size = 0x500;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = OHCI_PAGE_SIZE;
+
+ /*
+ * calculate ntd and nqh
+ */
+ if (parm->methods == &ohci_device_ctrl_methods) {
+ xfer->flags_int.bdma_enable = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ nitd = 0;
+ ntd = ((2 * xfer->nframes) + 1 /* STATUS */
+ + (xfer->max_data_length / xfer->max_hc_frame_size));
+ nqh = 1;
+
+ } else if (parm->methods == &ohci_device_bulk_methods) {
+ xfer->flags_int.bdma_enable = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ nitd = 0;
+ ntd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_hc_frame_size));
+ nqh = 1;
+
+ } else if (parm->methods == &ohci_device_intr_methods) {
+ xfer->flags_int.bdma_enable = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ nitd = 0;
+ ntd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_hc_frame_size));
+ nqh = 1;
+
+ } else if (parm->methods == &ohci_device_isoc_methods) {
+ xfer->flags_int.bdma_enable = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ nitd = ((xfer->max_data_length / OHCI_PAGE_SIZE) +
+ howmany(xfer->nframes, OHCI_ITD_NOFFSET) +
+ 1 /* EXTRA */ );
+ ntd = 0;
+ nqh = 1;
+
+ } else {
+ usbd_transfer_setup_sub(parm);
+
+ nitd = 0;
+ ntd = 0;
+ nqh = 0;
+ }
+
+alloc_dma_set:
+
+ if (parm->err) {
+ return;
+ }
+ last_obj = NULL;
+
+ if (usbd_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ohci_td_t),
+ OHCI_TD_ALIGN, ntd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != ntd; n++) {
+ ohci_td_t *td;
+
+ usbd_get_page(pc + n, 0, &page_info);
+
+ td = page_info.buffer;
+
+ /* init TD */
+ td->td_self = htole32(page_info.physaddr);
+ td->obj_next = last_obj;
+ td->page_cache = pc + n;
+
+ last_obj = td;
+
+ usb_pc_cpu_flush(pc + n);
+ }
+ }
+ if (usbd_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ohci_itd_t),
+ OHCI_ITD_ALIGN, nitd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nitd; n++) {
+ ohci_itd_t *itd;
+
+ usbd_get_page(pc + n, 0, &page_info);
+
+ itd = page_info.buffer;
+
+ /* init TD */
+ itd->itd_self = htole32(page_info.physaddr);
+ itd->obj_next = last_obj;
+ itd->page_cache = pc + n;
+
+ last_obj = itd;
+
+ usb_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ last_obj = NULL;
+
+ if (usbd_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(ohci_ed_t),
+ OHCI_ED_ALIGN, nqh)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nqh; n++) {
+ ohci_ed_t *ed;
+
+ usbd_get_page(pc + n, 0, &page_info);
+
+ ed = page_info.buffer;
+
+ /* init QH */
+ ed->ed_self = htole32(page_info.physaddr);
+ ed->obj_next = last_obj;
+ ed->page_cache = pc + n;
+
+ last_obj = ed;
+
+ usb_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ if (!xfer->flags_int.curr_dma_set) {
+ xfer->flags_int.curr_dma_set = 1;
+ goto alloc_dma_set;
+ }
+}
+
+static void
+ohci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
+ struct usb_endpoint *ep)
+{
+ ohci_softc_t *sc = OHCI_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ ep, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb_mode,
+ sc->sc_addr);
+
+ if (udev->device_index != sc->sc_addr) {
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ ep->methods = &ohci_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ ep->methods = &ohci_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ if (udev->speed == USB_SPEED_FULL) {
+ ep->methods = &ohci_device_isoc_methods;
+ }
+ break;
+ case UE_BULK:
+ ep->methods = &ohci_device_bulk_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+static void
+ohci_xfer_unsetup(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+ohci_get_dma_delay(struct usb_device *udev, uint32_t *pus)
+{
+ /*
+ * Wait until hardware has finished any possible use of the
+ * transfer descriptor(s) and QH
+ */
+ *pus = (1125); /* microseconds */
+}
+
+static void
+ohci_device_resume(struct usb_device *udev)
+{
+ struct ohci_softc *sc = OHCI_BUS2SC(udev->bus);
+ struct usb_xfer *xfer;
+ const struct usb_pipe_methods *methods;
+ ohci_ed_t *ed;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (xfer->xroot->udev == udev) {
+ methods = xfer->endpoint->methods;
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ if (methods == &ohci_device_bulk_methods) {
+ OHCI_APPEND_QH(ed, sc->sc_bulk_p_last);
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_BLF);
+ }
+ if (methods == &ohci_device_ctrl_methods) {
+ OHCI_APPEND_QH(ed, sc->sc_ctrl_p_last);
+ OWRITE4(sc, OHCI_COMMAND_STATUS, OHCI_CLF);
+ }
+ if (methods == &ohci_device_intr_methods) {
+ OHCI_APPEND_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ return;
+}
+
+static void
+ohci_device_suspend(struct usb_device *udev)
+{
+ struct ohci_softc *sc = OHCI_BUS2SC(udev->bus);
+ struct usb_xfer *xfer;
+ const struct usb_pipe_methods *methods;
+ ohci_ed_t *ed;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (xfer->xroot->udev == udev) {
+ methods = xfer->endpoint->methods;
+ ed = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ if (methods == &ohci_device_bulk_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_bulk_p_last);
+ }
+ if (methods == &ohci_device_ctrl_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_ctrl_p_last);
+ }
+ if (methods == &ohci_device_intr_methods) {
+ OHCI_REMOVE_QH(ed, sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ return;
+}
+
+static void
+ohci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state)
+{
+ struct ohci_softc *sc = OHCI_BUS2SC(bus);
+
+ switch (state) {
+ case USB_HW_POWER_SUSPEND:
+ case USB_HW_POWER_SHUTDOWN:
+ ohci_suspend(sc);
+ break;
+ case USB_HW_POWER_RESUME:
+ ohci_resume(sc);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+ohci_set_hw_power(struct usb_bus *bus)
+{
+ struct ohci_softc *sc = OHCI_BUS2SC(bus);
+ uint32_t temp;
+ uint32_t flags;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(bus);
+
+ flags = bus->hw_power_state;
+
+ temp = OREAD4(sc, OHCI_CONTROL);
+ temp &= ~(OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE);
+
+ if (flags & USB_HW_POWER_CONTROL)
+ temp |= OHCI_CLE;
+
+ if (flags & USB_HW_POWER_BULK)
+ temp |= OHCI_BLE;
+
+ if (flags & USB_HW_POWER_INTERRUPT)
+ temp |= OHCI_PLE;
+
+ if (flags & USB_HW_POWER_ISOC)
+ temp |= OHCI_IE | OHCI_PLE;
+
+ OWRITE4(sc, OHCI_CONTROL, temp);
+
+ USB_BUS_UNLOCK(bus);
+
+ return;
+}
+
+static const struct usb_bus_methods ohci_bus_methods =
+{
+ .endpoint_init = ohci_ep_init,
+ .xfer_setup = ohci_xfer_setup,
+ .xfer_unsetup = ohci_xfer_unsetup,
+ .get_dma_delay = ohci_get_dma_delay,
+ .device_resume = ohci_device_resume,
+ .device_suspend = ohci_device_suspend,
+ .set_hw_power = ohci_set_hw_power,
+ .set_hw_power_sleep = ohci_set_hw_power_sleep,
+ .roothub_exec = ohci_roothub_exec,
+ .xfer_poll = ohci_do_poll,
+};
diff --git a/sys/dev/usb/controller/ohci.h b/sys/dev/usb/controller/ohci.h
new file mode 100644
index 000000000000..e8f7ae39ba8d
--- /dev/null
+++ b/sys/dev/usb/controller/ohci.h
@@ -0,0 +1,264 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _OHCI_H_
+#define _OHCI_H_
+
+#define OHCI_MAX_DEVICES MIN(USB_MAX_DEVICES, 128)
+
+#define OHCI_NO_INTRS 32
+#define OHCI_HCCA_SIZE 256
+
+/* Structures alignment (bytes) */
+#define OHCI_HCCA_ALIGN 256
+#define OHCI_ED_ALIGN 16
+#define OHCI_TD_ALIGN 16
+#define OHCI_ITD_ALIGN 32
+
+#define OHCI_PAGE_SIZE 0x1000
+#define OHCI_PAGE(x) ((x) &~ 0xfff)
+#define OHCI_PAGE_OFFSET(x) ((x) & 0xfff)
+#define OHCI_PAGE_MASK(x) ((x) & 0xfff)
+
+#if ((USB_PAGE_SIZE < OHCI_ED_ALIGN) || (OHCI_ED_ALIGN == 0) || \
+ (USB_PAGE_SIZE < OHCI_TD_ALIGN) || (OHCI_TD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < OHCI_ITD_ALIGN) || (OHCI_ITD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < OHCI_PAGE_SIZE) || (OHCI_PAGE_SIZE == 0))
+#error "Invalid USB page size!"
+#endif
+
+#define OHCI_VIRTUAL_FRAMELIST_COUNT 128/* dummy */
+
+#if (OHCI_VIRTUAL_FRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER)
+#error "maximum number of full-speed isochronous frames is higher than supported!"
+#endif
+
+struct ohci_hcca {
+ volatile uint32_t hcca_interrupt_table[OHCI_NO_INTRS];
+ volatile uint32_t hcca_frame_number;
+ volatile uint32_t hcca_done_head;
+#define OHCI_DONE_INTRS 1
+} __aligned(OHCI_HCCA_ALIGN);
+
+typedef struct ohci_hcca ohci_hcca_t;
+
+struct ohci_ed {
+ volatile uint32_t ed_flags;
+#define OHCI_ED_GET_FA(s) ((s) & 0x7f)
+#define OHCI_ED_ADDRMASK 0x0000007f
+#define OHCI_ED_SET_FA(s) (s)
+#define OHCI_ED_GET_EN(s) (((s) >> 7) & 0xf)
+#define OHCI_ED_SET_EN(s) ((s) << 7)
+#define OHCI_ED_DIR_MASK 0x00001800
+#define OHCI_ED_DIR_TD 0x00000000
+#define OHCI_ED_DIR_OUT 0x00000800
+#define OHCI_ED_DIR_IN 0x00001000
+#define OHCI_ED_SPEED 0x00002000
+#define OHCI_ED_SKIP 0x00004000
+#define OHCI_ED_FORMAT_GEN 0x00000000
+#define OHCI_ED_FORMAT_ISO 0x00008000
+#define OHCI_ED_GET_MAXP(s) (((s) >> 16) & 0x07ff)
+#define OHCI_ED_SET_MAXP(s) ((s) << 16)
+#define OHCI_ED_MAXPMASK (0x7ff << 16)
+ volatile uint32_t ed_tailp;
+ volatile uint32_t ed_headp;
+#define OHCI_HALTED 0x00000001
+#define OHCI_TOGGLECARRY 0x00000002
+#define OHCI_HEADMASK 0xfffffffc
+ volatile uint32_t ed_next;
+/*
+ * Extra information needed:
+ */
+ struct ohci_ed *next;
+ struct ohci_ed *prev;
+ struct ohci_ed *obj_next;
+ struct usb_page_cache *page_cache;
+ uint32_t ed_self;
+} __aligned(OHCI_ED_ALIGN);
+
+typedef struct ohci_ed ohci_ed_t;
+
+struct ohci_td {
+ volatile uint32_t td_flags;
+#define OHCI_TD_R 0x00040000 /* Buffer Rounding */
+#define OHCI_TD_DP_MASK 0x00180000 /* Direction / PID */
+#define OHCI_TD_SETUP 0x00000000
+#define OHCI_TD_OUT 0x00080000
+#define OHCI_TD_IN 0x00100000
+#define OHCI_TD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */
+#define OHCI_TD_SET_DI(x) ((x) << 21)
+#define OHCI_TD_NOINTR 0x00e00000
+#define OHCI_TD_INTR_MASK 0x00e00000
+#define OHCI_TD_TOGGLE_CARRY 0x00000000
+#define OHCI_TD_TOGGLE_0 0x02000000
+#define OHCI_TD_TOGGLE_1 0x03000000
+#define OHCI_TD_TOGGLE_MASK 0x03000000
+#define OHCI_TD_GET_EC(x) (((x) >> 26) & 3) /* Error Count */
+#define OHCI_TD_GET_CC(x) ((x) >> 28) /* Condition Code */
+#define OHCI_TD_SET_CC(x) ((x) << 28)
+#define OHCI_TD_NOCC 0xf0000000
+ volatile uint32_t td_cbp; /* Current Buffer Pointer */
+ volatile uint32_t td_next; /* Next TD */
+#define OHCI_TD_NEXT_END 0
+ volatile uint32_t td_be; /* Buffer End */
+/*
+ * Extra information needed:
+ */
+ struct ohci_td *obj_next;
+ struct ohci_td *alt_next;
+ struct usb_page_cache *page_cache;
+ uint32_t td_self;
+ uint16_t len;
+} __aligned(OHCI_TD_ALIGN);
+
+typedef struct ohci_td ohci_td_t;
+
+struct ohci_itd {
+ volatile uint32_t itd_flags;
+#define OHCI_ITD_GET_SF(x) ((x) & 0x0000ffff)
+#define OHCI_ITD_SET_SF(x) ((x) & 0xffff)
+#define OHCI_ITD_GET_DI(x) (((x) >> 21) & 7) /* Delay Interrupt */
+#define OHCI_ITD_SET_DI(x) ((x) << 21)
+#define OHCI_ITD_NOINTR 0x00e00000
+#define OHCI_ITD_GET_FC(x) ((((x) >> 24) & 7)+1) /* Frame Count */
+#define OHCI_ITD_SET_FC(x) (((x)-1) << 24)
+#define OHCI_ITD_GET_CC(x) ((x) >> 28) /* Condition Code */
+#define OHCI_ITD_NOCC 0xf0000000
+#define OHCI_ITD_NOFFSET 8
+ volatile uint32_t itd_bp0; /* Buffer Page 0 */
+ volatile uint32_t itd_next; /* Next ITD */
+ volatile uint32_t itd_be; /* Buffer End */
+ volatile uint16_t itd_offset[OHCI_ITD_NOFFSET]; /* Buffer offsets and
+ * Status */
+#define OHCI_ITD_PAGE_SELECT 0x00001000
+#define OHCI_ITD_MK_OFFS(len) (0xe000 | ((len) & 0x1fff))
+#define OHCI_ITD_PSW_LENGTH(x) ((x) & 0xfff) /* Transfer length */
+#define OHCI_ITD_PSW_GET_CC(x) ((x) >> 12) /* Condition Code */
+/*
+ * Extra information needed:
+ */
+ struct ohci_itd *obj_next;
+ struct usb_page_cache *page_cache;
+ uint32_t itd_self;
+ uint8_t frames;
+} __aligned(OHCI_ITD_ALIGN);
+
+typedef struct ohci_itd ohci_itd_t;
+
+#define OHCI_CC_NO_ERROR 0
+#define OHCI_CC_CRC 1
+#define OHCI_CC_BIT_STUFFING 2
+#define OHCI_CC_DATA_TOGGLE_MISMATCH 3
+#define OHCI_CC_STALL 4
+#define OHCI_CC_DEVICE_NOT_RESPONDING 5
+#define OHCI_CC_PID_CHECK_FAILURE 6
+#define OHCI_CC_UNEXPECTED_PID 7
+#define OHCI_CC_DATA_OVERRUN 8
+#define OHCI_CC_DATA_UNDERRUN 9
+#define OHCI_CC_BUFFER_OVERRUN 12
+#define OHCI_CC_BUFFER_UNDERRUN 13
+#define OHCI_CC_NOT_ACCESSED 15
+
+/* Some delay needed when changing certain registers. */
+#define OHCI_ENABLE_POWER_DELAY 5
+#define OHCI_READ_DESC_DELAY 5
+
+#define OHCI_NO_EDS (2*OHCI_NO_INTRS)
+
+struct ohci_hw_softc {
+ struct usb_page_cache hcca_pc;
+ struct usb_page_cache ctrl_start_pc;
+ struct usb_page_cache bulk_start_pc;
+ struct usb_page_cache isoc_start_pc;
+ struct usb_page_cache intr_start_pc[OHCI_NO_EDS];
+
+ struct usb_page hcca_pg;
+ struct usb_page ctrl_start_pg;
+ struct usb_page bulk_start_pg;
+ struct usb_page isoc_start_pg;
+ struct usb_page intr_start_pg[OHCI_NO_EDS];
+};
+
+struct ohci_config_desc {
+ struct usb_config_descriptor confd;
+ struct usb_interface_descriptor ifcd;
+ struct usb_endpoint_descriptor endpd;
+} __packed;
+
+union ohci_hub_desc {
+ struct usb_status stat;
+ struct usb_port_status ps;
+ struct usb_hub_descriptor hubd;
+ uint8_t temp[128];
+};
+
+typedef struct ohci_softc {
+ struct ohci_hw_softc sc_hw;
+ struct usb_bus sc_bus; /* base device */
+ struct usb_callout sc_tmo_rhsc;
+ union ohci_hub_desc sc_hub_desc;
+
+ struct usb_device *sc_devices[OHCI_MAX_DEVICES];
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ struct ohci_hcca *sc_hcca_p;
+ struct ohci_ed *sc_ctrl_p_last;
+ struct ohci_ed *sc_bulk_p_last;
+ struct ohci_ed *sc_isoc_p_last;
+ struct ohci_ed *sc_intr_p_last[OHCI_NO_EDS];
+ void *sc_intr_hdl;
+ device_t sc_dev;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ uint32_t sc_eintrs; /* enabled interrupts */
+
+ uint16_t sc_intr_stat[OHCI_NO_EDS];
+ uint16_t sc_id_vendor;
+
+ uint8_t sc_noport;
+ uint8_t sc_addr; /* device address */
+ uint8_t sc_conf; /* device configuration */
+ uint8_t sc_hub_idata[32];
+
+ char sc_vendor[16];
+
+} ohci_softc_t;
+
+usb_bus_mem_cb_t ohci_iterate_hw_softc;
+
+usb_error_t ohci_init(ohci_softc_t *sc);
+void ohci_detach(struct ohci_softc *sc);
+void ohci_interrupt(ohci_softc_t *sc);
+
+#endif /* _OHCI_H_ */
diff --git a/sys/dev/usb/controller/ohci_pci.c b/sys/dev/usb/controller/ohci_pci.c
new file mode 100644
index 000000000000..0edcebcb0b38
--- /dev/null
+++ b/sys/dev/usb/controller/ohci_pci.c
@@ -0,0 +1,380 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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.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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/usb_pci.h>
+#include <dev/usb/controller/ohci.h>
+#include <dev/usb/controller/ohcireg.h>
+#include "usb_if.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_HYGON 0x1d94
+#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_BASE_REG 0x10
+
+static device_probe_t ohci_pci_probe;
+static device_attach_t ohci_pci_attach;
+static device_detach_t ohci_pci_detach;
+static usb_take_controller_t ohci_pci_take_controller;
+
+static int
+ohci_pci_take_controller(device_t self)
+{
+ uint32_t reg;
+ uint32_t int_line;
+
+ if (pci_get_powerstate(self) != PCI_POWERSTATE_D0) {
+ device_printf(self, "chip is in D%d mode "
+ "-- setting to D0\n", pci_get_powerstate(self));
+ reg = pci_read_config(self, PCI_CBMEM, 4);
+ int_line = pci_read_config(self, PCIR_INTLINE, 4);
+ pci_set_powerstate(self, PCI_POWERSTATE_D0);
+ pci_write_config(self, PCI_CBMEM, reg, 4);
+ pci_write_config(self, PCIR_INTLINE, int_line, 4);
+ }
+ return (0);
+}
+
+static const char *
+ohci_pci_match(device_t self)
+{
+ uint32_t device_id = pci_get_devid(self);
+
+ switch (device_id) {
+ case 0x523710b9:
+ return ("AcerLabs M5237 (Aladdin-V) USB controller");
+
+ case 0x740c1022:
+ return ("AMD-756 USB Controller");
+ case 0x74141022:
+ return ("AMD-766 USB Controller");
+ case 0x78071022:
+ return ("AMD FCH USB Controller");
+
+ case 0x43741002:
+ return "ATI SB400 USB Controller";
+ case 0x43751002:
+ return "ATI SB400 USB Controller";
+ case 0x43971002:
+ return ("AMD SB7x0/SB8x0/SB9x0 USB controller");
+ case 0x43981002:
+ return ("AMD SB7x0/SB8x0/SB9x0 USB controller");
+ case 0x43991002:
+ return ("AMD SB7x0/SB8x0/SB9x0 USB controller");
+
+ case 0x06701095:
+ return ("CMD Tech 670 (USB0670) USB controller");
+
+ case 0x06731095:
+ return ("CMD Tech 673 (USB0673) USB controller");
+
+ case 0xc8611045:
+ return ("OPTi 82C861 (FireLink) USB controller");
+
+ case 0x00351033:
+ return ("NEC uPD 9210 USB controller");
+
+ case 0x00d710de:
+ return ("nVidia nForce3 USB Controller");
+
+ case 0x005a10de:
+ return ("nVidia nForce CK804 USB Controller");
+ case 0x036c10de:
+ return ("nVidia nForce MCP55 USB Controller");
+ case 0x03f110de:
+ return ("nVidia nForce MCP61 USB Controller");
+ case 0x0aa510de:
+ return ("nVidia nForce MCP79 USB Controller");
+ case 0x0aa710de:
+ return ("nVidia nForce MCP79 USB Controller");
+ case 0x0aa810de:
+ return ("nVidia nForce MCP79 USB Controller");
+
+ case 0x70011039:
+ return ("SiS 5571 USB controller");
+
+ case 0x0019106b:
+ return ("Apple KeyLargo USB controller");
+ case 0x003f106b:
+ return ("Apple KeyLargo/Intrepid USB controller");
+
+ default:
+ break;
+ }
+ if ((pci_get_class(self) == PCIC_SERIALBUS) &&
+ (pci_get_subclass(self) == PCIS_SERIALBUS_USB) &&
+ (pci_get_progif(self) == PCI_INTERFACE_OHCI)) {
+ return ("OHCI (generic) USB controller");
+ }
+ return (NULL);
+}
+
+static int
+ohci_pci_probe(device_t self)
+{
+ const char *desc = ohci_pci_match(self);
+
+ if (desc) {
+ device_set_desc(self, desc);
+ return (0);
+ } else {
+ return (ENXIO);
+ }
+}
+
+static int
+ohci_pci_attach(device_t self)
+{
+ ohci_softc_t *sc = device_get_softc(self);
+ int rid;
+ int err;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = OHCI_MAX_DEVICES;
+ sc->sc_bus.dma_bits = 32;
+
+ /* get all DMA memory */
+ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self),
+ &ohci_iterate_hw_softc)) {
+ return (ENOMEM);
+ }
+ sc->sc_dev = self;
+
+ pci_enable_busmaster(self);
+
+ rid = PCI_CBMEM;
+ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (!sc->sc_io_res) {
+ device_printf(self, "Could not map memory\n");
+ goto error;
+ }
+ sc->sc_io_tag = rman_get_bustag(sc->sc_io_res);
+ sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = rman_get_size(sc->sc_io_res);
+
+ rid = 0;
+ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (sc->sc_irq_res == NULL) {
+ device_printf(self, "Could not allocate irq\n");
+ goto error;
+ }
+ sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY);
+ if (!sc->sc_bus.bdev) {
+ device_printf(self, "Could not add USB device\n");
+ goto error;
+ }
+ 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_HYGON:
+ sprintf(sc->sc_vendor, "Hygon");
+ 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));
+ }
+
+ /* sc->sc_bus.usbrev; set by ohci_init() */
+
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (driver_intr_t *)ohci_interrupt, sc, &sc->sc_intr_hdl);
+ if (err) {
+ device_printf(self, "Could not setup irq, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ goto error;
+ }
+ err = ohci_init(sc);
+ if (!err) {
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ }
+ if (err) {
+ device_printf(self, "USB init failed\n");
+ goto error;
+ }
+ return (0);
+
+error:
+ ohci_pci_detach(self);
+ return (ENXIO);
+}
+
+static int
+ohci_pci_detach(device_t self)
+{
+ ohci_softc_t *sc = device_get_softc(self);
+ int error;
+
+ /* during module unload there are lots of children leftover */
+ error = bus_generic_detach(self);
+ if (error != 0)
+ return (error);
+
+ pci_disable_busmaster(self);
+
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ /*
+ * only call ohci_detach() after ohci_init()
+ */
+ ohci_detach(sc);
+
+ int err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl);
+
+ if (err) {
+ /* XXX or should we panic? */
+ device_printf(self, "Could not tear down irq, %d\n",
+ err);
+ }
+ sc->sc_intr_hdl = NULL;
+ }
+ if (sc->sc_irq_res) {
+ bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ }
+ if (sc->sc_io_res) {
+ bus_release_resource(self, SYS_RES_MEMORY, PCI_CBMEM,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+ usb_bus_mem_free_all(&sc->sc_bus, &ohci_iterate_hw_softc);
+
+ return (0);
+}
+
+static device_method_t ohci_pci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ohci_pci_probe),
+ DEVMETHOD(device_attach, ohci_pci_attach),
+ DEVMETHOD(device_detach, ohci_pci_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+ DEVMETHOD(usb_take_controller, ohci_pci_take_controller),
+
+ DEVMETHOD_END
+};
+
+static driver_t ohci_driver = {
+ .name = "ohci",
+ .methods = ohci_pci_methods,
+ .size = sizeof(struct ohci_softc),
+};
+
+DRIVER_MODULE(ohci, pci, ohci_driver, 0, 0);
+MODULE_DEPEND(ohci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/ohcireg.h b/sys/dev/usb/controller/ohcireg.h
new file mode 100644
index 000000000000..2a904840bc1a
--- /dev/null
+++ b/sys/dev/usb/controller/ohcireg.h
@@ -0,0 +1,125 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _OHCIREG_H_
+#define _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 /* HostControllerFunctionalStat
+ * e */
+#define OHCI_HCFS_RESET 0x00000000
+#define OHCI_HCFS_RESUME 0x00000040
+#define OHCI_HCFS_OPERATIONAL 0x00000080
+#define OHCI_HCFS_SUSPEND 0x000000c0
+#define OHCI_IR 0x00000100 /* Interrupt Routing */
+#define OHCI_RWC 0x00000200 /* Remote Wakeup Connected */
+#define OHCI_RWE 0x00000400 /* Remote Wakeup Enabled */
+#define OHCI_COMMAND_STATUS 0x08
+#define OHCI_HCR 0x00000001 /* Host Controller Reset */
+#define OHCI_CLF 0x00000002 /* Control List Filled */
+#define OHCI_BLF 0x00000004 /* Bulk List Filled */
+#define OHCI_OCR 0x00000008 /* Ownership Change Request */
+#define OHCI_SOC_MASK 0x00030000 /* Scheduling Overrun Count */
+#define OHCI_INTERRUPT_STATUS 0x0c
+#define OHCI_SO 0x00000001 /* Scheduling Overrun */
+#define OHCI_WDH 0x00000002 /* Writeback Done Head */
+#define OHCI_SF 0x00000004 /* Start of Frame */
+#define OHCI_RD 0x00000008 /* Resume Detected */
+#define OHCI_UE 0x00000010 /* Unrecoverable Error */
+#define OHCI_FNO 0x00000020 /* Frame Number Overflow */
+#define OHCI_RHSC 0x00000040 /* Root Hub Status Change */
+#define OHCI_OC 0x40000000 /* Ownership Change */
+#define OHCI_MIE 0x80000000 /* Master Interrupt Enable */
+#define OHCI_INTERRUPT_ENABLE 0x10
+#define OHCI_INTERRUPT_DISABLE 0x14
+#define OHCI_HCCA 0x18
+#define OHCI_PERIOD_CURRENT_ED 0x1c
+#define OHCI_CONTROL_HEAD_ED 0x20
+#define OHCI_CONTROL_CURRENT_ED 0x24
+#define OHCI_BULK_HEAD_ED 0x28
+#define OHCI_BULK_CURRENT_ED 0x2c
+#define OHCI_DONE_HEAD 0x30
+#define OHCI_FM_INTERVAL 0x34
+#define OHCI_GET_IVAL(s) ((s) & 0x3fff)
+#define OHCI_GET_FSMPS(s) (((s) >> 16) & 0x7fff)
+#define OHCI_FIT 0x80000000
+#define OHCI_FM_REMAINING 0x38
+#define OHCI_FM_NUMBER 0x3c
+#define OHCI_PERIODIC_START 0x40
+#define OHCI_LS_THRESHOLD 0x44
+#define OHCI_RH_DESCRIPTOR_A 0x48
+#define OHCI_GET_NDP(s) ((s) & 0xff)
+#define OHCI_PSM 0x0100 /* Power Switching Mode */
+#define OHCI_NPS 0x0200 /* No Power Switching */
+#define OHCI_DT 0x0400 /* Device Type */
+#define OHCI_OCPM 0x0800 /* Overcurrent Protection Mode */
+#define OHCI_NOCP 0x1000 /* No Overcurrent Protection */
+#define OHCI_GET_POTPGT(s) ((s) >> 24)
+#define OHCI_RH_DESCRIPTOR_B 0x4c
+#define OHCI_RH_STATUS 0x50
+#define OHCI_LPS 0x00000001 /* Local Power Status */
+#define OHCI_OCI 0x00000002 /* OverCurrent Indicator */
+#define OHCI_DRWE 0x00008000 /* Device Remote Wakeup Enable */
+#define OHCI_LPSC 0x00010000 /* Local Power Status Change */
+#define OHCI_CCIC 0x00020000 /* OverCurrent Indicator
+ * Change */
+#define OHCI_CRWE 0x80000000 /* Clear Remote Wakeup Enable */
+#define OHCI_RH_PORT_STATUS(n) (0x50 + ((n)*4)) /* 1 based indexing */
+
+#define OHCI_LES (OHCI_PLE | OHCI_IE | OHCI_CLE | OHCI_BLE)
+#define OHCI_ALL_INTRS (OHCI_SO | OHCI_WDH | OHCI_SF | \
+ OHCI_RD | OHCI_UE | OHCI_FNO | \
+ OHCI_RHSC | OHCI_OC)
+#define OHCI_NORMAL_INTRS (OHCI_WDH | OHCI_RD | OHCI_UE | OHCI_RHSC)
+
+#define OHCI_FSMPS(i) (((i-210)*6/7) << 16)
+#define OHCI_PERIODIC(i) ((i)*9/10)
+
+#endif /* _OHCIREG_H_ */
diff --git a/sys/dev/usb/controller/uhci.c b/sys/dev/usb/controller/uhci.c
new file mode 100644
index 000000000000..c9143d438ac8
--- /dev/null
+++ b/sys/dev/usb/controller/uhci.c
@@ -0,0 +1,3159 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * USB 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
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#define USB_DEBUG_VAR uhcidebug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#include <dev/usb/controller/uhci.h>
+#include <dev/usb/controller/uhcireg.h>
+
+#define alt_next next
+#define UHCI_BUS2SC(bus) \
+ __containerof(bus, uhci_softc_t, sc_bus)
+
+#ifdef USB_DEBUG
+static int uhcidebug = 0;
+static int uhcinoloop = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uhci, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB uhci");
+SYSCTL_INT(_hw_usb_uhci, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &uhcidebug, 0, "uhci debug level");
+SYSCTL_INT(_hw_usb_uhci, OID_AUTO, loop, CTLFLAG_RWTUN,
+ &uhcinoloop, 0, "uhci noloop");
+
+static void uhci_dumpregs(uhci_softc_t *sc);
+static void uhci_dump_tds(uhci_td_t *td);
+
+#endif
+
+#define UBARR(sc) bus_space_barrier((sc)->sc_io_tag, (sc)->sc_io_hdl, 0, (sc)->sc_io_size, \
+ BUS_SPACE_BARRIER_READ|BUS_SPACE_BARRIER_WRITE)
+#define UWRITE1(sc, r, x) \
+ do { UBARR(sc); bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \
+ } while (/*CONSTCOND*/0)
+#define UWRITE2(sc, r, x) \
+ do { UBARR(sc); bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \
+ } while (/*CONSTCOND*/0)
+#define UWRITE4(sc, r, x) \
+ do { UBARR(sc); bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (r), (x)); \
+ } while (/*CONSTCOND*/0)
+#define UREAD1(sc, r) (UBARR(sc), bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+#define UREAD2(sc, r) (UBARR(sc), bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, (r)))
+#define UREAD4(sc, r) (UBARR(sc), bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, (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_INTR_ENDPT 1
+
+struct uhci_mem_layout {
+ struct usb_page_search buf_res;
+ struct usb_page_search fix_res;
+
+ struct usb_page_cache *buf_pc;
+ struct usb_page_cache *fix_pc;
+
+ uint32_t buf_offset;
+
+ uint16_t max_frame_size;
+};
+
+struct uhci_std_temp {
+ struct uhci_mem_layout ml;
+ uhci_td_t *td;
+ uhci_td_t *td_next;
+ uint32_t average;
+ uint32_t td_status;
+ uint32_t td_token;
+ uint32_t len;
+ uint16_t max_frame_size;
+ uint8_t shortpkt;
+ uint8_t setup_alt_next;
+ uint8_t last_frame;
+};
+
+static const struct usb_bus_methods uhci_bus_methods;
+static const struct usb_pipe_methods uhci_device_bulk_methods;
+static const struct usb_pipe_methods uhci_device_ctrl_methods;
+static const struct usb_pipe_methods uhci_device_intr_methods;
+static const struct usb_pipe_methods uhci_device_isoc_methods;
+
+static uint8_t uhci_restart(uhci_softc_t *sc);
+static void uhci_do_poll(struct usb_bus *);
+static void uhci_device_done(struct usb_xfer *, usb_error_t);
+static void uhci_transfer_intr_enqueue(struct usb_xfer *);
+static void uhci_timeout(void *);
+static uint8_t uhci_check_transfer(struct usb_xfer *);
+static void uhci_root_intr(uhci_softc_t *sc);
+
+void
+uhci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb)
+{
+ struct uhci_softc *sc = UHCI_BUS2SC(bus);
+ uint32_t i;
+
+ cb(bus, &sc->sc_hw.pframes_pc, &sc->sc_hw.pframes_pg,
+ sizeof(uint32_t) * UHCI_FRAMELIST_COUNT, UHCI_FRAMELIST_ALIGN);
+
+ cb(bus, &sc->sc_hw.ls_ctl_start_pc, &sc->sc_hw.ls_ctl_start_pg,
+ sizeof(uhci_qh_t), UHCI_QH_ALIGN);
+
+ cb(bus, &sc->sc_hw.fs_ctl_start_pc, &sc->sc_hw.fs_ctl_start_pg,
+ sizeof(uhci_qh_t), UHCI_QH_ALIGN);
+
+ cb(bus, &sc->sc_hw.bulk_start_pc, &sc->sc_hw.bulk_start_pg,
+ sizeof(uhci_qh_t), UHCI_QH_ALIGN);
+
+ cb(bus, &sc->sc_hw.last_qh_pc, &sc->sc_hw.last_qh_pg,
+ sizeof(uhci_qh_t), UHCI_QH_ALIGN);
+
+ cb(bus, &sc->sc_hw.last_td_pc, &sc->sc_hw.last_td_pg,
+ sizeof(uhci_td_t), UHCI_TD_ALIGN);
+
+ for (i = 0; i != UHCI_VFRAMELIST_COUNT; i++) {
+ cb(bus, sc->sc_hw.isoc_start_pc + i,
+ sc->sc_hw.isoc_start_pg + i,
+ sizeof(uhci_td_t), UHCI_TD_ALIGN);
+ }
+
+ for (i = 0; i != UHCI_IFRAMELIST_COUNT; i++) {
+ cb(bus, sc->sc_hw.intr_start_pc + i,
+ sc->sc_hw.intr_start_pg + i,
+ sizeof(uhci_qh_t), UHCI_QH_ALIGN);
+ }
+}
+
+static void
+uhci_mem_layout_init(struct uhci_mem_layout *ml, struct usb_xfer *xfer)
+{
+ ml->buf_pc = xfer->frbuffers + 0;
+ ml->fix_pc = xfer->buf_fixup;
+
+ ml->buf_offset = 0;
+
+ ml->max_frame_size = xfer->max_frame_size;
+}
+
+static void
+uhci_mem_layout_fixup(struct uhci_mem_layout *ml, struct uhci_td *td)
+{
+ usbd_get_page(ml->buf_pc, ml->buf_offset, &ml->buf_res);
+
+ if (ml->buf_res.length < td->len) {
+ /* need to do a fixup */
+
+ usbd_get_page(ml->fix_pc, 0, &ml->fix_res);
+
+ td->td_buffer = htole32(ml->fix_res.physaddr);
+
+ /*
+ * The UHCI driver cannot handle
+ * page crossings, so a fixup is
+ * needed:
+ *
+ * +----+----+ - - -
+ * | YYY|Y |
+ * +----+----+ - - -
+ * \ \
+ * \ \
+ * +----+
+ * |YYYY| (fixup)
+ * +----+
+ */
+
+ if ((td->td_token & htole32(UHCI_TD_PID)) ==
+ htole32(UHCI_TD_PID_IN)) {
+ td->fix_pc = ml->fix_pc;
+ usb_pc_cpu_invalidate(ml->fix_pc);
+
+ } else {
+ td->fix_pc = NULL;
+
+ /* copy data to fixup location */
+
+ usbd_copy_out(ml->buf_pc, ml->buf_offset,
+ ml->fix_res.buffer, td->len);
+
+ usb_pc_cpu_flush(ml->fix_pc);
+ }
+
+ /* prepare next fixup */
+
+ ml->fix_pc++;
+
+ } else {
+ td->td_buffer = htole32(ml->buf_res.physaddr);
+ td->fix_pc = NULL;
+ }
+
+ /* prepare next data location */
+
+ ml->buf_offset += td->len;
+}
+
+/*
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ */
+static uint8_t
+uhci_restart(uhci_softc_t *sc)
+{
+ struct usb_page_search buf_res;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ if (UREAD2(sc, UHCI_CMD) & UHCI_CMD_RS) {
+ DPRINTFN(2, "Already started\n");
+ return (0);
+ }
+
+ DPRINTFN(2, "Restarting\n");
+
+ usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res);
+
+ /* Reload fresh base address */
+ UWRITE4(sc, UHCI_FLBASEADDR, buf_res.physaddr);
+
+ /*
+ * Assume 64 byte packets at frame end and start HC controller:
+ */
+ UHCICMD(sc, (UHCI_CMD_MAXP | UHCI_CMD_RS));
+
+ /* wait 10 milliseconds */
+
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+
+ /* check that controller has started */
+
+ if (UREAD2(sc, UHCI_STS) & UHCI_STS_HCH) {
+ DPRINTFN(2, "Failed\n");
+ return (1);
+ }
+ return (0);
+}
+
+void
+uhci_reset(uhci_softc_t *sc)
+{
+ uint16_t n;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTF("resetting the HC\n");
+
+ /* disable interrupts */
+
+ UWRITE2(sc, UHCI_INTR, 0);
+
+ /* global reset */
+
+ UHCICMD(sc, UHCI_CMD_GRESET);
+
+ /* wait */
+
+ usb_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(USB_BUS_RESET_DELAY));
+
+ /* terminate all transfers */
+
+ UHCICMD(sc, UHCI_CMD_HCRESET);
+
+ /* the reset bit goes low when the controller is done */
+
+ n = UHCI_RESET_TIMEOUT;
+ while (n--) {
+ /* wait one millisecond */
+
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+
+ if (!(UREAD2(sc, UHCI_CMD) & UHCI_CMD_HCRESET)) {
+ goto done_1;
+ }
+ }
+
+ device_printf(sc->sc_bus.bdev,
+ "controller did not reset\n");
+
+done_1:
+
+ n = 10;
+ while (n--) {
+ /* wait one millisecond */
+
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 1000);
+
+ /* check if HC is stopped */
+ if (UREAD2(sc, UHCI_STS) & UHCI_STS_HCH) {
+ goto done_2;
+ }
+ }
+
+ device_printf(sc->sc_bus.bdev,
+ "controller did not stop\n");
+
+done_2:
+
+ /* reset frame number */
+ UWRITE2(sc, UHCI_FRNUM, 0);
+ /* set default SOF value */
+ UWRITE1(sc, UHCI_SOF, 0x40);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* stop root interrupt */
+ usb_callout_drain(&sc->sc_root_intr);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+}
+
+static void
+uhci_start(uhci_softc_t *sc)
+{
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTFN(2, "enabling\n");
+
+ /* enable interrupts */
+
+ UWRITE2(sc, UHCI_INTR,
+ (UHCI_INTR_TOCRCIE |
+ UHCI_INTR_RIE |
+ UHCI_INTR_IOCE |
+ UHCI_INTR_SPIE));
+
+ if (uhci_restart(sc)) {
+ device_printf(sc->sc_bus.bdev,
+ "cannot start HC controller\n");
+ }
+
+ /* start root interrupt */
+ uhci_root_intr(sc);
+}
+
+static struct uhci_qh *
+uhci_init_qh(struct usb_page_cache *pc)
+{
+ struct usb_page_search buf_res;
+ struct uhci_qh *qh;
+
+ usbd_get_page(pc, 0, &buf_res);
+
+ qh = buf_res.buffer;
+
+ qh->qh_self =
+ htole32(buf_res.physaddr) |
+ htole32(UHCI_PTR_QH);
+
+ qh->page_cache = pc;
+
+ return (qh);
+}
+
+static struct uhci_td *
+uhci_init_td(struct usb_page_cache *pc)
+{
+ struct usb_page_search buf_res;
+ struct uhci_td *td;
+
+ usbd_get_page(pc, 0, &buf_res);
+
+ td = buf_res.buffer;
+
+ td->td_self =
+ htole32(buf_res.physaddr) |
+ htole32(UHCI_PTR_TD);
+
+ td->page_cache = pc;
+
+ return (td);
+}
+
+usb_error_t
+uhci_init(uhci_softc_t *sc)
+{
+ uint16_t bit;
+ uint16_t x;
+ uint16_t y;
+
+ DPRINTF("start\n");
+
+ usb_callout_init_mtx(&sc->sc_root_intr, &sc->sc_bus.bus_mtx, 0);
+
+#ifdef USB_DEBUG
+ if (uhcidebug > 2) {
+ uhci_dumpregs(sc);
+ }
+#endif
+ /*
+ * Setup QH's
+ */
+ sc->sc_ls_ctl_p_last =
+ uhci_init_qh(&sc->sc_hw.ls_ctl_start_pc);
+
+ sc->sc_fs_ctl_p_last =
+ uhci_init_qh(&sc->sc_hw.fs_ctl_start_pc);
+
+ sc->sc_bulk_p_last =
+ uhci_init_qh(&sc->sc_hw.bulk_start_pc);
+#if 0
+ sc->sc_reclaim_qh_p =
+ sc->sc_fs_ctl_p_last;
+#else
+ /* setup reclaim looping point */
+ sc->sc_reclaim_qh_p =
+ sc->sc_bulk_p_last;
+#endif
+
+ sc->sc_last_qh_p =
+ uhci_init_qh(&sc->sc_hw.last_qh_pc);
+
+ sc->sc_last_td_p =
+ uhci_init_td(&sc->sc_hw.last_td_pc);
+
+ for (x = 0; x != UHCI_VFRAMELIST_COUNT; x++) {
+ sc->sc_isoc_p_last[x] =
+ uhci_init_td(sc->sc_hw.isoc_start_pc + x);
+ }
+
+ for (x = 0; x != UHCI_IFRAMELIST_COUNT; x++) {
+ sc->sc_intr_p_last[x] =
+ uhci_init_qh(sc->sc_hw.intr_start_pc + x);
+ }
+
+ /*
+ * the QHs are arranged to give poll intervals that are
+ * powers of 2 times 1ms
+ */
+ bit = UHCI_IFRAMELIST_COUNT / 2;
+ while (bit) {
+ x = bit;
+ while (x & bit) {
+ uhci_qh_t *qh_x;
+ uhci_qh_t *qh_y;
+
+ y = (x ^ bit) | (bit / 2);
+
+ /*
+ * the next QH has half the poll interval
+ */
+ qh_x = sc->sc_intr_p_last[x];
+ qh_y = sc->sc_intr_p_last[y];
+
+ qh_x->h_next = NULL;
+ qh_x->qh_h_next = qh_y->qh_self;
+ qh_x->e_next = NULL;
+ qh_x->qh_e_next = htole32(UHCI_PTR_T);
+ x++;
+ }
+ bit >>= 1;
+ }
+
+ if (1) {
+ uhci_qh_t *qh_ls;
+ uhci_qh_t *qh_intr;
+
+ qh_ls = sc->sc_ls_ctl_p_last;
+ qh_intr = sc->sc_intr_p_last[0];
+
+ /* start QH for interrupt traffic */
+ qh_intr->h_next = qh_ls;
+ qh_intr->qh_h_next = qh_ls->qh_self;
+ qh_intr->e_next = 0;
+ qh_intr->qh_e_next = htole32(UHCI_PTR_T);
+ }
+ for (x = 0; x != UHCI_VFRAMELIST_COUNT; x++) {
+ uhci_td_t *td_x;
+ uhci_qh_t *qh_intr;
+
+ td_x = sc->sc_isoc_p_last[x];
+ qh_intr = sc->sc_intr_p_last[x | (UHCI_IFRAMELIST_COUNT / 2)];
+
+ /* start TD for isochronous traffic */
+ td_x->next = NULL;
+ td_x->td_next = qh_intr->qh_self;
+ td_x->td_status = htole32(UHCI_TD_IOS);
+ td_x->td_token = htole32(0);
+ td_x->td_buffer = htole32(0);
+ }
+
+ if (1) {
+ uhci_qh_t *qh_ls;
+ uhci_qh_t *qh_fs;
+
+ qh_ls = sc->sc_ls_ctl_p_last;
+ qh_fs = sc->sc_fs_ctl_p_last;
+
+ /* start QH where low speed control traffic will be queued */
+ qh_ls->h_next = qh_fs;
+ qh_ls->qh_h_next = qh_fs->qh_self;
+ qh_ls->e_next = 0;
+ qh_ls->qh_e_next = htole32(UHCI_PTR_T);
+ }
+ if (1) {
+ uhci_qh_t *qh_ctl;
+ uhci_qh_t *qh_blk;
+ uhci_qh_t *qh_lst;
+ uhci_td_t *td_lst;
+
+ qh_ctl = sc->sc_fs_ctl_p_last;
+ qh_blk = sc->sc_bulk_p_last;
+
+ /* start QH where full speed control traffic will be queued */
+ qh_ctl->h_next = qh_blk;
+ qh_ctl->qh_h_next = qh_blk->qh_self;
+ qh_ctl->e_next = 0;
+ qh_ctl->qh_e_next = htole32(UHCI_PTR_T);
+
+ qh_lst = sc->sc_last_qh_p;
+
+ /* start QH where bulk traffic will be queued */
+ qh_blk->h_next = qh_lst;
+ qh_blk->qh_h_next = qh_lst->qh_self;
+ qh_blk->e_next = 0;
+ qh_blk->qh_e_next = htole32(UHCI_PTR_T);
+
+ td_lst = sc->sc_last_td_p;
+
+ /* end QH which is used for looping the QHs */
+ qh_lst->h_next = 0;
+ qh_lst->qh_h_next = htole32(UHCI_PTR_T); /* end of QH chain */
+ qh_lst->e_next = td_lst;
+ qh_lst->qh_e_next = td_lst->td_self;
+
+ /*
+ * end TD which hangs from the last QH, to avoid a bug in the PIIX
+ * that makes it run berserk otherwise
+ */
+ td_lst->next = 0;
+ td_lst->td_next = htole32(UHCI_PTR_T);
+ td_lst->td_status = htole32(0); /* inactive */
+ td_lst->td_token = htole32(0);
+ td_lst->td_buffer = htole32(0);
+ }
+ if (1) {
+ struct usb_page_search buf_res;
+ uint32_t *pframes;
+
+ usbd_get_page(&sc->sc_hw.pframes_pc, 0, &buf_res);
+
+ pframes = buf_res.buffer;
+
+ /*
+ * Setup UHCI framelist
+ *
+ * Execution order:
+ *
+ * pframes -> full speed isochronous -> interrupt QH's -> low
+ * speed control -> full speed control -> bulk transfers
+ *
+ */
+
+ for (x = 0; x != UHCI_FRAMELIST_COUNT; x++) {
+ pframes[x] =
+ sc->sc_isoc_p_last[x % UHCI_VFRAMELIST_COUNT]->td_self;
+ }
+ }
+ /* flush all cache into memory */
+
+ usb_bus_mem_flush_all(&sc->sc_bus, &uhci_iterate_hw_softc);
+
+ /* set up the bus struct */
+ sc->sc_bus.methods = &uhci_bus_methods;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ /* reset the controller */
+ uhci_reset(sc);
+
+ /* start the controller */
+ uhci_start(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch lost interrupts */
+ uhci_do_poll(&sc->sc_bus);
+
+ return (0);
+}
+
+static void
+uhci_suspend(uhci_softc_t *sc)
+{
+#ifdef USB_DEBUG
+ if (uhcidebug > 2) {
+ uhci_dumpregs(sc);
+ }
+#endif
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* stop the controller */
+
+ uhci_reset(sc);
+
+ /* enter global suspend */
+
+ UHCICMD(sc, UHCI_CMD_EGSM);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+uhci_resume(uhci_softc_t *sc)
+{
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* reset the controller */
+
+ uhci_reset(sc);
+
+ /* force global resume */
+
+ UHCICMD(sc, UHCI_CMD_FGR);
+
+ /* and start traffic again */
+
+ uhci_start(sc);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+#ifdef USB_DEBUG
+ if (uhcidebug > 2)
+ uhci_dumpregs(sc);
+#endif
+
+ /* catch lost interrupts */
+ uhci_do_poll(&sc->sc_bus);
+}
+
+#ifdef USB_DEBUG
+static void
+uhci_dumpregs(uhci_softc_t *sc)
+{
+ DPRINTFN(0, "%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));
+}
+
+static uint8_t
+uhci_dump_td(uhci_td_t *p)
+{
+ uint32_t td_next;
+ uint32_t td_status;
+ uint32_t td_token;
+ uint8_t temp;
+
+ usb_pc_cpu_invalidate(p->page_cache);
+
+ td_next = le32toh(p->td_next);
+ td_status = le32toh(p->td_status);
+ td_token = le32toh(p->td_token);
+
+ /*
+ * Check whether the link pointer in this TD marks the link pointer
+ * as end of queue:
+ */
+ temp = ((td_next & UHCI_PTR_T) || (td_next == 0));
+
+ printf("TD(%p) at 0x%08x = link=0x%08x status=0x%08x "
+ "token=0x%08x buffer=0x%08x\n",
+ p,
+ le32toh(p->td_self),
+ td_next,
+ td_status,
+ td_token,
+ le32toh(p->td_buffer));
+
+ printf("TD(%p) td_next=%s%s%s td_status=%s%s%s%s%s%s%s%s%s%s%s, errcnt=%d, actlen=%d pid=%02x,"
+ "addr=%d,endpt=%d,D=%d,maxlen=%d\n",
+ p,
+ (td_next & 1) ? "-T" : "",
+ (td_next & 2) ? "-Q" : "",
+ (td_next & 4) ? "-VF" : "",
+ (td_status & UHCI_TD_BITSTUFF) ? "-BITSTUFF" : "",
+ (td_status & UHCI_TD_CRCTO) ? "-CRCTO" : "",
+ (td_status & UHCI_TD_NAK) ? "-NAK" : "",
+ (td_status & UHCI_TD_BABBLE) ? "-BABBLE" : "",
+ (td_status & UHCI_TD_DBUFFER) ? "-DBUFFER" : "",
+ (td_status & UHCI_TD_STALLED) ? "-STALLED" : "",
+ (td_status & UHCI_TD_ACTIVE) ? "-ACTIVE" : "",
+ (td_status & UHCI_TD_IOC) ? "-IOC" : "",
+ (td_status & UHCI_TD_IOS) ? "-IOS" : "",
+ (td_status & UHCI_TD_LS) ? "-LS" : "",
+ (td_status & UHCI_TD_SPD) ? "-SPD" : "",
+ UHCI_TD_GET_ERRCNT(td_status),
+ UHCI_TD_GET_ACTLEN(td_status),
+ UHCI_TD_GET_PID(td_token),
+ UHCI_TD_GET_DEVADDR(td_token),
+ UHCI_TD_GET_ENDPT(td_token),
+ UHCI_TD_GET_DT(td_token),
+ UHCI_TD_GET_MAXLEN(td_token));
+
+ return (temp);
+}
+
+static uint8_t
+uhci_dump_qh(uhci_qh_t *sqh)
+{
+ uint8_t temp;
+ uint32_t qh_h_next;
+ uint32_t qh_e_next;
+
+ usb_pc_cpu_invalidate(sqh->page_cache);
+
+ qh_h_next = le32toh(sqh->qh_h_next);
+ qh_e_next = le32toh(sqh->qh_e_next);
+
+ DPRINTFN(0, "QH(%p) at 0x%08x: h_next=0x%08x e_next=0x%08x\n", sqh,
+ le32toh(sqh->qh_self), qh_h_next, qh_e_next);
+
+ temp = ((((sqh->h_next != NULL) && !(qh_h_next & UHCI_PTR_T)) ? 1 : 0) |
+ (((sqh->e_next != NULL) && !(qh_e_next & UHCI_PTR_T)) ? 2 : 0));
+
+ return (temp);
+}
+
+static void
+uhci_dump_all(uhci_softc_t *sc)
+{
+ uhci_dumpregs(sc);
+ uhci_dump_qh(sc->sc_ls_ctl_p_last);
+ uhci_dump_qh(sc->sc_fs_ctl_p_last);
+ uhci_dump_qh(sc->sc_bulk_p_last);
+ uhci_dump_qh(sc->sc_last_qh_p);
+}
+
+static void
+uhci_dump_tds(uhci_td_t *td)
+{
+ for (;
+ td != NULL;
+ td = td->obj_next) {
+ if (uhci_dump_td(td)) {
+ break;
+ }
+ }
+}
+
+#endif
+
+/*
+ * Let the last QH loop back to the full 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.
+ */
+static void
+uhci_add_loop(uhci_softc_t *sc)
+{
+ struct uhci_qh *qh_lst;
+ struct uhci_qh *qh_rec;
+
+#ifdef USB_DEBUG
+ if (uhcinoloop) {
+ return;
+ }
+#endif
+ if (++(sc->sc_loops) == 1) {
+ DPRINTFN(6, "add\n");
+
+ qh_lst = sc->sc_last_qh_p;
+ qh_rec = sc->sc_reclaim_qh_p;
+
+ /* NOTE: we don't loop back the soft pointer */
+
+ qh_lst->qh_h_next = qh_rec->qh_self;
+ usb_pc_cpu_flush(qh_lst->page_cache);
+ }
+}
+
+static void
+uhci_rem_loop(uhci_softc_t *sc)
+{
+ struct uhci_qh *qh_lst;
+
+#ifdef USB_DEBUG
+ if (uhcinoloop) {
+ return;
+ }
+#endif
+ if (--(sc->sc_loops) == 0) {
+ DPRINTFN(6, "remove\n");
+
+ qh_lst = sc->sc_last_qh_p;
+ qh_lst->qh_h_next = htole32(UHCI_PTR_T);
+ usb_pc_cpu_flush(qh_lst->page_cache);
+ }
+}
+
+static void
+uhci_transfer_intr_enqueue(struct usb_xfer *xfer)
+{
+ /* check for early completion */
+ if (uhci_check_transfer(xfer)) {
+ return;
+ }
+ /* put transfer on interrupt queue */
+ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usbd_transfer_timeout_ms(xfer, &uhci_timeout, xfer->timeout);
+ }
+}
+
+#define UHCI_APPEND_TD(std,last) (last) = _uhci_append_td(std,last)
+static uhci_td_t *
+_uhci_append_td(uhci_td_t *std, uhci_td_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->next = last->next;
+ std->td_next = last->td_next;
+
+ std->prev = last;
+
+ usb_pc_cpu_flush(std->page_cache);
+
+ /*
+ * the last->next->prev is never followed: std->next->prev = std;
+ */
+ last->next = std;
+ last->td_next = std->td_self;
+
+ usb_pc_cpu_flush(last->page_cache);
+
+ return (std);
+}
+
+#define UHCI_APPEND_QH(sqh,last) (last) = _uhci_append_qh(sqh,last)
+static uhci_qh_t *
+_uhci_append_qh(uhci_qh_t *sqh, uhci_qh_t *last)
+{
+ DPRINTFN(11, "%p to %p\n", sqh, last);
+
+ if (sqh->h_prev != NULL) {
+ /* should not happen */
+ DPRINTFN(0, "QH already linked!\n");
+ return (last);
+ }
+ /* (sc->sc_bus.mtx) must be locked */
+
+ sqh->h_next = last->h_next;
+ sqh->qh_h_next = last->qh_h_next;
+
+ sqh->h_prev = last;
+
+ usb_pc_cpu_flush(sqh->page_cache);
+
+ /*
+ * The "last->h_next->h_prev" is never followed:
+ *
+ * "sqh->h_next->h_prev" = sqh;
+ */
+
+ last->h_next = sqh;
+ last->qh_h_next = sqh->qh_self;
+
+ usb_pc_cpu_flush(last->page_cache);
+
+ return (sqh);
+}
+
+/**/
+
+#define UHCI_REMOVE_TD(std,last) (last) = _uhci_remove_td(std,last)
+static uhci_td_t *
+_uhci_remove_td(uhci_td_t *std, uhci_td_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", std, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ std->prev->next = std->next;
+ std->prev->td_next = std->td_next;
+
+ usb_pc_cpu_flush(std->prev->page_cache);
+
+ if (std->next) {
+ std->next->prev = std->prev;
+ usb_pc_cpu_flush(std->next->page_cache);
+ }
+ return ((last == std) ? std->prev : last);
+}
+
+#define UHCI_REMOVE_QH(sqh,last) (last) = _uhci_remove_qh(sqh,last)
+static uhci_qh_t *
+_uhci_remove_qh(uhci_qh_t *sqh, uhci_qh_t *last)
+{
+ DPRINTFN(11, "%p from %p\n", sqh, last);
+
+ /* (sc->sc_bus.mtx) must be locked */
+
+ /* only remove if not removed from a queue */
+ if (sqh->h_prev) {
+ sqh->h_prev->h_next = sqh->h_next;
+ sqh->h_prev->qh_h_next = sqh->qh_h_next;
+
+ usb_pc_cpu_flush(sqh->h_prev->page_cache);
+
+ if (sqh->h_next) {
+ sqh->h_next->h_prev = sqh->h_prev;
+ usb_pc_cpu_flush(sqh->h_next->page_cache);
+ }
+ last = ((last == sqh) ? sqh->h_prev : last);
+
+ sqh->h_prev = 0;
+
+ usb_pc_cpu_flush(sqh->page_cache);
+ }
+ return (last);
+}
+
+static void
+uhci_isoc_done(uhci_softc_t *sc, struct usb_xfer *xfer)
+{
+ struct usb_page_search res;
+ uint32_t nframes = xfer->nframes;
+ uint32_t status;
+ uint32_t offset = 0;
+ uint32_t *plen = xfer->frlengths;
+ uint16_t len = 0;
+ uhci_td_t *td = xfer->td_transfer_first;
+ uhci_td_t **pp_last = &sc->sc_isoc_p_last[xfer->qh_pos];
+
+ DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
+ xfer, xfer->endpoint);
+
+ /* sync any DMA memory before doing fixups */
+
+ usb_bdma_post_sync(xfer);
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_p_last[0];
+ }
+#ifdef USB_DEBUG
+ if (uhcidebug > 5) {
+ DPRINTF("isoc TD\n");
+ uhci_dump_td(td);
+ }
+#endif
+ usb_pc_cpu_invalidate(td->page_cache);
+ status = le32toh(td->td_status);
+
+ len = UHCI_TD_GET_ACTLEN(status);
+
+ if (len > *plen) {
+ len = *plen;
+ }
+ if (td->fix_pc) {
+ usbd_get_page(td->fix_pc, 0, &res);
+
+ /* copy data from fixup location to real location */
+
+ usb_pc_cpu_invalidate(td->fix_pc);
+
+ usbd_copy_in(xfer->frbuffers, offset,
+ res.buffer, len);
+ }
+ offset += *plen;
+
+ *plen = len;
+
+ /* remove TD from schedule */
+ UHCI_REMOVE_TD(td, *pp_last);
+
+ pp_last++;
+ plen++;
+ td = td->obj_next;
+ }
+
+ xfer->aframes = xfer->nframes;
+}
+
+static usb_error_t
+uhci_non_isoc_done_sub(struct usb_xfer *xfer)
+{
+ struct usb_page_search res;
+ uhci_td_t *td;
+ uhci_td_t *td_alt_next;
+ uint32_t status;
+ uint32_t token;
+ uint16_t len;
+
+ td = xfer->td_transfer_cache;
+ td_alt_next = td->alt_next;
+
+ if (xfer->aframes != xfer->nframes) {
+ usbd_xfer_set_frame_len(xfer, xfer->aframes, 0);
+ }
+ while (1) {
+ usb_pc_cpu_invalidate(td->page_cache);
+ status = le32toh(td->td_status);
+ token = le32toh(td->td_token);
+
+ /*
+ * Verify the status and add
+ * up the actual length:
+ */
+
+ len = UHCI_TD_GET_ACTLEN(status);
+ if (len > td->len) {
+ /* should not happen */
+ DPRINTF("Invalid status length, "
+ "0x%04x/0x%04x bytes\n", len, td->len);
+ status |= UHCI_TD_STALLED;
+
+ } else if ((xfer->aframes != xfer->nframes) && (len > 0)) {
+ if (td->fix_pc) {
+ usbd_get_page(td->fix_pc, 0, &res);
+
+ /*
+ * copy data from fixup location to real
+ * location
+ */
+
+ usb_pc_cpu_invalidate(td->fix_pc);
+
+ usbd_copy_in(xfer->frbuffers + xfer->aframes,
+ xfer->frlengths[xfer->aframes], res.buffer, len);
+ }
+ /* update actual length */
+
+ xfer->frlengths[xfer->aframes] += len;
+ }
+ /* Check for last transfer */
+ if (((void *)td) == xfer->td_transfer_last) {
+ td = NULL;
+ break;
+ }
+ if (status & UHCI_TD_STALLED) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len != td->len) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ td = td->alt_next;
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ break;
+ }
+ td = td->obj_next;
+
+ if (td->alt_next != td_alt_next) {
+ /* this USB frame is complete */
+ break;
+ }
+ }
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ /* update data toggle */
+
+ xfer->endpoint->toggle_next = (token & UHCI_TD_SET_DT(1)) ? 0 : 1;
+
+#ifdef USB_DEBUG
+ if (status & UHCI_TD_ERROR) {
+ DPRINTFN(11, "error, addr=%d, endpt=0x%02x, frame=0x%02x "
+ "status=%s%s%s%s%s%s%s%s%s%s%s\n",
+ xfer->address, xfer->endpointno, xfer->aframes,
+ (status & UHCI_TD_BITSTUFF) ? "[BITSTUFF]" : "",
+ (status & UHCI_TD_CRCTO) ? "[CRCTO]" : "",
+ (status & UHCI_TD_NAK) ? "[NAK]" : "",
+ (status & UHCI_TD_BABBLE) ? "[BABBLE]" : "",
+ (status & UHCI_TD_DBUFFER) ? "[DBUFFER]" : "",
+ (status & UHCI_TD_STALLED) ? "[STALLED]" : "",
+ (status & UHCI_TD_ACTIVE) ? "[ACTIVE]" : "[NOT_ACTIVE]",
+ (status & UHCI_TD_IOC) ? "[IOC]" : "",
+ (status & UHCI_TD_IOS) ? "[IOS]" : "",
+ (status & UHCI_TD_LS) ? "[LS]" : "",
+ (status & UHCI_TD_SPD) ? "[SPD]" : "");
+ }
+#endif
+ if (status & UHCI_TD_STALLED) {
+ /* try to separate I/O errors from STALL */
+ if (UHCI_TD_GET_ERRCNT(status) == 0)
+ return (USB_ERR_IOERROR);
+ return (USB_ERR_STALLED);
+ }
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+uhci_non_isoc_done(struct usb_xfer *xfer)
+{
+ usb_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
+ xfer, xfer->endpoint);
+
+#ifdef USB_DEBUG
+ if (uhcidebug > 10) {
+ uhci_dump_tds(xfer->td_transfer_first);
+ }
+#endif
+
+ /* sync any DMA memory before doing fixups */
+
+ usb_bdma_post_sync(xfer);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ err = uhci_non_isoc_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+ err = uhci_non_isoc_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+ err = uhci_non_isoc_done_sub(xfer);
+ }
+done:
+ uhci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * uhci_check_transfer_sub
+ *
+ * The main purpose of this function is to update the data-toggle
+ * in case it is wrong.
+ *------------------------------------------------------------------------*/
+static void
+uhci_check_transfer_sub(struct usb_xfer *xfer)
+{
+ uhci_qh_t *qh;
+ uhci_td_t *td;
+ uhci_td_t *td_alt_next;
+
+ uint32_t td_token;
+ uint32_t td_self;
+
+ td = xfer->td_transfer_cache;
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ td_token = td->obj_next->td_token;
+ td = td->alt_next;
+ xfer->td_transfer_cache = td;
+ td_self = td->td_self;
+ td_alt_next = td->alt_next;
+
+ if (xfer->flags_int.control_xfr)
+ goto skip; /* don't touch the DT value! */
+
+ if (!((td->td_token ^ td_token) & htole32(UHCI_TD_SET_DT(1))))
+ goto skip; /* data toggle has correct value */
+
+ /*
+ * The data toggle is wrong and we need to toggle it !
+ */
+ while (1) {
+ td->td_token ^= htole32(UHCI_TD_SET_DT(1));
+ usb_pc_cpu_flush(td->page_cache);
+
+ if (td == xfer->td_transfer_last) {
+ /* last transfer */
+ break;
+ }
+ td = td->obj_next;
+
+ if (td->alt_next != td_alt_next) {
+ /* next frame */
+ break;
+ }
+ }
+skip:
+
+ /* update the QH */
+ qh->qh_e_next = td_self;
+ usb_pc_cpu_flush(qh->page_cache);
+
+ DPRINTFN(13, "xfer=%p following alt next\n", xfer);
+}
+
+/*------------------------------------------------------------------------*
+ * uhci_check_transfer
+ *
+ * Return values:
+ * 0: USB transfer is not finished
+ * Else: USB transfer is finished
+ *------------------------------------------------------------------------*/
+static uint8_t
+uhci_check_transfer(struct usb_xfer *xfer)
+{
+ uint32_t status;
+ uhci_td_t *td;
+
+ DPRINTFN(16, "xfer=%p checking transfer\n", xfer);
+
+ if (xfer->endpoint->methods == &uhci_device_isoc_methods) {
+ /* isochronous transfer */
+
+ td = xfer->td_transfer_last;
+
+ usb_pc_cpu_invalidate(td->page_cache);
+ status = le32toh(td->td_status);
+
+ /* check also if the first is complete */
+
+ td = xfer->td_transfer_first;
+
+ usb_pc_cpu_invalidate(td->page_cache);
+ status |= le32toh(td->td_status);
+
+ if (!(status & UHCI_TD_ACTIVE)) {
+ uhci_device_done(xfer, USB_ERR_NORMAL_COMPLETION);
+ goto transferred;
+ }
+ } else {
+ /* non-isochronous transfer */
+
+ /*
+ * check whether there is an error somewhere
+ * in the middle, or whether there was a short
+ * packet (SPD and not ACTIVE)
+ */
+ td = xfer->td_transfer_cache;
+
+ while (1) {
+ usb_pc_cpu_invalidate(td->page_cache);
+ status = le32toh(td->td_status);
+
+ /*
+ * if there is an active TD the transfer isn't done
+ */
+ if (status & UHCI_TD_ACTIVE) {
+ /* update cache */
+ xfer->td_transfer_cache = td;
+ goto done;
+ }
+ /*
+ * last transfer descriptor makes the transfer done
+ */
+ if (((void *)td) == xfer->td_transfer_last) {
+ break;
+ }
+ /*
+ * any kind of error makes the transfer done
+ */
+ if (status & UHCI_TD_STALLED) {
+ break;
+ }
+ /*
+ * check if we reached the last packet
+ * or if there is a short packet:
+ */
+ if ((td->td_next == htole32(UHCI_PTR_T)) ||
+ (UHCI_TD_GET_ACTLEN(status) < td->len)) {
+ if (xfer->flags_int.short_frames_ok) {
+ /* follow alt next */
+ if (td->alt_next) {
+ /* update cache */
+ xfer->td_transfer_cache = td;
+ uhci_check_transfer_sub(xfer);
+ goto done;
+ }
+ }
+ /* transfer is done */
+ break;
+ }
+ td = td->obj_next;
+ }
+ uhci_non_isoc_done(xfer);
+ goto transferred;
+ }
+
+done:
+ DPRINTFN(13, "xfer=%p is still active\n", xfer);
+ return (0);
+
+transferred:
+ return (1);
+}
+
+static void
+uhci_interrupt_poll(uhci_softc_t *sc)
+{
+ struct usb_xfer *xfer;
+
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ /*
+ * check if transfer is transferred
+ */
+ if (uhci_check_transfer(xfer)) {
+ /* queue has been modified */
+ goto repeat;
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * uhci_interrupt - UHCI interrupt handler
+ *
+ * NOTE: Do not access "sc->sc_bus.bdev" inside the interrupt handler,
+ * hence the interrupt handler will be setup before "sc->sc_bus.bdev"
+ * is present !
+ *------------------------------------------------------------------------*/
+void
+uhci_interrupt(uhci_softc_t *sc)
+{
+ uint32_t status;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ DPRINTFN(16, "real interrupt\n");
+
+#ifdef USB_DEBUG
+ if (uhcidebug > 15) {
+ uhci_dumpregs(sc);
+ }
+#endif
+ status = UREAD2(sc, UHCI_STS) & UHCI_STS_ALLINTRS;
+ if (status == 0) {
+ /* the interrupt was not for us */
+ goto done;
+ }
+ if (status & (UHCI_STS_RD | UHCI_STS_HSE |
+ UHCI_STS_HCPE | UHCI_STS_HCH)) {
+ if (status & UHCI_STS_RD) {
+#ifdef USB_DEBUG
+ printf("%s: resume detect\n",
+ __FUNCTION__);
+#endif
+ }
+ if (status & UHCI_STS_HSE) {
+ printf("%s: host system error\n",
+ __FUNCTION__);
+ }
+ if (status & UHCI_STS_HCPE) {
+ printf("%s: host controller process error\n",
+ __FUNCTION__);
+ }
+ if (status & UHCI_STS_HCH) {
+ /* no acknowledge needed */
+ DPRINTF("%s: host controller halted\n",
+ __FUNCTION__);
+#ifdef USB_DEBUG
+ if (uhcidebug > 0) {
+ uhci_dump_all(sc);
+ }
+#endif
+ }
+ }
+ /* get acknowledge bits */
+ status &= (UHCI_STS_USBINT |
+ UHCI_STS_USBEI |
+ UHCI_STS_RD |
+ UHCI_STS_HSE |
+ UHCI_STS_HCPE |
+ UHCI_STS_HCH);
+
+ if (status == 0) {
+ /* nothing to acknowledge */
+ goto done;
+ }
+ /* acknowledge interrupts */
+ UWRITE2(sc, UHCI_STS, status);
+
+ /* poll all the USB transfers */
+ uhci_interrupt_poll(sc);
+
+done:
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*
+ * called when a request does not complete
+ */
+static void
+uhci_timeout(void *arg)
+{
+ struct usb_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ uhci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+uhci_do_poll(struct usb_bus *bus)
+{
+ struct uhci_softc *sc = UHCI_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ uhci_interrupt_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+uhci_setup_standard_chain_sub(struct uhci_std_temp *temp)
+{
+ uhci_td_t *td;
+ uhci_td_t *td_next;
+ uhci_td_t *td_alt_next;
+ uint32_t average;
+ uint32_t len_old;
+ uint8_t shortpkt_old;
+ uint8_t precompute;
+
+ td_alt_next = NULL;
+ shortpkt_old = temp->shortpkt;
+ len_old = temp->len;
+ precompute = 1;
+
+ /* software is used to detect short incoming transfers */
+
+ if ((temp->td_token & htole32(UHCI_TD_PID)) == htole32(UHCI_TD_PID_IN)) {
+ temp->td_status |= htole32(UHCI_TD_SPD);
+ } else {
+ temp->td_status &= ~htole32(UHCI_TD_SPD);
+ }
+
+ temp->ml.buf_offset = 0;
+
+restart:
+
+ temp->td_token &= ~htole32(UHCI_TD_SET_MAXLEN(0));
+ temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(temp->average));
+
+ td = temp->td;
+ td_next = temp->td_next;
+
+ while (1) {
+ if (temp->len == 0) {
+ if (temp->shortpkt) {
+ break;
+ }
+ /* send a Zero Length Packet, ZLP, last */
+
+ temp->shortpkt = 1;
+ temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(0));
+ average = 0;
+
+ } else {
+ average = temp->average;
+
+ if (temp->len < average) {
+ temp->shortpkt = 1;
+ temp->td_token &= ~htole32(UHCI_TD_SET_MAXLEN(0));
+ temp->td_token |= htole32(UHCI_TD_SET_MAXLEN(temp->len));
+ average = temp->len;
+ }
+ }
+
+ if (td_next == NULL) {
+ panic("%s: out of UHCI transfer descriptors!", __FUNCTION__);
+ }
+ /* get next TD */
+
+ td = td_next;
+ td_next = td->obj_next;
+
+ /* check if we are pre-computing */
+
+ if (precompute) {
+ /* update remaining length */
+
+ temp->len -= average;
+
+ continue;
+ }
+ /* fill out current TD */
+
+ td->td_status = temp->td_status;
+ td->td_token = temp->td_token;
+
+ /* update data toggle */
+
+ temp->td_token ^= htole32(UHCI_TD_SET_DT(1));
+
+ if (average == 0) {
+ td->len = 0;
+ td->td_buffer = 0;
+ td->fix_pc = NULL;
+
+ } else {
+ /* update remaining length */
+
+ temp->len -= average;
+
+ td->len = average;
+
+ /* fill out buffer pointer and do fixup, if any */
+
+ uhci_mem_layout_fixup(&temp->ml, td);
+ }
+
+ td->alt_next = td_alt_next;
+
+ if ((td_next == td_alt_next) && temp->setup_alt_next) {
+ /* we need to receive these frames one by one ! */
+ td->td_status |= htole32(UHCI_TD_IOC);
+ td->td_next = htole32(UHCI_PTR_T);
+ } else {
+ if (td_next) {
+ /* link the current TD with the next one */
+ td->td_next = td_next->td_self;
+ }
+ }
+
+ usb_pc_cpu_flush(td->page_cache);
+ }
+
+ if (precompute) {
+ precompute = 0;
+
+ /* setup alt next pointer, if any */
+ if (temp->last_frame) {
+ td_alt_next = NULL;
+ } else {
+ /* we use this field internally */
+ td_alt_next = td_next;
+ }
+
+ /* restore */
+ temp->shortpkt = shortpkt_old;
+ temp->len = len_old;
+ goto restart;
+ }
+ temp->td = td;
+ temp->td_next = td_next;
+}
+
+static uhci_td_t *
+uhci_setup_standard_chain(struct usb_xfer *xfer)
+{
+ struct uhci_std_temp temp;
+ uhci_td_t *td;
+ uint32_t x;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpointno),
+ xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
+
+ temp.average = xfer->max_frame_size;
+ temp.max_frame_size = xfer->max_frame_size;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ temp.td = NULL;
+ temp.td_next = td;
+ temp.last_frame = 0;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok;
+
+ uhci_mem_layout_init(&temp.ml, xfer);
+
+ temp.td_status =
+ htole32(UHCI_TD_ZERO_ACTLEN(UHCI_TD_SET_ERRCNT(3) |
+ UHCI_TD_ACTIVE));
+
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ temp.td_status |= htole32(UHCI_TD_LS);
+ }
+ temp.td_token =
+ htole32(UHCI_TD_SET_ENDPT(xfer->endpointno) |
+ UHCI_TD_SET_DEVADDR(xfer->address));
+
+ if (xfer->endpoint->toggle_next) {
+ /* DATA1 is next */
+ temp.td_token |= htole32(UHCI_TD_SET_DT(1));
+ }
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) |
+ UHCI_TD_SET_ENDPT(0xF));
+ temp.td_token |= htole32(UHCI_TD_PID_SETUP |
+ UHCI_TD_SET_DT(0));
+
+ temp.len = xfer->frlengths[0];
+ temp.ml.buf_pc = xfer->frbuffers + 0;
+ temp.shortpkt = temp.len ? 1 : 0;
+ /* check for last frame */
+ if (xfer->nframes == 1) {
+ /* no STATUS stage yet, SETUP is last */
+ if (xfer->flags_int.control_act) {
+ temp.last_frame = 1;
+ temp.setup_alt_next = 0;
+ }
+ }
+ uhci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ while (x != xfer->nframes) {
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+ temp.ml.buf_pc = xfer->frbuffers + x;
+
+ x++;
+
+ if (x == xfer->nframes) {
+ if (xfer->flags_int.control_xfr) {
+ /* no STATUS stage yet, DATA is last */
+ if (xfer->flags_int.control_act) {
+ temp.last_frame = 1;
+ temp.setup_alt_next = 0;
+ }
+ } else {
+ temp.last_frame = 1;
+ temp.setup_alt_next = 0;
+ }
+ }
+ /*
+ * Keep previous data toggle,
+ * device address and endpoint number:
+ */
+
+ temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) |
+ UHCI_TD_SET_ENDPT(0xF) |
+ UHCI_TD_SET_DT(1));
+
+ if (temp.len == 0) {
+ /* make sure that we send an USB packet */
+
+ temp.shortpkt = 0;
+
+ } else {
+ /* regular data transfer */
+
+ temp.shortpkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ /* set endpoint direction */
+
+ temp.td_token |=
+ (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ?
+ htole32(UHCI_TD_PID_IN) :
+ htole32(UHCI_TD_PID_OUT);
+
+ uhci_setup_standard_chain_sub(&temp);
+ }
+
+ /* check if we should append a status stage */
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+ /*
+ * send a DATA1 message and reverse the current endpoint
+ * direction
+ */
+
+ temp.td_token &= htole32(UHCI_TD_SET_DEVADDR(0x7F) |
+ UHCI_TD_SET_ENDPT(0xF) |
+ UHCI_TD_SET_DT(1));
+ temp.td_token |=
+ (UE_GET_DIR(xfer->endpointno) == UE_DIR_OUT) ?
+ htole32(UHCI_TD_PID_IN | UHCI_TD_SET_DT(1)) :
+ htole32(UHCI_TD_PID_OUT | UHCI_TD_SET_DT(1));
+
+ temp.len = 0;
+ temp.ml.buf_pc = NULL;
+ temp.shortpkt = 0;
+ temp.last_frame = 1;
+ temp.setup_alt_next = 0;
+
+ uhci_setup_standard_chain_sub(&temp);
+ }
+ td = temp.td;
+
+ /* Ensure that last TD is terminating: */
+ td->td_next = htole32(UHCI_PTR_T);
+
+ /* set interrupt bit */
+
+ td->td_status |= htole32(UHCI_TD_IOC);
+
+ usb_pc_cpu_flush(td->page_cache);
+
+ /* must have at least one frame! */
+
+ xfer->td_transfer_last = td;
+
+#ifdef USB_DEBUG
+ if (uhcidebug > 8) {
+ DPRINTF("nexttog=%d; data before transfer:\n",
+ xfer->endpoint->toggle_next);
+ uhci_dump_tds(xfer->td_transfer_first);
+ }
+#endif
+ return (xfer->td_transfer_first);
+}
+
+/* NOTE: "done" can be run two times in a row,
+ * from close and from interrupt
+ */
+
+static void
+uhci_device_done(struct usb_xfer *xfer, usb_error_t error)
+{
+ const struct usb_pipe_methods *methods = xfer->endpoint->methods;
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+ uhci_qh_t *qh;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n",
+ xfer, xfer->endpoint, error);
+
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+ if (qh) {
+ usb_pc_cpu_invalidate(qh->page_cache);
+ }
+ if (xfer->flags_int.bandwidth_reclaimed) {
+ xfer->flags_int.bandwidth_reclaimed = 0;
+ uhci_rem_loop(sc);
+ }
+ if (methods == &uhci_device_bulk_methods) {
+ UHCI_REMOVE_QH(qh, sc->sc_bulk_p_last);
+ }
+ if (methods == &uhci_device_ctrl_methods) {
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ UHCI_REMOVE_QH(qh, sc->sc_ls_ctl_p_last);
+ } else {
+ UHCI_REMOVE_QH(qh, sc->sc_fs_ctl_p_last);
+ }
+ }
+ if (methods == &uhci_device_intr_methods) {
+ UHCI_REMOVE_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ /*
+ * Only finish isochronous transfers once
+ * which will update "xfer->frlengths".
+ */
+ if (xfer->td_transfer_first &&
+ xfer->td_transfer_last) {
+ if (methods == &uhci_device_isoc_methods) {
+ uhci_isoc_done(sc, xfer);
+ }
+ xfer->td_transfer_first = NULL;
+ xfer->td_transfer_last = NULL;
+ }
+ /* dequeue transfer and start next transfer */
+ usbd_transfer_done(xfer, error);
+}
+
+/*------------------------------------------------------------------------*
+ * uhci bulk support
+ *------------------------------------------------------------------------*/
+static void
+uhci_device_bulk_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_device_bulk_close(struct usb_xfer *xfer)
+{
+ uhci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uhci_device_bulk_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_device_bulk_start(struct usb_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+ uhci_td_t *td;
+ uhci_qh_t *qh;
+
+ /* setup TD's */
+ td = uhci_setup_standard_chain(xfer);
+
+ /* setup QH */
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ qh->e_next = td;
+ qh->qh_e_next = td->td_self;
+
+ if (xfer->xroot->udev->flags.self_suspended == 0) {
+ UHCI_APPEND_QH(qh, sc->sc_bulk_p_last);
+ uhci_add_loop(sc);
+ xfer->flags_int.bandwidth_reclaimed = 1;
+ } else {
+ usb_pc_cpu_flush(qh->page_cache);
+ }
+
+ /* put transfer on interrupt queue */
+ uhci_transfer_intr_enqueue(xfer);
+}
+
+static const struct usb_pipe_methods uhci_device_bulk_methods =
+{
+ .open = uhci_device_bulk_open,
+ .close = uhci_device_bulk_close,
+ .enter = uhci_device_bulk_enter,
+ .start = uhci_device_bulk_start,
+};
+
+/*------------------------------------------------------------------------*
+ * uhci control support
+ *------------------------------------------------------------------------*/
+static void
+uhci_device_ctrl_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_device_ctrl_close(struct usb_xfer *xfer)
+{
+ uhci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uhci_device_ctrl_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_device_ctrl_start(struct usb_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+ uhci_qh_t *qh;
+ uhci_td_t *td;
+
+ /* setup TD's */
+ td = uhci_setup_standard_chain(xfer);
+
+ /* setup QH */
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ qh->e_next = td;
+ qh->qh_e_next = td->td_self;
+
+ /*
+ * NOTE: some devices choke on bandwidth- reclamation for control
+ * transfers
+ */
+ if (xfer->xroot->udev->flags.self_suspended == 0) {
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ UHCI_APPEND_QH(qh, sc->sc_ls_ctl_p_last);
+ } else {
+ UHCI_APPEND_QH(qh, sc->sc_fs_ctl_p_last);
+ }
+ } else {
+ usb_pc_cpu_flush(qh->page_cache);
+ }
+ /* put transfer on interrupt queue */
+ uhci_transfer_intr_enqueue(xfer);
+}
+
+static const struct usb_pipe_methods uhci_device_ctrl_methods =
+{
+ .open = uhci_device_ctrl_open,
+ .close = uhci_device_ctrl_close,
+ .enter = uhci_device_ctrl_enter,
+ .start = uhci_device_ctrl_start,
+};
+
+/*------------------------------------------------------------------------*
+ * uhci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+uhci_device_intr_open(struct usb_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+ uint16_t best;
+ uint16_t bit;
+ uint16_t x;
+
+ best = 0;
+ bit = UHCI_IFRAMELIST_COUNT / 2;
+ while (bit) {
+ if (xfer->interval >= bit) {
+ x = bit;
+ best = bit;
+ while (x & bit) {
+ if (sc->sc_intr_stat[x] <
+ sc->sc_intr_stat[best]) {
+ best = x;
+ }
+ x++;
+ }
+ break;
+ }
+ bit >>= 1;
+ }
+
+ sc->sc_intr_stat[best]++;
+ xfer->qh_pos = best;
+
+ DPRINTFN(3, "best=%d interval=%d\n",
+ best, xfer->interval);
+}
+
+static void
+uhci_device_intr_close(struct usb_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+
+ sc->sc_intr_stat[xfer->qh_pos]--;
+
+ uhci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uhci_device_intr_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_device_intr_start(struct usb_xfer *xfer)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+ uhci_qh_t *qh;
+ uhci_td_t *td;
+
+ /* setup TD's */
+ td = uhci_setup_standard_chain(xfer);
+
+ /* setup QH */
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ qh->e_next = td;
+ qh->qh_e_next = td->td_self;
+
+ if (xfer->xroot->udev->flags.self_suspended == 0) {
+ /* enter QHs into the controller data structures */
+ UHCI_APPEND_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]);
+ } else {
+ usb_pc_cpu_flush(qh->page_cache);
+ }
+
+ /* put transfer on interrupt queue */
+ uhci_transfer_intr_enqueue(xfer);
+}
+
+static const struct usb_pipe_methods uhci_device_intr_methods =
+{
+ .open = uhci_device_intr_open,
+ .close = uhci_device_intr_close,
+ .enter = uhci_device_intr_enter,
+ .start = uhci_device_intr_start,
+};
+
+/*------------------------------------------------------------------------*
+ * uhci isochronous support
+ *------------------------------------------------------------------------*/
+static void
+uhci_device_isoc_open(struct usb_xfer *xfer)
+{
+ uhci_td_t *td;
+ uint32_t td_token;
+ uint8_t ds;
+
+ td_token =
+ (UE_GET_DIR(xfer->endpointno) == UE_DIR_IN) ?
+ UHCI_TD_IN(0, xfer->endpointno, xfer->address, 0) :
+ UHCI_TD_OUT(0, xfer->endpointno, xfer->address, 0);
+
+ td_token = htole32(td_token);
+
+ /* initialize all TD's */
+
+ for (ds = 0; ds != 2; ds++) {
+ for (td = xfer->td_start[ds]; td; td = td->obj_next) {
+ /* mark TD as inactive */
+ td->td_status = htole32(UHCI_TD_IOS);
+ td->td_token = td_token;
+
+ usb_pc_cpu_flush(td->page_cache);
+ }
+ }
+}
+
+static void
+uhci_device_isoc_close(struct usb_xfer *xfer)
+{
+ uhci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uhci_device_isoc_enter(struct usb_xfer *xfer)
+{
+ struct uhci_mem_layout ml;
+ uhci_softc_t *sc = UHCI_BUS2SC(xfer->xroot->bus);
+ uint32_t nframes;
+ uint32_t startframe;
+ uint32_t *plen;
+
+#ifdef USB_DEBUG
+ uint8_t once = 1;
+
+#endif
+ uhci_td_t *td;
+ uhci_td_t *td_last = NULL;
+ uhci_td_t **pp_last;
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->endpoint->isoc_next, xfer->nframes);
+
+ nframes = UREAD2(sc, UHCI_FRNUM);
+
+ if (usbd_xfer_get_isochronous_start_frame(
+ xfer, nframes, 0, 1, UHCI_VFRAMELIST_COUNT - 1, &startframe))
+ DPRINTFN(3, "start next=%d\n", startframe);
+
+ /* get the real number of frames */
+
+ nframes = xfer->nframes;
+
+ uhci_mem_layout_init(&ml, xfer);
+
+ plen = xfer->frlengths;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+ xfer->td_transfer_first = td;
+
+ pp_last = &sc->sc_isoc_p_last[startframe];
+
+ /* store starting position */
+
+ xfer->qh_pos = startframe;
+
+ while (nframes--) {
+ if (td == NULL) {
+ panic("%s:%d: out of TD's\n",
+ __FUNCTION__, __LINE__);
+ }
+ if (pp_last >= &sc->sc_isoc_p_last[UHCI_VFRAMELIST_COUNT]) {
+ pp_last = &sc->sc_isoc_p_last[0];
+ }
+ if (*plen > xfer->max_frame_size) {
+#ifdef USB_DEBUG
+ if (once) {
+ once = 0;
+ printf("%s: frame length(%d) exceeds %d "
+ "bytes (frame truncated)\n",
+ __FUNCTION__, *plen,
+ xfer->max_frame_size);
+ }
+#endif
+ *plen = xfer->max_frame_size;
+ }
+ /* reuse td_token from last transfer */
+
+ td->td_token &= htole32(~UHCI_TD_MAXLEN_MASK);
+ td->td_token |= htole32(UHCI_TD_SET_MAXLEN(*plen));
+
+ td->len = *plen;
+
+ if (td->len == 0) {
+ /*
+ * Do not call "uhci_mem_layout_fixup()" when the
+ * length is zero!
+ */
+ td->td_buffer = 0;
+ td->fix_pc = NULL;
+
+ } else {
+ /* fill out buffer pointer and do fixup, if any */
+
+ uhci_mem_layout_fixup(&ml, td);
+ }
+
+ /* update status */
+ if (nframes == 0) {
+ td->td_status = htole32
+ (UHCI_TD_ZERO_ACTLEN
+ (UHCI_TD_SET_ERRCNT(0) |
+ UHCI_TD_ACTIVE |
+ UHCI_TD_IOS |
+ UHCI_TD_IOC));
+ } else {
+ td->td_status = htole32
+ (UHCI_TD_ZERO_ACTLEN
+ (UHCI_TD_SET_ERRCNT(0) |
+ UHCI_TD_ACTIVE |
+ UHCI_TD_IOS));
+ }
+
+ usb_pc_cpu_flush(td->page_cache);
+
+#ifdef USB_DEBUG
+ if (uhcidebug > 5) {
+ DPRINTF("TD %d\n", nframes);
+ uhci_dump_td(td);
+ }
+#endif
+ /* insert TD into schedule */
+ UHCI_APPEND_TD(td, *pp_last);
+ pp_last++;
+
+ plen++;
+ td_last = td;
+ td = td->obj_next;
+ }
+
+ xfer->td_transfer_last = td_last;
+}
+
+static void
+uhci_device_isoc_start(struct usb_xfer *xfer)
+{
+ /* put transfer on interrupt queue */
+ uhci_transfer_intr_enqueue(xfer);
+}
+
+static const struct usb_pipe_methods uhci_device_isoc_methods =
+{
+ .open = uhci_device_isoc_open,
+ .close = uhci_device_isoc_close,
+ .enter = uhci_device_isoc_enter,
+ .start = uhci_device_isoc_start,
+};
+
+/*------------------------------------------------------------------------*
+ * uhci root control support
+ *------------------------------------------------------------------------*
+ * Simulate a hardware hub by handling all the necessary requests.
+ *------------------------------------------------------------------------*/
+
+static const
+struct usb_device_descriptor uhci_devd =
+{
+ sizeof(struct usb_device_descriptor),
+ UDESC_DEVICE, /* type */
+ {0x00, 0x01}, /* USB version */
+ UDCLASS_HUB, /* class */
+ UDSUBCLASS_HUB, /* subclass */
+ UDPROTO_FSHUB, /* protocol */
+ 64, /* max packet */
+ {0}, {0}, {0x00, 0x01}, /* device id */
+ 1, 2, 0, /* string indexes */
+ 1 /* # of configurations */
+};
+
+static const struct uhci_config_desc uhci_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(uhci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0 /* max power */
+ },
+ .ifcd = {
+ .bLength = sizeof(struct usb_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = UIPROTO_FSHUB,
+ },
+ .endpd = {
+ .bLength = sizeof(struct usb_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = UE_DIR_IN | UHCI_INTR_ENDPT,
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8, /* max packet (63 ports) */
+ .bInterval = 255,
+ },
+};
+
+static const
+struct usb_hub_descriptor_min uhci_hubd_piix =
+{
+ .bDescLength = sizeof(uhci_hubd_piix),
+ .bDescriptorType = UDESC_HUB,
+ .bNbrPorts = 2,
+ .wHubCharacteristics = {UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL, 0},
+ .bPwrOn2PwrGood = 50,
+};
+
+/*
+ * 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 usb_error_t
+uhci_portreset(uhci_softc_t *sc, uint16_t index)
+{
+ uint16_t port;
+ uint16_t x;
+ uint8_t lim;
+
+ if (index == 1)
+ port = UHCI_PORTSC1;
+ else if (index == 2)
+ port = UHCI_PORTSC2;
+ else
+ return (USB_ERR_IOERROR);
+
+ /*
+ * Before we do anything, turn on SOF messages on the USB
+ * BUS. Some USB devices do not cope without them!
+ */
+ uhci_restart(sc);
+
+ x = URWMASK(UREAD2(sc, port));
+ UWRITE2(sc, port, x | UHCI_PORTSC_PR);
+
+ usb_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(usb_port_root_reset_delay));
+
+ DPRINTFN(4, "uhci port %d reset, status0 = 0x%04x\n",
+ index, UREAD2(sc, port));
+
+ x = URWMASK(UREAD2(sc, port));
+ UWRITE2(sc, port, x & ~UHCI_PORTSC_PR);
+
+ mtx_unlock(&sc->sc_bus.bus_mtx);
+
+ /*
+ * This delay needs to be exactly 100us, else some USB devices
+ * fail to attach!
+ */
+ DELAY(100);
+
+ mtx_lock(&sc->sc_bus.bus_mtx);
+
+ DPRINTFN(4, "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 = 0; lim < 12; lim++) {
+ usb_pause_mtx(&sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(usb_port_reset_delay));
+
+ x = UREAD2(sc, port);
+
+ DPRINTFN(4, "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(4, "uhci port %d loop %u, device detached\n",
+ index, lim);
+ goto done;
+ }
+ 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 */
+ goto done;
+ }
+ UWRITE2(sc, port, URWMASK(x) | UHCI_PORTSC_PE);
+ }
+
+ DPRINTFN(2, "uhci port %d reset timed out\n", index);
+ return (USB_ERR_TIMEOUT);
+
+done:
+ DPRINTFN(4, "uhci port %d reset, status2 = 0x%04x\n",
+ index, UREAD2(sc, port));
+
+ sc->sc_isreset = 1;
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+uhci_roothub_exec(struct usb_device *udev,
+ struct usb_device_request *req, const void **pptr, uint16_t *plength)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(udev->bus);
+ const void *ptr;
+ const char *str_ptr;
+ uint16_t x;
+ uint16_t port;
+ uint16_t value;
+ uint16_t index;
+ uint16_t status;
+ uint16_t change;
+ uint16_t len;
+ usb_error_t err;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* buffer reset */
+ ptr = (const void *)&sc->sc_hub_desc.temp;
+ len = 0;
+ err = 0;
+
+ value = UGETW(req->wValue);
+ index = UGETW(req->wIndex);
+
+ DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x "
+ "wValue=0x%04x wIndex=0x%04x\n",
+ req->bmRequestType, req->bRequest,
+ UGETW(req->wLength), value, index);
+
+#define C(x,y) ((x) | ((y) << 8))
+ switch (C(req->bRequest, req->bmRequestType)) {
+ case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
+ /*
+ * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops
+ * for the integrated root hub.
+ */
+ break;
+ case C(UR_GET_CONFIG, UT_READ_DEVICE):
+ len = 1;
+ sc->sc_hub_desc.temp[0] = sc->sc_conf;
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE):
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ len = sizeof(uhci_devd);
+ ptr = (const void *)&uhci_devd;
+ break;
+
+ case UDESC_CONFIG:
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ len = sizeof(uhci_confd);
+ ptr = (const void *)&uhci_confd;
+ break;
+
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ str_ptr = "\001";
+ break;
+
+ case 1: /* Vendor */
+ str_ptr = sc->sc_vendor;
+ break;
+
+ case 2: /* Product */
+ str_ptr = "UHCI root HUB";
+ break;
+
+ default:
+ str_ptr = "";
+ break;
+ }
+
+ len = usb_make_str_desc
+ (sc->sc_hub_desc.temp,
+ sizeof(sc->sc_hub_desc.temp),
+ str_ptr);
+ break;
+
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_GET_INTERFACE, UT_READ_INTERFACE):
+ len = 1;
+ sc->sc_hub_desc.temp[0] = 0;
+ break;
+ case C(UR_GET_STATUS, UT_READ_DEVICE):
+ len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED);
+ break;
+ case C(UR_GET_STATUS, UT_READ_INTERFACE):
+ case C(UR_GET_STATUS, UT_READ_ENDPOINT):
+ len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, 0);
+ break;
+ case C(UR_SET_ADDRESS, UT_WRITE_DEVICE):
+ if (value >= UHCI_MAX_DEVICES) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_addr = value;
+ break;
+ case C(UR_SET_CONFIG, UT_WRITE_DEVICE):
+ if ((value != 0) && (value != 1)) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_conf = value;
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_SET_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT):
+ err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
+ break;
+ case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT):
+ break;
+ /* Hub requests */
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER):
+ DPRINTFN(4, "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 = USB_ERR_IOERROR;
+ goto done;
+ }
+ 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 = USB_ERR_NORMAL_COMPLETION;
+ goto done;
+ case UHF_C_PORT_SUSPEND:
+ sc->sc_isresumed &= ~(1 << index);
+ break;
+ case UHF_PORT_CONNECTION:
+ case UHF_PORT_OVER_CURRENT:
+ case UHF_PORT_POWER:
+ case UHF_PORT_LOW_SPEED:
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ 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 = USB_ERR_IOERROR;
+ goto done;
+ }
+ len = 1;
+ sc->sc_hub_desc.temp[0] =
+ ((UREAD2(sc, port) & UHCI_PORTSC_LS) >>
+ UHCI_PORTSC_LS_SHIFT);
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE):
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ len = sizeof(uhci_hubd_piix);
+ ptr = (const void *)&uhci_hubd_piix;
+ break;
+ case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE):
+ len = 16;
+ memset(sc->sc_hub_desc.temp, 0, 16);
+ break;
+ case C(UR_GET_STATUS, UT_READ_CLASS_OTHER):
+ if (index == 1)
+ port = UHCI_PORTSC1;
+ else if (index == 2)
+ port = UHCI_PORTSC2;
+ else {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ 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_LSDA)
+ status |= UPS_LOW_SPEED;
+ if ((x & UHCI_PORTSC_PE) && (x & UHCI_PORTSC_RD)) {
+ /* need to do a write back */
+ UWRITE2(sc, port, URWMASK(x));
+
+ /* wait 20ms for resume sequence to complete */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50);
+
+ /* clear suspend and resume detect */
+ UWRITE2(sc, port, URWMASK(x) & ~(UHCI_PORTSC_RD |
+ UHCI_PORTSC_SUSP));
+
+ /* wait a little bit */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 500);
+
+ sc->sc_isresumed |= (1 << index);
+
+ } else if (x & UHCI_PORTSC_SUSP) {
+ status |= UPS_SUSPEND;
+ }
+ status |= UPS_PORT_POWER;
+ if (sc->sc_isresumed & (1 << index))
+ change |= UPS_C_SUSPEND;
+ if (sc->sc_isreset)
+ change |= UPS_C_PORT_RESET;
+ USETW(sc->sc_hub_desc.ps.wPortStatus, status);
+ USETW(sc->sc_hub_desc.ps.wPortChange, change);
+ len = sizeof(sc->sc_hub_desc.ps);
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE):
+ err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER):
+ if (index == 1)
+ port = UHCI_PORTSC1;
+ else if (index == 2)
+ port = UHCI_PORTSC2;
+ else {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ 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 done;
+ case UHF_PORT_POWER:
+ /* pretend we turned on power */
+ err = USB_ERR_NORMAL_COMPLETION;
+ goto done;
+ 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 = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+done:
+ *plength = len;
+ *pptr = ptr;
+ return (err);
+}
+
+/*
+ * This routine is executed periodically and simulates interrupts from
+ * the root controller interrupt pipe for port status change:
+ */
+static void
+uhci_root_intr(uhci_softc_t *sc)
+{
+ DPRINTFN(21, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ sc->sc_hub_idata[0] = 0;
+
+ if (UREAD2(sc, UHCI_PORTSC1) & (UHCI_PORTSC_CSC |
+ UHCI_PORTSC_OCIC | UHCI_PORTSC_RD)) {
+ sc->sc_hub_idata[0] |= 1 << 1;
+ }
+ if (UREAD2(sc, UHCI_PORTSC2) & (UHCI_PORTSC_CSC |
+ UHCI_PORTSC_OCIC | UHCI_PORTSC_RD)) {
+ sc->sc_hub_idata[0] |= 1 << 2;
+ }
+
+ /* restart timer */
+ usb_callout_reset(&sc->sc_root_intr, hz,
+ (void *)&uhci_root_intr, sc);
+
+ if (sc->sc_hub_idata[0] != 0) {
+ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
+ sizeof(sc->sc_hub_idata));
+ }
+}
+
+static void
+uhci_xfer_setup(struct usb_setup_params *parm)
+{
+ struct usb_page_search page_info;
+ struct usb_page_cache *pc;
+ struct usb_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t nqh;
+ uint32_t nfixup;
+ uint32_t n;
+ uint16_t align;
+
+ xfer = parm->curr_xfer;
+
+ parm->hc_max_packet_size = 0x500;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x500;
+
+ /*
+ * compute ntd and nqh
+ */
+ if (parm->methods == &uhci_device_ctrl_methods) {
+ xfer->flags_int.bdma_enable = 1;
+ xfer->flags_int.bdma_no_post_sync = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ /* see EHCI HC driver for proof of "ntd" formula */
+
+ nqh = 1;
+ ntd = ((2 * xfer->nframes) + 1 /* STATUS */
+ + (xfer->max_data_length / xfer->max_frame_size));
+
+ } else if (parm->methods == &uhci_device_bulk_methods) {
+ xfer->flags_int.bdma_enable = 1;
+ xfer->flags_int.bdma_no_post_sync = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ nqh = 1;
+ ntd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_frame_size));
+
+ } else if (parm->methods == &uhci_device_intr_methods) {
+ xfer->flags_int.bdma_enable = 1;
+ xfer->flags_int.bdma_no_post_sync = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ nqh = 1;
+ ntd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_frame_size));
+
+ } else if (parm->methods == &uhci_device_isoc_methods) {
+ xfer->flags_int.bdma_enable = 1;
+ xfer->flags_int.bdma_no_post_sync = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ nqh = 0;
+ ntd = xfer->nframes;
+
+ } else {
+ usbd_transfer_setup_sub(parm);
+
+ nqh = 0;
+ ntd = 0;
+ }
+
+ if (parm->err) {
+ return;
+ }
+ /*
+ * NOTE: the UHCI controller requires that
+ * every packet must be contiguous on
+ * the same USB memory page !
+ */
+ nfixup = (parm->bufsize / USB_PAGE_SIZE) + 1;
+
+ /*
+ * Compute a suitable power of two alignment
+ * for our "max_frame_size" fixup buffer(s):
+ */
+ align = xfer->max_frame_size;
+ n = 0;
+ while (align) {
+ align >>= 1;
+ n++;
+ }
+
+ /* check for power of two */
+ if (!(xfer->max_frame_size &
+ (xfer->max_frame_size - 1))) {
+ n--;
+ }
+ /*
+ * We don't allow alignments of
+ * less than 8 bytes:
+ *
+ * NOTE: Allocating using an alignment
+ * of 1 byte has special meaning!
+ */
+ if (n < 3) {
+ n = 3;
+ }
+ align = (1 << n);
+
+ if (usbd_transfer_setup_sub_malloc(
+ parm, &pc, xfer->max_frame_size,
+ align, nfixup)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ xfer->buf_fixup = pc;
+
+alloc_dma_set:
+
+ if (parm->err) {
+ return;
+ }
+ last_obj = NULL;
+
+ if (usbd_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(uhci_td_t),
+ UHCI_TD_ALIGN, ntd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != ntd; n++) {
+ uhci_td_t *td;
+
+ usbd_get_page(pc + n, 0, &page_info);
+
+ td = page_info.buffer;
+
+ /* init TD */
+ if ((parm->methods == &uhci_device_bulk_methods) ||
+ (parm->methods == &uhci_device_ctrl_methods) ||
+ (parm->methods == &uhci_device_intr_methods)) {
+ /* set depth first bit */
+ td->td_self = htole32(page_info.physaddr |
+ UHCI_PTR_TD | UHCI_PTR_VF);
+ } else {
+ td->td_self = htole32(page_info.physaddr |
+ UHCI_PTR_TD);
+ }
+
+ td->obj_next = last_obj;
+ td->page_cache = pc + n;
+
+ last_obj = td;
+
+ usb_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ last_obj = NULL;
+
+ if (usbd_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(uhci_qh_t),
+ UHCI_QH_ALIGN, nqh)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != nqh; n++) {
+ uhci_qh_t *qh;
+
+ usbd_get_page(pc + n, 0, &page_info);
+
+ qh = page_info.buffer;
+
+ /* init QH */
+ qh->qh_self = htole32(page_info.physaddr | UHCI_PTR_QH);
+ qh->obj_next = last_obj;
+ qh->page_cache = pc + n;
+
+ last_obj = qh;
+
+ usb_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->qh_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ if (!xfer->flags_int.curr_dma_set) {
+ xfer->flags_int.curr_dma_set = 1;
+ goto alloc_dma_set;
+ }
+}
+
+static void
+uhci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
+ struct usb_endpoint *ep)
+{
+ uhci_softc_t *sc = UHCI_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ ep, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb_mode,
+ sc->sc_addr);
+
+ if (udev->device_index != sc->sc_addr) {
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ ep->methods = &uhci_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ ep->methods = &uhci_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ if (udev->speed == USB_SPEED_FULL) {
+ ep->methods = &uhci_device_isoc_methods;
+ }
+ break;
+ case UE_BULK:
+ ep->methods = &uhci_device_bulk_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+static void
+uhci_xfer_unsetup(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uhci_get_dma_delay(struct usb_device *udev, uint32_t *pus)
+{
+ /*
+ * Wait until hardware has finished any possible use of the
+ * transfer descriptor(s) and QH
+ */
+ *pus = (1125); /* microseconds */
+}
+
+static void
+uhci_device_resume(struct usb_device *udev)
+{
+ struct uhci_softc *sc = UHCI_BUS2SC(udev->bus);
+ struct usb_xfer *xfer;
+ const struct usb_pipe_methods *methods;
+ uhci_qh_t *qh;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (xfer->xroot->udev == udev) {
+ methods = xfer->endpoint->methods;
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ if (methods == &uhci_device_bulk_methods) {
+ UHCI_APPEND_QH(qh, sc->sc_bulk_p_last);
+ uhci_add_loop(sc);
+ xfer->flags_int.bandwidth_reclaimed = 1;
+ }
+ if (methods == &uhci_device_ctrl_methods) {
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ UHCI_APPEND_QH(qh, sc->sc_ls_ctl_p_last);
+ } else {
+ UHCI_APPEND_QH(qh, sc->sc_fs_ctl_p_last);
+ }
+ }
+ if (methods == &uhci_device_intr_methods) {
+ UHCI_APPEND_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ return;
+}
+
+static void
+uhci_device_suspend(struct usb_device *udev)
+{
+ struct uhci_softc *sc = UHCI_BUS2SC(udev->bus);
+ struct usb_xfer *xfer;
+ const struct usb_pipe_methods *methods;
+ uhci_qh_t *qh;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(udev->bus);
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (xfer->xroot->udev == udev) {
+ methods = xfer->endpoint->methods;
+ qh = xfer->qh_start[xfer->flags_int.curr_dma_set];
+
+ if (xfer->flags_int.bandwidth_reclaimed) {
+ xfer->flags_int.bandwidth_reclaimed = 0;
+ uhci_rem_loop(sc);
+ }
+ if (methods == &uhci_device_bulk_methods) {
+ UHCI_REMOVE_QH(qh, sc->sc_bulk_p_last);
+ }
+ if (methods == &uhci_device_ctrl_methods) {
+ if (xfer->xroot->udev->speed == USB_SPEED_LOW) {
+ UHCI_REMOVE_QH(qh, sc->sc_ls_ctl_p_last);
+ } else {
+ UHCI_REMOVE_QH(qh, sc->sc_fs_ctl_p_last);
+ }
+ }
+ if (methods == &uhci_device_intr_methods) {
+ UHCI_REMOVE_QH(qh, sc->sc_intr_p_last[xfer->qh_pos]);
+ }
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ return;
+}
+
+static void
+uhci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state)
+{
+ struct uhci_softc *sc = UHCI_BUS2SC(bus);
+
+ switch (state) {
+ case USB_HW_POWER_SUSPEND:
+ case USB_HW_POWER_SHUTDOWN:
+ uhci_suspend(sc);
+ break;
+ case USB_HW_POWER_RESUME:
+ uhci_resume(sc);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+uhci_set_hw_power(struct usb_bus *bus)
+{
+ struct uhci_softc *sc = UHCI_BUS2SC(bus);
+ uint32_t flags;
+
+ DPRINTF("\n");
+
+ USB_BUS_LOCK(bus);
+
+ flags = bus->hw_power_state;
+
+ /*
+ * WARNING: Some FULL speed USB devices require periodic SOF
+ * messages! If any USB devices are connected through the
+ * UHCI, power save will be disabled!
+ */
+ if (flags & (USB_HW_POWER_CONTROL |
+ USB_HW_POWER_NON_ROOT_HUB |
+ USB_HW_POWER_BULK |
+ USB_HW_POWER_INTERRUPT |
+ USB_HW_POWER_ISOC)) {
+ DPRINTF("Some USB transfer is "
+ "active on unit %u.\n",
+ device_get_unit(sc->sc_bus.bdev));
+ uhci_restart(sc);
+ } else {
+ DPRINTF("Power save on unit %u.\n",
+ device_get_unit(sc->sc_bus.bdev));
+ UHCICMD(sc, UHCI_CMD_MAXP);
+ }
+
+ USB_BUS_UNLOCK(bus);
+
+ return;
+}
+
+static const struct usb_bus_methods uhci_bus_methods =
+{
+ .endpoint_init = uhci_ep_init,
+ .xfer_setup = uhci_xfer_setup,
+ .xfer_unsetup = uhci_xfer_unsetup,
+ .get_dma_delay = uhci_get_dma_delay,
+ .device_resume = uhci_device_resume,
+ .device_suspend = uhci_device_suspend,
+ .set_hw_power = uhci_set_hw_power,
+ .set_hw_power_sleep = uhci_set_hw_power_sleep,
+ .roothub_exec = uhci_roothub_exec,
+ .xfer_poll = uhci_do_poll,
+};
diff --git a/sys/dev/usb/controller/uhci.h b/sys/dev/usb/controller/uhci.h
new file mode 100644
index 000000000000..e30685715803
--- /dev/null
+++ b/sys/dev/usb/controller/uhci.h
@@ -0,0 +1,250 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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 _UHCI_H_
+#define _UHCI_H_
+
+#define UHCI_MAX_DEVICES MIN(USB_MAX_DEVICES, 128)
+
+#define UHCI_FRAMELIST_COUNT 1024 /* units */
+#define UHCI_FRAMELIST_ALIGN 4096 /* bytes */
+
+/* Structures alignment (bytes) */
+#define UHCI_TD_ALIGN 16
+#define UHCI_QH_ALIGN 16
+
+#if ((USB_PAGE_SIZE < UHCI_TD_ALIGN) || (UHCI_TD_ALIGN == 0) || \
+ (USB_PAGE_SIZE < UHCI_QH_ALIGN) || (UHCI_QH_ALIGN == 0))
+#error "Invalid USB page size!"
+#endif
+
+typedef uint32_t uhci_physaddr_t;
+
+#define UHCI_PTR_T 0x00000001
+#define UHCI_PTR_TD 0x00000000
+#define UHCI_PTR_QH 0x00000002
+#define UHCI_PTR_VF 0x00000004
+
+/*
+ * The Queue Heads (QH) and Transfer Descriptors (TD) are accessed by
+ * both the CPU and the USB-controller which run concurrently. Great
+ * care must be taken. When the data-structures are linked into the
+ * USB controller's frame list, the USB-controller "owns" the
+ * td_status and qh_elink fields, which will not be written by the
+ * CPU.
+ *
+ */
+
+struct uhci_td {
+/*
+ * Data used by the UHCI controller.
+ * volatile is used in order to mantain struct members ordering.
+ */
+ volatile uint32_t td_next;
+ volatile uint32_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
+ volatile uint32_t td_token;
+#define UHCI_TD_PID 0x000000ff
+#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)-1U) << 21)
+#define UHCI_TD_GET_MAXLEN(s) ((((s) >> 21) + 1) & 0x7ff)
+#define UHCI_TD_MAXLEN_MASK 0xffe00000
+ volatile uint32_t td_buffer;
+/*
+ * Extra information needed:
+ */
+ struct uhci_td *next;
+ struct uhci_td *prev;
+ struct uhci_td *obj_next;
+ struct usb_page_cache *page_cache;
+ struct usb_page_cache *fix_pc;
+ uint32_t td_self;
+ uint16_t len;
+} __aligned(UHCI_TD_ALIGN);
+
+typedef struct uhci_td 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))
+
+struct uhci_qh {
+/*
+ * Data used by the UHCI controller.
+ */
+ volatile uint32_t qh_h_next;
+ volatile uint32_t qh_e_next;
+/*
+ * Extra information needed:
+ */
+ struct uhci_qh *h_next;
+ struct uhci_qh *h_prev;
+ struct uhci_qh *obj_next;
+ struct uhci_td *e_next;
+ struct usb_page_cache *page_cache;
+ uint32_t qh_self;
+ uint16_t intr_pos;
+} __aligned(UHCI_QH_ALIGN);
+
+typedef struct uhci_qh uhci_qh_t;
+
+/* Maximum number of isochronous TD's and QH's interrupt */
+#define UHCI_VFRAMELIST_COUNT 128
+#define UHCI_IFRAMELIST_COUNT (2 * UHCI_VFRAMELIST_COUNT)
+
+#if (((UHCI_VFRAMELIST_COUNT & (UHCI_VFRAMELIST_COUNT-1)) != 0) || \
+ (UHCI_VFRAMELIST_COUNT > UHCI_FRAMELIST_COUNT))
+#error "UHCI_VFRAMELIST_COUNT is not power of two"
+#error "or UHCI_VFRAMELIST_COUNT > UHCI_FRAMELIST_COUNT"
+#endif
+
+#if (UHCI_VFRAMELIST_COUNT < USB_MAX_FS_ISOC_FRAMES_PER_XFER)
+#error "maximum number of full-speed isochronous frames is higher than supported!"
+#endif
+
+struct uhci_config_desc {
+ struct usb_config_descriptor confd;
+ struct usb_interface_descriptor ifcd;
+ struct usb_endpoint_descriptor endpd;
+} __packed;
+
+union uhci_hub_desc {
+ struct usb_status stat;
+ struct usb_port_status ps;
+ uint8_t temp[128];
+};
+
+struct uhci_hw_softc {
+ struct usb_page_cache pframes_pc;
+ struct usb_page_cache isoc_start_pc[UHCI_VFRAMELIST_COUNT];
+ struct usb_page_cache intr_start_pc[UHCI_IFRAMELIST_COUNT];
+ struct usb_page_cache ls_ctl_start_pc;
+ struct usb_page_cache fs_ctl_start_pc;
+ struct usb_page_cache bulk_start_pc;
+ struct usb_page_cache last_qh_pc;
+ struct usb_page_cache last_td_pc;
+
+ struct usb_page pframes_pg;
+ struct usb_page isoc_start_pg[UHCI_VFRAMELIST_COUNT];
+ struct usb_page intr_start_pg[UHCI_IFRAMELIST_COUNT];
+ struct usb_page ls_ctl_start_pg;
+ struct usb_page fs_ctl_start_pg;
+ struct usb_page bulk_start_pg;
+ struct usb_page last_qh_pg;
+ struct usb_page last_td_pg;
+};
+
+typedef struct uhci_softc {
+ struct uhci_hw_softc sc_hw;
+ struct usb_bus sc_bus; /* base device */
+ union uhci_hub_desc sc_hub_desc;
+ struct usb_callout sc_root_intr;
+
+ struct usb_device *sc_devices[UHCI_MAX_DEVICES];
+ /* pointer to last TD for isochronous */
+ struct uhci_td *sc_isoc_p_last[UHCI_VFRAMELIST_COUNT];
+ /* pointer to last QH for interrupt */
+ struct uhci_qh *sc_intr_p_last[UHCI_IFRAMELIST_COUNT];
+ /* pointer to last QH for low speed control */
+ struct uhci_qh *sc_ls_ctl_p_last;
+ /* pointer to last QH for full speed control */
+ struct uhci_qh *sc_fs_ctl_p_last;
+ /* pointer to last QH for bulk */
+ struct uhci_qh *sc_bulk_p_last;
+ struct uhci_qh *sc_reclaim_qh_p;
+ struct uhci_qh *sc_last_qh_p;
+ struct uhci_td *sc_last_td_p;
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ void *sc_intr_hdl;
+ device_t sc_dev;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ uint32_t sc_loops; /* number of QHs that wants looping */
+
+ uint16_t sc_intr_stat[UHCI_IFRAMELIST_COUNT];
+
+ uint8_t sc_addr; /* device address */
+ uint8_t sc_conf; /* device configuration */
+ uint8_t sc_isreset; /* bits set if a root hub is reset */
+ uint8_t sc_isresumed; /* bits set if a port was resumed */
+ uint8_t sc_hub_idata[1];
+
+ char sc_vendor[16]; /* vendor string for root hub */
+} uhci_softc_t;
+
+usb_bus_mem_cb_t uhci_iterate_hw_softc;
+
+usb_error_t uhci_init(uhci_softc_t *sc);
+void uhci_reset(uhci_softc_t *sc);
+void uhci_interrupt(uhci_softc_t *sc);
+
+#endif /* _UHCI_H_ */
diff --git a/sys/dev/usb/controller/uhci_pci.c b/sys/dev/usb/controller/uhci_pci.c
new file mode 100644
index 000000000000..97f6d09f9e65
--- /dev/null
+++ b/sys/dev/usb/controller/uhci_pci.c
@@ -0,0 +1,481 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+/* 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/usb_pci.h>
+#include <dev/usb/controller/uhci.h>
+#include <dev/usb/controller/uhcireg.h>
+#include "usb_if.h"
+
+#define PCI_UHCI_VENDORID_INTEL 0x8086
+#define PCI_UHCI_VENDORID_HP 0x103c
+#define PCI_UHCI_VENDORID_VIA 0x1106
+#define PCI_UHCI_VENDORID_VMWARE 0x15ad
+#define PCI_UHCI_VENDORID_ZHAOXIN 0x1d17
+
+/* PIIX4E has no separate stepping */
+
+static device_probe_t uhci_pci_probe;
+static device_attach_t uhci_pci_attach;
+static device_detach_t uhci_pci_detach;
+static usb_take_controller_t uhci_pci_take_controller;
+
+static int
+uhci_pci_take_controller(device_t self)
+{
+ pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2);
+
+ return (0);
+}
+
+static const char *
+uhci_pci_match(device_t self)
+{
+ uint32_t device_id = pci_get_devid(self);
+
+ switch (device_id) {
+ case 0x26888086:
+ return ("Intel 631XESB/632XESB/3100 USB controller USB-1");
+
+ case 0x26898086:
+ return ("Intel 631XESB/632XESB/3100 USB controller USB-2");
+
+ case 0x268a8086:
+ return ("Intel 631XESB/632XESB/3100 USB controller USB-3");
+
+ case 0x268b8086:
+ return ("Intel 631XESB/632XESB/3100 USB controller USB-4");
+
+ case 0x70208086:
+ return ("Intel 82371SB (PIIX3) USB controller");
+
+ case 0x71128086:
+ return ("Intel 82371AB/EB (PIIX4) USB controller");
+
+ case 0x24128086:
+ return ("Intel 82801AA (ICH) USB controller");
+
+ case 0x24228086:
+ return ("Intel 82801AB (ICH0) USB controller");
+
+ case 0x24428086:
+ return ("Intel 82801BA/BAM (ICH2) USB controller USB-A");
+
+ case 0x24448086:
+ return ("Intel 82801BA/BAM (ICH2) USB controller USB-B");
+
+ case 0x24828086:
+ return ("Intel 82801CA/CAM (ICH3) USB controller USB-A");
+
+ case 0x24848086:
+ return ("Intel 82801CA/CAM (ICH3) USB controller USB-B");
+
+ case 0x24878086:
+ return ("Intel 82801CA/CAM (ICH3) USB controller USB-C");
+
+ case 0x24c28086:
+ return ("Intel 82801DB (ICH4) USB controller USB-A");
+
+ case 0x24c48086:
+ return ("Intel 82801DB (ICH4) USB controller USB-B");
+
+ case 0x24c78086:
+ return ("Intel 82801DB (ICH4) USB controller USB-C");
+
+ case 0x24d28086:
+ return ("Intel 82801EB (ICH5) USB controller USB-A");
+
+ case 0x24d48086:
+ return ("Intel 82801EB (ICH5) USB controller USB-B");
+
+ case 0x24d78086:
+ return ("Intel 82801EB (ICH5) USB controller USB-C");
+
+ case 0x24de8086:
+ return ("Intel 82801EB (ICH5) USB controller USB-D");
+
+ case 0x25a98086:
+ return ("Intel 6300ESB USB controller USB-A");
+
+ case 0x25aa8086:
+ return ("Intel 6300ESB USB controller USB-B");
+
+ case 0x26588086:
+ return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-A");
+
+ case 0x26598086:
+ return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-B");
+
+ case 0x265a8086:
+ return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-C");
+
+ case 0x265b8086:
+ return ("Intel 82801FB/FR/FW/FRW (ICH6) USB controller USB-D");
+
+ case 0x27c88086:
+ return ("Intel 82801G (ICH7) USB controller USB-A");
+ case 0x27c98086:
+ return ("Intel 82801G (ICH7) USB controller USB-B");
+ case 0x27ca8086:
+ return ("Intel 82801G (ICH7) USB controller USB-C");
+ case 0x27cb8086:
+ return ("Intel 82801G (ICH7) USB controller USB-D");
+
+ case 0x28308086:
+ return ("Intel 82801H (ICH8) USB controller USB-A");
+ case 0x28318086:
+ return ("Intel 82801H (ICH8) USB controller USB-B");
+ case 0x28328086:
+ return ("Intel 82801H (ICH8) USB controller USB-C");
+ case 0x28348086:
+ return ("Intel 82801H (ICH8) USB controller USB-D");
+ case 0x28358086:
+ return ("Intel 82801H (ICH8) USB controller USB-E");
+ case 0x29348086:
+ return ("Intel 82801I (ICH9) USB controller");
+ case 0x29358086:
+ return ("Intel 82801I (ICH9) USB controller");
+ case 0x29368086:
+ return ("Intel 82801I (ICH9) USB controller");
+ case 0x29378086:
+ return ("Intel 82801I (ICH9) USB controller");
+ case 0x29388086:
+ return ("Intel 82801I (ICH9) USB controller");
+ case 0x29398086:
+ return ("Intel 82801I (ICH9) USB controller");
+ case 0x3a348086:
+ return ("Intel 82801JI (ICH10) USB controller USB-A");
+ case 0x3a358086:
+ return ("Intel 82801JI (ICH10) USB controller USB-B");
+ case 0x3a368086:
+ return ("Intel 82801JI (ICH10) USB controller USB-C");
+ case 0x3a378086:
+ return ("Intel 82801JI (ICH10) USB controller USB-D");
+ case 0x3a388086:
+ return ("Intel 82801JI (ICH10) USB controller USB-E");
+ case 0x3a398086:
+ return ("Intel 82801JI (ICH10) USB controller USB-F");
+ case 0x3a678086:
+ return ("Intel 82801JD (ICH10) USB controller USB-A");
+ case 0x3a688086:
+ return ("Intel 82801JD (ICH10) USB controller USB-B");
+ case 0x3a698086:
+ return ("Intel 82801JD (ICH10) USB controller USB-C");
+ case 0x3a648086:
+ return ("Intel 82801JD (ICH10) USB controller USB-D");
+ case 0x3a658086:
+ return ("Intel 82801JD (ICH10) USB controller USB-E");
+ case 0x3a668086:
+ return ("Intel 82801JD (ICH10) USB controller USB-F");
+
+ case 0x719a8086:
+ return ("Intel 82443MX USB controller");
+
+ case 0x76028086:
+ return ("Intel 82372FB/82468GX USB controller");
+
+ case 0x3300103c:
+ return ("HP iLO Standard Virtual USB controller");
+
+ case 0x30381106:
+ return ("VIA 83C572 USB controller");
+
+ case 0x077415ad:
+ return ("VMware USB controller");
+
+ case 0x30381d17:
+ return ("Zhaoxin ZX-100/ZX-200/ZX-E USB controller");
+
+ default:
+ break;
+ }
+
+ if ((pci_get_class(self) == PCIC_SERIALBUS) &&
+ (pci_get_subclass(self) == PCIS_SERIALBUS_USB) &&
+ (pci_get_progif(self) == PCI_INTERFACE_UHCI)) {
+ return ("UHCI (generic) USB controller");
+ }
+ return (NULL);
+}
+
+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;
+
+ /* initialise some bus fields */
+ sc->sc_bus.parent = self;
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = UHCI_MAX_DEVICES;
+ sc->sc_bus.dma_bits = 32;
+
+ /* get all DMA memory */
+ if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(self),
+ &uhci_iterate_hw_softc)) {
+ return ENOMEM;
+ }
+ sc->sc_dev = self;
+
+ pci_enable_busmaster(self);
+
+ rid = PCI_UHCI_BASE_REG;
+ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_IOPORT, &rid,
+ RF_ACTIVE);
+ if (!sc->sc_io_res) {
+ device_printf(self, "Could not map ports\n");
+ goto error;
+ }
+ sc->sc_io_tag = rman_get_bustag(sc->sc_io_res);
+ sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = rman_get_size(sc->sc_io_res);
+
+ /* disable interrupts */
+ bus_space_write_2(sc->sc_io_tag, sc->sc_io_hdl, UHCI_INTR, 0);
+
+ rid = 0;
+ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid,
+ RF_SHAREABLE | RF_ACTIVE);
+ if (sc->sc_irq_res == NULL) {
+ device_printf(self, "Could not allocate irq\n");
+ goto error;
+ }
+ sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY);
+ if (!sc->sc_bus.bdev) {
+ device_printf(self, "Could not add USB device\n");
+ goto error;
+ }
+ 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_HP:
+ sprintf(sc->sc_vendor, "HP");
+ break;
+ case PCI_UHCI_VENDORID_VIA:
+ sprintf(sc->sc_vendor, "VIA");
+ break;
+ case PCI_UHCI_VENDORID_VMWARE:
+ sprintf(sc->sc_vendor, "VMware");
+ break;
+ case PCI_UHCI_VENDORID_ZHAOXIN:
+ sprintf(sc->sc_vendor, "Zhaoxin");
+ 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_USB_REV_MASK) {
+ case PCI_USB_REV_PRE_1_0:
+ sc->sc_bus.usbrev = USB_REV_PRE_1_0;
+ break;
+ case PCI_USB_REV_1_0:
+ sc->sc_bus.usbrev = USB_REV_1_0;
+ break;
+ default:
+ /* Quirk for Parallels Desktop 4.0 */
+ device_printf(self, "USB revision is unknown. Assuming v1.1.\n");
+ sc->sc_bus.usbrev = USB_REV_1_1;
+ break;
+ }
+
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (driver_intr_t *)uhci_interrupt, sc, &sc->sc_intr_hdl);
+
+ if (err) {
+ device_printf(self, "Could not setup irq, %d\n", err);
+ sc->sc_intr_hdl = NULL;
+ goto error;
+ }
+ /*
+ * Set the PIRQD enable bit and switch off all the others. We don't
+ * want legacy support to interfere with us XXX Does this also mean
+ * that the BIOS won't touch the keyboard anymore if it is connected
+ * to the ports of the root hub?
+ */
+#ifdef USB_DEBUG
+ if (pci_read_config(self, PCI_LEGSUP, 2) != PCI_LEGSUP_USBPIRQDEN) {
+ device_printf(self, "LegSup = 0x%04x\n",
+ pci_read_config(self, PCI_LEGSUP, 2));
+ }
+#endif
+ pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2);
+
+ err = uhci_init(sc);
+ if (!err) {
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+ }
+ if (err) {
+ device_printf(self, "USB init failed\n");
+ goto error;
+ }
+ return (0);
+
+error:
+ uhci_pci_detach(self);
+ return (ENXIO);
+}
+
+int
+uhci_pci_detach(device_t self)
+{
+ uhci_softc_t *sc = device_get_softc(self);
+ int error;
+
+ /* during module unload there are lots of children leftover */
+ error = bus_generic_detach(self);
+ if (error != 0)
+ return (error);
+
+ /*
+ * disable interrupts that might have been switched on in
+ * uhci_init.
+ */
+ if (sc->sc_io_res) {
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* stop the controller */
+ uhci_reset(sc);
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ }
+ pci_disable_busmaster(self);
+
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ int err = bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl);
+
+ if (err) {
+ /* XXX or should we panic? */
+ device_printf(self, "Could not tear down irq, %d\n",
+ err);
+ }
+ sc->sc_intr_hdl = NULL;
+ }
+ if (sc->sc_irq_res) {
+ bus_release_resource(self, SYS_RES_IRQ, 0, sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ }
+ if (sc->sc_io_res) {
+ bus_release_resource(self, SYS_RES_IOPORT, PCI_UHCI_BASE_REG,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+ usb_bus_mem_free_all(&sc->sc_bus, &uhci_iterate_hw_softc);
+
+ return (0);
+}
+
+static device_method_t uhci_pci_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, uhci_pci_probe),
+ DEVMETHOD(device_attach, uhci_pci_attach),
+ DEVMETHOD(device_detach, uhci_pci_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+ DEVMETHOD(usb_take_controller, uhci_pci_take_controller),
+
+ DEVMETHOD_END
+};
+
+static driver_t uhci_driver = {
+ .name = "uhci",
+ .methods = uhci_pci_methods,
+ .size = sizeof(struct uhci_softc),
+};
+
+DRIVER_MODULE(uhci, pci, uhci_driver, 0, 0);
+MODULE_DEPEND(uhci, usb, 1, 1, 1);
diff --git a/sys/dev/usb/controller/uhcireg.h b/sys/dev/usb/controller/uhcireg.h
new file mode 100644
index 000000000000..b8749ddf2bc0
--- /dev/null
+++ b/sys/dev/usb/controller/uhcireg.h
@@ -0,0 +1,96 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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 _UHCIREG_H_
+#define _UHCIREG_H_
+
+#define PCI_UHCI_BASE_REG 0x20
+
+/* PCI config registers */
+#define PCI_USBREV 0x60 /* USB protocol revision */
+#define PCI_USB_REV_MASK 0xff
+#define PCI_USB_REV_PRE_1_0 0x00
+#define PCI_USB_REV_1_0 0x10
+#define PCI_USB_REV_1_1 0x11
+#define PCI_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))
+
+#endif /* _UHCIREG_H_ */
diff --git a/sys/dev/usb/controller/usb_controller.c b/sys/dev/usb/controller/usb_controller.c
new file mode 100644
index 000000000000..7e89a5ab0155
--- /dev/null
+++ b/sys/dev/usb/controller/usb_controller.c
@@ -0,0 +1,1035 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include "opt_ddb.h"
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#define USB_DEBUG_VAR usb_ctrl_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_dynamic.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_dev.h>
+#include <dev/usb/usb_hub.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/usb_pf.h>
+#include "usb_if.h"
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+/* function prototypes */
+
+static device_probe_t usb_probe;
+static device_attach_t usb_attach;
+static device_detach_t usb_detach;
+static device_suspend_t usb_suspend;
+static device_resume_t usb_resume;
+static device_shutdown_t usb_shutdown;
+
+static void usb_attach_sub(device_t, struct usb_bus *);
+
+/* static variables */
+
+#ifdef USB_DEBUG
+static int usb_ctrl_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ctrl, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB controller");
+SYSCTL_INT(_hw_usb_ctrl, OID_AUTO, debug, CTLFLAG_RWTUN, &usb_ctrl_debug, 0,
+ "Debug level");
+#endif
+
+#if USB_HAVE_ROOT_MOUNT_HOLD
+static int usb_no_boot_wait = 0;
+SYSCTL_INT(_hw_usb, OID_AUTO, no_boot_wait, CTLFLAG_RDTUN, &usb_no_boot_wait, 0,
+ "No USB device enumerate waiting at boot.");
+#endif
+
+static int usb_no_suspend_wait = 0;
+SYSCTL_INT(_hw_usb, OID_AUTO, no_suspend_wait, CTLFLAG_RWTUN,
+ &usb_no_suspend_wait, 0, "No USB device waiting at system suspend.");
+
+static int usb_no_shutdown_wait = 0;
+SYSCTL_INT(_hw_usb, OID_AUTO, no_shutdown_wait, CTLFLAG_RWTUN,
+ &usb_no_shutdown_wait, 0, "No USB device waiting at system shutdown.");
+
+static device_method_t usb_methods[] = {
+ DEVMETHOD(device_probe, usb_probe),
+ DEVMETHOD(device_attach, usb_attach),
+ DEVMETHOD(device_detach, usb_detach),
+ DEVMETHOD(device_suspend, usb_suspend),
+ DEVMETHOD(device_resume, usb_resume),
+ DEVMETHOD(device_shutdown, usb_shutdown),
+
+ DEVMETHOD_END
+};
+
+static driver_t usb_driver = {
+ .name = "usbus",
+ .methods = usb_methods,
+ .size = 0,
+};
+
+/* Host Only Drivers */
+DRIVER_MODULE(usbus, ohci, usb_driver, 0, 0);
+DRIVER_MODULE(usbus, uhci, usb_driver, 0, 0);
+DRIVER_MODULE(usbus, ehci, usb_driver, 0, 0);
+DRIVER_MODULE(usbus, xhci, usb_driver, 0, 0);
+
+/* Device Only Drivers */
+DRIVER_MODULE(usbus, musbotg, usb_driver, 0, 0);
+DRIVER_MODULE(usbus, uss820dci, usb_driver, 0, 0);
+DRIVER_MODULE(usbus, octusb, usb_driver, 0, 0);
+
+/* Dual Mode Drivers */
+DRIVER_MODULE(usbus, dwcotg, usb_driver, 0, 0);
+
+/*------------------------------------------------------------------------*
+ * usb_probe
+ *
+ * This function is called from "{ehci,ohci,uhci}_pci_attach()".
+ *------------------------------------------------------------------------*/
+static int
+usb_probe(device_t dev)
+{
+ DPRINTF("\n");
+ return (0);
+}
+
+#if USB_HAVE_ROOT_MOUNT_HOLD
+static void
+usb_root_mount_rel(struct usb_bus *bus)
+{
+ if (bus->bus_roothold != NULL) {
+ DPRINTF("Releasing root mount hold %p\n", bus->bus_roothold);
+ root_mount_rel(bus->bus_roothold);
+ bus->bus_roothold = NULL;
+ }
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usb_attach
+ *------------------------------------------------------------------------*/
+static int
+usb_attach(device_t dev)
+{
+ struct usb_bus *bus = device_get_ivars(dev);
+
+ DPRINTF("\n");
+
+ if (bus == NULL) {
+ device_printf(dev, "USB device has no ivars\n");
+ return (ENXIO);
+ }
+
+#if USB_HAVE_ROOT_MOUNT_HOLD
+ if (usb_no_boot_wait == 0) {
+ /* delay vfs_mountroot until the bus is explored */
+ bus->bus_roothold = root_mount_hold(device_get_nameunit(dev));
+ }
+#endif
+ usb_attach_sub(dev, bus);
+
+ return (0); /* return success */
+}
+
+/*------------------------------------------------------------------------*
+ * usb_detach
+ *------------------------------------------------------------------------*/
+static int
+usb_detach(device_t dev)
+{
+ struct usb_bus *bus = device_get_softc(dev);
+
+ DPRINTF("\n");
+
+ if (bus == NULL) {
+ /* was never setup properly */
+ return (0);
+ }
+ /* Stop power watchdog */
+ usb_callout_drain(&bus->power_wdog);
+
+#if USB_HAVE_ROOT_MOUNT_HOLD
+ /* Let the USB explore process detach all devices. */
+ usb_root_mount_rel(bus);
+#endif
+
+ USB_BUS_LOCK(bus);
+
+ /* Queue detach job */
+ usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus),
+ &bus->detach_msg[0], &bus->detach_msg[1]);
+
+ /* Wait for detach to complete */
+ usb_proc_mwait(USB_BUS_EXPLORE_PROC(bus),
+ &bus->detach_msg[0], &bus->detach_msg[1]);
+
+#if USB_HAVE_UGEN
+ /* Wait for cleanup to complete */
+ usb_proc_mwait(USB_BUS_EXPLORE_PROC(bus),
+ &bus->cleanup_msg[0], &bus->cleanup_msg[1]);
+#endif
+ USB_BUS_UNLOCK(bus);
+
+#if USB_HAVE_PER_BUS_PROCESS
+ /* Get rid of USB callback processes */
+
+ usb_proc_free(USB_BUS_GIANT_PROC(bus));
+ usb_proc_free(USB_BUS_NON_GIANT_ISOC_PROC(bus));
+ usb_proc_free(USB_BUS_NON_GIANT_BULK_PROC(bus));
+
+ /* Get rid of USB explore process */
+
+ usb_proc_free(USB_BUS_EXPLORE_PROC(bus));
+
+ /* Get rid of control transfer process */
+
+ usb_proc_free(USB_BUS_CONTROL_XFER_PROC(bus));
+#endif
+
+#if USB_HAVE_PF
+ usbpf_detach(bus);
+#endif
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_suspend
+ *------------------------------------------------------------------------*/
+static int
+usb_suspend(device_t dev)
+{
+ struct usb_bus *bus = device_get_softc(dev);
+
+ DPRINTF("\n");
+
+ if (bus == NULL) {
+ /* was never setup properly */
+ return (0);
+ }
+
+ USB_BUS_LOCK(bus);
+ usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus),
+ &bus->suspend_msg[0], &bus->suspend_msg[1]);
+ if (usb_no_suspend_wait == 0) {
+ /* wait for suspend callback to be executed */
+ usb_proc_mwait(USB_BUS_EXPLORE_PROC(bus),
+ &bus->suspend_msg[0], &bus->suspend_msg[1]);
+ }
+ USB_BUS_UNLOCK(bus);
+
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_resume
+ *------------------------------------------------------------------------*/
+static int
+usb_resume(device_t dev)
+{
+ struct usb_bus *bus = device_get_softc(dev);
+
+ DPRINTF("\n");
+
+ if (bus == NULL) {
+ /* was never setup properly */
+ return (0);
+ }
+
+ USB_BUS_LOCK(bus);
+ usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus),
+ &bus->resume_msg[0], &bus->resume_msg[1]);
+ USB_BUS_UNLOCK(bus);
+
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bus_reset_async_locked
+ *------------------------------------------------------------------------*/
+void
+usb_bus_reset_async_locked(struct usb_bus *bus)
+{
+ USB_BUS_LOCK_ASSERT(bus, MA_OWNED);
+
+ DPRINTF("\n");
+
+ if (bus->reset_msg[0].hdr.pm_qentry.tqe_prev != NULL ||
+ bus->reset_msg[1].hdr.pm_qentry.tqe_prev != NULL) {
+ DPRINTF("Reset already pending\n");
+ return;
+ }
+
+ device_printf(bus->parent, "Resetting controller\n");
+
+ usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus),
+ &bus->reset_msg[0], &bus->reset_msg[1]);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_shutdown
+ *------------------------------------------------------------------------*/
+static int
+usb_shutdown(device_t dev)
+{
+ struct usb_bus *bus = device_get_softc(dev);
+
+ DPRINTF("\n");
+
+ if (bus == NULL) {
+ /* was never setup properly */
+ return (0);
+ }
+
+ DPRINTF("%s: Controller shutdown\n", device_get_nameunit(bus->bdev));
+
+ USB_BUS_LOCK(bus);
+ usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus),
+ &bus->shutdown_msg[0], &bus->shutdown_msg[1]);
+ if (usb_no_shutdown_wait == 0) {
+ /* wait for shutdown callback to be executed */
+ usb_proc_mwait(USB_BUS_EXPLORE_PROC(bus),
+ &bus->shutdown_msg[0], &bus->shutdown_msg[1]);
+ }
+ USB_BUS_UNLOCK(bus);
+
+ DPRINTF("%s: Controller shutdown complete\n",
+ device_get_nameunit(bus->bdev));
+
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bus_explore
+ *
+ * This function is used to explore the device tree from the root.
+ *------------------------------------------------------------------------*/
+static void
+usb_bus_explore(struct usb_proc_msg *pm)
+{
+ struct usb_bus *bus;
+ struct usb_device *udev;
+
+ bus = ((struct usb_bus_msg *)pm)->bus;
+ udev = bus->devices[USB_ROOT_HUB_ADDR];
+
+ if (bus->no_explore != 0)
+ return;
+
+ if (udev != NULL) {
+ USB_BUS_UNLOCK(bus);
+ uhub_explore_handle_re_enumerate(udev);
+ USB_BUS_LOCK(bus);
+ }
+
+ if (udev != NULL && udev->hub != NULL) {
+ if (bus->do_probe) {
+ bus->do_probe = 0;
+ bus->driver_added_refcount++;
+ }
+ if (bus->driver_added_refcount == 0) {
+ /* avoid zero, hence that is memory default */
+ bus->driver_added_refcount = 1;
+ }
+
+#ifdef DDB
+ /*
+ * The following three lines of code are only here to
+ * recover from DDB:
+ */
+ usb_proc_rewakeup(USB_BUS_CONTROL_XFER_PROC(bus));
+ usb_proc_rewakeup(USB_BUS_GIANT_PROC(bus));
+ usb_proc_rewakeup(USB_BUS_NON_GIANT_ISOC_PROC(bus));
+ usb_proc_rewakeup(USB_BUS_NON_GIANT_BULK_PROC(bus));
+#endif
+
+ USB_BUS_UNLOCK(bus);
+
+#if USB_HAVE_POWERD
+ /*
+ * First update the USB power state!
+ */
+ usb_bus_powerd(bus);
+#endif
+ /* Explore the Root USB HUB. */
+ (udev->hub->explore) (udev);
+ USB_BUS_LOCK(bus);
+ }
+#if USB_HAVE_ROOT_MOUNT_HOLD
+ usb_root_mount_rel(bus);
+#endif
+
+ /* Nice the enumeration a bit, to avoid looping too fast. */
+ usb_pause_mtx(&bus->bus_mtx, USB_MS_TO_TICKS(usb_enum_nice_time));
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bus_detach
+ *
+ * This function is used to detach the device tree from the root.
+ *------------------------------------------------------------------------*/
+static void
+usb_bus_detach(struct usb_proc_msg *pm)
+{
+ struct usb_bus *bus;
+ struct usb_device *udev;
+ device_t dev;
+
+ bus = ((struct usb_bus_msg *)pm)->bus;
+ udev = bus->devices[USB_ROOT_HUB_ADDR];
+ dev = bus->bdev;
+ /* clear the softc */
+ device_set_softc(dev, NULL);
+ USB_BUS_UNLOCK(bus);
+
+ /* detach children first */
+ bus_topo_lock();
+ bus_detach_children(dev);
+ bus_topo_unlock();
+
+ /*
+ * Free USB device and all subdevices, if any.
+ */
+ usb_free_device(udev, 0);
+
+ USB_BUS_LOCK(bus);
+ /* clear bdev variable last */
+ bus->bdev = NULL;
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bus_suspend
+ *
+ * This function is used to suspend the USB controller.
+ *------------------------------------------------------------------------*/
+static void
+usb_bus_suspend(struct usb_proc_msg *pm)
+{
+ struct usb_bus *bus;
+ struct usb_device *udev;
+ usb_error_t err;
+ uint8_t do_unlock;
+
+ DPRINTF("\n");
+
+ bus = ((struct usb_bus_msg *)pm)->bus;
+ udev = bus->devices[USB_ROOT_HUB_ADDR];
+
+ if (udev == NULL || bus->bdev == NULL)
+ return;
+
+ USB_BUS_UNLOCK(bus);
+
+ /*
+ * We use the shutdown event here because the suspend and
+ * resume events are reserved for the USB port suspend and
+ * resume. The USB system suspend is implemented like full
+ * shutdown and all connected USB devices will be disconnected
+ * subsequently. At resume all USB devices will be
+ * re-connected again.
+ */
+
+ bus_generic_shutdown(bus->bdev);
+
+ do_unlock = usbd_enum_lock(udev);
+
+ err = usbd_set_config_index(udev, USB_UNCONFIG_INDEX);
+ if (err)
+ device_printf(bus->bdev, "Could not unconfigure root HUB\n");
+
+ USB_BUS_LOCK(bus);
+ bus->hw_power_state = 0;
+ bus->no_explore = 1;
+ USB_BUS_UNLOCK(bus);
+
+ if (bus->methods->set_hw_power != NULL)
+ (bus->methods->set_hw_power) (bus);
+
+ if (bus->methods->set_hw_power_sleep != NULL)
+ (bus->methods->set_hw_power_sleep) (bus, USB_HW_POWER_SUSPEND);
+
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+
+ USB_BUS_LOCK(bus);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bus_resume
+ *
+ * This function is used to resume the USB controller.
+ *------------------------------------------------------------------------*/
+static void
+usb_bus_resume(struct usb_proc_msg *pm)
+{
+ struct usb_bus *bus;
+ struct usb_device *udev;
+ usb_error_t err;
+ uint8_t do_unlock;
+
+ DPRINTF("\n");
+
+ bus = ((struct usb_bus_msg *)pm)->bus;
+ udev = bus->devices[USB_ROOT_HUB_ADDR];
+
+ if (udev == NULL || bus->bdev == NULL)
+ return;
+
+ USB_BUS_UNLOCK(bus);
+
+ do_unlock = usbd_enum_lock(udev);
+#if 0
+ DEVMETHOD(usb_take_controller, NULL); /* dummy */
+#endif
+ USB_TAKE_CONTROLLER(device_get_parent(bus->bdev));
+
+ USB_BUS_LOCK(bus);
+ bus->hw_power_state =
+ USB_HW_POWER_CONTROL |
+ USB_HW_POWER_BULK |
+ USB_HW_POWER_INTERRUPT |
+ USB_HW_POWER_ISOC |
+ USB_HW_POWER_NON_ROOT_HUB;
+ bus->no_explore = 0;
+ USB_BUS_UNLOCK(bus);
+
+ if (bus->methods->set_hw_power_sleep != NULL)
+ (bus->methods->set_hw_power_sleep) (bus, USB_HW_POWER_RESUME);
+
+ if (bus->methods->set_hw_power != NULL)
+ (bus->methods->set_hw_power) (bus);
+
+ /* restore USB configuration to index 0 */
+ err = usbd_set_config_index(udev, 0);
+ if (err)
+ device_printf(bus->bdev, "Could not configure root HUB\n");
+
+ /* probe and attach */
+ err = usb_probe_and_attach(udev, USB_IFACE_INDEX_ANY);
+ if (err) {
+ device_printf(bus->bdev, "Could not probe and "
+ "attach root HUB\n");
+ }
+
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+
+ USB_BUS_LOCK(bus);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bus_reset
+ *
+ * This function is used to reset the USB controller.
+ *------------------------------------------------------------------------*/
+static void
+usb_bus_reset(struct usb_proc_msg *pm)
+{
+ struct usb_bus *bus;
+
+ DPRINTF("\n");
+
+ bus = ((struct usb_bus_msg *)pm)->bus;
+
+ if (bus->bdev == NULL || bus->no_explore != 0)
+ return;
+
+ /* a suspend and resume will reset the USB controller */
+ usb_bus_suspend(pm);
+ usb_bus_resume(pm);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bus_shutdown
+ *
+ * This function is used to shutdown the USB controller.
+ *------------------------------------------------------------------------*/
+static void
+usb_bus_shutdown(struct usb_proc_msg *pm)
+{
+ struct usb_bus *bus;
+ struct usb_device *udev;
+ usb_error_t err;
+ uint8_t do_unlock;
+
+ bus = ((struct usb_bus_msg *)pm)->bus;
+ udev = bus->devices[USB_ROOT_HUB_ADDR];
+
+ if (udev == NULL || bus->bdev == NULL)
+ return;
+
+ USB_BUS_UNLOCK(bus);
+
+ bus_generic_shutdown(bus->bdev);
+
+ do_unlock = usbd_enum_lock(udev);
+
+ err = usbd_set_config_index(udev, USB_UNCONFIG_INDEX);
+ if (err)
+ device_printf(bus->bdev, "Could not unconfigure root HUB\n");
+
+ USB_BUS_LOCK(bus);
+ bus->hw_power_state = 0;
+ bus->no_explore = 1;
+ USB_BUS_UNLOCK(bus);
+
+ if (bus->methods->set_hw_power != NULL)
+ (bus->methods->set_hw_power) (bus);
+
+ if (bus->methods->set_hw_power_sleep != NULL)
+ (bus->methods->set_hw_power_sleep) (bus, USB_HW_POWER_SHUTDOWN);
+
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+
+ USB_BUS_LOCK(bus);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bus_cleanup
+ *
+ * This function is used to cleanup leftover USB character devices.
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_UGEN
+static void
+usb_bus_cleanup(struct usb_proc_msg *pm)
+{
+ struct usb_bus *bus;
+ struct usb_fs_privdata *pd;
+
+ bus = ((struct usb_bus_msg *)pm)->bus;
+
+ while ((pd = SLIST_FIRST(&bus->pd_cleanup_list)) != NULL) {
+ SLIST_REMOVE(&bus->pd_cleanup_list, pd, usb_fs_privdata, pd_next);
+ USB_BUS_UNLOCK(bus);
+
+ usb_destroy_dev_sync(pd);
+
+ USB_BUS_LOCK(bus);
+ }
+}
+#endif
+
+static void
+usb_power_wdog(void *arg)
+{
+ struct usb_bus *bus = arg;
+
+ USB_BUS_LOCK_ASSERT(bus, MA_OWNED);
+
+ usb_callout_reset(&bus->power_wdog,
+ 4 * hz, usb_power_wdog, arg);
+
+#ifdef DDB
+ /*
+ * The following line of code is only here to recover from
+ * DDB:
+ */
+ usb_proc_rewakeup(USB_BUS_EXPLORE_PROC(bus)); /* recover from DDB */
+#endif
+
+#if USB_HAVE_POWERD
+ USB_BUS_UNLOCK(bus);
+
+ usb_bus_power_update(bus);
+
+ USB_BUS_LOCK(bus);
+#endif
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bus_attach
+ *
+ * This function attaches USB in context of the explore thread.
+ *------------------------------------------------------------------------*/
+static void
+usb_bus_attach(struct usb_proc_msg *pm)
+{
+ struct usb_bus *bus;
+ struct usb_device *child;
+ device_t dev;
+ usb_error_t err;
+ enum usb_dev_speed speed;
+
+ bus = ((struct usb_bus_msg *)pm)->bus;
+ dev = bus->bdev;
+
+ DPRINTF("\n");
+
+ switch (bus->usbrev) {
+ case USB_REV_1_0:
+ speed = USB_SPEED_FULL;
+ device_printf(bus->bdev, "12Mbps Full Speed USB v1.0\n");
+ break;
+
+ case USB_REV_1_1:
+ speed = USB_SPEED_FULL;
+ device_printf(bus->bdev, "12Mbps Full Speed USB v1.1\n");
+ break;
+
+ case USB_REV_2_0:
+ speed = USB_SPEED_HIGH;
+ device_printf(bus->bdev, "480Mbps High Speed USB v2.0\n");
+ break;
+
+ case USB_REV_2_5:
+ speed = USB_SPEED_VARIABLE;
+ device_printf(bus->bdev, "480Mbps Wireless USB v2.5\n");
+ break;
+
+ case USB_REV_3_0:
+ speed = USB_SPEED_SUPER;
+ device_printf(bus->bdev, "5.0Gbps Super Speed USB v3.0\n");
+ break;
+
+ default:
+ device_printf(bus->bdev, "Unsupported USB revision\n");
+#if USB_HAVE_ROOT_MOUNT_HOLD
+ usb_root_mount_rel(bus);
+#endif
+ return;
+ }
+
+ /* default power_mask value */
+ bus->hw_power_state =
+ USB_HW_POWER_CONTROL |
+ USB_HW_POWER_BULK |
+ USB_HW_POWER_INTERRUPT |
+ USB_HW_POWER_ISOC |
+ USB_HW_POWER_NON_ROOT_HUB;
+
+ USB_BUS_UNLOCK(bus);
+
+ /* make sure power is set at least once */
+
+ if (bus->methods->set_hw_power != NULL) {
+ (bus->methods->set_hw_power) (bus);
+ }
+
+ /* allocate the Root USB device */
+
+ child = usb_alloc_device(bus->bdev, bus, NULL, 0, 0, 1,
+ speed, USB_MODE_HOST);
+ if (child) {
+ err = usb_probe_and_attach(child,
+ USB_IFACE_INDEX_ANY);
+ if (!err) {
+ if ((bus->devices[USB_ROOT_HUB_ADDR] == NULL) ||
+ (bus->devices[USB_ROOT_HUB_ADDR]->hub == NULL)) {
+ err = USB_ERR_NO_ROOT_HUB;
+ }
+ }
+ } else {
+ err = USB_ERR_NOMEM;
+ }
+
+ USB_BUS_LOCK(bus);
+
+ if (err) {
+ device_printf(bus->bdev, "Root HUB problem, error=%s\n",
+ usbd_errstr(err));
+#if USB_HAVE_ROOT_MOUNT_HOLD
+ usb_root_mount_rel(bus);
+#endif
+ }
+
+ /* set softc - we are ready */
+ device_set_softc(dev, bus);
+
+ /* start watchdog */
+ usb_power_wdog(bus);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_attach_sub
+ *
+ * This function creates a thread which runs the USB attach code.
+ *------------------------------------------------------------------------*/
+static void
+usb_attach_sub(device_t dev, struct usb_bus *bus)
+{
+ bus_topo_lock();
+ if (usb_devclass_ptr == NULL)
+ usb_devclass_ptr = devclass_find("usbus");
+ bus_topo_unlock();
+
+#if USB_HAVE_PF
+ usbpf_attach(bus);
+#endif
+ /* Initialise USB process messages */
+ bus->explore_msg[0].hdr.pm_callback = &usb_bus_explore;
+ bus->explore_msg[0].bus = bus;
+ bus->explore_msg[1].hdr.pm_callback = &usb_bus_explore;
+ bus->explore_msg[1].bus = bus;
+
+ bus->detach_msg[0].hdr.pm_callback = &usb_bus_detach;
+ bus->detach_msg[0].bus = bus;
+ bus->detach_msg[1].hdr.pm_callback = &usb_bus_detach;
+ bus->detach_msg[1].bus = bus;
+
+ bus->attach_msg[0].hdr.pm_callback = &usb_bus_attach;
+ bus->attach_msg[0].bus = bus;
+ bus->attach_msg[1].hdr.pm_callback = &usb_bus_attach;
+ bus->attach_msg[1].bus = bus;
+
+ bus->suspend_msg[0].hdr.pm_callback = &usb_bus_suspend;
+ bus->suspend_msg[0].bus = bus;
+ bus->suspend_msg[1].hdr.pm_callback = &usb_bus_suspend;
+ bus->suspend_msg[1].bus = bus;
+
+ bus->resume_msg[0].hdr.pm_callback = &usb_bus_resume;
+ bus->resume_msg[0].bus = bus;
+ bus->resume_msg[1].hdr.pm_callback = &usb_bus_resume;
+ bus->resume_msg[1].bus = bus;
+
+ bus->reset_msg[0].hdr.pm_callback = &usb_bus_reset;
+ bus->reset_msg[0].bus = bus;
+ bus->reset_msg[1].hdr.pm_callback = &usb_bus_reset;
+ bus->reset_msg[1].bus = bus;
+
+ bus->shutdown_msg[0].hdr.pm_callback = &usb_bus_shutdown;
+ bus->shutdown_msg[0].bus = bus;
+ bus->shutdown_msg[1].hdr.pm_callback = &usb_bus_shutdown;
+ bus->shutdown_msg[1].bus = bus;
+
+#if USB_HAVE_UGEN
+ SLIST_INIT(&bus->pd_cleanup_list);
+ bus->cleanup_msg[0].hdr.pm_callback = &usb_bus_cleanup;
+ bus->cleanup_msg[0].bus = bus;
+ bus->cleanup_msg[1].hdr.pm_callback = &usb_bus_cleanup;
+ bus->cleanup_msg[1].bus = bus;
+#endif
+
+#if USB_HAVE_PER_BUS_PROCESS
+ /* Create USB explore and callback processes */
+
+ if (usb_proc_create(USB_BUS_GIANT_PROC(bus),
+ &bus->bus_mtx, device_get_nameunit(dev), USB_PRI_MED)) {
+ device_printf(dev, "WARNING: Creation of USB Giant "
+ "callback process failed.\n");
+ } else if (usb_proc_create(USB_BUS_NON_GIANT_ISOC_PROC(bus),
+ &bus->bus_mtx, device_get_nameunit(dev), USB_PRI_HIGHEST)) {
+ device_printf(dev, "WARNING: Creation of USB non-Giant ISOC "
+ "callback process failed.\n");
+ } else if (usb_proc_create(USB_BUS_NON_GIANT_BULK_PROC(bus),
+ &bus->bus_mtx, device_get_nameunit(dev), USB_PRI_HIGH)) {
+ device_printf(dev, "WARNING: Creation of USB non-Giant BULK "
+ "callback process failed.\n");
+ } else if (usb_proc_create(USB_BUS_EXPLORE_PROC(bus),
+ &bus->bus_mtx, device_get_nameunit(dev), USB_PRI_MED)) {
+ device_printf(dev, "WARNING: Creation of USB explore "
+ "process failed.\n");
+ } else if (usb_proc_create(USB_BUS_CONTROL_XFER_PROC(bus),
+ &bus->bus_mtx, device_get_nameunit(dev), USB_PRI_MED)) {
+ device_printf(dev, "WARNING: Creation of USB control transfer "
+ "process failed.\n");
+ } else
+#endif
+ {
+ /* Get final attach going */
+ USB_BUS_LOCK(bus);
+ usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus),
+ &bus->attach_msg[0], &bus->attach_msg[1]);
+ USB_BUS_UNLOCK(bus);
+
+ /* Do initial explore */
+ usb_needs_explore(bus, 1);
+ }
+}
+SYSUNINIT(usb_bus_unload, SI_SUB_KLD, SI_ORDER_ANY, usb_bus_unload, NULL);
+
+/*------------------------------------------------------------------------*
+ * usb_bus_mem_flush_all_cb
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_BUSDMA
+static void
+usb_bus_mem_flush_all_cb(struct usb_bus *bus, struct usb_page_cache *pc,
+ struct usb_page *pg, usb_size_t size, usb_size_t align)
+{
+ usb_pc_cpu_flush(pc);
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usb_bus_mem_flush_all - factored out code
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_BUSDMA
+void
+usb_bus_mem_flush_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb)
+{
+ if (cb) {
+ cb(bus, &usb_bus_mem_flush_all_cb);
+ }
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usb_bus_mem_alloc_all_cb
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_BUSDMA
+static void
+usb_bus_mem_alloc_all_cb(struct usb_bus *bus, struct usb_page_cache *pc,
+ struct usb_page *pg, usb_size_t size, usb_size_t align)
+{
+ /* need to initialize the page cache */
+ pc->tag_parent = bus->dma_parent_tag;
+
+ if (usb_pc_alloc_mem(pc, pg, size, align)) {
+ bus->alloc_failed = 1;
+ }
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usb_bus_mem_alloc_all - factored out code
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+uint8_t
+usb_bus_mem_alloc_all(struct usb_bus *bus, bus_dma_tag_t dmat,
+ usb_bus_mem_cb_t *cb)
+{
+ bus->alloc_failed = 0;
+
+ mtx_init(&bus->bus_mtx, device_get_nameunit(bus->parent),
+ "usb_def_mtx", MTX_DEF | MTX_RECURSE);
+
+ mtx_init(&bus->bus_spin_lock, device_get_nameunit(bus->parent),
+ "usb_spin_mtx", MTX_SPIN | MTX_RECURSE);
+
+ usb_callout_init_mtx(&bus->power_wdog,
+ &bus->bus_mtx, 0);
+
+ TAILQ_INIT(&bus->intr_q.head);
+
+#if USB_HAVE_BUSDMA
+ usb_dma_tag_setup(bus->dma_parent_tag, bus->dma_tags,
+ dmat, &bus->bus_mtx, NULL, bus->dma_bits, USB_BUS_DMA_TAG_MAX);
+#endif
+ if ((bus->devices_max > USB_MAX_DEVICES) ||
+ (bus->devices_max < USB_MIN_DEVICES) ||
+ (bus->devices == NULL)) {
+ DPRINTFN(0, "Devices field has not been "
+ "initialised properly\n");
+ bus->alloc_failed = 1; /* failure */
+ }
+#if USB_HAVE_BUSDMA
+ if (cb) {
+ cb(bus, &usb_bus_mem_alloc_all_cb);
+ }
+#endif
+ if (bus->alloc_failed) {
+ usb_bus_mem_free_all(bus, cb);
+ }
+ return (bus->alloc_failed);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bus_mem_free_all_cb
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_BUSDMA
+static void
+usb_bus_mem_free_all_cb(struct usb_bus *bus, struct usb_page_cache *pc,
+ struct usb_page *pg, usb_size_t size, usb_size_t align)
+{
+ usb_pc_free_mem(pc);
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usb_bus_mem_free_all - factored out code
+ *------------------------------------------------------------------------*/
+void
+usb_bus_mem_free_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb)
+{
+#if USB_HAVE_BUSDMA
+ if (cb) {
+ cb(bus, &usb_bus_mem_free_all_cb);
+ }
+ usb_dma_tag_unsetup(bus->dma_parent_tag);
+#endif
+
+ mtx_destroy(&bus->bus_mtx);
+ mtx_destroy(&bus->bus_spin_lock);
+}
+
+/* convenience wrappers */
+void
+usb_proc_explore_mwait(struct usb_device *udev, void *pm1, void *pm2)
+{
+ usb_proc_mwait(USB_BUS_EXPLORE_PROC(udev->bus), pm1, pm2);
+}
+
+void *
+usb_proc_explore_msignal(struct usb_device *udev, void *pm1, void *pm2)
+{
+ return (usb_proc_msignal(USB_BUS_EXPLORE_PROC(udev->bus), pm1, pm2));
+}
+
+void
+usb_proc_explore_lock(struct usb_device *udev)
+{
+ USB_BUS_LOCK(udev->bus);
+}
+
+void
+usb_proc_explore_unlock(struct usb_device *udev)
+{
+ USB_BUS_UNLOCK(udev->bus);
+}
diff --git a/sys/dev/usb/controller/usb_nop_xceiv.c b/sys/dev/usb/controller/usb_nop_xceiv.c
new file mode 100644
index 000000000000..9821f4b5e92a
--- /dev/null
+++ b/sys/dev/usb/controller/usb_nop_xceiv.c
@@ -0,0 +1,206 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Rubicon Communications, LLC (Netgate)
+ *
+ * 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/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+
+#include <machine/bus.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/clk/clk.h>
+#include <dev/regulator/regulator.h>
+#include <dev/phy/phy_usb.h>
+
+#include "phynode_if.h"
+
+struct usb_nop_xceiv_softc {
+ device_t dev;
+ regulator_t vcc_supply;
+ clk_t clk;
+ uint32_t clk_freq;
+};
+
+static struct ofw_compat_data compat_data[] = {
+ {"usb-nop-xceiv", 1},
+ {NULL, 0}
+};
+
+/* Phy class and methods. */
+static int usb_nop_xceiv_phy_enable(struct phynode *phy, bool enable);
+static phynode_usb_method_t usb_nop_xceiv_phynode_methods[] = {
+ PHYNODEMETHOD(phynode_enable, usb_nop_xceiv_phy_enable),
+
+ PHYNODEMETHOD_END
+};
+DEFINE_CLASS_1(usb_nop_xceiv_phynode, usb_nop_xceiv_phynode_class,
+ usb_nop_xceiv_phynode_methods,
+ sizeof(struct phynode_usb_sc), phynode_usb_class);
+
+static int
+usb_nop_xceiv_phy_enable(struct phynode *phynode, bool enable)
+{
+ struct usb_nop_xceiv_softc *sc;
+ device_t dev;
+ intptr_t phy;
+ int error;
+
+ dev = phynode_get_device(phynode);
+ phy = phynode_get_id(phynode);
+ sc = device_get_softc(dev);
+
+ if (phy != 0)
+ return (ERANGE);
+
+ /* Enable the phy clock */
+ if (sc->clk_freq != 0) {
+ if (enable) {
+ error = clk_set_freq(sc->clk, sc->clk_freq,
+ CLK_SET_ROUND_ANY);
+ if (error != 0) {
+ device_printf(dev, "Cannot set clock to %dMhz\n",
+ sc->clk_freq);
+ goto fail;
+ }
+
+ error = clk_enable(sc->clk);
+ } else
+ error = clk_disable(sc->clk);
+
+ if (error != 0) {
+ device_printf(dev, "Cannot %sable the clock\n",
+ enable ? "En" : "Dis");
+ goto fail;
+ }
+ }
+ if (sc->vcc_supply) {
+ if (enable)
+ error = regulator_enable(sc->vcc_supply);
+ else
+ error = regulator_disable(sc->vcc_supply);
+ if (error != 0) {
+ device_printf(dev, "Cannot %sable the regulator\n",
+ enable ? "En" : "Dis");
+ goto fail;
+ }
+ }
+
+ return (0);
+
+fail:
+ return (ENXIO);
+}
+
+static int
+usb_nop_xceiv_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
+ return (ENXIO);
+
+ device_set_desc(dev, "USB NOP PHY");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+usb_nop_xceiv_attach(device_t dev)
+{
+ struct usb_nop_xceiv_softc *sc;
+ struct phynode *phynode;
+ struct phynode_init_def phy_init;
+ phandle_t node;
+ int error;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ node = ofw_bus_get_node(dev);
+
+ /* Parse the optional properties */
+ OF_getencprop(node, "clock-frequency", &sc->clk_freq, sizeof(uint32_t));
+
+ error = clk_get_by_ofw_name(dev, node, "main_clk", &sc->clk);
+ if (error != 0 && sc->clk_freq != 0) {
+ device_printf(dev, "clock property is mandatory if clock-frequency is present\n");
+ return (ENXIO);
+ }
+
+ regulator_get_by_ofw_property(dev, node, "vcc-supply", &sc->vcc_supply);
+
+ phy_init.id = 0;
+ phy_init.ofw_node = node;
+ phynode = phynode_create(dev, &usb_nop_xceiv_phynode_class,
+ &phy_init);
+ if (phynode == NULL) {
+ device_printf(dev, "failed to create USB NOP PHY\n");
+ return (ENXIO);
+ }
+ if (phynode_register(phynode) == NULL) {
+ device_printf(dev, "failed to create USB NOP PHY\n");
+ return (ENXIO);
+ }
+
+ OF_device_register_xref(OF_xref_from_node(node), dev);
+
+ return (0);
+}
+
+static int
+usb_nop_xceiv_detach(device_t dev)
+{
+
+ return (EBUSY);
+}
+
+static device_method_t usb_nop_xceiv_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, usb_nop_xceiv_probe),
+ DEVMETHOD(device_attach, usb_nop_xceiv_attach),
+ DEVMETHOD(device_detach, usb_nop_xceiv_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t usb_nop_xceiv_driver = {
+ "usb_nop_xceiv",
+ usb_nop_xceiv_methods,
+ sizeof(struct usb_nop_xceiv_softc),
+};
+
+EARLY_DRIVER_MODULE(usb_nop_xceiv, simplebus, usb_nop_xceiv_driver,
+ 0, 0, BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_MIDDLE);
diff --git a/sys/dev/usb/controller/uss820dci.c b/sys/dev/usb/controller/uss820dci.c
new file mode 100644
index 000000000000..12cf4e0abb8e
--- /dev/null
+++ b/sys/dev/usb/controller/uss820dci.c
@@ -0,0 +1,2380 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky <hselasky@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.
+ */
+
+/*
+ * This file contains the driver for the USS820 series USB Device
+ * Controller
+ *
+ * NOTE: The datasheet does not document everything.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#define USB_DEBUG_VAR uss820dcidebug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#include <dev/usb/controller/uss820dci.h>
+
+#define USS820_DCI_BUS2SC(bus) \
+ __containerof(bus, struct uss820dci_softc, sc_bus)
+
+#define USS820_DCI_PC2SC(pc) \
+ USS820_DCI_BUS2SC(USB_DMATAG_TO_XROOT((pc)->tag_parent)->bus)
+
+#define USS820_DCI_THREAD_IRQ \
+ (USS820_SSR_SUSPEND | USS820_SSR_RESUME | USS820_SSR_RESET)
+
+#ifdef USB_DEBUG
+static int uss820dcidebug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uss820dci, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB uss820dci");
+SYSCTL_INT(_hw_usb_uss820dci, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &uss820dcidebug, 0, "uss820dci debug level");
+#endif
+
+#define USS820_DCI_INTR_ENDPT 1
+
+/* prototypes */
+
+static const struct usb_bus_methods uss820dci_bus_methods;
+static const struct usb_pipe_methods uss820dci_device_bulk_methods;
+static const struct usb_pipe_methods uss820dci_device_ctrl_methods;
+static const struct usb_pipe_methods uss820dci_device_intr_methods;
+static const struct usb_pipe_methods uss820dci_device_isoc_fs_methods;
+
+static uss820dci_cmd_t uss820dci_setup_rx;
+static uss820dci_cmd_t uss820dci_data_rx;
+static uss820dci_cmd_t uss820dci_data_tx;
+static uss820dci_cmd_t uss820dci_data_tx_sync;
+static void uss820dci_device_done(struct usb_xfer *, usb_error_t);
+static void uss820dci_do_poll(struct usb_bus *);
+static void uss820dci_standard_done(struct usb_xfer *);
+static void uss820dci_intr_set(struct usb_xfer *, uint8_t);
+static void uss820dci_update_shared_1(struct uss820dci_softc *, uint8_t,
+ uint8_t, uint8_t);
+static void uss820dci_root_intr(struct uss820dci_softc *);
+
+/*
+ * Here is a list of what the USS820D chip can support. The main
+ * limitation is that the sum of the buffer sizes must be less than
+ * 1120 bytes.
+ */
+static const struct usb_hw_ep_profile
+ uss820dci_ep_profile[] = {
+ [0] = {
+ .max_in_frame_size = 32,
+ .max_out_frame_size = 32,
+ .is_simplex = 0,
+ .support_control = 1,
+ },
+ [1] = {
+ .max_in_frame_size = 64,
+ .max_out_frame_size = 64,
+ .is_simplex = 0,
+ .support_multi_buffer = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+ [2] = {
+ .max_in_frame_size = 8,
+ .max_out_frame_size = 8,
+ .is_simplex = 0,
+ .support_multi_buffer = 1,
+ .support_bulk = 1,
+ .support_interrupt = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+ [3] = {
+ .max_in_frame_size = 256,
+ .max_out_frame_size = 256,
+ .is_simplex = 0,
+ .support_multi_buffer = 1,
+ .support_isochronous = 1,
+ .support_in = 1,
+ .support_out = 1,
+ },
+};
+
+static void
+uss820dci_update_shared_1(struct uss820dci_softc *sc, uint8_t reg,
+ uint8_t keep_mask, uint8_t set_mask)
+{
+ uint8_t temp;
+
+ USS820_WRITE_1(sc, USS820_PEND, 1);
+ temp = USS820_READ_1(sc, reg);
+ temp &= (keep_mask);
+ temp |= (set_mask);
+ USS820_WRITE_1(sc, reg, temp);
+ USS820_WRITE_1(sc, USS820_PEND, 0);
+}
+
+static void
+uss820dci_get_hw_ep_profile(struct usb_device *udev,
+ const struct usb_hw_ep_profile **ppf, uint8_t ep_addr)
+{
+ if (ep_addr == 0) {
+ *ppf = uss820dci_ep_profile + 0;
+ } else if (ep_addr < 5) {
+ *ppf = uss820dci_ep_profile + 1;
+ } else if (ep_addr < 7) {
+ *ppf = uss820dci_ep_profile + 2;
+ } else if (ep_addr == 7) {
+ *ppf = uss820dci_ep_profile + 3;
+ } else {
+ *ppf = NULL;
+ }
+}
+
+static void
+uss820dci_pull_up(struct uss820dci_softc *sc)
+{
+ uint8_t temp;
+
+ /* pullup D+, if possible */
+
+ if (!sc->sc_flags.d_pulled_up &&
+ sc->sc_flags.port_powered) {
+ sc->sc_flags.d_pulled_up = 1;
+
+ DPRINTF("\n");
+
+ temp = USS820_READ_1(sc, USS820_MCSR);
+ temp |= USS820_MCSR_DPEN;
+ USS820_WRITE_1(sc, USS820_MCSR, temp);
+ }
+}
+
+static void
+uss820dci_pull_down(struct uss820dci_softc *sc)
+{
+ uint8_t temp;
+
+ /* pulldown D+, if possible */
+
+ if (sc->sc_flags.d_pulled_up) {
+ sc->sc_flags.d_pulled_up = 0;
+
+ DPRINTF("\n");
+
+ temp = USS820_READ_1(sc, USS820_MCSR);
+ temp &= ~USS820_MCSR_DPEN;
+ USS820_WRITE_1(sc, USS820_MCSR, temp);
+ }
+}
+
+static void
+uss820dci_wakeup_peer(struct uss820dci_softc *sc)
+{
+ if (!(sc->sc_flags.status_suspend)) {
+ return;
+ }
+ DPRINTFN(0, "not supported\n");
+}
+
+static void
+uss820dci_set_address(struct uss820dci_softc *sc, uint8_t addr)
+{
+ DPRINTFN(5, "addr=%d\n", addr);
+
+ USS820_WRITE_1(sc, USS820_FADDR, addr);
+}
+
+static uint8_t
+uss820dci_setup_rx(struct uss820dci_softc *sc, struct uss820dci_td *td)
+{
+ struct usb_device_request req;
+ uint16_t count;
+ uint8_t rx_stat;
+ uint8_t temp;
+
+ /* select the correct endpoint */
+ USS820_WRITE_1(sc, USS820_EPINDEX, td->ep_index);
+
+ /* read out FIFO status */
+ rx_stat = USS820_READ_1(sc, USS820_RXSTAT);
+
+ DPRINTFN(5, "rx_stat=0x%02x rem=%u\n", rx_stat, td->remainder);
+
+ if (!(rx_stat & USS820_RXSTAT_RXSETUP)) {
+ goto not_complete;
+ }
+ /* clear did stall */
+ td->did_stall = 0;
+
+ /* clear stall and all I/O */
+ uss820dci_update_shared_1(sc, USS820_EPCON,
+ 0xFF ^ (USS820_EPCON_TXSTL |
+ USS820_EPCON_RXSTL |
+ USS820_EPCON_RXIE |
+ USS820_EPCON_TXOE), 0);
+
+ /* clear end overwrite flag */
+ uss820dci_update_shared_1(sc, USS820_RXSTAT,
+ 0xFF ^ USS820_RXSTAT_EDOVW, 0);
+
+ /* get the packet byte count */
+ count = USS820_READ_1(sc, USS820_RXCNTL);
+ count |= (USS820_READ_1(sc, USS820_RXCNTH) << 8);
+ count &= 0x3FF;
+
+ /* verify data length */
+ if (count != td->remainder) {
+ DPRINTFN(0, "Invalid SETUP packet "
+ "length, %d bytes\n", count);
+ goto setup_not_complete;
+ }
+ if (count != sizeof(req)) {
+ DPRINTFN(0, "Unsupported SETUP packet "
+ "length, %d bytes\n", count);
+ goto setup_not_complete;
+ }
+ /* receive data */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ USS820_RXDAT * USS820_REG_STRIDE, (void *)&req, sizeof(req));
+
+ /* read out FIFO status */
+ rx_stat = USS820_READ_1(sc, USS820_RXSTAT);
+
+ if (rx_stat & (USS820_RXSTAT_EDOVW |
+ USS820_RXSTAT_STOVW)) {
+ DPRINTF("new SETUP packet received\n");
+ return (1); /* not complete */
+ }
+ /* clear receive setup bit */
+ uss820dci_update_shared_1(sc, USS820_RXSTAT,
+ 0xFF ^ (USS820_RXSTAT_RXSETUP |
+ USS820_RXSTAT_EDOVW |
+ USS820_RXSTAT_STOVW), 0);
+
+ /* set RXFFRC bit */
+ temp = USS820_READ_1(sc, USS820_RXCON);
+ temp |= USS820_RXCON_RXFFRC;
+ USS820_WRITE_1(sc, USS820_RXCON, temp);
+
+ /* copy data into real buffer */
+ usbd_copy_in(td->pc, 0, &req, sizeof(req));
+
+ td->offset = sizeof(req);
+ td->remainder = 0;
+
+ /* sneak peek the set address */
+ if ((req.bmRequestType == UT_WRITE_DEVICE) &&
+ (req.bRequest == UR_SET_ADDRESS)) {
+ sc->sc_dv_addr = req.wValue[0] & 0x7F;
+ } else {
+ sc->sc_dv_addr = 0xFF;
+ }
+
+ /* reset TX FIFO */
+ temp = USS820_READ_1(sc, USS820_TXCON);
+ temp |= USS820_TXCON_TXCLR;
+ USS820_WRITE_1(sc, USS820_TXCON, temp);
+ temp &= ~USS820_TXCON_TXCLR;
+ USS820_WRITE_1(sc, USS820_TXCON, temp);
+
+ return (0); /* complete */
+
+setup_not_complete:
+
+ /* set RXFFRC bit */
+ temp = USS820_READ_1(sc, USS820_RXCON);
+ temp |= USS820_RXCON_RXFFRC;
+ USS820_WRITE_1(sc, USS820_RXCON, temp);
+
+ /* FALLTHROUGH */
+
+not_complete:
+ /* abort any ongoing transfer */
+ if (!td->did_stall) {
+ DPRINTFN(5, "stalling\n");
+ /* set stall */
+ uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF,
+ (USS820_EPCON_TXSTL | USS820_EPCON_RXSTL));
+
+ td->did_stall = 1;
+ }
+
+ /* clear end overwrite flag, if any */
+ if (rx_stat & USS820_RXSTAT_RXSETUP) {
+ uss820dci_update_shared_1(sc, USS820_RXSTAT,
+ 0xFF ^ (USS820_RXSTAT_EDOVW |
+ USS820_RXSTAT_STOVW |
+ USS820_RXSTAT_RXSETUP), 0);
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+uss820dci_data_rx(struct uss820dci_softc *sc, struct uss820dci_td *td)
+{
+ struct usb_page_search buf_res;
+ uint16_t count;
+ uint8_t rx_flag;
+ uint8_t rx_stat;
+ uint8_t rx_cntl;
+ uint8_t to;
+ uint8_t got_short;
+
+ to = 2; /* don't loop forever! */
+ got_short = 0;
+
+ /* select the correct endpoint */
+ USS820_WRITE_1(sc, USS820_EPINDEX, td->ep_index);
+
+ /* check if any of the FIFO banks have data */
+repeat:
+ /* read out FIFO flag */
+ rx_flag = USS820_READ_1(sc, USS820_RXFLG);
+ /* read out FIFO status */
+ rx_stat = USS820_READ_1(sc, USS820_RXSTAT);
+
+ DPRINTFN(5, "rx_stat=0x%02x rx_flag=0x%02x rem=%u\n",
+ rx_stat, rx_flag, td->remainder);
+
+ if (rx_stat & (USS820_RXSTAT_RXSETUP |
+ USS820_RXSTAT_RXSOVW |
+ USS820_RXSTAT_EDOVW)) {
+ if (td->remainder == 0 && td->ep_index == 0) {
+ /*
+ * We are actually complete and have
+ * received the next SETUP
+ */
+ DPRINTFN(5, "faking complete\n");
+ return (0); /* complete */
+ }
+ /*
+ * USB Host Aborted the transfer.
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ /* check for errors */
+ if (rx_flag & (USS820_RXFLG_RXOVF |
+ USS820_RXFLG_RXURF)) {
+ DPRINTFN(5, "overflow or underflow\n");
+ /* should not happen */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ /* check status */
+ if (!(rx_flag & (USS820_RXFLG_RXFIF0 |
+ USS820_RXFLG_RXFIF1))) {
+ /* read out EPCON register */
+ /* enable RX input */
+ if (!td->did_enable) {
+ uss820dci_update_shared_1(USS820_DCI_PC2SC(td->pc),
+ USS820_EPCON, 0xFF, USS820_EPCON_RXIE);
+ td->did_enable = 1;
+ }
+ return (1); /* not complete */
+ }
+ /* get the packet byte count */
+ count = USS820_READ_1(sc, USS820_RXCNTL);
+ count |= (USS820_READ_1(sc, USS820_RXCNTH) << 8);
+ count &= 0x3FF;
+
+ DPRINTFN(5, "count=0x%04x\n", count);
+
+ /* verify the packet byte count */
+ if (count != td->max_packet_size) {
+ if (count < td->max_packet_size) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ got_short = 1;
+ } else {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ }
+ /* verify the packet byte count */
+ if (count > td->remainder) {
+ /* invalid USB packet */
+ td->error = 1;
+ return (0); /* we are complete */
+ }
+ while (count > 0) {
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* receive data */
+ bus_space_read_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ USS820_RXDAT * USS820_REG_STRIDE, buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* set RXFFRC bit */
+ rx_cntl = USS820_READ_1(sc, USS820_RXCON);
+ rx_cntl |= USS820_RXCON_RXFFRC;
+ USS820_WRITE_1(sc, USS820_RXCON, rx_cntl);
+
+ /* check if we are complete */
+ if ((td->remainder == 0) || got_short) {
+ if (td->short_pkt) {
+ /* we are complete */
+ return (0);
+ }
+ /* else need to receive a zero length packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+uss820dci_data_tx(struct uss820dci_softc *sc, struct uss820dci_td *td)
+{
+ struct usb_page_search buf_res;
+ uint16_t count;
+ uint16_t count_copy;
+ uint8_t rx_stat;
+ uint8_t tx_flag;
+ uint8_t to;
+
+ /* select the correct endpoint */
+ USS820_WRITE_1(sc, USS820_EPINDEX, td->ep_index);
+
+ to = 2; /* don't loop forever! */
+
+repeat:
+ /* read out TX FIFO flags */
+ tx_flag = USS820_READ_1(sc, USS820_TXFLG);
+
+ DPRINTFN(5, "tx_flag=0x%02x rem=%u\n", tx_flag, td->remainder);
+
+ if (td->ep_index == 0) {
+ /* read out RX FIFO status last */
+ rx_stat = USS820_READ_1(sc, USS820_RXSTAT);
+
+ DPRINTFN(5, "rx_stat=0x%02x\n", rx_stat);
+
+ if (rx_stat & (USS820_RXSTAT_RXSETUP |
+ USS820_RXSTAT_RXSOVW |
+ USS820_RXSTAT_EDOVW)) {
+ /*
+ * The current transfer was aborted by the USB
+ * Host:
+ */
+ td->error = 1;
+ return (0); /* complete */
+ }
+ }
+ if (tx_flag & (USS820_TXFLG_TXOVF |
+ USS820_TXFLG_TXURF)) {
+ td->error = 1;
+ return (0); /* complete */
+ }
+ if (tx_flag & USS820_TXFLG_TXFIF0) {
+ if (tx_flag & USS820_TXFLG_TXFIF1) {
+ return (1); /* not complete */
+ }
+ }
+ if ((!td->support_multi_buffer) &&
+ (tx_flag & (USS820_TXFLG_TXFIF0 |
+ USS820_TXFLG_TXFIF1))) {
+ return (1); /* not complete */
+ }
+ count = td->max_packet_size;
+ if (td->remainder < count) {
+ /* we have a short packet */
+ td->short_pkt = 1;
+ count = td->remainder;
+ }
+ count_copy = count;
+ while (count > 0) {
+ usbd_get_page(td->pc, td->offset, &buf_res);
+
+ /* get correct length */
+ if (buf_res.length > count) {
+ buf_res.length = count;
+ }
+ /* transmit data */
+ bus_space_write_multi_1(sc->sc_io_tag, sc->sc_io_hdl,
+ USS820_TXDAT * USS820_REG_STRIDE, buf_res.buffer, buf_res.length);
+
+ /* update counters */
+ count -= buf_res.length;
+ td->offset += buf_res.length;
+ td->remainder -= buf_res.length;
+ }
+
+ /* post-write high packet byte count first */
+ USS820_WRITE_1(sc, USS820_TXCNTH, count_copy >> 8);
+
+ /* post-write low packet byte count last */
+ USS820_WRITE_1(sc, USS820_TXCNTL, count_copy);
+
+ /*
+ * Enable TX output, which must happen after that we have written
+ * data into the FIFO. This is undocumented.
+ */
+ if (!td->did_enable) {
+ uss820dci_update_shared_1(USS820_DCI_PC2SC(td->pc),
+ USS820_EPCON, 0xFF, USS820_EPCON_TXOE);
+ td->did_enable = 1;
+ }
+ /* check remainder */
+ if (td->remainder == 0) {
+ if (td->short_pkt) {
+ return (0); /* complete */
+ }
+ /* else we need to transmit a short packet */
+ }
+ if (--to) {
+ goto repeat;
+ }
+ return (1); /* not complete */
+}
+
+static uint8_t
+uss820dci_data_tx_sync(struct uss820dci_softc *sc, struct uss820dci_td *td)
+{
+ uint8_t rx_stat;
+ uint8_t tx_flag;
+
+ /* select the correct endpoint */
+ USS820_WRITE_1(sc, USS820_EPINDEX, td->ep_index);
+
+ /* read out TX FIFO flag */
+ tx_flag = USS820_READ_1(sc, USS820_TXFLG);
+
+ if (td->ep_index == 0) {
+ /* read out RX FIFO status last */
+ rx_stat = USS820_READ_1(sc, USS820_RXSTAT);
+
+ DPRINTFN(5, "rx_stat=0x%02x rem=%u\n", rx_stat, td->remainder);
+
+ if (rx_stat & (USS820_RXSTAT_RXSETUP |
+ USS820_RXSTAT_RXSOVW |
+ USS820_RXSTAT_EDOVW)) {
+ DPRINTFN(5, "faking complete\n");
+ /* Race condition */
+ return (0); /* complete */
+ }
+ }
+ DPRINTFN(5, "tx_flag=0x%02x rem=%u\n", tx_flag, td->remainder);
+
+ if (tx_flag & (USS820_TXFLG_TXOVF |
+ USS820_TXFLG_TXURF)) {
+ td->error = 1;
+ return (0); /* complete */
+ }
+ if (tx_flag & (USS820_TXFLG_TXFIF0 |
+ USS820_TXFLG_TXFIF1)) {
+ return (1); /* not complete */
+ }
+ if (td->ep_index == 0 && sc->sc_dv_addr != 0xFF) {
+ /* write function address */
+ uss820dci_set_address(sc, sc->sc_dv_addr);
+ }
+ return (0); /* complete */
+}
+
+static void
+uss820dci_xfer_do_fifo(struct usb_xfer *xfer)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+ struct uss820dci_td *td;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+ if (td == NULL)
+ return;
+
+ while (1) {
+ if ((td->func) (sc, td)) {
+ /* operation in progress */
+ break;
+ }
+ if (((void *)td) == xfer->td_transfer_last) {
+ goto done;
+ }
+ if (td->error) {
+ goto done;
+ } else if (td->remainder > 0) {
+ /*
+ * We had a short transfer. If there is no alternate
+ * next, stop processing !
+ */
+ if (!td->alt_next) {
+ goto done;
+ }
+ }
+ /*
+ * Fetch the next transfer descriptor.
+ */
+ td = td->obj_next;
+ xfer->td_transfer_cache = td;
+ }
+ return;
+
+done:
+ /* compute all actual lengths */
+ xfer->td_transfer_cache = NULL;
+ sc->sc_xfer_complete = 1;
+}
+
+static uint8_t
+uss820dci_xfer_do_complete(struct usb_xfer *xfer)
+{
+ struct uss820dci_td *td;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+ if (td == NULL) {
+ /* compute all actual lengths */
+ uss820dci_standard_done(xfer);
+ return(1);
+ }
+ return (0);
+}
+
+static void
+uss820dci_interrupt_poll_locked(struct uss820dci_softc *sc)
+{
+ struct usb_xfer *xfer;
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry)
+ uss820dci_xfer_do_fifo(xfer);
+}
+
+static void
+uss820dci_interrupt_complete_locked(struct uss820dci_softc *sc)
+{
+ struct usb_xfer *xfer;
+repeat:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ if (uss820dci_xfer_do_complete(xfer))
+ goto repeat;
+ }
+}
+
+static void
+uss820dci_wait_suspend(struct uss820dci_softc *sc, uint8_t on)
+{
+ uint8_t scr;
+ uint8_t scratch;
+
+ scr = USS820_READ_1(sc, USS820_SCR);
+ scratch = USS820_READ_1(sc, USS820_SCRATCH);
+
+ if (on) {
+ scr |= USS820_SCR_IE_SUSP;
+ scratch &= ~USS820_SCRATCH_IE_RESUME;
+ } else {
+ scr &= ~USS820_SCR_IE_SUSP;
+ scratch |= USS820_SCRATCH_IE_RESUME;
+ }
+
+ USS820_WRITE_1(sc, USS820_SCR, scr);
+ USS820_WRITE_1(sc, USS820_SCRATCH, scratch);
+}
+
+int
+uss820dci_filter_interrupt(void *arg)
+{
+ struct uss820dci_softc *sc = arg;
+ int retval = FILTER_HANDLED;
+ uint8_t ssr;
+
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+
+ ssr = USS820_READ_1(sc, USS820_SSR);
+ uss820dci_update_shared_1(sc, USS820_SSR, USS820_DCI_THREAD_IRQ, 0);
+
+ if (ssr & USS820_DCI_THREAD_IRQ)
+ retval = FILTER_SCHEDULE_THREAD;
+
+ /* poll FIFOs, if any */
+ uss820dci_interrupt_poll_locked(sc);
+
+ if (sc->sc_xfer_complete != 0)
+ retval = FILTER_SCHEDULE_THREAD;
+
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+
+ return (retval);
+}
+
+void
+uss820dci_interrupt(void *arg)
+{
+ struct uss820dci_softc *sc = arg;
+ uint8_t ssr;
+ uint8_t event;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+
+ ssr = USS820_READ_1(sc, USS820_SSR);
+
+ /* acknowledge all interrupts */
+
+ uss820dci_update_shared_1(sc, USS820_SSR, ~USS820_DCI_THREAD_IRQ, 0);
+
+ /* check for any bus state change interrupts */
+
+ if (ssr & USS820_DCI_THREAD_IRQ) {
+ event = 0;
+
+ if (ssr & USS820_SSR_RESET) {
+ sc->sc_flags.status_bus_reset = 1;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ /* disable resume interrupt */
+ uss820dci_wait_suspend(sc, 1);
+
+ event = 1;
+ }
+ /*
+ * If "RESUME" and "SUSPEND" is set at the same time
+ * we interpret that like "RESUME". Resume is set when
+ * there is at least 3 milliseconds of inactivity on
+ * the USB BUS.
+ */
+ if (ssr & USS820_SSR_RESUME) {
+ if (sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 1;
+ /* disable resume interrupt */
+ uss820dci_wait_suspend(sc, 1);
+ event = 1;
+ }
+ } else if (ssr & USS820_SSR_SUSPEND) {
+ if (!sc->sc_flags.status_suspend) {
+ sc->sc_flags.status_suspend = 1;
+ sc->sc_flags.change_suspend = 1;
+ /* enable resume interrupt */
+ uss820dci_wait_suspend(sc, 0);
+ event = 1;
+ }
+ }
+ if (event) {
+ DPRINTF("real bus interrupt 0x%02x\n", ssr);
+
+ /* complete root HUB interrupt endpoint */
+ uss820dci_root_intr(sc);
+ }
+ }
+ /* acknowledge all SBI interrupts */
+ uss820dci_update_shared_1(sc, USS820_SBI, 0, 0);
+
+ /* acknowledge all SBI1 interrupts */
+ uss820dci_update_shared_1(sc, USS820_SBI1, 0, 0);
+
+ if (sc->sc_xfer_complete != 0) {
+ sc->sc_xfer_complete = 0;
+
+ /* complete FIFOs, if any */
+ uss820dci_interrupt_complete_locked(sc);
+ }
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+uss820dci_setup_standard_chain_sub(struct uss820_std_temp *temp)
+{
+ struct uss820dci_td *td;
+
+ /* get current Transfer Descriptor */
+ td = temp->td_next;
+ temp->td = td;
+
+ /* prepare for next TD */
+ temp->td_next = td->obj_next;
+
+ /* fill out the Transfer Descriptor */
+ td->func = temp->func;
+ td->pc = temp->pc;
+ td->offset = temp->offset;
+ td->remainder = temp->len;
+ td->error = 0;
+ td->did_enable = 0;
+ td->did_stall = temp->did_stall;
+ td->short_pkt = temp->short_pkt;
+ td->alt_next = temp->setup_alt_next;
+}
+
+static void
+uss820dci_setup_standard_chain(struct usb_xfer *xfer)
+{
+ struct uss820_std_temp temp;
+ struct uss820dci_td *td;
+ uint32_t x;
+
+ DPRINTFN(9, "addr=%d endpt=%d sumlen=%d speed=%d\n",
+ xfer->address, UE_GET_ADDR(xfer->endpointno),
+ xfer->sumlen, usbd_get_speed(xfer->xroot->udev));
+
+ temp.max_frame_size = xfer->max_frame_size;
+
+ td = xfer->td_start[0];
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ /* setup temp */
+
+ temp.pc = NULL;
+ temp.td = NULL;
+ temp.td_next = xfer->td_start[0];
+ temp.offset = 0;
+ temp.setup_alt_next = xfer->flags_int.short_frames_ok ||
+ xfer->flags_int.isochronous_xfr;
+ temp.did_stall = !xfer->flags_int.control_stall;
+
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ temp.func = &uss820dci_setup_rx;
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.short_pkt = temp.len ? 1 : 0;
+ /* check for last frame */
+ if (xfer->nframes == 1) {
+ /* no STATUS stage yet, SETUP is last */
+ if (xfer->flags_int.control_act)
+ temp.setup_alt_next = 0;
+ }
+
+ uss820dci_setup_standard_chain_sub(&temp);
+ }
+ x = 1;
+ } else {
+ x = 0;
+ }
+
+ if (x != xfer->nframes) {
+ if (xfer->endpointno & UE_DIR_IN) {
+ temp.func = &uss820dci_data_tx;
+ } else {
+ temp.func = &uss820dci_data_rx;
+ }
+
+ /* setup "pc" pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ while (x != xfer->nframes) {
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+
+ x++;
+
+ if (x == xfer->nframes) {
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_act) {
+ temp.setup_alt_next = 0;
+ }
+ } else {
+ temp.setup_alt_next = 0;
+ }
+ }
+ if (temp.len == 0) {
+ /* make sure that we send an USB packet */
+
+ temp.short_pkt = 0;
+
+ } else {
+ /* regular data transfer */
+
+ temp.short_pkt = (xfer->flags.force_short_xfer) ? 0 : 1;
+ }
+
+ uss820dci_setup_standard_chain_sub(&temp);
+
+ if (xfer->flags_int.isochronous_xfr) {
+ temp.offset += temp.len;
+ } else {
+ /* get next Page Cache pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ }
+
+ /* check for control transfer */
+ if (xfer->flags_int.control_xfr) {
+ uint8_t need_sync;
+
+ /* always setup a valid "pc" pointer for status and sync */
+ temp.pc = xfer->frbuffers + 0;
+ temp.len = 0;
+ temp.short_pkt = 0;
+ temp.setup_alt_next = 0;
+
+ /* check if we should append a status stage */
+ if (!xfer->flags_int.control_act) {
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ if (xfer->endpointno & UE_DIR_IN) {
+ temp.func = &uss820dci_data_rx;
+ need_sync = 0;
+ } else {
+ temp.func = &uss820dci_data_tx;
+ need_sync = 1;
+ }
+ temp.len = 0;
+ temp.short_pkt = 0;
+
+ uss820dci_setup_standard_chain_sub(&temp);
+ if (need_sync) {
+ /* we need a SYNC point after TX */
+ temp.func = &uss820dci_data_tx_sync;
+ uss820dci_setup_standard_chain_sub(&temp);
+ }
+ }
+ }
+ /* must have at least one frame! */
+ td = temp.td;
+ xfer->td_transfer_last = td;
+}
+
+static void
+uss820dci_timeout(void *arg)
+{
+ struct usb_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ uss820dci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+uss820dci_intr_set(struct usb_xfer *xfer, uint8_t set)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+ uint8_t ep_no = (xfer->endpointno & UE_ADDR);
+ uint8_t ep_reg;
+ uint8_t temp;
+
+ DPRINTFN(15, "endpoint 0x%02x\n", xfer->endpointno);
+
+ if (ep_no > 3) {
+ ep_reg = USS820_SBIE1;
+ } else {
+ ep_reg = USS820_SBIE;
+ }
+
+ ep_no &= 3;
+ ep_no = 1 << (2 * ep_no);
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ ep_no <<= 1; /* RX interrupt only */
+ } else {
+ ep_no |= (ep_no << 1); /* RX and TX interrupt */
+ }
+ } else {
+ if (!(xfer->endpointno & UE_DIR_IN)) {
+ ep_no <<= 1;
+ }
+ }
+ temp = USS820_READ_1(sc, ep_reg);
+ if (set) {
+ temp |= ep_no;
+ } else {
+ temp &= ~ep_no;
+ }
+ USS820_WRITE_1(sc, ep_reg, temp);
+}
+
+static void
+uss820dci_start_standard_chain(struct usb_xfer *xfer)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+
+ DPRINTFN(9, "\n");
+
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+
+ /* poll one time */
+ uss820dci_xfer_do_fifo(xfer);
+
+ if (uss820dci_xfer_do_complete(xfer) == 0) {
+ /*
+ * Only enable the endpoint interrupt when we are
+ * actually waiting for data, hence we are dealing
+ * with level triggered interrupts !
+ */
+ uss820dci_intr_set(xfer, 1);
+
+ /* put transfer on interrupt queue */
+ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0) {
+ usbd_transfer_timeout_ms(xfer,
+ &uss820dci_timeout, xfer->timeout);
+ }
+ }
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+}
+
+static void
+uss820dci_root_intr(struct uss820dci_softc *sc)
+{
+ DPRINTFN(9, "\n");
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* set port bit */
+ sc->sc_hub_idata[0] = 0x02; /* we only have one port */
+
+ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
+ sizeof(sc->sc_hub_idata));
+}
+
+static usb_error_t
+uss820dci_standard_done_sub(struct usb_xfer *xfer)
+{
+ struct uss820dci_td *td;
+ uint32_t len;
+ uint8_t error;
+
+ DPRINTFN(9, "\n");
+
+ td = xfer->td_transfer_cache;
+
+ do {
+ len = td->remainder;
+
+ if (xfer->aframes != xfer->nframes) {
+ /*
+ * Verify the length and subtract
+ * the remainder from "frlengths[]":
+ */
+ if (len > xfer->frlengths[xfer->aframes]) {
+ td->error = 1;
+ } else {
+ xfer->frlengths[xfer->aframes] -= len;
+ }
+ }
+ /* Check for transfer error */
+ if (td->error) {
+ /* the transfer is finished */
+ error = 1;
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len > 0) {
+ if (xfer->flags_int.short_frames_ok ||
+ xfer->flags_int.isochronous_xfr) {
+ /* follow alt next */
+ if (td->alt_next) {
+ td = td->obj_next;
+ } else {
+ td = NULL;
+ }
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ error = 0;
+ break;
+ }
+ td = td->obj_next;
+
+ /* this USB frame is complete */
+ error = 0;
+ break;
+
+ } while (0);
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ return (error ?
+ USB_ERR_STALLED : USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+uss820dci_standard_done(struct usb_xfer *xfer)
+{
+ usb_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
+ xfer, xfer->endpoint);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr) {
+ err = uss820dci_standard_done_sub(xfer);
+ }
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+ while (xfer->aframes != xfer->nframes) {
+ err = uss820dci_standard_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL) {
+ goto done;
+ }
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+ err = uss820dci_standard_done_sub(xfer);
+ }
+done:
+ uss820dci_device_done(xfer, err);
+}
+
+/*------------------------------------------------------------------------*
+ * uss820dci_device_done
+ *
+ * NOTE: this function can be called more than one time on the
+ * same USB transfer!
+ *------------------------------------------------------------------------*/
+static void
+uss820dci_device_done(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n",
+ xfer, xfer->endpoint, error);
+
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+
+ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
+ uss820dci_intr_set(xfer, 0);
+ }
+ /* dequeue transfer and start next transfer */
+ usbd_transfer_done(xfer, error);
+
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+}
+
+static void
+uss820dci_xfer_stall(struct usb_xfer *xfer)
+{
+ uss820dci_device_done(xfer, USB_ERR_STALLED);
+}
+
+static void
+uss820dci_set_stall(struct usb_device *udev,
+ struct usb_endpoint *ep, uint8_t *did_stall)
+{
+ struct uss820dci_softc *sc;
+ uint8_t ep_no;
+ uint8_t ep_type;
+ uint8_t ep_dir;
+ uint8_t temp;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ DPRINTFN(5, "endpoint=%p\n", ep);
+
+ /* set FORCESTALL */
+ sc = USS820_DCI_BUS2SC(udev->bus);
+ ep_no = (ep->edesc->bEndpointAddress & UE_ADDR);
+ ep_dir = (ep->edesc->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT));
+ ep_type = (ep->edesc->bmAttributes & UE_XFERTYPE);
+
+ if (ep_type == UE_CONTROL) {
+ /* should not happen */
+ return;
+ }
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+ USS820_WRITE_1(sc, USS820_EPINDEX, ep_no);
+
+ if (ep_dir == UE_DIR_IN) {
+ temp = USS820_EPCON_TXSTL;
+ } else {
+ temp = USS820_EPCON_RXSTL;
+ }
+ uss820dci_update_shared_1(sc, USS820_EPCON, 0xFF, temp);
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+}
+
+static void
+uss820dci_clear_stall_sub(struct uss820dci_softc *sc,
+ uint8_t ep_no, uint8_t ep_type, uint8_t ep_dir)
+{
+ uint8_t temp;
+
+ if (ep_type == UE_CONTROL) {
+ /* clearing stall is not needed */
+ return;
+ }
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+
+ /* select endpoint index */
+ USS820_WRITE_1(sc, USS820_EPINDEX, ep_no);
+
+ /* clear stall and disable I/O transfers */
+ if (ep_dir == UE_DIR_IN) {
+ temp = 0xFF ^ (USS820_EPCON_TXOE |
+ USS820_EPCON_TXSTL);
+ } else {
+ temp = 0xFF ^ (USS820_EPCON_RXIE |
+ USS820_EPCON_RXSTL);
+ }
+ uss820dci_update_shared_1(sc, USS820_EPCON, temp, 0);
+
+ if (ep_dir == UE_DIR_IN) {
+ /* reset data toggle */
+ USS820_WRITE_1(sc, USS820_TXSTAT,
+ USS820_TXSTAT_TXSOVW);
+
+ /* reset FIFO */
+ temp = USS820_READ_1(sc, USS820_TXCON);
+ temp |= USS820_TXCON_TXCLR;
+ USS820_WRITE_1(sc, USS820_TXCON, temp);
+ temp &= ~USS820_TXCON_TXCLR;
+ USS820_WRITE_1(sc, USS820_TXCON, temp);
+ } else {
+ /* reset data toggle */
+ uss820dci_update_shared_1(sc, USS820_RXSTAT,
+ 0, USS820_RXSTAT_RXSOVW);
+
+ /* reset FIFO */
+ temp = USS820_READ_1(sc, USS820_RXCON);
+ temp |= USS820_RXCON_RXCLR;
+ temp &= ~USS820_RXCON_RXFFRC;
+ USS820_WRITE_1(sc, USS820_RXCON, temp);
+ temp &= ~USS820_RXCON_RXCLR;
+ USS820_WRITE_1(sc, USS820_RXCON, temp);
+ }
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+}
+
+static void
+uss820dci_clear_stall(struct usb_device *udev, struct usb_endpoint *ep)
+{
+ struct uss820dci_softc *sc;
+ struct usb_endpoint_descriptor *ed;
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ DPRINTFN(5, "endpoint=%p\n", ep);
+
+ /* check mode */
+ if (udev->flags.usb_mode != USB_MODE_DEVICE) {
+ /* not supported */
+ return;
+ }
+ /* get softc */
+ sc = USS820_DCI_BUS2SC(udev->bus);
+
+ /* get endpoint descriptor */
+ ed = ep->edesc;
+
+ /* reset endpoint */
+ uss820dci_clear_stall_sub(sc,
+ (ed->bEndpointAddress & UE_ADDR),
+ (ed->bmAttributes & UE_XFERTYPE),
+ (ed->bEndpointAddress & (UE_DIR_IN | UE_DIR_OUT)));
+}
+
+usb_error_t
+uss820dci_init(struct uss820dci_softc *sc)
+{
+ const struct usb_hw_ep_profile *pf;
+ uint8_t n;
+ uint8_t temp;
+
+ DPRINTF("start\n");
+
+ /* set up the bus structure */
+ sc->sc_bus.usbrev = USB_REV_1_1;
+ sc->sc_bus.methods = &uss820dci_bus_methods;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* we always have VBUS */
+ sc->sc_flags.status_vbus = 1;
+
+ /* reset the chip */
+ USS820_WRITE_1(sc, USS820_SCR, USS820_SCR_SRESET);
+ DELAY(100);
+ USS820_WRITE_1(sc, USS820_SCR, 0);
+
+ /* wait for reset to complete */
+ for (n = 0;; n++) {
+ temp = USS820_READ_1(sc, USS820_MCSR);
+
+ if (temp & USS820_MCSR_INIT) {
+ break;
+ }
+ if (n == 100) {
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ return (USB_ERR_INVAL);
+ }
+ /* wait a little for things to stabilise */
+ DELAY(100);
+ }
+
+ /* do a pulldown */
+ uss820dci_pull_down(sc);
+
+ /* wait 10ms for pulldown to stabilise */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 100);
+
+ /* check hardware revision */
+ temp = USS820_READ_1(sc, USS820_REV);
+
+ if (temp < 0x13) {
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ return (USB_ERR_INVAL);
+ }
+ /* enable interrupts */
+ USS820_WRITE_1(sc, USS820_SCR,
+ USS820_SCR_T_IRQ |
+ USS820_SCR_IE_RESET |
+ /* USS820_SCR_RWUPE | */
+ USS820_SCR_IE_SUSP |
+ USS820_SCR_IRQPOL);
+
+ /* enable interrupts */
+ USS820_WRITE_1(sc, USS820_SCRATCH,
+ USS820_SCRATCH_IE_RESUME);
+
+ /* enable features */
+ USS820_WRITE_1(sc, USS820_MCSR,
+ USS820_MCSR_BDFEAT |
+ USS820_MCSR_FEAT);
+
+ sc->sc_flags.mcsr_feat = 1;
+
+ /* disable interrupts */
+ USS820_WRITE_1(sc, USS820_SBIE, 0);
+
+ /* disable interrupts */
+ USS820_WRITE_1(sc, USS820_SBIE1, 0);
+
+ /* disable all endpoints */
+ for (n = 0; n != USS820_EP_MAX; n++) {
+ /* select endpoint */
+ USS820_WRITE_1(sc, USS820_EPINDEX, n);
+
+ /* disable endpoint */
+ uss820dci_update_shared_1(sc, USS820_EPCON, 0, 0);
+ }
+
+ /*
+ * Initialise default values for some registers that cannot be
+ * changed during operation!
+ */
+ for (n = 0; n != USS820_EP_MAX; n++) {
+ uss820dci_get_hw_ep_profile(NULL, &pf, n);
+
+ /* the maximum frame sizes should be the same */
+ if (pf->max_in_frame_size != pf->max_out_frame_size) {
+ DPRINTF("Max frame size mismatch %u != %u\n",
+ pf->max_in_frame_size, pf->max_out_frame_size);
+ }
+ if (pf->support_isochronous) {
+ if (pf->max_in_frame_size <= 64) {
+ temp = (USS820_TXCON_FFSZ_16_64 |
+ USS820_TXCON_TXISO |
+ USS820_TXCON_ATM);
+ } else if (pf->max_in_frame_size <= 256) {
+ temp = (USS820_TXCON_FFSZ_64_256 |
+ USS820_TXCON_TXISO |
+ USS820_TXCON_ATM);
+ } else if (pf->max_in_frame_size <= 512) {
+ temp = (USS820_TXCON_FFSZ_8_512 |
+ USS820_TXCON_TXISO |
+ USS820_TXCON_ATM);
+ } else { /* 1024 bytes */
+ temp = (USS820_TXCON_FFSZ_32_1024 |
+ USS820_TXCON_TXISO |
+ USS820_TXCON_ATM);
+ }
+ } else {
+ if ((pf->max_in_frame_size <= 8) &&
+ (sc->sc_flags.mcsr_feat)) {
+ temp = (USS820_TXCON_FFSZ_8_512 |
+ USS820_TXCON_ATM);
+ } else if (pf->max_in_frame_size <= 16) {
+ temp = (USS820_TXCON_FFSZ_16_64 |
+ USS820_TXCON_ATM);
+ } else if ((pf->max_in_frame_size <= 32) &&
+ (sc->sc_flags.mcsr_feat)) {
+ temp = (USS820_TXCON_FFSZ_32_1024 |
+ USS820_TXCON_ATM);
+ } else { /* 64 bytes */
+ temp = (USS820_TXCON_FFSZ_64_256 |
+ USS820_TXCON_ATM);
+ }
+ }
+
+ /* need to configure the chip early */
+
+ USS820_WRITE_1(sc, USS820_EPINDEX, n);
+ USS820_WRITE_1(sc, USS820_TXCON, temp);
+ USS820_WRITE_1(sc, USS820_RXCON, temp);
+
+ if (pf->support_control) {
+ temp = USS820_EPCON_CTLEP |
+ USS820_EPCON_RXSPM |
+ USS820_EPCON_RXIE |
+ USS820_EPCON_RXEPEN |
+ USS820_EPCON_TXOE |
+ USS820_EPCON_TXEPEN;
+ } else {
+ temp = USS820_EPCON_RXEPEN | USS820_EPCON_TXEPEN;
+ }
+
+ uss820dci_update_shared_1(sc, USS820_EPCON, 0, temp);
+ }
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ /* catch any lost interrupts */
+
+ uss820dci_do_poll(&sc->sc_bus);
+
+ return (0); /* success */
+}
+
+void
+uss820dci_uninit(struct uss820dci_softc *sc)
+{
+ uint8_t temp;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* disable all interrupts */
+ temp = USS820_READ_1(sc, USS820_SCR);
+ temp &= ~USS820_SCR_T_IRQ;
+ USS820_WRITE_1(sc, USS820_SCR, temp);
+
+ sc->sc_flags.port_powered = 0;
+ sc->sc_flags.status_vbus = 0;
+ sc->sc_flags.status_bus_reset = 0;
+ sc->sc_flags.status_suspend = 0;
+ sc->sc_flags.change_suspend = 0;
+ sc->sc_flags.change_connect = 1;
+
+ uss820dci_pull_down(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+uss820dci_suspend(struct uss820dci_softc *sc)
+{
+ /* TODO */
+}
+
+static void
+uss820dci_resume(struct uss820dci_softc *sc)
+{
+ /* TODO */
+}
+
+static void
+uss820dci_do_poll(struct usb_bus *bus)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ USB_BUS_SPIN_LOCK(&sc->sc_bus);
+ uss820dci_interrupt_poll_locked(sc);
+ uss820dci_interrupt_complete_locked(sc);
+ USB_BUS_SPIN_UNLOCK(&sc->sc_bus);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * uss820dci bulk support
+ *------------------------------------------------------------------------*/
+static void
+uss820dci_device_bulk_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_bulk_close(struct usb_xfer *xfer)
+{
+ uss820dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uss820dci_device_bulk_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_bulk_start(struct usb_xfer *xfer)
+{
+ /* setup TDs */
+ uss820dci_setup_standard_chain(xfer);
+ uss820dci_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods uss820dci_device_bulk_methods =
+{
+ .open = uss820dci_device_bulk_open,
+ .close = uss820dci_device_bulk_close,
+ .enter = uss820dci_device_bulk_enter,
+ .start = uss820dci_device_bulk_start,
+};
+
+/*------------------------------------------------------------------------*
+ * uss820dci control support
+ *------------------------------------------------------------------------*/
+static void
+uss820dci_device_ctrl_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_ctrl_close(struct usb_xfer *xfer)
+{
+ uss820dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uss820dci_device_ctrl_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_ctrl_start(struct usb_xfer *xfer)
+{
+ /* setup TDs */
+ uss820dci_setup_standard_chain(xfer);
+ uss820dci_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods uss820dci_device_ctrl_methods =
+{
+ .open = uss820dci_device_ctrl_open,
+ .close = uss820dci_device_ctrl_close,
+ .enter = uss820dci_device_ctrl_enter,
+ .start = uss820dci_device_ctrl_start,
+};
+
+/*------------------------------------------------------------------------*
+ * uss820dci interrupt support
+ *------------------------------------------------------------------------*/
+static void
+uss820dci_device_intr_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_intr_close(struct usb_xfer *xfer)
+{
+ uss820dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uss820dci_device_intr_enter(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_intr_start(struct usb_xfer *xfer)
+{
+ /* setup TDs */
+ uss820dci_setup_standard_chain(xfer);
+ uss820dci_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods uss820dci_device_intr_methods =
+{
+ .open = uss820dci_device_intr_open,
+ .close = uss820dci_device_intr_close,
+ .enter = uss820dci_device_intr_enter,
+ .start = uss820dci_device_intr_start,
+};
+
+/*------------------------------------------------------------------------*
+ * uss820dci full speed isochronous support
+ *------------------------------------------------------------------------*/
+static void
+uss820dci_device_isoc_fs_open(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_device_isoc_fs_close(struct usb_xfer *xfer)
+{
+ uss820dci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+uss820dci_device_isoc_fs_enter(struct usb_xfer *xfer)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(xfer->xroot->bus);
+ uint32_t nframes;
+
+ DPRINTFN(6, "xfer=%p next=%d nframes=%d\n",
+ xfer, xfer->endpoint->isoc_next, xfer->nframes);
+
+ /* get the current frame index - we don't need the high bits */
+
+ nframes = USS820_READ_1(sc, USS820_SOFL);
+
+ if (usbd_xfer_get_isochronous_start_frame(
+ xfer, nframes, 0, 1, USS820_SOFL_MASK, NULL))
+ DPRINTFN(3, "start next=%d\n", xfer->endpoint->isoc_next);
+
+ /* setup TDs */
+ uss820dci_setup_standard_chain(xfer);
+}
+
+static void
+uss820dci_device_isoc_fs_start(struct usb_xfer *xfer)
+{
+ /* start TD chain */
+ uss820dci_start_standard_chain(xfer);
+}
+
+static const struct usb_pipe_methods uss820dci_device_isoc_fs_methods =
+{
+ .open = uss820dci_device_isoc_fs_open,
+ .close = uss820dci_device_isoc_fs_close,
+ .enter = uss820dci_device_isoc_fs_enter,
+ .start = uss820dci_device_isoc_fs_start,
+};
+
+/*------------------------------------------------------------------------*
+ * uss820dci root control support
+ *------------------------------------------------------------------------*
+ * Simulate a hardware HUB by handling all the necessary requests.
+ *------------------------------------------------------------------------*/
+
+static const struct usb_device_descriptor uss820dci_devd = {
+ .bLength = sizeof(struct usb_device_descriptor),
+ .bDescriptorType = UDESC_DEVICE,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_FSHUB,
+ .bMaxPacketSize = 64,
+ .bcdDevice = {0x00, 0x01},
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .bNumConfigurations = 1,
+};
+
+static const struct usb_device_qualifier uss820dci_odevd = {
+ .bLength = sizeof(struct usb_device_qualifier),
+ .bDescriptorType = UDESC_DEVICE_QUALIFIER,
+ .bcdUSB = {0x00, 0x02},
+ .bDeviceClass = UDCLASS_HUB,
+ .bDeviceSubClass = UDSUBCLASS_HUB,
+ .bDeviceProtocol = UDPROTO_FSHUB,
+ .bMaxPacketSize0 = 0,
+ .bNumConfigurations = 0,
+};
+
+static const struct uss820dci_config_desc uss820dci_confd = {
+ .confd = {
+ .bLength = sizeof(struct usb_config_descriptor),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(uss820dci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0,
+ },
+ .ifcd = {
+ .bLength = sizeof(struct usb_interface_descriptor),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = 0,
+ },
+
+ .endpd = {
+ .bLength = sizeof(struct usb_endpoint_descriptor),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = (UE_DIR_IN | USS820_DCI_INTR_ENDPT),
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 8,
+ .bInterval = 255,
+ },
+};
+#define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) }
+
+static const struct usb_hub_descriptor_min uss820dci_hubd = {
+ .bDescLength = sizeof(uss820dci_hubd),
+ .bDescriptorType = UDESC_HUB,
+ .bNbrPorts = 1,
+ HSETW(.wHubCharacteristics, (UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL)),
+ .bPwrOn2PwrGood = 50,
+ .bHubContrCurrent = 0,
+ .DeviceRemovable = {0}, /* port is removable */
+};
+
+#define STRING_VENDOR \
+ "A\0G\0E\0R\0E"
+
+#define STRING_PRODUCT \
+ "D\0C\0I\0 \0R\0o\0o\0t\0 \0H\0U\0B"
+
+USB_MAKE_STRING_DESC(STRING_VENDOR, uss820dci_vendor);
+USB_MAKE_STRING_DESC(STRING_PRODUCT, uss820dci_product);
+
+static usb_error_t
+uss820dci_roothub_exec(struct usb_device *udev,
+ struct usb_device_request *req, const void **pptr, uint16_t *plength)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(udev->bus);
+ const void *ptr;
+ uint16_t len;
+ uint16_t value;
+ uint16_t index;
+ usb_error_t err;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* buffer reset */
+ ptr = (const void *)&sc->sc_hub_temp;
+ len = 0;
+ err = 0;
+
+ value = UGETW(req->wValue);
+ index = UGETW(req->wIndex);
+
+ /* demultiplex the control request */
+
+ switch (req->bmRequestType) {
+ case UT_READ_DEVICE:
+ switch (req->bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_descriptor;
+ case UR_GET_CONFIG:
+ goto tr_handle_get_config;
+ case UR_GET_STATUS:
+ goto tr_handle_get_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_DEVICE:
+ switch (req->bRequest) {
+ case UR_SET_ADDRESS:
+ goto tr_handle_set_address;
+ case UR_SET_CONFIG:
+ goto tr_handle_set_config;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_DESCRIPTOR:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_ENDPOINT:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ switch (UGETW(req->wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_clear_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_clear_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SET_FEATURE:
+ switch (UGETW(req->wValue)) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_set_halt;
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_set_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SYNCH_FRAME:
+ goto tr_valid; /* nop */
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_ENDPOINT:
+ switch (req->bRequest) {
+ case UR_GET_STATUS:
+ goto tr_handle_get_ep_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_INTERFACE:
+ switch (req->bRequest) {
+ case UR_SET_INTERFACE:
+ goto tr_handle_set_interface;
+ case UR_CLEAR_FEATURE:
+ goto tr_valid; /* nop */
+ case UR_SET_FEATURE:
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_INTERFACE:
+ switch (req->bRequest) {
+ case UR_GET_INTERFACE:
+ goto tr_handle_get_interface;
+ case UR_GET_STATUS:
+ goto tr_handle_get_iface_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_INTERFACE:
+ case UT_WRITE_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_READ_CLASS_INTERFACE:
+ case UT_READ_VENDOR_INTERFACE:
+ /* XXX forward */
+ break;
+
+ case UT_WRITE_CLASS_DEVICE:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_valid;
+ case UR_SET_DESCRIPTOR:
+ case UR_SET_FEATURE:
+ break;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_CLASS_OTHER:
+ switch (req->bRequest) {
+ case UR_CLEAR_FEATURE:
+ goto tr_handle_clear_port_feature;
+ case UR_SET_FEATURE:
+ goto tr_handle_set_port_feature;
+ case UR_CLEAR_TT_BUFFER:
+ case UR_RESET_TT:
+ case UR_STOP_TT:
+ goto tr_valid;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_OTHER:
+ switch (req->bRequest) {
+ case UR_GET_TT_STATE:
+ goto tr_handle_get_tt_state;
+ case UR_GET_STATUS:
+ goto tr_handle_get_port_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_CLASS_DEVICE:
+ switch (req->bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_class_descriptor;
+ case UR_GET_STATUS:
+ goto tr_handle_get_class_status;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_valid;
+
+tr_handle_get_descriptor:
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ len = sizeof(uss820dci_devd);
+ ptr = (const void *)&uss820dci_devd;
+ goto tr_valid;
+ case UDESC_DEVICE_QUALIFIER:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ len = sizeof(uss820dci_odevd);
+ ptr = (const void *)&uss820dci_odevd;
+ goto tr_valid;
+ case UDESC_CONFIG:
+ if (value & 0xff) {
+ goto tr_stalled;
+ }
+ len = sizeof(uss820dci_confd);
+ ptr = (const void *)&uss820dci_confd;
+ goto tr_valid;
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ len = sizeof(usb_string_lang_en);
+ ptr = (const void *)&usb_string_lang_en;
+ goto tr_valid;
+
+ case 1: /* Vendor */
+ len = sizeof(uss820dci_vendor);
+ ptr = (const void *)&uss820dci_vendor;
+ goto tr_valid;
+
+ case 2: /* Product */
+ len = sizeof(uss820dci_product);
+ ptr = (const void *)&uss820dci_product;
+ goto tr_valid;
+ default:
+ break;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ goto tr_stalled;
+
+tr_handle_get_config:
+ len = 1;
+ sc->sc_hub_temp.wValue[0] = sc->sc_conf;
+ goto tr_valid;
+
+tr_handle_get_status:
+ len = 2;
+ USETW(sc->sc_hub_temp.wValue, UDS_SELF_POWERED);
+ goto tr_valid;
+
+tr_handle_set_address:
+ if (value & 0xFF00) {
+ goto tr_stalled;
+ }
+ sc->sc_rt_addr = value;
+ goto tr_valid;
+
+tr_handle_set_config:
+ if (value >= 2) {
+ goto tr_stalled;
+ }
+ sc->sc_conf = value;
+ goto tr_valid;
+
+tr_handle_get_interface:
+ len = 1;
+ sc->sc_hub_temp.wValue[0] = 0;
+ goto tr_valid;
+
+tr_handle_get_tt_state:
+tr_handle_get_class_status:
+tr_handle_get_iface_status:
+tr_handle_get_ep_status:
+ len = 2;
+ USETW(sc->sc_hub_temp.wValue, 0);
+ goto tr_valid;
+
+tr_handle_set_halt:
+tr_handle_set_interface:
+tr_handle_set_wakeup:
+tr_handle_clear_wakeup:
+tr_handle_clear_halt:
+ goto tr_valid;
+
+tr_handle_clear_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(9, "UR_CLEAR_PORT_FEATURE on port %d\n", index);
+
+ switch (value) {
+ case UHF_PORT_SUSPEND:
+ uss820dci_wakeup_peer(sc);
+ break;
+
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 0;
+ break;
+
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ case UHF_C_PORT_ENABLE:
+ case UHF_C_PORT_OVER_CURRENT:
+ case UHF_C_PORT_RESET:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 0;
+ uss820dci_pull_down(sc);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ sc->sc_flags.change_connect = 0;
+ break;
+ case UHF_C_PORT_SUSPEND:
+ sc->sc_flags.change_suspend = 0;
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_set_port_feature:
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ DPRINTFN(9, "UR_SET_PORT_FEATURE\n");
+
+ switch (value) {
+ case UHF_PORT_ENABLE:
+ sc->sc_flags.port_enabled = 1;
+ break;
+ case UHF_PORT_SUSPEND:
+ case UHF_PORT_RESET:
+ case UHF_PORT_TEST:
+ case UHF_PORT_INDICATOR:
+ /* nops */
+ break;
+ case UHF_PORT_POWER:
+ sc->sc_flags.port_powered = 1;
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ goto tr_valid;
+
+tr_handle_get_port_status:
+
+ DPRINTFN(9, "UR_GET_PORT_STATUS\n");
+
+ if (index != 1) {
+ goto tr_stalled;
+ }
+ if (sc->sc_flags.status_vbus) {
+ uss820dci_pull_up(sc);
+ } else {
+ uss820dci_pull_down(sc);
+ }
+
+ /* Select FULL-speed and Device Side Mode */
+
+ value = UPS_PORT_MODE_DEVICE;
+
+ if (sc->sc_flags.port_powered) {
+ value |= UPS_PORT_POWER;
+ }
+ if (sc->sc_flags.port_enabled) {
+ value |= UPS_PORT_ENABLED;
+ }
+ if (sc->sc_flags.status_vbus &&
+ sc->sc_flags.status_bus_reset) {
+ value |= UPS_CURRENT_CONNECT_STATUS;
+ }
+ if (sc->sc_flags.status_suspend) {
+ value |= UPS_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortStatus, value);
+
+ value = 0;
+
+ if (sc->sc_flags.change_connect) {
+ value |= UPS_C_CONNECT_STATUS;
+ }
+ if (sc->sc_flags.change_suspend) {
+ value |= UPS_C_SUSPEND;
+ }
+ USETW(sc->sc_hub_temp.ps.wPortChange, value);
+ len = sizeof(sc->sc_hub_temp.ps);
+ goto tr_valid;
+
+tr_handle_get_class_descriptor:
+ if (value & 0xFF) {
+ goto tr_stalled;
+ }
+ ptr = (const void *)&uss820dci_hubd;
+ len = sizeof(uss820dci_hubd);
+ goto tr_valid;
+
+tr_stalled:
+ err = USB_ERR_STALLED;
+tr_valid:
+done:
+ *plength = len;
+ *pptr = ptr;
+ return (err);
+}
+
+static void
+uss820dci_xfer_setup(struct usb_setup_params *parm)
+{
+ const struct usb_hw_ep_profile *pf;
+ struct usb_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t n;
+ uint8_t ep_no;
+
+ xfer = parm->curr_xfer;
+
+ /*
+ * NOTE: This driver does not use any of the parameters that
+ * are computed from the following values. Just set some
+ * reasonable dummies:
+ */
+ parm->hc_max_packet_size = 0x500;
+ parm->hc_max_packet_count = 1;
+ parm->hc_max_frame_size = 0x500;
+
+ usbd_transfer_setup_sub(parm);
+
+ /*
+ * compute maximum number of TDs
+ */
+ if (parm->methods == &uss820dci_device_ctrl_methods) {
+ ntd = xfer->nframes + 1 /* STATUS */ + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &uss820dci_device_bulk_methods) {
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &uss820dci_device_intr_methods) {
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else if (parm->methods == &uss820dci_device_isoc_fs_methods) {
+ ntd = xfer->nframes + 1 /* SYNC */ ;
+
+ } else {
+ ntd = 0;
+ }
+
+ /*
+ * check if "usbd_transfer_setup_sub" set an error
+ */
+ if (parm->err) {
+ return;
+ }
+ /*
+ * allocate transfer descriptors
+ */
+ last_obj = NULL;
+
+ /*
+ * get profile stuff
+ */
+ if (ntd) {
+ ep_no = xfer->endpointno & UE_ADDR;
+ uss820dci_get_hw_ep_profile(parm->udev, &pf, ep_no);
+
+ if (pf == NULL) {
+ /* should not happen */
+ parm->err = USB_ERR_INVAL;
+ return;
+ }
+ } else {
+ ep_no = 0;
+ pf = NULL;
+ }
+
+ /* align data */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ for (n = 0; n != ntd; n++) {
+ struct uss820dci_td *td;
+
+ if (parm->buf) {
+ td = USB_ADD_BYTES(parm->buf, parm->size[0]);
+
+ /* init TD */
+ td->max_packet_size = xfer->max_packet_size;
+ td->ep_index = ep_no;
+ if (pf->support_multi_buffer &&
+ (parm->methods != &uss820dci_device_ctrl_methods)) {
+ td->support_multi_buffer = 1;
+ }
+ td->obj_next = last_obj;
+
+ last_obj = td;
+ }
+ parm->size[0] += sizeof(*td);
+ }
+
+ xfer->td_start[0] = last_obj;
+}
+
+static void
+uss820dci_xfer_unsetup(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+uss820dci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
+ struct usb_endpoint *ep)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(udev->bus);
+
+ DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d (%d)\n",
+ ep, udev->address,
+ edesc->bEndpointAddress, udev->flags.usb_mode,
+ sc->sc_rt_addr);
+
+ if (udev->device_index != sc->sc_rt_addr) {
+ if (udev->speed != USB_SPEED_FULL) {
+ /* not supported */
+ return;
+ }
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ ep->methods = &uss820dci_device_ctrl_methods;
+ break;
+ case UE_INTERRUPT:
+ ep->methods = &uss820dci_device_intr_methods;
+ break;
+ case UE_ISOCHRONOUS:
+ ep->methods = &uss820dci_device_isoc_fs_methods;
+ break;
+ case UE_BULK:
+ ep->methods = &uss820dci_device_bulk_methods;
+ break;
+ default:
+ /* do nothing */
+ break;
+ }
+ }
+}
+
+static void
+uss820dci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state)
+{
+ struct uss820dci_softc *sc = USS820_DCI_BUS2SC(bus);
+
+ switch (state) {
+ case USB_HW_POWER_SUSPEND:
+ uss820dci_suspend(sc);
+ break;
+ case USB_HW_POWER_SHUTDOWN:
+ uss820dci_uninit(sc);
+ break;
+ case USB_HW_POWER_RESUME:
+ uss820dci_resume(sc);
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct usb_bus_methods uss820dci_bus_methods =
+{
+ .endpoint_init = &uss820dci_ep_init,
+ .xfer_setup = &uss820dci_xfer_setup,
+ .xfer_unsetup = &uss820dci_xfer_unsetup,
+ .get_hw_ep_profile = &uss820dci_get_hw_ep_profile,
+ .xfer_stall = &uss820dci_xfer_stall,
+ .set_stall = &uss820dci_set_stall,
+ .clear_stall = &uss820dci_clear_stall,
+ .roothub_exec = &uss820dci_roothub_exec,
+ .xfer_poll = &uss820dci_do_poll,
+ .set_hw_power_sleep = uss820dci_set_hw_power_sleep,
+};
diff --git a/sys/dev/usb/controller/uss820dci.h b/sys/dev/usb/controller/uss820dci.h
new file mode 100644
index 000000000000..2fc43c0e60d7
--- /dev/null
+++ b/sys/dev/usb/controller/uss820dci.h
@@ -0,0 +1,361 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2007 Hans Petter Selasky <hselasky@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.
+ */
+
+#ifndef _USS820_DCI_H_
+#define _USS820_DCI_H_
+
+#define USS820_MAX_DEVICES (USB_MIN_DEVICES + 1)
+
+#define USS820_EP_MAX 8 /* maximum number of endpoints */
+
+#define USS820_TXDAT 0x00 /* Transmit FIFO data */
+
+#define USS820_TXCNTL 0x01 /* Transmit FIFO byte count low */
+#define USS820_TXCNTL_MASK 0xFF
+
+#define USS820_TXCNTH 0x02 /* Transmit FIFO byte count high */
+#define USS820_TXCNTH_MASK 0x03
+#define USS820_TXCNTH_UNUSED 0xFC
+
+#define USS820_TXCON 0x03 /* USB transmit FIFO control */
+#define USS820_TXCON_REVRP 0x01
+#define USS820_TXCON_ADVRM 0x02
+#define USS820_TXCON_ATM 0x04 /* Automatic Transmit Management */
+#define USS820_TXCON_TXISO 0x08 /* Transmit Isochronous Data */
+#define USS820_TXCON_UNUSED 0x10
+#define USS820_TXCON_FFSZ_16_64 0x00
+#define USS820_TXCON_FFSZ_64_256 0x20
+#define USS820_TXCON_FFSZ_8_512 0x40
+#define USS820_TXCON_FFSZ_32_1024 0x60
+#define USS820_TXCON_FFSZ_MASK 0x60
+#define USS820_TXCON_TXCLR 0x80 /* Transmit FIFO clear */
+
+#define USS820_TXFLG 0x04 /* Transmit FIFO flag (Read Only) */
+#define USS820_TXFLG_TXOVF 0x01 /* TX overrun */
+#define USS820_TXFLG_TXURF 0x02 /* TX underrun */
+#define USS820_TXFLG_TXFULL 0x04 /* TX full */
+#define USS820_TXFLG_TXEMP 0x08 /* TX empty */
+#define USS820_TXFLG_UNUSED 0x30
+#define USS820_TXFLG_TXFIF0 0x40
+#define USS820_TXFLG_TXFIF1 0x80
+
+#define USS820_RXDAT 0x05 /* Receive FIFO data */
+
+#define USS820_RXCNTL 0x06 /* Receive FIFO byte count low */
+#define USS820_RXCNTL_MASK 0xFF
+
+#define USS820_RXCNTH 0x07 /* Receive FIFO byte count high */
+#define USS820_RXCNTH_MASK 0x03
+#define USS820_RXCNTH_UNUSED 0xFC
+
+#define USS820_RXCON 0x08 /* Receive FIFO control */
+#define USS820_RXCON_REVWP 0x01
+#define USS820_RXCON_ADVWM 0x02
+#define USS820_RXCON_ARM 0x04 /* Auto Receive Management */
+#define USS820_RXCON_RXISO 0x08 /* Receive Isochronous Data */
+#define USS820_RXCON_RXFFRC 0x10 /* FIFO Read Complete */
+#define USS820_RXCON_FFSZ_16_64 0x00
+#define USS820_RXCON_FFSZ_64_256 0x20
+#define USS820_RXCON_FFSZ_8_512 0x40
+#define USS820_RXCON_FFSZ_32_1024 0x60
+#define USS820_RXCON_RXCLR 0x80 /* Receive FIFO clear */
+
+#define USS820_RXFLG 0x09 /* Receive FIFO flag (Read Only) */
+#define USS820_RXFLG_RXOVF 0x01 /* RX overflow */
+#define USS820_RXFLG_RXURF 0x02 /* RX underflow */
+#define USS820_RXFLG_RXFULL 0x04 /* RX full */
+#define USS820_RXFLG_RXEMP 0x08 /* RX empty */
+#define USS820_RXFLG_RXFLUSH 0x10 /* RX flush */
+#define USS820_RXFLG_UNUSED 0x20
+#define USS820_RXFLG_RXFIF0 0x40
+#define USS820_RXFLG_RXFIF1 0x80
+
+#define USS820_EPINDEX 0x0a /* Endpoint index selection */
+#define USS820_EPINDEX_MASK 0x07
+#define USS820_EPINDEX_UNUSED 0xF8
+
+#define USS820_EPCON 0x0b /* Endpoint control */
+#define USS820_EPCON_TXEPEN 0x01 /* Transmit Endpoint Enable */
+#define USS820_EPCON_TXOE 0x02 /* Transmit Output Enable */
+#define USS820_EPCON_RXEPEN 0x04 /* Receive Endpoint Enable */
+#define USS820_EPCON_RXIE 0x08 /* Receive Input Enable */
+#define USS820_EPCON_RXSPM 0x10 /* Receive Single-Packet Mode */
+#define USS820_EPCON_CTLEP 0x20 /* Control Endpoint */
+#define USS820_EPCON_TXSTL 0x40 /* Stall Transmit Endpoint */
+#define USS820_EPCON_RXSTL 0x80 /* Stall Receive Endpoint */
+
+#define USS820_TXSTAT 0x0c /* Transmit status */
+#define USS820_TXSTAT_TXACK 0x01 /* Transmit Acknowledge */
+#define USS820_TXSTAT_TXERR 0x02 /* Transmit Error */
+#define USS820_TXSTAT_TXVOID 0x04 /* Transmit Void */
+#define USS820_TXSTAT_TXSOVW 0x08 /* Transmit Data Sequence Overwrite
+ * Bit */
+#define USS820_TXSTAT_TXFLUSH 0x10 /* Transmit FIFO Packet Flushed */
+#define USS820_TXSTAT_TXNAKE 0x20 /* Transmit NAK Mode Enable */
+#define USS820_TXSTAT_TXDSAM 0x40 /* Transmit Data-Set-Available Mode */
+#define USS820_TXSTAT_TXSEQ 0x80 /* Transmitter Current Sequence Bit */
+
+#define USS820_RXSTAT 0x0d /* Receive status */
+#define USS820_RXSTAT_RXACK 0x01 /* Receive Acknowledge */
+#define USS820_RXSTAT_RXERR 0x02 /* Receive Error */
+#define USS820_RXSTAT_RXVOID 0x04 /* Receive Void */
+#define USS820_RXSTAT_RXSOVW 0x08 /* Receive Data Sequence Overwrite Bit */
+#define USS820_RXSTAT_EDOVW 0x10 /* End Overwrite Flag */
+#define USS820_RXSTAT_STOVW 0x20 /* Start Overwrite Flag */
+#define USS820_RXSTAT_RXSETUP 0x40 /* Received SETUP token */
+#define USS820_RXSTAT_RXSEQ 0x80 /* Receiver Endpoint Sequence Bit */
+
+#define USS820_SOFL 0x0e /* Start Of Frame counter low */
+#define USS820_SOFL_MASK 0xFF
+
+#define USS820_SOFH 0x0f /* Start Of Frame counter high */
+#define USS820_SOFH_MASK 0x07
+#define USS820_SOFH_SOFDIS 0x08 /* SOF Pin Output Disable */
+#define USS820_SOFH_FTLOCK 0x10 /* Frame Timer Lock */
+#define USS820_SOFH_SOFIE 0x20 /* SOF Interrupt Enable */
+#define USS820_SOFH_ASOF 0x40 /* Any Start of Frame */
+#define USS820_SOFH_SOFACK 0x80 /* SOF Token Received Without Error */
+
+#define USS820_FADDR 0x10 /* Function Address */
+#define USS820_FADDR_MASK 0x7F
+#define USS820_FADDR_UNUSED 0x80
+
+#define USS820_SCR 0x11 /* System Control */
+#define USS820_SCR_UNUSED 0x01
+#define USS820_SCR_T_IRQ 0x02 /* Global Interrupt Enable */
+#define USS820_SCR_IRQLVL 0x04 /* Interrupt Mode */
+#define USS820_SCR_SRESET 0x08 /* Software reset */
+#define USS820_SCR_IE_RESET 0x10 /* Enable Reset Interrupt */
+#define USS820_SCR_IE_SUSP 0x20 /* Enable Suspend Interrupt */
+#define USS820_SCR_RWUPE 0x40 /* Enable Remote Wake-Up Feature */
+#define USS820_SCR_IRQPOL 0x80 /* IRQ polarity */
+
+#define USS820_SSR 0x12 /* System Status */
+#define USS820_SSR_RESET 0x01 /* Reset Condition Detected on USB
+ * cable */
+#define USS820_SSR_SUSPEND 0x02 /* Suspend Detected */
+#define USS820_SSR_RESUME 0x04 /* Resume Detected */
+#define USS820_SSR_SUSPDIS 0x08 /* Suspend Disable */
+#define USS820_SSR_SUSPPO 0x10 /* Suspend Power Off */
+#define USS820_SSR_UNUSED 0xE0
+
+#define USS820_UNK0 0x13 /* Unknown */
+#define USS820_UNK0_UNUSED 0xFF
+
+#define USS820_SBI 0x14 /* Serial bus interrupt low */
+#define USS820_SBI_FTXD0 0x01 /* Function Transmit Done, EP 0 */
+#define USS820_SBI_FRXD0 0x02 /* Function Receive Done, EP 0 */
+#define USS820_SBI_FTXD1 0x04
+#define USS820_SBI_FRXD1 0x08
+#define USS820_SBI_FTXD2 0x10
+#define USS820_SBI_FRXD2 0x20
+#define USS820_SBI_FTXD3 0x40
+#define USS820_SBI_FRXD3 0x80
+
+#define USS820_SBI1 0x15 /* Serial bus interrupt high */
+#define USS820_SBI1_FTXD4 0x01
+#define USS820_SBI1_FRXD4 0x02
+#define USS820_SBI1_FTXD5 0x04
+#define USS820_SBI1_FRXD5 0x08
+#define USS820_SBI1_FTXD6 0x10
+#define USS820_SBI1_FRXD6 0x20
+#define USS820_SBI1_FTXD7 0x40
+#define USS820_SBI1_FRXD7 0x80
+
+#define USS820_SBIE 0x16 /* Serial bus interrupt enable low */
+#define USS820_SBIE_FTXIE0 0x01
+#define USS820_SBIE_FRXIE0 0x02
+#define USS820_SBIE_FTXIE1 0x04
+#define USS820_SBIE_FRXIE1 0x08
+#define USS820_SBIE_FTXIE2 0x10
+#define USS820_SBIE_FRXIE2 0x20
+#define USS820_SBIE_FTXIE3 0x40
+#define USS820_SBIE_FRXIE3 0x80
+
+#define USS820_SBIE1 0x17 /* Serial bus interrupt enable high */
+#define USS820_SBIE1_FTXIE4 0x01
+#define USS820_SBIE1_FRXIE4 0x02
+#define USS820_SBIE1_FTXIE5 0x04
+#define USS820_SBIE1_FRXIE5 0x08
+#define USS820_SBIE1_FTXIE6 0x10
+#define USS820_SBIE1_FRXIE6 0x20
+#define USS820_SBIE1_FTXIE7 0x40
+#define USS820_SBIE1_FRXIE7 0x80
+
+#define USS820_REV 0x18 /* Hardware revision */
+#define USS820_REV_MIN 0x0F
+#define USS820_REV_MAJ 0xF0
+
+#define USS820_LOCK 0x19 /* Suspend power-off locking */
+#define USS820_LOCK_UNLOCKED 0x01
+#define USS820_LOCK_UNUSED 0xFE
+
+#define USS820_PEND 0x1a /* Pend hardware status update */
+#define USS820_PEND_PEND 0x01
+#define USS820_PEND_UNUSED 0xFE
+
+#define USS820_SCRATCH 0x1b /* Scratch firmware information */
+#define USS820_SCRATCH_MASK 0x7F
+#define USS820_SCRATCH_IE_RESUME 0x80 /* Enable Resume Interrupt */
+
+#define USS820_MCSR 0x1c /* Miscellaneous control and status */
+#define USS820_MCSR_DPEN 0x01 /* DPLS Pull-Up Enable */
+#define USS820_MCSR_SUSPLOE 0x02 /* Suspend Lock Out Enable */
+#define USS820_MCSR_BDFEAT 0x04 /* Board Feature Enable */
+#define USS820_MCSR_FEAT 0x08 /* Feature Enable */
+#define USS820_MCSR_PKGID 0x10 /* Package Identification */
+#define USS820_MCSR_SUSPS 0x20 /* Suspend Status */
+#define USS820_MCSR_INIT 0x40 /* Device Initialized */
+#define USS820_MCSR_RWUPR 0x80 /* Remote Wakeup-Up Remember */
+
+#define USS820_DSAV 0x1d /* Data set available low (Read Only) */
+#define USS820_DSAV_TXAV0 0x01
+#define USS820_DSAV_RXAV0 0x02
+#define USS820_DSAV_TXAV1 0x04
+#define USS820_DSAV_RXAV1 0x08
+#define USS820_DSAV_TXAV2 0x10
+#define USS820_DSAV_RXAV2 0x20
+#define USS820_DSAV_TXAV3 0x40
+#define USS820_DSAV_RXAV3 0x80
+
+#define USS820_DSAV1 0x1e /* Data set available high */
+#define USS820_DSAV1_TXAV4 0x01
+#define USS820_DSAV1_RXAV4 0x02
+#define USS820_DSAV1_TXAV5 0x04
+#define USS820_DSAV1_RXAV5 0x08
+#define USS820_DSAV1_TXAV6 0x10
+#define USS820_DSAV1_RXAV6 0x20
+#define USS820_DSAV1_TXAV7 0x40
+#define USS820_DSAV1_RXAV7 0x80
+
+#define USS820_UNK1 0x1f /* Unknown */
+#define USS820_UNK1_UNKNOWN 0xFF
+
+#ifndef USS820_REG_STRIDE
+#define USS820_REG_STRIDE 1
+#endif
+
+#define USS820_READ_1(sc, reg) \
+ bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (reg) * USS820_REG_STRIDE)
+
+#define USS820_WRITE_1(sc, reg, data) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, (reg) * USS820_REG_STRIDE, (data))
+
+struct uss820dci_td;
+struct uss820dci_softc;
+
+typedef uint8_t (uss820dci_cmd_t)(struct uss820dci_softc *, struct uss820dci_td *td);
+
+struct uss820dci_td {
+ struct uss820dci_td *obj_next;
+ uss820dci_cmd_t *func;
+ struct usb_page_cache *pc;
+ uint32_t offset;
+ uint32_t remainder;
+ uint16_t max_packet_size;
+ uint8_t ep_index;
+ uint8_t error:1;
+ uint8_t alt_next:1;
+ uint8_t short_pkt:1;
+ uint8_t support_multi_buffer:1;
+ uint8_t did_stall:1;
+ uint8_t did_enable:1;
+};
+
+struct uss820_std_temp {
+ uss820dci_cmd_t *func;
+ struct usb_page_cache *pc;
+ struct uss820dci_td *td;
+ struct uss820dci_td *td_next;
+ uint32_t len;
+ uint32_t offset;
+ uint16_t max_frame_size;
+ uint8_t short_pkt;
+ /*
+ * short_pkt = 0: transfer should be short terminated
+ * short_pkt = 1: transfer should not be short terminated
+ */
+ uint8_t setup_alt_next;
+ uint8_t did_stall;
+};
+
+struct uss820dci_config_desc {
+ struct usb_config_descriptor confd;
+ struct usb_interface_descriptor ifcd;
+ struct usb_endpoint_descriptor endpd;
+} __packed;
+
+union uss820_hub_temp {
+ uWord wValue;
+ struct usb_port_status ps;
+};
+
+struct uss820_flags {
+ uint8_t change_connect:1;
+ uint8_t change_suspend:1;
+ uint8_t status_suspend:1; /* set if suspended */
+ uint8_t status_vbus:1; /* set if present */
+ uint8_t status_bus_reset:1; /* set if reset complete */
+ uint8_t clocks_off:1;
+ uint8_t port_powered:1;
+ uint8_t port_enabled:1;
+ uint8_t d_pulled_up:1;
+ uint8_t mcsr_feat:1;
+};
+
+struct uss820dci_softc {
+ struct usb_bus sc_bus;
+ union uss820_hub_temp sc_hub_temp;
+
+ struct usb_device *sc_devices[USS820_MAX_DEVICES];
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ void *sc_intr_hdl;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+
+ uint32_t sc_xfer_complete;
+
+ uint8_t sc_rt_addr; /* root HUB address */
+ uint8_t sc_dv_addr; /* device address */
+ uint8_t sc_conf; /* root HUB config */
+
+ uint8_t sc_hub_idata[1];
+
+ struct uss820_flags sc_flags;
+};
+
+/* prototypes */
+
+usb_error_t uss820dci_init(struct uss820dci_softc *sc);
+void uss820dci_uninit(struct uss820dci_softc *sc);
+driver_filter_t uss820dci_filter_interrupt;
+driver_intr_t uss820dci_interrupt;
+
+#endif /* _USS820_DCI_H_ */
diff --git a/sys/dev/usb/controller/xhci.c b/sys/dev/usb/controller/xhci.c
new file mode 100644
index 000000000000..5be592512196
--- /dev/null
+++ b/sys/dev/usb/controller/xhci.c
@@ -0,0 +1,4410 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010-2022 Hans Petter Selasky
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * USB eXtensible Host Controller Interface, a.k.a. USB 3.0 controller.
+ *
+ * The XHCI 1.0 spec can be found at
+ * http://www.intel.com/technology/usb/download/xHCI_Specification_for_USB.pdf
+ * and the USB 3.0 spec at
+ * http://www.usb.org/developers/docs/usb_30_spec_060910.zip
+ */
+
+/*
+ * A few words about the design implementation: This driver emulates
+ * the concept about TDs which is found in EHCI specification. This
+ * way we achieve that the USB controller drivers look similar to
+ * eachother which makes it easier to understand the code.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#define USB_DEBUG_VAR xhcidebug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#include <dev/usb/controller/xhci.h>
+#include <dev/usb/controller/xhcireg.h>
+
+#define XHCI_BUS2SC(bus) \
+ __containerof(bus, struct xhci_softc, sc_bus)
+
+#define XHCI_GET_CTX(sc, which, field, ptr) \
+ ((sc)->sc_ctx_is_64_byte ? \
+ &((struct which##64 *)(ptr))->field.ctx : \
+ &((struct which *)(ptr))->field)
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, xhci, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB XHCI");
+
+static int xhcistreams;
+SYSCTL_INT(_hw_usb_xhci, OID_AUTO, streams, CTLFLAG_RWTUN,
+ &xhcistreams, 0, "Set to enable streams mode support");
+
+static int xhcictlquirk = 1;
+SYSCTL_INT(_hw_usb_xhci, OID_AUTO, ctlquirk, CTLFLAG_RWTUN,
+ &xhcictlquirk, 0, "Set to enable control endpoint quirk");
+
+static int xhcidcepquirk;
+SYSCTL_INT(_hw_usb_xhci, OID_AUTO, dcepquirk, CTLFLAG_RWTUN,
+ &xhcidcepquirk, 0, "Set to disable endpoint deconfigure command");
+
+#ifdef USB_DEBUG
+static int xhcidebug;
+static int xhciroute;
+static int xhcipolling;
+static int xhcidma32;
+static int xhcictlstep;
+
+SYSCTL_INT(_hw_usb_xhci, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &xhcidebug, 0, "Debug level");
+SYSCTL_INT(_hw_usb_xhci, OID_AUTO, xhci_port_route, CTLFLAG_RWTUN,
+ &xhciroute, 0, "Routing bitmap for switching EHCI ports to the XHCI controller");
+SYSCTL_INT(_hw_usb_xhci, OID_AUTO, use_polling, CTLFLAG_RWTUN,
+ &xhcipolling, 0, "Set to enable software interrupt polling for the XHCI controller");
+SYSCTL_INT(_hw_usb_xhci, OID_AUTO, dma32, CTLFLAG_RWTUN,
+ &xhcidma32, 0, "Set to only use 32-bit DMA for the XHCI controller");
+SYSCTL_INT(_hw_usb_xhci, OID_AUTO, ctlstep, CTLFLAG_RWTUN,
+ &xhcictlstep, 0, "Set to enable control endpoint status stage stepping");
+#else
+#define xhciroute 0
+#define xhcidma32 0
+#define xhcictlstep 0
+#endif
+
+#define XHCI_INTR_ENDPT 1
+
+struct xhci_std_temp {
+ struct xhci_softc *sc;
+ struct usb_page_cache *pc;
+ struct xhci_td *td;
+ struct xhci_td *td_next;
+ uint32_t len;
+ uint32_t offset;
+ uint32_t max_packet_size;
+ uint32_t average;
+ uint32_t isoc_frame;
+ uint16_t isoc_delta;
+ uint8_t shortpkt;
+ uint8_t multishort;
+ uint8_t last_frame;
+ uint8_t trb_type;
+ uint8_t direction;
+ uint8_t tbc;
+ uint8_t tlbpc;
+ uint8_t step_td;
+ uint8_t do_isoc_sync;
+};
+
+static void xhci_do_poll(struct usb_bus *);
+static void xhci_device_done(struct usb_xfer *, usb_error_t);
+static void xhci_root_intr(struct xhci_softc *);
+static void xhci_free_device_ext(struct usb_device *);
+static struct xhci_endpoint_ext *xhci_get_endpoint_ext(struct usb_device *,
+ struct usb_endpoint_descriptor *);
+static usb_proc_callback_t xhci_configure_msg;
+static usb_error_t xhci_configure_device(struct usb_device *);
+static usb_error_t xhci_configure_endpoint(struct usb_device *,
+ struct usb_endpoint_descriptor *, struct xhci_endpoint_ext *,
+ uint16_t, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t,
+ uint8_t);
+static usb_error_t xhci_configure_mask(struct usb_device *,
+ uint32_t, uint8_t);
+static usb_error_t xhci_cmd_evaluate_ctx(struct xhci_softc *,
+ uint64_t, uint8_t);
+static void xhci_endpoint_doorbell(struct usb_xfer *);
+
+static const struct usb_bus_methods xhci_bus_methods;
+
+#ifdef USB_DEBUG
+static void
+xhci_dump_trb(struct xhci_trb *trb)
+{
+ DPRINTFN(5, "trb = %p\n", trb);
+ DPRINTFN(5, "qwTrb0 = 0x%016llx\n", (long long)le64toh(trb->qwTrb0));
+ DPRINTFN(5, "dwTrb2 = 0x%08x\n", le32toh(trb->dwTrb2));
+ DPRINTFN(5, "dwTrb3 = 0x%08x\n", le32toh(trb->dwTrb3));
+}
+
+static void
+xhci_dump_endpoint(struct xhci_endp_ctx *pep)
+{
+ DPRINTFN(5, "pep = %p\n", pep);
+ DPRINTFN(5, "dwEpCtx0=0x%08x\n", le32toh(pep->dwEpCtx0));
+ DPRINTFN(5, "dwEpCtx1=0x%08x\n", le32toh(pep->dwEpCtx1));
+ DPRINTFN(5, "qwEpCtx2=0x%016llx\n", (long long)le64toh(pep->qwEpCtx2));
+ DPRINTFN(5, "dwEpCtx4=0x%08x\n", le32toh(pep->dwEpCtx4));
+ DPRINTFN(5, "dwEpCtx5=0x%08x\n", le32toh(pep->dwEpCtx5));
+ DPRINTFN(5, "dwEpCtx6=0x%08x\n", le32toh(pep->dwEpCtx6));
+ DPRINTFN(5, "dwEpCtx7=0x%08x\n", le32toh(pep->dwEpCtx7));
+}
+
+static void
+xhci_dump_device(struct xhci_slot_ctx *psl)
+{
+ DPRINTFN(5, "psl = %p\n", psl);
+ DPRINTFN(5, "dwSctx0=0x%08x\n", le32toh(psl->dwSctx0));
+ DPRINTFN(5, "dwSctx1=0x%08x\n", le32toh(psl->dwSctx1));
+ DPRINTFN(5, "dwSctx2=0x%08x\n", le32toh(psl->dwSctx2));
+ DPRINTFN(5, "dwSctx3=0x%08x\n", le32toh(psl->dwSctx3));
+}
+#endif
+
+uint8_t
+xhci_use_polling(void)
+{
+#ifdef USB_DEBUG
+ return (xhcipolling != 0);
+#else
+ return (0);
+#endif
+}
+
+static void
+xhci_iterate_hw_softc(struct usb_bus *bus, usb_bus_mem_sub_cb_t *cb)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(bus);
+ uint16_t i;
+
+ cb(bus, &sc->sc_hw.root_pc, &sc->sc_hw.root_pg,
+ sizeof(struct xhci_hw_root), XHCI_PAGE_SIZE);
+
+ cb(bus, &sc->sc_hw.ctx_pc, &sc->sc_hw.ctx_pg,
+ sizeof(struct xhci_dev_ctx_addr), XHCI_PAGE_SIZE);
+
+ for (i = 0; i != sc->sc_noscratch; i++) {
+ cb(bus, &sc->sc_hw.scratch_pc[i], &sc->sc_hw.scratch_pg[i],
+ XHCI_PAGE_SIZE, XHCI_PAGE_SIZE);
+ }
+}
+
+static int
+xhci_reset_command_queue_locked(struct xhci_softc *sc)
+{
+ struct usb_page_search buf_res;
+ struct xhci_hw_root *phwr;
+ uint64_t addr;
+ uint32_t temp;
+
+ DPRINTF("\n");
+
+ temp = XREAD4(sc, oper, XHCI_CRCR_LO);
+ if (temp & XHCI_CRCR_LO_CRR) {
+ DPRINTF("Command ring running\n");
+ temp &= ~(XHCI_CRCR_LO_CS | XHCI_CRCR_LO_CA);
+
+ /*
+ * Try to abort the last command as per section
+ * 4.6.1.2 "Aborting a Command" of the XHCI
+ * specification:
+ */
+
+ /* stop and cancel */
+ XWRITE4(sc, oper, XHCI_CRCR_LO, temp | XHCI_CRCR_LO_CS);
+ XWRITE4(sc, oper, XHCI_CRCR_HI, 0);
+
+ XWRITE4(sc, oper, XHCI_CRCR_LO, temp | XHCI_CRCR_LO_CA);
+ XWRITE4(sc, oper, XHCI_CRCR_HI, 0);
+
+ /* wait 250ms */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 4);
+
+ /* check if command ring is still running */
+ temp = XREAD4(sc, oper, XHCI_CRCR_LO);
+ if (temp & XHCI_CRCR_LO_CRR) {
+ DPRINTF("Comand ring still running\n");
+ return (USB_ERR_IOERROR);
+ }
+ }
+
+ /* reset command ring */
+ sc->sc_command_ccs = 1;
+ sc->sc_command_idx = 0;
+
+ usbd_get_page(&sc->sc_hw.root_pc, 0, &buf_res);
+
+ /* set up command ring control base address */
+ addr = buf_res.physaddr;
+ phwr = buf_res.buffer;
+ addr += __offsetof(struct xhci_hw_root, hwr_commands[0]);
+
+ DPRINTF("CRCR=0x%016llx\n", (unsigned long long)addr);
+
+ memset(phwr->hwr_commands, 0, sizeof(phwr->hwr_commands));
+ phwr->hwr_commands[XHCI_MAX_COMMANDS - 1].qwTrb0 = htole64(addr);
+
+ usb_pc_cpu_flush(&sc->sc_hw.root_pc);
+
+ XWRITE4(sc, oper, XHCI_CRCR_LO, ((uint32_t)addr) | XHCI_CRCR_LO_RCS);
+ XWRITE4(sc, oper, XHCI_CRCR_HI, (uint32_t)(addr >> 32));
+
+ return (0);
+}
+
+usb_error_t
+xhci_start_controller(struct xhci_softc *sc)
+{
+ struct usb_page_search buf_res;
+ struct xhci_hw_root *phwr;
+ struct xhci_dev_ctx_addr *pdctxa;
+ usb_error_t err;
+ uint64_t addr;
+ uint32_t temp;
+ uint16_t i;
+
+ DPRINTF("\n");
+
+ sc->sc_event_ccs = 1;
+ sc->sc_event_idx = 0;
+ sc->sc_command_ccs = 1;
+ sc->sc_command_idx = 0;
+
+ err = xhci_reset_controller(sc);
+ if (err)
+ return (err);
+
+ /* set up number of device slots */
+ DPRINTF("CONFIG=0x%08x -> 0x%08x\n",
+ XREAD4(sc, oper, XHCI_CONFIG), sc->sc_noslot);
+
+ XWRITE4(sc, oper, XHCI_CONFIG, sc->sc_noslot);
+
+ temp = XREAD4(sc, oper, XHCI_USBSTS);
+
+ /* clear interrupts */
+ XWRITE4(sc, oper, XHCI_USBSTS, temp);
+ /* disable all device notifications */
+ XWRITE4(sc, oper, XHCI_DNCTRL, 0);
+
+ /* set up device context base address */
+ usbd_get_page(&sc->sc_hw.ctx_pc, 0, &buf_res);
+ pdctxa = buf_res.buffer;
+ memset(pdctxa, 0, sizeof(*pdctxa));
+
+ addr = buf_res.physaddr;
+ addr += __offsetof(struct xhci_dev_ctx_addr, qwSpBufPtr[0]);
+
+ /* slot 0 points to the table of scratchpad pointers */
+ pdctxa->qwBaaDevCtxAddr[0] = htole64(addr);
+
+ for (i = 0; i != sc->sc_noscratch; i++) {
+ struct usb_page_search buf_scp;
+ usbd_get_page(&sc->sc_hw.scratch_pc[i], 0, &buf_scp);
+ pdctxa->qwSpBufPtr[i] = htole64((uint64_t)buf_scp.physaddr);
+ }
+
+ addr = buf_res.physaddr;
+
+ XWRITE4(sc, oper, XHCI_DCBAAP_LO, (uint32_t)addr);
+ XWRITE4(sc, oper, XHCI_DCBAAP_HI, (uint32_t)(addr >> 32));
+ XWRITE4(sc, oper, XHCI_DCBAAP_LO, (uint32_t)addr);
+ XWRITE4(sc, oper, XHCI_DCBAAP_HI, (uint32_t)(addr >> 32));
+
+ /* set up event table size */
+ DPRINTF("ERSTSZ=0x%08x -> 0x%08x\n",
+ XREAD4(sc, runt, XHCI_ERSTSZ(0)), sc->sc_erst_max);
+
+ XWRITE4(sc, runt, XHCI_ERSTSZ(0), XHCI_ERSTS_SET(sc->sc_erst_max));
+
+ /* set up interrupt rate */
+ XWRITE4(sc, runt, XHCI_IMOD(0), sc->sc_imod_default);
+
+ usbd_get_page(&sc->sc_hw.root_pc, 0, &buf_res);
+
+ phwr = buf_res.buffer;
+ addr = buf_res.physaddr;
+ addr += __offsetof(struct xhci_hw_root, hwr_events[0]);
+
+ /* reset hardware root structure */
+ memset(phwr, 0, sizeof(*phwr));
+
+ phwr->hwr_ring_seg[0].qwEvrsTablePtr = htole64(addr);
+ phwr->hwr_ring_seg[0].dwEvrsTableSize = htole32(XHCI_MAX_EVENTS);
+
+ /*
+ * PR 237666:
+ *
+ * According to the XHCI specification, the XWRITE4's to
+ * XHCI_ERSTBA_LO and _HI lead to the XHCI to copy the
+ * qwEvrsTablePtr and dwEvrsTableSize values above at that
+ * time, as the XHCI initializes its event ring support. This
+ * is before the event ring starts to pay attention to the
+ * RUN/STOP bit. Thus, make sure the values are observable to
+ * the XHCI before that point.
+ */
+ usb_bus_mem_flush_all(&sc->sc_bus, &xhci_iterate_hw_softc);
+
+ DPRINTF("ERDP(0)=0x%016llx\n", (unsigned long long)addr);
+
+ XWRITE4(sc, runt, XHCI_ERDP_LO(0), (uint32_t)addr);
+ XWRITE4(sc, runt, XHCI_ERDP_HI(0), (uint32_t)(addr >> 32));
+
+ addr = buf_res.physaddr;
+
+ DPRINTF("ERSTBA(0)=0x%016llx\n", (unsigned long long)addr);
+
+ XWRITE4(sc, runt, XHCI_ERSTBA_LO(0), (uint32_t)addr);
+ XWRITE4(sc, runt, XHCI_ERSTBA_HI(0), (uint32_t)(addr >> 32));
+
+ /* set up interrupter registers */
+ temp = XREAD4(sc, runt, XHCI_IMAN(0));
+ temp |= XHCI_IMAN_INTR_ENA;
+ XWRITE4(sc, runt, XHCI_IMAN(0), temp);
+
+ /* set up command ring control base address */
+ addr = buf_res.physaddr;
+ addr += __offsetof(struct xhci_hw_root, hwr_commands[0]);
+
+ DPRINTF("CRCR=0x%016llx\n", (unsigned long long)addr);
+
+ XWRITE4(sc, oper, XHCI_CRCR_LO, ((uint32_t)addr) | XHCI_CRCR_LO_RCS);
+ XWRITE4(sc, oper, XHCI_CRCR_HI, (uint32_t)(addr >> 32));
+
+ phwr->hwr_commands[XHCI_MAX_COMMANDS - 1].qwTrb0 = htole64(addr);
+
+ usb_bus_mem_flush_all(&sc->sc_bus, &xhci_iterate_hw_softc);
+
+ /* Go! */
+ XWRITE4(sc, oper, XHCI_USBCMD, XHCI_CMD_RS |
+ XHCI_CMD_INTE | XHCI_CMD_HSEE);
+
+ for (i = 0; i != 100; i++) {
+ usb_pause_mtx(NULL, hz / 100);
+ temp = XREAD4(sc, oper, XHCI_USBSTS) & XHCI_STS_HCH;
+ if (!temp)
+ break;
+ }
+ if (temp) {
+ XWRITE4(sc, oper, XHCI_USBCMD, 0);
+ device_printf(sc->sc_bus.parent, "Run timeout.\n");
+ return (USB_ERR_IOERROR);
+ }
+
+ /* catch any lost interrupts */
+ xhci_do_poll(&sc->sc_bus);
+
+ if (sc->sc_port_route != NULL) {
+ /* Route all ports to the XHCI by default */
+ sc->sc_port_route(sc->sc_bus.parent,
+ ~xhciroute, xhciroute);
+ }
+ return (0);
+}
+
+usb_error_t
+xhci_halt_controller(struct xhci_softc *sc)
+{
+ uint32_t temp;
+ uint16_t i;
+
+ DPRINTF("\n");
+
+ sc->sc_capa_off = 0;
+ sc->sc_oper_off = XREAD1(sc, capa, XHCI_CAPLENGTH);
+ sc->sc_runt_off = XREAD4(sc, capa, XHCI_RTSOFF) & ~0xF;
+ sc->sc_door_off = XREAD4(sc, capa, XHCI_DBOFF) & ~0x3;
+
+ /* Halt controller */
+ XWRITE4(sc, oper, XHCI_USBCMD, 0);
+
+ for (i = 0; i != 100; i++) {
+ usb_pause_mtx(NULL, hz / 100);
+ temp = XREAD4(sc, oper, XHCI_USBSTS) & XHCI_STS_HCH;
+ if (temp)
+ break;
+ }
+
+ if (!temp) {
+ device_printf(sc->sc_bus.parent, "Controller halt timeout.\n");
+ return (USB_ERR_IOERROR);
+ }
+ return (0);
+}
+
+usb_error_t
+xhci_reset_controller(struct xhci_softc *sc)
+{
+ uint32_t temp = 0;
+ uint16_t i;
+
+ DPRINTF("\n");
+
+ /* Reset controller */
+ XWRITE4(sc, oper, XHCI_USBCMD, XHCI_CMD_HCRST);
+
+ for (i = 0; i != 100; i++) {
+ usb_pause_mtx(NULL, hz / 100);
+ temp = (XREAD4(sc, oper, XHCI_USBCMD) & XHCI_CMD_HCRST) |
+ (XREAD4(sc, oper, XHCI_USBSTS) & XHCI_STS_CNR);
+ if (!temp)
+ break;
+ }
+
+ if (temp) {
+ device_printf(sc->sc_bus.parent, "Controller "
+ "reset timeout.\n");
+ return (USB_ERR_IOERROR);
+ }
+ return (0);
+}
+
+usb_error_t
+xhci_init(struct xhci_softc *sc, device_t self, uint8_t dma32)
+{
+ uint32_t temp;
+
+ DPRINTF("\n");
+
+ /* initialize some bus fields */
+ sc->sc_bus.parent = self;
+
+ /* set the bus revision */
+ sc->sc_bus.usbrev = USB_REV_3_0;
+
+ /* set up the bus struct */
+ sc->sc_bus.methods = &xhci_bus_methods;
+
+ /* set up devices array */
+ sc->sc_bus.devices = sc->sc_devices;
+ sc->sc_bus.devices_max = XHCI_MAX_DEVICES;
+
+ /* set default cycle state in case of early interrupts */
+ sc->sc_event_ccs = 1;
+ sc->sc_command_ccs = 1;
+
+ /* set up bus space offsets */
+ sc->sc_capa_off = 0;
+ sc->sc_oper_off = XREAD1(sc, capa, XHCI_CAPLENGTH);
+ sc->sc_runt_off = XREAD4(sc, capa, XHCI_RTSOFF) & ~0x1F;
+ sc->sc_door_off = XREAD4(sc, capa, XHCI_DBOFF) & ~0x3;
+
+ DPRINTF("CAPLENGTH=0x%x\n", sc->sc_oper_off);
+ DPRINTF("RUNTIMEOFFSET=0x%x\n", sc->sc_runt_off);
+ DPRINTF("DOOROFFSET=0x%x\n", sc->sc_door_off);
+
+ DPRINTF("xHCI version = 0x%04x\n", XREAD2(sc, capa, XHCI_HCIVERSION));
+
+ if (!(XREAD4(sc, oper, XHCI_PAGESIZE) & XHCI_PAGESIZE_4K)) {
+ device_printf(sc->sc_bus.parent, "Controller does "
+ "not support 4K page size.\n");
+ return (ENXIO);
+ }
+
+ temp = XREAD4(sc, capa, XHCI_HCSPARAMS0);
+
+ DPRINTF("HCS0 = 0x%08x\n", temp);
+
+ /* set up context size */
+ if (XHCI_HCS0_CSZ(temp)) {
+ sc->sc_ctx_is_64_byte = 1;
+ } else {
+ sc->sc_ctx_is_64_byte = 0;
+ }
+
+ /* get DMA bits */
+ sc->sc_bus.dma_bits = (XHCI_HCS0_AC64(temp) &&
+ xhcidma32 == 0 && dma32 == 0) ? 64 : 32;
+
+ device_printf(self, "%d bytes context size, %d-bit DMA\n",
+ sc->sc_ctx_is_64_byte ? 64 : 32, (int)sc->sc_bus.dma_bits);
+
+ /* enable 64Kbyte control endpoint quirk */
+ sc->sc_bus.control_ep_quirk = (xhcictlquirk ? 1 : 0);
+
+ temp = XREAD4(sc, capa, XHCI_HCSPARAMS1);
+
+ /* get number of device slots */
+ sc->sc_noport = XHCI_HCS1_N_PORTS(temp);
+
+ if (sc->sc_noport == 0) {
+ device_printf(sc->sc_bus.parent, "Invalid number "
+ "of ports: %u\n", sc->sc_noport);
+ return (ENXIO);
+ }
+
+ sc->sc_noslot = XHCI_HCS1_DEVSLOT_MAX(temp);
+
+ DPRINTF("Max slots: %u\n", sc->sc_noslot);
+
+ if (sc->sc_noslot > XHCI_MAX_DEVICES)
+ sc->sc_noslot = XHCI_MAX_DEVICES;
+
+ temp = XREAD4(sc, capa, XHCI_HCSPARAMS2);
+
+ DPRINTF("HCS2=0x%08x\n", temp);
+
+ /* get isochronous scheduling threshold */
+ sc->sc_ist = XHCI_HCS2_IST(temp);
+
+ /* get number of scratchpads */
+ sc->sc_noscratch = XHCI_HCS2_SPB_MAX(temp);
+
+ if (sc->sc_noscratch > XHCI_MAX_SCRATCHPADS) {
+ device_printf(sc->sc_bus.parent, "XHCI request "
+ "too many scratchpads\n");
+ return (ENOMEM);
+ }
+
+ DPRINTF("Max scratch: %u\n", sc->sc_noscratch);
+
+ /* get event table size */
+ sc->sc_erst_max = 1U << XHCI_HCS2_ERST_MAX(temp);
+ if (sc->sc_erst_max > XHCI_MAX_RSEG)
+ sc->sc_erst_max = XHCI_MAX_RSEG;
+
+ temp = XREAD4(sc, capa, XHCI_HCSPARAMS3);
+
+ /* get maximum exit latency */
+ sc->sc_exit_lat_max = XHCI_HCS3_U1_DEL(temp) +
+ XHCI_HCS3_U2_DEL(temp) + 250 /* us */;
+
+ /* Check if we should use the default IMOD value. */
+ if (sc->sc_imod_default == 0)
+ sc->sc_imod_default = XHCI_IMOD_DEFAULT;
+
+ /* get all DMA memory */
+ if (usb_bus_mem_alloc_all(&sc->sc_bus,
+ USB_GET_DMA_TAG(self), &xhci_iterate_hw_softc)) {
+ return (ENOMEM);
+ }
+
+ /* set up command queue mutex and condition varible */
+ cv_init(&sc->sc_cmd_cv, "CMDQ");
+ sx_init(&sc->sc_cmd_sx, "CMDQ lock");
+
+ sc->sc_config_msg[0].hdr.pm_callback = &xhci_configure_msg;
+ sc->sc_config_msg[0].bus = &sc->sc_bus;
+ sc->sc_config_msg[1].hdr.pm_callback = &xhci_configure_msg;
+ sc->sc_config_msg[1].bus = &sc->sc_bus;
+
+ return (0);
+}
+
+void
+xhci_uninit(struct xhci_softc *sc)
+{
+ /*
+ * NOTE: At this point the control transfer process is gone
+ * and "xhci_configure_msg" is no longer called. Consequently
+ * waiting for the configuration messages to complete is not
+ * needed.
+ */
+ usb_bus_mem_free_all(&sc->sc_bus, &xhci_iterate_hw_softc);
+
+ cv_destroy(&sc->sc_cmd_cv);
+ sx_destroy(&sc->sc_cmd_sx);
+}
+
+static void
+xhci_set_hw_power_sleep(struct usb_bus *bus, uint32_t state)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(bus);
+
+ switch (state) {
+ case USB_HW_POWER_SUSPEND:
+ DPRINTF("Stopping the XHCI\n");
+ xhci_halt_controller(sc);
+ xhci_reset_controller(sc);
+ break;
+ case USB_HW_POWER_SHUTDOWN:
+ DPRINTF("Stopping the XHCI\n");
+ xhci_halt_controller(sc);
+ xhci_reset_controller(sc);
+ break;
+ case USB_HW_POWER_RESUME:
+ DPRINTF("Starting the XHCI\n");
+ xhci_start_controller(sc);
+ break;
+ default:
+ break;
+ }
+}
+
+static usb_error_t
+xhci_generic_done_sub(struct usb_xfer *xfer)
+{
+ struct xhci_td *td;
+ struct xhci_td *td_alt_next;
+ uint32_t len;
+ uint8_t status;
+
+ td = xfer->td_transfer_cache;
+ td_alt_next = td->alt_next;
+
+ if (xfer->aframes != xfer->nframes)
+ usbd_xfer_set_frame_len(xfer, xfer->aframes, 0);
+
+ while (1) {
+ usb_pc_cpu_invalidate(td->page_cache);
+
+ status = td->status;
+ len = td->remainder;
+
+ DPRINTFN(4, "xfer=%p[%u/%u] rem=%u/%u status=%u\n",
+ xfer, (unsigned)xfer->aframes,
+ (unsigned)xfer->nframes,
+ (unsigned)len, (unsigned)td->len,
+ (unsigned)status);
+
+ /*
+ * Verify the status length and
+ * add the length to "frlengths[]":
+ */
+ if (len > td->len) {
+ /* should not happen */
+ DPRINTF("Invalid status length, "
+ "0x%04x/0x%04x bytes\n", len, td->len);
+ status = XHCI_TRB_ERROR_LENGTH;
+ } else if (xfer->aframes != xfer->nframes) {
+ xfer->frlengths[xfer->aframes] += td->len - len;
+ }
+ /* Check for last transfer */
+ if (((void *)td) == xfer->td_transfer_last) {
+ td = NULL;
+ break;
+ }
+ /* Check for transfer error */
+ if (status != XHCI_TRB_ERROR_SHORT_PKT &&
+ status != XHCI_TRB_ERROR_SUCCESS) {
+ /* the transfer is finished */
+ td = NULL;
+ break;
+ }
+ /* Check for short transfer */
+ if (len > 0) {
+ if (xfer->flags_int.short_frames_ok ||
+ xfer->flags_int.isochronous_xfr ||
+ xfer->flags_int.control_xfr) {
+ /* follow alt next */
+ td = td->alt_next;
+ } else {
+ /* the transfer is finished */
+ td = NULL;
+ }
+ break;
+ }
+ td = td->obj_next;
+
+ if (td->alt_next != td_alt_next) {
+ /* this USB frame is complete */
+ break;
+ }
+ }
+
+ /* update transfer cache */
+
+ xfer->td_transfer_cache = td;
+
+ return ((status == XHCI_TRB_ERROR_STALL) ? USB_ERR_STALLED :
+ (status != XHCI_TRB_ERROR_SHORT_PKT &&
+ status != XHCI_TRB_ERROR_SUCCESS) ? USB_ERR_IOERROR :
+ USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+xhci_generic_done(struct usb_xfer *xfer)
+{
+ usb_error_t err = 0;
+
+ DPRINTFN(13, "xfer=%p endpoint=%p transfer done\n",
+ xfer, xfer->endpoint);
+
+ /* reset scanner */
+
+ xfer->td_transfer_cache = xfer->td_transfer_first;
+
+ if (xfer->flags_int.control_xfr) {
+ if (xfer->flags_int.control_hdr)
+ err = xhci_generic_done_sub(xfer);
+
+ xfer->aframes = 1;
+
+ if (xfer->td_transfer_cache == NULL)
+ goto done;
+ }
+
+ while (xfer->aframes != xfer->nframes) {
+ err = xhci_generic_done_sub(xfer);
+ xfer->aframes++;
+
+ if (xfer->td_transfer_cache == NULL)
+ goto done;
+ }
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act)
+ err = xhci_generic_done_sub(xfer);
+done:
+ /* transfer is complete */
+ xhci_device_done(xfer, err);
+}
+
+static void
+xhci_activate_transfer(struct usb_xfer *xfer)
+{
+ struct xhci_td *td;
+
+ td = xfer->td_transfer_cache;
+
+ usb_pc_cpu_invalidate(td->page_cache);
+
+ if (!(td->td_trb[0].dwTrb3 & htole32(XHCI_TRB_3_CYCLE_BIT))) {
+ /* activate the transfer */
+
+ td->td_trb[0].dwTrb3 |= htole32(XHCI_TRB_3_CYCLE_BIT);
+ usb_pc_cpu_flush(td->page_cache);
+
+ xhci_endpoint_doorbell(xfer);
+ }
+}
+
+static void
+xhci_skip_transfer(struct usb_xfer *xfer)
+{
+ struct xhci_td *td;
+ struct xhci_td *td_last;
+
+ td = xfer->td_transfer_cache;
+ td_last = xfer->td_transfer_last;
+
+ td = td->alt_next;
+
+ usb_pc_cpu_invalidate(td->page_cache);
+
+ if (!(td->td_trb[0].dwTrb3 & htole32(XHCI_TRB_3_CYCLE_BIT))) {
+ usb_pc_cpu_invalidate(td_last->page_cache);
+
+ /* copy LINK TRB to current waiting location */
+
+ td->td_trb[0].qwTrb0 = td_last->td_trb[td_last->ntrb].qwTrb0;
+ td->td_trb[0].dwTrb2 = td_last->td_trb[td_last->ntrb].dwTrb2;
+ usb_pc_cpu_flush(td->page_cache);
+
+ td->td_trb[0].dwTrb3 = td_last->td_trb[td_last->ntrb].dwTrb3;
+ usb_pc_cpu_flush(td->page_cache);
+
+ xhci_endpoint_doorbell(xfer);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * xhci_check_transfer
+ *------------------------------------------------------------------------*/
+static void
+xhci_check_transfer(struct xhci_softc *sc, struct xhci_trb *trb)
+{
+ struct xhci_endpoint_ext *pepext;
+ int64_t offset;
+ uint64_t td_event;
+ uint32_t temp;
+ uint32_t remainder;
+ uint16_t stream_id = 0;
+ uint16_t i;
+ uint8_t status;
+ uint8_t halted;
+ uint8_t epno;
+ uint8_t index;
+
+ /* decode TRB */
+ td_event = le64toh(trb->qwTrb0);
+ temp = le32toh(trb->dwTrb2);
+
+ remainder = XHCI_TRB_2_REM_GET(temp);
+ status = XHCI_TRB_2_ERROR_GET(temp);
+
+ temp = le32toh(trb->dwTrb3);
+ epno = XHCI_TRB_3_EP_GET(temp);
+ index = XHCI_TRB_3_SLOT_GET(temp);
+
+ /* check if error means halted */
+ halted = (status != XHCI_TRB_ERROR_SHORT_PKT &&
+ status != XHCI_TRB_ERROR_SUCCESS);
+
+ DPRINTF("slot=%u epno=%u remainder=%u status=%u\n",
+ index, epno, remainder, status);
+
+ if (index > sc->sc_noslot) {
+ DPRINTF("Invalid slot.\n");
+ return;
+ }
+
+ if ((epno == 0) || (epno >= XHCI_MAX_ENDPOINTS)) {
+ DPRINTF("Invalid endpoint.\n");
+ return;
+ }
+
+ pepext = &sc->sc_hw.devs[index].endp[epno];
+
+ /* try to find the USB transfer that generated the event */
+ for (i = 0;; i++) {
+ struct usb_xfer *xfer;
+ struct xhci_td *td;
+
+ if (i == (XHCI_MAX_TRANSFERS - 1)) {
+ if (pepext->trb_ep_mode != USB_EP_MODE_STREAMS ||
+ stream_id == (XHCI_MAX_STREAMS - 1))
+ break;
+ stream_id++;
+ i = 0;
+ DPRINTFN(5, "stream_id=%u\n", stream_id);
+ }
+
+ xfer = pepext->xfer[i + (XHCI_MAX_TRANSFERS * stream_id)];
+ if (xfer == NULL)
+ continue;
+
+ td = xfer->td_transfer_cache;
+
+ DPRINTFN(5, "Checking if 0x%016llx == (0x%016llx .. 0x%016llx)\n",
+ (long long)td_event,
+ (long long)td->td_self,
+ (long long)td->td_self + sizeof(td->td_trb));
+
+ /*
+ * NOTE: Some XHCI implementations might not trigger
+ * an event on the last LINK TRB so we need to
+ * consider both the last and second last event
+ * address as conditions for a successful transfer.
+ *
+ * NOTE: We assume that the XHCI will only trigger one
+ * event per chain of TRBs.
+ */
+
+ offset = td_event - td->td_self;
+
+ if (offset >= 0 &&
+ offset < (int64_t)sizeof(td->td_trb)) {
+ usb_pc_cpu_invalidate(td->page_cache);
+
+ /* compute rest of remainder, if any */
+ for (i = (offset / 16) + 1; i < td->ntrb; i++) {
+ temp = le32toh(td->td_trb[i].dwTrb2);
+ remainder += XHCI_TRB_2_BYTES_GET(temp);
+ }
+
+ DPRINTFN(5, "New remainder: %u\n", remainder);
+
+ /* clear isochronous transfer errors */
+ if (xfer->flags_int.isochronous_xfr) {
+ if (halted) {
+ halted = 0;
+ status = XHCI_TRB_ERROR_SUCCESS;
+ remainder = td->len;
+ }
+ }
+
+ /* "td->remainder" is verified later */
+ td->remainder = remainder;
+ td->status = status;
+
+ usb_pc_cpu_flush(td->page_cache);
+
+ /*
+ * 1) Last transfer descriptor makes the
+ * transfer done
+ */
+ if (((void *)td) == xfer->td_transfer_last) {
+ DPRINTF("TD is last\n");
+ xhci_generic_done(xfer);
+ break;
+ }
+
+ /*
+ * 2) Any kind of error makes the transfer
+ * done
+ */
+ if (halted) {
+ DPRINTF("TD has I/O error\n");
+ xhci_generic_done(xfer);
+ break;
+ }
+
+ /*
+ * 3) If there is no alternate next transfer,
+ * a short packet also makes the transfer done
+ */
+ if (td->remainder > 0) {
+ if (td->alt_next == NULL) {
+ DPRINTF(
+ "short TD has no alternate next\n");
+ xhci_generic_done(xfer);
+ break;
+ }
+ DPRINTF("TD has short pkt\n");
+ if (xfer->flags_int.short_frames_ok ||
+ xfer->flags_int.isochronous_xfr ||
+ xfer->flags_int.control_xfr) {
+ /* follow the alt next */
+ xfer->td_transfer_cache = td->alt_next;
+ xhci_activate_transfer(xfer);
+ break;
+ }
+ xhci_skip_transfer(xfer);
+ xhci_generic_done(xfer);
+ break;
+ }
+
+ /*
+ * 4) Transfer complete - go to next TD
+ */
+ DPRINTF("Following next TD\n");
+ xfer->td_transfer_cache = td->obj_next;
+ xhci_activate_transfer(xfer);
+ break; /* there should only be one match */
+ }
+ }
+}
+
+static int
+xhci_check_command(struct xhci_softc *sc, struct xhci_trb *trb)
+{
+ if (sc->sc_cmd_addr == trb->qwTrb0) {
+ DPRINTF("Received command event\n");
+ sc->sc_cmd_result[0] = trb->dwTrb2;
+ sc->sc_cmd_result[1] = trb->dwTrb3;
+ cv_signal(&sc->sc_cmd_cv);
+ return (1); /* command match */
+ }
+ return (0);
+}
+
+static int
+xhci_interrupt_poll(struct xhci_softc *sc)
+{
+ struct usb_page_search buf_res;
+ struct xhci_hw_root *phwr;
+ uint64_t addr;
+ uint32_t temp;
+ int retval = 0;
+ uint16_t i;
+ uint8_t event;
+ uint8_t j;
+ uint8_t k;
+ uint8_t t;
+
+ usbd_get_page(&sc->sc_hw.root_pc, 0, &buf_res);
+
+ phwr = buf_res.buffer;
+
+ /* Receive any events */
+
+ usb_pc_cpu_invalidate(&sc->sc_hw.root_pc);
+
+ i = sc->sc_event_idx;
+ j = sc->sc_event_ccs;
+ t = 2;
+
+ while (1) {
+ temp = le32toh(phwr->hwr_events[i].dwTrb3);
+
+ k = (temp & XHCI_TRB_3_CYCLE_BIT) ? 1 : 0;
+
+ if (j != k)
+ break;
+
+ event = XHCI_TRB_3_TYPE_GET(temp);
+
+ DPRINTFN(10, "event[%u] = %u (0x%016llx 0x%08lx 0x%08lx)\n",
+ i, event, (long long)le64toh(phwr->hwr_events[i].qwTrb0),
+ (long)le32toh(phwr->hwr_events[i].dwTrb2),
+ (long)le32toh(phwr->hwr_events[i].dwTrb3));
+
+ switch (event) {
+ case XHCI_TRB_EVENT_TRANSFER:
+ xhci_check_transfer(sc, &phwr->hwr_events[i]);
+ break;
+ case XHCI_TRB_EVENT_CMD_COMPLETE:
+ retval |= xhci_check_command(sc, &phwr->hwr_events[i]);
+ break;
+ default:
+ DPRINTF("Unhandled event = %u\n", event);
+ break;
+ }
+
+ i++;
+
+ if (i == XHCI_MAX_EVENTS) {
+ i = 0;
+ j ^= 1;
+
+ /* check for timeout */
+ if (!--t)
+ break;
+ }
+ }
+
+ sc->sc_event_idx = i;
+ sc->sc_event_ccs = j;
+
+ /*
+ * NOTE: The Event Ring Dequeue Pointer Register is 64-bit
+ * latched. That means to activate the register we need to
+ * write both the low and high double word of the 64-bit
+ * register.
+ */
+
+ addr = buf_res.physaddr;
+ addr += __offsetof(struct xhci_hw_root, hwr_events[i]);
+
+ /* try to clear busy bit */
+ addr |= XHCI_ERDP_LO_BUSY;
+
+ XWRITE4(sc, runt, XHCI_ERDP_LO(0), (uint32_t)addr);
+ XWRITE4(sc, runt, XHCI_ERDP_HI(0), (uint32_t)(addr >> 32));
+
+ return (retval);
+}
+
+static usb_error_t
+xhci_do_command(struct xhci_softc *sc, struct xhci_trb *trb,
+ uint16_t timeout_ms)
+{
+ struct usb_page_search buf_res;
+ struct xhci_hw_root *phwr;
+ uint64_t addr;
+ uint32_t temp;
+ uint8_t i;
+ uint8_t j;
+ uint8_t timeout = 0;
+ int err;
+
+ XHCI_CMD_ASSERT_LOCKED(sc);
+
+ /* get hardware root structure */
+
+ usbd_get_page(&sc->sc_hw.root_pc, 0, &buf_res);
+
+ phwr = buf_res.buffer;
+
+ /* Queue command */
+
+ USB_BUS_LOCK(&sc->sc_bus);
+retry:
+ i = sc->sc_command_idx;
+ j = sc->sc_command_ccs;
+
+ DPRINTFN(10, "command[%u] = %u (0x%016llx, 0x%08lx, 0x%08lx)\n",
+ i, XHCI_TRB_3_TYPE_GET(le32toh(trb->dwTrb3)),
+ (long long)le64toh(trb->qwTrb0),
+ (long)le32toh(trb->dwTrb2),
+ (long)le32toh(trb->dwTrb3));
+
+ phwr->hwr_commands[i].qwTrb0 = trb->qwTrb0;
+ phwr->hwr_commands[i].dwTrb2 = trb->dwTrb2;
+
+ usb_pc_cpu_flush(&sc->sc_hw.root_pc);
+
+ temp = trb->dwTrb3;
+
+ if (j)
+ temp |= htole32(XHCI_TRB_3_CYCLE_BIT);
+ else
+ temp &= ~htole32(XHCI_TRB_3_CYCLE_BIT);
+
+ temp &= ~htole32(XHCI_TRB_3_TC_BIT);
+
+ phwr->hwr_commands[i].dwTrb3 = temp;
+
+ usb_pc_cpu_flush(&sc->sc_hw.root_pc);
+
+ addr = buf_res.physaddr;
+ addr += __offsetof(struct xhci_hw_root, hwr_commands[i]);
+
+ sc->sc_cmd_addr = htole64(addr);
+
+ i++;
+
+ if (i == (XHCI_MAX_COMMANDS - 1)) {
+ if (j) {
+ temp = htole32(XHCI_TRB_3_TC_BIT |
+ XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK) |
+ XHCI_TRB_3_CYCLE_BIT);
+ } else {
+ temp = htole32(XHCI_TRB_3_TC_BIT |
+ XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK));
+ }
+
+ phwr->hwr_commands[i].dwTrb3 = temp;
+
+ usb_pc_cpu_flush(&sc->sc_hw.root_pc);
+
+ i = 0;
+ j ^= 1;
+ }
+
+ sc->sc_command_idx = i;
+ sc->sc_command_ccs = j;
+
+ XWRITE4(sc, door, XHCI_DOORBELL(0), 0);
+
+ err = cv_timedwait(&sc->sc_cmd_cv, &sc->sc_bus.bus_mtx,
+ USB_MS_TO_TICKS(timeout_ms));
+
+ /*
+ * In some error cases event interrupts are not generated.
+ * Poll one time to see if the command has completed.
+ */
+ if (err != 0 && xhci_interrupt_poll(sc) != 0) {
+ DPRINTF("Command was completed when polling\n");
+ err = 0;
+ }
+ if (err != 0) {
+ DPRINTF("Command timeout!\n");
+ /*
+ * After some weeks of continuous operation, it has
+ * been observed that the ASMedia Technology, ASM1042
+ * SuperSpeed USB Host Controller can suddenly stop
+ * accepting commands via the command queue. Try to
+ * first reset the command queue. If that fails do a
+ * host controller reset.
+ */
+ if (timeout == 0 &&
+ xhci_reset_command_queue_locked(sc) == 0) {
+ temp = le32toh(trb->dwTrb3);
+
+ /*
+ * Avoid infinite XHCI reset loops if the set
+ * address command fails to respond due to a
+ * non-enumerating device:
+ */
+ if (XHCI_TRB_3_TYPE_GET(temp) == XHCI_TRB_TYPE_ADDRESS_DEVICE &&
+ (temp & XHCI_TRB_3_BSR_BIT) == 0) {
+ DPRINTF("Set address timeout\n");
+ } else {
+ timeout = 1;
+ goto retry;
+ }
+ } else {
+ DPRINTF("Controller reset!\n");
+ usb_bus_reset_async_locked(&sc->sc_bus);
+ }
+ err = USB_ERR_TIMEOUT;
+ trb->dwTrb2 = 0;
+ trb->dwTrb3 = 0;
+ } else {
+ temp = le32toh(sc->sc_cmd_result[0]);
+ if (XHCI_TRB_2_ERROR_GET(temp) != XHCI_TRB_ERROR_SUCCESS)
+ err = USB_ERR_IOERROR;
+
+ trb->dwTrb2 = sc->sc_cmd_result[0];
+ trb->dwTrb3 = sc->sc_cmd_result[1];
+ }
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ return (err);
+}
+
+#if 0
+static usb_error_t
+xhci_cmd_nop(struct xhci_softc *sc)
+{
+ struct xhci_trb trb;
+ uint32_t temp;
+
+ DPRINTF("\n");
+
+ trb.qwTrb0 = 0;
+ trb.dwTrb2 = 0;
+ temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_NOOP);
+
+ trb.dwTrb3 = htole32(temp);
+
+ return (xhci_do_command(sc, &trb, 100 /* ms */));
+}
+#endif
+
+static usb_error_t
+xhci_cmd_enable_slot(struct xhci_softc *sc, uint8_t *pslot)
+{
+ struct xhci_trb trb;
+ uint32_t temp;
+ usb_error_t err;
+
+ DPRINTF("\n");
+
+ trb.qwTrb0 = 0;
+ trb.dwTrb2 = 0;
+ trb.dwTrb3 = htole32(XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_ENABLE_SLOT));
+
+ err = xhci_do_command(sc, &trb, 100 /* ms */);
+ if (err)
+ goto done;
+
+ temp = le32toh(trb.dwTrb3);
+
+ *pslot = XHCI_TRB_3_SLOT_GET(temp);
+
+done:
+ return (err);
+}
+
+static usb_error_t
+xhci_cmd_disable_slot(struct xhci_softc *sc, uint8_t slot_id)
+{
+ struct xhci_trb trb;
+ uint32_t temp;
+
+ DPRINTF("\n");
+
+ trb.qwTrb0 = 0;
+ trb.dwTrb2 = 0;
+ temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_DISABLE_SLOT) |
+ XHCI_TRB_3_SLOT_SET(slot_id);
+
+ trb.dwTrb3 = htole32(temp);
+
+ return (xhci_do_command(sc, &trb, 100 /* ms */));
+}
+
+static usb_error_t
+xhci_cmd_set_address(struct xhci_softc *sc, uint64_t input_ctx,
+ uint8_t bsr, uint8_t slot_id)
+{
+ struct xhci_trb trb;
+ uint32_t temp;
+
+ DPRINTF("\n");
+
+ trb.qwTrb0 = htole64(input_ctx);
+ trb.dwTrb2 = 0;
+ temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_ADDRESS_DEVICE) |
+ XHCI_TRB_3_SLOT_SET(slot_id);
+
+ if (bsr)
+ temp |= XHCI_TRB_3_BSR_BIT;
+
+ trb.dwTrb3 = htole32(temp);
+
+ return (xhci_do_command(sc, &trb, 500 /* ms */));
+}
+
+static usb_error_t
+xhci_set_address(struct usb_device *udev, struct mtx *mtx, uint16_t address)
+{
+ struct usb_page_search buf_inp;
+ struct usb_page_search buf_dev;
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ struct xhci_hw_dev *hdev;
+ struct xhci_slot_ctx *slot;
+ struct xhci_endpoint_ext *pepext;
+ uint32_t temp;
+ uint16_t mps;
+ usb_error_t err;
+ uint8_t index;
+
+ /* the root HUB case is not handled here */
+ if (udev->parent_hub == NULL)
+ return (USB_ERR_INVAL);
+
+ index = udev->controller_slot_id;
+
+ hdev = &sc->sc_hw.devs[index];
+
+ if (mtx != NULL)
+ mtx_unlock(mtx);
+
+ XHCI_CMD_LOCK(sc);
+
+ switch (hdev->state) {
+ case XHCI_ST_DEFAULT:
+ case XHCI_ST_ENABLED:
+
+ hdev->state = XHCI_ST_ENABLED;
+
+ /* set configure mask to slot and EP0 */
+ xhci_configure_mask(udev, 3, 0);
+
+ /* configure input slot context structure */
+ err = xhci_configure_device(udev);
+
+ if (err != 0) {
+ DPRINTF("Could not configure device\n");
+ break;
+ }
+
+ /* configure input endpoint context structure */
+ switch (udev->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ mps = 8;
+ break;
+ case USB_SPEED_HIGH:
+ mps = 64;
+ break;
+ default:
+ mps = 512;
+ break;
+ }
+
+ pepext = xhci_get_endpoint_ext(udev,
+ &udev->ctrl_ep_desc);
+
+ /* ensure the control endpoint is setup again */
+ USB_BUS_LOCK(udev->bus);
+ pepext->trb_halted = 1;
+ pepext->trb_running = 0;
+ USB_BUS_UNLOCK(udev->bus);
+
+ err = xhci_configure_endpoint(udev,
+ &udev->ctrl_ep_desc, pepext,
+ 0, 1, 1, 0, mps, mps, USB_EP_MODE_DEFAULT);
+
+ if (err != 0) {
+ DPRINTF("Could not configure default endpoint\n");
+ break;
+ }
+
+ /* execute set address command */
+ usbd_get_page(&hdev->input_pc, 0, &buf_inp);
+
+ err = xhci_cmd_set_address(sc, buf_inp.physaddr,
+ (address == 0), index);
+
+ if (err != 0) {
+ temp = le32toh(sc->sc_cmd_result[0]);
+ if (address == 0 && sc->sc_port_route != NULL &&
+ XHCI_TRB_2_ERROR_GET(temp) ==
+ XHCI_TRB_ERROR_PARAMETER) {
+ /* LynxPoint XHCI - ports are not switchable */
+ /* Un-route all ports from the XHCI */
+ sc->sc_port_route(sc->sc_bus.parent, 0, ~0);
+ }
+ DPRINTF("Could not set address "
+ "for slot %u.\n", index);
+ if (address != 0)
+ break;
+ }
+
+ /* update device address to new value */
+
+ usbd_get_page(&hdev->device_pc, 0, &buf_dev);
+ slot = XHCI_GET_CTX(sc, xhci_dev_ctx, ctx_slot,
+ buf_dev.buffer);
+ usb_pc_cpu_invalidate(&hdev->device_pc);
+
+ temp = le32toh(slot->dwSctx3);
+ udev->address = XHCI_SCTX_3_DEV_ADDR_GET(temp);
+
+ /* update device state to new value */
+
+ if (address != 0)
+ hdev->state = XHCI_ST_ADDRESSED;
+ else
+ hdev->state = XHCI_ST_DEFAULT;
+ break;
+
+ default:
+ DPRINTF("Wrong state for set address.\n");
+ err = USB_ERR_IOERROR;
+ break;
+ }
+ XHCI_CMD_UNLOCK(sc);
+
+ if (mtx != NULL)
+ mtx_lock(mtx);
+
+ return (err);
+}
+
+static usb_error_t
+xhci_cmd_configure_ep(struct xhci_softc *sc, uint64_t input_ctx,
+ uint8_t deconfigure, uint8_t slot_id)
+{
+ struct xhci_trb trb;
+ uint32_t temp;
+
+ DPRINTF("\n");
+
+ trb.qwTrb0 = htole64(input_ctx);
+ trb.dwTrb2 = 0;
+ temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_CONFIGURE_EP) |
+ XHCI_TRB_3_SLOT_SET(slot_id);
+
+ if (deconfigure) {
+ if (sc->sc_no_deconfigure != 0 || xhcidcepquirk != 0)
+ return (0); /* Success */
+ temp |= XHCI_TRB_3_DCEP_BIT;
+ }
+
+ trb.dwTrb3 = htole32(temp);
+
+ return (xhci_do_command(sc, &trb, 100 /* ms */));
+}
+
+static usb_error_t
+xhci_cmd_evaluate_ctx(struct xhci_softc *sc, uint64_t input_ctx,
+ uint8_t slot_id)
+{
+ struct xhci_trb trb;
+ uint32_t temp;
+
+ DPRINTF("\n");
+
+ trb.qwTrb0 = htole64(input_ctx);
+ trb.dwTrb2 = 0;
+ temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_EVALUATE_CTX) |
+ XHCI_TRB_3_SLOT_SET(slot_id);
+ trb.dwTrb3 = htole32(temp);
+
+ return (xhci_do_command(sc, &trb, 100 /* ms */));
+}
+
+static usb_error_t
+xhci_cmd_reset_ep(struct xhci_softc *sc, uint8_t preserve,
+ uint8_t ep_id, uint8_t slot_id)
+{
+ struct xhci_trb trb;
+ uint32_t temp;
+
+ DPRINTF("\n");
+
+ trb.qwTrb0 = 0;
+ trb.dwTrb2 = 0;
+ temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_RESET_EP) |
+ XHCI_TRB_3_SLOT_SET(slot_id) |
+ XHCI_TRB_3_EP_SET(ep_id);
+
+ if (preserve)
+ temp |= XHCI_TRB_3_PRSV_BIT;
+
+ trb.dwTrb3 = htole32(temp);
+
+ return (xhci_do_command(sc, &trb, 100 /* ms */));
+}
+
+static usb_error_t
+xhci_cmd_set_tr_dequeue_ptr(struct xhci_softc *sc, uint64_t dequeue_ptr,
+ uint16_t stream_id, uint8_t ep_id, uint8_t slot_id)
+{
+ struct xhci_trb trb;
+ uint32_t temp;
+
+ DPRINTF("\n");
+
+ trb.qwTrb0 = htole64(dequeue_ptr);
+
+ temp = XHCI_TRB_2_STREAM_SET(stream_id);
+ trb.dwTrb2 = htole32(temp);
+
+ temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_SET_TR_DEQUEUE) |
+ XHCI_TRB_3_SLOT_SET(slot_id) |
+ XHCI_TRB_3_EP_SET(ep_id);
+ trb.dwTrb3 = htole32(temp);
+
+ return (xhci_do_command(sc, &trb, 100 /* ms */));
+}
+
+static usb_error_t
+xhci_cmd_stop_ep(struct xhci_softc *sc, uint8_t suspend,
+ uint8_t ep_id, uint8_t slot_id)
+{
+ struct xhci_trb trb;
+ uint32_t temp;
+
+ DPRINTF("\n");
+
+ trb.qwTrb0 = 0;
+ trb.dwTrb2 = 0;
+ temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_STOP_EP) |
+ XHCI_TRB_3_SLOT_SET(slot_id) |
+ XHCI_TRB_3_EP_SET(ep_id);
+
+ if (suspend)
+ temp |= XHCI_TRB_3_SUSP_EP_BIT;
+
+ trb.dwTrb3 = htole32(temp);
+
+ return (xhci_do_command(sc, &trb, 100 /* ms */));
+}
+
+static usb_error_t
+xhci_cmd_reset_dev(struct xhci_softc *sc, uint8_t slot_id)
+{
+ struct xhci_trb trb;
+ uint32_t temp;
+
+ DPRINTF("\n");
+
+ trb.qwTrb0 = 0;
+ trb.dwTrb2 = 0;
+ temp = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_RESET_DEVICE) |
+ XHCI_TRB_3_SLOT_SET(slot_id);
+
+ trb.dwTrb3 = htole32(temp);
+
+ return (xhci_do_command(sc, &trb, 100 /* ms */));
+}
+
+/*------------------------------------------------------------------------*
+ * xhci_interrupt - XHCI interrupt handler
+ *------------------------------------------------------------------------*/
+void
+xhci_interrupt(struct xhci_softc *sc)
+{
+ uint32_t status;
+ uint32_t temp;
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ status = XREAD4(sc, oper, XHCI_USBSTS);
+
+ /* acknowledge interrupts, if any */
+ if (status != 0) {
+ XWRITE4(sc, oper, XHCI_USBSTS, status);
+ DPRINTFN(16, "real interrupt (status=0x%08x)\n", status);
+ }
+
+ temp = XREAD4(sc, runt, XHCI_IMAN(0));
+
+ /* force clearing of pending interrupts */
+ if (temp & XHCI_IMAN_INTR_PEND)
+ XWRITE4(sc, runt, XHCI_IMAN(0), temp);
+
+ /* check for event(s) */
+ xhci_interrupt_poll(sc);
+
+ if (status & (XHCI_STS_PCD | XHCI_STS_HCH |
+ XHCI_STS_HSE | XHCI_STS_HCE)) {
+ if (status & XHCI_STS_PCD) {
+ xhci_root_intr(sc);
+ }
+
+ if (status & XHCI_STS_HCH) {
+ printf("%s: host controller halted\n",
+ __FUNCTION__);
+ }
+
+ if (status & XHCI_STS_HSE) {
+ printf("%s: host system error\n",
+ __FUNCTION__);
+ }
+
+ if (status & XHCI_STS_HCE) {
+ printf("%s: host controller error\n",
+ __FUNCTION__);
+ }
+ }
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+/*------------------------------------------------------------------------*
+ * xhci_timeout - XHCI timeout handler
+ *------------------------------------------------------------------------*/
+static void
+xhci_timeout(void *arg)
+{
+ struct usb_xfer *xfer = arg;
+
+ DPRINTF("xfer=%p\n", xfer);
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* transfer is transferred */
+ xhci_device_done(xfer, USB_ERR_TIMEOUT);
+}
+
+static void
+xhci_do_poll(struct usb_bus *bus)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(bus);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+ xhci_interrupt_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+}
+
+static void
+xhci_setup_generic_chain_sub(struct xhci_std_temp *temp)
+{
+ struct usb_page_search buf_res;
+ struct xhci_td *td;
+ struct xhci_td *td_next;
+ struct xhci_td *td_alt_next;
+ struct xhci_td *td_first;
+ uint32_t buf_offset;
+ uint32_t average;
+ uint32_t len_old;
+ uint32_t npkt_off;
+ uint32_t dword;
+ uint8_t shortpkt_old;
+ uint8_t precompute;
+ uint8_t x;
+
+ td_alt_next = NULL;
+ buf_offset = 0;
+ shortpkt_old = temp->shortpkt;
+ len_old = temp->len;
+ npkt_off = 0;
+ precompute = 1;
+
+restart:
+
+ td = temp->td;
+ td_next = td_first = temp->td_next;
+
+ while (1) {
+ if (temp->len == 0) {
+ if (temp->shortpkt)
+ break;
+
+ /* send a Zero Length Packet, ZLP, last */
+
+ temp->shortpkt = 1;
+ average = 0;
+
+ } else {
+ average = temp->average;
+
+ if (temp->len < average) {
+ if (temp->len % temp->max_packet_size) {
+ temp->shortpkt = 1;
+ }
+ average = temp->len;
+ }
+ }
+
+ if (td_next == NULL)
+ panic("%s: out of XHCI transfer descriptors!", __FUNCTION__);
+
+ /* get next TD */
+
+ td = td_next;
+ td_next = td->obj_next;
+
+ /* check if we are pre-computing */
+
+ if (precompute) {
+ /* update remaining length */
+
+ temp->len -= average;
+
+ continue;
+ }
+ /* fill out current TD */
+
+ td->len = average;
+ td->remainder = 0;
+ td->status = 0;
+
+ /* update remaining length */
+
+ temp->len -= average;
+
+ /* reset TRB index */
+
+ x = 0;
+
+ if (temp->trb_type == XHCI_TRB_TYPE_SETUP_STAGE) {
+ /* immediate data */
+
+ if (average > 8)
+ average = 8;
+
+ td->td_trb[0].qwTrb0 = 0;
+
+ usbd_copy_out(temp->pc, temp->offset + buf_offset,
+ (uint8_t *)(uintptr_t)&td->td_trb[0].qwTrb0,
+ average);
+
+ dword = XHCI_TRB_2_BYTES_SET(8) |
+ XHCI_TRB_2_TDSZ_SET(0) |
+ XHCI_TRB_2_IRQ_SET(0);
+
+ td->td_trb[0].dwTrb2 = htole32(dword);
+
+ dword = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_SETUP_STAGE) |
+ XHCI_TRB_3_IDT_BIT | XHCI_TRB_3_CYCLE_BIT;
+
+ /* check wLength */
+ if (td->td_trb[0].qwTrb0 &
+ htole64(XHCI_TRB_0_WLENGTH_MASK)) {
+ if (td->td_trb[0].qwTrb0 &
+ htole64(XHCI_TRB_0_DIR_IN_MASK))
+ dword |= XHCI_TRB_3_TRT_IN;
+ else
+ dword |= XHCI_TRB_3_TRT_OUT;
+ }
+
+ td->td_trb[0].dwTrb3 = htole32(dword);
+#ifdef USB_DEBUG
+ xhci_dump_trb(&td->td_trb[x]);
+#endif
+ x++;
+
+ } else do {
+ uint32_t npkt;
+
+ /* fill out buffer pointers */
+
+ if (average == 0) {
+ memset(&buf_res, 0, sizeof(buf_res));
+ } else {
+ usbd_get_page(temp->pc, temp->offset +
+ buf_offset, &buf_res);
+
+ /* get length to end of page */
+ if (buf_res.length > average)
+ buf_res.length = average;
+
+ /* check for maximum length */
+ if (buf_res.length > XHCI_TD_PAGE_SIZE)
+ buf_res.length = XHCI_TD_PAGE_SIZE;
+
+ npkt_off += buf_res.length;
+ }
+
+ /* set up npkt */
+ npkt = howmany(len_old - npkt_off,
+ temp->max_packet_size);
+
+ if (npkt == 0)
+ npkt = 1;
+ else if (npkt > 31)
+ npkt = 31;
+
+ /* fill out TRB's */
+ td->td_trb[x].qwTrb0 =
+ htole64((uint64_t)buf_res.physaddr);
+
+ dword =
+ XHCI_TRB_2_BYTES_SET(buf_res.length) |
+ XHCI_TRB_2_TDSZ_SET(npkt) |
+ XHCI_TRB_2_IRQ_SET(0);
+
+ td->td_trb[x].dwTrb2 = htole32(dword);
+
+ switch (temp->trb_type) {
+ case XHCI_TRB_TYPE_ISOCH:
+ dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT |
+ XHCI_TRB_3_TBC_SET(temp->tbc) |
+ XHCI_TRB_3_TLBPC_SET(temp->tlbpc);
+ if (td != td_first) {
+ dword |= XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_NORMAL);
+ } else if (temp->do_isoc_sync != 0) {
+ temp->do_isoc_sync = 0;
+ /* wait until "isoc_frame" */
+ dword |= XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_ISOCH) |
+ XHCI_TRB_3_FRID_SET(temp->isoc_frame / 8);
+ } else {
+ /* start data transfer at next interval */
+ dword |= XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_ISOCH) |
+ XHCI_TRB_3_ISO_SIA_BIT;
+ }
+ if (temp->direction == UE_DIR_IN)
+ dword |= XHCI_TRB_3_ISP_BIT;
+ break;
+ case XHCI_TRB_TYPE_DATA_STAGE:
+ dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT |
+ XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_DATA_STAGE);
+ if (temp->direction == UE_DIR_IN)
+ dword |= XHCI_TRB_3_DIR_IN | XHCI_TRB_3_ISP_BIT;
+ /*
+ * Section 3.2.9 in the XHCI
+ * specification about control
+ * transfers says that we should use a
+ * normal-TRB if there are more TRBs
+ * extending the data-stage
+ * TRB. Update the "trb_type".
+ */
+ temp->trb_type = XHCI_TRB_TYPE_NORMAL;
+ break;
+ case XHCI_TRB_TYPE_STATUS_STAGE:
+ dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT |
+ XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_STATUS_STAGE);
+ if (temp->direction == UE_DIR_IN)
+ dword |= XHCI_TRB_3_DIR_IN;
+ break;
+ default: /* XHCI_TRB_TYPE_NORMAL */
+ dword = XHCI_TRB_3_CHAIN_BIT | XHCI_TRB_3_CYCLE_BIT |
+ XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_NORMAL);
+ if (temp->direction == UE_DIR_IN)
+ dword |= XHCI_TRB_3_ISP_BIT;
+ break;
+ }
+ td->td_trb[x].dwTrb3 = htole32(dword);
+
+ average -= buf_res.length;
+ buf_offset += buf_res.length;
+#ifdef USB_DEBUG
+ xhci_dump_trb(&td->td_trb[x]);
+#endif
+ x++;
+
+ } while (average != 0);
+
+ td->td_trb[x-1].dwTrb3 |= htole32(XHCI_TRB_3_IOC_BIT);
+
+ /* store number of data TRB's */
+
+ td->ntrb = x;
+
+ DPRINTF("NTRB=%u\n", x);
+
+ /* fill out link TRB */
+
+ if (td_next != NULL) {
+ /* link the current TD with the next one */
+ td->td_trb[x].qwTrb0 = htole64((uint64_t)td_next->td_self);
+ DPRINTF("LINK=0x%08llx\n", (long long)td_next->td_self);
+ } else {
+ /* this field will get updated later */
+ DPRINTF("NOLINK\n");
+ }
+
+ dword = XHCI_TRB_2_IRQ_SET(0);
+
+ td->td_trb[x].dwTrb2 = htole32(dword);
+
+ dword = XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK) |
+ XHCI_TRB_3_CYCLE_BIT | XHCI_TRB_3_IOC_BIT |
+ /*
+ * CHAIN-BIT: Ensure that a multi-TRB IN-endpoint
+ * frame only receives a single short packet event
+ * by setting the CHAIN bit in the LINK field. In
+ * addition some XHCI controllers have problems
+ * sending a ZLP unless the CHAIN-BIT is set in
+ * the LINK TRB.
+ */
+ XHCI_TRB_3_CHAIN_BIT;
+
+ td->td_trb[x].dwTrb3 = htole32(dword);
+
+ td->alt_next = td_alt_next;
+#ifdef USB_DEBUG
+ xhci_dump_trb(&td->td_trb[x]);
+#endif
+ usb_pc_cpu_flush(td->page_cache);
+ }
+
+ if (precompute) {
+ precompute = 0;
+
+ /* set up alt next pointer, if any */
+ if (temp->last_frame) {
+ td_alt_next = NULL;
+ } else {
+ /* we use this field internally */
+ td_alt_next = td_next;
+ }
+
+ /* restore */
+ temp->shortpkt = shortpkt_old;
+ temp->len = len_old;
+ goto restart;
+ }
+
+ /*
+ * Remove cycle bit from the first TRB if we are
+ * stepping them:
+ */
+ if (temp->step_td != 0) {
+ td_first->td_trb[0].dwTrb3 &= ~htole32(XHCI_TRB_3_CYCLE_BIT);
+ usb_pc_cpu_flush(td_first->page_cache);
+ }
+
+ /* clear TD SIZE to zero, hence this is the last TRB */
+ /* remove chain bit because this is the last data TRB in the chain */
+ td->td_trb[td->ntrb - 1].dwTrb2 &= ~htole32(XHCI_TRB_2_TDSZ_SET(31));
+ td->td_trb[td->ntrb - 1].dwTrb3 &= ~htole32(XHCI_TRB_3_CHAIN_BIT);
+ /* remove CHAIN-BIT from last LINK TRB */
+ td->td_trb[td->ntrb].dwTrb3 &= ~htole32(XHCI_TRB_3_CHAIN_BIT);
+
+ usb_pc_cpu_flush(td->page_cache);
+
+ temp->td = td;
+ temp->td_next = td_next;
+}
+
+static void
+xhci_setup_generic_chain(struct usb_xfer *xfer)
+{
+ struct xhci_std_temp temp;
+ struct xhci_td *td;
+ uint32_t x;
+ uint32_t y;
+ uint8_t mult;
+
+ temp.do_isoc_sync = 0;
+ temp.step_td = 0;
+ temp.tbc = 0;
+ temp.tlbpc = 0;
+ temp.average = xfer->max_hc_frame_size;
+ temp.max_packet_size = xfer->max_packet_size;
+ temp.sc = XHCI_BUS2SC(xfer->xroot->bus);
+ temp.pc = NULL;
+ temp.last_frame = 0;
+ temp.offset = 0;
+ temp.multishort = xfer->flags_int.isochronous_xfr ||
+ xfer->flags_int.control_xfr ||
+ xfer->flags_int.short_frames_ok;
+
+ /* toggle the DMA set we are using */
+ xfer->flags_int.curr_dma_set ^= 1;
+
+ /* get next DMA set */
+ td = xfer->td_start[xfer->flags_int.curr_dma_set];
+
+ temp.td = NULL;
+ temp.td_next = td;
+
+ xfer->td_transfer_first = td;
+ xfer->td_transfer_cache = td;
+
+ if (xfer->flags_int.isochronous_xfr) {
+ uint8_t shift;
+
+ /* compute multiplier for ISOCHRONOUS transfers */
+ mult = xfer->endpoint->ecomp ?
+ UE_GET_SS_ISO_MULT(xfer->endpoint->ecomp->bmAttributes)
+ : 0;
+ /* check for USB 2.0 multiplier */
+ if (mult == 0) {
+ mult = (xfer->endpoint->edesc->
+ wMaxPacketSize[1] >> 3) & 3;
+ }
+ /* range check */
+ if (mult > 2)
+ mult = 3;
+ else
+ mult++;
+
+ x = XREAD4(temp.sc, runt, XHCI_MFINDEX);
+
+ DPRINTF("MFINDEX=0x%08x IST=0x%x\n", x, temp.sc->sc_ist);
+
+ switch (usbd_get_speed(xfer->xroot->udev)) {
+ case USB_SPEED_FULL:
+ shift = 3;
+ temp.isoc_delta = 8; /* 1ms */
+ break;
+ default:
+ shift = usbd_xfer_get_fps_shift(xfer);
+ temp.isoc_delta = 1U << shift;
+ break;
+ }
+
+ /* Compute isochronous scheduling threshold. */
+ if (temp.sc->sc_ist & 8)
+ y = (temp.sc->sc_ist & 7) << 3;
+ else
+ y = (temp.sc->sc_ist & 7);
+
+ /* Range check the IST. */
+ if (y < 8) {
+ y = 0;
+ } else if (y > 15) {
+ DPRINTFN(3, "IST(%d) is too big!\n", temp.sc->sc_ist);
+ /*
+ * The USB stack minimum isochronous transfer
+ * size is typically 2x2 ms of payload. If the
+ * IST makes is above 15 microframes, we have
+ * an effective scheduling delay of more than
+ * or equal to 2 milliseconds, which is too
+ * much.
+ */
+ y = 7;
+ } else {
+ /*
+ * Subtract one millisecond, because the
+ * generic code adds that to the latency.
+ */
+ y -= 8;
+ }
+
+ if (usbd_xfer_get_isochronous_start_frame(
+ xfer, x, y, 8, XHCI_MFINDEX_GET(-1), &temp.isoc_frame)) {
+ /* Start isochronous transfer at specified time. */
+ temp.do_isoc_sync = 1;
+
+ DPRINTFN(3, "start next=%d\n", temp.isoc_frame);
+ }
+
+ x = 0;
+ temp.trb_type = XHCI_TRB_TYPE_ISOCH;
+
+ } else if (xfer->flags_int.control_xfr) {
+ /* check if we should prepend a setup message */
+
+ if (xfer->flags_int.control_hdr) {
+ temp.len = xfer->frlengths[0];
+ temp.pc = xfer->frbuffers + 0;
+ temp.shortpkt = temp.len ? 1 : 0;
+ temp.trb_type = XHCI_TRB_TYPE_SETUP_STAGE;
+ temp.direction = 0;
+
+ /* check for last frame */
+ if (xfer->nframes == 1) {
+ /* no STATUS stage yet, SETUP is last */
+ if (xfer->flags_int.control_act)
+ temp.last_frame = 1;
+ }
+
+ xhci_setup_generic_chain_sub(&temp);
+ }
+ x = 1;
+ mult = 1;
+ temp.isoc_delta = 0;
+ temp.isoc_frame = 0;
+ temp.trb_type = xfer->flags_int.control_did_data ?
+ XHCI_TRB_TYPE_NORMAL : XHCI_TRB_TYPE_DATA_STAGE;
+ } else {
+ x = 0;
+ mult = 1;
+ temp.isoc_delta = 0;
+ temp.isoc_frame = 0;
+ temp.trb_type = XHCI_TRB_TYPE_NORMAL;
+ }
+
+ if (x != xfer->nframes) {
+ /* set up page_cache pointer */
+ temp.pc = xfer->frbuffers + x;
+ /* set endpoint direction */
+ temp.direction = UE_GET_DIR(xfer->endpointno);
+ }
+
+ while (x != xfer->nframes) {
+ /* DATA0 / DATA1 message */
+
+ temp.len = xfer->frlengths[x];
+ temp.step_td = ((xfer->endpointno & UE_DIR_IN) &&
+ x != 0 && temp.multishort == 0);
+
+ x++;
+
+ if (x == xfer->nframes) {
+ if (xfer->flags_int.control_xfr) {
+ /* no STATUS stage yet, DATA is last */
+ if (xfer->flags_int.control_act)
+ temp.last_frame = 1;
+ } else {
+ temp.last_frame = 1;
+ }
+ }
+ if (temp.len == 0) {
+ /* make sure that we send an USB packet */
+
+ temp.shortpkt = 0;
+
+ temp.tbc = 0;
+ temp.tlbpc = mult - 1;
+
+ } else if (xfer->flags_int.isochronous_xfr) {
+ uint8_t tdpc;
+
+ /*
+ * Isochronous transfers don't have short
+ * packet termination:
+ */
+
+ temp.shortpkt = 1;
+
+ /* isochronous transfers have a transfer limit */
+
+ if (temp.len > xfer->max_frame_size)
+ temp.len = xfer->max_frame_size;
+
+ /* compute TD packet count */
+ tdpc = howmany(temp.len, xfer->max_packet_size);
+
+ temp.tbc = howmany(tdpc, mult) - 1;
+ temp.tlbpc = (tdpc % mult);
+
+ if (temp.tlbpc == 0)
+ temp.tlbpc = mult - 1;
+ else
+ temp.tlbpc--;
+ } else {
+ /* regular data transfer */
+
+ temp.shortpkt = xfer->flags.force_short_xfer ? 0 : 1;
+ }
+
+ xhci_setup_generic_chain_sub(&temp);
+
+ if (xfer->flags_int.isochronous_xfr) {
+ temp.offset += xfer->frlengths[x - 1];
+ temp.isoc_frame += temp.isoc_delta;
+ } else {
+ /* get next Page Cache pointer */
+ temp.pc = xfer->frbuffers + x;
+ }
+ }
+
+ /* check if we should append a status stage */
+
+ if (xfer->flags_int.control_xfr &&
+ !xfer->flags_int.control_act) {
+ /*
+ * Send a DATA1 message and invert the current
+ * endpoint direction.
+ */
+ if (xhcictlstep || temp.sc->sc_ctlstep) {
+ /*
+ * Some XHCI controllers will not delay the
+ * status stage until the next SOF. Force this
+ * behaviour to avoid failed control
+ * transfers.
+ */
+ temp.step_td = (xfer->nframes != 0);
+ } else {
+ temp.step_td = 0;
+ }
+ temp.direction = UE_GET_DIR(xfer->endpointno) ^ UE_DIR_IN;
+ temp.len = 0;
+ temp.pc = NULL;
+ temp.shortpkt = 0;
+ temp.last_frame = 1;
+ temp.trb_type = XHCI_TRB_TYPE_STATUS_STAGE;
+
+ xhci_setup_generic_chain_sub(&temp);
+ }
+
+ td = temp.td;
+
+ /* must have at least one frame! */
+
+ xfer->td_transfer_last = td;
+
+ DPRINTF("first=%p last=%p\n", xfer->td_transfer_first, td);
+}
+
+static void
+xhci_set_slot_pointer(struct xhci_softc *sc, uint8_t index, uint64_t dev_addr)
+{
+ struct usb_page_search buf_res;
+ struct xhci_dev_ctx_addr *pdctxa;
+
+ usbd_get_page(&sc->sc_hw.ctx_pc, 0, &buf_res);
+
+ pdctxa = buf_res.buffer;
+
+ DPRINTF("addr[%u]=0x%016llx\n", index, (long long)dev_addr);
+
+ pdctxa->qwBaaDevCtxAddr[index] = htole64(dev_addr);
+
+ usb_pc_cpu_flush(&sc->sc_hw.ctx_pc);
+}
+
+static usb_error_t
+xhci_configure_mask(struct usb_device *udev, uint32_t mask, uint8_t drop)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ struct usb_page_search buf_inp;
+ struct xhci_input_ctx *input;
+ struct xhci_slot_ctx *slot;
+ uint32_t temp;
+ uint8_t index;
+ uint8_t x;
+
+ index = udev->controller_slot_id;
+
+ usbd_get_page(&sc->sc_hw.devs[index].input_pc, 0, &buf_inp);
+
+ input = XHCI_GET_CTX(sc, xhci_input_dev_ctx, ctx_input,
+ buf_inp.buffer);
+ slot = XHCI_GET_CTX(sc, xhci_input_dev_ctx, ctx_slot, buf_inp.buffer);
+
+ if (drop) {
+ mask &= XHCI_INCTX_NON_CTRL_MASK;
+ input->dwInCtx0 = htole32(mask);
+ input->dwInCtx1 = htole32(0);
+ } else {
+ /*
+ * Some hardware requires that we drop the endpoint
+ * context before adding it again:
+ */
+ input->dwInCtx0 = htole32(mask & XHCI_INCTX_NON_CTRL_MASK);
+
+ /* Add new endpoint context */
+ input->dwInCtx1 = htole32(mask);
+
+ /* find most significant set bit */
+ for (x = 31; x != 1; x--) {
+ if (mask & (1 << x))
+ break;
+ }
+
+ /* adjust */
+ x--;
+
+ /* figure out the maximum number of contexts */
+ if (x > sc->sc_hw.devs[index].context_num)
+ sc->sc_hw.devs[index].context_num = x;
+ else
+ x = sc->sc_hw.devs[index].context_num;
+
+ /* update number of contexts */
+ temp = le32toh(slot->dwSctx0);
+ temp &= ~XHCI_SCTX_0_CTX_NUM_SET(31);
+ temp |= XHCI_SCTX_0_CTX_NUM_SET(x + 1);
+ slot->dwSctx0 = htole32(temp);
+ }
+ usb_pc_cpu_flush(&sc->sc_hw.devs[index].input_pc);
+ return (0);
+}
+
+static usb_error_t
+xhci_configure_endpoint(struct usb_device *udev,
+ struct usb_endpoint_descriptor *edesc, struct xhci_endpoint_ext *pepext,
+ uint16_t interval, uint8_t max_packet_count,
+ uint8_t mult, uint8_t fps_shift, uint16_t max_packet_size,
+ uint16_t max_frame_size, uint8_t ep_mode)
+{
+ struct usb_page_search buf_inp;
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ struct xhci_endp_ctx *endp;
+ uint64_t ring_addr = pepext->physaddr;
+ uint32_t temp;
+ uint8_t index;
+ uint8_t epno;
+ uint8_t type;
+
+ index = udev->controller_slot_id;
+
+ usbd_get_page(&sc->sc_hw.devs[index].input_pc, 0, &buf_inp);
+
+ epno = edesc->bEndpointAddress;
+ type = edesc->bmAttributes & UE_XFERTYPE;
+
+ if (type == UE_CONTROL)
+ epno |= UE_DIR_IN;
+
+ epno = XHCI_EPNO2EPID(epno);
+
+ if (epno == 0)
+ return (USB_ERR_NO_PIPE); /* invalid */
+
+ if (max_packet_count == 0)
+ return (USB_ERR_BAD_BUFSIZE);
+
+ max_packet_count--;
+
+ if (mult == 0)
+ return (USB_ERR_BAD_BUFSIZE);
+
+ endp = XHCI_GET_CTX(sc, xhci_input_dev_ctx, ctx_ep[epno - 1],
+ buf_inp.buffer);
+
+ /* store endpoint mode */
+ pepext->trb_ep_mode = ep_mode;
+ /* store bMaxPacketSize for control endpoints */
+ pepext->trb_ep_maxp = edesc->wMaxPacketSize[0];
+ usb_pc_cpu_flush(pepext->page_cache);
+
+ if (ep_mode == USB_EP_MODE_STREAMS) {
+ temp = XHCI_EPCTX_0_EPSTATE_SET(0) |
+ XHCI_EPCTX_0_MAXP_STREAMS_SET(XHCI_MAX_STREAMS_LOG - 1) |
+ XHCI_EPCTX_0_LSA_SET(1);
+
+ ring_addr += sizeof(struct xhci_trb) *
+ XHCI_MAX_TRANSFERS * XHCI_MAX_STREAMS;
+ } else {
+ temp = XHCI_EPCTX_0_EPSTATE_SET(0) |
+ XHCI_EPCTX_0_MAXP_STREAMS_SET(0) |
+ XHCI_EPCTX_0_LSA_SET(0);
+
+ ring_addr |= XHCI_EPCTX_2_DCS_SET(1);
+ }
+
+ switch (udev->speed) {
+ case USB_SPEED_FULL:
+ case USB_SPEED_LOW:
+ /* 1ms -> 125us */
+ fps_shift += 3;
+ break;
+ default:
+ break;
+ }
+
+ switch (type) {
+ case UE_INTERRUPT:
+ if (fps_shift > 3)
+ fps_shift--;
+ temp |= XHCI_EPCTX_0_IVAL_SET(fps_shift);
+ break;
+ case UE_ISOCHRONOUS:
+ temp |= XHCI_EPCTX_0_IVAL_SET(fps_shift);
+
+ switch (udev->speed) {
+ case USB_SPEED_SUPER:
+ if (mult > 3)
+ mult = 3;
+ temp |= XHCI_EPCTX_0_MULT_SET(mult - 1);
+ max_packet_count /= mult;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ endp->dwEpCtx0 = htole32(temp);
+
+ temp =
+ XHCI_EPCTX_1_HID_SET(0) |
+ XHCI_EPCTX_1_MAXB_SET(max_packet_count) |
+ XHCI_EPCTX_1_MAXP_SIZE_SET(max_packet_size);
+
+ /*
+ * Always enable the "three strikes and you are gone" feature
+ * except for ISOCHRONOUS endpoints. This is suggested by
+ * section 4.3.3 in the XHCI specification about device slot
+ * initialisation.
+ */
+ if (type != UE_ISOCHRONOUS)
+ temp |= XHCI_EPCTX_1_CERR_SET(3);
+
+ switch (type) {
+ case UE_CONTROL:
+ temp |= XHCI_EPCTX_1_EPTYPE_SET(4);
+ break;
+ case UE_ISOCHRONOUS:
+ temp |= XHCI_EPCTX_1_EPTYPE_SET(1);
+ break;
+ case UE_BULK:
+ temp |= XHCI_EPCTX_1_EPTYPE_SET(2);
+ break;
+ default:
+ temp |= XHCI_EPCTX_1_EPTYPE_SET(3);
+ break;
+ }
+
+ /* check for IN direction */
+ if (epno & 1)
+ temp |= XHCI_EPCTX_1_EPTYPE_SET(4);
+
+ endp->dwEpCtx1 = htole32(temp);
+ endp->qwEpCtx2 = htole64(ring_addr);
+
+ switch (edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_INTERRUPT:
+ case UE_ISOCHRONOUS:
+ temp = XHCI_EPCTX_4_MAX_ESIT_PAYLOAD_SET(max_frame_size) |
+ XHCI_EPCTX_4_AVG_TRB_LEN_SET(MIN(XHCI_PAGE_SIZE,
+ max_frame_size));
+ break;
+ case UE_CONTROL:
+ temp = XHCI_EPCTX_4_AVG_TRB_LEN_SET(8);
+ break;
+ default:
+ temp = XHCI_EPCTX_4_AVG_TRB_LEN_SET(XHCI_PAGE_SIZE);
+ break;
+ }
+
+ endp->dwEpCtx4 = htole32(temp);
+
+#ifdef USB_DEBUG
+ xhci_dump_endpoint(endp);
+#endif
+ usb_pc_cpu_flush(&sc->sc_hw.devs[index].input_pc);
+
+ return (0); /* success */
+}
+
+static usb_error_t
+xhci_configure_endpoint_by_xfer(struct usb_xfer *xfer)
+{
+ struct xhci_endpoint_ext *pepext;
+ struct usb_endpoint_ss_comp_descriptor *ecomp;
+ usb_stream_t x;
+
+ pepext = xhci_get_endpoint_ext(xfer->xroot->udev,
+ xfer->endpoint->edesc);
+
+ ecomp = xfer->endpoint->ecomp;
+
+ for (x = 0; x != XHCI_MAX_STREAMS; x++) {
+ uint64_t temp;
+
+ /* halt any transfers */
+ pepext->trb[x * XHCI_MAX_TRANSFERS].dwTrb3 = 0;
+
+ /* compute start of TRB ring for stream "x" */
+ temp = pepext->physaddr +
+ (x * XHCI_MAX_TRANSFERS * sizeof(struct xhci_trb)) +
+ XHCI_SCTX_0_SCT_SEC_TR_RING;
+
+ /* make tree structure */
+ pepext->trb[(XHCI_MAX_TRANSFERS *
+ XHCI_MAX_STREAMS) + x].qwTrb0 = htole64(temp);
+
+ /* reserved fields */
+ pepext->trb[(XHCI_MAX_TRANSFERS *
+ XHCI_MAX_STREAMS) + x].dwTrb2 = 0;
+ pepext->trb[(XHCI_MAX_TRANSFERS *
+ XHCI_MAX_STREAMS) + x].dwTrb3 = 0;
+ }
+ usb_pc_cpu_flush(pepext->page_cache);
+
+ return (xhci_configure_endpoint(xfer->xroot->udev,
+ xfer->endpoint->edesc, pepext,
+ xfer->interval, xfer->max_packet_count,
+ (ecomp != NULL) ? UE_GET_SS_ISO_MULT(ecomp->bmAttributes) + 1 : 1,
+ usbd_xfer_get_fps_shift(xfer), xfer->max_packet_size,
+ xfer->max_frame_size, xfer->endpoint->ep_mode));
+}
+
+static usb_error_t
+xhci_configure_device(struct usb_device *udev)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ struct usb_page_search buf_inp;
+ struct usb_page_cache *pcinp;
+ struct xhci_slot_ctx *slot;
+ struct usb_device *hubdev;
+ uint32_t temp;
+ uint32_t route;
+ uint32_t rh_port;
+ uint8_t is_hub;
+ uint8_t index;
+ uint8_t depth;
+
+ index = udev->controller_slot_id;
+
+ DPRINTF("index=%u\n", index);
+
+ pcinp = &sc->sc_hw.devs[index].input_pc;
+
+ usbd_get_page(pcinp, 0, &buf_inp);
+
+ slot = XHCI_GET_CTX(sc, xhci_input_dev_ctx, ctx_slot, buf_inp.buffer);
+
+ rh_port = 0;
+ route = 0;
+
+ /* figure out route string and root HUB port number */
+
+ for (hubdev = udev; hubdev != NULL; hubdev = hubdev->parent_hub) {
+ if (hubdev->parent_hub == NULL)
+ break;
+
+ depth = hubdev->parent_hub->depth;
+
+ /*
+ * NOTE: HS/FS/LS devices and the SS root HUB can have
+ * more than 15 ports
+ */
+
+ rh_port = hubdev->port_no;
+
+ if (depth == 0)
+ break;
+
+ if (rh_port > 15)
+ rh_port = 15;
+
+ if (depth < 6)
+ route |= rh_port << (4 * (depth - 1));
+ }
+
+ DPRINTF("Route=0x%08x\n", route);
+
+ temp = XHCI_SCTX_0_ROUTE_SET(route) |
+ XHCI_SCTX_0_CTX_NUM_SET(
+ sc->sc_hw.devs[index].context_num + 1);
+
+ switch (udev->speed) {
+ case USB_SPEED_LOW:
+ temp |= XHCI_SCTX_0_SPEED_SET(2);
+ if (udev->parent_hs_hub != NULL &&
+ udev->parent_hs_hub->ddesc.bDeviceProtocol ==
+ UDPROTO_HSHUBMTT) {
+ DPRINTF("Device inherits MTT\n");
+ temp |= XHCI_SCTX_0_MTT_SET(1);
+ }
+ break;
+ case USB_SPEED_HIGH:
+ temp |= XHCI_SCTX_0_SPEED_SET(3);
+ if (sc->sc_hw.devs[index].nports != 0 &&
+ udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) {
+ DPRINTF("HUB supports MTT\n");
+ temp |= XHCI_SCTX_0_MTT_SET(1);
+ }
+ break;
+ case USB_SPEED_FULL:
+ temp |= XHCI_SCTX_0_SPEED_SET(1);
+ if (udev->parent_hs_hub != NULL &&
+ udev->parent_hs_hub->ddesc.bDeviceProtocol ==
+ UDPROTO_HSHUBMTT) {
+ DPRINTF("Device inherits MTT\n");
+ temp |= XHCI_SCTX_0_MTT_SET(1);
+ }
+ break;
+ default:
+ temp |= XHCI_SCTX_0_SPEED_SET(4);
+ break;
+ }
+
+ is_hub = sc->sc_hw.devs[index].nports != 0 &&
+ (udev->speed == USB_SPEED_SUPER ||
+ udev->speed == USB_SPEED_HIGH);
+
+ if (is_hub)
+ temp |= XHCI_SCTX_0_HUB_SET(1);
+
+ slot->dwSctx0 = htole32(temp);
+
+ temp = XHCI_SCTX_1_RH_PORT_SET(rh_port);
+
+ if (is_hub) {
+ temp |= XHCI_SCTX_1_NUM_PORTS_SET(
+ sc->sc_hw.devs[index].nports);
+ }
+
+ slot->dwSctx1 = htole32(temp);
+
+ temp = XHCI_SCTX_2_IRQ_TARGET_SET(0);
+
+ if (is_hub) {
+ temp |= XHCI_SCTX_2_TT_THINK_TIME_SET(
+ sc->sc_hw.devs[index].tt);
+ }
+
+ hubdev = udev->parent_hs_hub;
+
+ /* check if we should activate the transaction translator */
+ switch (udev->speed) {
+ case USB_SPEED_FULL:
+ case USB_SPEED_LOW:
+ if (hubdev != NULL) {
+ temp |= XHCI_SCTX_2_TT_HUB_SID_SET(
+ hubdev->controller_slot_id);
+ temp |= XHCI_SCTX_2_TT_PORT_NUM_SET(
+ udev->hs_port_no);
+ }
+ break;
+ default:
+ break;
+ }
+
+ slot->dwSctx2 = htole32(temp);
+
+ /*
+ * These fields should be initialized to zero, according to
+ * XHCI section 6.2.2 - slot context:
+ */
+ temp = XHCI_SCTX_3_DEV_ADDR_SET(0) |
+ XHCI_SCTX_3_SLOT_STATE_SET(0);
+
+ slot->dwSctx3 = htole32(temp);
+
+#ifdef USB_DEBUG
+ xhci_dump_device(slot);
+#endif
+ usb_pc_cpu_flush(pcinp);
+
+ return (0); /* success */
+}
+
+static usb_error_t
+xhci_alloc_device_ext(struct usb_device *udev)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ struct usb_page_search buf_dev;
+ struct usb_page_search buf_ep;
+ struct xhci_trb *trb;
+ struct usb_page_cache *pc;
+ struct usb_page *pg;
+ uint64_t addr;
+ uint8_t index;
+ uint8_t i;
+
+ index = udev->controller_slot_id;
+
+ pc = &sc->sc_hw.devs[index].device_pc;
+ pg = &sc->sc_hw.devs[index].device_pg;
+
+ /* need to initialize the page cache */
+ pc->tag_parent = sc->sc_bus.dma_parent_tag;
+
+ if (usb_pc_alloc_mem(pc, pg, sc->sc_ctx_is_64_byte ?
+ sizeof(struct xhci_dev_ctx64) :
+ sizeof(struct xhci_dev_ctx), XHCI_PAGE_SIZE))
+ goto error;
+
+ usbd_get_page(pc, 0, &buf_dev);
+
+ pc = &sc->sc_hw.devs[index].input_pc;
+ pg = &sc->sc_hw.devs[index].input_pg;
+
+ /* need to initialize the page cache */
+ pc->tag_parent = sc->sc_bus.dma_parent_tag;
+
+ if (usb_pc_alloc_mem(pc, pg, sc->sc_ctx_is_64_byte ?
+ sizeof(struct xhci_input_dev_ctx64) :
+ sizeof(struct xhci_input_dev_ctx), XHCI_PAGE_SIZE)) {
+ goto error;
+ }
+
+ /* initialize all endpoint LINK TRBs */
+
+ for (i = 0; i != XHCI_MAX_ENDPOINTS; i++) {
+ pc = &sc->sc_hw.devs[index].endpoint_pc[i];
+ pg = &sc->sc_hw.devs[index].endpoint_pg[i];
+
+ /* need to initialize the page cache */
+ pc->tag_parent = sc->sc_bus.dma_parent_tag;
+
+ if (usb_pc_alloc_mem(pc, pg,
+ sizeof(struct xhci_dev_endpoint_trbs), XHCI_TRB_ALIGN)) {
+ goto error;
+ }
+
+ /* lookup endpoint TRB ring */
+ usbd_get_page(pc, 0, &buf_ep);
+
+ /* get TRB pointer */
+ trb = buf_ep.buffer;
+ trb += XHCI_MAX_TRANSFERS - 1;
+
+ /* get TRB start address */
+ addr = buf_ep.physaddr;
+
+ /* create LINK TRB */
+ trb->qwTrb0 = htole64(addr);
+ trb->dwTrb2 = htole32(XHCI_TRB_2_IRQ_SET(0));
+ trb->dwTrb3 = htole32(XHCI_TRB_3_CYCLE_BIT |
+ XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK));
+
+ usb_pc_cpu_flush(pc);
+ }
+
+ xhci_set_slot_pointer(sc, index, buf_dev.physaddr);
+
+ return (0);
+
+error:
+ xhci_free_device_ext(udev);
+
+ return (USB_ERR_NOMEM);
+}
+
+static void
+xhci_free_device_ext(struct usb_device *udev)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ uint8_t index;
+ uint8_t i;
+
+ index = udev->controller_slot_id;
+ xhci_set_slot_pointer(sc, index, 0);
+
+ usb_pc_free_mem(&sc->sc_hw.devs[index].device_pc);
+ usb_pc_free_mem(&sc->sc_hw.devs[index].input_pc);
+ for (i = 0; i != XHCI_MAX_ENDPOINTS; i++)
+ usb_pc_free_mem(&sc->sc_hw.devs[index].endpoint_pc[i]);
+}
+
+static struct xhci_endpoint_ext *
+xhci_get_endpoint_ext(struct usb_device *udev, struct usb_endpoint_descriptor *edesc)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ struct xhci_endpoint_ext *pepext;
+ struct usb_page_cache *pc;
+ struct usb_page_search buf_ep;
+ uint8_t epno;
+ uint8_t index;
+
+ epno = edesc->bEndpointAddress;
+ if ((edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL)
+ epno |= UE_DIR_IN;
+
+ epno = XHCI_EPNO2EPID(epno);
+
+ index = udev->controller_slot_id;
+
+ pc = &sc->sc_hw.devs[index].endpoint_pc[epno];
+
+ usbd_get_page(pc, 0, &buf_ep);
+
+ pepext = &sc->sc_hw.devs[index].endp[epno];
+ pepext->page_cache = pc;
+ pepext->trb = buf_ep.buffer;
+ pepext->physaddr = buf_ep.physaddr;
+
+ return (pepext);
+}
+
+static void
+xhci_endpoint_doorbell(struct usb_xfer *xfer)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus);
+ uint8_t epno;
+ uint8_t index;
+
+ epno = xfer->endpointno;
+ if (xfer->flags_int.control_xfr)
+ epno |= UE_DIR_IN;
+
+ epno = XHCI_EPNO2EPID(epno);
+ index = xfer->xroot->udev->controller_slot_id;
+
+ if (xfer->xroot->udev->flags.self_suspended == 0) {
+ XWRITE4(sc, door, XHCI_DOORBELL(index),
+ epno | XHCI_DB_SID_SET(xfer->stream_id));
+ }
+}
+
+static void
+xhci_transfer_remove(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct xhci_endpoint_ext *pepext;
+
+ if (xfer->flags_int.bandwidth_reclaimed) {
+ xfer->flags_int.bandwidth_reclaimed = 0;
+
+ pepext = xhci_get_endpoint_ext(xfer->xroot->udev,
+ xfer->endpoint->edesc);
+
+ pepext->trb_used[xfer->stream_id]--;
+
+ pepext->xfer[xfer->qh_pos] = NULL;
+
+ if (error && pepext->trb_running != 0) {
+ pepext->trb_halted = 1;
+ pepext->trb_running = 0;
+ }
+ }
+}
+
+static usb_error_t
+xhci_transfer_insert(struct usb_xfer *xfer)
+{
+ struct xhci_td *td_first;
+ struct xhci_td *td_last;
+ struct xhci_trb *trb_link;
+ struct xhci_endpoint_ext *pepext;
+ uint64_t addr;
+ usb_stream_t id;
+ uint8_t i;
+ uint8_t inext;
+ uint8_t trb_limit;
+
+ DPRINTFN(8, "\n");
+
+ id = xfer->stream_id;
+
+ /* check if already inserted */
+ if (xfer->flags_int.bandwidth_reclaimed) {
+ DPRINTFN(8, "Already in schedule\n");
+ return (0);
+ }
+
+ pepext = xhci_get_endpoint_ext(xfer->xroot->udev,
+ xfer->endpoint->edesc);
+
+ td_first = xfer->td_transfer_first;
+ td_last = xfer->td_transfer_last;
+ addr = pepext->physaddr;
+
+ switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_CONTROL:
+ case UE_INTERRUPT:
+ /* single buffered */
+ trb_limit = 1;
+ break;
+ default:
+ /* multi buffered */
+ trb_limit = (XHCI_MAX_TRANSFERS - 2);
+ break;
+ }
+
+ if (pepext->trb_used[id] >= trb_limit) {
+ DPRINTFN(8, "Too many TDs queued.\n");
+ return (USB_ERR_NOMEM);
+ }
+
+ /* check if bMaxPacketSize changed */
+ if (xfer->flags_int.control_xfr != 0 &&
+ pepext->trb_ep_maxp != xfer->endpoint->edesc->wMaxPacketSize[0]) {
+ DPRINTFN(8, "Reconfigure control endpoint\n");
+
+ /* force driver to reconfigure endpoint */
+ pepext->trb_halted = 1;
+ pepext->trb_running = 0;
+ }
+
+ /* check for stopped condition, after putting transfer on interrupt queue */
+ if (pepext->trb_running == 0) {
+ struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus);
+
+ DPRINTFN(8, "Not running\n");
+
+ /* start configuration */
+ (void)usb_proc_msignal(USB_BUS_CONTROL_XFER_PROC(&sc->sc_bus),
+ &sc->sc_config_msg[0], &sc->sc_config_msg[1]);
+ return (0);
+ }
+
+ pepext->trb_used[id]++;
+
+ /* get current TRB index */
+ i = pepext->trb_index[id];
+
+ /* get next TRB index */
+ inext = (i + 1);
+
+ /* the last entry of the ring is a hardcoded link TRB */
+ if (inext >= (XHCI_MAX_TRANSFERS - 1))
+ inext = 0;
+
+ /* store next TRB index, before stream ID offset is added */
+ pepext->trb_index[id] = inext;
+
+ /* offset for stream */
+ i += id * XHCI_MAX_TRANSFERS;
+ inext += id * XHCI_MAX_TRANSFERS;
+
+ /* compute terminating return address */
+ addr += (inext * sizeof(struct xhci_trb));
+
+ /* compute link TRB pointer */
+ trb_link = td_last->td_trb + td_last->ntrb;
+
+ /* update next pointer of last link TRB */
+ trb_link->qwTrb0 = htole64(addr);
+ trb_link->dwTrb2 = htole32(XHCI_TRB_2_IRQ_SET(0));
+ trb_link->dwTrb3 = htole32(XHCI_TRB_3_IOC_BIT |
+ XHCI_TRB_3_CYCLE_BIT |
+ XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK));
+
+#ifdef USB_DEBUG
+ xhci_dump_trb(&td_last->td_trb[td_last->ntrb]);
+#endif
+ usb_pc_cpu_flush(td_last->page_cache);
+
+ /* write ahead chain end marker */
+
+ pepext->trb[inext].qwTrb0 = 0;
+ pepext->trb[inext].dwTrb2 = 0;
+ pepext->trb[inext].dwTrb3 = 0;
+
+ /* update next pointer of link TRB */
+
+ pepext->trb[i].qwTrb0 = htole64((uint64_t)td_first->td_self);
+ pepext->trb[i].dwTrb2 = htole32(XHCI_TRB_2_IRQ_SET(0));
+
+#ifdef USB_DEBUG
+ xhci_dump_trb(&pepext->trb[i]);
+#endif
+ usb_pc_cpu_flush(pepext->page_cache);
+
+ /* toggle cycle bit which activates the transfer chain */
+
+ pepext->trb[i].dwTrb3 = htole32(XHCI_TRB_3_CYCLE_BIT |
+ XHCI_TRB_3_TYPE_SET(XHCI_TRB_TYPE_LINK));
+
+ usb_pc_cpu_flush(pepext->page_cache);
+
+ DPRINTF("qh_pos = %u\n", i);
+
+ pepext->xfer[i] = xfer;
+
+ xfer->qh_pos = i;
+
+ xfer->flags_int.bandwidth_reclaimed = 1;
+
+ xhci_endpoint_doorbell(xfer);
+
+ return (0);
+}
+
+static void
+xhci_root_intr(struct xhci_softc *sc)
+{
+ uint16_t i;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* clear any old interrupt data */
+ memset(sc->sc_hub_idata, 0, sizeof(sc->sc_hub_idata));
+
+ for (i = 1; i <= sc->sc_noport; i++) {
+ /* pick out CHANGE bits from the status register */
+ if (XREAD4(sc, oper, XHCI_PORTSC(i)) & (
+ XHCI_PS_CSC | XHCI_PS_PEC |
+ XHCI_PS_OCC | XHCI_PS_WRC |
+ XHCI_PS_PRC | XHCI_PS_PLC |
+ XHCI_PS_CEC)) {
+ sc->sc_hub_idata[i / 8] |= 1 << (i % 8);
+ DPRINTF("port %d changed\n", i);
+ }
+ }
+ uhub_root_intr(&sc->sc_bus, sc->sc_hub_idata,
+ sizeof(sc->sc_hub_idata));
+}
+
+/*------------------------------------------------------------------------*
+ * xhci_device_done - XHCI done handler
+ *
+ * NOTE: This function can be called two times in a row on
+ * the same USB transfer. From close and from interrupt.
+ *------------------------------------------------------------------------*/
+static void
+xhci_device_done(struct usb_xfer *xfer, usb_error_t error)
+{
+ DPRINTFN(2, "xfer=%p, endpoint=%p, error=%d\n",
+ xfer, xfer->endpoint, error);
+
+ /* remove transfer from HW queue */
+ xhci_transfer_remove(xfer, error);
+
+ /* dequeue transfer and start next transfer */
+ usbd_transfer_done(xfer, error);
+}
+
+/*------------------------------------------------------------------------*
+ * XHCI data transfer support (generic type)
+ *------------------------------------------------------------------------*/
+static void
+xhci_device_generic_open(struct usb_xfer *xfer)
+{
+ DPRINTF("\n");
+}
+
+static void
+xhci_device_generic_close(struct usb_xfer *xfer)
+{
+ DPRINTF("\n");
+
+ xhci_device_done(xfer, USB_ERR_CANCELLED);
+}
+
+static void
+xhci_device_generic_multi_enter(struct usb_endpoint *ep,
+ usb_stream_t stream_id, struct usb_xfer *enter_xfer)
+{
+ struct usb_xfer *xfer;
+
+ /* check if there is a current transfer */
+ xfer = ep->endpoint_q[stream_id].curr;
+ if (xfer == NULL)
+ return;
+
+ /*
+ * Check if the current transfer is started and then pickup
+ * the next one, if any. Else wait for next start event due to
+ * block on failure feature.
+ */
+ if (!xfer->flags_int.bandwidth_reclaimed)
+ return;
+
+ xfer = TAILQ_FIRST(&ep->endpoint_q[stream_id].head);
+ if (xfer == NULL) {
+ /*
+ * In case of enter we have to consider that the
+ * transfer is queued by the USB core after the enter
+ * method is called.
+ */
+ xfer = enter_xfer;
+
+ if (xfer == NULL)
+ return;
+ }
+
+ /* try to multi buffer */
+ xhci_transfer_insert(xfer);
+}
+
+static void
+xhci_device_generic_enter(struct usb_xfer *xfer)
+{
+ DPRINTF("\n");
+
+ /* set up TD's and QH */
+ xhci_setup_generic_chain(xfer);
+
+ xhci_device_generic_multi_enter(xfer->endpoint,
+ xfer->stream_id, xfer);
+}
+
+static void
+xhci_device_generic_start(struct usb_xfer *xfer)
+{
+ DPRINTF("\n");
+
+ /* try to insert xfer on HW queue */
+ xhci_transfer_insert(xfer);
+
+ /* try to multi buffer */
+ xhci_device_generic_multi_enter(xfer->endpoint,
+ xfer->stream_id, NULL);
+
+ /* add transfer last on interrupt queue */
+ usbd_transfer_enqueue(&xfer->xroot->bus->intr_q, xfer);
+
+ /* start timeout, if any */
+ if (xfer->timeout != 0)
+ usbd_transfer_timeout_ms(xfer, &xhci_timeout, xfer->timeout);
+}
+
+static const struct usb_pipe_methods xhci_device_generic_methods =
+{
+ .open = xhci_device_generic_open,
+ .close = xhci_device_generic_close,
+ .enter = xhci_device_generic_enter,
+ .start = xhci_device_generic_start,
+};
+
+/*------------------------------------------------------------------------*
+ * xhci root HUB support
+ *------------------------------------------------------------------------*
+ * Simulate a hardware HUB by handling all the necessary requests.
+ *------------------------------------------------------------------------*/
+#define HSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) }
+
+static const
+struct usb_device_descriptor xhci_devd =
+{
+ .bLength = sizeof(xhci_devd),
+ .bDescriptorType = UDESC_DEVICE, /* type */
+ HSETW(.bcdUSB, 0x0300), /* USB version */
+ .bDeviceClass = UDCLASS_HUB, /* class */
+ .bDeviceSubClass = UDSUBCLASS_HUB, /* subclass */
+ .bDeviceProtocol = UDPROTO_SSHUB, /* protocol */
+ .bMaxPacketSize = 9, /* max packet size */
+ HSETW(.idVendor, 0x0000), /* vendor */
+ HSETW(.idProduct, 0x0000), /* product */
+ HSETW(.bcdDevice, 0x0100), /* device version */
+ .iManufacturer = 1,
+ .iProduct = 2,
+ .iSerialNumber = 0,
+ .bNumConfigurations = 1, /* # of configurations */
+};
+
+static const
+struct xhci_bos_desc xhci_bosd = {
+ .bosd = {
+ .bLength = sizeof(xhci_bosd.bosd),
+ .bDescriptorType = UDESC_BOS,
+ HSETW(.wTotalLength, sizeof(xhci_bosd)),
+ .bNumDeviceCaps = 3,
+ },
+ .usb2extd = {
+ .bLength = sizeof(xhci_bosd.usb2extd),
+ .bDescriptorType = 1,
+ .bDevCapabilityType = 2,
+ .bmAttributes[0] = 2,
+ },
+ .usbdcd = {
+ .bLength = sizeof(xhci_bosd.usbdcd),
+ .bDescriptorType = UDESC_DEVICE_CAPABILITY,
+ .bDevCapabilityType = 3,
+ .bmAttributes = 0, /* XXX */
+ HSETW(.wSpeedsSupported, 0x000C),
+ .bFunctionalitySupport = 8,
+ .bU1DevExitLat = 255, /* dummy - not used */
+ .wU2DevExitLat = { 0x00, 0x08 },
+ },
+ .cidd = {
+ .bLength = sizeof(xhci_bosd.cidd),
+ .bDescriptorType = 1,
+ .bDevCapabilityType = 4,
+ .bReserved = 0,
+ .bContainerID = 0, /* XXX */
+ },
+};
+
+static const
+struct xhci_config_desc xhci_confd = {
+ .confd = {
+ .bLength = sizeof(xhci_confd.confd),
+ .bDescriptorType = UDESC_CONFIG,
+ .wTotalLength[0] = sizeof(xhci_confd),
+ .bNumInterface = 1,
+ .bConfigurationValue = 1,
+ .iConfiguration = 0,
+ .bmAttributes = UC_SELF_POWERED,
+ .bMaxPower = 0 /* max power */
+ },
+ .ifcd = {
+ .bLength = sizeof(xhci_confd.ifcd),
+ .bDescriptorType = UDESC_INTERFACE,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = UICLASS_HUB,
+ .bInterfaceSubClass = UISUBCLASS_HUB,
+ .bInterfaceProtocol = 0,
+ },
+ .endpd = {
+ .bLength = sizeof(xhci_confd.endpd),
+ .bDescriptorType = UDESC_ENDPOINT,
+ .bEndpointAddress = UE_DIR_IN | XHCI_INTR_ENDPT,
+ .bmAttributes = UE_INTERRUPT,
+ .wMaxPacketSize[0] = 2, /* max 15 ports */
+ .bInterval = 255,
+ },
+ .endpcd = {
+ .bLength = sizeof(xhci_confd.endpcd),
+ .bDescriptorType = UDESC_ENDPOINT_SS_COMP,
+ .bMaxBurst = 0,
+ .bmAttributes = 0,
+ },
+};
+
+static const
+struct usb_hub_ss_descriptor xhci_hubd = {
+ .bLength = sizeof(xhci_hubd),
+ .bDescriptorType = UDESC_SS_HUB,
+};
+
+static usb_error_t
+xhci_roothub_exec(struct usb_device *udev,
+ struct usb_device_request *req, const void **pptr, uint16_t *plength)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ const char *str_ptr;
+ const void *ptr;
+ uint32_t port;
+ uint32_t v;
+ uint16_t len;
+ uint16_t i;
+ uint16_t value;
+ uint16_t index;
+ uint8_t j;
+ usb_error_t err;
+
+ USB_BUS_LOCK_ASSERT(&sc->sc_bus, MA_OWNED);
+
+ /* buffer reset */
+ ptr = (const void *)&sc->sc_hub_desc;
+ len = 0;
+ err = 0;
+
+ value = UGETW(req->wValue);
+ index = UGETW(req->wIndex);
+
+ DPRINTFN(3, "type=0x%02x request=0x%02x wLen=0x%04x "
+ "wValue=0x%04x wIndex=0x%04x\n",
+ req->bmRequestType, req->bRequest,
+ UGETW(req->wLength), value, index);
+
+#define C(x,y) ((x) | ((y) << 8))
+ switch (C(req->bRequest, req->bmRequestType)) {
+ case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
+ /*
+ * DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops
+ * for the integrated root hub.
+ */
+ break;
+ case C(UR_GET_CONFIG, UT_READ_DEVICE):
+ len = 1;
+ sc->sc_hub_desc.temp[0] = sc->sc_conf;
+ break;
+ case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE):
+ switch (value >> 8) {
+ case UDESC_DEVICE:
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ len = sizeof(xhci_devd);
+ ptr = (const void *)&xhci_devd;
+ break;
+
+ case UDESC_BOS:
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ len = sizeof(xhci_bosd);
+ ptr = (const void *)&xhci_bosd;
+ break;
+
+ case UDESC_CONFIG:
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ len = sizeof(xhci_confd);
+ ptr = (const void *)&xhci_confd;
+ break;
+
+ case UDESC_STRING:
+ switch (value & 0xff) {
+ case 0: /* Language table */
+ str_ptr = "\001";
+ break;
+
+ case 1: /* Vendor */
+ str_ptr = sc->sc_vendor;
+ break;
+
+ case 2: /* Product */
+ str_ptr = "XHCI root HUB";
+ break;
+
+ default:
+ str_ptr = "";
+ break;
+ }
+
+ len = usb_make_str_desc(
+ sc->sc_hub_desc.temp,
+ sizeof(sc->sc_hub_desc.temp),
+ str_ptr);
+ break;
+
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_GET_INTERFACE, UT_READ_INTERFACE):
+ len = 1;
+ sc->sc_hub_desc.temp[0] = 0;
+ break;
+ case C(UR_GET_STATUS, UT_READ_DEVICE):
+ len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, UDS_SELF_POWERED);
+ break;
+ case C(UR_GET_STATUS, UT_READ_INTERFACE):
+ case C(UR_GET_STATUS, UT_READ_ENDPOINT):
+ len = 2;
+ USETW(sc->sc_hub_desc.stat.wStatus, 0);
+ break;
+ case C(UR_SET_ADDRESS, UT_WRITE_DEVICE):
+ if (value >= XHCI_MAX_DEVICES) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+ case C(UR_SET_CONFIG, UT_WRITE_DEVICE):
+ if (value != 0 && value != 1) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ sc->sc_conf = value;
+ break;
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE):
+ break;
+ case C(UR_SET_FEATURE, UT_WRITE_DEVICE):
+ case C(UR_SET_FEATURE, UT_WRITE_INTERFACE):
+ case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT):
+ err = USB_ERR_IOERROR;
+ goto done;
+ case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
+ break;
+ case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT):
+ break;
+ /* Hub requests */
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+ case C(UR_CLEAR_FEATURE, UT_WRITE_CLASS_OTHER):
+ DPRINTFN(9, "UR_CLEAR_PORT_FEATURE\n");
+
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ port = XHCI_PORTSC(index);
+
+ v = XREAD4(sc, oper, port);
+ i = XHCI_PS_PLS_GET(v);
+ v &= ~XHCI_PS_CLEAR;
+
+ switch (value) {
+ case UHF_C_BH_PORT_RESET:
+ XWRITE4(sc, oper, port, v | XHCI_PS_WRC);
+ break;
+ case UHF_C_PORT_CONFIG_ERROR:
+ XWRITE4(sc, oper, port, v | XHCI_PS_CEC);
+ break;
+ case UHF_C_PORT_SUSPEND:
+ case UHF_C_PORT_LINK_STATE:
+ XWRITE4(sc, oper, port, v | XHCI_PS_PLC);
+ break;
+ case UHF_C_PORT_CONNECTION:
+ XWRITE4(sc, oper, port, v | XHCI_PS_CSC);
+ break;
+ case UHF_C_PORT_ENABLE:
+ XWRITE4(sc, oper, port, v | XHCI_PS_PEC);
+ break;
+ case UHF_C_PORT_OVER_CURRENT:
+ XWRITE4(sc, oper, port, v | XHCI_PS_OCC);
+ break;
+ case UHF_C_PORT_RESET:
+ XWRITE4(sc, oper, port, v | XHCI_PS_PRC);
+ break;
+ case UHF_PORT_ENABLE:
+ if ((sc->sc_quirks & XHCI_QUIRK_DISABLE_PORT_PED) == 0)
+ XWRITE4(sc, oper, port, v | XHCI_PS_PED);
+ break;
+ case UHF_PORT_POWER:
+ XWRITE4(sc, oper, port, v & ~XHCI_PS_PP);
+ break;
+ case UHF_PORT_INDICATOR:
+ XWRITE4(sc, oper, port, v & ~XHCI_PS_PIC_SET(3));
+ break;
+ case UHF_PORT_SUSPEND:
+
+ /* U3 -> U15 */
+ if (i == 3) {
+ XWRITE4(sc, oper, port, v |
+ XHCI_PS_PLS_SET(0xF) | XHCI_PS_LWS);
+ }
+
+ /* wait 20ms for resume sequence to complete */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 50);
+
+ /* U0 */
+ XWRITE4(sc, oper, port, v |
+ XHCI_PS_PLS_SET(0) | XHCI_PS_LWS);
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+
+ case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE):
+ if ((value & 0xff) != 0) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+
+ v = XREAD4(sc, capa, XHCI_HCSPARAMS0);
+
+ sc->sc_hub_desc.hubd = xhci_hubd;
+
+ sc->sc_hub_desc.hubd.bNbrPorts = sc->sc_noport;
+
+ if (XHCI_HCS0_PPC(v))
+ i = UHD_PWR_INDIVIDUAL;
+ else
+ i = UHD_PWR_GANGED;
+
+ if (XHCI_HCS0_PIND(v))
+ i |= UHD_PORT_IND;
+
+ i |= UHD_OC_INDIVIDUAL;
+
+ USETW(sc->sc_hub_desc.hubd.wHubCharacteristics, i);
+
+ /* see XHCI section 5.4.9: */
+ sc->sc_hub_desc.hubd.bPwrOn2PwrGood = 10;
+
+ for (j = 1; j <= sc->sc_noport; j++) {
+ v = XREAD4(sc, oper, XHCI_PORTSC(j));
+ if (v & XHCI_PS_DR) {
+ sc->sc_hub_desc.hubd.
+ DeviceRemovable[j / 8] |= 1U << (j % 8);
+ }
+ }
+ len = sc->sc_hub_desc.hubd.bLength;
+ break;
+
+ case C(UR_GET_STATUS, UT_READ_CLASS_DEVICE):
+ len = 16;
+ memset(sc->sc_hub_desc.temp, 0, 16);
+ break;
+
+ case C(UR_GET_STATUS, UT_READ_CLASS_OTHER):
+ DPRINTFN(9, "UR_GET_STATUS i=%d\n", index);
+
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+
+ v = XREAD4(sc, oper, XHCI_PORTSC(index));
+
+ DPRINTFN(9, "port status=0x%08x\n", v);
+
+ i = UPS_PORT_LINK_STATE_SET(XHCI_PS_PLS_GET(v));
+
+ switch (XHCI_PS_SPEED_GET(v)) {
+ case XHCI_PS_SPEED_HIGH:
+ i |= UPS_HIGH_SPEED;
+ break;
+ case XHCI_PS_SPEED_LOW:
+ i |= UPS_LOW_SPEED;
+ break;
+ case XHCI_PS_SPEED_FULL:
+ /* FULL speed */
+ break;
+ default:
+ i |= UPS_OTHER_SPEED;
+ break;
+ }
+
+ if (v & XHCI_PS_CCS)
+ i |= UPS_CURRENT_CONNECT_STATUS;
+ if (v & XHCI_PS_PED)
+ i |= UPS_PORT_ENABLED;
+ if (v & XHCI_PS_OCA)
+ i |= UPS_OVERCURRENT_INDICATOR;
+ if (v & XHCI_PS_PR)
+ i |= UPS_RESET;
+#if 0
+ if (v & XHCI_PS_PP)
+ /* XXX undefined */
+#endif
+ USETW(sc->sc_hub_desc.ps.wPortStatus, i);
+
+ i = 0;
+ if (v & XHCI_PS_CSC)
+ i |= UPS_C_CONNECT_STATUS;
+ if (v & XHCI_PS_PEC)
+ i |= UPS_C_PORT_ENABLED;
+ if (v & XHCI_PS_OCC)
+ i |= UPS_C_OVERCURRENT_INDICATOR;
+ if (v & XHCI_PS_WRC)
+ i |= UPS_C_BH_PORT_RESET;
+ if (v & XHCI_PS_PRC)
+ i |= UPS_C_PORT_RESET;
+ if (v & XHCI_PS_PLC)
+ i |= UPS_C_PORT_LINK_STATE;
+ if (v & XHCI_PS_CEC)
+ i |= UPS_C_PORT_CONFIG_ERROR;
+
+ USETW(sc->sc_hub_desc.ps.wPortChange, i);
+ len = sizeof(sc->sc_hub_desc.ps);
+ break;
+
+ case C(UR_SET_DESCRIPTOR, UT_WRITE_CLASS_DEVICE):
+ err = USB_ERR_IOERROR;
+ goto done;
+
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_DEVICE):
+ break;
+
+ case C(UR_SET_FEATURE, UT_WRITE_CLASS_OTHER):
+
+ i = index >> 8;
+ index &= 0x00FF;
+
+ if ((index < 1) ||
+ (index > sc->sc_noport)) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+
+ port = XHCI_PORTSC(index);
+ v = XREAD4(sc, oper, port) & ~XHCI_PS_CLEAR;
+
+ switch (value) {
+ case UHF_PORT_U1_TIMEOUT:
+ if (XHCI_PS_SPEED_GET(v) < XHCI_PS_SPEED_SS) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ port = XHCI_PORTPMSC(index);
+ v = XREAD4(sc, oper, port);
+ v &= ~XHCI_PM3_U1TO_SET(0xFF);
+ v |= XHCI_PM3_U1TO_SET(i);
+ XWRITE4(sc, oper, port, v);
+ break;
+ case UHF_PORT_U2_TIMEOUT:
+ if (XHCI_PS_SPEED_GET(v) < XHCI_PS_SPEED_SS) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ port = XHCI_PORTPMSC(index);
+ v = XREAD4(sc, oper, port);
+ v &= ~XHCI_PM3_U2TO_SET(0xFF);
+ v |= XHCI_PM3_U2TO_SET(i);
+ XWRITE4(sc, oper, port, v);
+ break;
+ case UHF_BH_PORT_RESET:
+ XWRITE4(sc, oper, port, v | XHCI_PS_WPR);
+ break;
+ case UHF_PORT_LINK_STATE:
+ XWRITE4(sc, oper, port, v |
+ XHCI_PS_PLS_SET(i) | XHCI_PS_LWS);
+ /* 4ms settle time */
+ usb_pause_mtx(&sc->sc_bus.bus_mtx, hz / 250);
+ break;
+ case UHF_PORT_ENABLE:
+ DPRINTFN(3, "set port enable %d\n", index);
+ break;
+ case UHF_PORT_SUSPEND:
+ DPRINTFN(6, "suspend port %u (LPM=%u)\n", index, i);
+ j = XHCI_PS_SPEED_GET(v);
+ if (j == 0 || j >= XHCI_PS_SPEED_SS) {
+ /* non-supported speed */
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ XWRITE4(sc, oper, port, v |
+ XHCI_PS_PLS_SET(i ? 2 /* LPM */ : 3) | XHCI_PS_LWS);
+ break;
+ case UHF_PORT_RESET:
+ DPRINTFN(6, "reset port %d\n", index);
+ XWRITE4(sc, oper, port, v | XHCI_PS_PR);
+ break;
+ case UHF_PORT_POWER:
+ DPRINTFN(3, "set port power %d\n", index);
+ XWRITE4(sc, oper, port, v | XHCI_PS_PP);
+ break;
+ case UHF_PORT_TEST:
+ DPRINTFN(3, "set port test %d\n", index);
+ break;
+ case UHF_PORT_INDICATOR:
+ DPRINTFN(3, "set port indicator %d\n", index);
+
+ v &= ~XHCI_PS_PIC_SET(3);
+ v |= XHCI_PS_PIC_SET(1);
+
+ XWRITE4(sc, oper, port, v);
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+ break;
+
+ case C(UR_CLEAR_TT_BUFFER, UT_WRITE_CLASS_OTHER):
+ case C(UR_RESET_TT, UT_WRITE_CLASS_OTHER):
+ case C(UR_GET_TT_STATE, UT_READ_CLASS_OTHER):
+ case C(UR_STOP_TT, UT_WRITE_CLASS_OTHER):
+ break;
+ default:
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+done:
+ *plength = len;
+ *pptr = ptr;
+ return (err);
+}
+
+static void
+xhci_xfer_setup(struct usb_setup_params *parm)
+{
+ struct usb_page_search page_info;
+ struct usb_page_cache *pc;
+ struct usb_xfer *xfer;
+ void *last_obj;
+ uint32_t ntd;
+ uint32_t n;
+
+ xfer = parm->curr_xfer;
+
+ /*
+ * The proof for the "ntd" formula is illustrated like this:
+ *
+ * +------------------------------------+
+ * | |
+ * | |remainder -> |
+ * | +-----+---+ |
+ * | | xxx | x | frm 0 |
+ * | +-----+---++ |
+ * | | xxx | xx | frm 1 |
+ * | +-----+----+ |
+ * | ... |
+ * +------------------------------------+
+ *
+ * "xxx" means a completely full USB transfer descriptor
+ *
+ * "x" and "xx" means a short USB packet
+ *
+ * For the remainder of an USB transfer modulo
+ * "max_data_length" we need two USB transfer descriptors.
+ * One to transfer the remaining data and one to finalise with
+ * a zero length packet in case the "force_short_xfer" flag is
+ * set. We only need two USB transfer descriptors in the case
+ * where the transfer length of the first one is a factor of
+ * "max_frame_size". The rest of the needed USB transfer
+ * descriptors is given by the buffer size divided by the
+ * maximum data payload.
+ */
+ parm->hc_max_packet_size = 0x400;
+ parm->hc_max_packet_count = 16 * 3;
+ parm->hc_max_frame_size = XHCI_TD_PAYLOAD_MAX;
+
+ xfer->flags_int.bdma_enable = 1;
+
+ usbd_transfer_setup_sub(parm);
+
+ if (xfer->flags_int.isochronous_xfr) {
+ ntd = ((1 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_hc_frame_size));
+ } else if (xfer->flags_int.control_xfr) {
+ ntd = ((2 * xfer->nframes) + 1 /* STATUS */
+ + (xfer->max_data_length / xfer->max_hc_frame_size));
+ } else {
+ ntd = ((2 * xfer->nframes)
+ + (xfer->max_data_length / xfer->max_hc_frame_size));
+ }
+
+alloc_dma_set:
+
+ if (parm->err)
+ return;
+
+ /*
+ * Allocate queue heads and transfer descriptors
+ */
+ last_obj = NULL;
+
+ if (usbd_transfer_setup_sub_malloc(
+ parm, &pc, sizeof(struct xhci_td),
+ XHCI_TD_ALIGN, ntd)) {
+ parm->err = USB_ERR_NOMEM;
+ return;
+ }
+ if (parm->buf) {
+ for (n = 0; n != ntd; n++) {
+ struct xhci_td *td;
+
+ usbd_get_page(pc + n, 0, &page_info);
+
+ td = page_info.buffer;
+
+ /* init TD */
+ td->td_self = page_info.physaddr;
+ td->obj_next = last_obj;
+ td->page_cache = pc + n;
+
+ last_obj = td;
+
+ usb_pc_cpu_flush(pc + n);
+ }
+ }
+ xfer->td_start[xfer->flags_int.curr_dma_set] = last_obj;
+
+ if (!xfer->flags_int.curr_dma_set) {
+ xfer->flags_int.curr_dma_set = 1;
+ goto alloc_dma_set;
+ }
+}
+
+static uint8_t
+xhci_get_endpoint_state(struct usb_device *udev, uint8_t epno)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ struct usb_page_search buf_dev;
+ struct xhci_hw_dev *hdev;
+ struct xhci_endp_ctx *endp;
+ uint32_t temp;
+
+ MPASS(epno != 0);
+
+ hdev = &sc->sc_hw.devs[udev->controller_slot_id];
+
+ usbd_get_page(&hdev->device_pc, 0, &buf_dev);
+ endp = XHCI_GET_CTX(sc, xhci_dev_ctx, ctx_ep[epno - 1],
+ buf_dev.buffer);
+ usb_pc_cpu_invalidate(&hdev->device_pc);
+
+ temp = le32toh(endp->dwEpCtx0);
+
+ return (XHCI_EPCTX_0_EPSTATE_GET(temp));
+}
+
+static usb_error_t
+xhci_configure_reset_endpoint(struct usb_xfer *xfer)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus);
+ struct usb_page_search buf_inp;
+ struct usb_device *udev;
+ struct xhci_endpoint_ext *pepext;
+ struct usb_endpoint_descriptor *edesc;
+ struct usb_page_cache *pcinp;
+ usb_error_t err;
+ usb_stream_t stream_id;
+ uint32_t mask;
+ uint8_t index;
+ uint8_t epno;
+ uint8_t drop;
+
+ pepext = xhci_get_endpoint_ext(xfer->xroot->udev,
+ xfer->endpoint->edesc);
+
+ udev = xfer->xroot->udev;
+ index = udev->controller_slot_id;
+
+ pcinp = &sc->sc_hw.devs[index].input_pc;
+
+ usbd_get_page(pcinp, 0, &buf_inp);
+
+ edesc = xfer->endpoint->edesc;
+
+ epno = edesc->bEndpointAddress;
+ stream_id = xfer->stream_id;
+
+ if ((edesc->bmAttributes & UE_XFERTYPE) == UE_CONTROL)
+ epno |= UE_DIR_IN;
+
+ epno = XHCI_EPNO2EPID(epno);
+
+ if (epno == 0)
+ return (USB_ERR_NO_PIPE); /* invalid */
+
+ XHCI_CMD_LOCK(sc);
+
+ /* configure endpoint */
+
+ err = xhci_configure_endpoint_by_xfer(xfer);
+
+ if (err != 0) {
+ XHCI_CMD_UNLOCK(sc);
+ return (err);
+ }
+
+ /*
+ * Get the endpoint into the stopped state according to the
+ * endpoint context state diagram in the XHCI specification:
+ */
+ switch (xhci_get_endpoint_state(udev, epno)) {
+ case XHCI_EPCTX_0_EPSTATE_DISABLED:
+ drop = 0;
+ break;
+ case XHCI_EPCTX_0_EPSTATE_STOPPED:
+ drop = 1;
+ break;
+ case XHCI_EPCTX_0_EPSTATE_HALTED:
+ err = xhci_cmd_reset_ep(sc, 0, epno, index);
+ drop = (err != 0);
+ if (drop)
+ DPRINTF("Could not reset endpoint %u\n", epno);
+ break;
+ default:
+ drop = 1;
+ err = xhci_cmd_stop_ep(sc, 0, epno, index);
+ if (err != 0)
+ DPRINTF("Could not stop endpoint %u\n", epno);
+ break;
+ }
+
+ err = xhci_cmd_set_tr_dequeue_ptr(sc,
+ (pepext->physaddr + (stream_id * sizeof(struct xhci_trb) *
+ XHCI_MAX_TRANSFERS)) | XHCI_EPCTX_2_DCS_SET(1),
+ stream_id, epno, index);
+
+ if (err != 0)
+ DPRINTF("Could not set dequeue ptr for endpoint %u\n", epno);
+
+ /*
+ * Get the endpoint into the running state according to the
+ * endpoint context state diagram in the XHCI specification:
+ */
+
+ mask = (1U << epno);
+
+ /*
+ * So-called control and isochronous transfer types have
+ * predefined data toggles (USB 2.0) or sequence numbers (USB
+ * 3.0) and does not need to be dropped.
+ */
+ if (drop != 0 &&
+ (edesc->bmAttributes & UE_XFERTYPE) != UE_CONTROL &&
+ (edesc->bmAttributes & UE_XFERTYPE) != UE_ISOCHRONOUS) {
+ /* drop endpoint context to reset data toggle value, if any. */
+ xhci_configure_mask(udev, mask, 1);
+ err = xhci_cmd_configure_ep(sc, buf_inp.physaddr, 0, index);
+ if (err != 0) {
+ DPRINTF("Could not drop "
+ "endpoint %u at slot %u.\n", epno, index);
+ } else {
+ sc->sc_hw.devs[index].ep_configured &= ~mask;
+ }
+ }
+
+ /*
+ * Always need to evaluate the slot context, because the maximum
+ * number of endpoint contexts is stored there.
+ */
+ xhci_configure_mask(udev, mask | 1U, 0);
+
+ if (!(sc->sc_hw.devs[index].ep_configured & mask)) {
+ err = xhci_cmd_configure_ep(sc, buf_inp.physaddr, 0, index);
+ if (err == 0)
+ sc->sc_hw.devs[index].ep_configured |= mask;
+ } else {
+ err = xhci_cmd_evaluate_ctx(sc, buf_inp.physaddr, index);
+ }
+
+ if (err != 0) {
+ DPRINTF("Could not configure "
+ "endpoint %u at slot %u.\n", epno, index);
+ }
+ XHCI_CMD_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+xhci_xfer_unsetup(struct usb_xfer *xfer)
+{
+ return;
+}
+
+static void
+xhci_start_dma_delay(struct usb_xfer *xfer)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(xfer->xroot->bus);
+
+ /* put transfer on interrupt queue (again) */
+ usbd_transfer_enqueue(&sc->sc_bus.intr_q, xfer);
+
+ (void)usb_proc_msignal(USB_BUS_CONTROL_XFER_PROC(&sc->sc_bus),
+ &sc->sc_config_msg[0], &sc->sc_config_msg[1]);
+}
+
+static void
+xhci_configure_msg(struct usb_proc_msg *pm)
+{
+ struct xhci_softc *sc;
+ struct xhci_endpoint_ext *pepext;
+ struct usb_xfer *xfer;
+
+ sc = XHCI_BUS2SC(((struct usb_bus_msg *)pm)->bus);
+
+restart:
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ pepext = xhci_get_endpoint_ext(xfer->xroot->udev,
+ xfer->endpoint->edesc);
+
+ if ((pepext->trb_halted != 0) ||
+ (pepext->trb_running == 0)) {
+ uint16_t i;
+
+ /* clear halted and running */
+ pepext->trb_halted = 0;
+ pepext->trb_running = 0;
+
+ /* nuke remaining buffered transfers */
+
+ for (i = 0; i != (XHCI_MAX_TRANSFERS *
+ XHCI_MAX_STREAMS); i++) {
+ /*
+ * NOTE: We need to use the timeout
+ * error code here else existing
+ * isochronous clients can get
+ * confused:
+ */
+ if (pepext->xfer[i] != NULL) {
+ xhci_device_done(pepext->xfer[i],
+ USB_ERR_TIMEOUT);
+ }
+ }
+
+ /*
+ * NOTE: The USB transfer cannot vanish in
+ * this state!
+ */
+
+ USB_BUS_UNLOCK(&sc->sc_bus);
+
+ xhci_configure_reset_endpoint(xfer);
+
+ USB_BUS_LOCK(&sc->sc_bus);
+
+ /* check if halted is still cleared */
+ if (pepext->trb_halted == 0) {
+ pepext->trb_running = 1;
+ memset(pepext->trb_index, 0,
+ sizeof(pepext->trb_index));
+ }
+ goto restart;
+ }
+
+ if (xfer->flags_int.did_dma_delay) {
+ /* remove transfer from interrupt queue (again) */
+ usbd_transfer_dequeue(xfer);
+
+ /* we are finally done */
+ usb_dma_delay_done_cb(xfer);
+
+ /* queue changed - restart */
+ goto restart;
+ }
+ }
+
+ TAILQ_FOREACH(xfer, &sc->sc_bus.intr_q.head, wait_entry) {
+ /* try to insert xfer on HW queue */
+ xhci_transfer_insert(xfer);
+
+ /* try to multi buffer */
+ xhci_device_generic_multi_enter(xfer->endpoint,
+ xfer->stream_id, NULL);
+ }
+}
+
+static void
+xhci_ep_init(struct usb_device *udev, struct usb_endpoint_descriptor *edesc,
+ struct usb_endpoint *ep)
+{
+ struct xhci_endpoint_ext *pepext;
+ struct xhci_softc *sc;
+ uint8_t index;
+ uint8_t epno;
+
+ DPRINTFN(2, "endpoint=%p, addr=%d, endpt=%d, mode=%d\n",
+ ep, udev->address, edesc->bEndpointAddress, udev->flags.usb_mode);
+
+ if (udev->parent_hub == NULL) {
+ /* root HUB has special endpoint handling */
+ return;
+ }
+
+ ep->methods = &xhci_device_generic_methods;
+
+ pepext = xhci_get_endpoint_ext(udev, edesc);
+
+ USB_BUS_LOCK(udev->bus);
+ pepext->trb_halted = 1;
+ pepext->trb_running = 0;
+
+ /*
+ * When doing an alternate setting, except for control
+ * endpoints, we need to re-configure the XHCI endpoint
+ * context:
+ */
+ if ((edesc->bEndpointAddress & UE_ADDR) != 0) {
+ sc = XHCI_BUS2SC(udev->bus);
+ index = udev->controller_slot_id;
+ epno = XHCI_EPNO2EPID(edesc->bEndpointAddress);
+ sc->sc_hw.devs[index].ep_configured &= ~(1U << epno);
+ }
+ USB_BUS_UNLOCK(udev->bus);
+}
+
+static void
+xhci_ep_uninit(struct usb_device *udev, struct usb_endpoint *ep)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ const struct usb_endpoint_descriptor *edesc = ep->edesc;
+ struct usb_page_search buf_inp;
+ struct usb_page_cache *pcinp;
+ uint32_t mask;
+ uint8_t index;
+ uint8_t epno;
+ usb_error_t err;
+
+ if (udev->parent_hub == NULL) {
+ /* root HUB has special endpoint handling */
+ return;
+ }
+
+ if ((edesc->bEndpointAddress & UE_ADDR) == 0) {
+ /* control endpoint is never unconfigured */
+ return;
+ }
+
+ XHCI_CMD_LOCK(sc);
+ index = udev->controller_slot_id;
+ epno = XHCI_EPNO2EPID(edesc->bEndpointAddress);
+ mask = 1U << epno;
+
+ if (sc->sc_hw.devs[index].ep_configured & mask) {
+ USB_BUS_LOCK(udev->bus);
+ xhci_configure_mask(udev, mask, 1);
+ USB_BUS_UNLOCK(udev->bus);
+
+ pcinp = &sc->sc_hw.devs[index].input_pc;
+ usbd_get_page(pcinp, 0, &buf_inp);
+ err = xhci_cmd_configure_ep(sc, buf_inp.physaddr, 0, index);
+ if (err) {
+ DPRINTF("Unconfiguring endpoint failed: %d\n", err);
+ } else {
+ USB_BUS_LOCK(udev->bus);
+ sc->sc_hw.devs[index].ep_configured &= ~mask;
+ USB_BUS_UNLOCK(udev->bus);
+ }
+ }
+ XHCI_CMD_UNLOCK(sc);
+}
+
+static void
+xhci_ep_clear_stall(struct usb_device *udev, struct usb_endpoint *ep)
+{
+ struct xhci_endpoint_ext *pepext;
+
+ DPRINTF("\n");
+
+ if (udev->flags.usb_mode != USB_MODE_HOST) {
+ /* not supported */
+ return;
+ }
+ if (udev->parent_hub == NULL) {
+ /* root HUB has special endpoint handling */
+ return;
+ }
+
+ pepext = xhci_get_endpoint_ext(udev, ep->edesc);
+
+ USB_BUS_LOCK(udev->bus);
+ pepext->trb_halted = 1;
+ pepext->trb_running = 0;
+ USB_BUS_UNLOCK(udev->bus);
+}
+
+static usb_error_t
+xhci_device_init(struct usb_device *udev)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ usb_error_t err;
+ uint8_t temp;
+
+ /* no init for root HUB */
+ if (udev->parent_hub == NULL)
+ return (0);
+
+ XHCI_CMD_LOCK(sc);
+
+ /* set invalid default */
+
+ udev->controller_slot_id = sc->sc_noslot + 1;
+
+ /* try to get a new slot ID from the XHCI */
+
+ err = xhci_cmd_enable_slot(sc, &temp);
+
+ if (err) {
+ XHCI_CMD_UNLOCK(sc);
+ return (err);
+ }
+
+ if (temp > sc->sc_noslot) {
+ XHCI_CMD_UNLOCK(sc);
+ return (USB_ERR_BAD_ADDRESS);
+ }
+
+ if (sc->sc_hw.devs[temp].state != XHCI_ST_DISABLED) {
+ DPRINTF("slot %u already allocated.\n", temp);
+ XHCI_CMD_UNLOCK(sc);
+ return (USB_ERR_BAD_ADDRESS);
+ }
+
+ /* store slot ID for later reference */
+
+ udev->controller_slot_id = temp;
+
+ /* reset data structure */
+
+ memset(&sc->sc_hw.devs[temp], 0, sizeof(sc->sc_hw.devs[0]));
+
+ /* set mark slot allocated */
+
+ sc->sc_hw.devs[temp].state = XHCI_ST_ENABLED;
+
+ err = xhci_alloc_device_ext(udev);
+
+ XHCI_CMD_UNLOCK(sc);
+
+ /* get device into default state */
+
+ if (err == 0)
+ err = xhci_set_address(udev, NULL, 0);
+
+ return (err);
+}
+
+static void
+xhci_device_uninit(struct usb_device *udev)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ uint8_t index;
+
+ /* no init for root HUB */
+ if (udev->parent_hub == NULL)
+ return;
+
+ XHCI_CMD_LOCK(sc);
+
+ index = udev->controller_slot_id;
+
+ if (index <= sc->sc_noslot) {
+ xhci_cmd_disable_slot(sc, index);
+ sc->sc_hw.devs[index].state = XHCI_ST_DISABLED;
+
+ /* free device extension */
+ xhci_free_device_ext(udev);
+ }
+
+ XHCI_CMD_UNLOCK(sc);
+}
+
+static void
+xhci_get_dma_delay(struct usb_device *udev, uint32_t *pus)
+{
+ /*
+ * Wait until the hardware has finished any possible use of
+ * the transfer descriptor(s)
+ */
+ *pus = 2048; /* microseconds */
+}
+
+static void
+xhci_device_resume(struct usb_device *udev)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ uint8_t index;
+ uint8_t n;
+ uint8_t p;
+
+ DPRINTF("\n");
+
+ /* check for root HUB */
+ if (udev->parent_hub == NULL)
+ return;
+
+ index = udev->controller_slot_id;
+
+ XHCI_CMD_LOCK(sc);
+
+ /* blindly resume all endpoints */
+
+ USB_BUS_LOCK(udev->bus);
+
+ for (n = 1; n != XHCI_MAX_ENDPOINTS; n++) {
+ for (p = 0; p != XHCI_MAX_STREAMS; p++) {
+ XWRITE4(sc, door, XHCI_DOORBELL(index),
+ n | XHCI_DB_SID_SET(p));
+ }
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ XHCI_CMD_UNLOCK(sc);
+}
+
+static void
+xhci_device_suspend(struct usb_device *udev)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ uint8_t index;
+ uint8_t n;
+ usb_error_t err;
+
+ DPRINTF("\n");
+
+ /* check for root HUB */
+ if (udev->parent_hub == NULL)
+ return;
+
+ index = udev->controller_slot_id;
+
+ XHCI_CMD_LOCK(sc);
+
+ /* blindly suspend all endpoints */
+
+ for (n = 1; n != XHCI_MAX_ENDPOINTS; n++) {
+ err = xhci_cmd_stop_ep(sc, 1, n, index);
+ if (err != 0) {
+ DPRINTF("Failed to suspend endpoint "
+ "%u on slot %u (ignored).\n", n, index);
+ }
+ }
+
+ XHCI_CMD_UNLOCK(sc);
+}
+
+static void
+xhci_set_hw_power(struct usb_bus *bus)
+{
+ DPRINTF("\n");
+}
+
+static void
+xhci_device_state_change(struct usb_device *udev)
+{
+ struct xhci_softc *sc = XHCI_BUS2SC(udev->bus);
+ struct usb_page_search buf_inp;
+ usb_error_t err;
+ uint8_t index;
+
+ /* check for root HUB */
+ if (udev->parent_hub == NULL)
+ return;
+
+ index = udev->controller_slot_id;
+
+ DPRINTF("\n");
+
+ if (usb_get_device_state(udev) == USB_STATE_CONFIGURED) {
+ err = uhub_query_info(udev, &sc->sc_hw.devs[index].nports,
+ &sc->sc_hw.devs[index].tt);
+ if (err != 0)
+ sc->sc_hw.devs[index].nports = 0;
+ }
+
+ XHCI_CMD_LOCK(sc);
+
+ switch (usb_get_device_state(udev)) {
+ case USB_STATE_POWERED:
+ if (sc->sc_hw.devs[index].state == XHCI_ST_DEFAULT)
+ break;
+
+ /* set default state */
+ sc->sc_hw.devs[index].state = XHCI_ST_DEFAULT;
+ sc->sc_hw.devs[index].ep_configured = 3U;
+
+ /* reset number of contexts */
+ sc->sc_hw.devs[index].context_num = 0;
+
+ err = xhci_cmd_reset_dev(sc, index);
+
+ if (err != 0) {
+ DPRINTF("Device reset failed "
+ "for slot %u.\n", index);
+ }
+ break;
+
+ case USB_STATE_ADDRESSED:
+ if (sc->sc_hw.devs[index].state == XHCI_ST_ADDRESSED)
+ break;
+
+ sc->sc_hw.devs[index].state = XHCI_ST_ADDRESSED;
+ sc->sc_hw.devs[index].ep_configured = 3U;
+
+ /* set configure mask to slot only */
+ xhci_configure_mask(udev, 1, 0);
+
+ /* deconfigure all endpoints, except EP0 */
+ err = xhci_cmd_configure_ep(sc, 0, 1, index);
+
+ if (err) {
+ DPRINTF("Failed to deconfigure "
+ "slot %u.\n", index);
+ }
+ break;
+
+ case USB_STATE_CONFIGURED:
+ if (sc->sc_hw.devs[index].state == XHCI_ST_CONFIGURED) {
+ /* deconfigure all endpoints, except EP0 */
+ err = xhci_cmd_configure_ep(sc, 0, 1, index);
+
+ if (err) {
+ DPRINTF("Failed to deconfigure "
+ "slot %u.\n", index);
+ }
+ }
+
+ /* set configured state */
+ sc->sc_hw.devs[index].state = XHCI_ST_CONFIGURED;
+ sc->sc_hw.devs[index].ep_configured = 3U;
+
+ /* reset number of contexts */
+ sc->sc_hw.devs[index].context_num = 0;
+
+ usbd_get_page(&sc->sc_hw.devs[index].input_pc, 0, &buf_inp);
+
+ xhci_configure_mask(udev, 3, 0);
+
+ err = xhci_configure_device(udev);
+ if (err != 0) {
+ DPRINTF("Could not configure device "
+ "at slot %u.\n", index);
+ }
+
+ err = xhci_cmd_evaluate_ctx(sc, buf_inp.physaddr, index);
+ if (err != 0) {
+ DPRINTF("Could not evaluate device "
+ "context at slot %u.\n", index);
+ }
+ break;
+
+ default:
+ break;
+ }
+ XHCI_CMD_UNLOCK(sc);
+}
+
+static usb_error_t
+xhci_set_endpoint_mode(struct usb_device *udev, struct usb_endpoint *ep,
+ uint8_t ep_mode)
+{
+ switch (ep_mode) {
+ case USB_EP_MODE_DEFAULT:
+ return (0);
+ case USB_EP_MODE_STREAMS:
+ if (xhcistreams == 0 ||
+ (ep->edesc->bmAttributes & UE_XFERTYPE) != UE_BULK ||
+ udev->speed != USB_SPEED_SUPER)
+ return (USB_ERR_INVAL);
+ return (0);
+ default:
+ return (USB_ERR_INVAL);
+ }
+}
+
+static const struct usb_bus_methods xhci_bus_methods = {
+ .endpoint_init = xhci_ep_init,
+ .endpoint_uninit = xhci_ep_uninit,
+ .xfer_setup = xhci_xfer_setup,
+ .xfer_unsetup = xhci_xfer_unsetup,
+ .get_dma_delay = xhci_get_dma_delay,
+ .device_init = xhci_device_init,
+ .device_uninit = xhci_device_uninit,
+ .device_resume = xhci_device_resume,
+ .device_suspend = xhci_device_suspend,
+ .set_hw_power = xhci_set_hw_power,
+ .roothub_exec = xhci_roothub_exec,
+ .xfer_poll = xhci_do_poll,
+ .start_dma_delay = xhci_start_dma_delay,
+ .set_address = xhci_set_address,
+ .clear_stall = xhci_ep_clear_stall,
+ .device_state_change = xhci_device_state_change,
+ .set_hw_power_sleep = xhci_set_hw_power_sleep,
+ .set_endpoint_mode = xhci_set_endpoint_mode,
+};
+
+MODULE_VERSION(xhci, 1);
diff --git a/sys/dev/usb/controller/xhci.h b/sys/dev/usb/controller/xhci.h
new file mode 100644
index 000000000000..3758815238ad
--- /dev/null
+++ b/sys/dev/usb/controller/xhci.h
@@ -0,0 +1,592 @@
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010-2022 Hans Petter Selasky
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _XHCI_H_
+#define _XHCI_H_
+
+#define XHCI_MAX_DEVICES MIN(USB_MAX_DEVICES, 128)
+#define XHCI_MAX_ENDPOINTS 32 /* hardcoded - do not change */
+#define XHCI_MAX_SCRATCHPADS 256 /* theoretical max is 1023 */
+#define XHCI_MAX_EVENTS 232
+#define XHCI_MAX_COMMANDS (16 * 1)
+#define XHCI_MAX_RSEG 1
+#define XHCI_MAX_TRANSFERS 4
+#if USB_MAX_EP_STREAMS == 8
+#define XHCI_MAX_STREAMS 8
+#define XHCI_MAX_STREAMS_LOG 3
+#elif USB_MAX_EP_STREAMS == 1
+#define XHCI_MAX_STREAMS 1
+#define XHCI_MAX_STREAMS_LOG 0
+#else
+#error "The USB_MAX_EP_STREAMS value is not supported."
+#endif
+#define XHCI_DEV_CTX_ADDR_ALIGN 64 /* bytes */
+#define XHCI_DEV_CTX_ALIGN 64 /* bytes */
+#define XHCI_INPUT_CTX_ALIGN 64 /* bytes */
+#define XHCI_SLOT_CTX_ALIGN 32 /* bytes */
+#define XHCI_ENDP_CTX_ALIGN 32 /* bytes */
+#define XHCI_STREAM_CTX_ALIGN 16 /* bytes */
+#define XHCI_TRANS_RING_SEG_ALIGN 16 /* bytes */
+#define XHCI_CMD_RING_SEG_ALIGN 64 /* bytes */
+#define XHCI_EVENT_RING_SEG_ALIGN 64 /* bytes */
+#define XHCI_SCRATCH_BUF_ARRAY_ALIGN 64 /* bytes */
+#define XHCI_SCRATCH_BUFFER_ALIGN USB_PAGE_SIZE
+#define XHCI_TRB_ALIGN 16 /* bytes */
+#define XHCI_TD_ALIGN 64 /* bytes */
+#define XHCI_PAGE_SIZE 4096 /* bytes */
+
+struct xhci_dev_ctx_addr {
+ volatile uint64_t qwBaaDevCtxAddr[USB_MAX_DEVICES + 1];
+ struct {
+ volatile uint64_t dummy;
+ } __aligned(64) padding;
+ volatile uint64_t qwSpBufPtr[XHCI_MAX_SCRATCHPADS];
+};
+
+#define XHCI_EPNO2EPID(x) \
+ ((((x) & UE_DIR_IN) ? 1 : 0) | (2 * ((x) & UE_ADDR)))
+
+struct xhci_slot_ctx {
+ volatile uint32_t dwSctx0;
+#define XHCI_SCTX_0_ROUTE_SET(x) ((x) & 0xFFFFF)
+#define XHCI_SCTX_0_ROUTE_GET(x) ((x) & 0xFFFFF)
+#define XHCI_SCTX_0_SPEED_SET(x) (((x) & 0xF) << 20)
+#define XHCI_SCTX_0_SPEED_GET(x) (((x) >> 20) & 0xF)
+#define XHCI_SCTX_0_MTT_SET(x) (((x) & 0x1) << 25)
+#define XHCI_SCTX_0_MTT_GET(x) (((x) >> 25) & 0x1)
+#define XHCI_SCTX_0_HUB_SET(x) (((x) & 0x1) << 26)
+#define XHCI_SCTX_0_HUB_GET(x) (((x) >> 26) & 0x1)
+#define XHCI_SCTX_0_CTX_NUM_SET(x) (((x) & 0x1F) << 27)
+#define XHCI_SCTX_0_CTX_NUM_GET(x) (((x) >> 27) & 0x1F)
+ volatile uint32_t dwSctx1;
+#define XHCI_SCTX_1_MAX_EL_SET(x) ((x) & 0xFFFF)
+#define XHCI_SCTX_1_MAX_EL_GET(x) ((x) & 0xFFFF)
+#define XHCI_SCTX_1_RH_PORT_SET(x) (((x) & 0xFF) << 16)
+#define XHCI_SCTX_1_RH_PORT_GET(x) (((x) >> 16) & 0xFF)
+#define XHCI_SCTX_1_NUM_PORTS_SET(x) (((x) & 0xFF) << 24)
+#define XHCI_SCTX_1_NUM_PORTS_GET(x) (((x) >> 24) & 0xFF)
+ volatile uint32_t dwSctx2;
+#define XHCI_SCTX_2_TT_HUB_SID_SET(x) ((x) & 0xFF)
+#define XHCI_SCTX_2_TT_HUB_SID_GET(x) ((x) & 0xFF)
+#define XHCI_SCTX_2_TT_PORT_NUM_SET(x) (((x) & 0xFF) << 8)
+#define XHCI_SCTX_2_TT_PORT_NUM_GET(x) (((x) >> 8) & 0xFF)
+#define XHCI_SCTX_2_TT_THINK_TIME_SET(x) (((x) & 0x3) << 16)
+#define XHCI_SCTX_2_TT_THINK_TIME_GET(x) (((x) >> 16) & 0x3)
+#define XHCI_SCTX_2_IRQ_TARGET_SET(x) (((x) & 0x3FF) << 22)
+#define XHCI_SCTX_2_IRQ_TARGET_GET(x) (((x) >> 22) & 0x3FF)
+ volatile uint32_t dwSctx3;
+#define XHCI_SCTX_3_DEV_ADDR_SET(x) ((x) & 0xFF)
+#define XHCI_SCTX_3_DEV_ADDR_GET(x) ((x) & 0xFF)
+#define XHCI_SCTX_3_SLOT_STATE_SET(x) (((x) & 0x1F) << 27)
+#define XHCI_SCTX_3_SLOT_STATE_GET(x) (((x) >> 27) & 0x1F)
+ volatile uint32_t dwSctx4;
+ volatile uint32_t dwSctx5;
+ volatile uint32_t dwSctx6;
+ volatile uint32_t dwSctx7;
+};
+
+struct xhci_slot_ctx64 {
+ struct xhci_slot_ctx ctx;
+ volatile uint8_t padding[32];
+};
+
+struct xhci_endp_ctx {
+ volatile uint32_t dwEpCtx0;
+#define XHCI_EPCTX_0_EPSTATE_SET(x) ((x) & 0x7)
+#define XHCI_EPCTX_0_EPSTATE_GET(x) ((x) & 0x7)
+#define XHCI_EPCTX_0_EPSTATE_DISABLED 0
+#define XHCI_EPCTX_0_EPSTATE_RUNNING 1
+#define XHCI_EPCTX_0_EPSTATE_HALTED 2
+#define XHCI_EPCTX_0_EPSTATE_STOPPED 3
+#define XHCI_EPCTX_0_EPSTATE_ERROR 4
+#define XHCI_EPCTX_0_EPSTATE_RESERVED_5 5
+#define XHCI_EPCTX_0_EPSTATE_RESERVED_6 6
+#define XHCI_EPCTX_0_EPSTATE_RESERVED_7 7
+#define XHCI_EPCTX_0_MULT_SET(x) (((x) & 0x3) << 8)
+#define XHCI_EPCTX_0_MULT_GET(x) (((x) >> 8) & 0x3)
+#define XHCI_EPCTX_0_MAXP_STREAMS_SET(x) (((x) & 0x1F) << 10)
+#define XHCI_EPCTX_0_MAXP_STREAMS_GET(x) (((x) >> 10) & 0x1F)
+#define XHCI_EPCTX_0_LSA_SET(x) (((x) & 0x1) << 15)
+#define XHCI_EPCTX_0_LSA_GET(x) (((x) >> 15) & 0x1)
+#define XHCI_EPCTX_0_IVAL_SET(x) (((x) & 0xFF) << 16)
+#define XHCI_EPCTX_0_IVAL_GET(x) (((x) >> 16) & 0xFF)
+ volatile uint32_t dwEpCtx1;
+#define XHCI_EPCTX_1_CERR_SET(x) (((x) & 0x3) << 1)
+#define XHCI_EPCTX_1_CERR_GET(x) (((x) >> 1) & 0x3)
+#define XHCI_EPCTX_1_EPTYPE_SET(x) (((x) & 0x7) << 3)
+#define XHCI_EPCTX_1_EPTYPE_GET(x) (((x) >> 3) & 0x7)
+#define XHCI_EPCTX_1_HID_SET(x) (((x) & 0x1) << 7)
+#define XHCI_EPCTX_1_HID_GET(x) (((x) >> 7) & 0x1)
+#define XHCI_EPCTX_1_MAXB_SET(x) (((x) & 0xFF) << 8)
+#define XHCI_EPCTX_1_MAXB_GET(x) (((x) >> 8) & 0xFF)
+#define XHCI_EPCTX_1_MAXP_SIZE_SET(x) (((x) & 0xFFFF) << 16)
+#define XHCI_EPCTX_1_MAXP_SIZE_GET(x) (((x) >> 16) & 0xFFFF)
+ volatile uint64_t qwEpCtx2;
+#define XHCI_EPCTX_2_DCS_SET(x) ((x) & 0x1)
+#define XHCI_EPCTX_2_DCS_GET(x) ((x) & 0x1)
+#define XHCI_EPCTX_2_TR_DQ_PTR_MASK 0xFFFFFFFFFFFFFFF0U
+ volatile uint32_t dwEpCtx4;
+#define XHCI_EPCTX_4_AVG_TRB_LEN_SET(x) ((x) & 0xFFFF)
+#define XHCI_EPCTX_4_AVG_TRB_LEN_GET(x) ((x) & 0xFFFF)
+#define XHCI_EPCTX_4_MAX_ESIT_PAYLOAD_SET(x) (((x) & 0xFFFF) << 16)
+#define XHCI_EPCTX_4_MAX_ESIT_PAYLOAD_GET(x) (((x) >> 16) & 0xFFFF)
+ volatile uint32_t dwEpCtx5;
+ volatile uint32_t dwEpCtx6;
+ volatile uint32_t dwEpCtx7;
+};
+
+struct xhci_endp_ctx64 {
+ struct xhci_endp_ctx ctx;
+ volatile uint8_t padding[32];
+};
+
+struct xhci_input_ctx {
+#define XHCI_INCTX_NON_CTRL_MASK 0xFFFFFFFCU
+ volatile uint32_t dwInCtx0;
+#define XHCI_INCTX_0_DROP_MASK(n) (1U << (n))
+ volatile uint32_t dwInCtx1;
+#define XHCI_INCTX_1_ADD_MASK(n) (1U << (n))
+ volatile uint32_t dwInCtx2;
+ volatile uint32_t dwInCtx3;
+ volatile uint32_t dwInCtx4;
+ volatile uint32_t dwInCtx5;
+ volatile uint32_t dwInCtx6;
+ volatile uint32_t dwInCtx7;
+};
+
+struct xhci_input_ctx64 {
+ struct xhci_input_ctx ctx;
+ volatile uint8_t padding[32];
+};
+
+struct xhci_input_dev_ctx {
+ struct xhci_input_ctx ctx_input;
+ struct xhci_slot_ctx ctx_slot;
+ struct xhci_endp_ctx ctx_ep[XHCI_MAX_ENDPOINTS - 1];
+};
+
+struct xhci_input_dev_ctx64 {
+ struct xhci_input_ctx64 ctx_input;
+ struct xhci_slot_ctx64 ctx_slot;
+ struct xhci_endp_ctx64 ctx_ep[XHCI_MAX_ENDPOINTS - 1];
+};
+
+struct xhci_dev_ctx {
+ struct xhci_slot_ctx ctx_slot;
+ struct xhci_endp_ctx ctx_ep[XHCI_MAX_ENDPOINTS - 1];
+} __aligned(XHCI_DEV_CTX_ALIGN);
+
+struct xhci_dev_ctx64 {
+ struct xhci_slot_ctx64 ctx_slot;
+ struct xhci_endp_ctx64 ctx_ep[XHCI_MAX_ENDPOINTS - 1];
+} __aligned(XHCI_DEV_CTX_ALIGN);
+
+struct xhci_stream_ctx {
+ volatile uint64_t qwSctx0;
+#define XHCI_SCTX_0_DCS_GET(x) ((x) & 0x1)
+#define XHCI_SCTX_0_DCS_SET(x) ((x) & 0x1)
+#define XHCI_SCTX_0_SCT_SET(x) (((x) & 0x7) << 1)
+#define XHCI_SCTX_0_SCT_GET(x) (((x) >> 1) & 0x7)
+#define XHCI_SCTX_0_SCT_SEC_TR_RING 0x0
+#define XHCI_SCTX_0_SCT_PRIM_TR_RING 0x1
+#define XHCI_SCTX_0_SCT_PRIM_SSA_8 0x2
+#define XHCI_SCTX_0_SCT_PRIM_SSA_16 0x3
+#define XHCI_SCTX_0_SCT_PRIM_SSA_32 0x4
+#define XHCI_SCTX_0_SCT_PRIM_SSA_64 0x5
+#define XHCI_SCTX_0_SCT_PRIM_SSA_128 0x6
+#define XHCI_SCTX_0_SCT_PRIM_SSA_256 0x7
+#define XHCI_SCTX_0_TR_DQ_PTR_MASK 0xFFFFFFFFFFFFFFF0U
+ volatile uint32_t dwSctx2;
+ volatile uint32_t dwSctx3;
+};
+
+struct xhci_trb {
+ volatile uint64_t qwTrb0;
+#define XHCI_TRB_0_DIR_IN_MASK (0x80ULL << 0)
+#define XHCI_TRB_0_WLENGTH_MASK (0xFFFFULL << 48)
+ volatile uint32_t dwTrb2;
+#define XHCI_TRB_2_ERROR_GET(x) (((x) >> 24) & 0xFF)
+#define XHCI_TRB_2_ERROR_SET(x) (((x) & 0xFF) << 24)
+#define XHCI_TRB_2_TDSZ_GET(x) (((x) >> 17) & 0x1F)
+#define XHCI_TRB_2_TDSZ_SET(x) (((x) & 0x1F) << 17)
+#define XHCI_TRB_2_REM_GET(x) ((x) & 0xFFFFFF)
+#define XHCI_TRB_2_REM_SET(x) ((x) & 0xFFFFFF)
+#define XHCI_TRB_2_BYTES_GET(x) ((x) & 0x1FFFF)
+#define XHCI_TRB_2_BYTES_SET(x) ((x) & 0x1FFFF)
+#define XHCI_TRB_2_IRQ_GET(x) (((x) >> 22) & 0x3FF)
+#define XHCI_TRB_2_IRQ_SET(x) (((x) & 0x3FF) << 22)
+#define XHCI_TRB_2_STREAM_GET(x) (((x) >> 16) & 0xFFFF)
+#define XHCI_TRB_2_STREAM_SET(x) (((x) & 0xFFFF) << 16)
+
+ volatile uint32_t dwTrb3;
+#define XHCI_TRB_3_TYPE_GET(x) (((x) >> 10) & 0x3F)
+#define XHCI_TRB_3_TYPE_SET(x) (((x) & 0x3F) << 10)
+#define XHCI_TRB_3_CYCLE_BIT (1U << 0)
+#define XHCI_TRB_3_TC_BIT (1U << 1) /* command ring only */
+#define XHCI_TRB_3_ENT_BIT (1U << 1) /* transfer ring only */
+#define XHCI_TRB_3_ISP_BIT (1U << 2)
+#define XHCI_TRB_3_NSNOOP_BIT (1U << 3)
+#define XHCI_TRB_3_CHAIN_BIT (1U << 4)
+#define XHCI_TRB_3_IOC_BIT (1U << 5)
+#define XHCI_TRB_3_IDT_BIT (1U << 6)
+#define XHCI_TRB_3_TBC_GET(x) (((x) >> 7) & 3)
+#define XHCI_TRB_3_TBC_SET(x) (((x) & 3) << 7)
+#define XHCI_TRB_3_BEI_BIT (1U << 9)
+#define XHCI_TRB_3_DCEP_BIT (1U << 9)
+#define XHCI_TRB_3_PRSV_BIT (1U << 9)
+#define XHCI_TRB_3_BSR_BIT (1U << 9)
+#define XHCI_TRB_3_TRT_MASK (3U << 16)
+#define XHCI_TRB_3_TRT_NONE (0U << 16)
+#define XHCI_TRB_3_TRT_OUT (2U << 16)
+#define XHCI_TRB_3_TRT_IN (3U << 16)
+#define XHCI_TRB_3_DIR_IN (1U << 16)
+#define XHCI_TRB_3_TLBPC_GET(x) (((x) >> 16) & 0xF)
+#define XHCI_TRB_3_TLBPC_SET(x) (((x) & 0xF) << 16)
+#define XHCI_TRB_3_EP_GET(x) (((x) >> 16) & 0x1F)
+#define XHCI_TRB_3_EP_SET(x) (((x) & 0x1F) << 16)
+#define XHCI_TRB_3_FRID_GET(x) (((x) >> 20) & 0x7FF)
+#define XHCI_TRB_3_FRID_SET(x) (((x) & 0x7FF) << 20)
+#define XHCI_TRB_3_ISO_SIA_BIT (1U << 31)
+#define XHCI_TRB_3_SUSP_EP_BIT (1U << 23)
+#define XHCI_TRB_3_SLOT_GET(x) (((x) >> 24) & 0xFF)
+#define XHCI_TRB_3_SLOT_SET(x) (((x) & 0xFF) << 24)
+
+/* Commands */
+#define XHCI_TRB_TYPE_RESERVED 0x00
+#define XHCI_TRB_TYPE_NORMAL 0x01
+#define XHCI_TRB_TYPE_SETUP_STAGE 0x02
+#define XHCI_TRB_TYPE_DATA_STAGE 0x03
+#define XHCI_TRB_TYPE_STATUS_STAGE 0x04
+#define XHCI_TRB_TYPE_ISOCH 0x05
+#define XHCI_TRB_TYPE_LINK 0x06
+#define XHCI_TRB_TYPE_EVENT_DATA 0x07
+#define XHCI_TRB_TYPE_NOOP 0x08
+#define XHCI_TRB_TYPE_ENABLE_SLOT 0x09
+#define XHCI_TRB_TYPE_DISABLE_SLOT 0x0A
+#define XHCI_TRB_TYPE_ADDRESS_DEVICE 0x0B
+#define XHCI_TRB_TYPE_CONFIGURE_EP 0x0C
+#define XHCI_TRB_TYPE_EVALUATE_CTX 0x0D
+#define XHCI_TRB_TYPE_RESET_EP 0x0E
+#define XHCI_TRB_TYPE_STOP_EP 0x0F
+#define XHCI_TRB_TYPE_SET_TR_DEQUEUE 0x10
+#define XHCI_TRB_TYPE_RESET_DEVICE 0x11
+#define XHCI_TRB_TYPE_FORCE_EVENT 0x12
+#define XHCI_TRB_TYPE_NEGOTIATE_BW 0x13
+#define XHCI_TRB_TYPE_SET_LATENCY_TOL 0x14
+#define XHCI_TRB_TYPE_GET_PORT_BW 0x15
+#define XHCI_TRB_TYPE_FORCE_HEADER 0x16
+#define XHCI_TRB_TYPE_NOOP_CMD 0x17
+
+/* Events */
+#define XHCI_TRB_EVENT_TRANSFER 0x20
+#define XHCI_TRB_EVENT_CMD_COMPLETE 0x21
+#define XHCI_TRB_EVENT_PORT_STS_CHANGE 0x22
+#define XHCI_TRB_EVENT_BW_REQUEST 0x23
+#define XHCI_TRB_EVENT_DOORBELL 0x24
+#define XHCI_TRB_EVENT_HOST_CTRL 0x25
+#define XHCI_TRB_EVENT_DEVICE_NOTIFY 0x26
+#define XHCI_TRB_EVENT_MFINDEX_WRAP 0x27
+
+/* Error codes */
+#define XHCI_TRB_ERROR_INVALID 0x00
+#define XHCI_TRB_ERROR_SUCCESS 0x01
+#define XHCI_TRB_ERROR_DATA_BUF 0x02
+#define XHCI_TRB_ERROR_BABBLE 0x03
+#define XHCI_TRB_ERROR_XACT 0x04
+#define XHCI_TRB_ERROR_TRB 0x05
+#define XHCI_TRB_ERROR_STALL 0x06
+#define XHCI_TRB_ERROR_RESOURCE 0x07
+#define XHCI_TRB_ERROR_BANDWIDTH 0x08
+#define XHCI_TRB_ERROR_NO_SLOTS 0x09
+#define XHCI_TRB_ERROR_STREAM_TYPE 0x0A
+#define XHCI_TRB_ERROR_SLOT_NOT_ON 0x0B
+#define XHCI_TRB_ERROR_ENDP_NOT_ON 0x0C
+#define XHCI_TRB_ERROR_SHORT_PKT 0x0D
+#define XHCI_TRB_ERROR_RING_UNDERRUN 0x0E
+#define XHCI_TRB_ERROR_RING_OVERRUN 0x0F
+#define XHCI_TRB_ERROR_VF_RING_FULL 0x10
+#define XHCI_TRB_ERROR_PARAMETER 0x11
+#define XHCI_TRB_ERROR_BW_OVERRUN 0x12
+#define XHCI_TRB_ERROR_CONTEXT_STATE 0x13
+#define XHCI_TRB_ERROR_NO_PING_RESP 0x14
+#define XHCI_TRB_ERROR_EV_RING_FULL 0x15
+#define XHCI_TRB_ERROR_INCOMPAT_DEV 0x16
+#define XHCI_TRB_ERROR_MISSED_SERVICE 0x17
+#define XHCI_TRB_ERROR_CMD_RING_STOP 0x18
+#define XHCI_TRB_ERROR_CMD_ABORTED 0x19
+#define XHCI_TRB_ERROR_STOPPED 0x1A
+#define XHCI_TRB_ERROR_LENGTH 0x1B
+#define XHCI_TRB_ERROR_BAD_MELAT 0x1D
+#define XHCI_TRB_ERROR_ISOC_OVERRUN 0x1F
+#define XHCI_TRB_ERROR_EVENT_LOST 0x20
+#define XHCI_TRB_ERROR_UNDEFINED 0x21
+#define XHCI_TRB_ERROR_INVALID_SID 0x22
+#define XHCI_TRB_ERROR_SEC_BW 0x23
+#define XHCI_TRB_ERROR_SPLIT_XACT 0x24
+} __aligned(4);
+
+struct xhci_dev_endpoint_trbs {
+ struct xhci_trb trb[(XHCI_MAX_STREAMS *
+ XHCI_MAX_TRANSFERS) + XHCI_MAX_STREAMS];
+};
+
+#if (USB_PAGE_SIZE < 4096)
+#error "The XHCI driver needs a pagesize above or equal to 4K"
+#endif
+
+/* Define the maximum payload which we will handle in a single TRB */
+#define XHCI_TD_PAYLOAD_MAX 65536 /* bytes */
+
+/* Define the maximum payload of a single scatter-gather list element */
+#define XHCI_TD_PAGE_SIZE \
+ ((USB_PAGE_SIZE < XHCI_TD_PAYLOAD_MAX) ? USB_PAGE_SIZE : XHCI_TD_PAYLOAD_MAX)
+
+/* Define the maximum length of the scatter-gather list */
+#define XHCI_TD_PAGE_NBUF \
+ (((XHCI_TD_PAYLOAD_MAX + XHCI_TD_PAGE_SIZE - 1) / XHCI_TD_PAGE_SIZE) + 1)
+
+struct xhci_td {
+ /* one LINK TRB has been added to the TRB array */
+ struct xhci_trb td_trb[XHCI_TD_PAGE_NBUF + 1];
+
+/*
+ * Extra information needed:
+ */
+ uint64_t td_self;
+ struct xhci_td *next;
+ struct xhci_td *alt_next;
+ struct xhci_td *obj_next;
+ struct usb_page_cache *page_cache;
+ uint32_t len;
+ uint32_t remainder;
+ uint8_t ntrb;
+ uint8_t status;
+} __aligned(XHCI_TRB_ALIGN);
+
+struct xhci_command {
+ struct xhci_trb trb;
+ TAILQ_ENTRY(xhci_command) entry;
+};
+
+struct xhci_event_ring_seg {
+ volatile uint64_t qwEvrsTablePtr;
+ volatile uint32_t dwEvrsTableSize;
+ volatile uint32_t dwEvrsReserved;
+};
+
+struct xhci_hw_root {
+ struct xhci_event_ring_seg hwr_ring_seg[XHCI_MAX_RSEG];
+ struct {
+ volatile uint64_t dummy;
+ } __aligned(64) padding;
+ struct xhci_trb hwr_events[XHCI_MAX_EVENTS];
+ struct xhci_trb hwr_commands[XHCI_MAX_COMMANDS];
+};
+
+CTASSERT(sizeof(struct xhci_hw_root) == XHCI_PAGE_SIZE);
+
+struct xhci_endpoint_ext {
+ struct xhci_trb *trb;
+ struct usb_xfer *xfer[XHCI_MAX_TRANSFERS * XHCI_MAX_STREAMS];
+ struct usb_page_cache *page_cache;
+ uint64_t physaddr;
+ uint8_t trb_used[XHCI_MAX_STREAMS];
+ uint8_t trb_index[XHCI_MAX_STREAMS];
+ uint8_t trb_halted;
+ uint8_t trb_running;
+ uint8_t trb_ep_mode;
+ uint8_t trb_ep_maxp;
+};
+
+enum {
+ XHCI_ST_DISABLED,
+ XHCI_ST_ENABLED,
+ XHCI_ST_DEFAULT,
+ XHCI_ST_ADDRESSED,
+ XHCI_ST_CONFIGURED,
+ XHCI_ST_MAX
+};
+
+struct xhci_hw_dev {
+ struct usb_page_cache device_pc;
+ struct usb_page_cache input_pc;
+ struct usb_page_cache endpoint_pc[XHCI_MAX_ENDPOINTS];
+
+ struct usb_page device_pg;
+ struct usb_page input_pg;
+ struct usb_page endpoint_pg[XHCI_MAX_ENDPOINTS];
+
+ struct xhci_endpoint_ext endp[XHCI_MAX_ENDPOINTS];
+
+ uint32_t ep_configured;
+
+ uint8_t state;
+ uint8_t nports;
+ uint8_t tt;
+ uint8_t context_num;
+};
+
+struct xhci_hw_softc {
+ struct usb_page_cache root_pc;
+ struct usb_page_cache ctx_pc;
+ struct usb_page_cache scratch_pc[XHCI_MAX_SCRATCHPADS];
+
+ struct usb_page root_pg;
+ struct usb_page ctx_pg;
+ struct usb_page scratch_pg[XHCI_MAX_SCRATCHPADS];
+
+ struct xhci_hw_dev devs[XHCI_MAX_DEVICES + 1];
+};
+
+struct xhci_config_desc {
+ struct usb_config_descriptor confd;
+ struct usb_interface_descriptor ifcd;
+ struct usb_endpoint_descriptor endpd;
+ struct usb_endpoint_ss_comp_descriptor endpcd;
+} __packed;
+
+struct xhci_bos_desc {
+ struct usb_bos_descriptor bosd;
+ struct usb_devcap_usb2ext_descriptor usb2extd;
+ struct usb_devcap_ss_descriptor usbdcd;
+ struct usb_devcap_container_id_descriptor cidd;
+} __packed;
+
+union xhci_hub_desc {
+ struct usb_status stat;
+ struct usb_port_status ps;
+ struct usb_hub_ss_descriptor hubd;
+ uint8_t temp[128];
+};
+
+typedef int (xhci_port_route_t)(device_t, uint32_t, uint32_t);
+
+enum xhci_quirks {
+ XHCI_QUIRK_DISABLE_PORT_PED = 0x00000001,
+ XHCI_QUIRK_DMA_32B = 0x00000002,
+};
+
+struct xhci_softc {
+ struct xhci_hw_softc sc_hw;
+ /* base device */
+ struct usb_bus sc_bus;
+ /* configure message */
+ struct usb_bus_msg sc_config_msg[2];
+
+ struct usb_callout sc_callout;
+
+ xhci_port_route_t *sc_port_route;
+
+ union xhci_hub_desc sc_hub_desc;
+
+ struct cv sc_cmd_cv;
+ struct sx sc_cmd_sx;
+
+ struct usb_device *sc_devices[XHCI_MAX_DEVICES];
+ struct resource *sc_io_res;
+ struct resource *sc_irq_res;
+ struct resource *sc_msix_res;
+
+ void *sc_intr_hdl;
+ bus_size_t sc_io_size;
+ bus_space_tag_t sc_io_tag;
+ bus_space_handle_t sc_io_hdl;
+ /* last pending command address */
+ uint64_t sc_cmd_addr;
+ /* result of command */
+ uint32_t sc_cmd_result[2];
+ /* copy of cmd register */
+ uint32_t sc_cmd;
+ /* worst case exit latency */
+ uint32_t sc_exit_lat_max;
+
+ /* offset to operational registers */
+ uint32_t sc_oper_off;
+ /* offset to capability registers */
+ uint32_t sc_capa_off;
+ /* offset to runtime registers */
+ uint32_t sc_runt_off;
+ /* offset to doorbell registers */
+ uint32_t sc_door_off;
+
+ /* chip specific */
+ uint16_t sc_erst_max;
+ uint16_t sc_event_idx;
+ uint16_t sc_command_idx;
+ uint16_t sc_imod_default;
+
+ /* number of scratch pages */
+ uint16_t sc_noscratch;
+
+ uint8_t sc_event_ccs;
+ uint8_t sc_command_ccs;
+ /* number of XHCI device slots */
+ uint8_t sc_noslot;
+ /* number of ports on root HUB */
+ uint8_t sc_noport;
+ /* root HUB device configuration */
+ uint8_t sc_conf;
+ /* step status stage of all control transfers */
+ uint8_t sc_ctlstep;
+ /* root HUB port event bitmap, max 256 ports */
+ uint8_t sc_hub_idata[32];
+
+ /* size of context */
+ uint8_t sc_ctx_is_64_byte;
+
+ /* deconfiguring USB device is not fully supported */
+ uint8_t sc_no_deconfigure;
+
+ /* Isochronous Scheduling Threshold */
+ uint8_t sc_ist;
+
+ /* vendor string for root HUB */
+ char sc_vendor[16];
+
+ /* XHCI quirks. */
+ uint32_t sc_quirks;
+};
+
+#define XHCI_CMD_LOCK(sc) sx_xlock(&(sc)->sc_cmd_sx)
+#define XHCI_CMD_UNLOCK(sc) sx_xunlock(&(sc)->sc_cmd_sx)
+#define XHCI_CMD_ASSERT_LOCKED(sc) sx_assert(&(sc)->sc_cmd_sx, SA_LOCKED)
+
+/* prototypes */
+
+uint8_t xhci_use_polling(void);
+usb_error_t xhci_halt_controller(struct xhci_softc *);
+usb_error_t xhci_reset_controller(struct xhci_softc *);
+usb_error_t xhci_init(struct xhci_softc *, device_t, uint8_t);
+usb_error_t xhci_start_controller(struct xhci_softc *);
+void xhci_interrupt(struct xhci_softc *);
+void xhci_uninit(struct xhci_softc *);
+int xhci_pci_attach(device_t);
+
+DECLARE_CLASS(xhci_pci_driver);
+
+#endif /* _XHCI_H_ */
diff --git a/sys/dev/usb/controller/xhci_pci.c b/sys/dev/usb/controller/xhci_pci.c
new file mode 100644
index 000000000000..d5cfd228a429
--- /dev/null
+++ b/sys/dev/usb/controller/xhci_pci.c
@@ -0,0 +1,558 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010-2022 Hans Petter Selasky
+ *
+ * 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/usb_pci.h>
+#include <dev/usb/controller/xhci.h>
+#include <dev/usb/controller/xhcireg.h>
+#include "usb_if.h"
+
+#define PCI_XHCI_VENDORID_AMD 0x1022
+#define PCI_XHCI_VENDORID_INTEL 0x8086
+#define PCI_XHCI_VENDORID_VMWARE 0x15ad
+#define PCI_XHCI_VENDORID_ZHAOXIN 0x1d17
+
+static device_probe_t xhci_pci_probe;
+static device_detach_t xhci_pci_detach;
+static usb_take_controller_t xhci_pci_take_controller;
+
+static device_method_t xhci_device_methods[] = {
+ /* device interface */
+ DEVMETHOD(device_probe, xhci_pci_probe),
+ DEVMETHOD(device_attach, xhci_pci_attach),
+ DEVMETHOD(device_detach, xhci_pci_detach),
+ DEVMETHOD(device_suspend, bus_generic_suspend),
+ DEVMETHOD(device_resume, bus_generic_resume),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+ DEVMETHOD(usb_take_controller, xhci_pci_take_controller),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(xhci, xhci_pci_driver, xhci_device_methods,
+ sizeof(struct xhci_softc));
+
+DRIVER_MODULE(xhci, pci, xhci_pci_driver, NULL, NULL);
+MODULE_DEPEND(xhci, usb, 1, 1, 1);
+
+static const char *
+xhci_pci_match(device_t self)
+{
+ uint32_t device_id = pci_get_devid(self);
+
+ switch (device_id) {
+ case 0x145c1022:
+ return ("AMD KERNCZ USB 3.0 controller");
+ case 0x148c1022:
+ return ("AMD Starship USB 3.0 controller");
+ case 0x149c1022:
+ return ("AMD Matisse USB 3.0 controller");
+ case 0x15b61022:
+ case 0x15b71022:
+ return ("AMD Raphael/Granite Ridge USB 3.1 controller");
+ case 0x15b81022:
+ return ("AMD Raphael/Granite Ridge USB 2.0 controller");
+ case 0x15e01022:
+ case 0x15e11022:
+ return ("AMD Raven USB 3.1 controller");
+ case 0x43ba1022:
+ return ("AMD X399 USB 3.0 controller");
+ case 0x43b91022: /* X370 */
+ case 0x43bb1022: /* B350 */
+ return ("AMD 300 Series USB 3.1 controller");
+ case 0x43d51022:
+ return ("AMD 400 Series USB 3.1 controller");
+ case 0x43f71022:
+ return ("AMD 600 Series USB 3.2 controller");
+ case 0x78121022:
+ case 0x78141022:
+ case 0x79141022:
+ return ("AMD FCH USB 3.0 controller");
+
+ case 0x077815ad:
+ case 0x077915ad:
+ return ("VMware USB 3.0 controller");
+
+ case 0x145f1d94:
+ return ("Hygon USB 3.0 controller");
+
+ case 0x01941033:
+ return ("NEC uPD720200 USB 3.0 controller");
+ case 0x00151912:
+ return ("NEC uPD720202 USB 3.0 controller");
+
+ case 0x10001b73:
+ return ("Fresco Logic FL1000G USB 3.0 controller");
+ case 0x10091b73:
+ return ("Fresco Logic FL1009 USB 3.0 controller");
+ case 0x11001b73:
+ return ("Fresco Logic FL1100 USB 3.0 controller");
+
+ case 0x10421b21:
+ return ("ASMedia ASM1042 USB 3.0 controller");
+ case 0x11421b21:
+ return ("ASMedia ASM1042A USB 3.0 controller");
+ case 0x13431b21:
+ return ("ASMedia ASM1143 USB 3.1 controller");
+ case 0x32421b21:
+ return ("ASMedia ASM3242 USB 3.2 controller");
+
+ case 0x0b278086:
+ return ("Intel Goshen Ridge Thunderbolt 4 USB controller");
+ case 0x0f358086:
+ return ("Intel BayTrail USB 3.0 controller");
+ case 0x11388086:
+ return ("Intel Maple Ridge Thunderbolt 4 USB controller");
+ case 0x15c18086:
+ case 0x15d48086:
+ case 0x15db8086:
+ return ("Intel Alpine Ridge Thunderbolt 3 USB controller");
+ case 0x15e98086:
+ case 0x15ec8086:
+ case 0x15f08086:
+ return ("Intel Titan Ridge Thunderbolt 3 USB controller");
+ case 0x19d08086:
+ return ("Intel Denverton USB 3.0 controller");
+ case 0x9c318086:
+ case 0x1e318086:
+ return ("Intel Panther Point USB 3.0 controller");
+ case 0x22b58086:
+ return ("Intel Braswell USB 3.0 controller");
+ case 0x31a88086:
+ return ("Intel Gemini Lake USB 3.0 controller");
+ case 0x34ed8086:
+ return ("Intel Ice Lake-LP USB 3.1 controller");
+ case 0x43ed8086:
+ return ("Intel Tiger Lake-H USB 3.2 controller");
+ case 0x461e8086:
+ return ("Intel Alder Lake-P Thunderbolt 4 USB controller");
+ case 0x51ed8086:
+ return ("Intel Alder Lake USB 3.2 controller");
+ case 0x5aa88086:
+ return ("Intel Apollo Lake USB 3.0 controller");
+ case 0x7ae08086:
+ return ("Intel Alder Lake USB 3.2 controller");
+ case 0x8a138086:
+ return ("Intel Ice Lake Thunderbolt 3 USB controller");
+ case 0x8c318086:
+ return ("Intel Lynx Point USB 3.0 controller");
+ case 0x8cb18086:
+ return ("Intel Wildcat Point USB 3.0 controller");
+ case 0x8d318086:
+ return ("Intel Wellsburg USB 3.0 controller");
+ case 0x9a138086:
+ return ("Intel Tiger Lake-LP Thunderbolt 4 USB controller");
+ case 0x9a178086:
+ return ("Intel Tiger Lake-H Thunderbolt 4 USB controller");
+ case 0x9cb18086:
+ return ("Broadwell Integrated PCH-LP chipset USB 3.0 controller");
+ case 0x9d2f8086:
+ return ("Intel Sunrise Point-LP USB 3.0 controller");
+ case 0xa0ed8086:
+ return ("Intel Tiger Lake-LP USB 3.2 controller");
+ case 0xa12f8086:
+ return ("Intel Sunrise Point USB 3.0 controller");
+ case 0xa1af8086:
+ return ("Intel Lewisburg USB 3.0 controller");
+ case 0xa2af8086:
+ return ("Intel Union Point USB 3.0 controller");
+ case 0xa36d8086:
+ return ("Intel Cannon Lake USB 3.1 controller");
+
+ case 0xa01b177d:
+ return ("Cavium ThunderX USB 3.0 controller");
+
+ case 0x1ada10de:
+ return ("NVIDIA TU106 USB 3.1 controller");
+
+ case 0x92021d17:
+ return ("Zhaoxin ZX-100 USB 3.0 controller");
+ case 0x92031d17:
+ return ("Zhaoxin ZX-200 USB 3.0 controller");
+ case 0x92041d17:
+ return ("Zhaoxin ZX-E USB 3.0 controller");
+
+ default:
+ break;
+ }
+
+ if ((pci_get_class(self) == PCIC_SERIALBUS)
+ && (pci_get_subclass(self) == PCIS_SERIALBUS_USB)
+ && (pci_get_progif(self) == PCIP_SERIALBUS_USB_XHCI)) {
+ return ("XHCI (generic) USB 3.0 controller");
+ }
+ return (NULL); /* dunno */
+}
+
+static int
+xhci_pci_probe(device_t self)
+{
+ const char *desc = xhci_pci_match(self);
+
+ if (desc) {
+ device_set_desc(self, desc);
+ return (BUS_PROBE_DEFAULT);
+ } else {
+ return (ENXIO);
+ }
+}
+
+static int xhci_use_msi = 1;
+TUNABLE_INT("hw.usb.xhci.msi", &xhci_use_msi);
+static int xhci_use_msix = 1;
+TUNABLE_INT("hw.usb.xhci.msix", &xhci_use_msix);
+
+static void
+xhci_interrupt_poll(void *_sc)
+{
+ struct xhci_softc *sc = _sc;
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ xhci_interrupt(sc);
+ USB_BUS_LOCK(&sc->sc_bus);
+ usb_callout_reset(&sc->sc_callout, 1, (void *)&xhci_interrupt_poll, sc);
+}
+
+static int
+xhci_pci_port_route(device_t self, uint32_t set, uint32_t clear)
+{
+ uint32_t temp;
+ uint32_t usb3_mask;
+ uint32_t usb2_mask;
+
+ temp = pci_read_config(self, PCI_XHCI_INTEL_USB3_PSSEN, 4) |
+ pci_read_config(self, PCI_XHCI_INTEL_XUSB2PR, 4);
+
+ temp |= set;
+ temp &= ~clear;
+
+ /* Don't set bits which the hardware doesn't support */
+ usb3_mask = pci_read_config(self, PCI_XHCI_INTEL_USB3PRM, 4);
+ usb2_mask = pci_read_config(self, PCI_XHCI_INTEL_USB2PRM, 4);
+
+ pci_write_config(self, PCI_XHCI_INTEL_USB3_PSSEN, temp & usb3_mask, 4);
+ pci_write_config(self, PCI_XHCI_INTEL_XUSB2PR, temp & usb2_mask, 4);
+
+ device_printf(self, "Port routing mask set to 0x%08x\n", temp);
+
+ return (0);
+}
+
+int
+xhci_pci_attach(device_t self)
+{
+ struct xhci_softc *sc = device_get_softc(self);
+ int count, err, msix_table, rid;
+ uint8_t usemsi = 1;
+ uint8_t usedma32 = 0;
+
+ rid = PCI_XHCI_CBMEM;
+ sc->sc_io_res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid,
+ RF_ACTIVE);
+ if (!sc->sc_io_res) {
+ device_printf(self, "Could not map memory\n");
+ return (ENOMEM);
+ }
+ sc->sc_io_tag = rman_get_bustag(sc->sc_io_res);
+ sc->sc_io_hdl = rman_get_bushandle(sc->sc_io_res);
+ sc->sc_io_size = rman_get_size(sc->sc_io_res);
+
+ switch (pci_get_devid(self)) {
+ case 0x10091b73: /* Fresco Logic FL1009 USB3.0 xHCI Controller */
+ case 0x8241104c: /* TUSB73x0 USB3.0 xHCI Controller */
+ sc->sc_no_deconfigure = 1;
+ break;
+ case 0x01941033: /* NEC uPD720200 USB 3.0 controller */
+ case 0x00141912: /* NEC uPD720201 USB 3.0 controller */
+ /* Don't use 64-bit DMA on these controllers. */
+ usedma32 = 1;
+ break;
+ case 0x10001b73: /* FL1000G */
+ /* Fresco Logic host doesn't support MSI. */
+ usemsi = 0;
+ break;
+ case 0x0f358086: /* BayTrail */
+ case 0x9c318086: /* Panther Point */
+ case 0x1e318086: /* Panther Point */
+ case 0x8c318086: /* Lynx Point */
+ case 0x8cb18086: /* Wildcat Point */
+ case 0x9cb18086: /* Broadwell Mobile Integrated */
+ /*
+ * On Intel chipsets, reroute ports from EHCI to XHCI
+ * controller and use a different IMOD value.
+ */
+ sc->sc_port_route = &xhci_pci_port_route;
+ sc->sc_imod_default = XHCI_IMOD_DEFAULT_LP;
+ sc->sc_ctlstep = 1;
+ break;
+ default:
+ break;
+ }
+
+ if (xhci_init(sc, self, usedma32)) {
+ device_printf(self, "Could not initialize softc\n");
+ bus_release_resource(self, SYS_RES_MEMORY, PCI_XHCI_CBMEM,
+ sc->sc_io_res);
+ return (ENXIO);
+ }
+
+ pci_enable_busmaster(self);
+
+ usb_callout_init_mtx(&sc->sc_callout, &sc->sc_bus.bus_mtx, 0);
+
+ rid = 0;
+ if (xhci_use_msix && (msix_table = pci_msix_table_bar(self)) >= 0) {
+ if (msix_table == PCI_XHCI_CBMEM) {
+ sc->sc_msix_res = sc->sc_io_res;
+ } else {
+ sc->sc_msix_res = bus_alloc_resource_any(self,
+ SYS_RES_MEMORY, &msix_table, RF_ACTIVE);
+ if (sc->sc_msix_res == NULL) {
+ /* May not be enabled */
+ device_printf(self,
+ "Unable to map MSI-X table\n");
+ }
+ }
+ if (sc->sc_msix_res != NULL) {
+ count = 1;
+ if (pci_alloc_msix(self, &count) == 0) {
+ if (bootverbose)
+ device_printf(self, "MSI-X enabled\n");
+ rid = 1;
+ } else {
+ if (sc->sc_msix_res != sc->sc_io_res) {
+ bus_release_resource(self,
+ SYS_RES_MEMORY,
+ msix_table, sc->sc_msix_res);
+ }
+ sc->sc_msix_res = NULL;
+ }
+ }
+ }
+ if (rid == 0 && xhci_use_msi && usemsi) {
+ count = 1;
+ if (pci_alloc_msi(self, &count) == 0) {
+ if (bootverbose)
+ device_printf(self, "MSI enabled\n");
+ rid = 1;
+ }
+ }
+ sc->sc_irq_res = bus_alloc_resource_any(self, SYS_RES_IRQ, &rid,
+ RF_ACTIVE | (rid != 0 ? 0 : RF_SHAREABLE));
+ if (sc->sc_irq_res == NULL) {
+ pci_release_msi(self);
+ device_printf(self, "Could not allocate IRQ\n");
+ /* goto error; FALLTHROUGH - use polling */
+ }
+ sc->sc_bus.bdev = device_add_child(self, "usbus", DEVICE_UNIT_ANY);
+ if (sc->sc_bus.bdev == NULL) {
+ device_printf(self, "Could not add USB device\n");
+ goto error;
+ }
+ device_set_ivars(sc->sc_bus.bdev, &sc->sc_bus);
+
+ switch (pci_get_vendor(self)) {
+ case PCI_XHCI_VENDORID_AMD:
+ strlcpy(sc->sc_vendor, "AMD", sizeof(sc->sc_vendor));
+ break;
+ case PCI_XHCI_VENDORID_INTEL:
+ strlcpy(sc->sc_vendor, "Intel", sizeof(sc->sc_vendor));
+ break;
+ case PCI_XHCI_VENDORID_VMWARE:
+ strlcpy(sc->sc_vendor, "VMware", sizeof(sc->sc_vendor));
+ break;
+ case PCI_XHCI_VENDORID_ZHAOXIN:
+ strlcpy(sc->sc_vendor, "Zhaoxin", sizeof(sc->sc_vendor));
+ break;
+ default:
+ if (bootverbose)
+ device_printf(self, "(New XHCI DeviceId=0x%08x)\n",
+ pci_get_devid(self));
+ snprintf(sc->sc_vendor, sizeof(sc->sc_vendor),
+ "(0x%04x)", pci_get_vendor(self));
+ break;
+ }
+
+ if (sc->sc_irq_res != NULL && xhci_use_polling() == 0) {
+ err = bus_setup_intr(self, sc->sc_irq_res, INTR_TYPE_BIO | INTR_MPSAFE,
+ NULL, (driver_intr_t *)xhci_interrupt, sc, &sc->sc_intr_hdl);
+ if (err != 0) {
+ bus_release_resource(self, SYS_RES_IRQ,
+ rman_get_rid(sc->sc_irq_res), sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ pci_release_msi(self);
+ device_printf(self, "Could not setup IRQ, err=%d\n", err);
+ sc->sc_intr_hdl = NULL;
+ }
+ }
+ if (sc->sc_irq_res == NULL || sc->sc_intr_hdl == NULL) {
+ if (xhci_use_polling() != 0) {
+ device_printf(self, "Interrupt polling at %dHz\n", hz);
+ USB_BUS_LOCK(&sc->sc_bus);
+ xhci_interrupt_poll(sc);
+ USB_BUS_UNLOCK(&sc->sc_bus);
+ } else
+ goto error;
+ }
+
+ xhci_pci_take_controller(self);
+
+ err = xhci_halt_controller(sc);
+
+ if (err == 0)
+ err = xhci_start_controller(sc);
+
+ if (err == 0)
+ err = device_probe_and_attach(sc->sc_bus.bdev);
+
+ if (err) {
+ device_printf(self, "XHCI halt/start/probe failed err=%d\n", err);
+ goto error;
+ }
+ return (0);
+
+error:
+ xhci_pci_detach(self);
+ return (ENXIO);
+}
+
+static int
+xhci_pci_detach(device_t self)
+{
+ struct xhci_softc *sc = device_get_softc(self);
+ int error;
+
+ /* during module unload there are lots of children leftover */
+ error = bus_generic_detach(self);
+ if (error != 0)
+ return (error);
+
+ usb_callout_drain(&sc->sc_callout);
+ xhci_halt_controller(sc);
+ xhci_reset_controller(sc);
+
+ pci_disable_busmaster(self);
+
+ if (sc->sc_irq_res && sc->sc_intr_hdl) {
+ bus_teardown_intr(self, sc->sc_irq_res, sc->sc_intr_hdl);
+ sc->sc_intr_hdl = NULL;
+ }
+ if (sc->sc_irq_res) {
+ bus_release_resource(self, SYS_RES_IRQ,
+ rman_get_rid(sc->sc_irq_res), sc->sc_irq_res);
+ sc->sc_irq_res = NULL;
+ pci_release_msi(self);
+ }
+ if (sc->sc_msix_res != NULL && sc->sc_msix_res != sc->sc_io_res) {
+ bus_release_resource(self, SYS_RES_MEMORY,
+ rman_get_rid(sc->sc_msix_res), sc->sc_msix_res);
+ sc->sc_msix_res = NULL;
+ }
+ if (sc->sc_io_res) {
+ bus_release_resource(self, SYS_RES_MEMORY, PCI_XHCI_CBMEM,
+ sc->sc_io_res);
+ sc->sc_io_res = NULL;
+ }
+
+ xhci_uninit(sc);
+
+ return (0);
+}
+
+static int
+xhci_pci_take_controller(device_t self)
+{
+ struct xhci_softc *sc = device_get_softc(self);
+ uint32_t cparams;
+ uint32_t eecp;
+ uint32_t eec;
+ uint16_t to;
+ uint8_t bios_sem;
+
+ cparams = XREAD4(sc, capa, XHCI_HCSPARAMS0);
+
+ eec = -1;
+
+ /* Synchronise with the BIOS if it owns the controller. */
+ for (eecp = XHCI_HCS0_XECP(cparams) << 2; eecp != 0 && XHCI_XECP_NEXT(eec);
+ eecp += XHCI_XECP_NEXT(eec) << 2) {
+ eec = XREAD4(sc, capa, eecp);
+
+ if (XHCI_XECP_ID(eec) != XHCI_ID_USB_LEGACY)
+ continue;
+ bios_sem = XREAD1(sc, capa, eecp +
+ XHCI_XECP_BIOS_SEM);
+ if (bios_sem == 0)
+ continue;
+ device_printf(sc->sc_bus.bdev, "waiting for BIOS "
+ "to give up control\n");
+ XWRITE1(sc, capa, eecp +
+ XHCI_XECP_OS_SEM, 1);
+ to = 500;
+ while (1) {
+ bios_sem = XREAD1(sc, capa, eecp +
+ XHCI_XECP_BIOS_SEM);
+ if (bios_sem == 0)
+ break;
+
+ if (--to == 0) {
+ device_printf(sc->sc_bus.bdev,
+ "timed out waiting for BIOS\n");
+ break;
+ }
+ usb_pause_mtx(NULL, hz / 100); /* wait 10ms */
+ }
+ }
+ return (0);
+}
diff --git a/sys/dev/usb/controller/xhcireg.h b/sys/dev/usb/controller/xhcireg.h
new file mode 100644
index 000000000000..9d0b6e2f4b4b
--- /dev/null
+++ b/sys/dev/usb/controller/xhcireg.h
@@ -0,0 +1,229 @@
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _XHCIREG_H_
+#define _XHCIREG_H_
+
+/* XHCI PCI config registers */
+#define PCI_XHCI_CBMEM 0x10 /* configuration base MEM */
+#define PCI_XHCI_USBREV 0x60 /* RO USB protocol revision */
+#define PCI_USB_REV_3_0 0x30 /* USB 3.0 */
+#define PCI_XHCI_FLADJ 0x61 /* RW frame length adjust */
+
+#define PCI_XHCI_INTEL_XUSB2PR 0xD0 /* Intel USB2 Port Routing */
+#define PCI_XHCI_INTEL_USB2PRM 0xD4 /* Intel USB2 Port Routing Mask */
+#define PCI_XHCI_INTEL_USB3_PSSEN 0xD8 /* Intel USB3 Port SuperSpeed Enable */
+#define PCI_XHCI_INTEL_USB3PRM 0xDC /* Intel USB3 Port Routing Mask */
+
+/* XHCI capability registers */
+#define XHCI_CAPLENGTH 0x00 /* RO capability */
+#define XHCI_RESERVED 0x01 /* Reserved */
+#define XHCI_HCIVERSION 0x02 /* RO Interface version number */
+#define XHCI_HCIVERSION_0_9 0x0090 /* xHCI version 0.9 */
+#define XHCI_HCIVERSION_1_0 0x0100 /* xHCI version 1.0 */
+#define XHCI_HCSPARAMS1 0x04 /* RO structural parameters 1 */
+#define XHCI_HCS1_DEVSLOT_MAX(x)((x) & 0xFF)
+#define XHCI_HCS1_IRQ_MAX(x) (((x) >> 8) & 0x3FF)
+#define XHCI_HCS1_N_PORTS(x) (((x) >> 24) & 0xFF)
+#define XHCI_HCSPARAMS2 0x08 /* RO structural parameters 2 */
+#define XHCI_HCS2_IST(x) ((x) & 0xF)
+#define XHCI_HCS2_ERST_MAX(x) (((x) >> 4) & 0xF)
+#define XHCI_HCS2_SPR(x) (((x) >> 26) & 0x1)
+#define XHCI_HCS2_SPB_MAX(x) ((((x) >> 16) & 0x3E0) | (((x) >> 27) & 0x1F))
+#define XHCI_HCSPARAMS3 0x0C /* RO structural parameters 3 */
+#define XHCI_HCS3_U1_DEL(x) ((x) & 0xFF)
+#define XHCI_HCS3_U2_DEL(x) (((x) >> 16) & 0xFFFF)
+#define XHCI_HCSPARAMS0 0x10 /* RO capability parameters */
+#define XHCI_HCS0_AC64(x) ((x) & 0x1) /* 64-bit capable */
+#define XHCI_HCS0_BNC(x) (((x) >> 1) & 0x1) /* BW negotiation */
+#define XHCI_HCS0_CSZ(x) (((x) >> 2) & 0x1) /* context size */
+#define XHCI_HCS0_PPC(x) (((x) >> 3) & 0x1) /* port power control */
+#define XHCI_HCS0_PIND(x) (((x) >> 4) & 0x1) /* port indicators */
+#define XHCI_HCS0_LHRC(x) (((x) >> 5) & 0x1) /* light HC reset */
+#define XHCI_HCS0_LTC(x) (((x) >> 6) & 0x1) /* latency tolerance msg */
+#define XHCI_HCS0_NSS(x) (((x) >> 7) & 0x1) /* no secondary sid */
+#define XHCI_HCS0_PSA_SZ_MAX(x) (((x) >> 12) & 0xF) /* max pri. stream array size */
+#define XHCI_HCS0_XECP(x) (((x) >> 16) & 0xFFFF) /* extended capabilities pointer */
+#define XHCI_DBOFF 0x14 /* RO doorbell offset */
+#define XHCI_RTSOFF 0x18 /* RO runtime register space offset */
+
+/* XHCI operational registers. Offset given by XHCI_CAPLENGTH register */
+#define XHCI_USBCMD 0x00 /* XHCI command */
+#define XHCI_CMD_RS 0x00000001 /* RW Run/Stop */
+#define XHCI_CMD_HCRST 0x00000002 /* RW Host Controller Reset */
+#define XHCI_CMD_INTE 0x00000004 /* RW Interrupter Enable */
+#define XHCI_CMD_HSEE 0x00000008 /* RW Host System Error Enable */
+#define XHCI_CMD_LHCRST 0x00000080 /* RO/RW Light Host Controller Reset */
+#define XHCI_CMD_CSS 0x00000100 /* RW Controller Save State */
+#define XHCI_CMD_CRS 0x00000200 /* RW Controller Restore State */
+#define XHCI_CMD_EWE 0x00000400 /* RW Enable Wrap Event */
+#define XHCI_CMD_EU3S 0x00000800 /* RW Enable U3 MFINDEX Stop */
+#define XHCI_USBSTS 0x04 /* XHCI status */
+#define XHCI_STS_HCH 0x00000001 /* RO - Host Controller Halted */
+#define XHCI_STS_HSE 0x00000004 /* RW - Host System Error */
+#define XHCI_STS_EINT 0x00000008 /* RW - Event Interrupt */
+#define XHCI_STS_PCD 0x00000010 /* RW - Port Change Detect */
+#define XHCI_STS_SSS 0x00000100 /* RO - Save State Status */
+#define XHCI_STS_RSS 0x00000200 /* RO - Restore State Status */
+#define XHCI_STS_SRE 0x00000400 /* RW - Save/Restore Error */
+#define XHCI_STS_CNR 0x00000800 /* RO - Controller Not Ready */
+#define XHCI_STS_HCE 0x00001000 /* RO - Host Controller Error */
+#define XHCI_PAGESIZE 0x08 /* XHCI page size mask */
+#define XHCI_PAGESIZE_4K 0x00000001 /* 4K Page Size */
+#define XHCI_PAGESIZE_8K 0x00000002 /* 8K Page Size */
+#define XHCI_PAGESIZE_16K 0x00000004 /* 16K Page Size */
+#define XHCI_PAGESIZE_32K 0x00000008 /* 32K Page Size */
+#define XHCI_PAGESIZE_64K 0x00000010 /* 64K Page Size */
+#define XHCI_DNCTRL 0x14 /* XHCI device notification control */
+#define XHCI_DNCTRL_MASK(n) (1U << (n))
+#define XHCI_CRCR_LO 0x18 /* XHCI command ring control */
+#define XHCI_CRCR_LO_RCS 0x00000001 /* RW - consumer cycle state */
+#define XHCI_CRCR_LO_CS 0x00000002 /* RW - command stop */
+#define XHCI_CRCR_LO_CA 0x00000004 /* RW - command abort */
+#define XHCI_CRCR_LO_CRR 0x00000008 /* RW - command ring running */
+#define XHCI_CRCR_LO_MASK 0x0000000F
+#define XHCI_CRCR_HI 0x1C /* XHCI command ring control */
+#define XHCI_DCBAAP_LO 0x30 /* XHCI dev context BA pointer */
+#define XHCI_DCBAAP_HI 0x34 /* XHCI dev context BA pointer */
+#define XHCI_CONFIG 0x38
+#define XHCI_CONFIG_SLOTS_MASK 0x000000FF /* RW - number of device slots enabled */
+
+/* XHCI port status registers */
+#define XHCI_PORTSC(n) (0x3F0 + (0x10 * (n))) /* XHCI port status */
+#define XHCI_PS_CCS 0x00000001 /* RO - current connect status */
+#define XHCI_PS_PED 0x00000002 /* RW - port enabled / disabled */
+#define XHCI_PS_OCA 0x00000008 /* RO - over current active */
+#define XHCI_PS_PR 0x00000010 /* RW - port reset */
+#define XHCI_PS_PLS_GET(x) (((x) >> 5) & 0xF) /* RW - port link state */
+#define XHCI_PS_PLS_SET(x) (((x) & 0xF) << 5) /* RW - port link state */
+#define XHCI_PS_PP 0x00000200 /* RW - port power */
+#define XHCI_PS_SPEED_GET(x) (((x) >> 10) & 0xF) /* RO - port speed */
+#define XHCI_PS_SPEED_FULL 0x1 /* Full Speed USB */
+#define XHCI_PS_SPEED_LOW 0x2 /* Low Speed USB */
+#define XHCI_PS_SPEED_HIGH 0x3 /* High Speed USB */
+#define XHCI_PS_SPEED_SS 0x4 /* Super Speed USB */
+#define XHCI_PS_PIC_GET(x) (((x) >> 14) & 0x3) /* RW - port indicator */
+#define XHCI_PS_PIC_SET(x) (((x) & 0x3) << 14) /* RW - port indicator */
+#define XHCI_PS_LWS 0x00010000 /* RW - port link state write strobe */
+#define XHCI_PS_CSC 0x00020000 /* RW - connect status change */
+#define XHCI_PS_PEC 0x00040000 /* RW - port enable/disable change */
+#define XHCI_PS_WRC 0x00080000 /* RW - warm port reset change */
+#define XHCI_PS_OCC 0x00100000 /* RW - over-current change */
+#define XHCI_PS_PRC 0x00200000 /* RW - port reset change */
+#define XHCI_PS_PLC 0x00400000 /* RW - port link state change */
+#define XHCI_PS_CEC 0x00800000 /* RW - config error change */
+#define XHCI_PS_CAS 0x01000000 /* RO - cold attach status */
+#define XHCI_PS_WCE 0x02000000 /* RW - wake on connect enable */
+#define XHCI_PS_WDE 0x04000000 /* RW - wake on disconnect enable */
+#define XHCI_PS_WOE 0x08000000 /* RW - wake on over-current enable */
+#define XHCI_PS_DR 0x40000000 /* RO - device removable */
+#define XHCI_PS_WPR 0x80000000U /* RW - warm port reset */
+#define XHCI_PS_CLEAR 0x80FF01FFU /* command bits */
+
+#define XHCI_PORTPMSC(n) (0x3F4 + (0x10 * (n))) /* XHCI status and control */
+#define XHCI_PM3_U1TO_GET(x) (((x) >> 0) & 0xFF) /* RW - U1 timeout */
+#define XHCI_PM3_U1TO_SET(x) (((x) & 0xFF) << 0) /* RW - U1 timeout */
+#define XHCI_PM3_U2TO_GET(x) (((x) >> 8) & 0xFF) /* RW - U2 timeout */
+#define XHCI_PM3_U2TO_SET(x) (((x) & 0xFF) << 8) /* RW - U2 timeout */
+#define XHCI_PM3_FLA 0x00010000 /* RW - Force Link PM Accept */
+#define XHCI_PM2_L1S_GET(x) (((x) >> 0) & 0x7) /* RO - L1 status */
+#define XHCI_PM2_RWE 0x00000008 /* RW - remote wakup enable */
+#define XHCI_PM2_HIRD_GET(x) (((x) >> 4) & 0xF) /* RW - host initiated resume duration */
+#define XHCI_PM2_HIRD_SET(x) (((x) & 0xF) << 4) /* RW - host initiated resume duration */
+#define XHCI_PM2_L1SLOT_GET(x) (((x) >> 8) & 0xFF) /* RW - L1 device slot */
+#define XHCI_PM2_L1SLOT_SET(x) (((x) & 0xFF) << 8) /* RW - L1 device slot */
+#define XHCI_PM2_HLE 0x00010000 /* RW - hardware LPM enable */
+#define XHCI_PORTLI(n) (0x3F8 + (0x10 * (n))) /* XHCI port link info */
+#define XHCI_PLI3_ERR_GET(x) (((x) >> 0) & 0xFFFF) /* RO - port link errors */
+#define XHCI_PORTRSV(n) (0x3FC + (0x10 * (n))) /* XHCI port reserved */
+
+/* XHCI runtime registers. Offset given by XHCI_CAPLENGTH + XHCI_RTSOFF registers */
+#define XHCI_MFINDEX 0x0000 /* RO - microframe index */
+#define XHCI_MFINDEX_GET(x) ((x) & 0x3FFF)
+#define XHCI_IMAN(n) (0x0020 + (0x20 * (n))) /* XHCI interrupt management */
+#define XHCI_IMAN_INTR_PEND 0x00000001 /* RW - interrupt pending */
+#define XHCI_IMAN_INTR_ENA 0x00000002 /* RW - interrupt enable */
+#define XHCI_IMOD(n) (0x0024 + (0x20 * (n))) /* XHCI interrupt moderation */
+#define XHCI_IMOD_IVAL_GET(x) (((x) >> 0) & 0xFFFF) /* 250ns unit */
+#define XHCI_IMOD_IVAL_SET(x) (((x) & 0xFFFF) << 0) /* 250ns unit */
+#define XHCI_IMOD_ICNT_GET(x) (((x) >> 16) & 0xFFFF) /* 250ns unit */
+#define XHCI_IMOD_ICNT_SET(x) (((x) & 0xFFFF) << 16) /* 250ns unit */
+#define XHCI_IMOD_DEFAULT 0x000001F4U /* 8000 IRQs/second */
+#define XHCI_IMOD_DEFAULT_LP 0x000003F8U /* 4000 IRQs/second - LynxPoint */
+#define XHCI_ERSTSZ(n) (0x0028 + (0x20 * (n))) /* XHCI event ring segment table size */
+#define XHCI_ERSTS_GET(x) ((x) & 0xFFFF)
+#define XHCI_ERSTS_SET(x) ((x) & 0xFFFF)
+#define XHCI_ERSTBA_LO(n) (0x0030 + (0x20 * (n))) /* XHCI event ring segment table BA */
+#define XHCI_ERSTBA_HI(n) (0x0034 + (0x20 * (n))) /* XHCI event ring segment table BA */
+#define XHCI_ERDP_LO(n) (0x0038 + (0x20 * (n))) /* XHCI event ring dequeue pointer */
+#define XHCI_ERDP_LO_SINDEX(x) ((x) & 0x7) /* RO - dequeue segment index */
+#define XHCI_ERDP_LO_BUSY 0x00000008 /* RW - event handler busy */
+#define XHCI_ERDP_HI(n) (0x003C + (0x20 * (n))) /* XHCI event ring dequeue pointer */
+
+/* XHCI doorbell registers. Offset given by XHCI_CAPLENGTH + XHCI_DBOFF registers */
+#define XHCI_DOORBELL(n) (0x0000 + (4 * (n)))
+#define XHCI_DB_TARGET_GET(x) ((x) & 0xFF) /* RW - doorbell target */
+#define XHCI_DB_TARGET_SET(x) ((x) & 0xFF) /* RW - doorbell target */
+#define XHCI_DB_SID_GET(x) (((x) >> 16) & 0xFFFF) /* RW - doorbell stream ID */
+#define XHCI_DB_SID_SET(x) (((x) & 0xFFFF) << 16) /* RW - doorbell stream ID */
+
+/* XHCI legacy support */
+#define XHCI_XECP_ID(x) ((x) & 0xFF)
+#define XHCI_XECP_NEXT(x) (((x) >> 8) & 0xFF)
+#define XHCI_XECP_BIOS_SEM 0x0002
+#define XHCI_XECP_OS_SEM 0x0003
+
+/* XHCI capability ID's */
+#define XHCI_ID_USB_LEGACY 0x0001
+#define XHCI_ID_PROTOCOLS 0x0002
+#define XHCI_ID_POWER_MGMT 0x0003
+#define XHCI_ID_VIRTUALIZATION 0x0004
+#define XHCI_ID_MSG_IRQ 0x0005
+#define XHCI_ID_USB_LOCAL_MEM 0x0006
+
+/* XHCI register R/W wrappers */
+#define XREAD1(sc, what, a) \
+ bus_space_read_1((sc)->sc_io_tag, (sc)->sc_io_hdl, \
+ (a) + (sc)->sc_##what##_off)
+#define XREAD2(sc, what, a) \
+ bus_space_read_2((sc)->sc_io_tag, (sc)->sc_io_hdl, \
+ (a) + (sc)->sc_##what##_off)
+#define XREAD4(sc, what, a) \
+ bus_space_read_4((sc)->sc_io_tag, (sc)->sc_io_hdl, \
+ (a) + (sc)->sc_##what##_off)
+#define XWRITE1(sc, what, a, x) \
+ bus_space_write_1((sc)->sc_io_tag, (sc)->sc_io_hdl, \
+ (a) + (sc)->sc_##what##_off, (x))
+#define XWRITE2(sc, what, a, x) \
+ bus_space_write_2((sc)->sc_io_tag, (sc)->sc_io_hdl, \
+ (a) + (sc)->sc_##what##_off, (x))
+#define XWRITE4(sc, what, a, x) \
+ bus_space_write_4((sc)->sc_io_tag, (sc)->sc_io_hdl, \
+ (a) + (sc)->sc_##what##_off, (x))
+
+#endif /* _XHCIREG_H_ */
diff --git a/sys/dev/usb/controller/xlnx_dwc3.c b/sys/dev/usb/controller/xlnx_dwc3.c
new file mode 100644
index 000000000000..c450734e4225
--- /dev/null
+++ b/sys/dev/usb/controller/xlnx_dwc3.c
@@ -0,0 +1,148 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG
+ *
+ * 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.
+ */
+
+/*
+ * Xilinx DWC3 glue
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/rman.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/gpio.h>
+#include <machine/bus.h>
+
+#include <dev/fdt/simplebus.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/ofw/ofw_subr.h>
+
+#include <dev/clk/clk.h>
+#include <dev/hwreset/hwreset.h>
+#include <dev/phy/phy_usb.h>
+#include <dev/syscon/syscon.h>
+
+static struct ofw_compat_data compat_data[] = {
+ { "xlnx,zynqmp-dwc3", 1 },
+ { NULL, 0 }
+};
+
+struct xlnx_dwc3_softc {
+ struct simplebus_softc sc;
+ device_t dev;
+ hwreset_t rst_crst;
+ hwreset_t rst_hibrst;
+ hwreset_t rst_apbrst;
+};
+
+static int
+xlnx_dwc3_probe(device_t dev)
+{
+ phandle_t node;
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
+ return (ENXIO);
+
+ /* Binding says that we need a child node for the actual dwc3 controller */
+ node = ofw_bus_get_node(dev);
+ if (OF_child(node) <= 0)
+ return (ENXIO);
+
+ device_set_desc(dev, "Xilinx ZYNQMP DWC3");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+xlnx_dwc3_attach(device_t dev)
+{
+ struct xlnx_dwc3_softc *sc;
+ device_t cdev;
+ phandle_t node, child;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ node = ofw_bus_get_node(dev);
+
+ /*
+ * Put module out of reset
+ * Based on the bindings this should be mandatory to have
+ * but reality shows that they aren't always there.
+ * This is the case on the DTB in the AVnet Ultra96
+ */
+ if (hwreset_get_by_ofw_name(dev, node, "usb_crst", &sc->rst_crst) == 0) {
+ if (hwreset_deassert(sc->rst_crst) != 0) {
+ device_printf(dev, "Cannot deassert reset\n");
+ return (ENXIO);
+ }
+ }
+ if (hwreset_get_by_ofw_name(dev, node, "usb_hibrst", &sc->rst_hibrst) == 0) {
+ if (hwreset_deassert(sc->rst_hibrst) != 0) {
+ device_printf(dev, "Cannot deassert reset\n");
+ return (ENXIO);
+ }
+ }
+ if (hwreset_get_by_ofw_name(dev, node, "usb_apbrst", &sc->rst_apbrst) == 0) {
+ if (hwreset_deassert(sc->rst_apbrst) != 0) {
+ device_printf(dev, "Cannot deassert reset\n");
+ return (ENXIO);
+ }
+ }
+
+ simplebus_init(dev, node);
+ if (simplebus_fill_ranges(node, &sc->sc) < 0) {
+ device_printf(dev, "could not get ranges\n");
+ return (ENXIO);
+ }
+
+ for (child = OF_child(node); child > 0; child = OF_peer(child)) {
+ cdev = simplebus_add_device(dev, child, 0, NULL, -1, NULL);
+ if (cdev != NULL)
+ device_probe_and_attach(cdev);
+ }
+
+ bus_attach_children(dev);
+ return (0);
+}
+
+static device_method_t xlnx_dwc3_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, xlnx_dwc3_probe),
+ DEVMETHOD(device_attach, xlnx_dwc3_attach),
+
+ DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(xlnx_dwc3, xlnx_dwc3_driver, xlnx_dwc3_methods,
+ sizeof(struct xlnx_dwc3_softc), simplebus_driver);
+DRIVER_MODULE(xlnx_dwc3, simplebus, xlnx_dwc3_driver, 0, 0);
diff --git a/sys/dev/usb/gadget/g_audio.c b/sys/dev/usb/gadget/g_audio.c
new file mode 100644
index 000000000000..3e84f13550f5
--- /dev/null
+++ b/sys/dev/usb/gadget/g_audio.c
@@ -0,0 +1,608 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * USB audio specs: http://www.usb.org/developers/devclass_docs/audio10.pdf
+ * http://www.usb.org/developers/devclass_docs/frmts10.pdf
+ * http://www.usb.org/developers/devclass_docs/termt10.pdf
+ */
+
+#include <sys/param.h>
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/linker_set.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_cdc.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include "usb_if.h"
+
+#define USB_DEBUG_VAR g_audio_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/gadget/g_audio.h>
+
+enum {
+ G_AUDIO_ISOC0_RD,
+ G_AUDIO_ISOC1_RD,
+ G_AUDIO_ISOC0_WR,
+ G_AUDIO_ISOC1_WR,
+ G_AUDIO_N_TRANSFER,
+};
+
+struct g_audio_softc {
+ struct mtx sc_mtx;
+ struct usb_callout sc_callout;
+ struct usb_callout sc_watchdog;
+ struct usb_xfer *sc_xfer[G_AUDIO_N_TRANSFER];
+
+ int sc_mode;
+ int sc_pattern_len;
+ int sc_throughput;
+ int sc_tx_interval;
+ int sc_state;
+ int sc_noise_rem;
+
+ int8_t sc_pattern[G_AUDIO_MAX_STRLEN];
+
+ uint16_t sc_data_len[2][G_AUDIO_FRAMES];
+
+ int16_t sc_data_buf[2][G_AUDIO_BUFSIZE / 2];
+
+ uint8_t sc_volume_setting[32];
+ uint8_t sc_volume_limit[32];
+ uint8_t sc_sample_rate[32];
+};
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, g_audio, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB audio gadget");
+
+#ifdef USB_DEBUG
+static int g_audio_debug = 0;
+
+SYSCTL_INT(_hw_usb_g_audio, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &g_audio_debug, 0, "Debug level");
+#endif
+
+static int g_audio_mode = 0;
+
+SYSCTL_INT(_hw_usb_g_audio, OID_AUTO, mode, CTLFLAG_RWTUN,
+ &g_audio_mode, 0, "Mode selection");
+
+static int g_audio_pattern_interval = 1000;
+
+SYSCTL_INT(_hw_usb_g_audio, OID_AUTO, pattern_interval, CTLFLAG_RWTUN,
+ &g_audio_pattern_interval, 0, "Pattern interval in milliseconds");
+
+static char g_audio_pattern_data[G_AUDIO_MAX_STRLEN];
+
+SYSCTL_STRING(_hw_usb_g_audio, OID_AUTO, pattern, CTLFLAG_RW,
+ &g_audio_pattern_data, sizeof(g_audio_pattern_data), "Data pattern");
+
+static int g_audio_throughput;
+
+SYSCTL_INT(_hw_usb_g_audio, OID_AUTO, throughput, CTLFLAG_RD,
+ &g_audio_throughput, sizeof(g_audio_throughput), "Throughput in bytes per second");
+
+static device_probe_t g_audio_probe;
+static device_attach_t g_audio_attach;
+static device_detach_t g_audio_detach;
+static usb_handle_request_t g_audio_handle_request;
+
+static usb_callback_t g_audio_isoc_read_callback;
+static usb_callback_t g_audio_isoc_write_callback;
+
+static void g_audio_watchdog(void *arg);
+static void g_audio_timeout(void *arg);
+
+static device_method_t g_audio_methods[] = {
+ /* USB interface */
+ DEVMETHOD(usb_handle_request, g_audio_handle_request),
+
+ /* Device interface */
+ DEVMETHOD(device_probe, g_audio_probe),
+ DEVMETHOD(device_attach, g_audio_attach),
+ DEVMETHOD(device_detach, g_audio_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t g_audio_driver = {
+ .name = "g_audio",
+ .methods = g_audio_methods,
+ .size = sizeof(struct g_audio_softc),
+};
+
+DRIVER_MODULE(g_audio, uhub, g_audio_driver, 0, 0);
+MODULE_DEPEND(g_audio, usb, 1, 1, 1);
+
+static const struct usb_config g_audio_config[G_AUDIO_N_TRANSFER] = {
+ [G_AUDIO_ISOC0_RD] = {
+ .type = UE_ISOCHRONOUS,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_RX,
+ .flags = {.ext_buffer = 1,.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = G_AUDIO_BUFSIZE,
+ .callback = &g_audio_isoc_read_callback,
+ .frames = G_AUDIO_FRAMES,
+ .usb_mode = USB_MODE_DEVICE,
+ .if_index = 1,
+ },
+
+ [G_AUDIO_ISOC1_RD] = {
+ .type = UE_ISOCHRONOUS,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_RX,
+ .flags = {.ext_buffer = 1,.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = G_AUDIO_BUFSIZE,
+ .callback = &g_audio_isoc_read_callback,
+ .frames = G_AUDIO_FRAMES,
+ .usb_mode = USB_MODE_DEVICE,
+ .if_index = 1,
+ },
+
+ [G_AUDIO_ISOC0_WR] = {
+ .type = UE_ISOCHRONOUS,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .flags = {.ext_buffer = 1,.pipe_bof = 1,},
+ .bufsize = G_AUDIO_BUFSIZE,
+ .callback = &g_audio_isoc_write_callback,
+ .frames = G_AUDIO_FRAMES,
+ .usb_mode = USB_MODE_DEVICE,
+ .if_index = 2,
+ },
+
+ [G_AUDIO_ISOC1_WR] = {
+ .type = UE_ISOCHRONOUS,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .flags = {.ext_buffer = 1,.pipe_bof = 1,},
+ .bufsize = G_AUDIO_BUFSIZE,
+ .callback = &g_audio_isoc_write_callback,
+ .frames = G_AUDIO_FRAMES,
+ .usb_mode = USB_MODE_DEVICE,
+ .if_index = 2,
+ },
+};
+
+static void
+g_audio_timeout_reset(struct g_audio_softc *sc)
+{
+ int i = g_audio_pattern_interval;
+
+ sc->sc_tx_interval = i;
+
+ if (i <= 0)
+ i = 1;
+ else if (i > 1023)
+ i = 1023;
+
+ i = USB_MS_TO_TICKS(i);
+
+ usb_callout_reset(&sc->sc_callout, i, &g_audio_timeout, sc);
+}
+
+static void
+g_audio_timeout(void *arg)
+{
+ struct g_audio_softc *sc = arg;
+
+ sc->sc_mode = g_audio_mode;
+
+ memcpy(sc->sc_pattern, g_audio_pattern_data, sizeof(sc->sc_pattern));
+
+ sc->sc_pattern[G_AUDIO_MAX_STRLEN - 1] = 0;
+
+ sc->sc_pattern_len = strlen(sc->sc_pattern);
+
+ if (sc->sc_mode != G_AUDIO_MODE_LOOP) {
+ usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC0_WR]);
+ usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC1_WR]);
+ }
+ g_audio_timeout_reset(sc);
+}
+
+static void
+g_audio_watchdog_reset(struct g_audio_softc *sc)
+{
+ usb_callout_reset(&sc->sc_watchdog, hz, &g_audio_watchdog, sc);
+}
+
+static void
+g_audio_watchdog(void *arg)
+{
+ struct g_audio_softc *sc = arg;
+ int i;
+
+ i = sc->sc_throughput;
+
+ sc->sc_throughput = 0;
+
+ g_audio_throughput = i;
+
+ g_audio_watchdog_reset(sc);
+}
+
+static int
+g_audio_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ DPRINTFN(11, "\n");
+
+ if (uaa->usb_mode != USB_MODE_DEVICE)
+ return (ENXIO);
+
+ if ((uaa->info.bInterfaceClass == UICLASS_AUDIO) &&
+ (uaa->info.bInterfaceSubClass == UISUBCLASS_AUDIOCONTROL))
+ return (0);
+
+ return (ENXIO);
+}
+
+static int
+g_audio_attach(device_t dev)
+{
+ struct g_audio_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ int error;
+ int i;
+ uint8_t iface_index[3];
+
+ DPRINTFN(11, "\n");
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, "g_audio", NULL, MTX_DEF);
+
+ usb_callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0);
+ usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0);
+
+ sc->sc_mode = G_AUDIO_MODE_SILENT;
+
+ sc->sc_noise_rem = 1;
+
+ for (i = 0; i != G_AUDIO_FRAMES; i++) {
+ sc->sc_data_len[0][i] = G_AUDIO_BUFSIZE / G_AUDIO_FRAMES;
+ sc->sc_data_len[1][i] = G_AUDIO_BUFSIZE / G_AUDIO_FRAMES;
+ }
+
+ iface_index[0] = uaa->info.bIfaceIndex;
+ iface_index[1] = uaa->info.bIfaceIndex + 1;
+ iface_index[2] = uaa->info.bIfaceIndex + 2;
+
+ error = usbd_set_alt_interface_index(uaa->device, iface_index[1], 1);
+ if (error) {
+ DPRINTF("alt iface setting error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+ error = usbd_set_alt_interface_index(uaa->device, iface_index[2], 1);
+ if (error) {
+ DPRINTF("alt iface setting error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+ error = usbd_transfer_setup(uaa->device,
+ iface_index, sc->sc_xfer, g_audio_config,
+ G_AUDIO_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error) {
+ DPRINTF("error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+ usbd_set_parent_iface(uaa->device, iface_index[1], iface_index[0]);
+ usbd_set_parent_iface(uaa->device, iface_index[2], iface_index[0]);
+
+ mtx_lock(&sc->sc_mtx);
+
+ usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC0_RD]);
+ usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC1_RD]);
+
+ usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC0_WR]);
+ usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC1_WR]);
+
+ g_audio_timeout_reset(sc);
+
+ g_audio_watchdog_reset(sc);
+
+ mtx_unlock(&sc->sc_mtx);
+
+ return (0); /* success */
+
+detach:
+ g_audio_detach(dev);
+
+ return (ENXIO); /* error */
+}
+
+static int
+g_audio_detach(device_t dev)
+{
+ struct g_audio_softc *sc = device_get_softc(dev);
+
+ DPRINTF("\n");
+
+ mtx_lock(&sc->sc_mtx);
+ usb_callout_stop(&sc->sc_callout);
+ usb_callout_stop(&sc->sc_watchdog);
+ mtx_unlock(&sc->sc_mtx);
+
+ usbd_transfer_unsetup(sc->sc_xfer, G_AUDIO_N_TRANSFER);
+
+ usb_callout_drain(&sc->sc_callout);
+ usb_callout_drain(&sc->sc_watchdog);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static int32_t
+g_noise(struct g_audio_softc *sc)
+{
+ uint32_t temp;
+ const uint32_t prime = 0xFFFF1D;
+
+ if (sc->sc_noise_rem & 1) {
+ sc->sc_noise_rem += prime;
+ }
+ sc->sc_noise_rem /= 2;
+
+ temp = sc->sc_noise_rem;
+
+ /* unsigned to signed conversion */
+
+ temp ^= 0x800000;
+ if (temp & 0x800000) {
+ temp |= (-0x800000);
+ }
+ return temp;
+}
+
+static void
+g_audio_make_samples(struct g_audio_softc *sc, int16_t *ptr, int samples)
+{
+ int i;
+ int j;
+
+ for (i = 0; i != samples; i++) {
+ j = g_noise(sc);
+
+ if ((sc->sc_state < 0) || (sc->sc_state >= sc->sc_pattern_len))
+ sc->sc_state = 0;
+
+ if (sc->sc_pattern_len != 0) {
+ j = (j * sc->sc_pattern[sc->sc_state]) >> 16;
+ sc->sc_state++;
+ }
+ *ptr++ = j / 256;
+ *ptr++ = j / 256;
+ }
+}
+
+static void
+g_audio_isoc_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct g_audio_softc *sc = usbd_xfer_softc(xfer);
+ int actlen;
+ int aframes;
+ int nr = (xfer == sc->sc_xfer[G_AUDIO_ISOC0_WR]) ? 0 : 1;
+ int16_t *ptr;
+ int i;
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ DPRINTF("st=%d aframes=%d actlen=%d bytes\n",
+ USB_GET_STATE(xfer), aframes, actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ sc->sc_throughput += actlen;
+
+ if (sc->sc_mode == G_AUDIO_MODE_LOOP)
+ break; /* sync with RX */
+
+ case USB_ST_SETUP:
+tr_setup:
+
+ ptr = sc->sc_data_buf[nr];
+
+ if (sc->sc_mode == G_AUDIO_MODE_PATTERN) {
+ for (i = 0; i != G_AUDIO_FRAMES; i++) {
+ usbd_xfer_set_frame_data(xfer, i, ptr, sc->sc_data_len[nr][i]);
+
+ g_audio_make_samples(sc, ptr, (G_AUDIO_BUFSIZE / G_AUDIO_FRAMES) / 2);
+
+ ptr += (G_AUDIO_BUFSIZE / G_AUDIO_FRAMES) / 2;
+ }
+ } else if (sc->sc_mode == G_AUDIO_MODE_LOOP) {
+ for (i = 0; i != G_AUDIO_FRAMES; i++) {
+ usbd_xfer_set_frame_data(xfer, i, ptr, sc->sc_data_len[nr][i] & ~3);
+
+ g_audio_make_samples(sc, ptr, sc->sc_data_len[nr][i] / 4);
+
+ ptr += (G_AUDIO_BUFSIZE / G_AUDIO_FRAMES) / 2;
+ }
+ }
+ break;
+
+ default: /* Error */
+ DPRINTF("error=%s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+g_audio_isoc_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct g_audio_softc *sc = usbd_xfer_softc(xfer);
+ int actlen;
+ int aframes;
+ int nr = (xfer == sc->sc_xfer[G_AUDIO_ISOC0_RD]) ? 0 : 1;
+ int16_t *ptr;
+ int i;
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ DPRINTF("st=%d aframes=%d actlen=%d bytes\n",
+ USB_GET_STATE(xfer), aframes, actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ sc->sc_throughput += actlen;
+
+ for (i = 0; i != G_AUDIO_FRAMES; i++) {
+ sc->sc_data_len[nr][i] = usbd_xfer_frame_len(xfer, i);
+ }
+
+ usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC0_WR]);
+ usbd_transfer_start(sc->sc_xfer[G_AUDIO_ISOC1_WR]);
+
+ break;
+
+ case USB_ST_SETUP:
+tr_setup:
+ ptr = sc->sc_data_buf[nr];
+
+ for (i = 0; i != G_AUDIO_FRAMES; i++) {
+ usbd_xfer_set_frame_data(xfer, i, ptr,
+ G_AUDIO_BUFSIZE / G_AUDIO_FRAMES);
+
+ ptr += (G_AUDIO_BUFSIZE / G_AUDIO_FRAMES) / 2;
+ }
+
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ DPRINTF("error=%s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static int
+g_audio_handle_request(device_t dev,
+ const void *preq, void **pptr, uint16_t *plen,
+ uint16_t offset, uint8_t *pstate)
+{
+ struct g_audio_softc *sc = device_get_softc(dev);
+ const struct usb_device_request *req = preq;
+ uint8_t is_complete = *pstate;
+
+ if (!is_complete) {
+ if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) &&
+ (req->bRequest == 0x82 /* get min */ )) {
+ if (offset == 0) {
+ USETW(sc->sc_volume_limit, 0);
+ *plen = 2;
+ *pptr = &sc->sc_volume_limit;
+ } else {
+ *plen = 0;
+ }
+ return (0);
+ } else if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) &&
+ (req->bRequest == 0x83 /* get max */ )) {
+ if (offset == 0) {
+ USETW(sc->sc_volume_limit, 0x2000);
+ *plen = 2;
+ *pptr = &sc->sc_volume_limit;
+ } else {
+ *plen = 0;
+ }
+ return (0);
+ } else if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) &&
+ (req->bRequest == 0x84 /* get residue */ )) {
+ if (offset == 0) {
+ USETW(sc->sc_volume_limit, 1);
+ *plen = 2;
+ *pptr = &sc->sc_volume_limit;
+ } else {
+ *plen = 0;
+ }
+ return (0);
+ } else if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) &&
+ (req->bRequest == 0x81 /* get value */ )) {
+ if (offset == 0) {
+ USETW(sc->sc_volume_setting, 0x2000);
+ *plen = sizeof(sc->sc_volume_setting);
+ *pptr = &sc->sc_volume_setting;
+ } else {
+ *plen = 0;
+ }
+ return (0);
+ } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->bRequest == 0x01 /* set value */ )) {
+ if (offset == 0) {
+ *plen = sizeof(sc->sc_volume_setting);
+ *pptr = &sc->sc_volume_setting;
+ } else {
+ *plen = 0;
+ }
+ return (0);
+ } else if ((req->bmRequestType == UT_WRITE_CLASS_ENDPOINT) &&
+ (req->bRequest == 0x01 /* set value */ )) {
+ if (offset == 0) {
+ *plen = sizeof(sc->sc_sample_rate);
+ *pptr = &sc->sc_sample_rate;
+ } else {
+ *plen = 0;
+ }
+ return (0);
+ }
+ }
+ return (ENXIO); /* use builtin handler */
+}
diff --git a/sys/dev/usb/gadget/g_audio.h b/sys/dev/usb/gadget/g_audio.h
new file mode 100644
index 000000000000..30a12d82b88d
--- /dev/null
+++ b/sys/dev/usb/gadget/g_audio.h
@@ -0,0 +1,41 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _G_AUDIO_H_
+#define _G_AUDIO_H_
+
+#define G_AUDIO_MAX_STRLEN 32 /* chars */
+#define G_AUDIO_FRAMES 8
+#define G_AUDIO_BUFSIZE (G_AUDIO_FRAMES * 2 * 2 * 48) /* units */
+
+#define G_AUDIO_MODE_SILENT 0
+#define G_AUDIO_MODE_DUMP 1
+#define G_AUDIO_MODE_LOOP 2
+#define G_AUDIO_MODE_PATTERN 3
+#define G_AUDIO_MODE_MAX 4
+
+#endif /* _G_AUDIO_H_ */
diff --git a/sys/dev/usb/gadget/g_keyboard.c b/sys/dev/usb/gadget/g_keyboard.c
new file mode 100644
index 000000000000..0ba5a75ed912
--- /dev/null
+++ b/sys/dev/usb/gadget/g_keyboard.c
@@ -0,0 +1,409 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf
+ */
+
+#include <sys/param.h>
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/linker_set.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include "usb_if.h"
+
+#define USB_DEBUG_VAR g_keyboard_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/gadget/g_keyboard.h>
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, g_keyboard,
+ CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB keyboard gadget");
+
+#ifdef USB_DEBUG
+static int g_keyboard_debug = 0;
+
+SYSCTL_INT(_hw_usb_g_keyboard, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &g_keyboard_debug, 0, "Debug level");
+#endif
+
+static int g_keyboard_mode = 0;
+
+SYSCTL_INT(_hw_usb_g_keyboard, OID_AUTO, mode, CTLFLAG_RWTUN,
+ &g_keyboard_mode, 0, "Mode selection");
+
+static int g_keyboard_key_press_interval = 1000;
+
+SYSCTL_INT(_hw_usb_g_keyboard, OID_AUTO, key_press_interval, CTLFLAG_RWTUN,
+ &g_keyboard_key_press_interval, 0, "Key Press Interval in milliseconds");
+
+static char g_keyboard_key_press_pattern[G_KEYBOARD_MAX_STRLEN];
+
+SYSCTL_STRING(_hw_usb_g_keyboard, OID_AUTO, key_press_pattern, CTLFLAG_RW,
+ g_keyboard_key_press_pattern, sizeof(g_keyboard_key_press_pattern),
+ "Key Press Patterns");
+
+#define UPROTO_BOOT_KEYBOARD 1
+
+#define G_KEYBOARD_NMOD 8 /* units */
+#define G_KEYBOARD_NKEYCODE 6 /* units */
+
+struct g_keyboard_data {
+ uint8_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
+ uint8_t reserved;
+ uint8_t keycode[G_KEYBOARD_NKEYCODE];
+};
+
+enum {
+ G_KEYBOARD_INTR_DT,
+ G_KEYBOARD_N_TRANSFER,
+};
+
+struct g_keyboard_softc {
+ struct mtx sc_mtx;
+ struct usb_callout sc_callout;
+ struct g_keyboard_data sc_data[2];
+ struct usb_xfer *sc_xfer[G_KEYBOARD_N_TRANSFER];
+
+ int sc_mode;
+ int sc_state;
+ int sc_pattern_len;
+
+ char sc_pattern[G_KEYBOARD_MAX_STRLEN];
+
+ uint8_t sc_led_state[4];
+};
+
+static device_probe_t g_keyboard_probe;
+static device_attach_t g_keyboard_attach;
+static device_detach_t g_keyboard_detach;
+static usb_handle_request_t g_keyboard_handle_request;
+static usb_callback_t g_keyboard_intr_callback;
+
+static device_method_t g_keyboard_methods[] = {
+ /* USB interface */
+ DEVMETHOD(usb_handle_request, g_keyboard_handle_request),
+
+ /* Device interface */
+ DEVMETHOD(device_probe, g_keyboard_probe),
+ DEVMETHOD(device_attach, g_keyboard_attach),
+ DEVMETHOD(device_detach, g_keyboard_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t g_keyboard_driver = {
+ .name = "g_keyboard",
+ .methods = g_keyboard_methods,
+ .size = sizeof(struct g_keyboard_softc),
+};
+
+DRIVER_MODULE(g_keyboard, uhub, g_keyboard_driver, 0, 0);
+MODULE_DEPEND(g_keyboard, usb, 1, 1, 1);
+
+static const struct usb_config g_keyboard_config[G_KEYBOARD_N_TRANSFER] = {
+ [G_KEYBOARD_INTR_DT] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.ext_buffer = 1,.pipe_bof = 1,},
+ .bufsize = sizeof(struct g_keyboard_data),
+ .callback = &g_keyboard_intr_callback,
+ .frames = 2,
+ .usb_mode = USB_MODE_DEVICE,
+ },
+};
+
+static void g_keyboard_timeout(void *arg);
+
+static void
+g_keyboard_timeout_reset(struct g_keyboard_softc *sc)
+{
+ int i = g_keyboard_key_press_interval;
+
+ if (i <= 0)
+ i = 1;
+ else if (i > 1023)
+ i = 1023;
+
+ i = USB_MS_TO_TICKS(i);
+
+ usb_callout_reset(&sc->sc_callout, i, &g_keyboard_timeout, sc);
+}
+
+static void
+g_keyboard_timeout(void *arg)
+{
+ struct g_keyboard_softc *sc = arg;
+
+ sc->sc_mode = g_keyboard_mode;
+
+ memcpy(sc->sc_pattern, g_keyboard_key_press_pattern, sizeof(sc->sc_pattern));
+
+ sc->sc_pattern[G_KEYBOARD_MAX_STRLEN - 1] = 0;
+
+ sc->sc_pattern_len = strlen(sc->sc_pattern);
+
+ DPRINTFN(11, "Timeout %p\n", sc->sc_xfer[G_KEYBOARD_INTR_DT]);
+
+ usbd_transfer_start(sc->sc_xfer[G_KEYBOARD_INTR_DT]);
+
+ g_keyboard_timeout_reset(sc);
+}
+
+static int
+g_keyboard_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ DPRINTFN(11, "\n");
+
+ if (uaa->usb_mode != USB_MODE_DEVICE)
+ return (ENXIO);
+
+ if ((uaa->info.bInterfaceClass == UICLASS_HID) &&
+ (uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) &&
+ (uaa->info.bInterfaceProtocol == UPROTO_BOOT_KEYBOARD))
+ return (0);
+
+ return (ENXIO);
+}
+
+static int
+g_keyboard_attach(device_t dev)
+{
+ struct g_keyboard_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ int error;
+
+ DPRINTFN(11, "\n");
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, "g_keyboard", NULL, MTX_DEF);
+
+ usb_callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0);
+
+ sc->sc_mode = G_KEYBOARD_MODE_SILENT;
+
+ error = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer, g_keyboard_config,
+ G_KEYBOARD_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error) {
+ DPRINTF("error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+ mtx_lock(&sc->sc_mtx);
+ g_keyboard_timeout_reset(sc);
+ mtx_unlock(&sc->sc_mtx);
+
+ return (0); /* success */
+
+detach:
+ g_keyboard_detach(dev);
+
+ return (ENXIO); /* error */
+}
+
+static int
+g_keyboard_detach(device_t dev)
+{
+ struct g_keyboard_softc *sc = device_get_softc(dev);
+
+ DPRINTF("\n");
+
+ mtx_lock(&sc->sc_mtx);
+ usb_callout_stop(&sc->sc_callout);
+ mtx_unlock(&sc->sc_mtx);
+
+ usbd_transfer_unsetup(sc->sc_xfer, G_KEYBOARD_N_TRANSFER);
+
+ usb_callout_drain(&sc->sc_callout);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static uint8_t
+g_keyboard_get_keycode(struct g_keyboard_softc *sc, int index)
+{
+ int key;
+ int mod = sc->sc_pattern_len;
+
+ if (mod == 0)
+ index = 0;
+ else
+ index %= mod;
+
+ if ((index >= 0) && (index < sc->sc_pattern_len))
+ key = sc->sc_pattern[index];
+ else
+ key = 'a';
+
+ if (key >= 'a' && key <= 'z')
+ return (key - 'a' + 0x04);
+ else
+ return (0x04);
+}
+
+static void
+g_keyboard_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct g_keyboard_softc *sc = usbd_xfer_softc(xfer);
+ int actlen;
+ int aframes;
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ DPRINTF("st=%d aframes=%d actlen=%d bytes\n",
+ USB_GET_STATE(xfer), aframes, actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ break;
+
+ case USB_ST_SETUP:
+tr_setup:
+ if (sc->sc_mode == G_KEYBOARD_MODE_SILENT) {
+ memset(&sc->sc_data, 0, sizeof(sc->sc_data));
+ usbd_xfer_set_frame_data(xfer, 0, &sc->sc_data[0], sizeof(sc->sc_data[0]));
+ usbd_xfer_set_frame_data(xfer, 1, &sc->sc_data[1], sizeof(sc->sc_data[1]));
+ usbd_xfer_set_frames(xfer, 2);
+ usbd_transfer_submit(xfer);
+
+ } else if (sc->sc_mode == G_KEYBOARD_MODE_PATTERN) {
+ memset(&sc->sc_data, 0, sizeof(sc->sc_data));
+
+ if ((sc->sc_state < 0) || (sc->sc_state >= G_KEYBOARD_MAX_STRLEN))
+ sc->sc_state = 0;
+
+ switch (sc->sc_state % 6) {
+ case 0:
+ sc->sc_data[0].keycode[0] =
+ g_keyboard_get_keycode(sc, sc->sc_state + 0);
+ case 1:
+ sc->sc_data[0].keycode[1] =
+ g_keyboard_get_keycode(sc, sc->sc_state + 1);
+ case 2:
+ sc->sc_data[0].keycode[2] =
+ g_keyboard_get_keycode(sc, sc->sc_state + 2);
+ case 3:
+ sc->sc_data[0].keycode[3] =
+ g_keyboard_get_keycode(sc, sc->sc_state + 3);
+ case 4:
+ sc->sc_data[0].keycode[4] =
+ g_keyboard_get_keycode(sc, sc->sc_state + 4);
+ default:
+ sc->sc_data[0].keycode[5] =
+ g_keyboard_get_keycode(sc, sc->sc_state + 5);
+ }
+
+ sc->sc_state++;
+
+ usbd_xfer_set_frame_data(xfer, 0, &sc->sc_data[0], sizeof(sc->sc_data[0]));
+ usbd_xfer_set_frame_data(xfer, 1, &sc->sc_data[1], sizeof(sc->sc_data[1]));
+ usbd_xfer_set_frames(xfer, 2);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ DPRINTF("error=%s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static int
+g_keyboard_handle_request(device_t dev,
+ const void *preq, void **pptr, uint16_t *plen,
+ uint16_t offset, uint8_t *pstate)
+{
+ struct g_keyboard_softc *sc = device_get_softc(dev);
+ const struct usb_device_request *req = preq;
+ uint8_t is_complete = *pstate;
+
+ if (!is_complete) {
+ if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->bRequest == UR_SET_REPORT) &&
+ (req->wValue[0] == 0x00) &&
+ (req->wValue[1] == 0x02)) {
+ if (offset == 0) {
+ *plen = sizeof(sc->sc_led_state);
+ *pptr = &sc->sc_led_state;
+ } else {
+ *plen = 0;
+ }
+ return (0);
+ } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->bRequest == UR_SET_PROTOCOL) &&
+ (req->wValue[0] == 0x00) &&
+ (req->wValue[1] == 0x00)) {
+ *plen = 0;
+ return (0);
+ } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->bRequest == UR_SET_IDLE)) {
+ *plen = 0;
+ return (0);
+ }
+ }
+ return (ENXIO); /* use builtin handler */
+}
diff --git a/sys/dev/usb/gadget/g_keyboard.h b/sys/dev/usb/gadget/g_keyboard.h
new file mode 100644
index 000000000000..d2999e76d262
--- /dev/null
+++ b/sys/dev/usb/gadget/g_keyboard.h
@@ -0,0 +1,36 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _G_KEYBOARD_H_
+#define _G_KEYBOARD_H_
+
+#define G_KEYBOARD_MAX_STRLEN 32
+#define G_KEYBOARD_MODE_SILENT 0
+#define G_KEYBOARD_MODE_PATTERN 1
+#define G_KEYBOARD_MODE_MAX 2
+
+#endif /* _G_KEYBOARD_H_ */
diff --git a/sys/dev/usb/gadget/g_modem.c b/sys/dev/usb/gadget/g_modem.c
new file mode 100644
index 000000000000..24445981e835
--- /dev/null
+++ b/sys/dev/usb/gadget/g_modem.c
@@ -0,0 +1,537 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Comm Class spec: http://www.usb.org/developers/devclass_docs/usbccs10.pdf
+ * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf
+ * http://www.usb.org/developers/devclass_docs/cdc_wmc10.zip
+ */
+
+#include <sys/param.h>
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/linker_set.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_cdc.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include "usb_if.h"
+
+#define USB_DEBUG_VAR g_modem_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/gadget/g_modem.h>
+
+enum {
+ G_MODEM_INTR_DT,
+ G_MODEM_BULK_RD,
+ G_MODEM_BULK_WR,
+ G_MODEM_N_TRANSFER,
+};
+
+struct g_modem_softc {
+ struct mtx sc_mtx;
+ struct usb_callout sc_callout;
+ struct usb_callout sc_watchdog;
+ struct usb_xfer *sc_xfer[G_MODEM_N_TRANSFER];
+
+ int sc_mode;
+ int sc_tx_busy;
+ int sc_pattern_len;
+ int sc_throughput;
+ int sc_tx_interval;
+
+ char sc_pattern[G_MODEM_MAX_STRLEN];
+
+ uint16_t sc_data_len;
+
+ uint8_t sc_data_buf[G_MODEM_BUFSIZE];
+ uint8_t sc_line_coding[32];
+ uint8_t sc_abstract_state[32];
+};
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, g_modem, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB modem gadget");
+
+#ifdef USB_DEBUG
+static int g_modem_debug = 0;
+
+SYSCTL_INT(_hw_usb_g_modem, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &g_modem_debug, 0, "Debug level");
+#endif
+
+static int g_modem_mode = 0;
+
+SYSCTL_INT(_hw_usb_g_modem, OID_AUTO, mode, CTLFLAG_RWTUN,
+ &g_modem_mode, 0, "Mode selection");
+
+static int g_modem_pattern_interval = 1000;
+
+SYSCTL_INT(_hw_usb_g_modem, OID_AUTO, pattern_interval, CTLFLAG_RWTUN,
+ &g_modem_pattern_interval, 0, "Pattern interval in milliseconds");
+
+static char g_modem_pattern_data[G_MODEM_MAX_STRLEN];
+
+SYSCTL_STRING(_hw_usb_g_modem, OID_AUTO, pattern, CTLFLAG_RW,
+ &g_modem_pattern_data, sizeof(g_modem_pattern_data), "Data pattern");
+
+static int g_modem_throughput;
+
+SYSCTL_INT(_hw_usb_g_modem, OID_AUTO, throughput, CTLFLAG_RD,
+ &g_modem_throughput, sizeof(g_modem_throughput), "Throughput in bytes per second");
+
+static device_probe_t g_modem_probe;
+static device_attach_t g_modem_attach;
+static device_detach_t g_modem_detach;
+static usb_handle_request_t g_modem_handle_request;
+static usb_callback_t g_modem_intr_callback;
+static usb_callback_t g_modem_bulk_read_callback;
+static usb_callback_t g_modem_bulk_write_callback;
+
+static void g_modem_timeout(void *arg);
+
+static device_method_t g_modem_methods[] = {
+ /* USB interface */
+ DEVMETHOD(usb_handle_request, g_modem_handle_request),
+
+ /* Device interface */
+ DEVMETHOD(device_probe, g_modem_probe),
+ DEVMETHOD(device_attach, g_modem_attach),
+ DEVMETHOD(device_detach, g_modem_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t g_modem_driver = {
+ .name = "g_modem",
+ .methods = g_modem_methods,
+ .size = sizeof(struct g_modem_softc),
+};
+
+DRIVER_MODULE(g_modem, uhub, g_modem_driver, 0, 0);
+MODULE_DEPEND(g_modem, usb, 1, 1, 1);
+
+static const struct usb_config g_modem_config[G_MODEM_N_TRANSFER] = {
+ [G_MODEM_INTR_DT] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .flags = {.ext_buffer = 1,.pipe_bof = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &g_modem_intr_callback,
+ .frames = 1,
+ .usb_mode = USB_MODE_DEVICE,
+ .if_index = 0,
+ },
+
+ [G_MODEM_BULK_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_RX,
+ .flags = {.ext_buffer = 1,.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = G_MODEM_BUFSIZE,
+ .callback = &g_modem_bulk_read_callback,
+ .frames = 1,
+ .usb_mode = USB_MODE_DEVICE,
+ .if_index = 1,
+ },
+
+ [G_MODEM_BULK_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .flags = {.ext_buffer = 1,.pipe_bof = 1,},
+ .bufsize = G_MODEM_BUFSIZE,
+ .callback = &g_modem_bulk_write_callback,
+ .frames = 1,
+ .usb_mode = USB_MODE_DEVICE,
+ .if_index = 1,
+ },
+};
+
+static void
+g_modem_timeout_reset(struct g_modem_softc *sc)
+{
+ int i = g_modem_pattern_interval;
+
+ sc->sc_tx_interval = i;
+
+ if (i <= 0)
+ i = 1;
+ else if (i > 1023)
+ i = 1023;
+
+ i = USB_MS_TO_TICKS(i);
+
+ usb_callout_reset(&sc->sc_callout, i, &g_modem_timeout, sc);
+}
+
+static void
+g_modem_timeout(void *arg)
+{
+ struct g_modem_softc *sc = arg;
+
+ sc->sc_mode = g_modem_mode;
+
+ memcpy(sc->sc_pattern, g_modem_pattern_data, sizeof(sc->sc_pattern));
+
+ sc->sc_pattern[G_MODEM_MAX_STRLEN - 1] = 0;
+
+ sc->sc_pattern_len = strlen(sc->sc_pattern);
+
+ DPRINTFN(11, "Timeout %p\n", sc->sc_xfer[G_MODEM_INTR_DT]);
+
+ usbd_transfer_start(sc->sc_xfer[G_MODEM_BULK_WR]);
+ usbd_transfer_start(sc->sc_xfer[G_MODEM_BULK_RD]);
+
+ g_modem_timeout_reset(sc);
+}
+
+static void g_modem_watchdog(void *arg);
+
+static void
+g_modem_watchdog_reset(struct g_modem_softc *sc)
+{
+ usb_callout_reset(&sc->sc_watchdog, hz, &g_modem_watchdog, sc);
+}
+
+static void
+g_modem_watchdog(void *arg)
+{
+ struct g_modem_softc *sc = arg;
+ int i;
+
+ i = sc->sc_throughput;
+
+ sc->sc_throughput = 0;
+
+ g_modem_throughput = i;
+
+ g_modem_watchdog_reset(sc);
+}
+
+static int
+g_modem_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ DPRINTFN(11, "\n");
+
+ if (uaa->usb_mode != USB_MODE_DEVICE)
+ return (ENXIO);
+
+ if ((uaa->info.bInterfaceClass == UICLASS_CDC) &&
+ (uaa->info.bInterfaceSubClass == UISUBCLASS_ABSTRACT_CONTROL_MODEL) &&
+ (uaa->info.bInterfaceProtocol == UIPROTO_CDC_AT))
+ return (0);
+
+ return (ENXIO);
+}
+
+static int
+g_modem_attach(device_t dev)
+{
+ struct g_modem_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ int error;
+ uint8_t iface_index[2];
+
+ DPRINTFN(11, "\n");
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, "g_modem", NULL, MTX_DEF);
+
+ usb_callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0);
+ usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0);
+
+ sc->sc_mode = G_MODEM_MODE_SILENT;
+
+ iface_index[0] = uaa->info.bIfaceIndex;
+ iface_index[1] = uaa->info.bIfaceIndex + 1;
+
+ error = usbd_transfer_setup(uaa->device,
+ iface_index, sc->sc_xfer, g_modem_config,
+ G_MODEM_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error) {
+ DPRINTF("error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+ usbd_set_parent_iface(uaa->device, iface_index[1], iface_index[0]);
+
+ mtx_lock(&sc->sc_mtx);
+ g_modem_timeout_reset(sc);
+ g_modem_watchdog_reset(sc);
+ mtx_unlock(&sc->sc_mtx);
+
+ return (0); /* success */
+
+detach:
+ g_modem_detach(dev);
+
+ return (ENXIO); /* error */
+}
+
+static int
+g_modem_detach(device_t dev)
+{
+ struct g_modem_softc *sc = device_get_softc(dev);
+
+ DPRINTF("\n");
+
+ mtx_lock(&sc->sc_mtx);
+ usb_callout_stop(&sc->sc_callout);
+ usb_callout_stop(&sc->sc_watchdog);
+ mtx_unlock(&sc->sc_mtx);
+
+ usbd_transfer_unsetup(sc->sc_xfer, G_MODEM_N_TRANSFER);
+
+ usb_callout_drain(&sc->sc_callout);
+ usb_callout_drain(&sc->sc_watchdog);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+g_modem_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ int actlen;
+ int aframes;
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ DPRINTF("st=%d aframes=%d actlen=%d bytes\n",
+ USB_GET_STATE(xfer), aframes, actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ break;
+
+ case USB_ST_SETUP:
+tr_setup:
+ break;
+
+ default: /* Error */
+ DPRINTF("error=%s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+g_modem_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct g_modem_softc *sc = usbd_xfer_softc(xfer);
+ int actlen;
+ int aframes;
+ int mod;
+ int x;
+ int max;
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ DPRINTF("st=%d aframes=%d actlen=%d bytes\n",
+ USB_GET_STATE(xfer), aframes, actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ sc->sc_tx_busy = 0;
+ sc->sc_throughput += actlen;
+
+ if (sc->sc_mode == G_MODEM_MODE_LOOP) {
+ /* start loop */
+ usbd_transfer_start(sc->sc_xfer[G_MODEM_BULK_RD]);
+ break;
+ } else if ((sc->sc_mode == G_MODEM_MODE_PATTERN) && (sc->sc_tx_interval != 0)) {
+ /* wait for next timeout */
+ break;
+ }
+ case USB_ST_SETUP:
+tr_setup:
+ if (sc->sc_mode == G_MODEM_MODE_PATTERN) {
+ mod = sc->sc_pattern_len;
+ max = sc->sc_tx_interval ? mod : G_MODEM_BUFSIZE;
+
+ if (mod == 0) {
+ for (x = 0; x != max; x++)
+ sc->sc_data_buf[x] = x % 255;
+ } else {
+ for (x = 0; x != max; x++)
+ sc->sc_data_buf[x] = sc->sc_pattern[x % mod];
+ }
+
+ usbd_xfer_set_frame_data(xfer, 0, sc->sc_data_buf, max);
+ usbd_xfer_set_interval(xfer, 0);
+ usbd_xfer_set_frames(xfer, 1);
+ usbd_transfer_submit(xfer);
+
+ } else if (sc->sc_mode == G_MODEM_MODE_LOOP) {
+ if (sc->sc_tx_busy == 0)
+ break;
+
+ x = sc->sc_tx_interval;
+
+ if (x < 0)
+ x = 0;
+ else if (x > 256)
+ x = 256;
+
+ usbd_xfer_set_frame_data(xfer, 0, sc->sc_data_buf, sc->sc_data_len);
+ usbd_xfer_set_interval(xfer, x);
+ usbd_xfer_set_frames(xfer, 1);
+ usbd_transfer_submit(xfer);
+ } else {
+ sc->sc_tx_busy = 0;
+ }
+ break;
+
+ default: /* Error */
+ DPRINTF("error=%s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+g_modem_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct g_modem_softc *sc = usbd_xfer_softc(xfer);
+ int actlen;
+ int aframes;
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ DPRINTF("st=%d aframes=%d actlen=%d bytes\n",
+ USB_GET_STATE(xfer), aframes, actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ sc->sc_throughput += actlen;
+
+ if (sc->sc_mode == G_MODEM_MODE_LOOP) {
+ sc->sc_tx_busy = 1;
+ sc->sc_data_len = actlen;
+ usbd_transfer_start(sc->sc_xfer[G_MODEM_BULK_WR]);
+ break;
+ }
+
+ case USB_ST_SETUP:
+tr_setup:
+ if ((sc->sc_mode == G_MODEM_MODE_SILENT) ||
+ (sc->sc_tx_busy != 0))
+ break;
+
+ usbd_xfer_set_frame_data(xfer, 0, sc->sc_data_buf, G_MODEM_BUFSIZE);
+ usbd_xfer_set_frames(xfer, 1);
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ DPRINTF("error=%s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static int
+g_modem_handle_request(device_t dev,
+ const void *preq, void **pptr, uint16_t *plen,
+ uint16_t offset, uint8_t *pstate)
+{
+ struct g_modem_softc *sc = device_get_softc(dev);
+ const struct usb_device_request *req = preq;
+ uint8_t is_complete = *pstate;
+
+ if (!is_complete) {
+ if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->bRequest == UCDC_SET_LINE_CODING) &&
+ (req->wValue[0] == 0x00) &&
+ (req->wValue[1] == 0x00)) {
+ if (offset == 0) {
+ *plen = sizeof(sc->sc_line_coding);
+ *pptr = &sc->sc_line_coding;
+ } else {
+ *plen = 0;
+ }
+ return (0);
+ } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->bRequest == UCDC_SET_COMM_FEATURE)) {
+ if (offset == 0) {
+ *plen = sizeof(sc->sc_abstract_state);
+ *pptr = &sc->sc_abstract_state;
+ } else {
+ *plen = 0;
+ }
+ return (0);
+ } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->bRequest == UCDC_SET_CONTROL_LINE_STATE)) {
+ *plen = 0;
+ return (0);
+ } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->bRequest == UCDC_SEND_BREAK)) {
+ *plen = 0;
+ return (0);
+ }
+ }
+ return (ENXIO); /* use builtin handler */
+}
diff --git a/sys/dev/usb/gadget/g_modem.h b/sys/dev/usb/gadget/g_modem.h
new file mode 100644
index 000000000000..f6ea3420ebe1
--- /dev/null
+++ b/sys/dev/usb/gadget/g_modem.h
@@ -0,0 +1,40 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _G_MODEM_H_
+#define _G_MODEM_H_
+
+#define G_MODEM_MAX_STRLEN 32 /* chars */
+#define G_MODEM_BUFSIZE 4096 /* bytes */
+
+#define G_MODEM_MODE_SILENT 0
+#define G_MODEM_MODE_DUMP 1
+#define G_MODEM_MODE_LOOP 2
+#define G_MODEM_MODE_PATTERN 3
+#define G_MODEM_MODE_MAX 4
+
+#endif /* _G_MODEM_H_ */
diff --git a/sys/dev/usb/gadget/g_mouse.c b/sys/dev/usb/gadget/g_mouse.c
new file mode 100644
index 000000000000..977ac132045a
--- /dev/null
+++ b/sys/dev/usb/gadget/g_mouse.c
@@ -0,0 +1,454 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf
+ */
+
+#include <sys/param.h>
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/linker_set.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include "usb_if.h"
+
+#define USB_DEBUG_VAR g_mouse_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/gadget/g_mouse.h>
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, g_mouse, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB mouse gadget");
+
+#ifdef USB_DEBUG
+static int g_mouse_debug = 0;
+
+SYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &g_mouse_debug, 0, "Debug level");
+#endif
+
+static int g_mouse_mode = 0;
+
+SYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, mode, CTLFLAG_RWTUN,
+ &g_mouse_mode, 0, "Mode selection");
+
+static int g_mouse_button_press_interval = 0;
+
+SYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, button_press_interval, CTLFLAG_RWTUN,
+ &g_mouse_button_press_interval, 0, "Mouse button update interval in milliseconds");
+
+static int g_mouse_cursor_update_interval = 1023;
+
+SYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, cursor_update_interval, CTLFLAG_RWTUN,
+ &g_mouse_cursor_update_interval, 0, "Mouse cursor update interval in milliseconds");
+
+static int g_mouse_cursor_radius = 128;
+
+SYSCTL_INT(_hw_usb_g_mouse, OID_AUTO, cursor_radius, CTLFLAG_RWTUN,
+ &g_mouse_cursor_radius, 0, "Mouse cursor radius in pixels");
+
+struct g_mouse_data {
+ uint8_t buttons;
+#define BUT_0 0x01
+#define BUT_1 0x02
+#define BUT_2 0x04
+ int8_t dx;
+ int8_t dy;
+ int8_t dz;
+};
+
+enum {
+ G_MOUSE_INTR_DT,
+ G_MOUSE_N_TRANSFER,
+};
+
+struct g_mouse_softc {
+ struct mtx sc_mtx;
+ struct usb_callout sc_button_press_callout;
+ struct usb_callout sc_cursor_update_callout;
+ struct g_mouse_data sc_data;
+ struct usb_xfer *sc_xfer[G_MOUSE_N_TRANSFER];
+
+ int sc_mode;
+ int sc_radius;
+ int sc_last_x_state;
+ int sc_last_y_state;
+ int sc_curr_x_state;
+ int sc_curr_y_state;
+ int sc_tick;
+
+ uint8_t sc_do_cursor_update;
+ uint8_t sc_do_button_update;
+};
+
+static device_probe_t g_mouse_probe;
+static device_attach_t g_mouse_attach;
+static device_detach_t g_mouse_detach;
+static usb_handle_request_t g_mouse_handle_request;
+static usb_callback_t g_mouse_intr_callback;
+
+static device_method_t g_mouse_methods[] = {
+ /* USB interface */
+ DEVMETHOD(usb_handle_request, g_mouse_handle_request),
+
+ /* Device interface */
+ DEVMETHOD(device_probe, g_mouse_probe),
+ DEVMETHOD(device_attach, g_mouse_attach),
+ DEVMETHOD(device_detach, g_mouse_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t g_mouse_driver = {
+ .name = "g_mouse",
+ .methods = g_mouse_methods,
+ .size = sizeof(struct g_mouse_softc),
+};
+
+DRIVER_MODULE(g_mouse, uhub, g_mouse_driver, 0, 0);
+MODULE_DEPEND(g_mouse, usb, 1, 1, 1);
+
+static const struct usb_config g_mouse_config[G_MOUSE_N_TRANSFER] = {
+ [G_MOUSE_INTR_DT] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.ext_buffer = 1,.pipe_bof = 1,},
+ .bufsize = sizeof(struct g_mouse_data),
+ .callback = &g_mouse_intr_callback,
+ .frames = 1,
+ .usb_mode = USB_MODE_DEVICE,
+ },
+};
+
+static void g_mouse_button_press_timeout(void *arg);
+static void g_mouse_cursor_update_timeout(void *arg);
+
+static void
+g_mouse_button_press_timeout_reset(struct g_mouse_softc *sc)
+{
+ int i = g_mouse_button_press_interval;
+
+ if (i <= 0) {
+ sc->sc_data.buttons = 0;
+ sc->sc_do_button_update = 0;
+ } else {
+ sc->sc_do_button_update = 1;
+ }
+
+ if ((i <= 0) || (i > 1023))
+ i = 1023;
+
+ i = USB_MS_TO_TICKS(i);
+
+ usb_callout_reset(&sc->sc_button_press_callout, i,
+ &g_mouse_button_press_timeout, sc);
+}
+
+static void
+g_mouse_cursor_update_timeout_reset(struct g_mouse_softc *sc)
+{
+ int i = g_mouse_cursor_update_interval;
+
+ if (i <= 0) {
+ sc->sc_data.dx = 0;
+ sc->sc_data.dy = 0;
+ sc->sc_do_cursor_update = 0;
+ sc->sc_tick = 0;
+ } else {
+ sc->sc_do_cursor_update = 1;
+ }
+
+ if ((i <= 0) || (i > 1023))
+ i = 1023;
+
+ i = USB_MS_TO_TICKS(i);
+
+ usb_callout_reset(&sc->sc_cursor_update_callout, i,
+ &g_mouse_cursor_update_timeout, sc);
+}
+
+static void
+g_mouse_update_mode_radius(struct g_mouse_softc *sc)
+{
+ sc->sc_mode = g_mouse_mode;
+ sc->sc_radius = g_mouse_cursor_radius;
+
+ if (sc->sc_radius < 0)
+ sc->sc_radius = 0;
+ else if (sc->sc_radius > 1023)
+ sc->sc_radius = 1023;
+}
+
+static void
+g_mouse_button_press_timeout(void *arg)
+{
+ struct g_mouse_softc *sc = arg;
+
+ g_mouse_update_mode_radius(sc);
+
+ DPRINTFN(11, "Timeout %p (button press)\n", sc->sc_xfer[G_MOUSE_INTR_DT]);
+
+ g_mouse_button_press_timeout_reset(sc);
+
+ usbd_transfer_start(sc->sc_xfer[G_MOUSE_INTR_DT]);
+}
+
+static void
+g_mouse_cursor_update_timeout(void *arg)
+{
+ struct g_mouse_softc *sc = arg;
+
+ g_mouse_update_mode_radius(sc);
+
+ DPRINTFN(11, "Timeout %p (cursor update)\n", sc->sc_xfer[G_MOUSE_INTR_DT]);
+
+ g_mouse_cursor_update_timeout_reset(sc);
+
+ usbd_transfer_start(sc->sc_xfer[G_MOUSE_INTR_DT]);
+}
+
+static int
+g_mouse_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ DPRINTFN(11, "\n");
+
+ if (uaa->usb_mode != USB_MODE_DEVICE)
+ return (ENXIO);
+
+ if ((uaa->info.bInterfaceClass == UICLASS_HID) &&
+ (uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) &&
+ (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE))
+ return (0);
+
+ return (ENXIO);
+}
+
+static int
+g_mouse_attach(device_t dev)
+{
+ struct g_mouse_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ int error;
+
+ DPRINTFN(11, "\n");
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, "g_mouse", NULL, MTX_DEF);
+
+ usb_callout_init_mtx(&sc->sc_button_press_callout, &sc->sc_mtx, 0);
+ usb_callout_init_mtx(&sc->sc_cursor_update_callout, &sc->sc_mtx, 0);
+
+ sc->sc_mode = G_MOUSE_MODE_SILENT;
+
+ error = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer, g_mouse_config,
+ G_MOUSE_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error) {
+ DPRINTF("error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+
+ mtx_lock(&sc->sc_mtx);
+ g_mouse_button_press_timeout_reset(sc);
+ g_mouse_cursor_update_timeout_reset(sc);
+ mtx_unlock(&sc->sc_mtx);
+
+ return (0); /* success */
+
+detach:
+ g_mouse_detach(dev);
+
+ return (ENXIO); /* error */
+}
+
+static int
+g_mouse_detach(device_t dev)
+{
+ struct g_mouse_softc *sc = device_get_softc(dev);
+
+ DPRINTF("\n");
+
+ mtx_lock(&sc->sc_mtx);
+ usb_callout_stop(&sc->sc_button_press_callout);
+ usb_callout_stop(&sc->sc_cursor_update_callout);
+ mtx_unlock(&sc->sc_mtx);
+
+ usbd_transfer_unsetup(sc->sc_xfer, G_MOUSE_N_TRANSFER);
+
+ usb_callout_drain(&sc->sc_button_press_callout);
+ usb_callout_drain(&sc->sc_cursor_update_callout);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+g_mouse_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct g_mouse_softc *sc = usbd_xfer_softc(xfer);
+ int actlen;
+ int aframes;
+ int dx;
+ int dy;
+ int radius;
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ DPRINTF("st=%d aframes=%d actlen=%d bytes\n",
+ USB_GET_STATE(xfer), aframes, actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (!(sc->sc_do_cursor_update || sc->sc_do_button_update))
+ break;
+
+ case USB_ST_SETUP:
+tr_setup:
+
+ if (sc->sc_do_cursor_update) {
+ sc->sc_do_cursor_update = 0;
+ sc->sc_tick += 80;
+ if ((sc->sc_tick < 0) || (sc->sc_tick > 7999))
+ sc->sc_tick = 0;
+ }
+
+ if (sc->sc_do_button_update) {
+ sc->sc_do_button_update = 0;
+ sc->sc_data.buttons ^= BUT_0;
+ }
+
+ radius = sc->sc_radius;
+
+ switch (sc->sc_mode) {
+ case G_MOUSE_MODE_SILENT:
+ sc->sc_data.buttons = 0;
+ break;
+ case G_MOUSE_MODE_SPIRAL:
+ radius = (radius * (8000-sc->sc_tick)) / 8000;
+ case G_MOUSE_MODE_CIRCLE:
+ /* TODO */
+ sc->sc_curr_y_state = 0;
+ sc->sc_curr_x_state = 0;
+ break;
+ case G_MOUSE_MODE_BOX:
+ if (sc->sc_tick < 2000) {
+ sc->sc_curr_x_state = (sc->sc_tick * radius) / 2000;
+ sc->sc_curr_y_state = 0;
+ } else if (sc->sc_tick < 4000) {
+ sc->sc_curr_x_state = radius;
+ sc->sc_curr_y_state = -(((sc->sc_tick - 2000) * radius) / 2000);
+ } else if (sc->sc_tick < 6000) {
+ sc->sc_curr_x_state = radius - (((sc->sc_tick - 4000) * radius) / 2000);
+ sc->sc_curr_y_state = -radius;
+ } else {
+ sc->sc_curr_x_state = 0;
+ sc->sc_curr_y_state = -radius + (((sc->sc_tick - 6000) * radius) / 2000);
+ }
+ break;
+ default:
+ break;
+ }
+
+ dx = sc->sc_curr_x_state - sc->sc_last_x_state;
+ dy = sc->sc_curr_y_state - sc->sc_last_y_state;
+
+ if (dx < -63)
+ dx = -63;
+ else if (dx > 63)
+ dx = 63;
+
+ if (dy < -63)
+ dy = -63;
+ else if (dy > 63)
+ dy = 63;
+
+ sc->sc_last_x_state += dx;
+ sc->sc_last_y_state += dy;
+
+ sc->sc_data.dx = dx;
+ sc->sc_data.dy = dy;
+
+ usbd_xfer_set_frame_data(xfer, 0, &sc->sc_data, sizeof(sc->sc_data));
+ usbd_xfer_set_frames(xfer, 1);
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ DPRINTF("error=%s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static int
+g_mouse_handle_request(device_t dev,
+ const void *preq, void **pptr, uint16_t *plen,
+ uint16_t offset, uint8_t *pstate)
+{
+ const struct usb_device_request *req = preq;
+ uint8_t is_complete = *pstate;
+
+ if (!is_complete) {
+ if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->bRequest == UR_SET_PROTOCOL) &&
+ (req->wValue[0] == 0x00) &&
+ (req->wValue[1] == 0x00)) {
+ *plen = 0;
+ return (0);
+ }
+ }
+ return (ENXIO); /* use builtin handler */
+}
diff --git a/sys/dev/usb/gadget/g_mouse.h b/sys/dev/usb/gadget/g_mouse.h
new file mode 100644
index 000000000000..4ddef8a47296
--- /dev/null
+++ b/sys/dev/usb/gadget/g_mouse.h
@@ -0,0 +1,37 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _G_MOUSE_H_
+#define _G_MOUSE_H_
+
+#define G_MOUSE_MODE_SILENT 0
+#define G_MOUSE_MODE_CIRCLE 1
+#define G_MOUSE_MODE_BOX 2
+#define G_MOUSE_MODE_SPIRAL 3
+#define G_MOUSE_MODE_MAX 4
+
+#endif /* _G_MOUSE_H_ */
diff --git a/sys/dev/usb/input/atp.c b/sys/dev/usb/input/atp.c
new file mode 100644
index 000000000000..41ab37c6f1cc
--- /dev/null
+++ b/sys/dev/usb/input/atp.c
@@ -0,0 +1,2632 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2014 Rohit Grover
+ * 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.
+ */
+
+/*
+ * Some tables, structures, definitions and constant values for the
+ * touchpad protocol has been copied from Linux's
+ * "drivers/input/mouse/bcm5974.c" which has the following copyright
+ * holders under GPLv2. All device specific code in this driver has
+ * been written from scratch. The decoding algorithm is based on
+ * output from FreeBSD's usbdump.
+ *
+ * Copyright (C) 2008 Henrik Rydberg (rydberg@euromail.se)
+ * Copyright (C) 2008 Scott Shawcroft (scott.shawcroft@gmail.com)
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2005 Johannes Berg (johannes@sipsolutions.net)
+ * Copyright (C) 2005 Stelian Pop (stelian@popies.net)
+ * Copyright (C) 2005 Frank Arnold (frank@scirocco-5v-turbo.de)
+ * Copyright (C) 2005 Peter Osterlund (petero2@telia.com)
+ * Copyright (C) 2005 Michael Hanselmann (linux-kernel@hansmi.ch)
+ * Copyright (C) 2006 Nicolas Boichat (nicolas@boichat.ch)
+ */
+
+/*
+ * Author's note: 'atp' supports two distinct families of Apple trackpad
+ * products: the older Fountain/Geyser and the latest Wellspring trackpads.
+ * The first version made its appearance with FreeBSD 8 and worked only with
+ * the Fountain/Geyser hardware. A fork of this driver for Wellspring was
+ * contributed by Huang Wen Hui. This driver unifies the Wellspring effort
+ * and also improves upon the original work.
+ *
+ * I'm grateful to Stephan Scheunig, Angela Naegele, and Nokia IT-support
+ * for helping me with access to hardware. Thanks also go to Nokia for
+ * giving me an opportunity to do this work.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/sysctl.h>
+#include <sys/malloc.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/file.h>
+#include <sys/selinfo.h>
+#include <sys/poll.h>
+
+#include <dev/hid/hid.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR atp_debug
+#include <dev/usb/usb_debug.h>
+
+#include <sys/mouse.h>
+
+#define ATP_DRIVER_NAME "atp"
+
+/*
+ * Driver specific options: the following options may be set by
+ * `options' statements in the kernel configuration file.
+ */
+
+/* The divisor used to translate sensor reported positions to mickeys. */
+#ifndef ATP_SCALE_FACTOR
+#define ATP_SCALE_FACTOR 16
+#endif
+
+/* Threshold for small movement noise (in mickeys) */
+#ifndef ATP_SMALL_MOVEMENT_THRESHOLD
+#define ATP_SMALL_MOVEMENT_THRESHOLD 30
+#endif
+
+/* Threshold of instantaneous deltas beyond which movement is considered fast.*/
+#ifndef ATP_FAST_MOVEMENT_TRESHOLD
+#define ATP_FAST_MOVEMENT_TRESHOLD 150
+#endif
+
+/*
+ * This is the age in microseconds beyond which a touch is considered
+ * to be a slide; and therefore a tap event isn't registered.
+ */
+#ifndef ATP_TOUCH_TIMEOUT
+#define ATP_TOUCH_TIMEOUT 125000
+#endif
+
+#ifndef ATP_IDLENESS_THRESHOLD
+#define ATP_IDLENESS_THRESHOLD 10
+#endif
+
+#ifndef FG_SENSOR_NOISE_THRESHOLD
+#define FG_SENSOR_NOISE_THRESHOLD 2
+#endif
+
+/*
+ * A double-tap followed by a single-finger slide is treated as a
+ * special gesture. The driver responds to this gesture by assuming a
+ * virtual button-press for the lifetime of the slide. The following
+ * threshold is the maximum time gap (in microseconds) between the two
+ * tap events preceding the slide for such a gesture.
+ */
+#ifndef ATP_DOUBLE_TAP_N_DRAG_THRESHOLD
+#define ATP_DOUBLE_TAP_N_DRAG_THRESHOLD 200000
+#endif
+
+/*
+ * The wait duration in ticks after losing a touch contact before
+ * zombied strokes are reaped and turned into button events.
+ */
+#define ATP_ZOMBIE_STROKE_REAP_INTERVAL (hz / 20) /* 50 ms */
+
+/* The multiplier used to translate sensor reported positions to mickeys. */
+#define FG_SCALE_FACTOR 380
+
+/*
+ * The movement threshold for a stroke; this is the maximum difference
+ * in position which will be resolved as a continuation of a stroke
+ * component.
+ */
+#define FG_MAX_DELTA_MICKEYS ((3 * (FG_SCALE_FACTOR)) >> 1)
+
+/* Distance-squared threshold for matching a finger with a known stroke */
+#ifndef WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ
+#define WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ 1000000
+#endif
+
+/* Ignore pressure spans with cumulative press. below this value. */
+#define FG_PSPAN_MIN_CUM_PRESSURE 10
+
+/* Maximum allowed width for pressure-spans.*/
+#define FG_PSPAN_MAX_WIDTH 4
+
+/* end of driver specific options */
+
+/* Tunables */
+static SYSCTL_NODE(_hw_usb, OID_AUTO, atp, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB ATP");
+
+#ifdef USB_DEBUG
+enum atp_log_level {
+ ATP_LLEVEL_DISABLED = 0,
+ ATP_LLEVEL_ERROR,
+ ATP_LLEVEL_DEBUG, /* for troubleshooting */
+ ATP_LLEVEL_INFO, /* for diagnostics */
+};
+static int atp_debug = ATP_LLEVEL_ERROR; /* the default is to only log errors */
+SYSCTL_INT(_hw_usb_atp, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &atp_debug, ATP_LLEVEL_ERROR, "ATP debug level");
+#endif /* USB_DEBUG */
+
+static u_int atp_touch_timeout = ATP_TOUCH_TIMEOUT;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, touch_timeout, CTLFLAG_RWTUN,
+ &atp_touch_timeout, 125000, "age threshold in microseconds for a touch");
+
+static u_int atp_double_tap_threshold = ATP_DOUBLE_TAP_N_DRAG_THRESHOLD;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, double_tap_threshold, CTLFLAG_RWTUN,
+ &atp_double_tap_threshold, ATP_DOUBLE_TAP_N_DRAG_THRESHOLD,
+ "maximum time in microseconds to allow association between a double-tap and "
+ "drag gesture");
+
+static u_int atp_mickeys_scale_factor = ATP_SCALE_FACTOR;
+static int atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS);
+SYSCTL_PROC(_hw_usb_atp, OID_AUTO, scale_factor,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &atp_mickeys_scale_factor, sizeof(atp_mickeys_scale_factor),
+ atp_sysctl_scale_factor_handler, "IU",
+ "movement scale factor");
+
+static u_int atp_small_movement_threshold = ATP_SMALL_MOVEMENT_THRESHOLD;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, small_movement, CTLFLAG_RWTUN,
+ &atp_small_movement_threshold, ATP_SMALL_MOVEMENT_THRESHOLD,
+ "the small movement black-hole for filtering noise");
+
+static u_int atp_tap_minimum = 1;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, tap_minimum, CTLFLAG_RWTUN,
+ &atp_tap_minimum, 1, "Minimum number of taps before detection");
+
+/*
+ * Strokes which accumulate at least this amount of absolute movement
+ * from the aggregate of their components are considered as
+ * slides. Unit: mickeys.
+ */
+static u_int atp_slide_min_movement = 2 * ATP_SMALL_MOVEMENT_THRESHOLD;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, slide_min_movement, CTLFLAG_RWTUN,
+ &atp_slide_min_movement, 2 * ATP_SMALL_MOVEMENT_THRESHOLD,
+ "strokes with at least this amt. of movement are considered slides");
+
+/*
+ * The minimum age of a stroke for it to be considered mature; this
+ * helps filter movements (noise) from immature strokes. Units: interrupts.
+ */
+static u_int atp_stroke_maturity_threshold = 4;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, stroke_maturity_threshold, CTLFLAG_RWTUN,
+ &atp_stroke_maturity_threshold, 4,
+ "the minimum age of a stroke for it to be considered mature");
+
+typedef enum atp_trackpad_family {
+ TRACKPAD_FAMILY_FOUNTAIN_GEYSER,
+ TRACKPAD_FAMILY_WELLSPRING,
+ TRACKPAD_FAMILY_MAX /* keep this at the tail end of the enumeration */
+} trackpad_family_t;
+
+enum fountain_geyser_product {
+ FOUNTAIN,
+ GEYSER1,
+ GEYSER1_17inch,
+ GEYSER2,
+ GEYSER3,
+ GEYSER4,
+ FOUNTAIN_GEYSER_PRODUCT_MAX /* keep this at the end */
+};
+
+enum wellspring_product {
+ WELLSPRING1,
+ WELLSPRING2,
+ WELLSPRING3,
+ WELLSPRING4,
+ WELLSPRING4A,
+ WELLSPRING5,
+ WELLSPRING6A,
+ WELLSPRING6,
+ WELLSPRING5A,
+ WELLSPRING7,
+ WELLSPRING7A,
+ WELLSPRING8,
+ WELLSPRING_PRODUCT_MAX /* keep this at the end of the enumeration */
+};
+
+/* trackpad header types */
+enum fountain_geyser_trackpad_type {
+ FG_TRACKPAD_TYPE_GEYSER1,
+ FG_TRACKPAD_TYPE_GEYSER2,
+ FG_TRACKPAD_TYPE_GEYSER3,
+ FG_TRACKPAD_TYPE_GEYSER4,
+};
+enum wellspring_trackpad_type {
+ WSP_TRACKPAD_TYPE1, /* plain trackpad */
+ WSP_TRACKPAD_TYPE2, /* button integrated in trackpad */
+ WSP_TRACKPAD_TYPE3 /* additional header fields since June 2013 */
+};
+
+/*
+ * Trackpad family and product and family are encoded together in the
+ * driver_info value associated with a trackpad product.
+ */
+#define N_PROD_BITS 8 /* Number of bits used to encode product */
+#define ENCODE_DRIVER_INFO(FAMILY, PROD) \
+ (((FAMILY) << N_PROD_BITS) | (PROD))
+#define DECODE_FAMILY_FROM_DRIVER_INFO(INFO) ((INFO) >> N_PROD_BITS)
+#define DECODE_PRODUCT_FROM_DRIVER_INFO(INFO) \
+ ((INFO) & ((1 << N_PROD_BITS) - 1))
+
+#define FG_DRIVER_INFO(PRODUCT) \
+ ENCODE_DRIVER_INFO(TRACKPAD_FAMILY_FOUNTAIN_GEYSER, PRODUCT)
+#define WELLSPRING_DRIVER_INFO(PRODUCT) \
+ ENCODE_DRIVER_INFO(TRACKPAD_FAMILY_WELLSPRING, PRODUCT)
+
+/*
+ * The following structure captures the state of a pressure span along
+ * an axis. Each contact with the touchpad results in separate
+ * pressure spans along the two axes.
+ */
+typedef struct fg_pspan {
+ u_int width; /* in units of sensors */
+ u_int cum; /* cumulative compression (from all sensors) */
+ u_int cog; /* center of gravity */
+ u_int loc; /* location (scaled using the mickeys factor) */
+ boolean_t matched; /* to track pspans as they match against strokes. */
+} fg_pspan;
+
+#define FG_MAX_PSPANS_PER_AXIS 3
+#define FG_MAX_STROKES (2 * FG_MAX_PSPANS_PER_AXIS)
+
+#define WELLSPRING_INTERFACE_INDEX 1
+
+/* trackpad finger data offsets, le16-aligned */
+#define WSP_TYPE1_FINGER_DATA_OFFSET (13 * 2)
+#define WSP_TYPE2_FINGER_DATA_OFFSET (15 * 2)
+#define WSP_TYPE3_FINGER_DATA_OFFSET (19 * 2)
+
+/* trackpad button data offsets */
+#define WSP_TYPE2_BUTTON_DATA_OFFSET 15
+#define WSP_TYPE3_BUTTON_DATA_OFFSET 23
+
+/* list of device capability bits */
+#define HAS_INTEGRATED_BUTTON 1
+
+/* trackpad finger structure - little endian */
+struct wsp_finger_sensor_data {
+ int16_t origin; /* zero when switching track finger */
+ int16_t abs_x; /* absolute x coordinate */
+ int16_t abs_y; /* absolute y coordinate */
+ int16_t rel_x; /* relative x coordinate */
+ int16_t rel_y; /* relative y coordinate */
+ int16_t tool_major; /* tool area, major axis */
+ int16_t tool_minor; /* tool area, minor axis */
+ int16_t orientation; /* 16384 when point, else 15 bit angle */
+ int16_t touch_major; /* touch area, major axis */
+ int16_t touch_minor; /* touch area, minor axis */
+ int16_t unused[3]; /* zeros */
+ int16_t multi; /* one finger: varies, more fingers: constant */
+} __packed;
+
+typedef struct wsp_finger {
+ /* to track fingers as they match against strokes. */
+ boolean_t matched;
+
+ /* location (scaled using the mickeys factor) */
+ int x;
+ int y;
+} wsp_finger_t;
+
+#define WSP_MAX_FINGERS 16
+#define WSP_SIZEOF_FINGER_SENSOR_DATA sizeof(struct wsp_finger_sensor_data)
+#define WSP_SIZEOF_ALL_FINGER_DATA (WSP_MAX_FINGERS * \
+ WSP_SIZEOF_FINGER_SENSOR_DATA)
+#define WSP_MAX_FINGER_ORIENTATION 16384
+
+#define ATP_SENSOR_DATA_BUF_MAX 1024
+#if (ATP_SENSOR_DATA_BUF_MAX < ((WSP_MAX_FINGERS * 14 * 2) + \
+ WSP_TYPE3_FINGER_DATA_OFFSET))
+/* note: 14 * 2 in the above is based on sizeof(struct wsp_finger_sensor_data)*/
+#error "ATP_SENSOR_DATA_BUF_MAX is too small"
+#endif
+
+#define ATP_MAX_STROKES MAX(WSP_MAX_FINGERS, FG_MAX_STROKES)
+
+#define FG_MAX_XSENSORS 26
+#define FG_MAX_YSENSORS 16
+
+/* device-specific configuration */
+struct fg_dev_params {
+ u_int data_len; /* for sensor data */
+ u_int n_xsensors;
+ u_int n_ysensors;
+ enum fountain_geyser_trackpad_type prot;
+};
+struct wsp_dev_params {
+ uint8_t caps; /* device capability bitmask */
+ uint8_t tp_type; /* type of trackpad interface */
+ uint8_t finger_data_offset; /* offset to trackpad finger data */
+};
+
+static const struct fg_dev_params fg_dev_params[FOUNTAIN_GEYSER_PRODUCT_MAX] = {
+ [FOUNTAIN] = {
+ .data_len = 81,
+ .n_xsensors = 16,
+ .n_ysensors = 16,
+ .prot = FG_TRACKPAD_TYPE_GEYSER1
+ },
+ [GEYSER1] = {
+ .data_len = 81,
+ .n_xsensors = 16,
+ .n_ysensors = 16,
+ .prot = FG_TRACKPAD_TYPE_GEYSER1
+ },
+ [GEYSER1_17inch] = {
+ .data_len = 81,
+ .n_xsensors = 26,
+ .n_ysensors = 16,
+ .prot = FG_TRACKPAD_TYPE_GEYSER1
+ },
+ [GEYSER2] = {
+ .data_len = 64,
+ .n_xsensors = 15,
+ .n_ysensors = 9,
+ .prot = FG_TRACKPAD_TYPE_GEYSER2
+ },
+ [GEYSER3] = {
+ .data_len = 64,
+ .n_xsensors = 20,
+ .n_ysensors = 10,
+ .prot = FG_TRACKPAD_TYPE_GEYSER3
+ },
+ [GEYSER4] = {
+ .data_len = 64,
+ .n_xsensors = 20,
+ .n_ysensors = 10,
+ .prot = FG_TRACKPAD_TYPE_GEYSER4
+ }
+};
+
+static const STRUCT_USB_HOST_ID fg_devs[] = {
+ /* PowerBooks Feb 2005, iBooks G4 */
+ { USB_VPI(USB_VENDOR_APPLE, 0x020e, FG_DRIVER_INFO(FOUNTAIN)) },
+ { USB_VPI(USB_VENDOR_APPLE, 0x020f, FG_DRIVER_INFO(FOUNTAIN)) },
+ { USB_VPI(USB_VENDOR_APPLE, 0x0210, FG_DRIVER_INFO(FOUNTAIN)) },
+ { USB_VPI(USB_VENDOR_APPLE, 0x030a, FG_DRIVER_INFO(FOUNTAIN)) },
+ { USB_VPI(USB_VENDOR_APPLE, 0x030b, FG_DRIVER_INFO(GEYSER1)) },
+
+ /* PowerBooks Oct 2005 */
+ { USB_VPI(USB_VENDOR_APPLE, 0x0214, FG_DRIVER_INFO(GEYSER2)) },
+ { USB_VPI(USB_VENDOR_APPLE, 0x0215, FG_DRIVER_INFO(GEYSER2)) },
+ { USB_VPI(USB_VENDOR_APPLE, 0x0216, FG_DRIVER_INFO(GEYSER2)) },
+
+ /* Core Duo MacBook & MacBook Pro */
+ { USB_VPI(USB_VENDOR_APPLE, 0x0217, FG_DRIVER_INFO(GEYSER3)) },
+ { USB_VPI(USB_VENDOR_APPLE, 0x0218, FG_DRIVER_INFO(GEYSER3)) },
+ { USB_VPI(USB_VENDOR_APPLE, 0x0219, FG_DRIVER_INFO(GEYSER3)) },
+
+ /* Core2 Duo MacBook & MacBook Pro */
+ { USB_VPI(USB_VENDOR_APPLE, 0x021a, FG_DRIVER_INFO(GEYSER4)) },
+ { USB_VPI(USB_VENDOR_APPLE, 0x021b, FG_DRIVER_INFO(GEYSER4)) },
+ { USB_VPI(USB_VENDOR_APPLE, 0x021c, FG_DRIVER_INFO(GEYSER4)) },
+
+ /* Core2 Duo MacBook3,1 */
+ { USB_VPI(USB_VENDOR_APPLE, 0x0229, FG_DRIVER_INFO(GEYSER4)) },
+ { USB_VPI(USB_VENDOR_APPLE, 0x022a, FG_DRIVER_INFO(GEYSER4)) },
+ { USB_VPI(USB_VENDOR_APPLE, 0x022b, FG_DRIVER_INFO(GEYSER4)) },
+
+ /* 17 inch PowerBook */
+ { USB_VPI(USB_VENDOR_APPLE, 0x020d, FG_DRIVER_INFO(GEYSER1_17inch)) },
+};
+
+static const struct wsp_dev_params wsp_dev_params[WELLSPRING_PRODUCT_MAX] = {
+ [WELLSPRING1] = {
+ .caps = 0,
+ .tp_type = WSP_TRACKPAD_TYPE1,
+ .finger_data_offset = WSP_TYPE1_FINGER_DATA_OFFSET,
+ },
+ [WELLSPRING2] = {
+ .caps = 0,
+ .tp_type = WSP_TRACKPAD_TYPE1,
+ .finger_data_offset = WSP_TYPE1_FINGER_DATA_OFFSET,
+ },
+ [WELLSPRING3] = {
+ .caps = HAS_INTEGRATED_BUTTON,
+ .tp_type = WSP_TRACKPAD_TYPE2,
+ .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET,
+ },
+ [WELLSPRING4] = {
+ .caps = HAS_INTEGRATED_BUTTON,
+ .tp_type = WSP_TRACKPAD_TYPE2,
+ .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET,
+ },
+ [WELLSPRING4A] = {
+ .caps = HAS_INTEGRATED_BUTTON,
+ .tp_type = WSP_TRACKPAD_TYPE2,
+ .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET,
+ },
+ [WELLSPRING5] = {
+ .caps = HAS_INTEGRATED_BUTTON,
+ .tp_type = WSP_TRACKPAD_TYPE2,
+ .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET,
+ },
+ [WELLSPRING6] = {
+ .caps = HAS_INTEGRATED_BUTTON,
+ .tp_type = WSP_TRACKPAD_TYPE2,
+ .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET,
+ },
+ [WELLSPRING5A] = {
+ .caps = HAS_INTEGRATED_BUTTON,
+ .tp_type = WSP_TRACKPAD_TYPE2,
+ .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET,
+ },
+ [WELLSPRING6A] = {
+ .caps = HAS_INTEGRATED_BUTTON,
+ .tp_type = WSP_TRACKPAD_TYPE2,
+ .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET,
+ },
+ [WELLSPRING7] = {
+ .caps = HAS_INTEGRATED_BUTTON,
+ .tp_type = WSP_TRACKPAD_TYPE2,
+ .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET,
+ },
+ [WELLSPRING7A] = {
+ .caps = HAS_INTEGRATED_BUTTON,
+ .tp_type = WSP_TRACKPAD_TYPE2,
+ .finger_data_offset = WSP_TYPE2_FINGER_DATA_OFFSET,
+ },
+ [WELLSPRING8] = {
+ .caps = HAS_INTEGRATED_BUTTON,
+ .tp_type = WSP_TRACKPAD_TYPE3,
+ .finger_data_offset = WSP_TYPE3_FINGER_DATA_OFFSET,
+ },
+};
+#define ATP_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
+
+/* TODO: STRUCT_USB_HOST_ID */
+static const struct usb_device_id wsp_devs[] = {
+ /* MacbookAir1.1 */
+ ATP_DEV(APPLE, WELLSPRING_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING1)),
+ ATP_DEV(APPLE, WELLSPRING_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING1)),
+ ATP_DEV(APPLE, WELLSPRING_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING1)),
+
+ /* MacbookProPenryn, aka wellspring2 */
+ ATP_DEV(APPLE, WELLSPRING2_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING2)),
+ ATP_DEV(APPLE, WELLSPRING2_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING2)),
+ ATP_DEV(APPLE, WELLSPRING2_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING2)),
+
+ /* Macbook5,1 (unibody), aka wellspring3 */
+ ATP_DEV(APPLE, WELLSPRING3_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING3)),
+ ATP_DEV(APPLE, WELLSPRING3_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING3)),
+ ATP_DEV(APPLE, WELLSPRING3_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING3)),
+
+ /* MacbookAir3,2 (unibody), aka wellspring4 */
+ ATP_DEV(APPLE, WELLSPRING4_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4)),
+ ATP_DEV(APPLE, WELLSPRING4_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING4)),
+ ATP_DEV(APPLE, WELLSPRING4_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING4)),
+
+ /* MacbookAir3,1 (unibody), aka wellspring4 */
+ ATP_DEV(APPLE, WELLSPRING4A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
+ ATP_DEV(APPLE, WELLSPRING4A_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
+ ATP_DEV(APPLE, WELLSPRING4A_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
+
+ /* Macbook8 (unibody, March 2011) */
+ ATP_DEV(APPLE, WELLSPRING5_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5)),
+ ATP_DEV(APPLE, WELLSPRING5_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING5)),
+ ATP_DEV(APPLE, WELLSPRING5_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING5)),
+
+ /* MacbookAir4,1 (unibody, July 2011) */
+ ATP_DEV(APPLE, WELLSPRING6A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
+ ATP_DEV(APPLE, WELLSPRING6A_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
+ ATP_DEV(APPLE, WELLSPRING6A_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
+
+ /* MacbookAir4,2 (unibody, July 2011) */
+ ATP_DEV(APPLE, WELLSPRING6_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6)),
+ ATP_DEV(APPLE, WELLSPRING6_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING6)),
+ ATP_DEV(APPLE, WELLSPRING6_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING6)),
+
+ /* Macbook8,2 (unibody) */
+ ATP_DEV(APPLE, WELLSPRING5A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
+ ATP_DEV(APPLE, WELLSPRING5A_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
+ ATP_DEV(APPLE, WELLSPRING5A_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
+
+ /* MacbookPro10,1 (unibody, June 2012) */
+ /* MacbookPro11,? (unibody, June 2013) */
+ ATP_DEV(APPLE, WELLSPRING7_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7)),
+ ATP_DEV(APPLE, WELLSPRING7_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING7)),
+ ATP_DEV(APPLE, WELLSPRING7_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING7)),
+
+ /* MacbookPro10,2 (unibody, October 2012) */
+ ATP_DEV(APPLE, WELLSPRING7A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
+ ATP_DEV(APPLE, WELLSPRING7A_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
+ ATP_DEV(APPLE, WELLSPRING7A_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
+
+ /* MacbookAir6,2 (unibody, June 2013) */
+ ATP_DEV(APPLE, WELLSPRING8_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING8)),
+ ATP_DEV(APPLE, WELLSPRING8_ISO, WELLSPRING_DRIVER_INFO(WELLSPRING8)),
+ ATP_DEV(APPLE, WELLSPRING8_JIS, WELLSPRING_DRIVER_INFO(WELLSPRING8)),
+};
+
+typedef enum atp_stroke_type {
+ ATP_STROKE_TOUCH,
+ ATP_STROKE_SLIDE,
+} atp_stroke_type;
+
+typedef enum atp_axis {
+ X = 0,
+ Y = 1,
+ NUM_AXES
+} atp_axis;
+
+#define ATP_FIFO_BUF_SIZE 8 /* bytes */
+#define ATP_FIFO_QUEUE_MAXLEN 50 /* units */
+
+enum {
+ ATP_INTR_DT,
+ ATP_RESET,
+ ATP_N_TRANSFER,
+};
+
+typedef struct fg_stroke_component {
+ /* Fields encapsulating the pressure-span. */
+ u_int loc; /* location (scaled) */
+ u_int cum_pressure; /* cumulative compression */
+ u_int max_cum_pressure; /* max cumulative compression */
+ boolean_t matched; /*to track components as they match against pspans.*/
+
+ int delta_mickeys; /* change in location (un-smoothened movement)*/
+} fg_stroke_component_t;
+
+/*
+ * The following structure captures a finger contact with the
+ * touchpad. A stroke comprises two p-span components and some state.
+ */
+typedef struct atp_stroke {
+ TAILQ_ENTRY(atp_stroke) entry;
+
+ atp_stroke_type type;
+ uint32_t flags; /* the state of this stroke */
+#define ATSF_ZOMBIE 0x1
+ boolean_t matched; /* to track match against fingers.*/
+
+ struct timeval ctime; /* create time; for coincident siblings. */
+
+ /*
+ * Unit: interrupts; we maintain this value in
+ * addition to 'ctime' in order to avoid the
+ * expensive call to microtime() at every
+ * interrupt.
+ */
+ uint32_t age;
+
+ /* Location */
+ int x;
+ int y;
+
+ /* Fields containing information about movement. */
+ int instantaneous_dx; /* curr. change in X location (un-smoothened) */
+ int instantaneous_dy; /* curr. change in Y location (un-smoothened) */
+ int pending_dx; /* cum. of pending short movements */
+ int pending_dy; /* cum. of pending short movements */
+ int movement_dx; /* interpreted smoothened movement */
+ int movement_dy; /* interpreted smoothened movement */
+ int cum_movement_x; /* cum. horizontal movement */
+ int cum_movement_y; /* cum. vertical movement */
+
+ /*
+ * The following member is relevant only for fountain-geyser trackpads.
+ * For these, there is the need to track pressure-spans and cumulative
+ * pressures for stroke components.
+ */
+ fg_stroke_component_t components[NUM_AXES];
+} atp_stroke_t;
+
+struct atp_softc; /* forward declaration */
+typedef void (*sensor_data_interpreter_t)(struct atp_softc *sc, u_int len);
+
+struct atp_softc {
+ device_t sc_dev;
+ struct usb_device *sc_usb_device;
+ struct mtx sc_mutex; /* for synchronization */
+ struct usb_fifo_sc sc_fifo;
+
+#define MODE_LENGTH 8
+ char sc_mode_bytes[MODE_LENGTH]; /* device mode */
+
+ trackpad_family_t sc_family;
+ const void *sc_params; /* device configuration */
+ sensor_data_interpreter_t sensor_data_interpreter;
+
+ mousehw_t sc_hw;
+ mousemode_t sc_mode;
+ mousestatus_t sc_status;
+
+ u_int sc_state;
+#define ATP_ENABLED 0x01
+#define ATP_ZOMBIES_EXIST 0x02
+#define ATP_DOUBLE_TAP_DRAG 0x04
+#define ATP_VALID 0x08
+
+ struct usb_xfer *sc_xfer[ATP_N_TRANSFER];
+
+ u_int sc_pollrate;
+ int sc_fflags;
+
+ atp_stroke_t sc_strokes_data[ATP_MAX_STROKES];
+ TAILQ_HEAD(,atp_stroke) sc_stroke_free;
+ TAILQ_HEAD(,atp_stroke) sc_stroke_used;
+ u_int sc_n_strokes;
+
+ struct callout sc_callout;
+
+ /*
+ * button status. Set to non-zero if the mouse-button is physically
+ * pressed. This state variable is exposed through softc to allow
+ * reap_sibling_zombies to avoid registering taps while the trackpad
+ * button is pressed.
+ */
+ uint8_t sc_ibtn;
+
+ /*
+ * Time when touch zombies were last reaped; useful for detecting
+ * double-touch-n-drag.
+ */
+ struct timeval sc_touch_reap_time;
+
+ u_int sc_idlecount;
+
+ /* Regarding the data transferred from t-pad in USB INTR packets. */
+ u_int sc_expected_sensor_data_len;
+ uint8_t sc_sensor_data[ATP_SENSOR_DATA_BUF_MAX] __aligned(4);
+
+ int sc_cur_x[FG_MAX_XSENSORS]; /* current sensor readings */
+ int sc_cur_y[FG_MAX_YSENSORS];
+ int sc_base_x[FG_MAX_XSENSORS]; /* base sensor readings */
+ int sc_base_y[FG_MAX_YSENSORS];
+ int sc_pressure_x[FG_MAX_XSENSORS]; /* computed pressures */
+ int sc_pressure_y[FG_MAX_YSENSORS];
+ fg_pspan sc_pspans_x[FG_MAX_PSPANS_PER_AXIS];
+ fg_pspan sc_pspans_y[FG_MAX_PSPANS_PER_AXIS];
+};
+
+/*
+ * The last byte of the fountain-geyser sensor data contains status bits; the
+ * following values define the meanings of these bits.
+ * (only Geyser 3/4)
+ */
+enum geyser34_status_bits {
+ FG_STATUS_BUTTON = (uint8_t)0x01, /* The button was pressed */
+ FG_STATUS_BASE_UPDATE = (uint8_t)0x04, /* Data from an untouched pad.*/
+};
+
+typedef enum interface_mode {
+ RAW_SENSOR_MODE = (uint8_t)0x01,
+ HID_MODE = (uint8_t)0x08
+} interface_mode;
+
+/*
+ * function prototypes
+ */
+static usb_fifo_cmd_t atp_start_read;
+static usb_fifo_cmd_t atp_stop_read;
+static usb_fifo_open_t atp_open;
+static usb_fifo_close_t atp_close;
+static usb_fifo_ioctl_t atp_ioctl;
+
+static struct usb_fifo_methods atp_fifo_methods = {
+ .f_open = &atp_open,
+ .f_close = &atp_close,
+ .f_ioctl = &atp_ioctl,
+ .f_start_read = &atp_start_read,
+ .f_stop_read = &atp_stop_read,
+ .basename[0] = ATP_DRIVER_NAME,
+};
+
+/* device initialization and shutdown */
+static usb_error_t atp_set_device_mode(struct atp_softc *, interface_mode);
+static void atp_reset_callback(struct usb_xfer *, usb_error_t);
+static int atp_enable(struct atp_softc *);
+static void atp_disable(struct atp_softc *);
+
+/* sensor interpretation */
+static void fg_interpret_sensor_data(struct atp_softc *, u_int);
+static void fg_extract_sensor_data(const int8_t *, u_int, atp_axis,
+ int *, enum fountain_geyser_trackpad_type);
+static void fg_get_pressures(int *, const int *, const int *, int);
+static void fg_detect_pspans(int *, u_int, u_int, fg_pspan *, u_int *);
+static void wsp_interpret_sensor_data(struct atp_softc *, u_int);
+
+/* movement detection */
+static boolean_t fg_match_stroke_component(fg_stroke_component_t *,
+ const fg_pspan *, atp_stroke_type);
+static void fg_match_strokes_against_pspans(struct atp_softc *,
+ atp_axis, fg_pspan *, u_int, u_int);
+static boolean_t wsp_match_strokes_against_fingers(struct atp_softc *,
+ wsp_finger_t *, u_int);
+static boolean_t fg_update_strokes(struct atp_softc *, fg_pspan *, u_int,
+ fg_pspan *, u_int);
+static boolean_t wsp_update_strokes(struct atp_softc *,
+ wsp_finger_t [WSP_MAX_FINGERS], u_int);
+static void fg_add_stroke(struct atp_softc *, const fg_pspan *, const fg_pspan *);
+static void fg_add_new_strokes(struct atp_softc *, fg_pspan *,
+ u_int, fg_pspan *, u_int);
+static void wsp_add_stroke(struct atp_softc *, const wsp_finger_t *);
+static void atp_advance_stroke_state(struct atp_softc *,
+ atp_stroke_t *, boolean_t *);
+static boolean_t atp_stroke_has_small_movement(const atp_stroke_t *);
+static void atp_update_pending_mickeys(atp_stroke_t *);
+static boolean_t atp_compute_stroke_movement(atp_stroke_t *);
+static void atp_terminate_stroke(struct atp_softc *, atp_stroke_t *);
+
+/* tap detection */
+static boolean_t atp_is_horizontal_scroll(const atp_stroke_t *);
+static boolean_t atp_is_vertical_scroll(const atp_stroke_t *);
+static void atp_reap_sibling_zombies(void *);
+static void atp_convert_to_slide(struct atp_softc *, atp_stroke_t *);
+
+/* updating fifo */
+static void atp_reset_buf(struct atp_softc *);
+static void atp_add_to_queue(struct atp_softc *, int, int, int, uint32_t);
+
+/* Device methods. */
+static device_probe_t atp_probe;
+static device_attach_t atp_attach;
+static device_detach_t atp_detach;
+static usb_callback_t atp_intr;
+
+static const struct usb_config atp_xfer_config[ATP_N_TRANSFER] = {
+ [ATP_INTR_DT] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {
+ .pipe_bof = 1, /* block pipe on failure */
+ .short_xfer_ok = 1,
+ },
+ .bufsize = ATP_SENSOR_DATA_BUF_MAX,
+ .callback = &atp_intr,
+ },
+ [ATP_RESET] = {
+ .type = UE_CONTROL,
+ .endpoint = 0, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request) + MODE_LENGTH,
+ .callback = &atp_reset_callback,
+ .interval = 0, /* no pre-delay */
+ },
+};
+
+static atp_stroke_t *
+atp_alloc_stroke(struct atp_softc *sc)
+{
+ atp_stroke_t *pstroke;
+
+ pstroke = TAILQ_FIRST(&sc->sc_stroke_free);
+ if (pstroke == NULL)
+ goto done;
+
+ TAILQ_REMOVE(&sc->sc_stroke_free, pstroke, entry);
+ memset(pstroke, 0, sizeof(*pstroke));
+ TAILQ_INSERT_TAIL(&sc->sc_stroke_used, pstroke, entry);
+
+ sc->sc_n_strokes++;
+done:
+ return (pstroke);
+}
+
+static void
+atp_free_stroke(struct atp_softc *sc, atp_stroke_t *pstroke)
+{
+ if (pstroke == NULL)
+ return;
+
+ sc->sc_n_strokes--;
+
+ TAILQ_REMOVE(&sc->sc_stroke_used, pstroke, entry);
+ TAILQ_INSERT_TAIL(&sc->sc_stroke_free, pstroke, entry);
+}
+
+static void
+atp_init_stroke_pool(struct atp_softc *sc)
+{
+ u_int x;
+
+ TAILQ_INIT(&sc->sc_stroke_free);
+ TAILQ_INIT(&sc->sc_stroke_used);
+
+ sc->sc_n_strokes = 0;
+
+ memset(&sc->sc_strokes_data, 0, sizeof(sc->sc_strokes_data));
+
+ for (x = 0; x != ATP_MAX_STROKES; x++) {
+ TAILQ_INSERT_TAIL(&sc->sc_stroke_free, &sc->sc_strokes_data[x],
+ entry);
+ }
+}
+
+static usb_error_t
+atp_set_device_mode(struct atp_softc *sc, interface_mode newMode)
+{
+ uint8_t mode_value;
+ usb_error_t err;
+
+ if ((newMode != RAW_SENSOR_MODE) && (newMode != HID_MODE))
+ return (USB_ERR_INVAL);
+
+ if ((newMode == RAW_SENSOR_MODE) &&
+ (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER))
+ mode_value = (uint8_t)0x04;
+ else
+ mode_value = newMode;
+
+ err = usbd_req_get_report(sc->sc_usb_device, NULL /* mutex */,
+ sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */,
+ 0x03 /* type */, 0x00 /* id */);
+ if (err != USB_ERR_NORMAL_COMPLETION) {
+ DPRINTF("Failed to read device mode (%d)\n", err);
+ return (err);
+ }
+
+ if (sc->sc_mode_bytes[0] == mode_value)
+ return (err);
+
+ /*
+ * XXX Need to wait at least 250ms for hardware to get
+ * ready. The device mode handling appears to be handled
+ * asynchronously and we should not issue these commands too
+ * quickly.
+ */
+ pause("WHW", hz / 4);
+
+ sc->sc_mode_bytes[0] = mode_value;
+ return (usbd_req_set_report(sc->sc_usb_device, NULL /* mutex */,
+ sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */,
+ 0x03 /* type */, 0x00 /* id */));
+}
+
+static void
+atp_reset_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ usb_device_request_t req;
+ struct usb_page_cache *pc;
+ struct atp_softc *sc = usbd_xfer_softc(xfer);
+
+ uint8_t mode_value;
+ if (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER)
+ mode_value = 0x04;
+ else
+ mode_value = RAW_SENSOR_MODE;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ sc->sc_mode_bytes[0] = mode_value;
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UR_SET_REPORT;
+ USETW2(req.wValue,
+ (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, MODE_LENGTH);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+ pc = usbd_xfer_get_frame(xfer, 1);
+ usbd_copy_in(pc, 0, sc->sc_mode_bytes, MODE_LENGTH);
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, MODE_LENGTH);
+ usbd_xfer_set_frames(xfer, 2);
+ usbd_transfer_submit(xfer);
+ break;
+
+ case USB_ST_TRANSFERRED:
+ default:
+ break;
+ }
+}
+
+static int
+atp_enable(struct atp_softc *sc)
+{
+ if (sc->sc_state & ATP_ENABLED)
+ return (0);
+
+ /* reset status */
+ memset(&sc->sc_status, 0, sizeof(sc->sc_status));
+
+ atp_init_stroke_pool(sc);
+
+ sc->sc_state |= ATP_ENABLED;
+
+ DPRINTFN(ATP_LLEVEL_INFO, "enabled atp\n");
+ return (0);
+}
+
+static void
+atp_disable(struct atp_softc *sc)
+{
+ sc->sc_state &= ~(ATP_ENABLED | ATP_VALID);
+ DPRINTFN(ATP_LLEVEL_INFO, "disabled atp\n");
+}
+
+static void
+fg_interpret_sensor_data(struct atp_softc *sc, u_int data_len)
+{
+ u_int n_xpspans = 0;
+ u_int n_ypspans = 0;
+ uint8_t status_bits;
+
+ const struct fg_dev_params *params =
+ (const struct fg_dev_params *)sc->sc_params;
+
+ fg_extract_sensor_data(sc->sc_sensor_data, params->n_xsensors, X,
+ sc->sc_cur_x, params->prot);
+ fg_extract_sensor_data(sc->sc_sensor_data, params->n_ysensors, Y,
+ sc->sc_cur_y, params->prot);
+
+ /*
+ * If this is the initial update (from an untouched
+ * pad), we should set the base values for the sensor
+ * data; deltas with respect to these base values can
+ * be used as pressure readings subsequently.
+ */
+ status_bits = sc->sc_sensor_data[params->data_len - 1];
+ if (((params->prot == FG_TRACKPAD_TYPE_GEYSER3) ||
+ (params->prot == FG_TRACKPAD_TYPE_GEYSER4)) &&
+ ((sc->sc_state & ATP_VALID) == 0)) {
+ if (status_bits & FG_STATUS_BASE_UPDATE) {
+ memcpy(sc->sc_base_x, sc->sc_cur_x,
+ params->n_xsensors * sizeof(*sc->sc_base_x));
+ memcpy(sc->sc_base_y, sc->sc_cur_y,
+ params->n_ysensors * sizeof(*sc->sc_base_y));
+ sc->sc_state |= ATP_VALID;
+ return;
+ }
+ }
+
+ /* Get pressure readings and detect p-spans for both axes. */
+ fg_get_pressures(sc->sc_pressure_x, sc->sc_cur_x, sc->sc_base_x,
+ params->n_xsensors);
+ fg_detect_pspans(sc->sc_pressure_x, params->n_xsensors,
+ FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_x, &n_xpspans);
+ fg_get_pressures(sc->sc_pressure_y, sc->sc_cur_y, sc->sc_base_y,
+ params->n_ysensors);
+ fg_detect_pspans(sc->sc_pressure_y, params->n_ysensors,
+ FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_y, &n_ypspans);
+
+ /* Update strokes with new pspans to detect movements. */
+ if (fg_update_strokes(sc, sc->sc_pspans_x, n_xpspans, sc->sc_pspans_y, n_ypspans))
+ sc->sc_status.flags |= MOUSE_POSCHANGED;
+
+ sc->sc_ibtn = (status_bits & FG_STATUS_BUTTON) ? MOUSE_BUTTON1DOWN : 0;
+ sc->sc_status.button = sc->sc_ibtn;
+
+ /*
+ * The Fountain/Geyser device continues to trigger interrupts
+ * at a fast rate even after touchpad activity has
+ * stopped. Upon detecting that the device has remained idle
+ * beyond a threshold, we reinitialize it to silence the
+ * interrupts.
+ */
+ if ((sc->sc_status.flags == 0) && (sc->sc_n_strokes == 0)) {
+ sc->sc_idlecount++;
+ if (sc->sc_idlecount >= ATP_IDLENESS_THRESHOLD) {
+ /*
+ * Use the last frame before we go idle for
+ * calibration on pads which do not send
+ * calibration frames.
+ */
+ const struct fg_dev_params *params =
+ (const struct fg_dev_params *)sc->sc_params;
+
+ DPRINTFN(ATP_LLEVEL_INFO, "idle\n");
+
+ if (params->prot < FG_TRACKPAD_TYPE_GEYSER3) {
+ memcpy(sc->sc_base_x, sc->sc_cur_x,
+ params->n_xsensors * sizeof(*(sc->sc_base_x)));
+ memcpy(sc->sc_base_y, sc->sc_cur_y,
+ params->n_ysensors * sizeof(*(sc->sc_base_y)));
+ }
+
+ sc->sc_idlecount = 0;
+ usbd_transfer_start(sc->sc_xfer[ATP_RESET]);
+ }
+ } else {
+ sc->sc_idlecount = 0;
+ }
+}
+
+/*
+ * Interpret the data from the X and Y pressure sensors. This function
+ * is called separately for the X and Y sensor arrays. The data in the
+ * USB packet is laid out in the following manner:
+ *
+ * sensor_data:
+ * --,--,Y1,Y2,--,Y3,Y4,--,Y5,...,Y10, ... X1,X2,--,X3,X4
+ * indices: 0 1 2 3 4 5 6 7 8 ... 15 ... 20 21 22 23 24
+ *
+ * '--' (in the above) indicates that the value is unimportant.
+ *
+ * Information about the above layout was obtained from the
+ * implementation of the AppleTouch driver in Linux.
+ *
+ * parameters:
+ * sensor_data
+ * raw sensor data from the USB packet.
+ * num
+ * The number of elements in the array 'arr'.
+ * axis
+ * Axis of data to fetch
+ * arr
+ * The array to be initialized with the readings.
+ * prot
+ * The protocol to use to interpret the data
+ */
+static void
+fg_extract_sensor_data(const int8_t *sensor_data, u_int num, atp_axis axis,
+ int *arr, enum fountain_geyser_trackpad_type prot)
+{
+ u_int i;
+ u_int di; /* index into sensor data */
+
+ switch (prot) {
+ case FG_TRACKPAD_TYPE_GEYSER1:
+ /*
+ * For Geyser 1, the sensors are laid out in pairs
+ * every 5 bytes.
+ */
+ for (i = 0, di = (axis == Y) ? 1 : 2; i < 8; di += 5, i++) {
+ arr[i] = sensor_data[di];
+ arr[i+8] = sensor_data[di+2];
+ if ((axis == X) && (num > 16))
+ arr[i+16] = sensor_data[di+40];
+ }
+
+ break;
+ case FG_TRACKPAD_TYPE_GEYSER2:
+ for (i = 0, di = (axis == Y) ? 1 : 19; i < num; /* empty */ ) {
+ arr[i++] = sensor_data[di++];
+ arr[i++] = sensor_data[di++];
+ di++;
+ }
+ break;
+ case FG_TRACKPAD_TYPE_GEYSER3:
+ case FG_TRACKPAD_TYPE_GEYSER4:
+ for (i = 0, di = (axis == Y) ? 2 : 20; i < num; /* empty */ ) {
+ arr[i++] = sensor_data[di++];
+ arr[i++] = sensor_data[di++];
+ di++;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+fg_get_pressures(int *p, const int *cur, const int *base, int n)
+{
+ int i;
+
+ for (i = 0; i < n; i++) {
+ p[i] = cur[i] - base[i];
+ if (p[i] > 127)
+ p[i] -= 256;
+ if (p[i] < -127)
+ p[i] += 256;
+ if (p[i] < 0)
+ p[i] = 0;
+
+ /*
+ * Shave off pressures below the noise-pressure
+ * threshold; this will reduce the contribution from
+ * lower pressure readings.
+ */
+ if ((u_int)p[i] <= FG_SENSOR_NOISE_THRESHOLD)
+ p[i] = 0; /* filter away noise */
+ else
+ p[i] -= FG_SENSOR_NOISE_THRESHOLD;
+ }
+}
+
+static void
+fg_detect_pspans(int *p, u_int num_sensors,
+ u_int max_spans, /* max # of pspans permitted */
+ fg_pspan *spans, /* finger spans */
+ u_int *nspans_p) /* num spans detected */
+{
+ u_int i;
+ int maxp; /* max pressure seen within a span */
+ u_int num_spans = 0;
+
+ enum fg_pspan_state {
+ ATP_PSPAN_INACTIVE,
+ ATP_PSPAN_INCREASING,
+ ATP_PSPAN_DECREASING,
+ } state; /* state of the pressure span */
+
+ /*
+ * The following is a simple state machine to track
+ * the phase of the pressure span.
+ */
+ memset(spans, 0, max_spans * sizeof(fg_pspan));
+ maxp = 0;
+ state = ATP_PSPAN_INACTIVE;
+ for (i = 0; i < num_sensors; i++) {
+ if (num_spans >= max_spans)
+ break;
+
+ if (p[i] == 0) {
+ if (state == ATP_PSPAN_INACTIVE) {
+ /*
+ * There is no pressure information for this
+ * sensor, and we aren't tracking a finger.
+ */
+ continue;
+ } else {
+ state = ATP_PSPAN_INACTIVE;
+ maxp = 0;
+ num_spans++;
+ }
+ } else {
+ switch (state) {
+ case ATP_PSPAN_INACTIVE:
+ state = ATP_PSPAN_INCREASING;
+ maxp = p[i];
+ break;
+
+ case ATP_PSPAN_INCREASING:
+ if (p[i] > maxp)
+ maxp = p[i];
+ else if (p[i] <= (maxp >> 1))
+ state = ATP_PSPAN_DECREASING;
+ break;
+
+ case ATP_PSPAN_DECREASING:
+ if (p[i] > p[i - 1]) {
+ /*
+ * This is the beginning of
+ * another span; change state
+ * to give the appearance that
+ * we're starting from an
+ * inactive span, and then
+ * re-process this reading in
+ * the next iteration.
+ */
+ num_spans++;
+ state = ATP_PSPAN_INACTIVE;
+ maxp = 0;
+ i--;
+ continue;
+ }
+ break;
+ }
+
+ /* Update the finger span with this reading. */
+ spans[num_spans].width++;
+ spans[num_spans].cum += p[i];
+ spans[num_spans].cog += p[i] * (i + 1);
+ }
+ }
+ if (state != ATP_PSPAN_INACTIVE)
+ num_spans++; /* close the last finger span */
+
+ /* post-process the spans */
+ for (i = 0; i < num_spans; i++) {
+ /* filter away unwanted pressure spans */
+ if ((spans[i].cum < FG_PSPAN_MIN_CUM_PRESSURE) ||
+ (spans[i].width > FG_PSPAN_MAX_WIDTH)) {
+ if ((i + 1) < num_spans) {
+ memcpy(&spans[i], &spans[i + 1],
+ (num_spans - i - 1) * sizeof(fg_pspan));
+ i--;
+ }
+ num_spans--;
+ continue;
+ }
+
+ /* compute this span's representative location */
+ spans[i].loc = spans[i].cog * FG_SCALE_FACTOR /
+ spans[i].cum;
+
+ spans[i].matched = false; /* not yet matched against a stroke */
+ }
+
+ *nspans_p = num_spans;
+}
+
+static void
+wsp_interpret_sensor_data(struct atp_softc *sc, u_int data_len)
+{
+ const struct wsp_dev_params *params = sc->sc_params;
+ wsp_finger_t fingers[WSP_MAX_FINGERS];
+ struct wsp_finger_sensor_data *source_fingerp;
+ u_int n_source_fingers;
+ u_int n_fingers;
+ u_int i;
+
+ /* validate sensor data length */
+ if ((data_len < params->finger_data_offset) ||
+ ((data_len - params->finger_data_offset) %
+ WSP_SIZEOF_FINGER_SENSOR_DATA) != 0)
+ return;
+
+ /* compute number of source fingers */
+ n_source_fingers = (data_len - params->finger_data_offset) /
+ WSP_SIZEOF_FINGER_SENSOR_DATA;
+
+ if (n_source_fingers > WSP_MAX_FINGERS)
+ n_source_fingers = WSP_MAX_FINGERS;
+
+ /* iterate over the source data collecting useful fingers */
+ n_fingers = 0;
+ source_fingerp = (struct wsp_finger_sensor_data *)(sc->sc_sensor_data +
+ params->finger_data_offset);
+
+ for (i = 0; i < n_source_fingers; i++, source_fingerp++) {
+ /* swap endianness, if any */
+ if (le16toh(0x1234) != 0x1234) {
+ source_fingerp->origin = le16toh((uint16_t)source_fingerp->origin);
+ source_fingerp->abs_x = le16toh((uint16_t)source_fingerp->abs_x);
+ source_fingerp->abs_y = le16toh((uint16_t)source_fingerp->abs_y);
+ source_fingerp->rel_x = le16toh((uint16_t)source_fingerp->rel_x);
+ source_fingerp->rel_y = le16toh((uint16_t)source_fingerp->rel_y);
+ source_fingerp->tool_major = le16toh((uint16_t)source_fingerp->tool_major);
+ source_fingerp->tool_minor = le16toh((uint16_t)source_fingerp->tool_minor);
+ source_fingerp->orientation = le16toh((uint16_t)source_fingerp->orientation);
+ source_fingerp->touch_major = le16toh((uint16_t)source_fingerp->touch_major);
+ source_fingerp->touch_minor = le16toh((uint16_t)source_fingerp->touch_minor);
+ source_fingerp->multi = le16toh((uint16_t)source_fingerp->multi);
+ }
+
+ /* check for minium threshold */
+ if (source_fingerp->touch_major == 0)
+ continue;
+
+ fingers[n_fingers].matched = false;
+ fingers[n_fingers].x = source_fingerp->abs_x;
+ fingers[n_fingers].y = -source_fingerp->abs_y;
+
+ n_fingers++;
+ }
+
+ if ((sc->sc_n_strokes == 0) && (n_fingers == 0))
+ return;
+
+ if (wsp_update_strokes(sc, fingers, n_fingers))
+ sc->sc_status.flags |= MOUSE_POSCHANGED;
+
+ switch(params->tp_type) {
+ case WSP_TRACKPAD_TYPE2:
+ sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE2_BUTTON_DATA_OFFSET];
+ break;
+ case WSP_TRACKPAD_TYPE3:
+ sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE3_BUTTON_DATA_OFFSET];
+ break;
+ default:
+ break;
+ }
+ sc->sc_status.button = sc->sc_ibtn ? MOUSE_BUTTON1DOWN : 0;
+}
+
+/*
+ * Match a pressure-span against a stroke-component. If there is a
+ * match, update the component's state and return true.
+ */
+static boolean_t
+fg_match_stroke_component(fg_stroke_component_t *component,
+ const fg_pspan *pspan, atp_stroke_type stroke_type)
+{
+ int delta_mickeys;
+ u_int min_pressure;
+
+ delta_mickeys = pspan->loc - component->loc;
+
+ if (abs(delta_mickeys) > (int)FG_MAX_DELTA_MICKEYS)
+ return (false); /* the finger span is too far out; no match */
+
+ component->loc = pspan->loc;
+
+ /*
+ * A sudden and significant increase in a pspan's cumulative
+ * pressure indicates the incidence of a new finger
+ * contact. This usually revises the pspan's
+ * centre-of-gravity, and hence the location of any/all
+ * matching stroke component(s). But such a change should
+ * *not* be interpreted as a movement.
+ */
+ if (pspan->cum > ((3 * component->cum_pressure) >> 1))
+ delta_mickeys = 0;
+
+ component->cum_pressure = pspan->cum;
+ if (pspan->cum > component->max_cum_pressure)
+ component->max_cum_pressure = pspan->cum;
+
+ /*
+ * Disregard the component's movement if its cumulative
+ * pressure drops below a fraction of the maximum; this
+ * fraction is determined based on the stroke's type.
+ */
+ if (stroke_type == ATP_STROKE_TOUCH)
+ min_pressure = (3 * component->max_cum_pressure) >> 2;
+ else
+ min_pressure = component->max_cum_pressure >> 2;
+ if (component->cum_pressure < min_pressure)
+ delta_mickeys = 0;
+
+ component->delta_mickeys = delta_mickeys;
+ return (true);
+}
+
+static void
+fg_match_strokes_against_pspans(struct atp_softc *sc, atp_axis axis,
+ fg_pspan *pspans, u_int n_pspans, u_int repeat_count)
+{
+ atp_stroke_t *strokep;
+ u_int repeat_index = 0;
+ u_int i;
+
+ /* Determine the index of the multi-span. */
+ if (repeat_count) {
+ for (i = 0; i < n_pspans; i++) {
+ if (pspans[i].cum > pspans[repeat_index].cum)
+ repeat_index = i;
+ }
+ }
+
+ TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+ if (strokep->components[axis].matched)
+ continue; /* skip matched components */
+
+ for (i = 0; i < n_pspans; i++) {
+ if (pspans[i].matched)
+ continue; /* skip matched pspans */
+
+ if (fg_match_stroke_component(
+ &strokep->components[axis], &pspans[i],
+ strokep->type)) {
+ /* There is a match. */
+ strokep->components[axis].matched = true;
+
+ /* Take care to repeat at the multi-span. */
+ if ((repeat_count > 0) && (i == repeat_index))
+ repeat_count--;
+ else
+ pspans[i].matched = true;
+
+ break; /* skip to the next strokep */
+ }
+ } /* loop over pspans */
+ } /* loop over strokes */
+}
+
+static boolean_t
+wsp_match_strokes_against_fingers(struct atp_softc *sc,
+ wsp_finger_t *fingers, u_int n_fingers)
+{
+ boolean_t movement = false;
+ atp_stroke_t *strokep;
+ u_int i;
+
+ /* reset the matched status for all strokes */
+ TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry)
+ strokep->matched = false;
+
+ for (i = 0; i != n_fingers; i++) {
+ u_int least_distance_sq = WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ;
+ atp_stroke_t *strokep_best = NULL;
+
+ TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+ int instantaneous_dx;
+ int instantaneous_dy;
+ u_int d_squared;
+
+ if (strokep->matched)
+ continue;
+
+ instantaneous_dx = fingers[i].x - strokep->x;
+ instantaneous_dy = fingers[i].y - strokep->y;
+
+ /* skip strokes which are far away */
+ d_squared =
+ (instantaneous_dx * instantaneous_dx) +
+ (instantaneous_dy * instantaneous_dy);
+
+ if (d_squared < least_distance_sq) {
+ least_distance_sq = d_squared;
+ strokep_best = strokep;
+ }
+ }
+
+ strokep = strokep_best;
+
+ if (strokep != NULL) {
+ fingers[i].matched = true;
+
+ strokep->matched = true;
+ strokep->instantaneous_dx = fingers[i].x - strokep->x;
+ strokep->instantaneous_dy = fingers[i].y - strokep->y;
+ strokep->x = fingers[i].x;
+ strokep->y = fingers[i].y;
+
+ atp_advance_stroke_state(sc, strokep, &movement);
+ }
+ }
+ return (movement);
+}
+
+/*
+ * Update strokes by matching against current pressure-spans.
+ * Return true if any movement is detected.
+ */
+static boolean_t
+fg_update_strokes(struct atp_softc *sc, fg_pspan *pspans_x,
+ u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans)
+{
+ atp_stroke_t *strokep;
+ atp_stroke_t *strokep_next;
+ boolean_t movement = false;
+ u_int repeat_count = 0;
+ u_int i;
+ u_int j;
+
+ /* Reset X and Y components of all strokes as unmatched. */
+ TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+ strokep->components[X].matched = false;
+ strokep->components[Y].matched = false;
+ }
+
+ /*
+ * Usually, the X and Y pspans come in pairs (the common case
+ * being a single pair). It is possible, however, that
+ * multiple contacts resolve to a single pspan along an
+ * axis, as illustrated in the following:
+ *
+ * F = finger-contact
+ *
+ * pspan pspan
+ * +-----------------------+
+ * | . . |
+ * | . . |
+ * | . . |
+ * | . . |
+ * pspan |.........F......F |
+ * | |
+ * | |
+ * | |
+ * +-----------------------+
+ *
+ *
+ * The above case can be detected by a difference in the
+ * number of X and Y pspans. When this happens, X and Y pspans
+ * aren't easy to pair or match against strokes.
+ *
+ * When X and Y pspans differ in number, the axis with the
+ * smaller number of pspans is regarded as having a repeating
+ * pspan (or a multi-pspan)--in the above illustration, the
+ * Y-axis has a repeating pspan. Our approach is to try to
+ * match the multi-pspan repeatedly against strokes. The
+ * difference between the number of X and Y pspans gives us a
+ * crude repeat_count for matching multi-pspans--i.e. the
+ * multi-pspan along the Y axis (above) has a repeat_count of 1.
+ */
+ repeat_count = abs(n_xpspans - n_ypspans);
+
+ fg_match_strokes_against_pspans(sc, X, pspans_x, n_xpspans,
+ (((repeat_count != 0) && ((n_xpspans < n_ypspans))) ?
+ repeat_count : 0));
+ fg_match_strokes_against_pspans(sc, Y, pspans_y, n_ypspans,
+ (((repeat_count != 0) && (n_ypspans < n_xpspans)) ?
+ repeat_count : 0));
+
+ /* Update the state of strokes based on the above pspan matches. */
+ TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
+ if (strokep->components[X].matched &&
+ strokep->components[Y].matched) {
+ strokep->matched = true;
+ strokep->instantaneous_dx =
+ strokep->components[X].delta_mickeys;
+ strokep->instantaneous_dy =
+ strokep->components[Y].delta_mickeys;
+ atp_advance_stroke_state(sc, strokep, &movement);
+ } else {
+ /*
+ * At least one component of this stroke
+ * didn't match against current pspans;
+ * terminate it.
+ */
+ atp_terminate_stroke(sc, strokep);
+ }
+ }
+
+ /* Add new strokes for pairs of unmatched pspans */
+ for (i = 0; i < n_xpspans; i++) {
+ if (pspans_x[i].matched == false) break;
+ }
+ for (j = 0; j < n_ypspans; j++) {
+ if (pspans_y[j].matched == false) break;
+ }
+ if ((i < n_xpspans) && (j < n_ypspans)) {
+#ifdef USB_DEBUG
+ if (atp_debug >= ATP_LLEVEL_INFO) {
+ printf("unmatched pspans:");
+ for (; i < n_xpspans; i++) {
+ if (pspans_x[i].matched)
+ continue;
+ printf(" X:[loc:%u,cum:%u]",
+ pspans_x[i].loc, pspans_x[i].cum);
+ }
+ for (; j < n_ypspans; j++) {
+ if (pspans_y[j].matched)
+ continue;
+ printf(" Y:[loc:%u,cum:%u]",
+ pspans_y[j].loc, pspans_y[j].cum);
+ }
+ printf("\n");
+ }
+#endif /* USB_DEBUG */
+ if ((n_xpspans == 1) && (n_ypspans == 1))
+ /* The common case of a single pair of new pspans. */
+ fg_add_stroke(sc, &pspans_x[0], &pspans_y[0]);
+ else
+ fg_add_new_strokes(sc, pspans_x, n_xpspans,
+ pspans_y, n_ypspans);
+ }
+
+#ifdef USB_DEBUG
+ if (atp_debug >= ATP_LLEVEL_INFO) {
+ TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+ printf(" %s%clc:%u,dm:%d,cum:%d,max:%d,%c"
+ ",%clc:%u,dm:%d,cum:%d,max:%d,%c",
+ (strokep->flags & ATSF_ZOMBIE) ? "zomb:" : "",
+ (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<',
+ strokep->components[X].loc,
+ strokep->components[X].delta_mickeys,
+ strokep->components[X].cum_pressure,
+ strokep->components[X].max_cum_pressure,
+ (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>',
+ (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<',
+ strokep->components[Y].loc,
+ strokep->components[Y].delta_mickeys,
+ strokep->components[Y].cum_pressure,
+ strokep->components[Y].max_cum_pressure,
+ (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>');
+ }
+ if (TAILQ_FIRST(&sc->sc_stroke_used) != NULL)
+ printf("\n");
+ }
+#endif /* USB_DEBUG */
+ return (movement);
+}
+
+/*
+ * Update strokes by matching against current pressure-spans.
+ * Return true if any movement is detected.
+ */
+static boolean_t
+wsp_update_strokes(struct atp_softc *sc, wsp_finger_t fingers[WSP_MAX_FINGERS],
+ u_int n_fingers)
+{
+ boolean_t movement = false;
+ atp_stroke_t *strokep_next;
+ atp_stroke_t *strokep;
+ u_int i;
+
+ if (sc->sc_n_strokes > 0) {
+ movement = wsp_match_strokes_against_fingers(
+ sc, fingers, n_fingers);
+
+ /* handle zombie strokes */
+ TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
+ if (strokep->matched)
+ continue;
+ atp_terminate_stroke(sc, strokep);
+ }
+ }
+
+ /* initialize unmatched fingers as strokes */
+ for (i = 0; i != n_fingers; i++) {
+ if (fingers[i].matched)
+ continue;
+
+ wsp_add_stroke(sc, fingers + i);
+ }
+ return (movement);
+}
+
+/* Initialize a stroke using a pressure-span. */
+static void
+fg_add_stroke(struct atp_softc *sc, const fg_pspan *pspan_x,
+ const fg_pspan *pspan_y)
+{
+ atp_stroke_t *strokep;
+
+ strokep = atp_alloc_stroke(sc);
+ if (strokep == NULL)
+ return;
+
+ /*
+ * Strokes begin as potential touches. If a stroke survives
+ * longer than a threshold, or if it records significant
+ * cumulative movement, then it is considered a 'slide'.
+ */
+ strokep->type = ATP_STROKE_TOUCH;
+ strokep->matched = false;
+ microtime(&strokep->ctime);
+ strokep->age = 1; /* number of interrupts */
+ strokep->x = pspan_x->loc;
+ strokep->y = pspan_y->loc;
+
+ strokep->components[X].loc = pspan_x->loc;
+ strokep->components[X].cum_pressure = pspan_x->cum;
+ strokep->components[X].max_cum_pressure = pspan_x->cum;
+ strokep->components[X].matched = true;
+
+ strokep->components[Y].loc = pspan_y->loc;
+ strokep->components[Y].cum_pressure = pspan_y->cum;
+ strokep->components[Y].max_cum_pressure = pspan_y->cum;
+ strokep->components[Y].matched = true;
+
+ if (sc->sc_n_strokes > 1) {
+ /* Reset double-tap-n-drag if we have more than one strokes. */
+ sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
+ }
+
+ DPRINTFN(ATP_LLEVEL_INFO, "[%u,%u], time: %u,%ld\n",
+ strokep->components[X].loc,
+ strokep->components[Y].loc,
+ (u_int)strokep->ctime.tv_sec,
+ (unsigned long int)strokep->ctime.tv_usec);
+}
+
+static void
+fg_add_new_strokes(struct atp_softc *sc, fg_pspan *pspans_x,
+ u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans)
+{
+ fg_pspan spans[2][FG_MAX_PSPANS_PER_AXIS];
+ u_int nspans[2];
+ u_int i;
+ u_int j;
+
+ /* Copy unmatched pspans into the local arrays. */
+ for (i = 0, nspans[X] = 0; i < n_xpspans; i++) {
+ if (pspans_x[i].matched == false) {
+ spans[X][nspans[X]] = pspans_x[i];
+ nspans[X]++;
+ }
+ }
+ for (j = 0, nspans[Y] = 0; j < n_ypspans; j++) {
+ if (pspans_y[j].matched == false) {
+ spans[Y][nspans[Y]] = pspans_y[j];
+ nspans[Y]++;
+ }
+ }
+
+ if (nspans[X] == nspans[Y]) {
+ /* Create new strokes from pairs of unmatched pspans */
+ for (i = 0, j = 0; (i < nspans[X]) && (j < nspans[Y]); i++, j++)
+ fg_add_stroke(sc, &spans[X][i], &spans[Y][j]);
+ } else {
+ u_int cum = 0;
+ atp_axis repeat_axis; /* axis with multi-pspans */
+ u_int repeat_count; /* repeat count for the multi-pspan*/
+ u_int repeat_index = 0; /* index of the multi-span */
+
+ repeat_axis = (nspans[X] > nspans[Y]) ? Y : X;
+ repeat_count = abs(nspans[X] - nspans[Y]);
+ for (i = 0; i < nspans[repeat_axis]; i++) {
+ if (spans[repeat_axis][i].cum > cum) {
+ repeat_index = i;
+ cum = spans[repeat_axis][i].cum;
+ }
+ }
+
+ /* Create new strokes from pairs of unmatched pspans */
+ i = 0, j = 0;
+ for (; (i < nspans[X]) && (j < nspans[Y]); i++, j++) {
+ fg_add_stroke(sc, &spans[X][i], &spans[Y][j]);
+
+ /* Take care to repeat at the multi-pspan. */
+ if (repeat_count > 0) {
+ if ((repeat_axis == X) &&
+ (repeat_index == i)) {
+ i--; /* counter loop increment */
+ repeat_count--;
+ } else if ((repeat_axis == Y) &&
+ (repeat_index == j)) {
+ j--; /* counter loop increment */
+ repeat_count--;
+ }
+ }
+ }
+ }
+}
+
+/* Initialize a stroke from an unmatched finger. */
+static void
+wsp_add_stroke(struct atp_softc *sc, const wsp_finger_t *fingerp)
+{
+ atp_stroke_t *strokep;
+
+ strokep = atp_alloc_stroke(sc);
+ if (strokep == NULL)
+ return;
+
+ /*
+ * Strokes begin as potential touches. If a stroke survives
+ * longer than a threshold, or if it records significant
+ * cumulative movement, then it is considered a 'slide'.
+ */
+ strokep->type = ATP_STROKE_TOUCH;
+ strokep->matched = true;
+ microtime(&strokep->ctime);
+ strokep->age = 1; /* number of interrupts */
+ strokep->x = fingerp->x;
+ strokep->y = fingerp->y;
+
+ /* Reset double-tap-n-drag if we have more than one strokes. */
+ if (sc->sc_n_strokes > 1)
+ sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
+
+ DPRINTFN(ATP_LLEVEL_INFO, "[%d,%d]\n", strokep->x, strokep->y);
+}
+
+static void
+atp_advance_stroke_state(struct atp_softc *sc, atp_stroke_t *strokep,
+ boolean_t *movementp)
+{
+ /* Revitalize stroke if it had previously been marked as a zombie. */
+ if (strokep->flags & ATSF_ZOMBIE)
+ strokep->flags &= ~ATSF_ZOMBIE;
+
+ strokep->age++;
+ if (strokep->age <= atp_stroke_maturity_threshold) {
+ /* Avoid noise from immature strokes. */
+ strokep->instantaneous_dx = 0;
+ strokep->instantaneous_dy = 0;
+ }
+
+ if (atp_compute_stroke_movement(strokep))
+ *movementp = true;
+
+ if (strokep->type != ATP_STROKE_TOUCH)
+ return;
+
+ /* Convert touch strokes to slides upon detecting movement or age. */
+ if ((abs(strokep->cum_movement_x) > atp_slide_min_movement) ||
+ (abs(strokep->cum_movement_y) > atp_slide_min_movement))
+ atp_convert_to_slide(sc, strokep);
+ else {
+ /* Compute the stroke's age. */
+ struct timeval tdiff;
+ getmicrotime(&tdiff);
+ if (timevalcmp(&tdiff, &strokep->ctime, >)) {
+ timevalsub(&tdiff, &strokep->ctime);
+
+ if ((tdiff.tv_sec > (atp_touch_timeout / 1000000)) ||
+ ((tdiff.tv_sec == (atp_touch_timeout / 1000000)) &&
+ (tdiff.tv_usec >= (atp_touch_timeout % 1000000))))
+ atp_convert_to_slide(sc, strokep);
+ }
+ }
+}
+
+static boolean_t
+atp_stroke_has_small_movement(const atp_stroke_t *strokep)
+{
+ return (((u_int)abs(strokep->instantaneous_dx) <=
+ atp_small_movement_threshold) &&
+ ((u_int)abs(strokep->instantaneous_dy) <=
+ atp_small_movement_threshold));
+}
+
+/*
+ * Accumulate instantaneous changes into the stroke's 'pending' bucket; if
+ * the aggregate exceeds the small_movement_threshold, then retain
+ * instantaneous changes for later.
+ */
+static void
+atp_update_pending_mickeys(atp_stroke_t *strokep)
+{
+ /* accumulate instantaneous movement */
+ strokep->pending_dx += strokep->instantaneous_dx;
+ strokep->pending_dy += strokep->instantaneous_dy;
+
+#define UPDATE_INSTANTANEOUS_AND_PENDING(I, P) \
+ if (abs((P)) <= atp_small_movement_threshold) \
+ (I) = 0; /* clobber small movement */ \
+ else { \
+ if ((I) > 0) { \
+ /* \
+ * Round up instantaneous movement to the nearest \
+ * ceiling. This helps preserve small mickey \
+ * movements from being lost in following scaling \
+ * operation. \
+ */ \
+ (I) = (((I) + (atp_mickeys_scale_factor - 1)) / \
+ atp_mickeys_scale_factor) * \
+ atp_mickeys_scale_factor; \
+ \
+ /* \
+ * Deduct the rounded mickeys from pending mickeys. \
+ * Note: we multiply by 2 to offset the previous \
+ * accumulation of instantaneous movement into \
+ * pending. \
+ */ \
+ (P) -= ((I) << 1); \
+ \
+ /* truncate pending to 0 if it becomes negative. */ \
+ (P) = imax((P), 0); \
+ } else { \
+ /* \
+ * Round down instantaneous movement to the nearest \
+ * ceiling. This helps preserve small mickey \
+ * movements from being lost in following scaling \
+ * operation. \
+ */ \
+ (I) = (((I) - (atp_mickeys_scale_factor - 1)) / \
+ atp_mickeys_scale_factor) * \
+ atp_mickeys_scale_factor; \
+ \
+ /* \
+ * Deduct the rounded mickeys from pending mickeys. \
+ * Note: we multiply by 2 to offset the previous \
+ * accumulation of instantaneous movement into \
+ * pending. \
+ */ \
+ (P) -= ((I) << 1); \
+ \
+ /* truncate pending to 0 if it becomes positive. */ \
+ (P) = imin((P), 0); \
+ } \
+ }
+
+ UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dx,
+ strokep->pending_dx);
+ UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dy,
+ strokep->pending_dy);
+}
+
+/*
+ * Compute a smoothened value for the stroke's movement from
+ * instantaneous changes in the X and Y components.
+ */
+static boolean_t
+atp_compute_stroke_movement(atp_stroke_t *strokep)
+{
+ /*
+ * Short movements are added first to the 'pending' bucket,
+ * and then acted upon only when their aggregate exceeds a
+ * threshold. This has the effect of filtering away movement
+ * noise.
+ */
+ if (atp_stroke_has_small_movement(strokep))
+ atp_update_pending_mickeys(strokep);
+ else { /* large movement */
+ /* clear away any pending mickeys if there are large movements*/
+ strokep->pending_dx = 0;
+ strokep->pending_dy = 0;
+ }
+
+ /* scale movement */
+ strokep->movement_dx = (strokep->instantaneous_dx) /
+ (int)atp_mickeys_scale_factor;
+ strokep->movement_dy = (strokep->instantaneous_dy) /
+ (int)atp_mickeys_scale_factor;
+
+ if ((abs(strokep->instantaneous_dx) >= ATP_FAST_MOVEMENT_TRESHOLD) ||
+ (abs(strokep->instantaneous_dy) >= ATP_FAST_MOVEMENT_TRESHOLD)) {
+ strokep->movement_dx <<= 1;
+ strokep->movement_dy <<= 1;
+ }
+
+ strokep->cum_movement_x += strokep->movement_dx;
+ strokep->cum_movement_y += strokep->movement_dy;
+
+ return ((strokep->movement_dx != 0) || (strokep->movement_dy != 0));
+}
+
+/*
+ * Terminate a stroke. Aside from immature strokes, a slide or touch is
+ * retained as a zombies so as to reap all their termination siblings
+ * together; this helps establish the number of fingers involved at the
+ * end of a multi-touch gesture.
+ */
+static void
+atp_terminate_stroke(struct atp_softc *sc, atp_stroke_t *strokep)
+{
+ if (strokep->flags & ATSF_ZOMBIE)
+ return;
+
+ /* Drop immature strokes rightaway. */
+ if (strokep->age <= atp_stroke_maturity_threshold) {
+ atp_free_stroke(sc, strokep);
+ return;
+ }
+
+ strokep->flags |= ATSF_ZOMBIE;
+ sc->sc_state |= ATP_ZOMBIES_EXIST;
+
+ callout_reset(&sc->sc_callout, ATP_ZOMBIE_STROKE_REAP_INTERVAL,
+ atp_reap_sibling_zombies, sc);
+
+ /*
+ * Reset the double-click-n-drag at the termination of any
+ * slide stroke.
+ */
+ if (strokep->type == ATP_STROKE_SLIDE)
+ sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
+}
+
+static boolean_t
+atp_is_horizontal_scroll(const atp_stroke_t *strokep)
+{
+ if (abs(strokep->cum_movement_x) < atp_slide_min_movement)
+ return (false);
+ if (strokep->cum_movement_y == 0)
+ return (true);
+ return (abs(strokep->cum_movement_x / strokep->cum_movement_y) >= 4);
+}
+
+static boolean_t
+atp_is_vertical_scroll(const atp_stroke_t *strokep)
+{
+ if (abs(strokep->cum_movement_y) < atp_slide_min_movement)
+ return (false);
+ if (strokep->cum_movement_x == 0)
+ return (true);
+ return (abs(strokep->cum_movement_y / strokep->cum_movement_x) >= 4);
+}
+
+static void
+atp_reap_sibling_zombies(void *arg)
+{
+ struct atp_softc *sc = (struct atp_softc *)arg;
+ u_int8_t n_touches_reaped = 0;
+ u_int8_t n_slides_reaped = 0;
+ u_int8_t n_horizontal_scrolls = 0;
+ int horizontal_scroll = 0;
+ atp_stroke_t *strokep;
+ atp_stroke_t *strokep_next;
+
+ DPRINTFN(ATP_LLEVEL_INFO, "\n");
+
+ TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
+ if ((strokep->flags & ATSF_ZOMBIE) == 0)
+ continue;
+
+ if (strokep->type == ATP_STROKE_TOUCH) {
+ n_touches_reaped++;
+ } else {
+ n_slides_reaped++;
+
+ if (atp_is_horizontal_scroll(strokep)) {
+ n_horizontal_scrolls++;
+ horizontal_scroll += strokep->cum_movement_x;
+ }
+ }
+
+ atp_free_stroke(sc, strokep);
+ }
+
+ DPRINTFN(ATP_LLEVEL_INFO, "reaped %u zombies\n",
+ n_touches_reaped + n_slides_reaped);
+ sc->sc_state &= ~ATP_ZOMBIES_EXIST;
+
+ /* No further processing necessary if physical button is depressed. */
+ if (sc->sc_ibtn != 0)
+ return;
+
+ if ((n_touches_reaped == 0) && (n_slides_reaped == 0))
+ return;
+
+ /* Add a pair of virtual button events (button-down and button-up) if
+ * the physical button isn't pressed. */
+ if (n_touches_reaped != 0) {
+ if (n_touches_reaped < atp_tap_minimum)
+ return;
+
+ switch (n_touches_reaped) {
+ case 1:
+ atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON1DOWN);
+ microtime(&sc->sc_touch_reap_time); /* remember this time */
+ break;
+ case 2:
+ atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON3DOWN);
+ break;
+ case 3:
+ atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON2DOWN);
+ break;
+ default:
+ /* we handle taps of only up to 3 fingers */
+ return;
+ }
+ atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */
+
+ } else if ((n_slides_reaped == 2) && (n_horizontal_scrolls == 2)) {
+ if (horizontal_scroll < 0)
+ atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON4DOWN);
+ else
+ atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON5DOWN);
+ atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */
+ }
+}
+
+/* Switch a given touch stroke to being a slide. */
+static void
+atp_convert_to_slide(struct atp_softc *sc, atp_stroke_t *strokep)
+{
+ strokep->type = ATP_STROKE_SLIDE;
+
+ /* Are we at the beginning of a double-click-n-drag? */
+ if ((sc->sc_n_strokes == 1) &&
+ ((sc->sc_state & ATP_ZOMBIES_EXIST) == 0) &&
+ timevalcmp(&strokep->ctime, &sc->sc_touch_reap_time, >)) {
+ struct timeval delta;
+ struct timeval window = {
+ atp_double_tap_threshold / 1000000,
+ atp_double_tap_threshold % 1000000
+ };
+
+ delta = strokep->ctime;
+ timevalsub(&delta, &sc->sc_touch_reap_time);
+ if (timevalcmp(&delta, &window, <=))
+ sc->sc_state |= ATP_DOUBLE_TAP_DRAG;
+ }
+}
+
+static void
+atp_reset_buf(struct atp_softc *sc)
+{
+ /* reset read queue */
+ usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]);
+}
+
+static void
+atp_add_to_queue(struct atp_softc *sc, int dx, int dy, int dz,
+ uint32_t buttons_in)
+{
+ uint32_t buttons_out;
+ uint8_t buf[8];
+
+ dx = imin(dx, 254); dx = imax(dx, -256);
+ dy = imin(dy, 254); dy = imax(dy, -256);
+ dz = imin(dz, 126); dz = imax(dz, -128);
+
+ buttons_out = MOUSE_MSC_BUTTONS;
+ if (buttons_in & MOUSE_BUTTON1DOWN)
+ buttons_out &= ~MOUSE_MSC_BUTTON1UP;
+ else if (buttons_in & MOUSE_BUTTON2DOWN)
+ buttons_out &= ~MOUSE_MSC_BUTTON2UP;
+ else if (buttons_in & MOUSE_BUTTON3DOWN)
+ buttons_out &= ~MOUSE_MSC_BUTTON3UP;
+
+ DPRINTFN(ATP_LLEVEL_INFO, "dx=%d, dy=%d, buttons=%x\n",
+ dx, dy, buttons_out);
+
+ /* Encode the mouse data in standard format; refer to mouse(4) */
+ buf[0] = sc->sc_mode.syncmask[1];
+ buf[0] |= buttons_out;
+ buf[1] = dx >> 1;
+ buf[2] = dy >> 1;
+ buf[3] = dx - (dx >> 1);
+ buf[4] = dy - (dy >> 1);
+ /* Encode extra bytes for level 1 */
+ if (sc->sc_mode.level == 1) {
+ buf[5] = dz >> 1;
+ buf[6] = dz - (dz >> 1);
+ buf[7] = (((~buttons_in) >> 3) & MOUSE_SYS_EXTBUTTONS);
+ }
+
+ usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf,
+ sc->sc_mode.packetsize, 1);
+}
+
+static int
+atp_probe(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ if (uaa->info.bInterfaceClass != UICLASS_HID)
+ return (ENXIO);
+ /*
+ * Note: for some reason, the check
+ * (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) doesn't hold true
+ * for wellspring trackpads, so we've removed it from the common path.
+ */
+
+ if ((usbd_lookup_id_by_uaa(fg_devs, sizeof(fg_devs), uaa)) == 0)
+ return ((uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) ?
+ BUS_PROBE_DEFAULT : ENXIO);
+
+ if ((usbd_lookup_id_by_uaa(wsp_devs, sizeof(wsp_devs), uaa)) == 0)
+ if (uaa->info.bIfaceIndex == WELLSPRING_INTERFACE_INDEX)
+ return (BUS_PROBE_DEFAULT);
+
+ return (ENXIO);
+}
+
+static int
+atp_attach(device_t dev)
+{
+ struct atp_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ usb_error_t err;
+ void *descriptor_ptr = NULL;
+ uint16_t descriptor_len;
+ unsigned long di;
+
+ DPRINTFN(ATP_LLEVEL_INFO, "sc=%p\n", sc);
+
+ sc->sc_dev = dev;
+ sc->sc_usb_device = uaa->device;
+
+ /* Get HID descriptor */
+ if (usbd_req_get_hid_desc(uaa->device, NULL, &descriptor_ptr,
+ &descriptor_len, M_TEMP, uaa->info.bIfaceIndex) !=
+ USB_ERR_NORMAL_COMPLETION)
+ return (ENXIO);
+
+ /* Get HID report descriptor length */
+ sc->sc_expected_sensor_data_len = hid_report_size_max(descriptor_ptr,
+ descriptor_len, hid_input, NULL);
+ free(descriptor_ptr, M_TEMP);
+
+ if ((sc->sc_expected_sensor_data_len <= 0) ||
+ (sc->sc_expected_sensor_data_len > ATP_SENSOR_DATA_BUF_MAX)) {
+ DPRINTF("atp_attach: datalength invalid or too large: %d\n",
+ sc->sc_expected_sensor_data_len);
+ return (ENXIO);
+ }
+
+ di = USB_GET_DRIVER_INFO(uaa);
+ sc->sc_family = DECODE_FAMILY_FROM_DRIVER_INFO(di);
+
+ /*
+ * By default the touchpad behaves like an HID device, sending
+ * packets with reportID = 2. Such reports contain only
+ * limited information--they encode movement deltas and button
+ * events,--but do not include data from the pressure
+ * sensors. The device input mode can be switched from HID
+ * reports to raw sensor data using vendor-specific USB
+ * control commands.
+ * FOUNTAIN devices will give an error when trying to switch
+ * input mode, so we skip this command
+ */
+ if ((sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER) &&
+ (DECODE_PRODUCT_FROM_DRIVER_INFO(di) == FOUNTAIN))
+ DPRINTF("device mode switch skipped: Fountain device\n");
+ else if ((err = atp_set_device_mode(sc, RAW_SENSOR_MODE)) != 0) {
+ DPRINTF("failed to set mode to 'RAW_SENSOR' (%d)\n", err);
+ return (ENXIO);
+ }
+
+ mtx_init(&sc->sc_mutex, "atpmtx", NULL, MTX_DEF | MTX_RECURSE);
+
+ switch(sc->sc_family) {
+ case TRACKPAD_FAMILY_FOUNTAIN_GEYSER:
+ sc->sc_params =
+ &fg_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)];
+ sc->sensor_data_interpreter = fg_interpret_sensor_data;
+ break;
+ case TRACKPAD_FAMILY_WELLSPRING:
+ sc->sc_params =
+ &wsp_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)];
+ sc->sensor_data_interpreter = wsp_interpret_sensor_data;
+ break;
+ default:
+ goto detach;
+ }
+
+ err = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer, atp_xfer_config,
+ ATP_N_TRANSFER, sc, &sc->sc_mutex);
+ if (err) {
+ DPRINTF("error=%s\n", usbd_errstr(err));
+ goto detach;
+ }
+
+ if (usb_fifo_attach(sc->sc_usb_device, sc, &sc->sc_mutex,
+ &atp_fifo_methods, &sc->sc_fifo,
+ device_get_unit(dev), -1, uaa->info.bIfaceIndex,
+ UID_ROOT, GID_OPERATOR, 0644)) {
+ goto detach;
+ }
+
+ device_set_usb_desc(dev);
+
+ sc->sc_hw.buttons = 3;
+ sc->sc_hw.iftype = MOUSE_IF_USB;
+ sc->sc_hw.type = MOUSE_PAD;
+ sc->sc_hw.model = MOUSE_MODEL_GENERIC;
+ sc->sc_hw.hwid = 0;
+ sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+ sc->sc_mode.rate = -1;
+ sc->sc_mode.resolution = MOUSE_RES_UNKNOWN;
+ sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+ sc->sc_mode.accelfactor = 0;
+ sc->sc_mode.level = 0;
+
+ sc->sc_state = 0;
+ sc->sc_ibtn = 0;
+
+ callout_init_mtx(&sc->sc_callout, &sc->sc_mutex, 0);
+
+ return (0);
+
+detach:
+ atp_detach(dev);
+ return (ENOMEM);
+}
+
+static int
+atp_detach(device_t dev)
+{
+ struct atp_softc *sc;
+
+ sc = device_get_softc(dev);
+ atp_set_device_mode(sc, HID_MODE);
+
+ mtx_lock(&sc->sc_mutex);
+ callout_drain(&sc->sc_callout);
+ if (sc->sc_state & ATP_ENABLED)
+ atp_disable(sc);
+ mtx_unlock(&sc->sc_mutex);
+
+ usb_fifo_detach(&sc->sc_fifo);
+
+ usbd_transfer_unsetup(sc->sc_xfer, ATP_N_TRANSFER);
+
+ mtx_destroy(&sc->sc_mutex);
+
+ return (0);
+}
+
+static void
+atp_intr(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct atp_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int len;
+
+ usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, sc->sc_sensor_data, len);
+ if (len < sc->sc_expected_sensor_data_len) {
+ /* make sure we don't process old data */
+ memset(sc->sc_sensor_data + len, 0,
+ sc->sc_expected_sensor_data_len - len);
+ }
+
+ sc->sc_status.flags &= ~(MOUSE_STDBUTTONSCHANGED |
+ MOUSE_POSCHANGED);
+ sc->sc_status.obutton = sc->sc_status.button;
+
+ (sc->sensor_data_interpreter)(sc, len);
+
+ if (sc->sc_status.button != 0) {
+ /* Reset DOUBLE_TAP_N_DRAG if the button is pressed. */
+ sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
+ } else if (sc->sc_state & ATP_DOUBLE_TAP_DRAG) {
+ /* Assume a button-press with DOUBLE_TAP_N_DRAG. */
+ sc->sc_status.button = MOUSE_BUTTON1DOWN;
+ }
+
+ sc->sc_status.flags |=
+ sc->sc_status.button ^ sc->sc_status.obutton;
+ if (sc->sc_status.flags & MOUSE_STDBUTTONSCHANGED) {
+ DPRINTFN(ATP_LLEVEL_INFO, "button %s\n",
+ ((sc->sc_status.button & MOUSE_BUTTON1DOWN) ?
+ "pressed" : "released"));
+ }
+
+ if (sc->sc_status.flags & (MOUSE_POSCHANGED |
+ MOUSE_STDBUTTONSCHANGED)) {
+ atp_stroke_t *strokep;
+ u_int8_t n_movements = 0;
+ int dx = 0;
+ int dy = 0;
+ int dz = 0;
+
+ TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+ if (strokep->flags & ATSF_ZOMBIE)
+ continue;
+
+ dx += strokep->movement_dx;
+ dy += strokep->movement_dy;
+ if (strokep->movement_dx ||
+ strokep->movement_dy)
+ n_movements++;
+ }
+
+ /* average movement if multiple strokes record motion.*/
+ if (n_movements > 1) {
+ dx /= (int)n_movements;
+ dy /= (int)n_movements;
+ }
+
+ /* detect multi-finger vertical scrolls */
+ if (n_movements >= 2) {
+ boolean_t all_vertical_scrolls = true;
+ TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+ if (strokep->flags & ATSF_ZOMBIE)
+ continue;
+
+ if (!atp_is_vertical_scroll(strokep))
+ all_vertical_scrolls = false;
+ }
+ if (all_vertical_scrolls) {
+ dz = dy;
+ dy = dx = 0;
+ }
+ }
+
+ sc->sc_status.dx += dx;
+ sc->sc_status.dy += dy;
+ sc->sc_status.dz += dz;
+ atp_add_to_queue(sc, dx, -dy, -dz, sc->sc_status.button);
+ }
+
+ case USB_ST_SETUP:
+ tr_setup:
+ /* check if we can put more data into the FIFO */
+ if (usb_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) != 0) {
+ usbd_xfer_set_frame_len(xfer, 0,
+ sc->sc_expected_sensor_data_len);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+atp_start_read(struct usb_fifo *fifo)
+{
+ struct atp_softc *sc = usb_fifo_softc(fifo);
+ int rate;
+
+ /* Check if we should override the default polling interval */
+ rate = sc->sc_pollrate;
+ /* Range check rate */
+ if (rate > 1000)
+ rate = 1000;
+ /* Check for set rate */
+ if ((rate > 0) && (sc->sc_xfer[ATP_INTR_DT] != NULL)) {
+ /* Stop current transfer, if any */
+ usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]);
+ /* Set new interval */
+ usbd_xfer_set_interval(sc->sc_xfer[ATP_INTR_DT], 1000 / rate);
+ /* Only set pollrate once */
+ sc->sc_pollrate = 0;
+ }
+
+ usbd_transfer_start(sc->sc_xfer[ATP_INTR_DT]);
+}
+
+static void
+atp_stop_read(struct usb_fifo *fifo)
+{
+ struct atp_softc *sc = usb_fifo_softc(fifo);
+ usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]);
+}
+
+static int
+atp_open(struct usb_fifo *fifo, int fflags)
+{
+ struct atp_softc *sc = usb_fifo_softc(fifo);
+
+ /* check for duplicate open, should not happen */
+ if (sc->sc_fflags & fflags)
+ return (EBUSY);
+
+ /* check for first open */
+ if (sc->sc_fflags == 0) {
+ int rc;
+ if ((rc = atp_enable(sc)) != 0)
+ return (rc);
+ }
+
+ if (fflags & FREAD) {
+ if (usb_fifo_alloc_buffer(fifo,
+ ATP_FIFO_BUF_SIZE, ATP_FIFO_QUEUE_MAXLEN)) {
+ return (ENOMEM);
+ }
+ }
+
+ sc->sc_fflags |= (fflags & (FREAD | FWRITE));
+ return (0);
+}
+
+static void
+atp_close(struct usb_fifo *fifo, int fflags)
+{
+ struct atp_softc *sc = usb_fifo_softc(fifo);
+ if (fflags & FREAD)
+ usb_fifo_free_buffer(fifo);
+
+ sc->sc_fflags &= ~(fflags & (FREAD | FWRITE));
+ if (sc->sc_fflags == 0) {
+ atp_disable(sc);
+ }
+}
+
+static int
+atp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
+{
+ struct atp_softc *sc = usb_fifo_softc(fifo);
+ mousemode_t mode;
+ int error = 0;
+
+ mtx_lock(&sc->sc_mutex);
+
+ switch(cmd) {
+ case MOUSE_GETHWINFO:
+ *(mousehw_t *)addr = sc->sc_hw;
+ break;
+ case MOUSE_GETMODE:
+ *(mousemode_t *)addr = sc->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)) {
+ error = EINVAL;
+ break;
+ }
+ sc->sc_mode.level = mode.level;
+ sc->sc_pollrate = mode.rate;
+ sc->sc_hw.buttons = 3;
+
+ if (sc->sc_mode.level == 0) {
+ sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+ sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+ } else if (sc->sc_mode.level == 1) {
+ sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
+ sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+ }
+ atp_reset_buf(sc);
+ break;
+ case MOUSE_GETLEVEL:
+ *(int *)addr = sc->sc_mode.level;
+ break;
+ case MOUSE_SETLEVEL:
+ if ((*(int *)addr < 0) || (*(int *)addr > 1)) {
+ error = EINVAL;
+ break;
+ }
+ sc->sc_mode.level = *(int *)addr;
+ sc->sc_hw.buttons = 3;
+
+ if (sc->sc_mode.level == 0) {
+ sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+ sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+ } else if (sc->sc_mode.level == 1) {
+ sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
+ sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+ }
+ atp_reset_buf(sc);
+ break;
+ case MOUSE_GETSTATUS: {
+ mousestatus_t *status = (mousestatus_t *)addr;
+
+ *status = sc->sc_status;
+ sc->sc_status.obutton = sc->sc_status.button;
+ sc->sc_status.button = 0;
+ sc->sc_status.dx = 0;
+ sc->sc_status.dy = 0;
+ sc->sc_status.dz = 0;
+
+ if (status->dx || status->dy || status->dz)
+ status->flags |= MOUSE_POSCHANGED;
+ if (status->button != status->obutton)
+ status->flags |= MOUSE_BUTTONSCHANGED;
+ break;
+ }
+
+ default:
+ error = ENOTTY;
+ break;
+ }
+
+ mtx_unlock(&sc->sc_mutex);
+ return (error);
+}
+
+static int
+atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS)
+{
+ int error;
+ u_int tmp;
+
+ tmp = atp_mickeys_scale_factor;
+ error = sysctl_handle_int(oidp, &tmp, 0, req);
+ if (error != 0 || req->newptr == NULL)
+ return (error);
+
+ if (tmp == atp_mickeys_scale_factor)
+ return (0); /* no change */
+ if ((tmp == 0) || (tmp > (10 * ATP_SCALE_FACTOR)))
+ return (EINVAL);
+
+ atp_mickeys_scale_factor = tmp;
+ DPRINTFN(ATP_LLEVEL_INFO, "%s: resetting mickeys_scale_factor to %u\n",
+ ATP_DRIVER_NAME, tmp);
+
+ return (0);
+}
+
+static device_method_t atp_methods[] = {
+ DEVMETHOD(device_probe, atp_probe),
+ DEVMETHOD(device_attach, atp_attach),
+ DEVMETHOD(device_detach, atp_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t atp_driver = {
+ .name = ATP_DRIVER_NAME,
+ .methods = atp_methods,
+ .size = sizeof(struct atp_softc)
+};
+
+DRIVER_MODULE(atp, uhub, atp_driver, NULL, NULL);
+MODULE_DEPEND(atp, usb, 1, 1, 1);
+MODULE_DEPEND(atp, hid, 1, 1, 1);
+MODULE_VERSION(atp, 1);
+USB_PNP_HOST_INFO(fg_devs);
+USB_PNP_HOST_INFO(wsp_devs);
diff --git a/sys/dev/usb/input/uep.c b/sys/dev/usb/input/uep.c
new file mode 100644
index 000000000000..25eeb450491d
--- /dev/null
+++ b/sys/dev/usb/input/uep.c
@@ -0,0 +1,537 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010, Gleb Smirnoff <glebius@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.
+ */
+
+/*
+ * http://www.eeti.com.tw/pdf/Software%20Programming%20Guide_v2.0.pdf
+ */
+
+#include "opt_evdev.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/callout.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include "usbdevs.h"
+
+#ifdef EVDEV_SUPPORT
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+#else
+#include <sys/ioccom.h>
+#include <sys/fcntl.h>
+#endif
+
+#define USB_DEBUG_VAR uep_debug
+#include <dev/usb/usb_debug.h>
+
+#ifdef USB_DEBUG
+static int uep_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uep, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB uep");
+SYSCTL_INT(_hw_usb_uep, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &uep_debug, 0, "Debug level");
+#endif
+
+#define UEP_MAX_X 2047
+#define UEP_MAX_Y 2047
+
+#define UEP_DOWN 0x01
+#define UEP_PACKET_LEN_MAX 16
+#define UEP_PACKET_LEN_REPORT 5
+#define UEP_PACKET_LEN_REPORT2 6
+#define UEP_PACKET_DIAG 0x0a
+#define UEP_PACKET_REPORT_MASK 0xe0
+#define UEP_PACKET_REPORT 0x80
+#define UEP_PACKET_REPORT_PRESSURE 0xc0
+#define UEP_PACKET_REPORT_PLAYER 0xa0
+#define UEP_PACKET_LEN_MASK
+
+#define UEP_FIFO_BUF_SIZE 8 /* bytes */
+#define UEP_FIFO_QUEUE_MAXLEN 50 /* units */
+
+enum {
+ UEP_INTR_DT,
+ UEP_N_TRANSFER,
+};
+
+struct uep_softc {
+ struct mtx mtx;
+
+ struct usb_xfer *xfer[UEP_N_TRANSFER];
+#ifdef EVDEV_SUPPORT
+ struct evdev_dev *evdev;
+#else
+ struct usb_fifo_sc fifo;
+
+ u_int pollrate;
+ u_int state;
+#define UEP_ENABLED 0x01
+#endif
+
+ /* Reassembling buffer. */
+ u_char buf[UEP_PACKET_LEN_MAX];
+ uint8_t buf_len;
+};
+
+static usb_callback_t uep_intr_callback;
+
+static device_probe_t uep_probe;
+static device_attach_t uep_attach;
+static device_detach_t uep_detach;
+
+#ifdef EVDEV_SUPPORT
+
+static evdev_open_t uep_ev_open;
+static evdev_close_t uep_ev_close;
+
+static const struct evdev_methods uep_evdev_methods = {
+ .ev_open = &uep_ev_open,
+ .ev_close = &uep_ev_close,
+};
+
+#else /* !EVDEV_SUPPORT */
+
+static usb_fifo_cmd_t uep_start_read;
+static usb_fifo_cmd_t uep_stop_read;
+static usb_fifo_open_t uep_open;
+static usb_fifo_close_t uep_close;
+
+static void uep_put_queue(struct uep_softc *, u_char *);
+
+static struct usb_fifo_methods uep_fifo_methods = {
+ .f_open = &uep_open,
+ .f_close = &uep_close,
+ .f_start_read = &uep_start_read,
+ .f_stop_read = &uep_stop_read,
+ .basename[0] = "uep",
+};
+#endif /* !EVDEV_SUPPORT */
+
+static int
+get_pkt_len(u_char *buf)
+{
+ if (buf[0] == UEP_PACKET_DIAG) {
+ int len;
+
+ len = buf[1] + 2;
+ if (len > UEP_PACKET_LEN_MAX) {
+ DPRINTF("bad packet len %u\n", len);
+ return (UEP_PACKET_LEN_MAX);
+ }
+
+ return (len);
+ }
+
+ switch (buf[0] & UEP_PACKET_REPORT_MASK) {
+ case UEP_PACKET_REPORT:
+ return (UEP_PACKET_LEN_REPORT);
+ case UEP_PACKET_REPORT_PRESSURE:
+ case UEP_PACKET_REPORT_PLAYER:
+ case UEP_PACKET_REPORT_PRESSURE | UEP_PACKET_REPORT_PLAYER:
+ return (UEP_PACKET_LEN_REPORT2);
+ default:
+ DPRINTF("bad packet len 0\n");
+ return (0);
+ }
+}
+
+static void
+uep_process_pkt(struct uep_softc *sc, u_char *buf)
+{
+ int32_t x __usbdebug_used, y __usbdebug_used;
+#ifdef EVDEV_SUPPORT
+ int touch;
+#endif
+
+ if ((buf[0] & 0xFE) != 0x80) {
+ DPRINTF("bad input packet format 0x%.2x\n", buf[0]);
+ return;
+ }
+
+ /*
+ * Packet format is 5 bytes:
+ *
+ * 1000000T
+ * 0000AAAA
+ * 0AAAAAAA
+ * 0000BBBB
+ * 0BBBBBBB
+ *
+ * T: 1=touched 0=not touched
+ * A: bits of axis A position, MSB to LSB
+ * B: bits of axis B position, MSB to LSB
+ *
+ * For the unit I have, which is CTF1020-S from CarTFT.com,
+ * A = X and B = Y. But in NetBSD uep(4) it is other way round :)
+ *
+ * The controller sends a stream of T=1 events while the
+ * panel is touched, followed by a single T=0 event.
+ *
+ */
+
+ x = (buf[1] << 7) | buf[2];
+ y = (buf[3] << 7) | buf[4];
+
+ DPRINTFN(2, "x %u y %u\n", x, y);
+
+#ifdef EVDEV_SUPPORT
+ touch = buf[0] & (1 << 0);
+ if (touch) {
+ evdev_push_abs(sc->evdev, ABS_X, x);
+ evdev_push_abs(sc->evdev, ABS_Y, y);
+ }
+ evdev_push_key(sc->evdev, BTN_TOUCH, touch);
+ evdev_sync(sc->evdev);
+#else
+ uep_put_queue(sc, buf);
+#endif
+}
+
+static void
+uep_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uep_softc *sc = usbd_xfer_softc(xfer);
+ int len;
+
+ usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ {
+ struct usb_page_cache *pc;
+ u_char buf[17], *p;
+ int pkt_len;
+
+ if (len > (int)sizeof(buf)) {
+ DPRINTF("bad input length %d\n", len);
+ goto tr_setup;
+ }
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, buf, len);
+
+ /*
+ * The below code mimics Linux a lot. I don't know
+ * why NetBSD reads complete packets, but we need
+ * to reassamble 'em like Linux does (tries?).
+ */
+ if (sc->buf_len > 0) {
+ int res;
+
+ if (sc->buf_len == 1)
+ sc->buf[1] = buf[0];
+
+ if ((pkt_len = get_pkt_len(sc->buf)) == 0)
+ goto tr_setup;
+
+ res = pkt_len - sc->buf_len;
+ memcpy(sc->buf + sc->buf_len, buf, res);
+ uep_process_pkt(sc, sc->buf);
+ sc->buf_len = 0;
+
+ p = buf + res;
+ len -= res;
+ } else
+ p = buf;
+
+ if (len == 1) {
+ sc->buf[0] = buf[0];
+ sc->buf_len = 1;
+
+ goto tr_setup;
+ }
+
+ while (len > 0) {
+ if ((pkt_len = get_pkt_len(p)) == 0)
+ goto tr_setup;
+
+ /* full packet: process */
+ if (pkt_len <= len) {
+ uep_process_pkt(sc, p);
+ } else {
+ /* incomplete packet: save in buffer */
+ memcpy(sc->buf, p, len);
+ sc->buf_len = len;
+ }
+ p += pkt_len;
+ len -= pkt_len;
+ }
+ }
+ case USB_ST_SETUP:
+ tr_setup:
+#ifndef EVDEV_SUPPORT
+ /* check if we can put more data into the FIFO */
+ if (usb_fifo_put_bytes_max(sc->fifo.fp[USB_FIFO_RX]) == 0)
+ break;
+#endif
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default:
+ if (error != USB_ERR_CANCELLED) {
+ /* try clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static const struct usb_config uep_config[UEP_N_TRANSFER] = {
+ [UEP_INTR_DT] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &uep_intr_callback,
+ },
+};
+
+static const STRUCT_USB_HOST_ID uep_devs[] = {
+ {USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL, 0)},
+ {USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL2, 0)},
+ {USB_VPI(USB_VENDOR_EGALAX2, USB_PRODUCT_EGALAX2_TPANEL, 0)},
+};
+
+static int
+uep_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != 0)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != 0)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(uep_devs, sizeof(uep_devs), uaa));
+}
+
+static int
+uep_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct uep_softc *sc = device_get_softc(dev);
+ int error;
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->mtx, "uep lock", NULL, MTX_DEF);
+
+ error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex,
+ sc->xfer, uep_config, UEP_N_TRANSFER, sc, &sc->mtx);
+
+ if (error) {
+ DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+
+#ifdef EVDEV_SUPPORT
+ sc->evdev = evdev_alloc();
+ evdev_set_name(sc->evdev, device_get_desc(dev));
+ evdev_set_phys(sc->evdev, device_get_nameunit(dev));
+ evdev_set_id(sc->evdev, BUS_USB, uaa->info.idVendor,
+ uaa->info.idProduct, 0);
+ evdev_set_serial(sc->evdev, usb_get_serial(uaa->device));
+ evdev_set_methods(sc->evdev, sc, &uep_evdev_methods);
+ evdev_support_prop(sc->evdev, INPUT_PROP_DIRECT);
+ evdev_support_event(sc->evdev, EV_SYN);
+ evdev_support_event(sc->evdev, EV_ABS);
+ evdev_support_event(sc->evdev, EV_KEY);
+ evdev_support_key(sc->evdev, BTN_TOUCH);
+ evdev_support_abs(sc->evdev, ABS_X, 0, UEP_MAX_X, 0, 0, 0);
+ evdev_support_abs(sc->evdev, ABS_Y, 0, UEP_MAX_Y, 0, 0, 0);
+
+ error = evdev_register_mtx(sc->evdev, &sc->mtx);
+ if (error) {
+ DPRINTF("evdev_register_mtx error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+#else /* !EVDEV_SUPPORT */
+ error = usb_fifo_attach(uaa->device, sc, &sc->mtx, &uep_fifo_methods,
+ &sc->fifo, device_get_unit(dev), -1, uaa->info.bIfaceIndex,
+ UID_ROOT, GID_OPERATOR, 0644);
+
+ if (error) {
+ DPRINTF("usb_fifo_attach error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+#endif /* !EVDEV_SUPPORT */
+
+ sc->buf_len = 0;
+
+ return (0);
+
+detach:
+ uep_detach(dev);
+
+ return (ENOMEM); /* XXX */
+}
+
+static int
+uep_detach(device_t dev)
+{
+ struct uep_softc *sc = device_get_softc(dev);
+
+#ifdef EVDEV_SUPPORT
+ evdev_free(sc->evdev);
+#else
+ usb_fifo_detach(&sc->fifo);
+#endif
+
+ usbd_transfer_unsetup(sc->xfer, UEP_N_TRANSFER);
+
+ mtx_destroy(&sc->mtx);
+
+ return (0);
+}
+
+#ifdef EVDEV_SUPPORT
+
+static int
+uep_ev_close(struct evdev_dev *evdev)
+{
+ struct uep_softc *sc = evdev_get_softc(evdev);
+
+ mtx_assert(&sc->mtx, MA_OWNED);
+ usbd_transfer_stop(sc->xfer[UEP_INTR_DT]);
+
+ return (0);
+}
+
+static int
+uep_ev_open(struct evdev_dev *evdev)
+{
+ struct uep_softc *sc = evdev_get_softc(evdev);
+
+ mtx_assert(&sc->mtx, MA_OWNED);
+ usbd_transfer_start(sc->xfer[UEP_INTR_DT]);
+
+ return (0);
+}
+
+#else /* !EVDEV_SUPPORT */
+
+static void
+uep_start_read(struct usb_fifo *fifo)
+{
+ struct uep_softc *sc = usb_fifo_softc(fifo);
+ u_int rate;
+
+ if ((rate = sc->pollrate) > 1000)
+ rate = 1000;
+
+ if (rate > 0 && sc->xfer[UEP_INTR_DT] != NULL) {
+ usbd_transfer_stop(sc->xfer[UEP_INTR_DT]);
+ usbd_xfer_set_interval(sc->xfer[UEP_INTR_DT], 1000 / rate);
+ sc->pollrate = 0;
+ }
+
+ usbd_transfer_start(sc->xfer[UEP_INTR_DT]);
+}
+
+static void
+uep_stop_read(struct usb_fifo *fifo)
+{
+ struct uep_softc *sc = usb_fifo_softc(fifo);
+
+ usbd_transfer_stop(sc->xfer[UEP_INTR_DT]);
+}
+
+static void
+uep_put_queue(struct uep_softc *sc, u_char *buf)
+{
+ usb_fifo_put_data_linear(sc->fifo.fp[USB_FIFO_RX], buf,
+ UEP_PACKET_LEN_REPORT, 1);
+}
+
+static int
+uep_open(struct usb_fifo *fifo, int fflags)
+{
+ if (fflags & FREAD) {
+ struct uep_softc *sc = usb_fifo_softc(fifo);
+
+ if (sc->state & UEP_ENABLED)
+ return (EBUSY);
+ if (usb_fifo_alloc_buffer(fifo, UEP_FIFO_BUF_SIZE,
+ UEP_FIFO_QUEUE_MAXLEN))
+ return (ENOMEM);
+
+ sc->state |= UEP_ENABLED;
+ }
+
+ return (0);
+}
+
+static void
+uep_close(struct usb_fifo *fifo, int fflags)
+{
+ if (fflags & FREAD) {
+ struct uep_softc *sc = usb_fifo_softc(fifo);
+
+ sc->state &= ~(UEP_ENABLED);
+ usb_fifo_free_buffer(fifo);
+ }
+}
+#endif /* !EVDEV_SUPPORT */
+
+static device_method_t uep_methods[] = {
+ DEVMETHOD(device_probe, uep_probe),
+ DEVMETHOD(device_attach, uep_attach),
+ DEVMETHOD(device_detach, uep_detach),
+ { 0, 0 },
+};
+
+static driver_t uep_driver = {
+ .name = "uep",
+ .methods = uep_methods,
+ .size = sizeof(struct uep_softc),
+};
+
+DRIVER_MODULE(uep, uhub, uep_driver, NULL, NULL);
+MODULE_DEPEND(uep, usb, 1, 1, 1);
+#ifdef EVDEV_SUPPORT
+MODULE_DEPEND(uep, evdev, 1, 1, 1);
+#endif
+MODULE_VERSION(uep, 1);
+USB_PNP_HOST_INFO(uep_devs);
diff --git a/sys/dev/usb/input/uhid.c b/sys/dev/usb/input/uhid.c
new file mode 100644
index 000000000000..a31081663f0c
--- /dev/null
+++ b/sys/dev/usb/input/uhid.c
@@ -0,0 +1,944 @@
+/* $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 $
+ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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
+ */
+
+#include "opt_hid.h"
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+
+#include <dev/hid/hid.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/usb_ioctl.h>
+#include <dev/usb/usb_generic.h>
+
+#define USB_DEBUG_VAR uhid_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/input/usb_rdesc.h>
+#include <dev/usb/quirk/usb_quirk.h>
+
+#ifdef USB_DEBUG
+static int uhid_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uhid, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB uhid");
+SYSCTL_INT(_hw_usb_uhid, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &uhid_debug, 0, "Debug level");
+#endif
+
+#define UHID_BSIZE 1024 /* bytes, buffer size */
+#define UHID_FRAME_NUM 50 /* bytes, frame number */
+
+enum {
+ UHID_INTR_DT_WR,
+ UHID_INTR_DT_RD,
+ UHID_CTRL_DT_WR,
+ UHID_CTRL_DT_RD,
+ UHID_N_TRANSFER,
+};
+
+struct uhid_softc {
+ struct usb_fifo_sc sc_fifo;
+ struct mtx sc_mtx;
+
+ struct usb_xfer *sc_xfer[UHID_N_TRANSFER];
+ struct usb_device *sc_udev;
+ void *sc_repdesc_ptr;
+
+ uint32_t sc_isize;
+ uint32_t sc_osize;
+ uint32_t sc_fsize;
+
+ uint16_t sc_repdesc_size;
+
+ uint8_t sc_iface_no;
+ uint8_t sc_iface_index;
+ uint8_t sc_iid;
+ uint8_t sc_oid;
+ uint8_t sc_fid;
+ uint8_t sc_flags;
+#define UHID_FLAG_IMMED 0x01 /* set if read should be immediate */
+#define UHID_FLAG_STATIC_DESC 0x04 /* set if report descriptors are
+ * static */
+};
+
+static const uint8_t uhid_xb360gp_report_descr[] = {UHID_XB360GP_REPORT_DESCR()};
+static const uint8_t uhid_graphire_report_descr[] = {UHID_GRAPHIRE_REPORT_DESCR()};
+static const uint8_t uhid_graphire3_4x5_report_descr[] = {UHID_GRAPHIRE3_4X5_REPORT_DESCR()};
+
+/* prototypes */
+
+static device_probe_t uhid_probe;
+static device_attach_t uhid_attach;
+static device_detach_t uhid_detach;
+
+static usb_callback_t uhid_intr_write_callback;
+static usb_callback_t uhid_intr_read_callback;
+static usb_callback_t uhid_write_callback;
+static usb_callback_t uhid_read_callback;
+
+static usb_fifo_cmd_t uhid_start_read;
+static usb_fifo_cmd_t uhid_stop_read;
+static usb_fifo_cmd_t uhid_start_write;
+static usb_fifo_cmd_t uhid_stop_write;
+static usb_fifo_open_t uhid_open;
+static usb_fifo_close_t uhid_close;
+static usb_fifo_ioctl_t uhid_ioctl;
+static usb_fifo_ioctl_t uhid_ioctl_post;
+
+static struct usb_fifo_methods uhid_fifo_methods = {
+ .f_open = &uhid_open,
+ .f_close = &uhid_close,
+ .f_ioctl = &uhid_ioctl,
+ .f_ioctl_post = &uhid_ioctl_post,
+ .f_start_read = &uhid_start_read,
+ .f_stop_read = &uhid_stop_read,
+ .f_start_write = &uhid_start_write,
+ .f_stop_write = &uhid_stop_write,
+ .basename[0] = "uhid",
+};
+
+static void
+uhid_intr_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhid_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (usb_fifo_get_data(sc->sc_fifo.fp[USB_FIFO_TX], pc,
+ 0, usbd_xfer_max_len(xfer), &actlen, 0)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uhid_intr_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhid_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTF("transferred!\n");
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+
+ /*
+ * If the ID byte is non zero we allow descriptors
+ * having multiple sizes:
+ */
+ if ((actlen >= (int)sc->sc_isize) ||
+ ((actlen > 0) && (sc->sc_iid != 0))) {
+ /* limit report length to the maximum */
+ if (actlen > (int)sc->sc_isize)
+ actlen = sc->sc_isize;
+ usb_fifo_put_data(sc->sc_fifo.fp[USB_FIFO_RX], pc,
+ 0, actlen, 1);
+
+ /*
+ * Do not do read-ahead, because this may lead
+ * to data loss!
+ */
+ return;
+ } else {
+ /* ignore it */
+ DPRINTF("ignored transfer, %d bytes\n", actlen);
+ }
+
+ case USB_ST_SETUP:
+re_submit:
+ if (usb_fifo_put_bytes_max(
+ sc->sc_fifo.fp[USB_FIFO_RX]) != 0) {
+ usbd_xfer_set_frame_len(xfer, 0, sc->sc_isize);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto re_submit;
+ }
+ return;
+ }
+}
+
+static void
+uhid_fill_set_report(struct usb_device_request *req, uint8_t iface_no,
+ uint8_t type, uint8_t id, uint16_t size)
+{
+ req->bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req->bRequest = UR_SET_REPORT;
+ USETW2(req->wValue, type, id);
+ req->wIndex[0] = iface_no;
+ req->wIndex[1] = 0;
+ USETW(req->wLength, size);
+}
+
+static void
+uhid_fill_get_report(struct usb_device_request *req, uint8_t iface_no,
+ uint8_t type, uint8_t id, uint16_t size)
+{
+ req->bmRequestType = UT_READ_CLASS_INTERFACE;
+ req->bRequest = UR_GET_REPORT;
+ USETW2(req->wValue, type, id);
+ req->wIndex[0] = iface_no;
+ req->wIndex[1] = 0;
+ USETW(req->wLength, size);
+}
+
+static void
+uhid_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhid_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_device_request req;
+ struct usb_page_cache *pc;
+ uint32_t size = sc->sc_osize;
+ uint32_t actlen;
+ uint8_t id;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+ /* try to extract the ID byte */
+ if (sc->sc_oid) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (usb_fifo_get_data(sc->sc_fifo.fp[USB_FIFO_TX], pc,
+ 0, 1, &actlen, 0)) {
+ if (actlen != 1) {
+ goto tr_error;
+ }
+ usbd_copy_out(pc, 0, &id, 1);
+
+ } else {
+ return;
+ }
+ if (size) {
+ size--;
+ }
+ } else {
+ id = 0;
+ }
+
+ pc = usbd_xfer_get_frame(xfer, 1);
+ if (usb_fifo_get_data(sc->sc_fifo.fp[USB_FIFO_TX], pc,
+ 0, UHID_BSIZE, &actlen, 1)) {
+ if (actlen != size) {
+ goto tr_error;
+ }
+ uhid_fill_set_report
+ (&req, sc->sc_iface_no,
+ UHID_OUTPUT_REPORT, id, size);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, size);
+ usbd_xfer_set_frames(xfer, size ? 2 : 1);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default:
+tr_error:
+ /* bomb out */
+ usb_fifo_get_data_error(sc->sc_fifo.fp[USB_FIFO_TX]);
+ return;
+ }
+}
+
+static void
+uhid_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhid_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_device_request req;
+ struct usb_page_cache *pc;
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ usb_fifo_put_data(sc->sc_fifo.fp[USB_FIFO_RX], pc, sizeof(req),
+ sc->sc_isize, 1);
+ return;
+
+ case USB_ST_SETUP:
+
+ if (usb_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) > 0) {
+ uhid_fill_get_report
+ (&req, sc->sc_iface_no, UHID_INPUT_REPORT,
+ sc->sc_iid, sc->sc_isize);
+
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, sc->sc_isize);
+ usbd_xfer_set_frames(xfer, sc->sc_isize ? 2 : 1);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ /* bomb out */
+ usb_fifo_put_data_error(sc->sc_fifo.fp[USB_FIFO_RX]);
+ return;
+ }
+}
+
+static const struct usb_config uhid_config[UHID_N_TRANSFER] = {
+ [UHID_INTR_DT_WR] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .flags = {.pipe_bof = 1,.no_pipe_ok = 1, },
+ .bufsize = UHID_BSIZE,
+ .callback = &uhid_intr_write_callback,
+ },
+
+ [UHID_INTR_DT_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = UHID_BSIZE,
+ .callback = &uhid_intr_read_callback,
+ },
+
+ [UHID_CTRL_DT_WR] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request) + UHID_BSIZE,
+ .callback = &uhid_write_callback,
+ .timeout = 1000, /* 1 second */
+ },
+
+ [UHID_CTRL_DT_RD] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request) + UHID_BSIZE,
+ .callback = &uhid_read_callback,
+ .timeout = 1000, /* 1 second */
+ },
+};
+
+static void
+uhid_start_read(struct usb_fifo *fifo)
+{
+ struct uhid_softc *sc = usb_fifo_softc(fifo);
+
+ if (sc->sc_flags & UHID_FLAG_IMMED) {
+ usbd_transfer_start(sc->sc_xfer[UHID_CTRL_DT_RD]);
+ } else {
+ usbd_transfer_start(sc->sc_xfer[UHID_INTR_DT_RD]);
+ }
+}
+
+static void
+uhid_stop_read(struct usb_fifo *fifo)
+{
+ struct uhid_softc *sc = usb_fifo_softc(fifo);
+
+ usbd_transfer_stop(sc->sc_xfer[UHID_CTRL_DT_RD]);
+ usbd_transfer_stop(sc->sc_xfer[UHID_INTR_DT_RD]);
+}
+
+static void
+uhid_start_write(struct usb_fifo *fifo)
+{
+ struct uhid_softc *sc = usb_fifo_softc(fifo);
+
+ if ((sc->sc_flags & UHID_FLAG_IMMED) ||
+ sc->sc_xfer[UHID_INTR_DT_WR] == NULL) {
+ usbd_transfer_start(sc->sc_xfer[UHID_CTRL_DT_WR]);
+ } else {
+ usbd_transfer_start(sc->sc_xfer[UHID_INTR_DT_WR]);
+ }
+}
+
+static void
+uhid_stop_write(struct usb_fifo *fifo)
+{
+ struct uhid_softc *sc = usb_fifo_softc(fifo);
+
+ usbd_transfer_stop(sc->sc_xfer[UHID_CTRL_DT_WR]);
+ usbd_transfer_stop(sc->sc_xfer[UHID_INTR_DT_WR]);
+}
+
+static int
+uhid_get_report(struct uhid_softc *sc, uint8_t type,
+ uint8_t id, void *kern_data, void *user_data,
+ uint16_t len)
+{
+ int err;
+ uint8_t free_data = 0;
+
+ if (kern_data == NULL) {
+ kern_data = malloc(len, M_USBDEV, M_WAITOK);
+ free_data = 1;
+ }
+ err = usbd_req_get_report(sc->sc_udev, NULL, kern_data,
+ len, sc->sc_iface_index, type, id);
+ if (err) {
+ err = ENXIO;
+ goto done;
+ }
+ if (user_data) {
+ /* dummy buffer */
+ err = copyout(kern_data, user_data, len);
+ if (err) {
+ goto done;
+ }
+ }
+done:
+ if (free_data) {
+ free(kern_data, M_USBDEV);
+ }
+ return (err);
+}
+
+static int
+uhid_set_report(struct uhid_softc *sc, uint8_t type,
+ uint8_t id, void *kern_data, void *user_data,
+ uint16_t len)
+{
+ int err;
+ uint8_t free_data = 0;
+
+ if (kern_data == NULL) {
+ kern_data = malloc(len, M_USBDEV, M_WAITOK);
+ free_data = 1;
+ err = copyin(user_data, kern_data, len);
+ if (err) {
+ goto done;
+ }
+ }
+ err = usbd_req_set_report(sc->sc_udev, NULL, kern_data,
+ len, sc->sc_iface_index, type, id);
+ if (err) {
+ err = ENXIO;
+ goto done;
+ }
+done:
+ if (free_data) {
+ free(kern_data, M_USBDEV);
+ }
+ return (err);
+}
+
+static int
+uhid_open(struct usb_fifo *fifo, int fflags)
+{
+ struct uhid_softc *sc = usb_fifo_softc(fifo);
+
+ /*
+ * The buffers are one byte larger than maximum so that one
+ * can detect too large read/writes and short transfers:
+ */
+ if (fflags & FREAD) {
+ /* reset flags */
+ mtx_lock(&sc->sc_mtx);
+ sc->sc_flags &= ~UHID_FLAG_IMMED;
+ mtx_unlock(&sc->sc_mtx);
+
+ if (usb_fifo_alloc_buffer(fifo,
+ sc->sc_isize + 1, UHID_FRAME_NUM)) {
+ return (ENOMEM);
+ }
+ }
+ if (fflags & FWRITE) {
+ if (usb_fifo_alloc_buffer(fifo,
+ sc->sc_osize + 1, UHID_FRAME_NUM)) {
+ return (ENOMEM);
+ }
+ }
+ return (0);
+}
+
+static void
+uhid_close(struct usb_fifo *fifo, int fflags)
+{
+ if (fflags & (FREAD | FWRITE)) {
+ usb_fifo_free_buffer(fifo);
+ }
+}
+
+static int
+uhid_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr,
+ int fflags)
+{
+ struct uhid_softc *sc = usb_fifo_softc(fifo);
+ struct usb_gen_descriptor *ugd;
+#ifdef COMPAT_FREEBSD32
+ struct usb_gen_descriptor local_ugd;
+ struct usb_gen_descriptor32 *ugd32 = NULL;
+#endif
+ uint32_t size;
+ int error = 0;
+ uint8_t id;
+
+ ugd = addr;
+#ifdef COMPAT_FREEBSD32
+ switch (cmd) {
+ case USB_GET_REPORT_DESC32:
+ case USB_GET_REPORT32:
+ case USB_SET_REPORT32:
+ ugd32 = addr;
+ ugd = &local_ugd;
+ usb_gen_descriptor_from32(ugd, ugd32);
+ cmd = _IOC_NEWTYPE(cmd, struct usb_gen_descriptor);
+ break;
+ }
+#endif
+
+ switch (cmd) {
+ case USB_GET_REPORT_DESC:
+ if (sc->sc_repdesc_size > ugd->ugd_maxlen) {
+ size = ugd->ugd_maxlen;
+ } else {
+ size = sc->sc_repdesc_size;
+ }
+ ugd->ugd_actlen = size;
+ if (ugd->ugd_data == NULL)
+ break; /* descriptor length only */
+ error = copyout(sc->sc_repdesc_ptr, ugd->ugd_data, size);
+ break;
+
+ case USB_SET_IMMED:
+ if (!(fflags & FREAD)) {
+ error = EPERM;
+ break;
+ }
+ if (*(int *)addr) {
+ /* do a test read */
+
+ error = uhid_get_report(sc, UHID_INPUT_REPORT,
+ sc->sc_iid, NULL, NULL, sc->sc_isize);
+ if (error) {
+ break;
+ }
+ mtx_lock(&sc->sc_mtx);
+ sc->sc_flags |= UHID_FLAG_IMMED;
+ mtx_unlock(&sc->sc_mtx);
+ } else {
+ mtx_lock(&sc->sc_mtx);
+ sc->sc_flags &= ~UHID_FLAG_IMMED;
+ mtx_unlock(&sc->sc_mtx);
+ }
+ break;
+
+ case USB_GET_REPORT:
+ if (!(fflags & FREAD)) {
+ error = EPERM;
+ break;
+ }
+ switch (ugd->ugd_report_type) {
+ 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);
+ }
+ size = imin(ugd->ugd_maxlen, size);
+ if (id != 0)
+ error = copyin(ugd->ugd_data, &id, 1);
+ if (error == 0)
+ error = uhid_get_report(sc, ugd->ugd_report_type, id,
+ NULL, ugd->ugd_data, size);
+ ugd->ugd_actlen = size;
+ break;
+
+ case USB_SET_REPORT:
+ if (!(fflags & FWRITE)) {
+ error = EPERM;
+ break;
+ }
+ switch (ugd->ugd_report_type) {
+ 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);
+ }
+ if (id != 0)
+ error = copyin(ugd->ugd_data, &id, 1);
+ if (error == 0)
+ error = uhid_set_report(sc, ugd->ugd_report_type, id,
+ NULL, ugd->ugd_data, imin(ugd->ugd_maxlen, size));
+ break;
+
+ case USB_GET_REPORT_ID:
+ *(int *)addr = 0; /* XXX: we only support reportid 0? */
+ break;
+
+ default:
+ error = ENOIOCTL;
+ break;
+ }
+#ifdef COMPAT_FREEBSD32
+ if (ugd32 != NULL)
+ update_usb_gen_descriptor32(ugd32, ugd);
+#endif
+ return (error);
+}
+
+static int
+uhid_ioctl_post(struct usb_fifo *fifo, u_long cmd, void *addr,
+ int fflags)
+{
+ int error;
+
+ switch (cmd) {
+ case USB_GET_DEVICEINFO:
+ error = ugen_fill_deviceinfo(fifo, addr);
+ break;
+
+ default:
+ error = EINVAL;
+ break;
+ }
+ return (error);
+}
+
+static const STRUCT_USB_HOST_ID uhid_devs[] = {
+ /* generic HID class */
+ {USB_IFACE_CLASS(UICLASS_HID),},
+ /* the Xbox 360 gamepad doesn't use the HID class */
+ {USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(UISUBCLASS_XBOX360_CONTROLLER),
+ USB_IFACE_PROTOCOL(UIPROTO_XBOX360_GAMEPAD),},
+};
+
+static int
+uhid_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ int error;
+ void *buf;
+ uint16_t len;
+
+ DPRINTFN(11, "\n");
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ error = usbd_lookup_id_by_uaa(uhid_devs, sizeof(uhid_devs), uaa);
+ if (error)
+ return (error);
+
+ if (usb_test_quirk(uaa, UQ_HID_IGNORE))
+ return (ENXIO);
+
+ /*
+ * Don't attach to mouse and keyboard devices, hence then no
+ * "nomatch" event is generated and then ums and ukbd won't
+ * attach properly when loaded.
+ */
+ if ((uaa->info.bInterfaceClass == UICLASS_HID) &&
+ (uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) &&
+ (((uaa->info.bInterfaceProtocol == UIPROTO_BOOT_KEYBOARD) &&
+ !usb_test_quirk(uaa, UQ_KBD_IGNORE)) ||
+ ((uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) &&
+ !usb_test_quirk(uaa, UQ_UMS_IGNORE))))
+ return (ENXIO);
+
+ /* Check for mandatory multitouch usages to give wmt(4) a chance */
+ if (!usb_test_quirk(uaa, UQ_WMT_IGNORE)) {
+ error = usbd_req_get_hid_desc(uaa->device, NULL,
+ &buf, &len, M_USBDEV, uaa->info.bIfaceIndex);
+ /* Let HID decscriptor-less devices to be handled at attach */
+ if (!error) {
+ if (hid_locate(buf, len,
+ HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACT_MAX),
+ hid_feature, 0, NULL, NULL, NULL) &&
+ hid_locate(buf, len,
+ HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID),
+ hid_input, 0, NULL, NULL, NULL)) {
+ free(buf, M_USBDEV);
+ return (ENXIO);
+ }
+ free(buf, M_USBDEV);
+ }
+ }
+
+ return (BUS_PROBE_GENERIC);
+}
+
+static int
+uhid_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct uhid_softc *sc = device_get_softc(dev);
+ int unit = device_get_unit(dev);
+ int error = 0;
+
+ DPRINTFN(10, "sc=%p\n", sc);
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, "uhid lock", NULL, MTX_DEF | MTX_RECURSE);
+
+ sc->sc_udev = uaa->device;
+
+ sc->sc_iface_no = uaa->info.bIfaceNum;
+ sc->sc_iface_index = uaa->info.bIfaceIndex;
+
+ error = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer, uhid_config,
+ UHID_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error) {
+ DPRINTF("error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+ if (uaa->info.idVendor == USB_VENDOR_WACOM) {
+ /* the report descriptor for the Wacom Graphire is broken */
+
+ if (uaa->info.idProduct == USB_PRODUCT_WACOM_GRAPHIRE) {
+ sc->sc_repdesc_size = sizeof(uhid_graphire_report_descr);
+ sc->sc_repdesc_ptr = __DECONST(void *, &uhid_graphire_report_descr);
+ sc->sc_flags |= UHID_FLAG_STATIC_DESC;
+
+ } else if (uaa->info.idProduct == USB_PRODUCT_WACOM_GRAPHIRE3_4X5) {
+ static uint8_t reportbuf[] = {2, 2, 2};
+
+ /*
+ * The Graphire3 needs 0x0202 to be written to
+ * feature report ID 2 before it'll start
+ * returning digitizer data.
+ */
+ error = usbd_req_set_report(uaa->device, NULL,
+ reportbuf, sizeof(reportbuf),
+ uaa->info.bIfaceIndex, UHID_FEATURE_REPORT, 2);
+
+ if (error) {
+ DPRINTF("set report failed, error=%s (ignored)\n",
+ usbd_errstr(error));
+ }
+ sc->sc_repdesc_size = sizeof(uhid_graphire3_4x5_report_descr);
+ sc->sc_repdesc_ptr = __DECONST(void *, &uhid_graphire3_4x5_report_descr);
+ sc->sc_flags |= UHID_FLAG_STATIC_DESC;
+ }
+ } else if ((uaa->info.bInterfaceClass == UICLASS_VENDOR) &&
+ (uaa->info.bInterfaceSubClass == UISUBCLASS_XBOX360_CONTROLLER) &&
+ (uaa->info.bInterfaceProtocol == UIPROTO_XBOX360_GAMEPAD)) {
+ static const uint8_t reportbuf[3] = {1, 3, 0};
+ /*
+ * Turn off the four LEDs on the gamepad which
+ * are blinking by default:
+ */
+ error = usbd_req_set_report(uaa->device, NULL,
+ __DECONST(void *, reportbuf), sizeof(reportbuf),
+ uaa->info.bIfaceIndex, UHID_OUTPUT_REPORT, 0);
+ if (error) {
+ DPRINTF("set output report failed, error=%s (ignored)\n",
+ usbd_errstr(error));
+ }
+ /* the Xbox 360 gamepad has no report descriptor */
+ sc->sc_repdesc_size = sizeof(uhid_xb360gp_report_descr);
+ sc->sc_repdesc_ptr = __DECONST(void *, &uhid_xb360gp_report_descr);
+ sc->sc_flags |= UHID_FLAG_STATIC_DESC;
+ }
+ if (sc->sc_repdesc_ptr == NULL) {
+ error = usbd_req_get_hid_desc(uaa->device, NULL,
+ &sc->sc_repdesc_ptr, &sc->sc_repdesc_size,
+ M_USBDEV, uaa->info.bIfaceIndex);
+
+ if (error) {
+ device_printf(dev, "no report descriptor\n");
+ goto detach;
+ }
+ }
+ error = usbd_req_set_idle(uaa->device, NULL,
+ uaa->info.bIfaceIndex, 0, 0);
+
+ if (error) {
+ DPRINTF("set idle failed, error=%s (ignored)\n",
+ usbd_errstr(error));
+ }
+ sc->sc_isize = hid_report_size_max
+ (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_input, &sc->sc_iid);
+
+ sc->sc_osize = hid_report_size_max
+ (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_output, &sc->sc_oid);
+
+ sc->sc_fsize = hid_report_size_max
+ (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_feature, &sc->sc_fid);
+
+ if (sc->sc_isize > UHID_BSIZE) {
+ DPRINTF("input size is too large, "
+ "%d bytes (truncating)\n",
+ sc->sc_isize);
+ sc->sc_isize = UHID_BSIZE;
+ }
+ if (sc->sc_osize > UHID_BSIZE) {
+ DPRINTF("output size is too large, "
+ "%d bytes (truncating)\n",
+ sc->sc_osize);
+ sc->sc_osize = UHID_BSIZE;
+ }
+ if (sc->sc_fsize > UHID_BSIZE) {
+ DPRINTF("feature size is too large, "
+ "%d bytes (truncating)\n",
+ sc->sc_fsize);
+ sc->sc_fsize = UHID_BSIZE;
+ }
+
+ error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx,
+ &uhid_fifo_methods, &sc->sc_fifo,
+ unit, -1, uaa->info.bIfaceIndex,
+ UID_ROOT, GID_OPERATOR, 0644);
+ if (error) {
+ goto detach;
+ }
+ return (0); /* success */
+
+detach:
+ uhid_detach(dev);
+ return (ENOMEM);
+}
+
+static int
+uhid_detach(device_t dev)
+{
+ struct uhid_softc *sc = device_get_softc(dev);
+
+ usb_fifo_detach(&sc->sc_fifo);
+
+ usbd_transfer_unsetup(sc->sc_xfer, UHID_N_TRANSFER);
+
+ if (sc->sc_repdesc_ptr) {
+ if (!(sc->sc_flags & UHID_FLAG_STATIC_DESC)) {
+ free(sc->sc_repdesc_ptr, M_USBDEV);
+ }
+ }
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static device_method_t uhid_methods[] = {
+ DEVMETHOD(device_probe, uhid_probe),
+ DEVMETHOD(device_attach, uhid_attach),
+ DEVMETHOD(device_detach, uhid_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t uhid_driver = {
+#ifdef HIDRAW_MAKE_UHID_ALIAS
+ .name = "hidraw",
+#else
+ .name = "uhid",
+#endif
+ .methods = uhid_methods,
+ .size = sizeof(struct uhid_softc),
+};
+
+DRIVER_MODULE(uhid, uhub, uhid_driver, NULL, NULL);
+MODULE_DEPEND(uhid, usb, 1, 1, 1);
+MODULE_DEPEND(uhid, hid, 1, 1, 1);
+MODULE_VERSION(uhid, 1);
+USB_PNP_HOST_INFO(uhid_devs);
diff --git a/sys/dev/usb/input/uhid_snes.c b/sys/dev/usb/input/uhid_snes.c
new file mode 100644
index 000000000000..7a16542794cc
--- /dev/null
+++ b/sys/dev/usb/input/uhid_snes.c
@@ -0,0 +1,643 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2013, Michael Terrell <vashisnotatree@gmail.com>
+ * Copyright 2018, Johannes Lundberg <johalun0@gmail.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.
+ *
+ * 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/param.h>
+#include <sys/module.h>
+#include <sys/kernel.h>
+#include <sys/systm.h>
+
+#include <sys/conf.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/syslog.h>
+#include <sys/fcntl.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/usb_ioctl.h>
+
+#include "usb_rdesc.h"
+
+#define UHID_SNES_IFQ_MAX_LEN 8
+
+#define UREQ_GET_PORT_STATUS 0x01
+#define UREQ_SOFT_RESET 0x02
+
+#define UP 0x7f00
+#define DOWN 0x7fff
+#define LEFT 0x00ff
+#define RIGHT 0xff7f
+#define X 0x1f
+#define Y 0x8f
+#define A 0x2f
+#define B 0x4f
+#define SELECT 0x10
+#define START 0x20
+#define LEFT_T 0x01
+#define RIGHT_T 0x02
+
+static const uint8_t uhid_snes_report_descr[] = { UHID_SNES_REPORT_DESCR() };
+#define SNES_DEV(v,p,i) { USB_VPI(v,p,i) }
+
+static const STRUCT_USB_HOST_ID snes_devs[] = {
+ SNES_DEV(0x0810, 0xe501, 0), /* GeeekPi K-0161 */
+ SNES_DEV(0x0079, 0x0011, 0) /* Dragonrise */
+};
+
+enum {
+ UHID_SNES_INTR_DT_RD,
+ UHID_SNES_STATUS_DT_RD,
+ UHID_SNES_N_TRANSFER
+};
+
+struct uhid_snes_softc {
+ device_t sc_dev;
+ struct usb_device *sc_usb_device;
+ struct mtx sc_mutex;
+ struct usb_callout sc_watchdog;
+ uint8_t sc_iface_num;
+ struct usb_xfer *sc_transfer[UHID_SNES_N_TRANSFER];
+ struct usb_fifo_sc sc_fifo;
+ struct usb_fifo_sc sc_fifo_no_reset;
+ int sc_fflags;
+ struct usb_fifo *sc_fifo_open[2];
+ uint8_t sc_zero_length_packets;
+ uint8_t sc_iid;
+ uint8_t sc_oid;
+ uint8_t sc_fid;
+ uint8_t sc_iface_index;
+
+ uint32_t sc_isize;
+ uint32_t sc_osize;
+ uint32_t sc_fsize;
+
+ void *sc_repdesc_ptr;
+
+ uint16_t sc_repdesc_size;
+
+ struct usb_device *sc_udev;
+#define UHID_FLAG_IMMED 0x01 /* set if read should be immediate */
+
+};
+
+static device_probe_t uhid_snes_probe;
+static device_attach_t uhid_snes_attach;
+static device_detach_t uhid_snes_detach;
+
+static usb_fifo_open_t uhid_snes_open;
+static usb_fifo_close_t uhid_snes_close;
+static usb_fifo_ioctl_t uhid_snes_ioctl;
+static usb_fifo_cmd_t uhid_snes_start_read;
+static usb_fifo_cmd_t uhid_snes_stop_read;
+
+static void uhid_snes_reset(struct uhid_snes_softc *);
+static void uhid_snes_watchdog(void *);
+
+static usb_callback_t uhid_snes_read_callback;
+static usb_callback_t uhid_snes_status_callback;
+
+static struct usb_fifo_methods uhid_snes_fifo_methods = {
+ .f_open = &uhid_snes_open,
+ .f_close = &uhid_snes_close,
+ .f_ioctl = &uhid_snes_ioctl,
+ .f_start_read = &uhid_snes_start_read,
+ .f_stop_read = &uhid_snes_stop_read,
+ .basename[0] = "uhid_snes"
+};
+
+static const struct usb_config uhid_snes_config[UHID_SNES_N_TRANSFER] = {
+ [UHID_SNES_INTR_DT_RD] = {
+ .callback = &uhid_snes_read_callback,
+ .bufsize = sizeof(struct usb_device_request) +1,
+ .flags = {.short_xfer_ok = 1, .short_frames_ok = 1,
+ .pipe_bof =1, .proxy_buffer =1},
+ .type = UE_INTERRUPT,
+ .endpoint = 0x81,
+ .direction = UE_DIR_IN
+ },
+ [UHID_SNES_STATUS_DT_RD] = {
+ .callback = &uhid_snes_status_callback,
+ .bufsize = sizeof(struct usb_device_request) + 1,
+ .timeout = 1000,
+ .type = UE_CONTROL,
+ .endpoint = 0x00,
+ .direction = UE_DIR_ANY
+ }
+};
+
+static int
+uhid_get_report(struct uhid_snes_softc *sc, uint8_t type,
+ uint8_t id, void *kern_data, void *user_data, uint16_t len)
+{
+ int err;
+ uint8_t free_data = 0;
+
+ if (kern_data == NULL) {
+ kern_data = malloc(len, M_USBDEV, M_WAITOK);
+ free_data = 1;
+ }
+ err = usbd_req_get_report(sc->sc_udev, NULL, kern_data,
+ len, sc->sc_iface_index, type, id);
+ if (err) {
+ err = ENXIO;
+ goto done;
+ }
+ if (user_data) {
+ /* dummy buffer */
+ err = copyout(kern_data, user_data, len);
+ if (err) {
+ goto done;
+ }
+ }
+done:
+ if (free_data) {
+ free(kern_data, M_USBDEV);
+ }
+ return (err);
+}
+
+static int
+uhid_set_report(struct uhid_snes_softc *sc, uint8_t type,
+ uint8_t id, void *kern_data, void *user_data, uint16_t len)
+{
+ int err;
+ uint8_t free_data = 0;
+
+ if (kern_data == NULL) {
+ kern_data = malloc(len, M_USBDEV, M_WAITOK);
+ free_data = 1;
+ err = copyin(user_data, kern_data, len);
+ if (err) {
+ goto done;
+ }
+ }
+ err = usbd_req_set_report(sc->sc_udev, NULL, kern_data,
+ len, sc->sc_iface_index, type, id);
+ if (err) {
+ err = ENXIO;
+ goto done;
+ }
+done:
+ if (free_data) {
+ free(kern_data, M_USBDEV);
+ }
+ return (err);
+}
+
+static int
+uhid_snes_open(struct usb_fifo *fifo, int fflags)
+{
+ struct uhid_snes_softc *sc = usb_fifo_softc(fifo);
+ int error;
+
+ if (sc->sc_fflags & fflags) {
+ uhid_snes_reset(sc);
+ return (EBUSY);
+ }
+
+ mtx_lock(&sc->sc_mutex);
+ usbd_xfer_set_stall(sc->sc_transfer[UHID_SNES_INTR_DT_RD]);
+ mtx_unlock(&sc->sc_mutex);
+
+ error = usb_fifo_alloc_buffer(fifo,
+ usbd_xfer_max_len(sc->sc_transfer[UHID_SNES_INTR_DT_RD]),
+ UHID_SNES_IFQ_MAX_LEN);
+ if (error)
+ return (ENOMEM);
+
+ sc->sc_fifo_open[USB_FIFO_RX] = fifo;
+
+ return (0);
+}
+
+static void
+uhid_snes_reset(struct uhid_snes_softc *sc)
+{
+ struct usb_device_request req;
+ int error;
+
+ req.bRequest = UREQ_SOFT_RESET;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, sc->sc_iface_num);
+ USETW(req.wLength, 0);
+
+ mtx_lock(&sc->sc_mutex);
+
+ error = usbd_do_request_flags(sc->sc_usb_device, &sc->sc_mutex,
+ &req, NULL, 0, NULL, 2 * USB_MS_HZ);
+
+ if (error) {
+ usbd_do_request_flags(sc->sc_usb_device, &sc->sc_mutex,
+ &req, NULL, 0, NULL, 2 * USB_MS_HZ);
+ }
+
+ mtx_unlock(&sc->sc_mutex);
+}
+
+static void
+uhid_snes_close(struct usb_fifo *fifo, int fflags)
+{
+ struct uhid_snes_softc *sc = usb_fifo_softc(fifo);
+
+ sc->sc_fflags &= ~(fflags & FREAD);
+ usb_fifo_free_buffer(fifo);
+}
+
+static int
+uhid_snes_ioctl(struct usb_fifo *fifo, u_long cmd, void *data, int fflags)
+{
+ struct uhid_snes_softc *sc = usb_fifo_softc(fifo);
+ struct usb_gen_descriptor *ugd;
+#ifdef COMPAT_FREEBSD32
+ struct usb_gen_descriptor local_ugd;
+ struct usb_gen_descriptor32 *ugd32 = NULL;
+#endif
+ uint32_t size;
+ int error = 0;
+ uint8_t id;
+
+ ugd = data;
+#ifdef COMPAT_FREEBSD32
+ switch (cmd) {
+ case USB_GET_REPORT_DESC32:
+ case USB_GET_REPORT32:
+ case USB_SET_REPORT32:
+ ugd32 = data;
+ ugd = &local_ugd;
+ usb_gen_descriptor_from32(ugd, ugd32);
+ cmd = _IOC_NEWTYPE(cmd, struct usb_gen_descriptor);
+ break;
+ }
+#endif
+
+ switch (cmd) {
+ case USB_GET_REPORT_DESC:
+ if (sc->sc_repdesc_size > ugd->ugd_maxlen) {
+ size = ugd->ugd_maxlen;
+ } else {
+ size = sc->sc_repdesc_size;
+ }
+
+ ugd->ugd_actlen = size;
+ if (ugd->ugd_data == NULL)
+ break; /* descriptor length only*/
+ error = copyout(sc->sc_repdesc_ptr, ugd->ugd_data, size);
+ break;
+
+ case USB_SET_IMMED:
+ if (!(fflags & FREAD)) {
+ error = EPERM;
+ break;
+ }
+
+ if (*(int *)data) {
+ /* do a test read */
+ error = uhid_get_report(sc, UHID_INPUT_REPORT,
+ sc->sc_iid, NULL, NULL, sc->sc_isize);
+ if (error) {
+ break;
+ }
+ mtx_lock(&sc->sc_mutex);
+ sc->sc_fflags |= UHID_FLAG_IMMED;
+ mtx_unlock(&sc->sc_mutex);
+ } else {
+ mtx_lock(&sc->sc_mutex);
+ sc->sc_fflags &= ~UHID_FLAG_IMMED;
+ mtx_unlock(&sc->sc_mutex);
+ }
+ break;
+
+ case USB_GET_REPORT:
+ if (!(fflags & FREAD)) {
+ error = EPERM;
+ break;
+ }
+ switch (ugd->ugd_report_type) {
+ 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);
+ }
+ if (id != 0)
+ error = copyin(ugd->ugd_data, &id, 1);
+ if (error == 0)
+ error = uhid_get_report(sc, ugd->ugd_report_type, id,
+ NULL, ugd->ugd_data, imin(ugd->ugd_maxlen, size));
+ break;
+
+ case USB_SET_REPORT:
+ if (!(fflags & FWRITE)) {
+ error = EPERM;
+ break;
+ }
+ switch (ugd->ugd_report_type) {
+ 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);
+ }
+ if (id != 0)
+ error = copyin(ugd->ugd_data, &id, 1);
+ if (error == 0)
+ error = uhid_set_report(sc, ugd->ugd_report_type, id,
+ NULL, ugd->ugd_data, imin(ugd->ugd_maxlen, size));
+ break;
+
+ case USB_GET_REPORT_ID:
+ /* XXX: we only support reportid 0? */
+ *(int *)data = 0;
+ break;
+
+ default:
+ error = EINVAL;
+ break;
+ }
+
+#ifdef COMPAT_FREEBSD32
+ if (ugd32 != NULL)
+ update_usb_gen_descriptor32(ugd32, ugd);
+#endif
+ return (error);
+}
+
+static void
+uhid_snes_watchdog(void *arg)
+{
+ struct uhid_snes_softc *sc = arg;
+
+ mtx_assert(&sc->sc_mutex, MA_OWNED);
+
+ if (sc->sc_fflags == 0)
+ usbd_transfer_start(sc->sc_transfer[UHID_SNES_STATUS_DT_RD]);
+
+ usb_callout_reset(&sc->sc_watchdog, hz, &uhid_snes_watchdog, sc);
+}
+
+static void
+uhid_snes_start_read(struct usb_fifo *fifo)
+{
+ struct uhid_snes_softc *sc = usb_fifo_softc(fifo);
+
+ usbd_transfer_start(sc->sc_transfer[UHID_SNES_INTR_DT_RD]);
+}
+
+static void
+uhid_snes_stop_read(struct usb_fifo *fifo)
+{
+ struct uhid_snes_softc *sc = usb_fifo_softc(fifo);
+
+ usbd_transfer_stop(sc->sc_transfer[UHID_SNES_INTR_DT_RD]);
+}
+
+static void
+uhid_snes_read_callback(struct usb_xfer *transfer, usb_error_t error)
+{
+ struct uhid_snes_softc *sc = usbd_xfer_softc(transfer);
+ struct usb_fifo *fifo = sc->sc_fifo_open[USB_FIFO_RX];
+ struct usb_page_cache *pc;
+ int actual, max;
+
+ usbd_xfer_status(transfer, &actual, NULL, NULL, NULL);
+ if (fifo == NULL)
+ return;
+
+ switch (USB_GET_STATE(transfer)) {
+ case USB_ST_TRANSFERRED:
+ if (actual == 0) {
+ if (sc->sc_zero_length_packets == 4)
+ /* Throttle transfers. */
+ usbd_xfer_set_interval(transfer, 500);
+ else
+ sc->sc_zero_length_packets++;
+
+ } else {
+ /* disable throttling. */
+ usbd_xfer_set_interval(transfer, 0);
+ sc->sc_zero_length_packets = 0;
+ }
+ pc = usbd_xfer_get_frame(transfer, 0);
+ usb_fifo_put_data(fifo, pc, 0, actual, 1);
+ /* Fall through */
+ setup:
+ case USB_ST_SETUP:
+ if (usb_fifo_put_bytes_max(fifo) != 0) {
+ max = usbd_xfer_max_len(transfer);
+ usbd_xfer_set_frame_len(transfer, 0, max);
+ usbd_transfer_submit(transfer);
+ }
+ break;
+
+ default:
+ /*disable throttling. */
+ usbd_xfer_set_interval(transfer, 0);
+ sc->sc_zero_length_packets = 0;
+
+ if (error != USB_ERR_CANCELLED) {
+ /* Issue a clear-stall request. */
+ usbd_xfer_set_stall(transfer);
+ goto setup;
+ }
+ break;
+ }
+}
+
+static void
+uhid_snes_status_callback(struct usb_xfer *transfer, usb_error_t error)
+{
+ struct uhid_snes_softc *sc = usbd_xfer_softc(transfer);
+ struct usb_device_request req;
+ struct usb_page_cache *pc;
+
+ switch (USB_GET_STATE(transfer)) {
+ case USB_ST_SETUP:
+ req.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.bRequest = UREQ_GET_PORT_STATUS;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_iface_num;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 1);
+
+ pc = usbd_xfer_get_frame(transfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+ usbd_xfer_set_frame_len(transfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(transfer, 1, 1);
+ usbd_xfer_set_frames(transfer, 2);
+ usbd_transfer_submit(transfer);
+ break;
+
+ default:
+ break;
+ }
+
+}
+
+static int
+uhid_snes_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(snes_devs, sizeof(snes_devs), uaa));
+}
+
+static int
+uhid_snes_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct uhid_snes_softc *sc = device_get_softc(dev);
+ struct usb_interface_descriptor *idesc;
+ struct usb_config_descriptor *cdesc;
+ uint8_t alt_index, iface_index = uaa->info.bIfaceIndex;
+ int error,unit = device_get_unit(dev);
+
+ sc->sc_dev = dev;
+ sc->sc_usb_device = uaa->device;
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mutex, "uhid_snes", NULL, MTX_DEF | MTX_RECURSE);
+ usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mutex, 0);
+
+ idesc = usbd_get_interface_descriptor(uaa->iface);
+ alt_index = -1;
+ for(;;) {
+ if (idesc == NULL)
+ break;
+
+ if ((idesc->bDescriptorType == UDESC_INTERFACE) &&
+ (idesc->bLength >= sizeof(*idesc))) {
+ if (idesc->bInterfaceNumber != uaa->info.bIfaceNum) {
+ break;
+ } else {
+ alt_index++;
+ if (idesc->bInterfaceClass == UICLASS_HID)
+ goto found;
+ }
+ }
+
+ cdesc = usbd_get_config_descriptor(uaa->device);
+ idesc = (void *)usb_desc_foreach(cdesc, (void *)idesc);
+ goto found;
+ }
+ goto detach;
+
+found:
+ if (alt_index) {
+ error = usbd_set_alt_interface_index(uaa->device, iface_index, alt_index);
+ if (error)
+ goto detach;
+ }
+
+ sc->sc_iface_num = idesc->bInterfaceNumber;
+
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_transfer, uhid_snes_config, UHID_SNES_N_TRANSFER, sc,
+ &sc->sc_mutex);
+
+ if (error)
+ goto detach;
+
+ error = usb_fifo_attach(uaa->device, sc, &sc->sc_mutex,
+ &uhid_snes_fifo_methods, &sc->sc_fifo, unit, -1,
+ iface_index, UID_ROOT, GID_OPERATOR, 0644);
+ sc->sc_repdesc_size = sizeof(uhid_snes_report_descr);
+ sc->sc_repdesc_ptr = __DECONST(void*, &uhid_snes_report_descr);
+
+ if (error)
+ goto detach;
+
+ mtx_lock(&sc->sc_mutex);
+ uhid_snes_watchdog(sc);
+ mtx_unlock(&sc->sc_mutex);
+ return (0);
+
+detach:
+ uhid_snes_detach(dev);
+ return (ENOMEM);
+}
+
+static int
+uhid_snes_detach(device_t dev)
+{
+ struct uhid_snes_softc *sc = device_get_softc(dev);
+
+ usb_fifo_detach(&sc->sc_fifo);
+ usb_fifo_detach(&sc->sc_fifo_no_reset);
+
+ mtx_lock(&sc->sc_mutex);
+ usb_callout_stop(&sc->sc_watchdog);
+ mtx_unlock(&sc->sc_mutex);
+
+ usbd_transfer_unsetup(sc->sc_transfer, UHID_SNES_N_TRANSFER);
+ usb_callout_drain(&sc->sc_watchdog);
+ mtx_destroy(&sc->sc_mutex);
+
+ return (0);
+}
+
+static device_method_t uhid_snes_methods[] = {
+ DEVMETHOD(device_probe, uhid_snes_probe),
+ DEVMETHOD(device_attach, uhid_snes_attach),
+ DEVMETHOD(device_detach, uhid_snes_detach),
+ DEVMETHOD_END
+};
+
+static driver_t uhid_snes_driver = {
+ "uhid_snes",
+ uhid_snes_methods,
+ sizeof(struct uhid_snes_softc)
+};
+
+DRIVER_MODULE(uhid_snes, uhub, uhid_snes_driver, NULL, NULL);
+MODULE_DEPEND(uhid_snes, usb, 1, 1, 1);
+USB_PNP_HOST_INFO(snes_devs);
diff --git a/sys/dev/usb/input/ukbd.c b/sys/dev/usb/input/ukbd.c
new file mode 100644
index 000000000000..57e9beac34b6
--- /dev/null
+++ b/sys/dev/usb/input/ukbd.c
@@ -0,0 +1,2257 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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
+ */
+
+#include "opt_kbd.h"
+#include "opt_ukbd.h"
+#include "opt_evdev.h"
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/proc.h>
+
+#include <dev/hid/hid.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+
+#define USB_DEBUG_VAR ukbd_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/quirk/usb_quirk.h>
+
+#include "usbdevs.h"
+
+#ifdef EVDEV_SUPPORT
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+#endif
+
+#include <sys/ioccom.h>
+#include <sys/filio.h>
+#include <sys/kbio.h>
+
+#include <dev/kbd/kbdreg.h>
+
+/* 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
+
+/* the following file must be included after "ukbdmap.h" */
+#include <dev/kbd/kbdtables.h>
+
+#ifdef USB_DEBUG
+static int ukbd_debug = 0;
+static int ukbd_no_leds = 0;
+static int ukbd_pollrate = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ukbd, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB keyboard");
+SYSCTL_INT(_hw_usb_ukbd, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &ukbd_debug, 0, "Debug level");
+SYSCTL_INT(_hw_usb_ukbd, OID_AUTO, no_leds, CTLFLAG_RWTUN,
+ &ukbd_no_leds, 0, "Disables setting of keyboard leds");
+SYSCTL_INT(_hw_usb_ukbd, OID_AUTO, pollrate, CTLFLAG_RWTUN,
+ &ukbd_pollrate, 0, "Force this polling rate, 1-1000Hz");
+#endif
+
+#define UKBD_EMULATE_ATSCANCODE 1
+#define UKBD_DRIVER_NAME "ukbd"
+#define UKBD_NKEYCODE 256 /* units */
+#define UKBD_IN_BUF_SIZE (4 * UKBD_NKEYCODE) /* scancodes */
+#define UKBD_IN_BUF_FULL ((UKBD_IN_BUF_SIZE / 2) - 1) /* scancodes */
+#define UKBD_NFKEY (sizeof(fkey_tab)/sizeof(fkey_tab[0])) /* units */
+#define UKBD_BUFFER_SIZE 64 /* bytes */
+#define UKBD_KEY_PRESSED(map, key) ({ \
+ CTASSERT((key) >= 0 && (key) < UKBD_NKEYCODE); \
+ ((map)[(key) / 64] & (1ULL << ((key) % 64))); \
+})
+
+#define MOD_EJECT 0x01
+#define MOD_FN 0x02
+
+struct ukbd_data {
+ uint64_t bitmap[howmany(UKBD_NKEYCODE, 64)];
+};
+
+enum {
+ UKBD_INTR_DT_0,
+ UKBD_INTR_DT_1,
+ UKBD_CTRL_LED,
+ UKBD_N_TRANSFER,
+};
+
+struct ukbd_softc {
+ keyboard_t sc_kbd;
+ keymap_t sc_keymap;
+ accentmap_t sc_accmap;
+ fkeytab_t sc_fkeymap[UKBD_NFKEY];
+ uint64_t sc_loc_key_valid[howmany(UKBD_NKEYCODE, 64)];
+ struct hid_location sc_loc_apple_eject;
+ struct hid_location sc_loc_apple_fn;
+ struct hid_location sc_loc_key[UKBD_NKEYCODE];
+ struct hid_location sc_loc_numlock;
+ struct hid_location sc_loc_capslock;
+ struct hid_location sc_loc_scrolllock;
+ struct usb_callout sc_callout;
+ struct ukbd_data sc_ndata;
+ struct ukbd_data sc_odata;
+
+ struct thread *sc_poll_thread;
+ struct usb_device *sc_udev;
+ struct usb_interface *sc_iface;
+ struct usb_xfer *sc_xfer[UKBD_N_TRANSFER];
+#ifdef EVDEV_SUPPORT
+ struct evdev_dev *sc_evdev;
+#endif
+
+ sbintime_t sc_co_basetime;
+ int sc_delay;
+ uint32_t sc_repeat_time;
+ uint32_t sc_input[UKBD_IN_BUF_SIZE]; /* input buffer */
+ uint32_t sc_time_ms;
+ uint32_t sc_composed_char; /* composed char code, if non-zero */
+#ifdef UKBD_EMULATE_ATSCANCODE
+ uint32_t sc_buffered_char[2];
+#endif
+ uint32_t sc_flags; /* flags */
+#define UKBD_FLAG_COMPOSE 0x00000001
+#define UKBD_FLAG_POLLING 0x00000002
+#define UKBD_FLAG_SET_LEDS 0x00000004
+#define UKBD_FLAG_ATTACHED 0x00000010
+#define UKBD_FLAG_GONE 0x00000020
+
+/* set in ukbd_attach */
+#define UKBD_FLAG_APPLE_SWAP 0x00000040
+/* set in ukbd_parse_hid */
+#define UKBD_FLAG_APPLE_EJECT 0x00000080
+#define UKBD_FLAG_APPLE_FN 0x00000100
+#define UKBD_FLAG_NUMLOCK 0x00080000
+#define UKBD_FLAG_CAPSLOCK 0x00100000
+#define UKBD_FLAG_SCROLLLOCK 0x00200000
+#define UKBD_FLAG_HID_MASK UKBD_FLAG_APPLE_EJECT | \
+ UKBD_FLAG_APPLE_FN | \
+ UKBD_FLAG_NUMLOCK | \
+ UKBD_FLAG_CAPSLOCK | \
+ UKBD_FLAG_SCROLLLOCK
+
+ int sc_mode; /* input mode (K_XLATE,K_RAW,K_CODE) */
+ int sc_state; /* shift/lock key state */
+ int sc_accents; /* accent key index (> 0) */
+ int sc_polling; /* polling recursion count */
+ int sc_led_size;
+ int sc_kbd_size;
+
+ uint16_t sc_inputs;
+ uint16_t sc_inputhead;
+ uint16_t sc_inputtail;
+
+ uint8_t sc_leds; /* store for async led requests */
+ uint8_t sc_iface_index;
+ uint8_t sc_iface_no;
+ uint8_t sc_id_apple_eject;
+ uint8_t sc_id_apple_fn;
+ uint8_t sc_id_loc_key[UKBD_NKEYCODE];
+ uint8_t sc_id_numlock;
+ uint8_t sc_id_capslock;
+ uint8_t sc_id_scrolllock;
+ uint8_t sc_kbd_id;
+ uint8_t sc_repeat_key;
+
+ uint8_t sc_buffer[UKBD_BUFFER_SIZE];
+};
+
+#define KEY_NONE 0x00
+#define KEY_ERROR 0x01
+
+#define KEY_PRESS 0
+#define KEY_RELEASE 0x400
+#define KEY_INDEX(c) ((c) & 0xFF)
+
+#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 UKBD_LOCK() USB_MTX_LOCK(&Giant)
+#define UKBD_UNLOCK() USB_MTX_UNLOCK(&Giant)
+#define UKBD_LOCK_ASSERT() USB_MTX_ASSERT(&Giant, MA_OWNED)
+
+#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
+ *
+ * USB Apple Keyboard JIS generates:
+ * 0x90: Kana
+ * 0x91: Eisu
+ */
+static const uint8_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, 123, /* 80 - 87 */
+ 124, 125, 126, 127, 128, NN, NN, NN, /* 88 - 8F */
+ 129, 130, 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 */
+};
+
+static const uint8_t ukbd_boot_desc[] = {
+ 0x05, 0x01, 0x09, 0x06, 0xa1,
+ 0x01, 0x05, 0x07, 0x19, 0xe0,
+ 0x29, 0xe7, 0x15, 0x00, 0x25,
+ 0x01, 0x75, 0x01, 0x95, 0x08,
+ 0x81, 0x02, 0x95, 0x01, 0x75,
+ 0x08, 0x81, 0x01, 0x95, 0x03,
+ 0x75, 0x01, 0x05, 0x08, 0x19,
+ 0x01, 0x29, 0x03, 0x91, 0x02,
+ 0x95, 0x05, 0x75, 0x01, 0x91,
+ 0x01, 0x95, 0x06, 0x75, 0x08,
+ 0x15, 0x00, 0x26, 0xff, 0x00,
+ 0x05, 0x07, 0x19, 0x00, 0x2a,
+ 0xff, 0x00, 0x81, 0x00, 0xc0
+};
+
+static const STRUCT_USB_HOST_ID ukbd_apple_iso_models[] = {
+ /* PowerBooks Feb 2005, iBooks G4 */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_FOUNTAIN_ISO) },
+ /* PowerBooks Oct 2005 */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_GEYSER_ISO) },
+ /* Core Duo MacBook & MacBook Pro */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_GEYSER3_ISO) },
+ /* Core2 Duo MacBook & MacBook Pro */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_GEYSER4_ISO) },
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_GEYSER4_HF_ISO) },
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_ALU_MINI_ISO) },
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_ALU_ISO) },
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_ALU_REVB_ISO) },
+ /* MacbookAir, aka wellspring */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_WELLSPRING_ISO) },
+ /* MacbookProPenryn, aka wellspring2 */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_WELLSPRING2_ISO) },
+ /* Macbook5,1 (unibody), aka wellspring3 */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_WELLSPRING3_ISO) },
+ /* MacbookAir3,2 (unibody), aka wellspring4 */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_WELLSPRING4_ISO) },
+ /* MacbookAir3,1 (unibody), aka wellspring4 */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_WELLSPRING4A_ISO) },
+ /* Macbook8 (unibody, March 2011) */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_WELLSPRING5_ISO) },
+ /* Macbook8,2 (unibody) */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_WELLSPRING5A_ISO) },
+ /* MacbookAir4,2 (unibody, July 2011) */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_WELLSPRING6_ISO) },
+ /* MacbookAir4,1 (unibody, July 2011) */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_WELLSPRING6A_ISO) },
+ /* MacbookPro10,1 (unibody, June 2012) */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_WELLSPRING7_ISO) },
+ /* MacbookPro10,2 (unibody, October 2012) */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_WELLSPRING7A_ISO) },
+ /* MacbookAir6,2 (unibody, June 2013) */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_WELLSPRING8_ISO) },
+ /* MacbookPro12,1 */
+ { USB_VP(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_WELLSPRING9_ISO) },
+};
+
+
+/* prototypes */
+static void ukbd_timeout(void *);
+static void ukbd_set_leds(struct ukbd_softc *, uint8_t);
+static int ukbd_set_typematic(keyboard_t *, int);
+#ifdef UKBD_EMULATE_ATSCANCODE
+static uint32_t ukbd_atkeycode(int, const uint64_t *);
+static int ukbd_key2scan(struct ukbd_softc *, int, const uint64_t *, int);
+#endif
+static uint32_t ukbd_read_char(keyboard_t *, int);
+static void ukbd_clear_state(keyboard_t *);
+static int ukbd_ioctl(keyboard_t *, u_long, caddr_t);
+static int ukbd_enable(keyboard_t *);
+static int ukbd_disable(keyboard_t *);
+static void ukbd_interrupt(struct ukbd_softc *);
+static void ukbd_event_keyinput(struct ukbd_softc *);
+
+static device_probe_t ukbd_probe;
+static device_attach_t ukbd_attach;
+static device_detach_t ukbd_detach;
+static device_resume_t ukbd_resume;
+
+#ifdef EVDEV_SUPPORT
+static evdev_event_t ukbd_ev_event;
+
+static const struct evdev_methods ukbd_evdev_methods = {
+ .ev_event = ukbd_ev_event,
+};
+#endif
+
+static bool
+ukbd_any_key_pressed(struct ukbd_softc *sc)
+{
+ bool ret = false;
+ unsigned i;
+
+ for (i = 0; i != howmany(UKBD_NKEYCODE, 64); i++)
+ ret |= (sc->sc_odata.bitmap[i] != 0);
+ return (ret);
+}
+
+static bool
+ukbd_any_key_valid(struct ukbd_softc *sc)
+{
+ bool ret = false;
+ unsigned i;
+
+ for (i = 0; i != howmany(UKBD_NKEYCODE, 64); i++)
+ ret |= (sc->sc_loc_key_valid[i] != 0);
+ return (ret);
+}
+
+static bool
+ukbd_is_modifier_key(uint32_t key)
+{
+
+ return (key >= 0xe0 && key <= 0xe7);
+}
+
+static void
+ukbd_start_timer(struct ukbd_softc *sc)
+{
+ sbintime_t delay, now, prec;
+
+ now = sbinuptime();
+
+ /* check if initial delay passed and fallback to key repeat delay */
+ if (sc->sc_delay == 0)
+ sc->sc_delay = sc->sc_kbd.kb_delay2;
+
+ /* compute timeout */
+ delay = SBT_1MS * sc->sc_delay;
+ sc->sc_co_basetime += delay;
+
+ /* check if we are running behind */
+ if (sc->sc_co_basetime < now)
+ sc->sc_co_basetime = now;
+
+ /* This is rarely called, so prefer precision to efficiency. */
+ prec = qmin(delay >> 7, SBT_1MS * 10);
+ usb_callout_reset_sbt(&sc->sc_callout, sc->sc_co_basetime, prec,
+ ukbd_timeout, sc, C_ABSOLUTE);
+}
+
+static void
+ukbd_put_key(struct ukbd_softc *sc, uint32_t key)
+{
+
+ UKBD_LOCK_ASSERT();
+
+ DPRINTF("0x%02x (%d) %s\n", key, key,
+ (key & KEY_RELEASE) ? "released" : "pressed");
+
+#ifdef EVDEV_SUPPORT
+ if (evdev_rcpt_mask & EVDEV_RCPT_HW_KBD && sc->sc_evdev != NULL)
+ evdev_push_event(sc->sc_evdev, EV_KEY,
+ evdev_hid2key(KEY_INDEX(key)), !(key & KEY_RELEASE));
+ if (sc->sc_evdev != NULL && evdev_is_grabbed(sc->sc_evdev))
+ return;
+#endif
+
+ if (sc->sc_inputs < UKBD_IN_BUF_SIZE) {
+ sc->sc_input[sc->sc_inputtail] = key;
+ ++(sc->sc_inputs);
+ ++(sc->sc_inputtail);
+ if (sc->sc_inputtail >= UKBD_IN_BUF_SIZE) {
+ sc->sc_inputtail = 0;
+ }
+ } else {
+ DPRINTF("input buffer is full\n");
+ }
+}
+
+static void
+ukbd_do_poll(struct ukbd_softc *sc, uint8_t wait)
+{
+
+ UKBD_LOCK_ASSERT();
+ KASSERT((sc->sc_flags & UKBD_FLAG_POLLING) != 0,
+ ("ukbd_do_poll called when not polling\n"));
+ DPRINTFN(2, "polling\n");
+
+ if (USB_IN_POLLING_MODE_FUNC() == 0) {
+ /*
+ * In this context the kernel is polling for input,
+ * but the USB subsystem works in normal interrupt-driven
+ * mode, so we just wait on the USB threads to do the job.
+ * Note that we currently hold the Giant, but it's also used
+ * as the transfer mtx, so we must release it while waiting.
+ */
+ while (sc->sc_inputs == 0) {
+ /*
+ * Give USB threads a chance to run. Note that
+ * kern_yield performs DROP_GIANT + PICKUP_GIANT.
+ */
+ kern_yield(PRI_UNCHANGED);
+ if (!wait)
+ break;
+ }
+ return;
+ }
+
+ while (sc->sc_inputs == 0) {
+ usbd_transfer_poll(sc->sc_xfer, UKBD_N_TRANSFER);
+
+ /* Delay-optimised support for repetition of keys */
+ if (ukbd_any_key_pressed(sc)) {
+ /* a key is pressed - need timekeeping */
+ DELAY(1000);
+
+ /* 1 millisecond has passed */
+ sc->sc_time_ms += 1;
+ }
+
+ ukbd_interrupt(sc);
+
+ if (!wait)
+ break;
+ }
+}
+
+static int32_t
+ukbd_get_key(struct ukbd_softc *sc, uint8_t wait)
+{
+ int32_t c;
+
+ UKBD_LOCK_ASSERT();
+ KASSERT((USB_IN_POLLING_MODE_FUNC() == 0) ||
+ (sc->sc_flags & UKBD_FLAG_POLLING) != 0,
+ ("not polling in kdb or panic\n"));
+
+ if (sc->sc_inputs == 0 &&
+ (sc->sc_flags & UKBD_FLAG_GONE) == 0) {
+ /* start transfer, if not already started */
+ usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT_0]);
+ usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT_1]);
+ }
+
+ if (sc->sc_flags & UKBD_FLAG_POLLING)
+ ukbd_do_poll(sc, wait);
+
+ if (sc->sc_inputs == 0) {
+ c = -1;
+ } else {
+ c = sc->sc_input[sc->sc_inputhead];
+ --(sc->sc_inputs);
+ ++(sc->sc_inputhead);
+ if (sc->sc_inputhead >= UKBD_IN_BUF_SIZE) {
+ sc->sc_inputhead = 0;
+ }
+ }
+ return (c);
+}
+
+static void
+ukbd_interrupt(struct ukbd_softc *sc)
+{
+ const uint32_t now = sc->sc_time_ms;
+ unsigned key;
+
+ UKBD_LOCK_ASSERT();
+
+ /* Check for modifier key changes first */
+ for (key = 0xe0; key != 0xe8; key++) {
+ const uint64_t mask = 1ULL << (key % 64);
+ const uint64_t delta =
+ sc->sc_odata.bitmap[key / 64] ^
+ sc->sc_ndata.bitmap[key / 64];
+
+ if (delta & mask) {
+ if (sc->sc_odata.bitmap[key / 64] & mask)
+ ukbd_put_key(sc, key | KEY_RELEASE);
+ else
+ ukbd_put_key(sc, key | KEY_PRESS);
+ }
+ }
+
+ /* Check for key changes */
+ for (key = 0; key != UKBD_NKEYCODE; key++) {
+ const uint64_t mask = 1ULL << (key % 64);
+ const uint64_t delta =
+ sc->sc_odata.bitmap[key / 64] ^
+ sc->sc_ndata.bitmap[key / 64];
+
+ if (mask == 1 && delta == 0) {
+ key += 63;
+ continue; /* skip empty areas */
+ } else if (ukbd_is_modifier_key(key)) {
+ continue;
+ } else if (delta & mask) {
+ if (sc->sc_odata.bitmap[key / 64] & mask) {
+ ukbd_put_key(sc, key | KEY_RELEASE);
+
+ /* clear repeating key, if any */
+ if (sc->sc_repeat_key == key)
+ sc->sc_repeat_key = 0;
+ } else {
+ ukbd_put_key(sc, key | KEY_PRESS);
+
+ sc->sc_co_basetime = sbinuptime();
+ sc->sc_delay = sc->sc_kbd.kb_delay1;
+ ukbd_start_timer(sc);
+
+ /* set repeat time for last key */
+ sc->sc_repeat_time = now + sc->sc_kbd.kb_delay1;
+ sc->sc_repeat_key = key;
+ }
+ }
+ }
+
+ /* synchronize old data with new data */
+ sc->sc_odata = sc->sc_ndata;
+
+ /* check if last key is still pressed */
+ if (sc->sc_repeat_key != 0) {
+ const int32_t dtime = (sc->sc_repeat_time - now);
+
+ /* check if time has elapsed */
+ if (dtime <= 0) {
+ ukbd_put_key(sc, sc->sc_repeat_key | KEY_PRESS);
+ sc->sc_repeat_time = now + sc->sc_kbd.kb_delay2;
+ }
+ }
+
+#ifdef EVDEV_SUPPORT
+ if (evdev_rcpt_mask & EVDEV_RCPT_HW_KBD && sc->sc_evdev != NULL)
+ evdev_sync(sc->sc_evdev);
+ if (sc->sc_evdev != NULL && evdev_is_grabbed(sc->sc_evdev))
+ return;
+#endif
+
+ /* wakeup keyboard system */
+ ukbd_event_keyinput(sc);
+}
+
+static void
+ukbd_event_keyinput(struct ukbd_softc *sc)
+{
+ int c;
+
+ UKBD_LOCK_ASSERT();
+
+ if ((sc->sc_flags & UKBD_FLAG_POLLING) != 0)
+ return;
+
+ if (sc->sc_inputs == 0)
+ return;
+
+ if (KBD_IS_ACTIVE(&sc->sc_kbd) &&
+ KBD_IS_BUSY(&sc->sc_kbd)) {
+ /* let the callback function process the input */
+ (sc->sc_kbd.kb_callback.kc_func) (&sc->sc_kbd, KBDIO_KEYINPUT,
+ sc->sc_kbd.kb_callback.kc_arg);
+ } else {
+ /* read and discard the input, no one is waiting for it */
+ do {
+ c = ukbd_read_char(&sc->sc_kbd, 0);
+ } while (c != NOKEY);
+ }
+}
+
+static void
+ukbd_timeout(void *arg)
+{
+ struct ukbd_softc *sc = arg;
+
+ UKBD_LOCK_ASSERT();
+
+ sc->sc_time_ms += sc->sc_delay;
+ sc->sc_delay = 0;
+
+ ukbd_interrupt(sc);
+
+ /* Make sure any leftover key events gets read out */
+ ukbd_event_keyinput(sc);
+
+ if (ukbd_any_key_pressed(sc) || (sc->sc_inputs != 0)) {
+ ukbd_start_timer(sc);
+ }
+}
+
+static uint32_t
+ukbd_apple_fn(uint32_t keycode)
+{
+ switch (keycode) {
+ case 0x28: return 0x49; /* RETURN -> INSERT */
+ case 0x2a: return 0x4c; /* BACKSPACE -> DEL */
+ case 0x50: return 0x4a; /* LEFT ARROW -> HOME */
+ case 0x4f: return 0x4d; /* RIGHT ARROW -> END */
+ case 0x52: return 0x4b; /* UP ARROW -> PGUP */
+ case 0x51: return 0x4e; /* DOWN ARROW -> PGDN */
+ default: return keycode;
+ }
+}
+
+static uint32_t
+ukbd_apple_swap(uint32_t keycode)
+{
+ switch (keycode) {
+ case 0x35: return 0x64;
+ case 0x64: return 0x35;
+ default: return keycode;
+ }
+}
+
+static void
+ukbd_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ukbd_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t i;
+ uint8_t id;
+ uint8_t modifiers;
+ int offset;
+ int len;
+
+ UKBD_LOCK_ASSERT();
+
+ usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+ pc = usbd_xfer_get_frame(xfer, 0);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTF("actlen=%d bytes\n", len);
+
+ if (len == 0) {
+ DPRINTF("zero length data\n");
+ goto tr_setup;
+ }
+
+ if (sc->sc_kbd_id != 0) {
+ /* check and remove HID ID byte */
+ usbd_copy_out(pc, 0, &id, 1);
+ offset = 1;
+ len--;
+ if (len == 0) {
+ DPRINTF("zero length data\n");
+ goto tr_setup;
+ }
+ } else {
+ offset = 0;
+ id = 0;
+ }
+
+ if (len > UKBD_BUFFER_SIZE)
+ len = UKBD_BUFFER_SIZE;
+
+ /* get data */
+ usbd_copy_out(pc, offset, sc->sc_buffer, len);
+
+ /* clear temporary storage */
+ memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata));
+
+ /* clear modifiers */
+ modifiers = 0;
+
+ /* scan through HID data */
+ if ((sc->sc_flags & UKBD_FLAG_APPLE_EJECT) &&
+ (id == sc->sc_id_apple_eject)) {
+ if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_apple_eject))
+ modifiers |= MOD_EJECT;
+ }
+ if ((sc->sc_flags & UKBD_FLAG_APPLE_FN) &&
+ (id == sc->sc_id_apple_fn)) {
+ if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_apple_fn))
+ modifiers |= MOD_FN;
+ }
+
+ for (i = 0; i != UKBD_NKEYCODE; i++) {
+ const uint64_t valid = sc->sc_loc_key_valid[i / 64];
+ const uint64_t mask = 1ULL << (i % 64);
+
+ if (mask == 1 && valid == 0) {
+ i += 63;
+ continue; /* skip empty areas */
+ } else if (~valid & mask) {
+ continue; /* location is not valid */
+ } else if (id != sc->sc_id_loc_key[i]) {
+ continue; /* invalid HID ID */
+ } else if (i == 0) {
+ struct hid_location tmp_loc = sc->sc_loc_key[0];
+ /* range check array size */
+ if (tmp_loc.count > UKBD_NKEYCODE)
+ tmp_loc.count = UKBD_NKEYCODE;
+ while (tmp_loc.count--) {
+ uint32_t key =
+ hid_get_udata(sc->sc_buffer, len, &tmp_loc);
+ /* advance to next location */
+ tmp_loc.pos += tmp_loc.size;
+ if (key == KEY_ERROR) {
+ DPRINTF("KEY_ERROR\n");
+ sc->sc_ndata = sc->sc_odata;
+ goto tr_setup; /* ignore */
+ }
+ if (modifiers & MOD_FN)
+ key = ukbd_apple_fn(key);
+ if (sc->sc_flags & UKBD_FLAG_APPLE_SWAP)
+ key = ukbd_apple_swap(key);
+ if (key == KEY_NONE || key >= UKBD_NKEYCODE)
+ continue;
+ /* set key in bitmap */
+ sc->sc_ndata.bitmap[key / 64] |= 1ULL << (key % 64);
+ }
+ } else if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_key[i])) {
+ uint32_t key = i;
+
+ if (modifiers & MOD_FN)
+ key = ukbd_apple_fn(key);
+ if (sc->sc_flags & UKBD_FLAG_APPLE_SWAP)
+ key = ukbd_apple_swap(key);
+ if (key == KEY_NONE || key == KEY_ERROR || key >= UKBD_NKEYCODE)
+ continue;
+ /* set key in bitmap */
+ sc->sc_ndata.bitmap[key / 64] |= 1ULL << (key % 64);
+ }
+ }
+#ifdef USB_DEBUG
+ DPRINTF("modifiers = 0x%04x\n", modifiers);
+ for (i = 0; i != UKBD_NKEYCODE; i++) {
+ const uint64_t valid = sc->sc_ndata.bitmap[i / 64];
+ const uint64_t mask = 1ULL << (i % 64);
+
+ if (valid & mask)
+ DPRINTF("Key 0x%02x pressed\n", i);
+ }
+#endif
+ ukbd_interrupt(sc);
+
+ case USB_ST_SETUP:
+tr_setup:
+ if (sc->sc_inputs < UKBD_IN_BUF_FULL) {
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ } else {
+ DPRINTF("input queue is full!\n");
+ }
+ break;
+
+ default: /* Error */
+ DPRINTF("error=%s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+ukbd_set_leds_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ukbd_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_device_request req;
+ struct usb_page_cache *pc;
+ uint8_t id;
+ uint8_t any;
+ int len;
+
+ UKBD_LOCK_ASSERT();
+
+#ifdef USB_DEBUG
+ if (ukbd_no_leds)
+ return;
+#endif
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+ if (!(sc->sc_flags & UKBD_FLAG_SET_LEDS))
+ break;
+ sc->sc_flags &= ~UKBD_FLAG_SET_LEDS;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UR_SET_REPORT;
+ USETW2(req.wValue, UHID_OUTPUT_REPORT, 0);
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ req.wLength[1] = 0;
+
+ memset(sc->sc_buffer, 0, UKBD_BUFFER_SIZE);
+
+ id = 0;
+ any = 0;
+
+ /* Assumption: All led bits must be in the same ID. */
+
+ if (sc->sc_flags & UKBD_FLAG_NUMLOCK) {
+ if (sc->sc_leds & NLKED) {
+ hid_put_udata(sc->sc_buffer + 1, UKBD_BUFFER_SIZE - 1,
+ &sc->sc_loc_numlock, 1);
+ }
+ id = sc->sc_id_numlock;
+ any = 1;
+ }
+
+ if (sc->sc_flags & UKBD_FLAG_SCROLLLOCK) {
+ if (sc->sc_leds & SLKED) {
+ hid_put_udata(sc->sc_buffer + 1, UKBD_BUFFER_SIZE - 1,
+ &sc->sc_loc_scrolllock, 1);
+ }
+ id = sc->sc_id_scrolllock;
+ any = 1;
+ }
+
+ if (sc->sc_flags & UKBD_FLAG_CAPSLOCK) {
+ if (sc->sc_leds & CLKED) {
+ hid_put_udata(sc->sc_buffer + 1, UKBD_BUFFER_SIZE - 1,
+ &sc->sc_loc_capslock, 1);
+ }
+ id = sc->sc_id_capslock;
+ any = 1;
+ }
+
+ /* if no leds, nothing to do */
+ if (!any)
+ break;
+
+ /* range check output report length */
+ len = sc->sc_led_size;
+ if (len > (UKBD_BUFFER_SIZE - 1))
+ len = (UKBD_BUFFER_SIZE - 1);
+
+ /* check if we need to prefix an ID byte */
+ sc->sc_buffer[0] = id;
+
+ pc = usbd_xfer_get_frame(xfer, 1);
+ if (id != 0) {
+ len++;
+ usbd_copy_in(pc, 0, sc->sc_buffer, len);
+ } else {
+ usbd_copy_in(pc, 0, sc->sc_buffer + 1, len);
+ }
+ req.wLength[0] = len;
+ usbd_xfer_set_frame_len(xfer, 1, len);
+
+ DPRINTF("len=%d, id=%d\n", len, id);
+
+ /* setup control request last */
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+
+ /* start data transfer */
+ usbd_xfer_set_frames(xfer, 2);
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ DPRINTFN(1, "error=%s\n", usbd_errstr(error));
+ break;
+ }
+}
+
+static const struct usb_config ukbd_config[UKBD_N_TRANSFER] = {
+ [UKBD_INTR_DT_0] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &ukbd_intr_callback,
+ },
+
+ [UKBD_INTR_DT_1] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &ukbd_intr_callback,
+ },
+
+ [UKBD_CTRL_LED] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request) + UKBD_BUFFER_SIZE,
+ .callback = &ukbd_set_leds_callback,
+ .timeout = 1000, /* 1 second */
+ },
+};
+
+/* A match on these entries will load ukbd */
+static const STRUCT_USB_HOST_ID __used ukbd_devs[] = {
+ {USB_IFACE_CLASS(UICLASS_HID),
+ USB_IFACE_SUBCLASS(UISUBCLASS_BOOT),
+ USB_IFACE_PROTOCOL(UIPROTO_BOOT_KEYBOARD),},
+};
+
+static int
+ukbd_probe(device_t dev)
+{
+ keyboard_switch_t *sw = kbd_get_switch(UKBD_DRIVER_NAME);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ void *d_ptr;
+ int error;
+ uint16_t d_len;
+
+ UKBD_LOCK_ASSERT();
+ DPRINTFN(11, "\n");
+
+ if (sw == NULL) {
+ return (ENXIO);
+ }
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+
+ if (uaa->info.bInterfaceClass != UICLASS_HID)
+ return (ENXIO);
+
+ if (usb_test_quirk(uaa, UQ_KBD_IGNORE))
+ return (ENXIO);
+
+ if ((uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) &&
+ (uaa->info.bInterfaceProtocol == UIPROTO_BOOT_KEYBOARD))
+ return (BUS_PROBE_DEFAULT);
+
+ error = usbd_req_get_hid_desc(uaa->device, NULL,
+ &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex);
+
+ if (error)
+ return (ENXIO);
+
+ if (hid_is_keyboard(d_ptr, d_len)) {
+ if (hid_is_mouse(d_ptr, d_len)) {
+ /*
+ * NOTE: We currently don't support USB mouse
+ * and USB keyboard on the same USB endpoint.
+ * Let "ums" driver win.
+ */
+ error = ENXIO;
+ } else {
+ error = BUS_PROBE_DEFAULT;
+ }
+ } else {
+ error = ENXIO;
+ }
+ free(d_ptr, M_TEMP);
+ return (error);
+}
+
+static void
+ukbd_parse_hid(struct ukbd_softc *sc, const uint8_t *ptr, uint32_t len)
+{
+ uint32_t flags;
+ uint32_t key;
+
+ /* reset detected bits */
+ sc->sc_flags &= ~UKBD_FLAG_HID_MASK;
+
+ /* reset detected keys */
+ memset(sc->sc_loc_key_valid, 0, sizeof(sc->sc_loc_key_valid));
+
+ /* check if there is an ID byte */
+ sc->sc_kbd_size = hid_report_size_max(ptr, len,
+ hid_input, &sc->sc_kbd_id);
+
+ /* investigate if this is an Apple Keyboard */
+ if (hid_locate(ptr, len,
+ HID_USAGE2(HUP_CONSUMER, HUG_APPLE_EJECT),
+ hid_input, 0, &sc->sc_loc_apple_eject, &flags,
+ &sc->sc_id_apple_eject)) {
+ if (flags & HIO_VARIABLE)
+ sc->sc_flags |= UKBD_FLAG_APPLE_EJECT;
+ DPRINTFN(1, "Found Apple eject-key\n");
+ }
+ if (hid_locate(ptr, len,
+ HID_USAGE2(0xFFFF, 0x0003),
+ hid_input, 0, &sc->sc_loc_apple_fn, &flags,
+ &sc->sc_id_apple_fn)) {
+ if (flags & HIO_VARIABLE)
+ sc->sc_flags |= UKBD_FLAG_APPLE_FN;
+ DPRINTFN(1, "Found Apple FN-key\n");
+ }
+
+ /* figure out event buffer */
+ if (hid_locate(ptr, len,
+ HID_USAGE2(HUP_KEYBOARD, 0x00),
+ hid_input, 0, &sc->sc_loc_key[0], &flags,
+ &sc->sc_id_loc_key[0])) {
+ if (flags & HIO_VARIABLE) {
+ DPRINTFN(1, "Ignoring keyboard event control\n");
+ } else {
+ sc->sc_loc_key_valid[0] |= 1;
+ DPRINTFN(1, "Found keyboard event array\n");
+ }
+ }
+
+ /* figure out the keys */
+ for (key = 1; key != UKBD_NKEYCODE; key++) {
+ if (hid_locate(ptr, len,
+ HID_USAGE2(HUP_KEYBOARD, key),
+ hid_input, 0, &sc->sc_loc_key[key], &flags,
+ &sc->sc_id_loc_key[key])) {
+ if (flags & HIO_VARIABLE) {
+ sc->sc_loc_key_valid[key / 64] |=
+ 1ULL << (key % 64);
+ DPRINTFN(1, "Found key 0x%02x\n", key);
+ }
+ }
+ }
+
+ /* figure out leds on keyboard */
+ sc->sc_led_size = hid_report_size_max(ptr, len,
+ hid_output, NULL);
+
+ if (hid_locate(ptr, len,
+ HID_USAGE2(HUP_LEDS, 0x01),
+ hid_output, 0, &sc->sc_loc_numlock, &flags,
+ &sc->sc_id_numlock)) {
+ if (flags & HIO_VARIABLE)
+ sc->sc_flags |= UKBD_FLAG_NUMLOCK;
+ DPRINTFN(1, "Found keyboard numlock\n");
+ }
+ if (hid_locate(ptr, len,
+ HID_USAGE2(HUP_LEDS, 0x02),
+ hid_output, 0, &sc->sc_loc_capslock, &flags,
+ &sc->sc_id_capslock)) {
+ if (flags & HIO_VARIABLE)
+ sc->sc_flags |= UKBD_FLAG_CAPSLOCK;
+ DPRINTFN(1, "Found keyboard capslock\n");
+ }
+ if (hid_locate(ptr, len,
+ HID_USAGE2(HUP_LEDS, 0x03),
+ hid_output, 0, &sc->sc_loc_scrolllock, &flags,
+ &sc->sc_id_scrolllock)) {
+ if (flags & HIO_VARIABLE)
+ sc->sc_flags |= UKBD_FLAG_SCROLLLOCK;
+ DPRINTFN(1, "Found keyboard scrolllock\n");
+ }
+}
+
+static int
+ukbd_attach(device_t dev)
+{
+ struct ukbd_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ int unit = device_get_unit(dev);
+ keyboard_t *kbd = &sc->sc_kbd;
+ void *hid_ptr = NULL;
+ usb_error_t err;
+ uint16_t n;
+ uint16_t hid_len;
+#ifdef EVDEV_SUPPORT
+ struct evdev_dev *evdev;
+ int i;
+#endif
+#ifdef USB_DEBUG
+ int rate;
+#endif
+ UKBD_LOCK_ASSERT();
+
+ kbd_init_struct(kbd, UKBD_DRIVER_NAME, KB_OTHER, unit, 0, 0, 0);
+
+ kbd->kb_data = (void *)sc;
+
+ device_set_usb_desc(dev);
+
+ sc->sc_udev = uaa->device;
+ sc->sc_iface = uaa->iface;
+ sc->sc_iface_index = uaa->info.bIfaceIndex;
+ sc->sc_iface_no = uaa->info.bIfaceNum;
+ sc->sc_mode = K_XLATE;
+
+ usb_callout_init_mtx(&sc->sc_callout, &Giant, 0);
+
+#ifdef UKBD_NO_POLLING
+ err = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer, ukbd_config,
+ UKBD_N_TRANSFER, sc, &Giant);
+#else
+ /*
+ * Setup the UKBD USB transfers one by one, so they are memory
+ * independent which allows for handling panics triggered by
+ * the keyboard driver itself, typically via CTRL+ALT+ESC
+ * sequences. Or if the USB keyboard driver was processing a
+ * key at the moment of panic.
+ */
+ for (n = 0; n != UKBD_N_TRANSFER; n++) {
+ err = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer + n, ukbd_config + n,
+ 1, sc, &Giant);
+ if (err)
+ break;
+ }
+#endif
+
+ if (err) {
+ DPRINTF("error=%s\n", usbd_errstr(err));
+ goto detach;
+ }
+ /* setup default keyboard maps */
+
+ sc->sc_keymap = key_map;
+ sc->sc_accmap = accent_map;
+ for (n = 0; n < UKBD_NFKEY; n++) {
+ sc->sc_fkeymap[n] = fkey_tab[n];
+ }
+
+ /* check if this is an Apple keyboard with swapped key codes
+ * apparently, these are the ISO layout models
+ */
+ DPRINTF("uaa vendor: 0x%04x, uaa product 0x%04x\n", uaa->info.idVendor, uaa->info.idProduct );
+ if (usbd_lookup_id_by_uaa(ukbd_apple_iso_models, sizeof(ukbd_apple_iso_models), uaa) == 0) {
+ sc->sc_flags |= UKBD_FLAG_APPLE_SWAP;
+ DPRINTF("UKBD_FLAG_APPLE_SWAP set\n");
+ } else {
+ DPRINTF("UKBD_FLAG_APPLE_SWAP not set\n");
+ }
+
+ kbd_set_maps(kbd, &sc->sc_keymap, &sc->sc_accmap,
+ sc->sc_fkeymap, UKBD_NFKEY);
+
+ KBD_FOUND_DEVICE(kbd);
+
+ ukbd_clear_state(kbd);
+
+ /*
+ * FIXME: set the initial value for lock keys in "sc_state"
+ * according to the BIOS data?
+ */
+ KBD_PROBE_DONE(kbd);
+
+ /* get HID descriptor */
+ err = usbd_req_get_hid_desc(uaa->device, NULL, &hid_ptr,
+ &hid_len, M_TEMP, uaa->info.bIfaceIndex);
+
+ if (err == 0) {
+ DPRINTF("Parsing HID descriptor of %d bytes\n",
+ (int)hid_len);
+
+ ukbd_parse_hid(sc, hid_ptr, hid_len);
+
+ free(hid_ptr, M_TEMP);
+ }
+
+ /* check if we should use the boot protocol */
+ if (usb_test_quirk(uaa, UQ_KBD_BOOTPROTO) ||
+ (err != 0) || ukbd_any_key_valid(sc) == false) {
+ DPRINTF("Forcing boot protocol\n");
+
+ err = usbd_req_set_protocol(sc->sc_udev, NULL,
+ sc->sc_iface_index, 0);
+
+ if (err != 0) {
+ DPRINTF("Set protocol error=%s (ignored)\n",
+ usbd_errstr(err));
+ }
+
+ ukbd_parse_hid(sc, ukbd_boot_desc, sizeof(ukbd_boot_desc));
+ }
+
+ /* ignore if SETIDLE fails, hence it is not crucial */
+ usbd_req_set_idle(sc->sc_udev, NULL, sc->sc_iface_index, 0, 0);
+
+ ukbd_ioctl(kbd, KDSETLED, (caddr_t)&sc->sc_state);
+
+ KBD_INIT_DONE(kbd);
+
+ if (kbd_register(kbd) < 0) {
+ goto detach;
+ }
+ KBD_CONFIG_DONE(kbd);
+
+ ukbd_enable(kbd);
+
+#ifdef KBD_INSTALL_CDEV
+ if (kbd_attach(kbd)) {
+ goto detach;
+ }
+#endif
+
+#ifdef EVDEV_SUPPORT
+ evdev = evdev_alloc();
+ evdev_set_name(evdev, device_get_desc(dev));
+ evdev_set_phys(evdev, device_get_nameunit(dev));
+ evdev_set_id(evdev, BUS_USB, uaa->info.idVendor,
+ uaa->info.idProduct, 0);
+ evdev_set_serial(evdev, usb_get_serial(uaa->device));
+ evdev_set_methods(evdev, kbd, &ukbd_evdev_methods);
+ evdev_support_event(evdev, EV_SYN);
+ evdev_support_event(evdev, EV_KEY);
+ if (sc->sc_flags & (UKBD_FLAG_NUMLOCK | UKBD_FLAG_CAPSLOCK |
+ UKBD_FLAG_SCROLLLOCK))
+ evdev_support_event(evdev, EV_LED);
+ evdev_support_event(evdev, EV_REP);
+
+ for (i = 0x00; i <= 0xFF; i++)
+ evdev_support_key(evdev, evdev_hid2key(i));
+ if (sc->sc_flags & UKBD_FLAG_NUMLOCK)
+ evdev_support_led(evdev, LED_NUML);
+ if (sc->sc_flags & UKBD_FLAG_CAPSLOCK)
+ evdev_support_led(evdev, LED_CAPSL);
+ if (sc->sc_flags & UKBD_FLAG_SCROLLLOCK)
+ evdev_support_led(evdev, LED_SCROLLL);
+
+ if (evdev_register_mtx(evdev, &Giant))
+ evdev_free(evdev);
+ else
+ sc->sc_evdev = evdev;
+#endif
+
+ sc->sc_flags |= UKBD_FLAG_ATTACHED;
+
+ if (bootverbose) {
+ kbdd_diag(kbd, bootverbose);
+ }
+
+#ifdef USB_DEBUG
+ /* check for polling rate override */
+ rate = ukbd_pollrate;
+ if (rate > 0) {
+ if (rate > 1000)
+ rate = 1;
+ else
+ rate = 1000 / rate;
+
+ /* set new polling interval in ms */
+ usbd_xfer_set_interval(sc->sc_xfer[UKBD_INTR_DT_0], rate);
+ usbd_xfer_set_interval(sc->sc_xfer[UKBD_INTR_DT_1], rate);
+ }
+#endif
+ /* start the keyboard */
+ usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT_0]);
+ usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT_1]);
+
+ return (0); /* success */
+
+detach:
+ ukbd_detach(dev);
+ return (ENXIO); /* error */
+}
+
+static int
+ukbd_detach(device_t dev)
+{
+ struct ukbd_softc *sc = device_get_softc(dev);
+ int error;
+
+ UKBD_LOCK_ASSERT();
+
+ DPRINTF("\n");
+
+ sc->sc_flags |= UKBD_FLAG_GONE;
+
+ usb_callout_stop(&sc->sc_callout);
+
+ /* kill any stuck keys */
+ if (sc->sc_flags & UKBD_FLAG_ATTACHED) {
+ /* stop receiving events from the USB keyboard */
+ usbd_transfer_stop(sc->sc_xfer[UKBD_INTR_DT_0]);
+ usbd_transfer_stop(sc->sc_xfer[UKBD_INTR_DT_1]);
+
+ /* release all leftover keys, if any */
+ memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata));
+
+ /* process releasing of all keys */
+ ukbd_interrupt(sc);
+ }
+
+ ukbd_disable(&sc->sc_kbd);
+
+#ifdef KBD_INSTALL_CDEV
+ if (sc->sc_flags & UKBD_FLAG_ATTACHED) {
+ error = kbd_detach(&sc->sc_kbd);
+ if (error) {
+ /* usb attach cannot return an error */
+ device_printf(dev, "WARNING: kbd_detach() "
+ "returned non-zero! (ignored)\n");
+ }
+ }
+#endif
+
+#ifdef EVDEV_SUPPORT
+ evdev_free(sc->sc_evdev);
+#endif
+
+ if (KBD_IS_CONFIGURED(&sc->sc_kbd)) {
+ error = kbd_unregister(&sc->sc_kbd);
+ if (error) {
+ /* usb attach cannot return an error */
+ device_printf(dev, "WARNING: kbd_unregister() "
+ "returned non-zero! (ignored)\n");
+ }
+ }
+ sc->sc_kbd.kb_flags = 0;
+
+ usbd_transfer_unsetup(sc->sc_xfer, UKBD_N_TRANSFER);
+
+ usb_callout_drain(&sc->sc_callout);
+
+ DPRINTF("%s: disconnected\n",
+ device_get_nameunit(dev));
+
+ return (0);
+}
+
+static int
+ukbd_resume(device_t dev)
+{
+ struct ukbd_softc *sc = device_get_softc(dev);
+
+ UKBD_LOCK_ASSERT();
+
+ ukbd_clear_state(&sc->sc_kbd);
+
+ return (0);
+}
+
+#ifdef EVDEV_SUPPORT
+static void
+ukbd_ev_event(struct evdev_dev *evdev, uint16_t type, uint16_t code,
+ int32_t value)
+{
+ keyboard_t *kbd = evdev_get_softc(evdev);
+
+ if (evdev_rcpt_mask & EVDEV_RCPT_HW_KBD &&
+ (type == EV_LED || type == EV_REP)) {
+ mtx_lock(&Giant);
+ kbd_ev_event(kbd, type, code, value);
+ mtx_unlock(&Giant);
+ }
+}
+#endif
+
+/* early keyboard probe, not supported */
+static int
+ukbd_configure(int flags)
+{
+ return (0);
+}
+
+/* detect a keyboard, not used */
+static int
+ukbd__probe(int unit, void *arg, int flags)
+{
+ return (ENXIO);
+}
+
+/* reset and initialize the device, not used */
+static int
+ukbd_init(int unit, keyboard_t **kbdp, void *arg, int flags)
+{
+ return (ENXIO);
+}
+
+/* test the interface to the device, not used */
+static int
+ukbd_test_if(keyboard_t *kbd)
+{
+ return (0);
+}
+
+/* finish using this keyboard, not used */
+static int
+ukbd_term(keyboard_t *kbd)
+{
+ return (ENXIO);
+}
+
+/* keyboard interrupt routine, not used */
+static int
+ukbd_intr(keyboard_t *kbd, void *arg)
+{
+ return (0);
+}
+
+/* lock the access to the keyboard, not used */
+static int
+ukbd_lock(keyboard_t *kbd, int lock)
+{
+ return (1);
+}
+
+/*
+ * 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)
+{
+
+ UKBD_LOCK();
+ KBD_ACTIVATE(kbd);
+ UKBD_UNLOCK();
+
+ return (0);
+}
+
+/* disallow the access to the device */
+static int
+ukbd_disable(keyboard_t *kbd)
+{
+
+ UKBD_LOCK();
+ KBD_DEACTIVATE(kbd);
+ UKBD_UNLOCK();
+
+ return (0);
+}
+
+/* check if data is waiting */
+/* Currently unused. */
+static int
+ukbd_check(keyboard_t *kbd)
+{
+ struct ukbd_softc *sc = kbd->kb_data;
+
+ UKBD_LOCK_ASSERT();
+
+ if (!KBD_IS_ACTIVE(kbd))
+ return (0);
+
+ if (sc->sc_flags & UKBD_FLAG_POLLING)
+ ukbd_do_poll(sc, 0);
+
+#ifdef UKBD_EMULATE_ATSCANCODE
+ if (sc->sc_buffered_char[0]) {
+ return (1);
+ }
+#endif
+ if (sc->sc_inputs > 0) {
+ return (1);
+ }
+ return (0);
+}
+
+/* check if char is waiting */
+static int
+ukbd_check_char_locked(keyboard_t *kbd)
+{
+ struct ukbd_softc *sc = kbd->kb_data;
+
+ UKBD_LOCK_ASSERT();
+
+ if (!KBD_IS_ACTIVE(kbd))
+ return (0);
+
+ if ((sc->sc_composed_char > 0) &&
+ (!(sc->sc_flags & UKBD_FLAG_COMPOSE))) {
+ return (1);
+ }
+ return (ukbd_check(kbd));
+}
+
+static int
+ukbd_check_char(keyboard_t *kbd)
+{
+ int result;
+
+ UKBD_LOCK();
+ result = ukbd_check_char_locked(kbd);
+ UKBD_UNLOCK();
+
+ return (result);
+}
+
+/* read one byte from the keyboard if it's allowed */
+/* Currently unused. */
+static int
+ukbd_read(keyboard_t *kbd, int wait)
+{
+ struct ukbd_softc *sc = kbd->kb_data;
+ int32_t usbcode;
+#ifdef UKBD_EMULATE_ATSCANCODE
+ uint32_t keycode;
+ uint32_t scancode;
+
+#endif
+
+ UKBD_LOCK_ASSERT();
+
+ if (!KBD_IS_ACTIVE(kbd))
+ return (-1);
+
+#ifdef UKBD_EMULATE_ATSCANCODE
+ if (sc->sc_buffered_char[0]) {
+ scancode = sc->sc_buffered_char[0];
+ if (scancode & SCAN_PREFIX) {
+ sc->sc_buffered_char[0] &= ~SCAN_PREFIX;
+ return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1);
+ }
+ sc->sc_buffered_char[0] = sc->sc_buffered_char[1];
+ sc->sc_buffered_char[1] = 0;
+ return (scancode);
+ }
+#endif /* UKBD_EMULATE_ATSCANCODE */
+
+ /* XXX */
+ usbcode = ukbd_get_key(sc, (wait == FALSE) ? 0 : 1);
+ if (!KBD_IS_ACTIVE(kbd) || (usbcode == -1))
+ return (-1);
+
+ ++(kbd->kb_count);
+
+#ifdef UKBD_EMULATE_ATSCANCODE
+ keycode = ukbd_atkeycode(usbcode, sc->sc_ndata.bitmap);
+ if (keycode == NN) {
+ return -1;
+ }
+ return (ukbd_key2scan(sc, keycode, sc->sc_ndata.bitmap,
+ (usbcode & KEY_RELEASE)));
+#else /* !UKBD_EMULATE_ATSCANCODE */
+ return (usbcode);
+#endif /* UKBD_EMULATE_ATSCANCODE */
+}
+
+/* read char from the keyboard */
+static uint32_t
+ukbd_read_char_locked(keyboard_t *kbd, int wait)
+{
+ struct ukbd_softc *sc = kbd->kb_data;
+ uint32_t action;
+ uint32_t keycode;
+ int32_t usbcode;
+#ifdef UKBD_EMULATE_ATSCANCODE
+ uint32_t scancode;
+#endif
+
+ UKBD_LOCK_ASSERT();
+
+ if (!KBD_IS_ACTIVE(kbd))
+ return (NOKEY);
+
+next_code:
+
+ /* do we have a composed char to return ? */
+
+ if ((sc->sc_composed_char > 0) &&
+ (!(sc->sc_flags & UKBD_FLAG_COMPOSE))) {
+ action = sc->sc_composed_char;
+ sc->sc_composed_char = 0;
+
+ if (action > 0xFF) {
+ goto errkey;
+ }
+ goto done;
+ }
+#ifdef UKBD_EMULATE_ATSCANCODE
+
+ /* do we have a pending raw scan code? */
+
+ if (sc->sc_mode == K_RAW) {
+ scancode = sc->sc_buffered_char[0];
+ if (scancode) {
+ if (scancode & SCAN_PREFIX) {
+ sc->sc_buffered_char[0] = (scancode & ~SCAN_PREFIX);
+ return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1);
+ }
+ sc->sc_buffered_char[0] = sc->sc_buffered_char[1];
+ sc->sc_buffered_char[1] = 0;
+ return (scancode);
+ }
+ }
+#endif /* UKBD_EMULATE_ATSCANCODE */
+
+ /* see if there is something in the keyboard port */
+ /* XXX */
+ usbcode = ukbd_get_key(sc, (wait == FALSE) ? 0 : 1);
+ if (usbcode == -1) {
+ return (NOKEY);
+ }
+ ++kbd->kb_count;
+
+#ifdef UKBD_EMULATE_ATSCANCODE
+ /* USB key index -> key code -> AT scan code */
+ keycode = ukbd_atkeycode(usbcode, sc->sc_ndata.bitmap);
+ if (keycode == NN) {
+ return (NOKEY);
+ }
+ /* return an AT scan code for the K_RAW mode */
+ if (sc->sc_mode == K_RAW) {
+ return (ukbd_key2scan(sc, keycode, sc->sc_ndata.bitmap,
+ (usbcode & KEY_RELEASE)));
+ }
+#else /* !UKBD_EMULATE_ATSCANCODE */
+
+ /* return the byte as is for the K_RAW mode */
+ if (sc->sc_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 (sc->sc_flags & UKBD_FLAG_COMPOSE) {
+ sc->sc_flags &= ~UKBD_FLAG_COMPOSE;
+
+ if (sc->sc_composed_char > 0xFF) {
+ sc->sc_composed_char = 0;
+ }
+ }
+ } else {
+ if (!(sc->sc_flags & UKBD_FLAG_COMPOSE)) {
+ sc->sc_flags |= UKBD_FLAG_COMPOSE;
+ sc->sc_composed_char = 0;
+ }
+ }
+ break;
+ }
+
+ /* return the key code in the K_CODE mode */
+ if (usbcode & KEY_RELEASE) {
+ keycode |= SCAN_RELEASE;
+ }
+ if (sc->sc_mode == K_CODE) {
+ return (keycode);
+ }
+ /* compose a character code */
+ if (sc->sc_flags & UKBD_FLAG_COMPOSE) {
+ switch (keycode) {
+ /* key pressed, process it */
+ case 0x47:
+ case 0x48:
+ case 0x49: /* keypad 7,8,9 */
+ sc->sc_composed_char *= 10;
+ sc->sc_composed_char += keycode - 0x40;
+ goto check_composed;
+
+ case 0x4B:
+ case 0x4C:
+ case 0x4D: /* keypad 4,5,6 */
+ sc->sc_composed_char *= 10;
+ sc->sc_composed_char += keycode - 0x47;
+ goto check_composed;
+
+ case 0x4F:
+ case 0x50:
+ case 0x51: /* keypad 1,2,3 */
+ sc->sc_composed_char *= 10;
+ sc->sc_composed_char += keycode - 0x4E;
+ goto check_composed;
+
+ case 0x52: /* keypad 0 */
+ sc->sc_composed_char *= 10;
+ goto check_composed;
+
+ /* 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 (sc->sc_composed_char > 0) {
+ sc->sc_flags &= ~UKBD_FLAG_COMPOSE;
+ sc->sc_composed_char = 0;
+ goto errkey;
+ }
+ break;
+ }
+ }
+ /* keycode to key action */
+ action = genkbd_keyaction(kbd, SCAN_CHAR(keycode),
+ (keycode & SCAN_RELEASE),
+ &sc->sc_state, &sc->sc_accents);
+ if (action == NOKEY) {
+ goto next_code;
+ }
+done:
+ return (action);
+
+check_composed:
+ if (sc->sc_composed_char <= 0xFF) {
+ goto next_code;
+ }
+errkey:
+ return (ERRKEY);
+}
+
+/* Currently wait is always false. */
+static uint32_t
+ukbd_read_char(keyboard_t *kbd, int wait)
+{
+ uint32_t keycode;
+
+ UKBD_LOCK();
+ keycode = ukbd_read_char_locked(kbd, wait);
+ UKBD_UNLOCK();
+
+ return (keycode);
+}
+
+/* some useful control functions */
+static int
+ukbd_ioctl_locked(keyboard_t *kbd, u_long cmd, caddr_t arg)
+{
+ struct ukbd_softc *sc = kbd->kb_data;
+ int i;
+#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
+ defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
+ int ival;
+
+#endif
+
+ UKBD_LOCK_ASSERT();
+
+ switch (cmd) {
+ case KDGKBMODE: /* get keyboard mode */
+ *(int *)arg = sc->sc_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 (sc->sc_mode != K_XLATE) {
+ /* make lock key state and LED state match */
+ sc->sc_state &= ~LOCK_MASK;
+ sc->sc_state |= KBD_LED_VAL(kbd);
+ }
+ /* FALLTHROUGH */
+ case K_RAW:
+ case K_CODE:
+ if (sc->sc_mode != *(int *)arg) {
+ if ((sc->sc_flags & UKBD_FLAG_POLLING) == 0)
+ ukbd_clear_state(kbd);
+ sc->sc_mode = *(int *)arg;
+ }
+ break;
+ default:
+ 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 "sc_state" won't be changed */
+ if (*(int *)arg & ~LOCK_MASK)
+ return (EINVAL);
+
+ i = *(int *)arg;
+
+ /* replace CAPS LED with ALTGR LED for ALTGR keyboards */
+ if (sc->sc_mode == K_XLATE &&
+ kbd->kb_keymap->n_keys > ALTGR_OFFSET) {
+ if (i & ALKED)
+ i |= CLKED;
+ else
+ i &= ~CLKED;
+ }
+ if (KBD_HAS_DEVICE(kbd))
+ ukbd_set_leds(sc, i);
+
+ KBD_LED_VAL(kbd) = *(int *)arg;
+ break;
+ case KDGKBSTATE: /* get lock key state */
+ *(int *)arg = sc->sc_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) {
+ return (EINVAL);
+ }
+ sc->sc_state &= ~LOCK_MASK;
+ sc->sc_state |= *(int *)arg;
+
+ /* set LEDs and quit */
+ return (ukbd_ioctl(kbd, KDSETLED, arg));
+
+ case KDSETREPEAT: /* set keyboard repeat rate (new
+ * interface) */
+ if (!KBD_HAS_DEVICE(kbd)) {
+ return (0);
+ }
+ /*
+ * Convert negative, zero and tiny args to the same limits
+ * as atkbd. We could support delays of 1 msec, but
+ * anything much shorter than the shortest atkbd value
+ * of 250.34 is almost unusable as well as incompatible.
+ */
+ kbd->kb_delay1 = imax(((int *)arg)[0], 250);
+ kbd->kb_delay2 = imax(((int *)arg)[1], 34);
+#ifdef EVDEV_SUPPORT
+ if (sc->sc_evdev != NULL)
+ evdev_push_repeats(sc->sc_evdev, kbd);
+#endif
+ 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) */
+ return (ukbd_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 */
+#ifdef COMPAT_FREEBSD13
+ case OPIO_KEYMAP: /* set keyboard translation table
+ * (compat) */
+ case OPIO_DEADKEYMAP: /* set accent key translation table
+ * (compat) */
+#endif /* COMPAT_FREEBSD13 */
+ sc->sc_accents = 0;
+ /* FALLTHROUGH */
+ default:
+ return (genkbd_commonioctl(kbd, cmd, arg));
+ }
+
+ return (0);
+}
+
+static int
+ukbd_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg)
+{
+ int result;
+
+ /*
+ * XXX Check if someone is calling us from a critical section:
+ */
+ if (curthread->td_critnest != 0)
+ return (EDEADLK);
+
+ /*
+ * XXX KDGKBSTATE, KDSKBSTATE and KDSETLED can be called from any
+ * context where printf(9) can be called, which among other things
+ * includes interrupt filters and threads with any kinds of locks
+ * already held. For this reason it would be dangerous to acquire
+ * the Giant here unconditionally. On the other hand we have to
+ * have it to handle the ioctl.
+ * So we make our best effort to auto-detect whether we can grab
+ * the Giant or not. Blame syscons(4) for this.
+ */
+ switch (cmd) {
+ case KDGKBSTATE:
+ case KDSKBSTATE:
+ case KDSETLED:
+ if (!mtx_owned(&Giant) && !USB_IN_POLLING_MODE_FUNC())
+ return (EDEADLK); /* best I could come up with */
+ /* FALLTHROUGH */
+ default:
+ UKBD_LOCK();
+ result = ukbd_ioctl_locked(kbd, cmd, arg);
+ UKBD_UNLOCK();
+ return (result);
+ }
+}
+
+/* clear the internal state of the keyboard */
+static void
+ukbd_clear_state(keyboard_t *kbd)
+{
+ struct ukbd_softc *sc = kbd->kb_data;
+
+ UKBD_LOCK_ASSERT();
+
+ sc->sc_flags &= ~(UKBD_FLAG_COMPOSE | UKBD_FLAG_POLLING);
+ sc->sc_state &= LOCK_MASK; /* preserve locking key state */
+ sc->sc_accents = 0;
+ sc->sc_composed_char = 0;
+#ifdef UKBD_EMULATE_ATSCANCODE
+ sc->sc_buffered_char[0] = 0;
+ sc->sc_buffered_char[1] = 0;
+#endif
+ memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata));
+ memset(&sc->sc_odata, 0, sizeof(sc->sc_odata));
+ sc->sc_repeat_time = 0;
+ sc->sc_repeat_key = 0;
+}
+
+/* save the internal state, not used */
+static int
+ukbd_get_state(keyboard_t *kbd, void *buf, size_t len)
+{
+ return (len == 0) ? 1 : -1;
+}
+
+/* set the internal state, not used */
+static int
+ukbd_set_state(keyboard_t *kbd, void *buf, size_t len)
+{
+ return (EINVAL);
+}
+
+static int
+ukbd_poll(keyboard_t *kbd, int on)
+{
+ struct ukbd_softc *sc = kbd->kb_data;
+
+ UKBD_LOCK();
+ /*
+ * Keep a reference count on polling to allow recursive
+ * cngrab() during a panic for example.
+ */
+ if (on)
+ sc->sc_polling++;
+ else if (sc->sc_polling > 0)
+ sc->sc_polling--;
+
+ if (sc->sc_polling != 0) {
+ sc->sc_flags |= UKBD_FLAG_POLLING;
+ sc->sc_poll_thread = curthread;
+ } else {
+ sc->sc_flags &= ~UKBD_FLAG_POLLING;
+ sc->sc_delay = 0;
+ }
+ UKBD_UNLOCK();
+
+ return (0);
+}
+
+/* local functions */
+
+static void
+ukbd_set_leds(struct ukbd_softc *sc, uint8_t leds)
+{
+
+ UKBD_LOCK_ASSERT();
+ DPRINTF("leds=0x%02x\n", leds);
+
+#ifdef EVDEV_SUPPORT
+ if (sc->sc_evdev != NULL)
+ evdev_push_leds(sc->sc_evdev, leds);
+#endif
+
+ sc->sc_leds = leds;
+ sc->sc_flags |= UKBD_FLAG_SET_LEDS;
+
+ /* start transfer, if not already started */
+
+ usbd_transfer_start(sc->sc_xfer[UKBD_CTRL_LED]);
+}
+
+static int
+ukbd_set_typematic(keyboard_t *kbd, int code)
+{
+#ifdef EVDEV_SUPPORT
+ struct ukbd_softc *sc = kbd->kb_data;
+#endif
+ if (code & ~0x7f) {
+ return (EINVAL);
+ }
+ kbd->kb_delay1 = kbdelays[(code >> 5) & 3];
+ kbd->kb_delay2 = kbrates[code & 0x1f];
+#ifdef EVDEV_SUPPORT
+ if (sc->sc_evdev != NULL)
+ evdev_push_repeats(sc->sc_evdev, kbd);
+#endif
+ return (0);
+}
+
+#ifdef UKBD_EMULATE_ATSCANCODE
+static uint32_t
+ukbd_atkeycode(int usbcode, const uint64_t *bitmap)
+{
+ uint32_t keycode;
+
+ keycode = ukbd_trtab[KEY_INDEX(usbcode)];
+
+ /*
+ * Translate Alt-PrintScreen to SysRq.
+ *
+ * Some or all AT keyboards connected through USB have already
+ * mapped Alted PrintScreens to an unusual usbcode (0x8a).
+ * ukbd_trtab translates this to 0x7e, and key2scan() would
+ * translate that to 0x79 (Intl' 4). Assume that if we have
+ * an Alted 0x7e here then it actually is an Alted PrintScreen.
+ *
+ * The usual usbcode for all PrintScreens is 0x46. ukbd_trtab
+ * translates this to 0x5c, so the Alt check to classify 0x5c
+ * is routine.
+ */
+ if ((keycode == 0x5c || keycode == 0x7e) &&
+ (UKBD_KEY_PRESSED(bitmap, 0xe2 /* ALT-L */) ||
+ UKBD_KEY_PRESSED(bitmap, 0xe6 /* ALT-R */)))
+ return (0x54);
+ return (keycode);
+}
+
+static int
+ukbd_key2scan(struct ukbd_softc *sc, int code, const uint64_t *bitmap, int up)
+{
+ static const int scan[] = {
+ /* 89 */
+ 0x11c, /* Enter */
+ /* 90-99 */
+ 0x11d, /* Ctrl-R */
+ 0x135, /* Divide */
+ 0x137, /* PrintScreen */
+ 0x138, /* Alt-R */
+ 0x147, /* Home */
+ 0x148, /* Up */
+ 0x149, /* PageUp */
+ 0x14b, /* Left */
+ 0x14d, /* Right */
+ 0x14f, /* End */
+ /* 100-109 */
+ 0x150, /* Down */
+ 0x151, /* PageDown */
+ 0x152, /* Insert */
+ 0x153, /* Delete */
+ 0x146, /* Pause/Break */
+ 0x15b, /* Win_L(Super_L) */
+ 0x15c, /* Win_R(Super_R) */
+ 0x15d, /* Application(Menu) */
+
+ /* SUN TYPE 6 USB KEYBOARD */
+ 0x168, /* Sun Type 6 Help */
+ 0x15e, /* Sun Type 6 Stop */
+ /* 110 - 119 */
+ 0x15f, /* Sun Type 6 Again */
+ 0x160, /* Sun Type 6 Props */
+ 0x161, /* Sun Type 6 Undo */
+ 0x162, /* Sun Type 6 Front */
+ 0x163, /* Sun Type 6 Copy */
+ 0x164, /* Sun Type 6 Open */
+ 0x165, /* Sun Type 6 Paste */
+ 0x166, /* Sun Type 6 Find */
+ 0x167, /* Sun Type 6 Cut */
+ 0x125, /* Sun Type 6 Mute */
+ /* 120 - 130 */
+ 0x11f, /* Sun Type 6 VolumeDown */
+ 0x11e, /* Sun Type 6 VolumeUp */
+ 0x120, /* Sun Type 6 PowerDown */
+
+ /* Japanese 106/109 keyboard */
+ 0x73, /* Keyboard Intl' 1 (backslash / underscore) */
+ 0x70, /* Keyboard Intl' 2 (Katakana / Hiragana) */
+ 0x7d, /* Keyboard Intl' 3 (Yen sign) (Not using in jp106/109) */
+ 0x79, /* Keyboard Intl' 4 (Henkan) */
+ 0x7b, /* Keyboard Intl' 5 (Muhenkan) */
+ 0x5c, /* Keyboard Intl' 6 (Keypad ,) (For PC-9821 layout) */
+ 0x71, /* Apple Keyboard JIS (Kana) */
+ 0x72, /* Apple Keyboard JIS (Eisu) */
+ };
+
+ if ((code >= 89) && (code < (int)(89 + nitems(scan)))) {
+ code = scan[code - 89];
+ }
+ /* PrintScreen */
+ if (code == 0x137 && (!(
+ UKBD_KEY_PRESSED(bitmap, 0xe0 /* CTRL-L */) ||
+ UKBD_KEY_PRESSED(bitmap, 0xe4 /* CTRL-R */) ||
+ UKBD_KEY_PRESSED(bitmap, 0xe1 /* SHIFT-L */) ||
+ UKBD_KEY_PRESSED(bitmap, 0xe5 /* SHIFT-R */)))) {
+ code |= SCAN_PREFIX_SHIFT;
+ }
+ /* Pause/Break */
+ if ((code == 0x146) && (!(
+ UKBD_KEY_PRESSED(bitmap, 0xe0 /* CTRL-L */) ||
+ UKBD_KEY_PRESSED(bitmap, 0xe4 /* CTRL-R */)))) {
+ code = (0x45 | SCAN_PREFIX_E1 | SCAN_PREFIX_CTL);
+ }
+ code |= (up ? SCAN_RELEASE : SCAN_PRESS);
+
+ if (code & SCAN_PREFIX) {
+ if (code & SCAN_PREFIX_CTL) {
+ /* Ctrl */
+ sc->sc_buffered_char[0] = (0x1d | (code & SCAN_RELEASE));
+ sc->sc_buffered_char[1] = (code & ~SCAN_PREFIX);
+ } else if (code & SCAN_PREFIX_SHIFT) {
+ /* Shift */
+ sc->sc_buffered_char[0] = (0x2a | (code & SCAN_RELEASE));
+ sc->sc_buffered_char[1] = (code & ~SCAN_PREFIX_SHIFT);
+ } else {
+ sc->sc_buffered_char[0] = (code & ~SCAN_PREFIX);
+ sc->sc_buffered_char[1] = 0;
+ }
+ return ((code & SCAN_PREFIX_E0) ? 0xe0 : 0xe1);
+ }
+ return (code);
+
+}
+
+#endif /* UKBD_EMULATE_ATSCANCODE */
+
+static keyboard_switch_t ukbdsw = {
+ .probe = &ukbd__probe,
+ .init = &ukbd_init,
+ .term = &ukbd_term,
+ .intr = &ukbd_intr,
+ .test_if = &ukbd_test_if,
+ .enable = &ukbd_enable,
+ .disable = &ukbd_disable,
+ .read = &ukbd_read,
+ .check = &ukbd_check,
+ .read_char = &ukbd_read_char,
+ .check_char = &ukbd_check_char,
+ .ioctl = &ukbd_ioctl,
+ .lock = &ukbd_lock,
+ .clear_state = &ukbd_clear_state,
+ .get_state = &ukbd_get_state,
+ .set_state = &ukbd_set_state,
+ .poll = &ukbd_poll,
+};
+
+KEYBOARD_DRIVER(ukbd, ukbdsw, ukbd_configure);
+
+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 (0);
+}
+
+static device_method_t ukbd_methods[] = {
+ DEVMETHOD(device_probe, ukbd_probe),
+ DEVMETHOD(device_attach, ukbd_attach),
+ DEVMETHOD(device_detach, ukbd_detach),
+ DEVMETHOD(device_resume, ukbd_resume),
+
+ DEVMETHOD_END
+};
+
+static driver_t ukbd_driver = {
+ .name = "ukbd",
+ .methods = ukbd_methods,
+ .size = sizeof(struct ukbd_softc),
+};
+
+DRIVER_MODULE(ukbd, uhub, ukbd_driver, ukbd_driver_load, NULL);
+MODULE_DEPEND(ukbd, usb, 1, 1, 1);
+MODULE_DEPEND(ukbd, hid, 1, 1, 1);
+#ifdef EVDEV_SUPPORT
+MODULE_DEPEND(ukbd, evdev, 1, 1, 1);
+#endif
+MODULE_VERSION(ukbd, 1);
+USB_PNP_HOST_INFO(ukbd_devs);
diff --git a/sys/dev/usb/input/ums.c b/sys/dev/usb/input/ums.c
new file mode 100644
index 000000000000..523ec4d05db9
--- /dev/null
+++ b/sys/dev/usb/input/ums.c
@@ -0,0 +1,1232 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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
+ */
+
+#include "opt_evdev.h"
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/sbuf.h>
+
+#include <dev/hid/hid.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR ums_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/quirk/usb_quirk.h>
+
+#ifdef EVDEV_SUPPORT
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+#endif
+
+#include <sys/ioccom.h>
+#include <sys/filio.h>
+#include <sys/mouse.h>
+
+#ifdef USB_DEBUG
+static int ums_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ums, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB ums");
+SYSCTL_INT(_hw_usb_ums, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &ums_debug, 0, "Debug level");
+#endif
+
+#define MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE)
+#define MOUSE_FLAGS (HIO_RELATIVE)
+
+#define UMS_BUF_SIZE 8 /* bytes */
+#define UMS_IFQ_MAXLEN 50 /* units */
+#define UMS_BUTTON_MAX 31 /* exclusive, must be less than 32 */
+#define UMS_BUT(i) ((i) < 3 ? (((i) + 2) % 3) : (i))
+#define UMS_INFO_MAX 2 /* maximum number of HID sets */
+
+enum {
+ UMS_INTR_DT,
+ UMS_N_TRANSFER,
+};
+
+struct ums_info {
+ struct hid_location sc_loc_w;
+ struct hid_location sc_loc_x;
+ struct hid_location sc_loc_y;
+ struct hid_location sc_loc_z;
+ struct hid_location sc_loc_t;
+ struct hid_location sc_loc_btn[UMS_BUTTON_MAX];
+
+ uint32_t sc_flags;
+#define UMS_FLAG_X_AXIS 0x0001
+#define UMS_FLAG_Y_AXIS 0x0002
+#define UMS_FLAG_Z_AXIS 0x0004
+#define UMS_FLAG_T_AXIS 0x0008
+#define UMS_FLAG_SBU 0x0010 /* spurious button up events */
+#define UMS_FLAG_REVZ 0x0020 /* Z-axis is reversed */
+#define UMS_FLAG_W_AXIS 0x0040
+#define UMS_FLAG_VBTN 0x0080 /* Buttons in vendor usage page */
+
+ uint8_t sc_iid_w;
+ uint8_t sc_iid_x;
+ uint8_t sc_iid_y;
+ uint8_t sc_iid_z;
+ uint8_t sc_iid_t;
+ uint8_t sc_iid_btn[UMS_BUTTON_MAX];
+ uint8_t sc_buttons;
+};
+
+struct ums_softc {
+ struct usb_fifo_sc sc_fifo;
+ struct mtx sc_mtx;
+ struct usb_callout sc_callout;
+ struct ums_info sc_info[UMS_INFO_MAX];
+
+ mousehw_t sc_hw;
+ mousemode_t sc_mode;
+ mousestatus_t sc_status;
+
+ struct usb_xfer *sc_xfer[UMS_N_TRANSFER];
+
+ int sc_pollrate;
+ int sc_fflags;
+#ifdef EVDEV_SUPPORT
+ int sc_evflags;
+#define UMS_EVDEV_OPENED 1
+#endif
+
+ uint8_t sc_buttons;
+ uint8_t sc_iid;
+ uint8_t sc_temp[64];
+
+#ifdef EVDEV_SUPPORT
+ struct evdev_dev *sc_evdev;
+#endif
+};
+
+static void ums_put_queue_timeout(void *__sc);
+
+static usb_callback_t ums_intr_callback;
+
+static device_probe_t ums_probe;
+static device_attach_t ums_attach;
+static device_detach_t ums_detach;
+
+static usb_fifo_cmd_t ums_fifo_start_read;
+static usb_fifo_cmd_t ums_fifo_stop_read;
+static usb_fifo_open_t ums_fifo_open;
+static usb_fifo_close_t ums_fifo_close;
+static usb_fifo_ioctl_t ums_fifo_ioctl;
+
+#ifdef EVDEV_SUPPORT
+static evdev_open_t ums_ev_open;
+static evdev_close_t ums_ev_close;
+static void ums_evdev_push(struct ums_softc *, int32_t, int32_t,
+ int32_t, int32_t, int32_t);
+#endif
+
+static void ums_start_rx(struct ums_softc *);
+static void ums_stop_rx(struct ums_softc *);
+static void ums_put_queue(struct ums_softc *, int32_t, int32_t,
+ int32_t, int32_t, int32_t);
+static int ums_sysctl_handler_parseinfo(SYSCTL_HANDLER_ARGS);
+
+static struct usb_fifo_methods ums_fifo_methods = {
+ .f_open = &ums_fifo_open,
+ .f_close = &ums_fifo_close,
+ .f_ioctl = &ums_fifo_ioctl,
+ .f_start_read = &ums_fifo_start_read,
+ .f_stop_read = &ums_fifo_stop_read,
+ .basename[0] = "ums",
+};
+
+#ifdef EVDEV_SUPPORT
+static const struct evdev_methods ums_evdev_methods = {
+ .ev_open = &ums_ev_open,
+ .ev_close = &ums_ev_close,
+};
+#endif
+
+static void
+ums_put_queue_timeout(void *__sc)
+{
+ struct ums_softc *sc = __sc;
+
+ mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+ ums_put_queue(sc, 0, 0, 0, 0, 0);
+#ifdef EVDEV_SUPPORT
+ ums_evdev_push(sc, 0, 0, 0, 0, 0);
+#endif
+}
+
+static void
+ums_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ums_softc *sc = usbd_xfer_softc(xfer);
+ struct ums_info *info = &sc->sc_info[0];
+ struct usb_page_cache *pc;
+ uint8_t *buf = sc->sc_temp;
+ int32_t buttons = 0;
+ int32_t buttons_found = 0;
+#ifdef EVDEV_SUPPORT
+ int32_t buttons_reported = 0;
+#endif
+ int32_t dw = 0;
+ int32_t dx = 0;
+ int32_t dy = 0;
+ int32_t dz = 0;
+ int32_t dt = 0;
+ uint8_t i;
+ uint8_t id;
+ int len;
+
+ usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(6, "sc=%p actlen=%d\n", sc, len);
+
+ if (len > (int)sizeof(sc->sc_temp)) {
+ DPRINTFN(6, "truncating large packet to %zu bytes\n",
+ sizeof(sc->sc_temp));
+ len = sizeof(sc->sc_temp);
+ }
+ if (len == 0)
+ goto tr_setup;
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, buf, len);
+
+ DPRINTFN(6, "data = %02x %02x %02x %02x "
+ "%02x %02x %02x %02x\n",
+ (len > 0) ? buf[0] : 0, (len > 1) ? buf[1] : 0,
+ (len > 2) ? buf[2] : 0, (len > 3) ? buf[3] : 0,
+ (len > 4) ? buf[4] : 0, (len > 5) ? buf[5] : 0,
+ (len > 6) ? buf[6] : 0, (len > 7) ? buf[7] : 0);
+
+ if (sc->sc_iid) {
+ id = *buf;
+
+ len--;
+ buf++;
+
+ } else {
+ id = 0;
+ if (sc->sc_info[0].sc_flags & UMS_FLAG_SBU) {
+ if ((*buf == 0x14) || (*buf == 0x15)) {
+ goto tr_setup;
+ }
+ }
+ }
+
+ repeat:
+ if ((info->sc_flags & UMS_FLAG_W_AXIS) &&
+ (id == info->sc_iid_w))
+ dw += hid_get_data(buf, len, &info->sc_loc_w);
+
+ if ((info->sc_flags & UMS_FLAG_X_AXIS) &&
+ (id == info->sc_iid_x))
+ dx += hid_get_data(buf, len, &info->sc_loc_x);
+
+ if ((info->sc_flags & UMS_FLAG_Y_AXIS) &&
+ (id == info->sc_iid_y))
+ dy -= hid_get_data(buf, len, &info->sc_loc_y);
+
+ if ((info->sc_flags & UMS_FLAG_Z_AXIS) &&
+ (id == info->sc_iid_z)) {
+ int32_t temp;
+ temp = hid_get_data(buf, len, &info->sc_loc_z);
+ if (info->sc_flags & UMS_FLAG_REVZ)
+ temp = -temp;
+ dz -= temp;
+ }
+
+ if ((info->sc_flags & UMS_FLAG_T_AXIS) &&
+ (id == info->sc_iid_t)) {
+ dt += hid_get_data(buf, len, &info->sc_loc_t);
+ /* T-axis is translated into button presses */
+ buttons_found |= (1UL << 5) | (1UL << 6);
+ }
+
+ for (i = 0; i < info->sc_buttons; i++) {
+ uint32_t mask;
+ mask = 1UL << UMS_BUT(i);
+ /* check for correct button ID */
+ if (id != info->sc_iid_btn[i])
+ continue;
+ /* check for button pressed */
+ if (hid_get_data(buf, len, &info->sc_loc_btn[i]))
+ buttons |= mask;
+ /* register button mask */
+ buttons_found |= mask;
+ }
+
+ if (++info != &sc->sc_info[UMS_INFO_MAX])
+ goto repeat;
+
+ /* keep old button value(s) for non-detected buttons */
+ buttons |= sc->sc_status.button & ~buttons_found;
+
+#ifdef EVDEV_SUPPORT
+ buttons_reported = buttons;
+#endif
+
+ if (dx || dy || dz || dt || dw ||
+ (buttons != sc->sc_status.button)) {
+ DPRINTFN(6, "x:%d y:%d z:%d t:%d w:%d buttons:0x%08x\n",
+ dx, dy, dz, dt, dw, buttons);
+
+ /* translate T-axis into button presses until further */
+ if (dt > 0) {
+ ums_put_queue(sc, 0, 0, 0, 0, buttons);
+ buttons |= 1UL << 6;
+ } else if (dt < 0) {
+ ums_put_queue(sc, 0, 0, 0, 0, buttons);
+ buttons |= 1UL << 5;
+ }
+
+ sc->sc_status.button = buttons;
+ sc->sc_status.dx += dx;
+ sc->sc_status.dy += dy;
+ sc->sc_status.dz += dz;
+ /*
+ * sc->sc_status.dt += dt;
+ * no way to export this yet
+ */
+
+ /*
+ * 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->sc_info[0].sc_flags & UMS_FLAG_SBU) &&
+ (dx == 0) && (dy == 0) && (dz == 0) && (dt == 0) &&
+ (dw == 0) && (buttons == 0)) {
+ usb_callout_reset(&sc->sc_callout, hz / 20,
+ &ums_put_queue_timeout, sc);
+ } else {
+ usb_callout_stop(&sc->sc_callout);
+
+ ums_put_queue(sc, dx, dy, dz, dt, buttons);
+#ifdef EVDEV_SUPPORT
+ ums_evdev_push(sc, dx, dy, dz, dt,
+ buttons_reported);
+#endif
+ }
+ }
+ case USB_ST_SETUP:
+tr_setup:
+ /* check if we can put more data into the FIFO */
+ if (usb_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) == 0) {
+#ifdef EVDEV_SUPPORT
+ if ((sc->sc_evflags & UMS_EVDEV_OPENED) == 0)
+ break;
+#else
+ break;
+#endif
+ }
+
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static const struct usb_config ums_config[UMS_N_TRANSFER] = {
+ [UMS_INTR_DT] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &ums_intr_callback,
+ },
+};
+
+/* A match on these entries will load ums */
+static const STRUCT_USB_HOST_ID __used ums_devs[] = {
+ {USB_IFACE_CLASS(UICLASS_HID),
+ USB_IFACE_SUBCLASS(UISUBCLASS_BOOT),
+ USB_IFACE_PROTOCOL(UIPROTO_MOUSE),},
+};
+
+static int
+ums_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ void *d_ptr;
+ int error;
+ uint16_t d_len;
+
+ DPRINTFN(11, "\n");
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ if (uaa->info.bInterfaceClass != UICLASS_HID)
+ return (ENXIO);
+
+ if (usb_test_quirk(uaa, UQ_UMS_IGNORE))
+ return (ENXIO);
+
+ if ((uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) &&
+ (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE))
+ return (BUS_PROBE_DEFAULT);
+
+ error = usbd_req_get_hid_desc(uaa->device, NULL,
+ &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex);
+
+ if (error)
+ return (ENXIO);
+
+ if (hid_is_mouse(d_ptr, d_len))
+ error = BUS_PROBE_DEFAULT;
+ else
+ error = ENXIO;
+
+ free(d_ptr, M_TEMP);
+ return (error);
+}
+
+static void
+ums_hid_parse(struct ums_softc *sc, device_t dev, const uint8_t *buf,
+ uint16_t len, uint8_t index)
+{
+ struct ums_info *info = &sc->sc_info[index];
+ uint32_t flags;
+ uint8_t i;
+ uint8_t j;
+
+ if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
+ hid_input, index, &info->sc_loc_x, &flags, &info->sc_iid_x)) {
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+ info->sc_flags |= UMS_FLAG_X_AXIS;
+ }
+ }
+ if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
+ hid_input, index, &info->sc_loc_y, &flags, &info->sc_iid_y)) {
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+ info->sc_flags |= UMS_FLAG_Y_AXIS;
+ }
+ }
+ /* Try the wheel first as the Z activator since it's tradition. */
+ if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+ HUG_WHEEL), hid_input, index, &info->sc_loc_z, &flags,
+ &info->sc_iid_z) ||
+ hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+ HUG_TWHEEL), hid_input, index, &info->sc_loc_z, &flags,
+ &info->sc_iid_z)) {
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+ info->sc_flags |= UMS_FLAG_Z_AXIS;
+ }
+ /*
+ * We might have both a wheel and Z direction, if so put
+ * put the Z on the W coordinate.
+ */
+ if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+ HUG_Z), hid_input, index, &info->sc_loc_w, &flags,
+ &info->sc_iid_w)) {
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+ info->sc_flags |= UMS_FLAG_W_AXIS;
+ }
+ }
+ } else if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+ HUG_Z), hid_input, index, &info->sc_loc_z, &flags,
+ &info->sc_iid_z)) {
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+ info->sc_flags |= UMS_FLAG_Z_AXIS;
+ }
+ }
+ /*
+ * The Microsoft Wireless Intellimouse 2.0 reports it's wheel
+ * using 0x0048, which is 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(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+ HUG_TWHEEL), hid_input, index, &info->sc_loc_t,
+ &flags, &info->sc_iid_t)) {
+ info->sc_loc_t.pos += 8;
+
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+ info->sc_flags |= UMS_FLAG_T_AXIS;
+ }
+ } else if (hid_locate(buf, len, HID_USAGE2(HUP_CONSUMER,
+ HUC_AC_PAN), hid_input, index, &info->sc_loc_t,
+ &flags, &info->sc_iid_t)) {
+ if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS)
+ info->sc_flags |= UMS_FLAG_T_AXIS;
+ }
+ /* figure out the number of buttons */
+
+ for (i = 0; i < UMS_BUTTON_MAX; i++) {
+ if (!hid_locate(buf, len, HID_USAGE2(HUP_BUTTON, (i + 1)),
+ hid_input, index, &info->sc_loc_btn[i], NULL,
+ &info->sc_iid_btn[i])) {
+ break;
+ }
+ }
+
+ /* detect other buttons */
+ if (info->sc_flags & UMS_FLAG_VBTN) {
+ for (j = 0; (i < UMS_BUTTON_MAX) && (j < 2); i++, j++) {
+ if (!hid_locate(buf, len, HID_USAGE2(HUP_MICROSOFT,
+ (j + 1)), hid_input, index, &info->sc_loc_btn[i],
+ NULL, &info->sc_iid_btn[i])) {
+ break;
+ }
+ }
+ }
+
+ info->sc_buttons = i;
+
+ if (i > sc->sc_buttons)
+ sc->sc_buttons = i;
+
+ if (info->sc_flags == 0)
+ return;
+
+ /* announce information about the mouse */
+ device_printf(dev, "%d buttons and [%s%s%s%s%s] coordinates ID=%u\n",
+ (info->sc_buttons),
+ (info->sc_flags & UMS_FLAG_X_AXIS) ? "X" : "",
+ (info->sc_flags & UMS_FLAG_Y_AXIS) ? "Y" : "",
+ (info->sc_flags & UMS_FLAG_Z_AXIS) ? "Z" : "",
+ (info->sc_flags & UMS_FLAG_T_AXIS) ? "T" : "",
+ (info->sc_flags & UMS_FLAG_W_AXIS) ? "W" : "",
+ info->sc_iid_x);
+}
+
+static int
+ums_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct ums_softc *sc = device_get_softc(dev);
+ struct ums_info *info;
+ void *d_ptr = NULL;
+ int isize;
+ int err;
+ uint16_t d_len;
+ uint8_t i;
+#ifdef USB_DEBUG
+ uint8_t j;
+#endif
+
+ DPRINTFN(11, "sc=%p\n", sc);
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, "ums lock", NULL, MTX_DEF | MTX_RECURSE);
+
+ usb_callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0);
+
+ /*
+ * Force the report (non-boot) protocol.
+ *
+ * Mice without boot protocol support may choose not to implement
+ * Set_Protocol at all; Ignore any error.
+ */
+ err = usbd_req_set_protocol(uaa->device, NULL,
+ uaa->info.bIfaceIndex, 1);
+
+ err = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer, ums_config,
+ UMS_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (err) {
+ DPRINTF("error=%s\n", usbd_errstr(err));
+ goto detach;
+ }
+
+ /* Get HID descriptor */
+ err = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr,
+ &d_len, M_TEMP, uaa->info.bIfaceIndex);
+
+ if (err) {
+ device_printf(dev, "error reading report description\n");
+ goto detach;
+ }
+
+ isize = hid_report_size_max(d_ptr, d_len, hid_input, &sc->sc_iid);
+
+ if (usb_test_quirk(uaa, UQ_MS_VENDOR_BTN))
+ for (i = 0; i < UMS_INFO_MAX; i++)
+ sc->sc_info[i].sc_flags |= UMS_FLAG_VBTN;
+
+ /*
+ * 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 additional buttons and a tilt wheel.
+ */
+ if (usb_test_quirk(uaa, UQ_MS_BAD_CLASS)) {
+ sc->sc_iid = 0;
+
+ info = &sc->sc_info[0];
+ info->sc_flags = (UMS_FLAG_X_AXIS |
+ UMS_FLAG_Y_AXIS |
+ UMS_FLAG_Z_AXIS |
+ UMS_FLAG_SBU);
+ info->sc_buttons = 3;
+ isize = 5;
+ /* 1st byte of descriptor report contains garbage */
+ info->sc_loc_x.pos = 16;
+ info->sc_loc_x.size = 8;
+ info->sc_loc_y.pos = 24;
+ info->sc_loc_y.size = 8;
+ info->sc_loc_z.pos = 32;
+ info->sc_loc_z.size = 8;
+ info->sc_loc_btn[0].pos = 8;
+ info->sc_loc_btn[0].size = 1;
+ info->sc_loc_btn[1].pos = 9;
+ info->sc_loc_btn[1].size = 1;
+ info->sc_loc_btn[2].pos = 10;
+ info->sc_loc_btn[2].size = 1;
+
+ /* Announce device */
+ device_printf(dev, "3 buttons and [XYZ] "
+ "coordinates ID=0\n");
+
+ } else {
+ /* Search the HID descriptor and announce device */
+ for (i = 0; i < UMS_INFO_MAX; i++) {
+ ums_hid_parse(sc, dev, d_ptr, d_len, i);
+ }
+ }
+
+ if (usb_test_quirk(uaa, UQ_MS_REVZ)) {
+ info = &sc->sc_info[0];
+ /* Some wheels need the Z axis reversed. */
+ info->sc_flags |= UMS_FLAG_REVZ;
+ }
+ if (isize > (int)usbd_xfer_max_framelen(sc->sc_xfer[UMS_INTR_DT])) {
+ DPRINTF("WARNING: report size, %d bytes, is larger "
+ "than interrupt size, %d bytes!\n", isize,
+ usbd_xfer_max_framelen(sc->sc_xfer[UMS_INTR_DT]));
+ }
+ free(d_ptr, M_TEMP);
+ d_ptr = NULL;
+
+#ifdef USB_DEBUG
+ for (j = 0; j < UMS_INFO_MAX; j++) {
+ info = &sc->sc_info[j];
+
+ DPRINTF("sc=%p, index=%d\n", sc, j);
+ DPRINTF("X\t%d/%d id=%d\n", info->sc_loc_x.pos,
+ info->sc_loc_x.size, info->sc_iid_x);
+ DPRINTF("Y\t%d/%d id=%d\n", info->sc_loc_y.pos,
+ info->sc_loc_y.size, info->sc_iid_y);
+ DPRINTF("Z\t%d/%d id=%d\n", info->sc_loc_z.pos,
+ info->sc_loc_z.size, info->sc_iid_z);
+ DPRINTF("T\t%d/%d id=%d\n", info->sc_loc_t.pos,
+ info->sc_loc_t.size, info->sc_iid_t);
+ DPRINTF("W\t%d/%d id=%d\n", info->sc_loc_w.pos,
+ info->sc_loc_w.size, info->sc_iid_w);
+
+ for (i = 0; i < info->sc_buttons; i++) {
+ DPRINTF("B%d\t%d/%d id=%d\n",
+ i + 1, info->sc_loc_btn[i].pos,
+ info->sc_loc_btn[i].size, info->sc_iid_btn[i]);
+ }
+ }
+ DPRINTF("size=%d, id=%d\n", isize, sc->sc_iid);
+#endif
+
+ err = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx,
+ &ums_fifo_methods, &sc->sc_fifo,
+ device_get_unit(dev), -1, uaa->info.bIfaceIndex,
+ UID_ROOT, GID_OPERATOR, 0644);
+ if (err)
+ goto detach;
+
+#ifdef EVDEV_SUPPORT
+ sc->sc_evdev = evdev_alloc();
+ evdev_set_name(sc->sc_evdev, device_get_desc(dev));
+ evdev_set_phys(sc->sc_evdev, device_get_nameunit(dev));
+ evdev_set_id(sc->sc_evdev, BUS_USB, uaa->info.idVendor,
+ uaa->info.idProduct, 0);
+ evdev_set_serial(sc->sc_evdev, usb_get_serial(uaa->device));
+ evdev_set_methods(sc->sc_evdev, sc, &ums_evdev_methods);
+ evdev_support_prop(sc->sc_evdev, INPUT_PROP_POINTER);
+ evdev_support_event(sc->sc_evdev, EV_SYN);
+ evdev_support_event(sc->sc_evdev, EV_REL);
+ evdev_support_event(sc->sc_evdev, EV_KEY);
+
+ info = &sc->sc_info[0];
+
+ if (info->sc_flags & UMS_FLAG_X_AXIS)
+ evdev_support_rel(sc->sc_evdev, REL_X);
+
+ if (info->sc_flags & UMS_FLAG_Y_AXIS)
+ evdev_support_rel(sc->sc_evdev, REL_Y);
+
+ if (info->sc_flags & UMS_FLAG_Z_AXIS)
+ evdev_support_rel(sc->sc_evdev, REL_WHEEL);
+
+ if (info->sc_flags & UMS_FLAG_T_AXIS)
+ evdev_support_rel(sc->sc_evdev, REL_HWHEEL);
+
+ for (i = 0; i < info->sc_buttons; i++)
+ evdev_support_key(sc->sc_evdev, BTN_MOUSE + i);
+
+ err = evdev_register_mtx(sc->sc_evdev, &sc->sc_mtx);
+ if (err)
+ goto detach;
+#endif
+
+ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
+ OID_AUTO, "parseinfo",
+ CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ sc, 0, ums_sysctl_handler_parseinfo, "",
+ "Dump of parsed HID report descriptor");
+
+ return (0);
+
+detach:
+ if (d_ptr) {
+ free(d_ptr, M_TEMP);
+ }
+ ums_detach(dev);
+ return (ENOMEM);
+}
+
+static int
+ums_detach(device_t self)
+{
+ struct ums_softc *sc = device_get_softc(self);
+
+ DPRINTF("sc=%p\n", sc);
+
+ usb_fifo_detach(&sc->sc_fifo);
+
+#ifdef EVDEV_SUPPORT
+ evdev_free(sc->sc_evdev);
+#endif
+
+ usbd_transfer_unsetup(sc->sc_xfer, UMS_N_TRANSFER);
+
+ usb_callout_drain(&sc->sc_callout);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+ums_reset(struct ums_softc *sc)
+{
+
+ /* reset all USB mouse parameters */
+
+ if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON)
+ sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON;
+ else
+ sc->sc_hw.buttons = sc->sc_buttons;
+
+ sc->sc_hw.iftype = MOUSE_IF_USB;
+ sc->sc_hw.type = MOUSE_MOUSE;
+ sc->sc_hw.model = MOUSE_MODEL_GENERIC;
+ sc->sc_hw.hwid = 0;
+
+ sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+ sc->sc_mode.rate = -1;
+ sc->sc_mode.resolution = MOUSE_RES_UNKNOWN;
+ sc->sc_mode.accelfactor = 0;
+ sc->sc_mode.level = 0;
+ sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+
+ /* reset status */
+
+ sc->sc_status.flags = 0;
+ sc->sc_status.button = 0;
+ sc->sc_status.obutton = 0;
+ sc->sc_status.dx = 0;
+ sc->sc_status.dy = 0;
+ sc->sc_status.dz = 0;
+ /* sc->sc_status.dt = 0; */
+}
+
+static void
+ums_start_rx(struct ums_softc *sc)
+{
+ int rate;
+
+ /* Check if we should override the default polling interval */
+ rate = sc->sc_pollrate;
+ /* Range check rate */
+ if (rate > 1000)
+ rate = 1000;
+ /* Check for set rate */
+ if ((rate > 0) && (sc->sc_xfer[UMS_INTR_DT] != NULL)) {
+ DPRINTF("Setting pollrate = %d\n", rate);
+ /* Stop current transfer, if any */
+ usbd_transfer_stop(sc->sc_xfer[UMS_INTR_DT]);
+ /* Set new interval */
+ usbd_xfer_set_interval(sc->sc_xfer[UMS_INTR_DT], 1000 / rate);
+ /* Only set pollrate once */
+ sc->sc_pollrate = 0;
+ }
+
+ usbd_transfer_start(sc->sc_xfer[UMS_INTR_DT]);
+}
+
+static void
+ums_stop_rx(struct ums_softc *sc)
+{
+ usbd_transfer_stop(sc->sc_xfer[UMS_INTR_DT]);
+ usb_callout_stop(&sc->sc_callout);
+}
+
+static void
+ums_fifo_start_read(struct usb_fifo *fifo)
+{
+ struct ums_softc *sc = usb_fifo_softc(fifo);
+
+ ums_start_rx(sc);
+}
+
+static void
+ums_fifo_stop_read(struct usb_fifo *fifo)
+{
+ struct ums_softc *sc = usb_fifo_softc(fifo);
+
+#ifdef EVDEV_SUPPORT
+ if ((sc->sc_evflags & UMS_EVDEV_OPENED) == 0)
+#endif
+ ums_stop_rx(sc);
+}
+
+#if ((MOUSE_SYS_PACKETSIZE != 8) || \
+ (MOUSE_MSC_PACKETSIZE != 5))
+#error "Software assumptions are not met. Please update code."
+#endif
+
+static void
+ums_put_queue(struct ums_softc *sc, int32_t dx, int32_t dy,
+ int32_t dz, int32_t dt, int32_t buttons)
+{
+ uint8_t buf[8];
+
+#ifdef EVDEV_SUPPORT
+ if (evdev_is_grabbed(sc->sc_evdev))
+ return;
+#endif
+ if (1) {
+ 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;
+
+ buf[0] = sc->sc_mode.syncmask[1];
+ buf[0] |= (~buttons) & MOUSE_MSC_BUTTONS;
+ buf[1] = dx >> 1;
+ buf[2] = dy >> 1;
+ buf[3] = dx - (dx >> 1);
+ buf[4] = dy - (dy >> 1);
+
+ if (sc->sc_mode.level == 1) {
+ buf[5] = dz >> 1;
+ buf[6] = dz - (dz >> 1);
+ buf[7] = (((~buttons) >> 3) & MOUSE_SYS_EXTBUTTONS);
+ }
+ usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf,
+ sc->sc_mode.packetsize, 1);
+ } else {
+ DPRINTF("Buffer full, discarded packet\n");
+ }
+}
+
+#ifdef EVDEV_SUPPORT
+static void
+ums_evdev_push(struct ums_softc *sc, int32_t dx, int32_t dy,
+ int32_t dz, int32_t dt, int32_t buttons)
+{
+ if (evdev_rcpt_mask & EVDEV_RCPT_HW_MOUSE) {
+ /* Push evdev event */
+ evdev_push_rel(sc->sc_evdev, REL_X, dx);
+ evdev_push_rel(sc->sc_evdev, REL_Y, -dy);
+ evdev_push_rel(sc->sc_evdev, REL_WHEEL, -dz);
+ evdev_push_rel(sc->sc_evdev, REL_HWHEEL, dt);
+ evdev_push_mouse_btn(sc->sc_evdev,
+ (buttons & ~MOUSE_STDBUTTONS) |
+ (buttons & (1 << 2) ? MOUSE_BUTTON1DOWN : 0) |
+ (buttons & (1 << 1) ? MOUSE_BUTTON2DOWN : 0) |
+ (buttons & (1 << 0) ? MOUSE_BUTTON3DOWN : 0));
+ evdev_sync(sc->sc_evdev);
+ }
+}
+#endif
+
+static void
+ums_reset_buf(struct ums_softc *sc)
+{
+ /* reset read queue, must be called locked */
+ usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]);
+}
+
+#ifdef EVDEV_SUPPORT
+static int
+ums_ev_open(struct evdev_dev *evdev)
+{
+ struct ums_softc *sc = evdev_get_softc(evdev);
+
+ mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+ sc->sc_evflags |= UMS_EVDEV_OPENED;
+
+ if (sc->sc_fflags == 0) {
+ ums_reset(sc);
+ }
+ ums_start_rx(sc);
+
+ return (0);
+}
+
+static int
+ums_ev_close(struct evdev_dev *evdev)
+{
+ struct ums_softc *sc = evdev_get_softc(evdev);
+
+ mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+ sc->sc_evflags &= ~UMS_EVDEV_OPENED;
+
+ if (sc->sc_fflags == 0)
+ ums_stop_rx(sc);
+
+ return (0);
+}
+#endif
+
+static int
+ums_fifo_open(struct usb_fifo *fifo, int fflags)
+{
+ struct ums_softc *sc = usb_fifo_softc(fifo);
+
+ DPRINTFN(2, "\n");
+
+ /* check for duplicate open, should not happen */
+ if (sc->sc_fflags & fflags)
+ return (EBUSY);
+
+ /* check for first open */
+#ifdef EVDEV_SUPPORT
+ if (sc->sc_fflags == 0 && (sc->sc_evflags & UMS_EVDEV_OPENED) == 0)
+ ums_reset(sc);
+#else
+ if (sc->sc_fflags == 0)
+ ums_reset(sc);
+#endif
+
+ if (fflags & FREAD) {
+ /* allocate RX buffer */
+ if (usb_fifo_alloc_buffer(fifo,
+ UMS_BUF_SIZE, UMS_IFQ_MAXLEN)) {
+ return (ENOMEM);
+ }
+ }
+
+ sc->sc_fflags |= fflags & (FREAD | FWRITE);
+ return (0);
+}
+
+static void
+ums_fifo_close(struct usb_fifo *fifo, int fflags)
+{
+ struct ums_softc *sc = usb_fifo_softc(fifo);
+
+ DPRINTFN(2, "\n");
+
+ if (fflags & FREAD)
+ usb_fifo_free_buffer(fifo);
+
+ sc->sc_fflags &= ~(fflags & (FREAD | FWRITE));
+}
+
+static int
+ums_fifo_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
+{
+ struct ums_softc *sc = usb_fifo_softc(fifo);
+ mousemode_t mode;
+ int error = 0;
+
+ DPRINTFN(2, "\n");
+
+ mtx_lock(&sc->sc_mtx);
+
+ switch (cmd) {
+ case MOUSE_GETHWINFO:
+ *(mousehw_t *)addr = sc->sc_hw;
+ break;
+
+ case MOUSE_GETMODE:
+ *(mousemode_t *)addr = sc->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)) {
+ error = EINVAL;
+ break;
+ } else {
+ sc->sc_mode.level = mode.level;
+ }
+
+ /* store polling rate */
+ sc->sc_pollrate = mode.rate;
+
+ if (sc->sc_mode.level == 0) {
+ if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON)
+ sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON;
+ else
+ sc->sc_hw.buttons = sc->sc_buttons;
+ sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+ sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+ } else if (sc->sc_mode.level == 1) {
+ if (sc->sc_buttons > MOUSE_SYS_MAXBUTTON)
+ sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON;
+ else
+ sc->sc_hw.buttons = sc->sc_buttons;
+ sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
+ sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+ }
+ ums_reset_buf(sc);
+ break;
+
+ case MOUSE_GETLEVEL:
+ *(int *)addr = sc->sc_mode.level;
+ break;
+
+ case MOUSE_SETLEVEL:
+ if (*(int *)addr < 0 || *(int *)addr > 1) {
+ error = EINVAL;
+ break;
+ }
+ sc->sc_mode.level = *(int *)addr;
+
+ if (sc->sc_mode.level == 0) {
+ if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON)
+ sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON;
+ else
+ sc->sc_hw.buttons = sc->sc_buttons;
+ sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+ sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+ } else if (sc->sc_mode.level == 1) {
+ if (sc->sc_buttons > MOUSE_SYS_MAXBUTTON)
+ sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON;
+ else
+ sc->sc_hw.buttons = sc->sc_buttons;
+ sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
+ sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+ }
+ ums_reset_buf(sc);
+ break;
+
+ case MOUSE_GETSTATUS:{
+ mousestatus_t *status = (mousestatus_t *)addr;
+
+ *status = sc->sc_status;
+ sc->sc_status.obutton = sc->sc_status.button;
+ sc->sc_status.button = 0;
+ sc->sc_status.dx = 0;
+ sc->sc_status.dy = 0;
+ sc->sc_status.dz = 0;
+ /* sc->sc_status.dt = 0; */
+
+ 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;
+ break;
+ }
+
+ mtx_unlock(&sc->sc_mtx);
+ return (error);
+}
+
+static int
+ums_sysctl_handler_parseinfo(SYSCTL_HANDLER_ARGS)
+{
+ struct ums_softc *sc = arg1;
+ struct ums_info *info;
+ struct sbuf *sb;
+ int i, j, err, had_output;
+
+ sb = sbuf_new_auto();
+ for (i = 0, had_output = 0; i < UMS_INFO_MAX; i++) {
+ info = &sc->sc_info[i];
+
+ /* Don't emit empty info */
+ if ((info->sc_flags &
+ (UMS_FLAG_X_AXIS | UMS_FLAG_Y_AXIS | UMS_FLAG_Z_AXIS |
+ UMS_FLAG_T_AXIS | UMS_FLAG_W_AXIS)) == 0 &&
+ info->sc_buttons == 0)
+ continue;
+
+ if (had_output)
+ sbuf_printf(sb, "\n");
+ had_output = 1;
+ sbuf_printf(sb, "i%d:", i + 1);
+ if (info->sc_flags & UMS_FLAG_X_AXIS)
+ sbuf_printf(sb, " X:r%d, p%d, s%d;",
+ (int)info->sc_iid_x,
+ (int)info->sc_loc_x.pos,
+ (int)info->sc_loc_x.size);
+ if (info->sc_flags & UMS_FLAG_Y_AXIS)
+ sbuf_printf(sb, " Y:r%d, p%d, s%d;",
+ (int)info->sc_iid_y,
+ (int)info->sc_loc_y.pos,
+ (int)info->sc_loc_y.size);
+ if (info->sc_flags & UMS_FLAG_Z_AXIS)
+ sbuf_printf(sb, " Z:r%d, p%d, s%d;",
+ (int)info->sc_iid_z,
+ (int)info->sc_loc_z.pos,
+ (int)info->sc_loc_z.size);
+ if (info->sc_flags & UMS_FLAG_T_AXIS)
+ sbuf_printf(sb, " T:r%d, p%d, s%d;",
+ (int)info->sc_iid_t,
+ (int)info->sc_loc_t.pos,
+ (int)info->sc_loc_t.size);
+ if (info->sc_flags & UMS_FLAG_W_AXIS)
+ sbuf_printf(sb, " W:r%d, p%d, s%d;",
+ (int)info->sc_iid_w,
+ (int)info->sc_loc_w.pos,
+ (int)info->sc_loc_w.size);
+
+ for (j = 0; j < info->sc_buttons; j++) {
+ sbuf_printf(sb, " B%d:r%d, p%d, s%d;", j + 1,
+ (int)info->sc_iid_btn[j],
+ (int)info->sc_loc_btn[j].pos,
+ (int)info->sc_loc_btn[j].size);
+ }
+ }
+ sbuf_finish(sb);
+ err = SYSCTL_OUT(req, sbuf_data(sb), sbuf_len(sb) + 1);
+ sbuf_delete(sb);
+
+ return (err);
+}
+
+static device_method_t ums_methods[] = {
+ DEVMETHOD(device_probe, ums_probe),
+ DEVMETHOD(device_attach, ums_attach),
+ DEVMETHOD(device_detach, ums_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t ums_driver = {
+ .name = "ums",
+ .methods = ums_methods,
+ .size = sizeof(struct ums_softc),
+};
+
+DRIVER_MODULE(ums, uhub, ums_driver, NULL, NULL);
+MODULE_DEPEND(ums, usb, 1, 1, 1);
+MODULE_DEPEND(ums, hid, 1, 1, 1);
+#ifdef EVDEV_SUPPORT
+MODULE_DEPEND(ums, evdev, 1, 1, 1);
+#endif
+MODULE_VERSION(ums, 1);
+USB_PNP_HOST_INFO(ums_devs);
diff --git a/sys/dev/usb/input/usb_rdesc.h b/sys/dev/usb/input/usb_rdesc.h
new file mode 100644
index 000000000000..61963d51d2ee
--- /dev/null
+++ b/sys/dev/usb/input/usb_rdesc.h
@@ -0,0 +1,37 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.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.
+ *
+ * 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 a proxy file for replacements for broken HID report descriptors.
+ */
+
+#include <dev/hid/hidrdesc.h>
+
+#define UHID_GRAPHIRE_REPORT_DESCR HID_GRAPHIRE_REPORT_DESCR
+#define UHID_GRAPHIRE3_4X5_REPORT_DESCR HID_GRAPHIRE3_4X5_REPORT_DESCR
+#define UHID_XB360GP_REPORT_DESCR HID_XB360GP_REPORT_DESCR
+#define UHID_SNES_REPORT_DESCR HID_SNES_REPORT_DESCR
+#define UHID_MOUSE_BOOTPROTO_DESCR HID_MOUSE_BOOTPROTO_DESCR
+#define UHID_KBD_BOOTPROTO_DESCR HID_KBD_BOOTPROTO_DESCR
diff --git a/sys/dev/usb/input/usbhid.c b/sys/dev/usb/input/usbhid.c
new file mode 100644
index 000000000000..3bb7d5e594e3
--- /dev/null
+++ b/sys/dev/usb/input/usbhid.c
@@ -0,0 +1,899 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 1998 The NetBSD Foundation, Inc.
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
+ *
+ * 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.
+ *
+ * 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: https://www.usb.org/sites/default/files/documents/hid1_11.pdf
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+
+#include <dev/evdev/input.h>
+
+#include <dev/hid/hid.h>
+#include <dev/hid/hidquirk.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/usb_core.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_util.h>
+
+#define USB_DEBUG_VAR usbhid_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/quirk/usb_quirk.h>
+
+#include "hid_if.h"
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, usbhid, CTLFLAG_RW, 0, "USB usbhid");
+static int usbhid_enable = 0;
+SYSCTL_INT(_hw_usb_usbhid, OID_AUTO, enable, CTLFLAG_RWTUN,
+ &usbhid_enable, 0, "Enable usbhid and prefer it to other USB HID drivers");
+#ifdef USB_DEBUG
+static int usbhid_debug = 0;
+SYSCTL_INT(_hw_usb_usbhid, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &usbhid_debug, 0, "Debug level");
+#endif
+
+/* Second set of USB transfers for polling mode */
+#define POLL_XFER(xfer) ((xfer) + USBHID_N_TRANSFER)
+enum {
+ USBHID_INTR_OUT_DT,
+ USBHID_INTR_IN_DT,
+ USBHID_CTRL_DT,
+ USBHID_N_TRANSFER,
+};
+
+struct usbhid_xfer_ctx;
+typedef int usbhid_callback_t(struct usbhid_xfer_ctx *xfer_ctx);
+
+union usbhid_device_request {
+ struct { /* INTR xfers */
+ uint16_t maxlen;
+ uint16_t actlen;
+ } intr;
+ struct usb_device_request ctrl; /* CTRL xfers */
+};
+
+/* Syncronous USB transfer context */
+struct usbhid_xfer_ctx {
+ union usbhid_device_request req;
+ uint8_t *buf;
+ int error;
+ usbhid_callback_t *cb;
+ void *cb_ctx;
+ int waiters;
+ bool influx;
+};
+
+struct usbhid_softc {
+ hid_intr_t *sc_intr_handler;
+ void *sc_intr_ctx;
+ void *sc_intr_buf;
+
+ struct hid_device_info sc_hw;
+
+ struct mtx sc_mtx;
+ struct usb_config sc_config[USBHID_N_TRANSFER];
+ struct usb_xfer *sc_xfer[POLL_XFER(USBHID_N_TRANSFER)];
+ struct usbhid_xfer_ctx sc_xfer_ctx[POLL_XFER(USBHID_N_TRANSFER)];
+ bool sc_can_poll;
+
+ struct usb_device *sc_udev;
+ uint8_t sc_iface_no;
+ uint8_t sc_iface_index;
+};
+
+/* prototypes */
+
+static device_probe_t usbhid_probe;
+static device_attach_t usbhid_attach;
+static device_detach_t usbhid_detach;
+
+static usb_callback_t usbhid_intr_out_callback;
+static usb_callback_t usbhid_intr_in_callback;
+static usb_callback_t usbhid_ctrl_callback;
+
+static usbhid_callback_t usbhid_intr_handler_cb;
+static usbhid_callback_t usbhid_sync_wakeup_cb;
+
+static void
+usbhid_intr_out_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int len;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+tr_setup:
+ len = xfer_ctx->req.intr.maxlen;
+ if (len == 0) {
+ if (USB_IN_POLLING_MODE_FUNC())
+ xfer_ctx->error = 0;
+ return;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, xfer_ctx->buf, len);
+ usbd_xfer_set_frame_len(xfer, 0, len);
+ usbd_transfer_submit(xfer);
+ xfer_ctx->req.intr.maxlen = 0;
+ if (USB_IN_POLLING_MODE_FUNC())
+ return;
+ xfer_ctx->error = 0;
+ goto tr_exit;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ xfer_ctx->error = EIO;
+tr_exit:
+ (void)xfer_ctx->cb(xfer_ctx);
+ return;
+ }
+}
+
+static void
+usbhid_intr_in_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTF("transferred!\n");
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, xfer_ctx->buf, actlen);
+ xfer_ctx->req.intr.actlen = actlen;
+ if (xfer_ctx->cb(xfer_ctx) != 0)
+ return;
+
+ case USB_ST_SETUP:
+re_submit:
+ usbd_xfer_set_frame_len(xfer, 0, xfer_ctx->req.intr.maxlen);
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto re_submit;
+ }
+ return;
+ }
+}
+
+static void
+usbhid_ctrl_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer);
+ struct usb_device_request *req = &xfer_ctx->req.ctrl;
+ struct usb_page_cache *pc;
+ int len = UGETW(req->wLength);
+ bool is_rd = (req->bmRequestType & UT_READ) != 0;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ if (!is_rd && len != 0) {
+ pc = usbd_xfer_get_frame(xfer, 1);
+ usbd_copy_in(pc, 0, xfer_ctx->buf, len);
+ }
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, req, sizeof(*req));
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(*req));
+ if (len != 0)
+ usbd_xfer_set_frame_len(xfer, 1, len);
+ usbd_xfer_set_frames(xfer, len != 0 ? 2 : 1);
+ usbd_transfer_submit(xfer);
+ return;
+
+ case USB_ST_TRANSFERRED:
+ if (is_rd && len != 0) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, sizeof(*req), xfer_ctx->buf, len);
+ }
+ xfer_ctx->error = 0;
+ goto tr_exit;
+
+ default: /* Error */
+ /* bomb out */
+ DPRINTFN(1, "error=%s\n", usbd_errstr(error));
+ xfer_ctx->error = EIO;
+tr_exit:
+ (void)xfer_ctx->cb(xfer_ctx);
+ return;
+ }
+}
+
+static int
+usbhid_intr_handler_cb(struct usbhid_xfer_ctx *xfer_ctx)
+{
+ struct usbhid_softc *sc = xfer_ctx->cb_ctx;
+
+ sc->sc_intr_handler(sc->sc_intr_ctx, xfer_ctx->buf,
+ xfer_ctx->req.intr.actlen);
+
+ return (0);
+}
+
+static int
+usbhid_sync_wakeup_cb(struct usbhid_xfer_ctx *xfer_ctx)
+{
+
+ if (!USB_IN_POLLING_MODE_FUNC())
+ wakeup(xfer_ctx->cb_ctx);
+
+ return (ECANCELED);
+}
+
+static const struct usb_config usbhid_config[USBHID_N_TRANSFER] = {
+
+ [USBHID_INTR_OUT_DT] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .flags = {.pipe_bof = 1,.proxy_buffer = 1},
+ .callback = &usbhid_intr_out_callback,
+ },
+ [USBHID_INTR_IN_DT] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1},
+ .callback = &usbhid_intr_in_callback,
+ },
+ [USBHID_CTRL_DT] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .flags = {.proxy_buffer = 1},
+ .callback = &usbhid_ctrl_callback,
+ .timeout = 1000, /* 1 second */
+ },
+};
+
+static inline usb_frlength_t
+usbhid_xfer_max_len(struct usb_xfer *xfer)
+{
+ return (xfer == NULL ? 0 : usbd_xfer_max_len(xfer));
+}
+
+static inline int
+usbhid_xfer_check_len(struct usbhid_softc* sc, int xfer_idx, hid_size_t len)
+{
+ if (USB_IN_POLLING_MODE_FUNC())
+ xfer_idx = POLL_XFER(xfer_idx);
+ if (sc->sc_xfer[xfer_idx] == NULL)
+ return (ENODEV);
+ if (len > usbd_xfer_max_len(sc->sc_xfer[xfer_idx]))
+ return (ENOBUFS);
+ return (0);
+}
+
+static void
+usbhid_intr_setup(device_t dev, device_t child __unused, hid_intr_t intr,
+ void *context, struct hid_rdesc_info *rdesc)
+{
+ struct usbhid_softc* sc = device_get_softc(dev);
+ uint16_t n;
+ bool nowrite;
+ int error;
+
+ nowrite = hid_test_quirk(&sc->sc_hw, HQ_NOWRITE);
+
+ /*
+ * Setup the USB transfers one by one, so they are memory independent
+ * which allows for handling panics triggered by the HID drivers
+ * itself, typically by hkbd via CTRL+ALT+ESC sequences. Or if the HID
+ * keyboard driver was processing a key at the moment of panic.
+ */
+ if (intr == NULL) {
+ if (sc->sc_can_poll)
+ return;
+ for (n = 0; n != USBHID_N_TRANSFER; n++) {
+ if (nowrite && n == USBHID_INTR_OUT_DT)
+ continue;
+ error = usbd_transfer_setup(sc->sc_udev,
+ &sc->sc_iface_index, sc->sc_xfer + POLL_XFER(n),
+ sc->sc_config + n, 1,
+ (void *)(sc->sc_xfer_ctx + POLL_XFER(n)),
+ &sc->sc_mtx);
+ if (error)
+ DPRINTF("xfer %d setup error=%s\n", n,
+ usbd_errstr(error));
+ }
+ mtx_lock(&sc->sc_mtx);
+ if (sc->sc_xfer[USBHID_INTR_IN_DT] != NULL &&
+ sc->sc_xfer[USBHID_INTR_IN_DT]->flags_int.started)
+ usbd_transfer_start(
+ sc->sc_xfer[POLL_XFER(USBHID_INTR_IN_DT)]);
+ mtx_unlock(&sc->sc_mtx);
+ sc->sc_can_poll = true;
+ return;
+ }
+
+ sc->sc_intr_handler = intr;
+ sc->sc_intr_ctx = context;
+ bcopy(usbhid_config, sc->sc_config, sizeof(usbhid_config));
+ bzero(sc->sc_xfer, sizeof(sc->sc_xfer));
+
+ /* Set buffer sizes to match HID report sizes */
+ sc->sc_config[USBHID_INTR_OUT_DT].bufsize = rdesc->osize;
+ sc->sc_config[USBHID_INTR_IN_DT].bufsize = rdesc->isize;
+ sc->sc_config[USBHID_CTRL_DT].bufsize =
+ MAX(rdesc->isize, MAX(rdesc->osize, rdesc->fsize));
+
+ for (n = 0; n != USBHID_N_TRANSFER; n++) {
+ if (nowrite && n == USBHID_INTR_OUT_DT)
+ continue;
+ error = usbd_transfer_setup(sc->sc_udev, &sc->sc_iface_index,
+ sc->sc_xfer + n, sc->sc_config + n, 1,
+ (void *)(sc->sc_xfer_ctx + n), &sc->sc_mtx);
+ if (error)
+ DPRINTF("xfer %d setup error=%s\n", n,
+ usbd_errstr(error));
+ }
+
+ rdesc->rdsize = usbhid_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]);
+ rdesc->grsize = usbhid_xfer_max_len(sc->sc_xfer[USBHID_CTRL_DT]);
+ rdesc->srsize = rdesc->grsize;
+ rdesc->wrsize = nowrite ? rdesc->srsize :
+ usbhid_xfer_max_len(sc->sc_xfer[USBHID_INTR_OUT_DT]);
+
+ sc->sc_intr_buf = malloc(rdesc->rdsize, M_USBDEV, M_ZERO | M_WAITOK);
+}
+
+static void
+usbhid_intr_unsetup(device_t dev, device_t child __unused)
+{
+ struct usbhid_softc* sc = device_get_softc(dev);
+
+ usbd_transfer_unsetup(sc->sc_xfer, USBHID_N_TRANSFER);
+ if (sc->sc_can_poll)
+ usbd_transfer_unsetup(
+ sc->sc_xfer, POLL_XFER(USBHID_N_TRANSFER));
+ sc->sc_can_poll = false;
+ free(sc->sc_intr_buf, M_USBDEV);
+}
+
+static int
+usbhid_intr_start(device_t dev, device_t child __unused)
+{
+ struct usbhid_softc* sc = device_get_softc(dev);
+
+ if (sc->sc_xfer[USBHID_INTR_IN_DT] == NULL)
+ return (ENODEV);
+
+ mtx_lock(&sc->sc_mtx);
+ sc->sc_xfer_ctx[USBHID_INTR_IN_DT] = (struct usbhid_xfer_ctx) {
+ .req.intr.maxlen =
+ usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]),
+ .cb = usbhid_intr_handler_cb,
+ .cb_ctx = sc,
+ .buf = sc->sc_intr_buf,
+ };
+ sc->sc_xfer_ctx[POLL_XFER(USBHID_INTR_IN_DT)] = (struct usbhid_xfer_ctx) {
+ .req.intr.maxlen =
+ usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]),
+ .cb = usbhid_intr_handler_cb,
+ .cb_ctx = sc,
+ .buf = sc->sc_intr_buf,
+ };
+ usbd_transfer_start(sc->sc_xfer[USBHID_INTR_IN_DT]);
+ if (sc->sc_can_poll)
+ usbd_transfer_start(sc->sc_xfer[POLL_XFER(USBHID_INTR_IN_DT)]);
+ mtx_unlock(&sc->sc_mtx);
+
+ return (0);
+}
+
+static int
+usbhid_intr_stop(device_t dev, device_t child __unused)
+{
+ struct usbhid_softc* sc = device_get_softc(dev);
+
+ usbd_transfer_drain(sc->sc_xfer[USBHID_INTR_IN_DT]);
+ usbd_transfer_drain(sc->sc_xfer[USBHID_INTR_OUT_DT]);
+ if (sc->sc_can_poll)
+ usbd_transfer_drain(sc->sc_xfer[POLL_XFER(USBHID_INTR_IN_DT)]);
+
+ return (0);
+}
+
+static void
+usbhid_intr_poll(device_t dev, device_t child __unused)
+{
+ struct usbhid_softc* sc = device_get_softc(dev);
+
+ MPASS(sc->sc_can_poll);
+ usbd_transfer_poll(sc->sc_xfer + USBHID_INTR_IN_DT, 1);
+ usbd_transfer_poll(sc->sc_xfer + POLL_XFER(USBHID_INTR_IN_DT), 1);
+}
+
+/*
+ * HID interface
+ */
+static int
+usbhid_sync_xfer(struct usbhid_softc* sc, int xfer_idx,
+ union usbhid_device_request *req, void *buf)
+{
+ int error, timeout;
+ struct usbhid_xfer_ctx *xfer_ctx;
+
+ xfer_ctx = sc->sc_xfer_ctx + xfer_idx;
+
+ if (USB_IN_POLLING_MODE_FUNC()) {
+ xfer_ctx = POLL_XFER(xfer_ctx);
+ xfer_idx = POLL_XFER(xfer_idx);
+ } else {
+ mtx_lock(&sc->sc_mtx);
+ ++xfer_ctx->waiters;
+ while (xfer_ctx->influx)
+ mtx_sleep(&xfer_ctx->waiters, &sc->sc_mtx, 0,
+ "usbhid wt", 0);
+ --xfer_ctx->waiters;
+ xfer_ctx->influx = true;
+ }
+
+ xfer_ctx->buf = buf;
+ xfer_ctx->req = *req;
+ xfer_ctx->error = ETIMEDOUT;
+ xfer_ctx->cb = &usbhid_sync_wakeup_cb;
+ xfer_ctx->cb_ctx = xfer_ctx;
+ timeout = USB_DEFAULT_TIMEOUT;
+ usbd_transfer_start(sc->sc_xfer[xfer_idx]);
+
+ if (USB_IN_POLLING_MODE_FUNC())
+ while (timeout > 0 && xfer_ctx->error == ETIMEDOUT) {
+ usbd_transfer_poll(sc->sc_xfer + xfer_idx, 1);
+ DELAY(1000);
+ timeout--;
+ }
+ else
+ msleep_sbt(xfer_ctx, &sc->sc_mtx, 0, "usbhid io",
+ SBT_1MS * timeout, 0, C_HARDCLOCK);
+
+ /* Perform usbhid_write() asyncronously to improve pipelining */
+ if (USB_IN_POLLING_MODE_FUNC() || xfer_ctx->error != 0 ||
+ sc->sc_config[xfer_idx].type != UE_INTERRUPT ||
+ sc->sc_config[xfer_idx].direction != UE_DIR_OUT)
+ usbd_transfer_stop(sc->sc_xfer[xfer_idx]);
+ error = xfer_ctx->error;
+ if (error == 0)
+ *req = xfer_ctx->req;
+
+ if (!USB_IN_POLLING_MODE_FUNC()) {
+ xfer_ctx->influx = false;
+ if (xfer_ctx->waiters != 0)
+ wakeup_one(&xfer_ctx->waiters);
+ mtx_unlock(&sc->sc_mtx);
+ }
+
+ if (error)
+ DPRINTF("USB IO error:%d\n", error);
+
+ return (error);
+}
+
+static int
+usbhid_get_rdesc(device_t dev, device_t child __unused, void *buf,
+ hid_size_t len)
+{
+ struct usbhid_softc* sc = device_get_softc(dev);
+ int error;
+
+ error = usbd_req_get_report_descriptor(sc->sc_udev, NULL,
+ buf, len, sc->sc_iface_index);
+
+ if (error)
+ DPRINTF("no report descriptor: %s\n", usbd_errstr(error));
+
+ return (error == 0 ? 0 : ENXIO);
+}
+
+static int
+usbhid_get_report(device_t dev, device_t child __unused, void *buf,
+ hid_size_t maxlen, hid_size_t *actlen, uint8_t type, uint8_t id)
+{
+ struct usbhid_softc* sc = device_get_softc(dev);
+ union usbhid_device_request req;
+ int error;
+
+ error = usbhid_xfer_check_len(sc, USBHID_CTRL_DT, maxlen);
+ if (error)
+ return (error);
+
+ req.ctrl.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.ctrl.bRequest = UR_GET_REPORT;
+ USETW2(req.ctrl.wValue, type, id);
+ req.ctrl.wIndex[0] = sc->sc_iface_no;
+ req.ctrl.wIndex[1] = 0;
+ USETW(req.ctrl.wLength, maxlen);
+
+ error = usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, buf);
+ if (!error && actlen != NULL)
+ *actlen = maxlen;
+
+ return (error);
+}
+
+static int
+usbhid_set_report(device_t dev, device_t child __unused, const void *buf,
+ hid_size_t len, uint8_t type, uint8_t id)
+{
+ struct usbhid_softc* sc = device_get_softc(dev);
+ union usbhid_device_request req;
+ int error;
+
+ error = usbhid_xfer_check_len(sc, USBHID_CTRL_DT, len);
+ if (error)
+ return (error);
+
+ req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.ctrl.bRequest = UR_SET_REPORT;
+ USETW2(req.ctrl.wValue, type, id);
+ req.ctrl.wIndex[0] = sc->sc_iface_no;
+ req.ctrl.wIndex[1] = 0;
+ USETW(req.ctrl.wLength, len);
+
+ return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req,
+ __DECONST(void *, buf)));
+}
+
+static int
+usbhid_read(device_t dev, device_t child __unused, void *buf,
+ hid_size_t maxlen, hid_size_t *actlen)
+{
+ struct usbhid_softc* sc = device_get_softc(dev);
+ union usbhid_device_request req;
+ int error;
+
+ error = usbhid_xfer_check_len(sc, USBHID_INTR_IN_DT, maxlen);
+ if (error)
+ return (error);
+
+ req.intr.maxlen = maxlen;
+ error = usbhid_sync_xfer(sc, USBHID_INTR_IN_DT, &req, buf);
+ if (error == 0 && actlen != NULL)
+ *actlen = req.intr.actlen;
+
+ return (error);
+}
+
+static int
+usbhid_write(device_t dev, device_t child __unused, const void *buf,
+ hid_size_t len)
+{
+ struct usbhid_softc* sc = device_get_softc(dev);
+ union usbhid_device_request req;
+ int error;
+
+ error = usbhid_xfer_check_len(sc, USBHID_INTR_OUT_DT, len);
+ if (error)
+ return (error);
+
+ req.intr.maxlen = len;
+ return (usbhid_sync_xfer(sc, USBHID_INTR_OUT_DT, &req,
+ __DECONST(void *, buf)));
+}
+
+static int
+usbhid_set_idle(device_t dev, device_t child __unused, uint16_t duration,
+ uint8_t id)
+{
+ struct usbhid_softc* sc = device_get_softc(dev);
+ union usbhid_device_request req;
+ int error;
+
+ error = usbhid_xfer_check_len(sc, USBHID_CTRL_DT, 0);
+ if (error)
+ return (error);
+
+ /* Duration is measured in 4 milliseconds per unit. */
+ req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.ctrl.bRequest = UR_SET_IDLE;
+ USETW2(req.ctrl.wValue, (duration + 3) / 4, id);
+ req.ctrl.wIndex[0] = sc->sc_iface_no;
+ req.ctrl.wIndex[1] = 0;
+ USETW(req.ctrl.wLength, 0);
+
+ return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, NULL));
+}
+
+static int
+usbhid_set_protocol(device_t dev, device_t child __unused, uint16_t protocol)
+{
+ struct usbhid_softc* sc = device_get_softc(dev);
+ union usbhid_device_request req;
+ int error;
+
+ error = usbhid_xfer_check_len(sc, USBHID_CTRL_DT, 0);
+ if (error)
+ return (error);
+
+ req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.ctrl.bRequest = UR_SET_PROTOCOL;
+ USETW(req.ctrl.wValue, protocol);
+ req.ctrl.wIndex[0] = sc->sc_iface_no;
+ req.ctrl.wIndex[1] = 0;
+ USETW(req.ctrl.wLength, 0);
+
+ return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, NULL));
+}
+
+static int
+usbhid_ioctl(device_t dev, device_t child __unused, unsigned long cmd,
+ uintptr_t data)
+{
+ struct usbhid_softc* sc = device_get_softc(dev);
+ struct usb_ctl_request *ucr;
+ union usbhid_device_request req;
+ int error;
+
+ switch (cmd) {
+ case USB_REQUEST:
+ ucr = (struct usb_ctl_request *)data;
+ req.ctrl = ucr->ucr_request;
+ error = usbhid_xfer_check_len(
+ sc, USBHID_CTRL_DT, UGETW(req.ctrl.wLength));
+ if (error)
+ break;
+ error = usb_check_request(sc->sc_udev, &req.ctrl);
+ if (error)
+ break;
+ error = usbhid_sync_xfer(
+ sc, USBHID_CTRL_DT, &req, ucr->ucr_data);
+ if (error == 0)
+ ucr->ucr_actlen = UGETW(req.ctrl.wLength);
+ break;
+ default:
+ error = EINVAL;
+ }
+
+ return (error);
+}
+
+static void
+usbhid_init_device_info(struct usb_attach_arg *uaa, struct hid_device_info *hw)
+{
+
+ hw->idBus = BUS_USB;
+ hw->idVendor = uaa->info.idVendor;
+ hw->idProduct = uaa->info.idProduct;
+ hw->idVersion = uaa->info.bcdDevice;
+
+ /* Set various quirks based on usb_attach_arg */
+ hid_add_dynamic_quirk(hw, USB_GET_DRIVER_INFO(uaa));
+}
+
+static void
+usbhid_fill_device_info(struct usb_attach_arg *uaa, struct hid_device_info *hw)
+{
+ struct usb_device *udev = uaa->device;
+ struct usb_interface *iface = uaa->iface;
+ struct usb_hid_descriptor *hid;
+ struct usb_endpoint *ep;
+
+ snprintf(hw->name, sizeof(hw->name), "%s %s",
+ usb_get_manufacturer(udev), usb_get_product(udev));
+ strlcpy(hw->serial, usb_get_serial(udev), sizeof(hw->serial));
+
+ if (uaa->info.bInterfaceClass == UICLASS_HID &&
+ iface != NULL && iface->idesc != NULL) {
+ hid = hid_get_descriptor_from_usb(
+ usbd_get_config_descriptor(udev), iface->idesc);
+ if (hid != NULL)
+ hw->rdescsize =
+ UGETW(hid->descrs[0].wDescriptorLength);
+ }
+
+ /* See if there is a interrupt out endpoint. */
+ ep = usbd_get_endpoint(udev, uaa->info.bIfaceIndex,
+ usbhid_config + USBHID_INTR_OUT_DT);
+ if (ep == NULL || ep->methods == NULL)
+ hid_add_dynamic_quirk(hw, HQ_NOWRITE);
+}
+
+static const STRUCT_USB_HOST_ID usbhid_devs[] = {
+ /* the Xbox 360 gamepad doesn't use the HID class */
+ {USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(UISUBCLASS_XBOX360_CONTROLLER),
+ USB_IFACE_PROTOCOL(UIPROTO_XBOX360_GAMEPAD),
+ USB_DRIVER_INFO(HQ_IS_XBOX360GP)},
+ /* HID keyboard with boot protocol support */
+ {USB_IFACE_CLASS(UICLASS_HID),
+ USB_IFACE_SUBCLASS(UISUBCLASS_BOOT),
+ USB_IFACE_PROTOCOL(UIPROTO_BOOT_KEYBOARD),
+ USB_DRIVER_INFO(HQ_HAS_KBD_BOOTPROTO)},
+ /* HID mouse with boot protocol support */
+ {USB_IFACE_CLASS(UICLASS_HID),
+ USB_IFACE_SUBCLASS(UISUBCLASS_BOOT),
+ USB_IFACE_PROTOCOL(UIPROTO_MOUSE),
+ USB_DRIVER_INFO(HQ_HAS_MS_BOOTPROTO)},
+ /* generic HID class */
+ {USB_IFACE_CLASS(UICLASS_HID), USB_DRIVER_INFO(HQ_NONE)},
+};
+
+static int
+usbhid_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct usbhid_softc *sc = device_get_softc(dev);
+ int error;
+
+ DPRINTFN(11, "\n");
+
+ if (usbhid_enable == 0)
+ return (ENXIO);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ error = usbd_lookup_id_by_uaa(usbhid_devs, sizeof(usbhid_devs), uaa);
+ if (error)
+ return (error);
+
+ if (usb_test_quirk(uaa, UQ_HID_IGNORE))
+ return (ENXIO);
+
+ /*
+ * Setup temporary hid_device_info so that we can figure out some
+ * basic quirks for this device.
+ */
+ usbhid_init_device_info(uaa, &sc->sc_hw);
+
+ if (hid_test_quirk(&sc->sc_hw, HQ_HID_IGNORE))
+ return (ENXIO);
+
+ return (BUS_PROBE_DEFAULT + 1);
+}
+
+static int
+usbhid_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct usbhid_softc *sc = device_get_softc(dev);
+ device_t child;
+ int error = 0;
+
+ DPRINTFN(10, "sc=%p\n", sc);
+
+ device_set_usb_desc(dev);
+
+ sc->sc_udev = uaa->device;
+ sc->sc_iface_no = uaa->info.bIfaceNum;
+ sc->sc_iface_index = uaa->info.bIfaceIndex;
+
+ usbhid_fill_device_info(uaa, &sc->sc_hw);
+
+ error = usbd_req_set_idle(uaa->device, NULL,
+ uaa->info.bIfaceIndex, 0, 0);
+ if (error)
+ DPRINTF("set idle failed, error=%s (ignored)\n",
+ usbd_errstr(error));
+
+ mtx_init(&sc->sc_mtx, "usbhid lock", NULL, MTX_DEF);
+
+ child = device_add_child(dev, "hidbus", DEVICE_UNIT_ANY);
+ if (child == NULL) {
+ device_printf(dev, "Could not add hidbus device\n");
+ usbhid_detach(dev);
+ return (ENOMEM);
+ }
+
+ device_set_ivars(child, &sc->sc_hw);
+ bus_attach_children(dev);
+
+ return (0); /* success */
+}
+
+static int
+usbhid_detach(device_t dev)
+{
+ struct usbhid_softc *sc = device_get_softc(dev);
+ int error;
+
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static device_method_t usbhid_methods[] = {
+ DEVMETHOD(device_probe, usbhid_probe),
+ DEVMETHOD(device_attach, usbhid_attach),
+ DEVMETHOD(device_detach, usbhid_detach),
+
+ DEVMETHOD(hid_intr_setup, usbhid_intr_setup),
+ DEVMETHOD(hid_intr_unsetup, usbhid_intr_unsetup),
+ DEVMETHOD(hid_intr_start, usbhid_intr_start),
+ DEVMETHOD(hid_intr_stop, usbhid_intr_stop),
+ DEVMETHOD(hid_intr_poll, usbhid_intr_poll),
+
+ /* HID interface */
+ DEVMETHOD(hid_get_rdesc, usbhid_get_rdesc),
+ DEVMETHOD(hid_read, usbhid_read),
+ DEVMETHOD(hid_write, usbhid_write),
+ DEVMETHOD(hid_get_report, usbhid_get_report),
+ DEVMETHOD(hid_set_report, usbhid_set_report),
+ DEVMETHOD(hid_set_idle, usbhid_set_idle),
+ DEVMETHOD(hid_set_protocol, usbhid_set_protocol),
+ DEVMETHOD(hid_ioctl, usbhid_ioctl),
+
+ DEVMETHOD_END
+};
+
+static driver_t usbhid_driver = {
+ .name = "usbhid",
+ .methods = usbhid_methods,
+ .size = sizeof(struct usbhid_softc),
+};
+
+DRIVER_MODULE(usbhid, uhub, usbhid_driver, NULL, NULL);
+MODULE_DEPEND(usbhid, usb, 1, 1, 1);
+MODULE_DEPEND(usbhid, hid, 1, 1, 1);
+MODULE_DEPEND(usbhid, hidbus, 1, 1, 1);
+MODULE_VERSION(usbhid, 1);
+USB_PNP_HOST_INFO(usbhid_devs);
diff --git a/sys/dev/usb/input/wmt.c b/sys/dev/usb/input/wmt.c
new file mode 100644
index 000000000000..03e4da35a9fe
--- /dev/null
+++ b/sys/dev/usb/input/wmt.c
@@ -0,0 +1,1008 @@
+/*-
+ * Copyright (c) 2014-2017 Vladimir Kondratyev <wulf@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.
+ */
+
+/*
+ * MS Windows 7/8/10 compatible USB HID Multi-touch Device driver.
+ * https://msdn.microsoft.com/en-us/library/windows/hardware/jj151569(v=vs.85).aspx
+ * https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/stddef.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <dev/hid/hid.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/quirk/usb_quirk.h>
+
+#include <dev/evdev/evdev.h>
+#include <dev/evdev/input.h>
+
+#define USB_DEBUG_VAR wmt_debug
+#include <dev/usb/usb_debug.h>
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, wmt, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB MSWindows 7/8/10 compatible Multi-touch Device");
+#ifdef USB_DEBUG
+static int wmt_debug = 0;
+SYSCTL_INT(_hw_usb_wmt, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &wmt_debug, 1, "Debug level");
+#endif
+static bool wmt_timestamps = 0;
+SYSCTL_BOOL(_hw_usb_wmt, OID_AUTO, timestamps, CTLFLAG_RDTUN,
+ &wmt_timestamps, 1, "Enable hardware timestamp reporting");
+
+#define WMT_BSIZE 1024 /* bytes, buffer size */
+#define WMT_BTN_MAX 8 /* Number of buttons supported */
+
+enum {
+ WMT_INTR_DT,
+ WMT_N_TRANSFER,
+};
+
+enum wmt_type {
+ WMT_TYPE_UNKNOWN = 0, /* HID report descriptor is not probed */
+ WMT_TYPE_UNSUPPORTED, /* Repdescr does not belong to MT device */
+ WMT_TYPE_TOUCHPAD,
+ WMT_TYPE_TOUCHSCREEN,
+};
+
+enum wmt_input_mode {
+ WMT_INPUT_MODE_MOUSE = 0x0,
+ WMT_INPUT_MODE_MT_TOUCHSCREEN = 0x2,
+ WMT_INPUT_MODE_MT_TOUCHPAD = 0x3,
+};
+
+enum {
+ WMT_TIP_SWITCH = ABS_MT_INDEX(ABS_MT_TOOL_TYPE),
+ WMT_WIDTH = ABS_MT_INDEX(ABS_MT_TOUCH_MAJOR),
+ WMT_HEIGHT = ABS_MT_INDEX(ABS_MT_TOUCH_MINOR),
+ WMT_ORIENTATION = ABS_MT_INDEX(ABS_MT_ORIENTATION),
+ WMT_X = ABS_MT_INDEX(ABS_MT_POSITION_X),
+ WMT_Y = ABS_MT_INDEX(ABS_MT_POSITION_Y),
+ WMT_CONTACTID = ABS_MT_INDEX(ABS_MT_TRACKING_ID),
+ WMT_PRESSURE = ABS_MT_INDEX(ABS_MT_PRESSURE),
+ WMT_IN_RANGE = ABS_MT_INDEX(ABS_MT_DISTANCE),
+ WMT_CONFIDENCE = ABS_MT_INDEX(ABS_MT_BLOB_ID),
+ WMT_TOOL_X = ABS_MT_INDEX(ABS_MT_TOOL_X),
+ WMT_TOOL_Y = ABS_MT_INDEX(ABS_MT_TOOL_Y),
+};
+
+#define WMT_N_USAGES MT_CNT
+#define WMT_NO_USAGE -1
+
+struct wmt_hid_map_item {
+ char name[5];
+ int32_t usage; /* HID usage */
+ bool reported; /* Item value is passed to evdev */
+ bool required; /* Required for MT Digitizers */
+};
+
+static const struct wmt_hid_map_item wmt_hid_map[WMT_N_USAGES] = {
+ [WMT_TIP_SWITCH] = {
+ .name = "TIP",
+ .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH),
+ .reported = false,
+ .required = true,
+ },
+ [WMT_WIDTH] = {
+ .name = "WDTH",
+ .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH),
+ .reported = true,
+ .required = false,
+ },
+ [WMT_HEIGHT] = {
+ .name = "HGHT",
+ .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT),
+ .reported = true,
+ .required = false,
+ },
+ [WMT_ORIENTATION] = {
+ .name = "ORIE",
+ .usage = WMT_NO_USAGE,
+ .reported = true,
+ .required = false,
+ },
+ [WMT_X] = {
+ .name = "X",
+ .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
+ .reported = true,
+ .required = true,
+ },
+ [WMT_Y] = {
+ .name = "Y",
+ .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
+ .reported = true,
+ .required = true,
+ },
+ [WMT_CONTACTID] = {
+ .name = "C_ID",
+ .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID),
+ .reported = true,
+ .required = true,
+ },
+ [WMT_PRESSURE] = {
+ .name = "PRES",
+ .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_PRESSURE),
+ .reported = true,
+ .required = false,
+ },
+ [WMT_IN_RANGE] = {
+ .name = "RANG",
+ .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_IN_RANGE),
+ .reported = true,
+ .required = false,
+ },
+ [WMT_CONFIDENCE] = {
+ .name = "CONF",
+ .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE),
+ .reported = false,
+ .required = false,
+ },
+ [WMT_TOOL_X] = { /* Shares HID usage with WMT_X */
+ .name = "TL_X",
+ .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
+ .reported = true,
+ .required = false,
+ },
+ [WMT_TOOL_Y] = { /* Shares HID usage with WMT_Y */
+ .name = "TL_Y",
+ .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
+ .reported = true,
+ .required = false,
+ },
+};
+
+struct wmt_absinfo {
+ int32_t min;
+ int32_t max;
+ int32_t res;
+};
+
+struct wmt_softc {
+ device_t dev;
+ enum wmt_type type;
+
+ int32_t cont_count_max;
+ struct mtx mtx;
+ struct wmt_absinfo ai[WMT_N_USAGES];
+ struct hid_location locs[MAX_MT_SLOTS][WMT_N_USAGES];
+ struct hid_location cont_count_loc;
+ struct hid_location btn_loc[WMT_BTN_MAX];
+ struct hid_location int_btn_loc;
+ struct hid_location scan_time_loc;
+ int32_t scan_time_max;
+ int32_t scan_time;
+ int32_t timestamp;
+ bool touch;
+ bool prev_touch;
+
+ struct usb_xfer *xfer[WMT_N_TRANSFER];
+ struct evdev_dev *evdev;
+
+ union evdev_mt_slot slot_data;
+ uint8_t caps[howmany(WMT_N_USAGES, 8)];
+ uint8_t buttons[howmany(WMT_BTN_MAX, 8)];
+ uint32_t isize;
+ uint32_t nconts_per_report;
+ uint32_t nconts_todo;
+ uint32_t report_len;
+ uint8_t report_id;
+ uint32_t max_button;
+ bool has_int_button;
+ bool is_clickpad;
+ bool do_timestamps;
+
+ struct hid_location cont_max_loc;
+ uint32_t cont_max_rlen;
+ uint8_t cont_max_rid;
+ struct hid_location btn_type_loc;
+ uint32_t btn_type_rlen;
+ uint8_t btn_type_rid;
+ uint32_t thqa_cert_rlen;
+ uint8_t thqa_cert_rid;
+ struct hid_location input_mode_loc;
+ uint32_t input_mode_rlen;
+ uint8_t input_mode_rid;
+
+ uint8_t buf[WMT_BSIZE] __aligned(4);
+};
+
+#define WMT_FOREACH_USAGE(caps, usage) \
+ for ((usage) = 0; (usage) < WMT_N_USAGES; ++(usage)) \
+ if (isset((caps), (usage)))
+
+static enum wmt_type wmt_hid_parse(struct wmt_softc *, const void *, uint16_t);
+static int wmt_set_input_mode(struct wmt_softc *, enum wmt_input_mode);
+
+static usb_callback_t wmt_intr_callback;
+
+static device_probe_t wmt_probe;
+static device_attach_t wmt_attach;
+static device_detach_t wmt_detach;
+
+static evdev_open_t wmt_ev_open;
+static evdev_close_t wmt_ev_close;
+
+static const struct evdev_methods wmt_evdev_methods = {
+ .ev_open = &wmt_ev_open,
+ .ev_close = &wmt_ev_close,
+};
+
+static const struct usb_config wmt_config[WMT_N_TRANSFER] = {
+ [WMT_INTR_DT] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = { .pipe_bof = 1, .short_xfer_ok = 1 },
+ .bufsize = WMT_BSIZE,
+ .callback = &wmt_intr_callback,
+ },
+};
+
+static int
+wmt_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct wmt_softc *sc = device_get_softc(dev);
+ void *d_ptr;
+ uint16_t d_len;
+ int err;
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ if (uaa->info.bInterfaceClass != UICLASS_HID)
+ return (ENXIO);
+
+ if (usb_test_quirk(uaa, UQ_WMT_IGNORE))
+ return (ENXIO);
+
+ err = usbd_req_get_hid_desc(uaa->device, NULL,
+ &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex);
+ if (err)
+ return (ENXIO);
+
+ /* Check if report descriptor belongs to a HID multitouch device */
+ if (sc->type == WMT_TYPE_UNKNOWN)
+ sc->type = wmt_hid_parse(sc, d_ptr, d_len);
+ if (sc->type != WMT_TYPE_UNSUPPORTED)
+ err = BUS_PROBE_DEFAULT;
+ else
+ err = ENXIO;
+
+ /* Check HID report length */
+ if (sc->type != WMT_TYPE_UNSUPPORTED &&
+ (sc->isize <= 0 || sc->isize > WMT_BSIZE)) {
+ DPRINTF("Input size invalid or too large: %d\n", sc->isize);
+ err = ENXIO;
+ }
+
+ free(d_ptr, M_TEMP);
+ return (err);
+}
+
+static int
+wmt_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct wmt_softc *sc = device_get_softc(dev);
+ uint32_t cont_count_max;
+ int nbuttons, btn;
+ size_t i;
+ int err;
+
+ device_set_usb_desc(dev);
+ sc->dev = dev;
+
+ /* Fetch and parse "Contact count maximum" feature report */
+ if (sc->cont_max_rlen > 0 && sc->cont_max_rlen <= WMT_BSIZE) {
+ err = usbd_req_get_report(uaa->device, NULL, sc->buf,
+ sc->cont_max_rlen, uaa->info.bIfaceIndex,
+ UHID_FEATURE_REPORT, sc->cont_max_rid);
+ if (err == USB_ERR_NORMAL_COMPLETION) {
+ cont_count_max = hid_get_udata(sc->buf + 1,
+ sc->cont_max_rlen - 1, &sc->cont_max_loc);
+ /*
+ * Feature report is a primary source of
+ * 'Contact Count Maximum'
+ */
+ if (cont_count_max > 0)
+ sc->cont_count_max = cont_count_max;
+ } else
+ DPRINTF("usbd_req_get_report error=(%s)\n",
+ usbd_errstr(err));
+ } else
+ DPRINTF("Feature report %hhu size invalid or too large: %u\n",
+ sc->cont_max_rid, sc->cont_max_rlen);
+
+ /* Fetch and parse "Button type" feature report */
+ if (sc->btn_type_rlen > 1 && sc->btn_type_rlen <= WMT_BSIZE &&
+ sc->btn_type_rid != sc->cont_max_rid) {
+ bzero(sc->buf, sc->btn_type_rlen);
+ err = usbd_req_get_report(uaa->device, NULL, sc->buf,
+ sc->btn_type_rlen, uaa->info.bIfaceIndex,
+ UHID_FEATURE_REPORT, sc->btn_type_rid);
+ }
+ if (sc->btn_type_rlen > 1) {
+ if (err == 0)
+ sc->is_clickpad = hid_get_udata(sc->buf + 1,
+ sc->btn_type_rlen - 1, &sc->btn_type_loc) == 0;
+ else
+ DPRINTF("usbd_req_get_report error=%d\n", err);
+ }
+
+ /* Fetch THQA certificate to enable some devices like WaveShare */
+ if (sc->thqa_cert_rlen > 0 && sc->thqa_cert_rlen <= WMT_BSIZE &&
+ sc->thqa_cert_rid != sc->cont_max_rid)
+ (void)usbd_req_get_report(uaa->device, NULL, sc->buf,
+ sc->thqa_cert_rlen, uaa->info.bIfaceIndex,
+ UHID_FEATURE_REPORT, sc->thqa_cert_rid);
+
+ /* Switch touchpad in to absolute multitouch mode */
+ if (sc->type == WMT_TYPE_TOUCHPAD) {
+ err = wmt_set_input_mode(sc, WMT_INPUT_MODE_MT_TOUCHPAD);
+ if (err != 0)
+ DPRINTF("Failed to set input mode: %d\n", err);
+ }
+
+ /* Cap contact count maximum to MAX_MT_SLOTS */
+ if (sc->cont_count_max > MAX_MT_SLOTS) {
+ DPRINTF("Hardware reported %d contacts while only %d is "
+ "supported\n", (int)sc->cont_count_max, MAX_MT_SLOTS);
+ sc->cont_count_max = MAX_MT_SLOTS;
+ }
+
+ if (/*usb_test_quirk(hw, UQ_MT_TIMESTAMP) ||*/ wmt_timestamps)
+ sc->do_timestamps = true;
+
+ mtx_init(&sc->mtx, "wmt lock", NULL, MTX_DEF);
+
+ err = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex,
+ sc->xfer, wmt_config, WMT_N_TRANSFER, sc, &sc->mtx);
+ if (err != USB_ERR_NORMAL_COMPLETION) {
+ DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(err));
+ goto detach;
+ }
+
+ sc->evdev = evdev_alloc();
+ evdev_set_name(sc->evdev, device_get_desc(dev));
+ evdev_set_phys(sc->evdev, device_get_nameunit(dev));
+ evdev_set_id(sc->evdev, BUS_USB, uaa->info.idVendor,
+ uaa->info.idProduct, 0);
+ evdev_set_serial(sc->evdev, usb_get_serial(uaa->device));
+ evdev_set_methods(sc->evdev, sc, &wmt_evdev_methods);
+ evdev_set_flag(sc->evdev, EVDEV_FLAG_MT_STCOMPAT);
+ switch (sc->type) {
+ case WMT_TYPE_TOUCHSCREEN:
+ evdev_support_prop(sc->evdev, INPUT_PROP_DIRECT);
+ break;
+ case WMT_TYPE_TOUCHPAD:
+ evdev_support_prop(sc->evdev, INPUT_PROP_POINTER);
+ if (sc->is_clickpad)
+ evdev_support_prop(sc->evdev, INPUT_PROP_BUTTONPAD);
+ break;
+ default:
+ KASSERT(0, ("wmt_attach: unsupported touch device type"));
+ }
+ evdev_support_event(sc->evdev, EV_SYN);
+ evdev_support_event(sc->evdev, EV_ABS);
+ if (sc->do_timestamps) {
+ evdev_support_event(sc->evdev, EV_MSC);
+ evdev_support_msc(sc->evdev, MSC_TIMESTAMP);
+ }
+ nbuttons = 0;
+ if (sc->max_button != 0 || sc->has_int_button) {
+ evdev_support_event(sc->evdev, EV_KEY);
+ if (sc->has_int_button)
+ evdev_support_key(sc->evdev, BTN_LEFT);
+ for (btn = 0; btn < sc->max_button; ++btn) {
+ if (isset(sc->buttons, btn)) {
+ evdev_support_key(sc->evdev, BTN_MOUSE + btn);
+ nbuttons++;
+ }
+ }
+ }
+ evdev_support_abs(sc->evdev,
+ ABS_MT_SLOT, 0, sc->cont_count_max - 1, 0, 0, 0);
+ WMT_FOREACH_USAGE(sc->caps, i) {
+ if (wmt_hid_map[i].reported)
+ evdev_support_abs(sc->evdev, ABS_MT_FIRST + i,
+ sc->ai[i].min, sc->ai[i].max, 0, 0, sc->ai[i].res);
+ }
+
+ err = evdev_register_mtx(sc->evdev, &sc->mtx);
+ if (err)
+ goto detach;
+
+ /* Announce information about the touch device */
+ device_printf(sc->dev, "Multitouch %s with %d external button%s%s\n",
+ sc->type == WMT_TYPE_TOUCHSCREEN ? "touchscreen" : "touchpad",
+ nbuttons, nbuttons != 1 ? "s" : "",
+ sc->is_clickpad ? ", click-pad" : "");
+ device_printf(sc->dev,
+ "%d contacts and [%s%s%s%s%s]. Report range [%d:%d] - [%d:%d]\n",
+ (int)sc->cont_count_max,
+ isset(sc->caps, WMT_IN_RANGE) ? "R" : "",
+ isset(sc->caps, WMT_CONFIDENCE) ? "C" : "",
+ isset(sc->caps, WMT_WIDTH) ? "W" : "",
+ isset(sc->caps, WMT_HEIGHT) ? "H" : "",
+ isset(sc->caps, WMT_PRESSURE) ? "P" : "",
+ (int)sc->ai[WMT_X].min, (int)sc->ai[WMT_Y].min,
+ (int)sc->ai[WMT_X].max, (int)sc->ai[WMT_Y].max);
+
+ return (0);
+
+detach:
+ wmt_detach(dev);
+ return (ENXIO);
+}
+
+static int
+wmt_detach(device_t dev)
+{
+ struct wmt_softc *sc = device_get_softc(dev);
+
+ evdev_free(sc->evdev);
+ usbd_transfer_unsetup(sc->xfer, WMT_N_TRANSFER);
+ mtx_destroy(&sc->mtx);
+ return (0);
+}
+
+static void
+wmt_process_report(struct wmt_softc *sc, uint8_t *buf, int len)
+{
+ size_t usage;
+ union evdev_mt_slot *slot_data;
+ uint32_t cont, btn;
+ uint32_t cont_count;
+ uint32_t width;
+ uint32_t height;
+ uint32_t int_btn = 0;
+ uint32_t left_btn = 0;
+ int slot;
+ uint32_t scan_time;
+ int32_t delta;
+
+ /*
+ * "In Parallel mode, devices report all contact information in a
+ * single packet. Each physical contact is represented by a logical
+ * collection that is embedded in the top-level collection."
+ *
+ * Since additional contacts that were not present will still be in the
+ * report with contactid=0 but contactids are zero-based, find
+ * contactcount first.
+ */
+ cont_count = hid_get_udata(buf, len, &sc->cont_count_loc);
+ /*
+ * "In Hybrid mode, the number of contacts that can be reported in one
+ * report is less than the maximum number of contacts that the device
+ * supports. For example, a device that supports a maximum of
+ * 4 concurrent physical contacts, can set up its top-level collection
+ * to deliver a maximum of two contacts in one report. If four contact
+ * points are present, the device can break these up into two serial
+ * reports that deliver two contacts each.
+ *
+ * "When a device delivers data in this manner, the Contact Count usage
+ * value in the first report should reflect the total number of
+ * contacts that are being delivered in the hybrid reports. The other
+ * serial reports should have a contact count of zero (0)."
+ */
+ if (cont_count != 0)
+ sc->nconts_todo = cont_count;
+
+#ifdef USB_DEBUG
+ DPRINTFN(6, "cont_count:%2u", (unsigned)cont_count);
+ if (wmt_debug >= 6) {
+ WMT_FOREACH_USAGE(sc->caps, usage) {
+ if (wmt_hid_map[usage].usage != WMT_NO_USAGE)
+ printf(" %-4s", wmt_hid_map[usage].name);
+ }
+ printf("\n");
+ }
+#endif
+
+ /* Find the number of contacts reported in current report */
+ cont_count = MIN(sc->nconts_todo, sc->nconts_per_report);
+
+ /* Use protocol Type B for reporting events */
+ for (cont = 0; cont < cont_count; cont++) {
+ slot_data = &sc->slot_data;
+ bzero(slot_data, sizeof(sc->slot_data));
+ WMT_FOREACH_USAGE(sc->caps, usage) {
+ if (sc->locs[cont][usage].size > 0)
+ slot_data->val[usage] = hid_get_udata(
+ buf, len, &sc->locs[cont][usage]);
+ }
+
+ slot = evdev_mt_id_to_slot(sc->evdev, slot_data->id);
+
+#ifdef USB_DEBUG
+ DPRINTFN(6, "cont%01x: data = ", cont);
+ if (wmt_debug >= 6) {
+ WMT_FOREACH_USAGE(sc->caps, usage) {
+ if (wmt_hid_map[usage].usage != WMT_NO_USAGE)
+ printf("%04x ", slot_data->val[usage]);
+ }
+ printf("slot = %d\n", slot);
+ }
+#endif
+
+ if (slot == -1) {
+ DPRINTF("Slot overflow for contact_id %u\n",
+ (unsigned)slot_data->id);
+ continue;
+ }
+
+ if (slot_data->val[WMT_TIP_SWITCH] != 0 &&
+ !(isset(sc->caps, WMT_CONFIDENCE) &&
+ slot_data->val[WMT_CONFIDENCE] == 0)) {
+ /* This finger is in proximity of the sensor */
+ sc->touch = true;
+ slot_data->dist = !slot_data->val[WMT_IN_RANGE];
+ /* Divided by two to match visual scale of touch */
+ width = slot_data->val[WMT_WIDTH] >> 1;
+ height = slot_data->val[WMT_HEIGHT] >> 1;
+ slot_data->ori = width > height;
+ slot_data->maj = MAX(width, height);
+ slot_data->min = MIN(width, height);
+ } else
+ slot_data = NULL;
+
+ evdev_mt_push_slot(sc->evdev, slot, slot_data);
+ }
+
+ sc->nconts_todo -= cont_count;
+ if (sc->do_timestamps && sc->nconts_todo == 0) {
+ /* HUD_SCAN_TIME is measured in 100us, convert to us. */
+ scan_time = hid_get_udata(buf, len, &sc->scan_time_loc);
+ if (sc->prev_touch) {
+ delta = scan_time - sc->scan_time;
+ if (delta < 0)
+ delta += sc->scan_time_max;
+ } else
+ delta = 0;
+ sc->scan_time = scan_time;
+ sc->timestamp += delta * 100;
+ evdev_push_msc(sc->evdev, MSC_TIMESTAMP, sc->timestamp);
+ sc->prev_touch = sc->touch;
+ sc->touch = false;
+ if (!sc->prev_touch)
+ sc->timestamp = 0;
+ }
+ if (sc->nconts_todo == 0) {
+ /* Report both the click and external left btns as BTN_LEFT */
+ if (sc->has_int_button)
+ int_btn = hid_get_data(buf, len, &sc->int_btn_loc);
+ if (isset(sc->buttons, 0))
+ left_btn = hid_get_data(buf, len, &sc->btn_loc[0]);
+ if (sc->has_int_button || isset(sc->buttons, 0))
+ evdev_push_key(sc->evdev, BTN_LEFT,
+ (int_btn != 0) | (left_btn != 0));
+ for (btn = 1; btn < sc->max_button; ++btn) {
+ if (isset(sc->buttons, btn))
+ evdev_push_key(sc->evdev, BTN_MOUSE + btn,
+ hid_get_data(buf,
+ len,
+ &sc->btn_loc[btn]) != 0);
+ }
+ evdev_sync(sc->evdev);
+ }
+}
+
+static void
+wmt_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct wmt_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint8_t *buf = sc->buf;
+ int len;
+
+ usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+
+ DPRINTFN(6, "sc=%p actlen=%d\n", sc, len);
+
+ if (len >= (int)sc->report_len ||
+ (len > 0 && sc->report_id != 0)) {
+ /* Limit report length to the maximum */
+ if (len > (int)sc->report_len)
+ len = sc->report_len;
+
+ usbd_copy_out(pc, 0, buf, len);
+
+ /* Ignore irrelevant reports */
+ if (sc->report_id && *buf != sc->report_id)
+ goto tr_ignore;
+
+ /* Make sure we don't process old data */
+ if (len < sc->report_len)
+ bzero(buf + len, sc->report_len - len);
+
+ /* Strip leading "report ID" byte */
+ if (sc->report_id) {
+ len--;
+ buf++;
+ }
+
+ wmt_process_report(sc, buf, len);
+ } else {
+tr_ignore:
+ DPRINTF("Ignored transfer, %d bytes\n", len);
+ }
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, sc->isize);
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ if (error != USB_ERR_CANCELLED) {
+ /* Try clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+wmt_ev_close_11(struct evdev_dev *evdev, void *ev_softc)
+{
+ struct wmt_softc *sc = ev_softc;
+
+ mtx_assert(&sc->mtx, MA_OWNED);
+ usbd_transfer_stop(sc->xfer[WMT_INTR_DT]);
+}
+
+static int
+wmt_ev_open_11(struct evdev_dev *evdev, void *ev_softc)
+{
+ struct wmt_softc *sc = ev_softc;
+
+ mtx_assert(&sc->mtx, MA_OWNED);
+ usbd_transfer_start(sc->xfer[WMT_INTR_DT]);
+
+ return (0);
+}
+
+static int
+wmt_ev_close(struct evdev_dev *evdev)
+{
+ struct wmt_softc *sc = evdev_get_softc(evdev);
+
+ wmt_ev_close_11(evdev, sc);
+
+ return (0);
+}
+
+static int
+wmt_ev_open(struct evdev_dev *evdev)
+{
+ struct wmt_softc *sc = evdev_get_softc(evdev);
+
+ return (wmt_ev_open_11(evdev, sc));
+
+}
+
+static enum wmt_type
+wmt_hid_parse(struct wmt_softc *sc, const void *d_ptr, uint16_t d_len)
+{
+ struct hid_item hi;
+ struct hid_data *hd;
+ size_t i;
+ size_t cont = 0;
+ enum wmt_type type = WMT_TYPE_UNSUPPORTED;
+ uint32_t left_btn, btn;
+ int32_t cont_count_max = 0;
+ uint8_t report_id = 0;
+ bool touch_coll = false;
+ bool finger_coll = false;
+ bool cont_count_found = false;
+ bool scan_time_found = false;
+ bool has_int_button = false;
+
+#define WMT_HI_ABSOLUTE(hi) \
+ (((hi).flags & (HIO_CONST|HIO_VARIABLE|HIO_RELATIVE)) == HIO_VARIABLE)
+#define HUMS_THQA_CERT 0xC5
+
+ /* Parse features for maximum contact count */
+ hd = hid_start_parse(d_ptr, d_len, 1 << hid_feature);
+ while (hid_get_item(hd, &hi)) {
+ switch (hi.kind) {
+ case hid_collection:
+ if (hi.collevel == 1 && hi.usage ==
+ HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN)) {
+ touch_coll = true;
+ type = WMT_TYPE_TOUCHSCREEN;
+ left_btn = 1;
+ break;
+ }
+ if (hi.collevel == 1 && hi.usage ==
+ HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHPAD)) {
+ touch_coll = true;
+ type = WMT_TYPE_TOUCHPAD;
+ left_btn = 2;
+ }
+ break;
+ case hid_endcollection:
+ if (hi.collevel == 0 && touch_coll)
+ touch_coll = false;
+ break;
+ case hid_feature:
+ if (hi.collevel == 1 && touch_coll && hi.usage ==
+ HID_USAGE2(HUP_MICROSOFT, HUMS_THQA_CERT)) {
+ sc->thqa_cert_rid = hi.report_ID;
+ break;
+ }
+ if (hi.collevel == 1 && touch_coll && hi.usage ==
+ HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACT_MAX)) {
+ cont_count_max = hi.logical_maximum;
+ sc->cont_max_rid = hi.report_ID;
+ sc->cont_max_loc = hi.loc;
+ break;
+ }
+ if (hi.collevel == 1 && touch_coll && hi.usage ==
+ HID_USAGE2(HUP_DIGITIZERS, HUD_BUTTON_TYPE)) {
+ sc->btn_type_rid = hi.report_ID;
+ sc->btn_type_loc = hi.loc;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ hid_end_parse(hd);
+
+ if (type == WMT_TYPE_UNSUPPORTED)
+ return (WMT_TYPE_UNSUPPORTED);
+ /* Maximum contact count is required usage */
+ if (sc->cont_max_rid == 0)
+ return (WMT_TYPE_UNSUPPORTED);
+
+ touch_coll = false;
+
+ /* Parse input for other parameters */
+ hd = hid_start_parse(d_ptr, d_len, 1 << hid_input);
+ while (hid_get_item(hd, &hi)) {
+ switch (hi.kind) {
+ case hid_collection:
+ if (hi.collevel == 1 && hi.usage ==
+ HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN))
+ touch_coll = true;
+ else if (touch_coll && hi.collevel == 2 &&
+ (report_id == 0 || report_id == hi.report_ID) &&
+ hi.usage == HID_USAGE2(HUP_DIGITIZERS, HUD_FINGER))
+ finger_coll = true;
+ break;
+ case hid_endcollection:
+ if (hi.collevel == 1 && finger_coll) {
+ finger_coll = false;
+ cont++;
+ } else if (hi.collevel == 0 && touch_coll)
+ touch_coll = false;
+ break;
+ case hid_input:
+ /*
+ * Ensure that all usages are located within the same
+ * report and proper collection.
+ */
+ if (WMT_HI_ABSOLUTE(hi) && touch_coll &&
+ (report_id == 0 || report_id == hi.report_ID))
+ report_id = hi.report_ID;
+ else
+ break;
+
+ if (hi.collevel == 1 && left_btn == 2 &&
+ hi.usage == HID_USAGE2(HUP_BUTTON, 1)) {
+ has_int_button = true;
+ sc->int_btn_loc = hi.loc;
+ break;
+ }
+ if (hi.collevel == 1 &&
+ hi.usage >= HID_USAGE2(HUP_BUTTON, left_btn) &&
+ hi.usage <= HID_USAGE2(HUP_BUTTON, WMT_BTN_MAX)) {
+ btn = (hi.usage & 0xFFFF) - left_btn;
+ setbit(sc->buttons, btn);
+ sc->btn_loc[btn] = hi.loc;
+ if (btn >= sc->max_button)
+ sc->max_button = btn + 1;
+ break;
+ }
+ if (hi.collevel == 1 && hi.usage ==
+ HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT)) {
+ cont_count_found = true;
+ sc->cont_count_loc = hi.loc;
+ break;
+ }
+ /* Scan time is required but clobbered by evdev */
+ if (hi.collevel == 1 && hi.usage ==
+ HID_USAGE2(HUP_DIGITIZERS, HUD_SCAN_TIME)) {
+ scan_time_found = true;
+ sc->scan_time_loc = hi.loc;
+ sc->scan_time_max = hi.logical_maximum;
+ break;
+ }
+
+ if (!finger_coll || hi.collevel != 2)
+ break;
+ if (cont >= MAX_MT_SLOTS) {
+ DPRINTF("Finger %zu ignored\n", cont);
+ break;
+ }
+
+ for (i = 0; i < WMT_N_USAGES; i++) {
+ if (hi.usage == wmt_hid_map[i].usage) {
+ /*
+ * HUG_X usage is an array mapped to
+ * both ABS_MT_POSITION and ABS_MT_TOOL
+ * events. So don`t stop search if we
+ * already have HUG_X mapping done.
+ */
+ if (sc->locs[cont][i].size)
+ continue;
+ sc->locs[cont][i] = hi.loc;
+ /*
+ * Hid parser returns valid logical and
+ * physical sizes for first finger only
+ * at least on ElanTS 0x04f3:0x0012.
+ */
+ if (cont > 0)
+ break;
+ setbit(sc->caps, i);
+ sc->ai[i] = (struct wmt_absinfo) {
+ .max = hi.logical_maximum,
+ .min = hi.logical_minimum,
+ .res = hid_item_resolution(&hi),
+ };
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ hid_end_parse(hd);
+
+ /* Check for required HID Usages */
+ if (!cont_count_found || !scan_time_found || cont == 0)
+ return (WMT_TYPE_UNSUPPORTED);
+ for (i = 0; i < WMT_N_USAGES; i++) {
+ if (wmt_hid_map[i].required && isclr(sc->caps, i))
+ return (WMT_TYPE_UNSUPPORTED);
+ }
+
+ /* Touchpads must have at least one button */
+ if (type == WMT_TYPE_TOUCHPAD && !sc->max_button && !has_int_button)
+ return (WMT_TYPE_UNSUPPORTED);
+
+ /*
+ * According to specifications 'Contact Count Maximum' should be read
+ * from Feature Report rather than from HID descriptor. Set sane
+ * default value now to handle the case of 'Get Report' request failure
+ */
+ if (cont_count_max < 1)
+ cont_count_max = cont;
+
+ /* Report touch orientation if both width and height are supported */
+ if (isset(sc->caps, WMT_WIDTH) && isset(sc->caps, WMT_HEIGHT)) {
+ setbit(sc->caps, WMT_ORIENTATION);
+ sc->ai[WMT_ORIENTATION].max = 1;
+ }
+
+ sc->isize = hid_report_size_max(d_ptr, d_len, hid_input, NULL);
+ sc->report_len = hid_report_size(d_ptr, d_len, hid_input,
+ report_id);
+ sc->cont_max_rlen = hid_report_size(d_ptr, d_len, hid_feature,
+ sc->cont_max_rid);
+ if (sc->btn_type_rid > 0)
+ sc->btn_type_rlen = hid_report_size(d_ptr, d_len,
+ hid_feature, sc->btn_type_rid);
+ if (sc->thqa_cert_rid > 0)
+ sc->thqa_cert_rlen = hid_report_size(d_ptr, d_len,
+ hid_feature, sc->thqa_cert_rid);
+
+ sc->report_id = report_id;
+ sc->nconts_per_report = cont;
+ sc->has_int_button = has_int_button;
+ sc->cont_count_max = cont_count_max;
+
+ return (type);
+}
+
+static int
+wmt_set_input_mode(struct wmt_softc *sc, enum wmt_input_mode mode)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(sc->dev);
+ int err;
+
+ if (sc->input_mode_rlen < 3 || sc->input_mode_rlen > WMT_BSIZE) {
+ DPRINTF("Feature report %hhu size invalid or too large: %u\n",
+ sc->input_mode_rid, sc->input_mode_rlen);
+ return (USB_ERR_BAD_BUFSIZE);
+ }
+
+ /* Input Mode report is not strictly required to be readable */
+ err = usbd_req_get_report(uaa->device, NULL, sc->buf,
+ sc->input_mode_rlen, uaa->info.bIfaceIndex,
+ UHID_FEATURE_REPORT, sc->input_mode_rid);
+ if (err != USB_ERR_NORMAL_COMPLETION)
+ bzero(sc->buf + 1, sc->input_mode_rlen - 1);
+
+ sc->buf[0] = sc->input_mode_rid;
+ hid_put_udata(sc->buf + 1, sc->input_mode_rlen - 1,
+ &sc->input_mode_loc, mode);
+ err = usbd_req_set_report(uaa->device, NULL, sc->buf,
+ sc->input_mode_rlen, uaa->info.bIfaceIndex,
+ UHID_FEATURE_REPORT, sc->input_mode_rid);
+
+ return (err);
+}
+
+static const STRUCT_USB_HOST_ID wmt_devs[] = {
+ /* generic HID class w/o boot interface */
+ {USB_IFACE_CLASS(UICLASS_HID),
+ USB_IFACE_SUBCLASS(0),},
+};
+
+static device_method_t wmt_methods[] = {
+ DEVMETHOD(device_probe, wmt_probe),
+ DEVMETHOD(device_attach, wmt_attach),
+ DEVMETHOD(device_detach, wmt_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t wmt_driver = {
+ .name = "wmt",
+ .methods = wmt_methods,
+ .size = sizeof(struct wmt_softc),
+};
+
+DRIVER_MODULE(wmt, uhub, wmt_driver, NULL, NULL);
+MODULE_DEPEND(wmt, usb, 1, 1, 1);
+MODULE_DEPEND(wmt, hid, 1, 1, 1);
+MODULE_DEPEND(wmt, evdev, 1, 1, 1);
+MODULE_VERSION(wmt, 1);
+USB_PNP_HOST_INFO(wmt_devs);
diff --git a/sys/dev/usb/input/wsp.c b/sys/dev/usb/input/wsp.c
new file mode 100644
index 000000000000..f78d64f69c08
--- /dev/null
+++ b/sys/dev/usb/input/wsp.c
@@ -0,0 +1,1662 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012 Huang Wen Hui
+ * 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 "opt_evdev.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/file.h>
+#include <sys/selinfo.h>
+#include <sys/poll.h>
+#include <sys/sysctl.h>
+
+#include <dev/hid/hid.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR wsp_debug
+#include <dev/usb/usb_debug.h>
+
+#ifdef EVDEV_SUPPORT
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+#endif
+
+#include <sys/mouse.h>
+
+#define WSP_DRIVER_NAME "wsp"
+#define WSP_BUFFER_MAX 1024
+
+#define WSP_CLAMP(x,low,high) do { \
+ if ((x) < (low)) \
+ (x) = (low); \
+ else if ((x) > (high)) \
+ (x) = (high); \
+} while (0)
+
+/* Tunables */
+static SYSCTL_NODE(_hw_usb, OID_AUTO, wsp, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB wsp");
+
+#ifdef USB_DEBUG
+enum wsp_log_level {
+ WSP_LLEVEL_DISABLED = 0,
+ WSP_LLEVEL_ERROR,
+ WSP_LLEVEL_DEBUG, /* for troubleshooting */
+ WSP_LLEVEL_INFO, /* for diagnostics */
+};
+static int wsp_debug = WSP_LLEVEL_ERROR;/* the default is to only log errors */
+
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &wsp_debug, WSP_LLEVEL_ERROR, "WSP debug level (0-3)");
+#endif /* USB_DEBUG */
+
+static struct wsp_tuning {
+ int scale_factor;
+ int scroll_finger_count;
+ int horizontal_swipe_finger_count;
+ int z_factor;
+ int z_invert;
+ int t_factor;
+ int t_invert;
+ int pressure_touch_threshold;
+ int pressure_untouch_threshold;
+ int pressure_tap_threshold;
+ int scr_threshold;
+ int max_finger_diameter;
+ int max_scroll_finger_distance;
+ int max_double_tap_distance;
+ int enable_single_tap_clicks;
+ int enable_single_tap_movement;
+}
+ wsp_tuning =
+{
+ .scale_factor = 12,
+ .scroll_finger_count = 2,
+ .horizontal_swipe_finger_count = 3,
+ .z_factor = 5,
+ .z_invert = 0,
+ .t_factor = 0,
+ .t_invert = 0,
+ .pressure_touch_threshold = 50,
+ .pressure_untouch_threshold = 10,
+ .pressure_tap_threshold = 120,
+ .scr_threshold = 20,
+ .max_finger_diameter = 1900,
+ .max_scroll_finger_distance = 8192,
+ .max_double_tap_distance = 2500,
+ .enable_single_tap_clicks = 1,
+ .enable_single_tap_movement = 1,
+};
+
+static void
+wsp_running_rangecheck(struct wsp_tuning *ptun)
+{
+ WSP_CLAMP(ptun->scale_factor, 1, 63);
+ WSP_CLAMP(ptun->scroll_finger_count, 0, 3);
+ WSP_CLAMP(ptun->horizontal_swipe_finger_count, 0, 3);
+ WSP_CLAMP(ptun->z_factor, 0, 63);
+ WSP_CLAMP(ptun->z_invert, 0, 1);
+ WSP_CLAMP(ptun->t_factor, 0, 63);
+ WSP_CLAMP(ptun->t_invert, 0, 1);
+ WSP_CLAMP(ptun->pressure_touch_threshold, 1, 255);
+ WSP_CLAMP(ptun->pressure_untouch_threshold, 1, 255);
+ WSP_CLAMP(ptun->pressure_tap_threshold, 1, 255);
+ WSP_CLAMP(ptun->max_finger_diameter, 1, 2400);
+ WSP_CLAMP(ptun->max_scroll_finger_distance, 1, 16384);
+ WSP_CLAMP(ptun->max_double_tap_distance, 1, 16384);
+ WSP_CLAMP(ptun->scr_threshold, 1, 255);
+ WSP_CLAMP(ptun->enable_single_tap_clicks, 0, 1);
+ WSP_CLAMP(ptun->enable_single_tap_movement, 0, 1);
+}
+
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, scale_factor, CTLFLAG_RWTUN,
+ &wsp_tuning.scale_factor, 0, "movement scale factor");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, scroll_finger_count, CTLFLAG_RWTUN,
+ &wsp_tuning.scroll_finger_count, 0, "amount of fingers to use scrolling gesture");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, horizontal_swipe_finger_count, CTLFLAG_RWTUN,
+ &wsp_tuning.horizontal_swipe_finger_count, 0, "amount of fingers to use horizontal swipe gesture");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, z_factor, CTLFLAG_RWTUN,
+ &wsp_tuning.z_factor, 0, "Z-axis (vertical) scale factor");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, z_invert, CTLFLAG_RWTUN,
+ &wsp_tuning.z_invert, 0, "enable (vertical) Z-axis inversion");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, t_factor, CTLFLAG_RWTUN,
+ &wsp_tuning.t_factor, 0, "T-axis (horizontal) scale factor");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, t_invert, CTLFLAG_RWTUN,
+ &wsp_tuning.t_invert, 0, "enable T-axis (horizontal) inversion");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, pressure_touch_threshold, CTLFLAG_RWTUN,
+ &wsp_tuning.pressure_touch_threshold, 0, "touch pressure threshold");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, pressure_untouch_threshold, CTLFLAG_RWTUN,
+ &wsp_tuning.pressure_untouch_threshold, 0, "untouch pressure threshold");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, pressure_tap_threshold, CTLFLAG_RWTUN,
+ &wsp_tuning.pressure_tap_threshold, 0, "tap pressure threshold");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, max_finger_diameter, CTLFLAG_RWTUN,
+ &wsp_tuning.max_finger_diameter, 0, "maximum finger diameter");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, max_scroll_finger_distance, CTLFLAG_RWTUN,
+ &wsp_tuning.max_scroll_finger_distance, 0, "maximum scroll finger distance");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, max_double_tap_distance, CTLFLAG_RWTUN,
+ &wsp_tuning.max_double_tap_distance, 0, "maximum double-finger click distance");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, scr_threshold, CTLFLAG_RWTUN,
+ &wsp_tuning.scr_threshold, 0, "scrolling threshold");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, enable_single_tap_clicks, CTLFLAG_RWTUN,
+ &wsp_tuning.enable_single_tap_clicks, 0, "enable single tap clicks");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, enable_single_tap_movement, CTLFLAG_RWTUN,
+ &wsp_tuning.enable_single_tap_movement, 0, "enable single tap movement");
+
+
+/*
+ * Some tables, structures, definitions and constant values for the
+ * touchpad protocol has been copied from Linux's
+ * "drivers/input/mouse/bcm5974.c" which has the following copyright
+ * holders under GPLv2. All device specific code in this driver has
+ * been written from scratch. The decoding algorithm is based on
+ * output from FreeBSD's usbdump.
+ *
+ * Copyright (C) 2008 Henrik Rydberg (rydberg@euromail.se)
+ * Copyright (C) 2008 Scott Shawcroft (scott.shawcroft@gmail.com)
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2005 Johannes Berg (johannes@sipsolutions.net)
+ * Copyright (C) 2005 Stelian Pop (stelian@popies.net)
+ * Copyright (C) 2005 Frank Arnold (frank@scirocco-5v-turbo.de)
+ * Copyright (C) 2005 Peter Osterlund (petero2@telia.com)
+ * Copyright (C) 2005 Michael Hanselmann (linux-kernel@hansmi.ch)
+ * Copyright (C) 2006 Nicolas Boichat (nicolas@boichat.ch)
+ */
+
+/* button data structure */
+struct bt_data {
+ uint8_t unknown1; /* constant */
+ uint8_t button; /* left button */
+ uint8_t rel_x; /* relative x coordinate */
+ uint8_t rel_y; /* relative y coordinate */
+} __packed;
+
+/* trackpad header types */
+enum tp_type {
+ TYPE1, /* plain trackpad */
+ TYPE2, /* button integrated in trackpad */
+ TYPE3, /* additional header fields since June 2013 */
+ TYPE4, /* additional header field for pressure data */
+ TYPE_CNT
+};
+
+/* trackpad finger data offsets, le16-aligned */
+#define FINGER_TYPE1 (13 * 2)
+#define FINGER_TYPE2 (15 * 2)
+#define FINGER_TYPE3 (19 * 2)
+#define FINGER_TYPE4 (23 * 2)
+
+/* trackpad button data offsets */
+#define BUTTON_TYPE2 15
+#define BUTTON_TYPE3 23
+#define BUTTON_TYPE4 31
+
+/* list of device capability bits */
+#define HAS_INTEGRATED_BUTTON 1
+
+/* trackpad finger data block size */
+#define FSIZE_TYPE1 (14 * 2)
+#define FSIZE_TYPE2 (14 * 2)
+#define FSIZE_TYPE3 (14 * 2)
+#define FSIZE_TYPE4 (15 * 2)
+
+struct wsp_tp {
+ uint8_t caps; /* device capability bitmask */
+ uint8_t button; /* offset to button data */
+ uint8_t offset; /* offset to trackpad finger data */
+ uint8_t fsize; /* bytes in single finger block */
+ uint8_t delta; /* offset from header to finger struct */
+ uint8_t iface_index;
+ uint8_t um_size; /* usb control message length */
+ uint8_t um_req_idx; /* usb control message index */
+ uint8_t um_switch_idx; /* usb control message mode switch index */
+ uint8_t um_switch_on; /* usb control message mode switch on */
+ uint8_t um_switch_off; /* usb control message mode switch off */
+} const static wsp_tp[TYPE_CNT] = {
+ [TYPE1] = {
+ .caps = 0,
+ .button = 0,
+ .offset = FINGER_TYPE1,
+ .fsize = FSIZE_TYPE1,
+ .delta = 0,
+ .iface_index = 0,
+ .um_size = 8,
+ .um_req_idx = 0x00,
+ .um_switch_idx = 0,
+ .um_switch_on = 0x01,
+ .um_switch_off = 0x08,
+ },
+ [TYPE2] = {
+ .caps = HAS_INTEGRATED_BUTTON,
+ .button = BUTTON_TYPE2,
+ .offset = FINGER_TYPE2,
+ .fsize = FSIZE_TYPE2,
+ .delta = 0,
+ .iface_index = 0,
+ .um_size = 8,
+ .um_req_idx = 0x00,
+ .um_switch_idx = 0,
+ .um_switch_on = 0x01,
+ .um_switch_off = 0x08,
+ },
+ [TYPE3] = {
+ .caps = HAS_INTEGRATED_BUTTON,
+ .button = BUTTON_TYPE3,
+ .offset = FINGER_TYPE3,
+ .fsize = FSIZE_TYPE3,
+ .delta = 0,
+ },
+ [TYPE4] = {
+ .caps = HAS_INTEGRATED_BUTTON,
+ .button = BUTTON_TYPE4,
+ .offset = FINGER_TYPE4,
+ .fsize = FSIZE_TYPE4,
+ .delta = 2,
+ .iface_index = 2,
+ .um_size = 2,
+ .um_req_idx = 0x02,
+ .um_switch_idx = 1,
+ .um_switch_on = 0x01,
+ .um_switch_off = 0x00,
+ },
+};
+
+/* trackpad finger header - little endian */
+struct tp_header {
+ uint8_t flag;
+ uint8_t sn0;
+ uint16_t wFixed0;
+ uint32_t dwSn1;
+ uint32_t dwFixed1;
+ uint16_t wLength;
+ uint8_t nfinger;
+ uint8_t ibt;
+ int16_t wUnknown[6];
+ uint8_t q1;
+ uint8_t q2;
+} __packed;
+
+/* trackpad finger structure - little endian */
+struct tp_finger {
+ int16_t origin; /* zero when switching track finger */
+ int16_t abs_x; /* absolute x coodinate */
+ int16_t abs_y; /* absolute y coodinate */
+ int16_t rel_x; /* relative x coodinate */
+ int16_t rel_y; /* relative y coodinate */
+ int16_t tool_major; /* tool area, major axis */
+ int16_t tool_minor; /* tool area, minor axis */
+ int16_t orientation; /* 16384 when point, else 15 bit angle */
+ int16_t touch_major; /* touch area, major axis */
+ int16_t touch_minor; /* touch area, minor axis */
+ int16_t unused[2]; /* zeros */
+ int16_t pressure; /* pressure on forcetouch touchpad */
+ int16_t multi; /* one finger: varies, more fingers:
+ * constant */
+} __packed;
+
+/* trackpad finger data size, empirically at least ten fingers */
+#ifdef EVDEV_SUPPORT
+#define MAX_FINGERS MAX_MT_SLOTS
+#else
+#define MAX_FINGERS 16
+#endif
+#define SIZEOF_FINGER sizeof(struct tp_finger)
+#define SIZEOF_ALL_FINGERS (MAX_FINGERS * SIZEOF_FINGER)
+#define MAX_FINGER_ORIENTATION 16384
+
+#if (WSP_BUFFER_MAX < ((MAX_FINGERS * FSIZE_TYPE4) + FINGER_TYPE4))
+#error "WSP_BUFFER_MAX is too small"
+#endif
+
+enum {
+ WSP_FLAG_WELLSPRING1,
+ WSP_FLAG_WELLSPRING2,
+ WSP_FLAG_WELLSPRING3,
+ WSP_FLAG_WELLSPRING4,
+ WSP_FLAG_WELLSPRING4A,
+ WSP_FLAG_WELLSPRING5,
+ WSP_FLAG_WELLSPRING6A,
+ WSP_FLAG_WELLSPRING6,
+ WSP_FLAG_WELLSPRING5A,
+ WSP_FLAG_WELLSPRING7,
+ WSP_FLAG_WELLSPRING7A,
+ WSP_FLAG_WELLSPRING8,
+ WSP_FLAG_WELLSPRING9,
+ WSP_FLAG_MAX,
+};
+
+/* device-specific parameters */
+struct wsp_param {
+ int snratio; /* signal-to-noise ratio */
+ int min; /* device minimum reading */
+ int max; /* device maximum reading */
+ int size; /* physical size, mm */
+};
+
+/* device-specific configuration */
+struct wsp_dev_params {
+ const struct wsp_tp* tp;
+ struct wsp_param p; /* finger pressure limits */
+ struct wsp_param w; /* finger width limits */
+ struct wsp_param x; /* horizontal limits */
+ struct wsp_param y; /* vertical limits */
+ struct wsp_param o; /* orientation limits */
+};
+
+/* logical signal quality */
+#define SN_PRESSURE 45 /* pressure signal-to-noise ratio */
+#define SN_WIDTH 25 /* width signal-to-noise ratio */
+#define SN_COORD 250 /* coordinate signal-to-noise ratio */
+#define SN_ORIENT 10 /* orientation signal-to-noise ratio */
+
+static const struct wsp_dev_params wsp_dev_params[WSP_FLAG_MAX] = {
+ [WSP_FLAG_WELLSPRING1] = {
+ .tp = wsp_tp + TYPE1,
+ .p = { SN_PRESSURE, 0, 256, 0 },
+ .w = { SN_WIDTH, 0, 2048, 0 },
+ .x = { SN_COORD, -4824, 5342, 105 },
+ .y = { SN_COORD, -172, 5820, 75 },
+ .o = { SN_ORIENT,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
+ },
+ [WSP_FLAG_WELLSPRING2] = {
+ .tp = wsp_tp + TYPE1,
+ .p = { SN_PRESSURE, 0, 256, 0 },
+ .w = { SN_WIDTH, 0, 2048, 0 },
+ .x = { SN_COORD, -4824, 4824, 105 },
+ .y = { SN_COORD, -172, 4290, 75 },
+ .o = { SN_ORIENT,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
+ },
+ [WSP_FLAG_WELLSPRING3] = {
+ .tp = wsp_tp + TYPE2,
+ .p = { SN_PRESSURE, 0, 300, 0 },
+ .w = { SN_WIDTH, 0, 2048, 0 },
+ .x = { SN_COORD, -4460, 5166, 105 },
+ .y = { SN_COORD, -75, 6700, 75 },
+ .o = { SN_ORIENT,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
+ },
+ [WSP_FLAG_WELLSPRING4] = {
+ .tp = wsp_tp + TYPE2,
+ .p = { SN_PRESSURE, 0, 300, 0 },
+ .w = { SN_WIDTH, 0, 2048, 0 },
+ .x = { SN_COORD, -4620, 5140, 105 },
+ .y = { SN_COORD, -150, 6600, 75 },
+ .o = { SN_ORIENT,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
+ },
+ [WSP_FLAG_WELLSPRING4A] = {
+ .tp = wsp_tp + TYPE2,
+ .p = { SN_PRESSURE, 0, 300, 0 },
+ .w = { SN_WIDTH, 0, 2048, 0 },
+ .x = { SN_COORD, -4616, 5112, 105 },
+ .y = { SN_COORD, -142, 5234, 75 },
+ .o = { SN_ORIENT,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
+ },
+ [WSP_FLAG_WELLSPRING5] = {
+ .tp = wsp_tp + TYPE2,
+ .p = { SN_PRESSURE, 0, 300, 0 },
+ .w = { SN_WIDTH, 0, 2048, 0 },
+ .x = { SN_COORD, -4415, 5050, 105 },
+ .y = { SN_COORD, -55, 6680, 75 },
+ .o = { SN_ORIENT,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
+ },
+ [WSP_FLAG_WELLSPRING6] = {
+ .tp = wsp_tp + TYPE2,
+ .p = { SN_PRESSURE, 0, 300, 0 },
+ .w = { SN_WIDTH, 0, 2048, 0 },
+ .x = { SN_COORD, -4620, 5140, 105 },
+ .y = { SN_COORD, -150, 6600, 75 },
+ .o = { SN_ORIENT,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
+ },
+ [WSP_FLAG_WELLSPRING5A] = {
+ .tp = wsp_tp + TYPE2,
+ .p = { SN_PRESSURE, 0, 300, 0 },
+ .w = { SN_WIDTH, 0, 2048, 0 },
+ .x = { SN_COORD, -4750, 5280, 105 },
+ .y = { SN_COORD, -150, 6730, 75 },
+ .o = { SN_ORIENT,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
+ },
+ [WSP_FLAG_WELLSPRING6A] = {
+ .tp = wsp_tp + TYPE2,
+ .p = { SN_PRESSURE, 0, 300, 0 },
+ .w = { SN_WIDTH, 0, 2048, 0 },
+ .x = { SN_COORD, -4620, 5140, 105 },
+ .y = { SN_COORD, -150, 6600, 75 },
+ .o = { SN_ORIENT,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
+ },
+ [WSP_FLAG_WELLSPRING7] = {
+ .tp = wsp_tp + TYPE2,
+ .p = { SN_PRESSURE, 0, 300, 0 },
+ .w = { SN_WIDTH, 0, 2048, 0 },
+ .x = { SN_COORD, -4750, 5280, 105 },
+ .y = { SN_COORD, -150, 6730, 75 },
+ .o = { SN_ORIENT,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
+ },
+ [WSP_FLAG_WELLSPRING7A] = {
+ .tp = wsp_tp + TYPE2,
+ .p = { SN_PRESSURE, 0, 300, 0 },
+ .w = { SN_WIDTH, 0, 2048, 0 },
+ .x = { SN_COORD, -4750, 5280, 105 },
+ .y = { SN_COORD, -150, 6730, 75 },
+ .o = { SN_ORIENT,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
+ },
+ [WSP_FLAG_WELLSPRING8] = {
+ .tp = wsp_tp + TYPE3,
+ .p = { SN_PRESSURE, 0, 300, 0 },
+ .w = { SN_WIDTH, 0, 2048, 0 },
+ .x = { SN_COORD, -4620, 5140, 105 },
+ .y = { SN_COORD, -150, 6600, 75 },
+ .o = { SN_ORIENT,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
+ },
+ [WSP_FLAG_WELLSPRING9] = {
+ .tp = wsp_tp + TYPE4,
+ .p = { SN_PRESSURE, 0, 300, 0 },
+ .w = { SN_WIDTH, 0, 2048, 0 },
+ .x = { SN_COORD, -4828, 5345, 105 },
+ .y = { SN_COORD, -203, 6803, 75 },
+ .o = { SN_ORIENT,
+ -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 },
+ },
+};
+#define WSP_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
+
+static const STRUCT_USB_HOST_ID wsp_devs[] = {
+ /* MacbookAir1.1 */
+ WSP_DEV(APPLE, WELLSPRING_ANSI, WSP_FLAG_WELLSPRING1),
+ WSP_DEV(APPLE, WELLSPRING_ISO, WSP_FLAG_WELLSPRING1),
+ WSP_DEV(APPLE, WELLSPRING_JIS, WSP_FLAG_WELLSPRING1),
+
+ /* MacbookProPenryn, aka wellspring2 */
+ WSP_DEV(APPLE, WELLSPRING2_ANSI, WSP_FLAG_WELLSPRING2),
+ WSP_DEV(APPLE, WELLSPRING2_ISO, WSP_FLAG_WELLSPRING2),
+ WSP_DEV(APPLE, WELLSPRING2_JIS, WSP_FLAG_WELLSPRING2),
+
+ /* Macbook5,1 (unibody), aka wellspring3 */
+ WSP_DEV(APPLE, WELLSPRING3_ANSI, WSP_FLAG_WELLSPRING3),
+ WSP_DEV(APPLE, WELLSPRING3_ISO, WSP_FLAG_WELLSPRING3),
+ WSP_DEV(APPLE, WELLSPRING3_JIS, WSP_FLAG_WELLSPRING3),
+
+ /* MacbookAir3,2 (unibody), aka wellspring4 */
+ WSP_DEV(APPLE, WELLSPRING4_ANSI, WSP_FLAG_WELLSPRING4),
+ WSP_DEV(APPLE, WELLSPRING4_ISO, WSP_FLAG_WELLSPRING4),
+ WSP_DEV(APPLE, WELLSPRING4_JIS, WSP_FLAG_WELLSPRING4),
+
+ /* MacbookAir3,1 (unibody), aka wellspring4 */
+ WSP_DEV(APPLE, WELLSPRING4A_ANSI, WSP_FLAG_WELLSPRING4A),
+ WSP_DEV(APPLE, WELLSPRING4A_ISO, WSP_FLAG_WELLSPRING4A),
+ WSP_DEV(APPLE, WELLSPRING4A_JIS, WSP_FLAG_WELLSPRING4A),
+
+ /* Macbook8 (unibody, March 2011) */
+ WSP_DEV(APPLE, WELLSPRING5_ANSI, WSP_FLAG_WELLSPRING5),
+ WSP_DEV(APPLE, WELLSPRING5_ISO, WSP_FLAG_WELLSPRING5),
+ WSP_DEV(APPLE, WELLSPRING5_JIS, WSP_FLAG_WELLSPRING5),
+
+ /* MacbookAir4,1 (unibody, July 2011) */
+ WSP_DEV(APPLE, WELLSPRING6A_ANSI, WSP_FLAG_WELLSPRING6A),
+ WSP_DEV(APPLE, WELLSPRING6A_ISO, WSP_FLAG_WELLSPRING6A),
+ WSP_DEV(APPLE, WELLSPRING6A_JIS, WSP_FLAG_WELLSPRING6A),
+
+ /* MacbookAir4,2 (unibody, July 2011) */
+ WSP_DEV(APPLE, WELLSPRING6_ANSI, WSP_FLAG_WELLSPRING6),
+ WSP_DEV(APPLE, WELLSPRING6_ISO, WSP_FLAG_WELLSPRING6),
+ WSP_DEV(APPLE, WELLSPRING6_JIS, WSP_FLAG_WELLSPRING6),
+
+ /* Macbook8,2 (unibody) */
+ WSP_DEV(APPLE, WELLSPRING5A_ANSI, WSP_FLAG_WELLSPRING5A),
+ WSP_DEV(APPLE, WELLSPRING5A_ISO, WSP_FLAG_WELLSPRING5A),
+ WSP_DEV(APPLE, WELLSPRING5A_JIS, WSP_FLAG_WELLSPRING5A),
+
+ /* MacbookPro10,1 (unibody, June 2012) */
+ /* MacbookPro11,1-3 (unibody, June 2013) */
+ WSP_DEV(APPLE, WELLSPRING7_ANSI, WSP_FLAG_WELLSPRING7),
+ WSP_DEV(APPLE, WELLSPRING7_ISO, WSP_FLAG_WELLSPRING7),
+ WSP_DEV(APPLE, WELLSPRING7_JIS, WSP_FLAG_WELLSPRING7),
+
+ /* MacbookPro10,2 (unibody, October 2012) */
+ WSP_DEV(APPLE, WELLSPRING7A_ANSI, WSP_FLAG_WELLSPRING7A),
+ WSP_DEV(APPLE, WELLSPRING7A_ISO, WSP_FLAG_WELLSPRING7A),
+ WSP_DEV(APPLE, WELLSPRING7A_JIS, WSP_FLAG_WELLSPRING7A),
+
+ /* MacbookAir6,2 (unibody, June 2013) */
+ WSP_DEV(APPLE, WELLSPRING8_ANSI, WSP_FLAG_WELLSPRING8),
+ WSP_DEV(APPLE, WELLSPRING8_ISO, WSP_FLAG_WELLSPRING8),
+ WSP_DEV(APPLE, WELLSPRING8_JIS, WSP_FLAG_WELLSPRING8),
+
+ /* MacbookPro12,1 MacbookPro11,4 */
+ WSP_DEV(APPLE, WELLSPRING9_ANSI, WSP_FLAG_WELLSPRING9),
+ WSP_DEV(APPLE, WELLSPRING9_ISO, WSP_FLAG_WELLSPRING9),
+ WSP_DEV(APPLE, WELLSPRING9_JIS, WSP_FLAG_WELLSPRING9),
+};
+
+#define WSP_FIFO_BUF_SIZE 8 /* bytes */
+#define WSP_FIFO_QUEUE_MAXLEN 50 /* units */
+
+enum {
+ WSP_INTR_DT,
+ WSP_N_TRANSFER,
+};
+
+struct wsp_softc {
+ struct usb_device *sc_usb_device;
+ struct mtx sc_mutex; /* for synchronization */
+ struct usb_xfer *sc_xfer[WSP_N_TRANSFER];
+ struct usb_fifo_sc sc_fifo;
+
+ const struct wsp_dev_params *sc_params; /* device configuration */
+
+#ifdef EVDEV_SUPPORT
+ struct evdev_dev *sc_evdev;
+#endif
+ mousehw_t sc_hw;
+ mousemode_t sc_mode;
+ u_int sc_pollrate;
+ mousestatus_t sc_status;
+ int sc_fflags;
+ u_int sc_state;
+#define WSP_ENABLED 0x01
+#define WSP_EVDEV_OPENED 0x02
+
+ struct tp_finger *index[MAX_FINGERS]; /* finger index data */
+ int16_t pos_x[MAX_FINGERS]; /* position array */
+ int16_t pos_y[MAX_FINGERS]; /* position array */
+ int16_t pre_pos_x[MAX_FINGERS]; /* previous position array */
+ int16_t pre_pos_y[MAX_FINGERS]; /* previous position array */
+ u_int sc_touch; /* touch status */
+#define WSP_UNTOUCH 0x00
+#define WSP_FIRST_TOUCH 0x01
+#define WSP_SECOND_TOUCH 0x02
+#define WSP_TOUCHING 0x04
+ int dx_sum; /* x axis cumulative movement */
+ int dy_sum; /* y axis cumulative movement */
+ int dz_sum; /* z axis cumulative movement */
+ int dz_count;
+#define WSP_DZ_MAX_COUNT 32
+ int dt_sum; /* T-axis cumulative movement */
+ int rdx; /* x axis remainder of divide by scale_factor */
+ int rdy; /* y axis remainder of divide by scale_factor */
+ int rdz; /* z axis remainder of divide by scale_factor */
+ int tp_datalen;
+ uint8_t o_ntouch; /* old touch finger status */
+ uint8_t finger; /* 0 or 1 *, check which finger moving */
+ uint16_t intr_count;
+#define WSP_TAP_THRESHOLD 3
+#define WSP_TAP_MAX_COUNT 20
+ int distance; /* the distance of 2 fingers */
+ uint8_t ibtn; /* button status in tapping */
+ uint8_t ntaps; /* finger status in tapping */
+ uint8_t scr_mode; /* scroll status in movement */
+#define WSP_SCR_NONE 0
+#define WSP_SCR_VER 1
+#define WSP_SCR_HOR 2
+ uint8_t tp_data[WSP_BUFFER_MAX] __aligned(4); /* trackpad transferred data */
+};
+
+/*
+ * function prototypes
+ */
+static usb_fifo_cmd_t wsp_fifo_start_read;
+static usb_fifo_cmd_t wsp_fifo_stop_read;
+static usb_fifo_open_t wsp_open;
+static usb_fifo_close_t wsp_close;
+static usb_fifo_ioctl_t wsp_ioctl;
+
+static struct usb_fifo_methods wsp_fifo_methods = {
+ .f_open = &wsp_open,
+ .f_close = &wsp_close,
+ .f_ioctl = &wsp_ioctl,
+ .f_start_read = &wsp_fifo_start_read,
+ .f_stop_read = &wsp_fifo_stop_read,
+ .basename[0] = WSP_DRIVER_NAME,
+};
+
+#ifdef EVDEV_SUPPORT
+static evdev_open_t wsp_ev_open;
+static evdev_close_t wsp_ev_close;
+static const struct evdev_methods wsp_evdev_methods = {
+ .ev_open = &wsp_ev_open,
+ .ev_close = &wsp_ev_close,
+};
+#endif
+
+/* device initialization and shutdown */
+static int wsp_enable(struct wsp_softc *sc);
+static void wsp_disable(struct wsp_softc *sc);
+
+/* updating fifo */
+static void wsp_reset_buf(struct wsp_softc *sc);
+static void wsp_add_to_queue(struct wsp_softc *, int, int, int, uint32_t);
+
+/* Device methods. */
+static device_probe_t wsp_probe;
+static device_attach_t wsp_attach;
+static device_detach_t wsp_detach;
+static usb_callback_t wsp_intr_callback;
+
+static const struct usb_config wsp_config[WSP_N_TRANSFER] = {
+ [WSP_INTR_DT] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {
+ .pipe_bof = 0,
+ .short_xfer_ok = 1,
+ },
+ .bufsize = WSP_BUFFER_MAX,
+ .callback = &wsp_intr_callback,
+ },
+};
+
+static usb_error_t
+wsp_set_device_mode(struct wsp_softc *sc, uint8_t on)
+{
+ const struct wsp_dev_params *params = sc->sc_params;
+ uint8_t mode_bytes[8];
+ usb_error_t err;
+
+ /* Type 3 does not require a mode switch */
+ if (params->tp == wsp_tp + TYPE3)
+ return 0;
+
+ err = usbd_req_get_report(sc->sc_usb_device, NULL,
+ mode_bytes, params->tp->um_size, params->tp->iface_index,
+ UHID_FEATURE_REPORT, params->tp->um_req_idx);
+
+ if (err != USB_ERR_NORMAL_COMPLETION) {
+ DPRINTF("Failed to read device mode (%d)\n", err);
+ return (err);
+ }
+
+ /*
+ * XXX Need to wait at least 250ms for hardware to get
+ * ready. The device mode handling appears to be handled
+ * asynchronously and we should not issue these commands too
+ * quickly.
+ */
+ pause("WHW", hz / 4);
+
+ mode_bytes[params->tp->um_switch_idx] =
+ on ? params->tp->um_switch_on : params->tp->um_switch_off;
+
+ return (usbd_req_set_report(sc->sc_usb_device, NULL,
+ mode_bytes, params->tp->um_size, params->tp->iface_index,
+ UHID_FEATURE_REPORT, params->tp->um_req_idx));
+}
+
+static int
+wsp_enable(struct wsp_softc *sc)
+{
+ /* reset status */
+ memset(&sc->sc_status, 0, sizeof(sc->sc_status));
+ sc->sc_state |= WSP_ENABLED;
+
+ DPRINTFN(WSP_LLEVEL_INFO, "enabled wsp\n");
+ return (0);
+}
+
+static void
+wsp_disable(struct wsp_softc *sc)
+{
+ sc->sc_state &= ~WSP_ENABLED;
+ DPRINTFN(WSP_LLEVEL_INFO, "disabled wsp\n");
+}
+
+static int
+wsp_probe(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+ struct usb_interface_descriptor *id;
+ struct usb_interface *iface;
+ uint8_t i;
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ /* figure out first interface matching */
+ for (i = 1;; i++) {
+ iface = usbd_get_iface(uaa->device, i);
+ if (iface == NULL || i == 3)
+ return (ENXIO);
+ id = iface->idesc;
+ if ((id == NULL) ||
+ (id->bInterfaceClass != UICLASS_HID) ||
+ (id->bInterfaceProtocol != 0 &&
+ id->bInterfaceProtocol != UIPROTO_MOUSE))
+ continue;
+ break;
+ }
+ /* check if we are attaching to the first match */
+ if (uaa->info.bIfaceIndex != i)
+ return (ENXIO);
+ if (usbd_lookup_id_by_uaa(wsp_devs, sizeof(wsp_devs), uaa) != 0)
+ return (ENXIO);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+wsp_attach(device_t dev)
+{
+ struct wsp_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ usb_error_t err;
+ void *d_ptr = NULL;
+ uint16_t d_len;
+
+ DPRINTFN(WSP_LLEVEL_INFO, "sc=%p\n", sc);
+
+ /* Get HID descriptor */
+ err = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr,
+ &d_len, M_TEMP, uaa->info.bIfaceIndex);
+
+ if (err == USB_ERR_NORMAL_COMPLETION) {
+ /* Get HID report descriptor length */
+ sc->tp_datalen = hid_report_size_max(d_ptr, d_len, hid_input,
+ NULL);
+ free(d_ptr, M_TEMP);
+
+ if (sc->tp_datalen <= 0 || sc->tp_datalen > WSP_BUFFER_MAX) {
+ DPRINTF("Invalid datalength or too big "
+ "datalength: %d\n", sc->tp_datalen);
+ return (ENXIO);
+ }
+ } else {
+ return (ENXIO);
+ }
+
+ sc->sc_usb_device = uaa->device;
+
+ /* get device specific configuration */
+ sc->sc_params = wsp_dev_params + USB_GET_DRIVER_INFO(uaa);
+
+ /*
+ * By default the touchpad behaves like a HID device, sending
+ * packets with reportID = 8. Such reports contain only
+ * limited information. They encode movement deltas and button
+ * events, but do not include data from the pressure
+ * sensors. The device input mode can be switched from HID
+ * reports to raw sensor data using vendor-specific USB
+ * control commands:
+ */
+
+ /*
+ * During re-enumeration of the device we need to force the
+ * device back into HID mode before switching it to RAW
+ * mode. Else the device does not work like expected.
+ */
+ err = wsp_set_device_mode(sc, 0);
+ if (err != USB_ERR_NORMAL_COMPLETION) {
+ DPRINTF("Failed to set mode to HID MODE (%d)\n", err);
+ return (ENXIO);
+ }
+
+ err = wsp_set_device_mode(sc, 1);
+ if (err != USB_ERR_NORMAL_COMPLETION) {
+ DPRINTF("failed to set mode to RAW MODE (%d)\n", err);
+ return (ENXIO);
+ }
+
+ mtx_init(&sc->sc_mutex, "wspmtx", NULL, MTX_DEF | MTX_RECURSE);
+
+ err = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer, wsp_config,
+ WSP_N_TRANSFER, sc, &sc->sc_mutex);
+ if (err) {
+ DPRINTF("error=%s\n", usbd_errstr(err));
+ goto detach;
+ }
+ if (usb_fifo_attach(sc->sc_usb_device, sc, &sc->sc_mutex,
+ &wsp_fifo_methods, &sc->sc_fifo,
+ device_get_unit(dev), -1, uaa->info.bIfaceIndex,
+ UID_ROOT, GID_OPERATOR, 0644)) {
+ goto detach;
+ }
+ device_set_usb_desc(dev);
+
+ sc->sc_hw.buttons = 3;
+ sc->sc_hw.iftype = MOUSE_IF_USB;
+ sc->sc_hw.type = MOUSE_PAD;
+ sc->sc_hw.model = MOUSE_MODEL_GENERIC;
+ sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+ sc->sc_mode.rate = -1;
+ sc->sc_mode.resolution = MOUSE_RES_UNKNOWN;
+ sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+
+ sc->sc_touch = WSP_UNTOUCH;
+ sc->scr_mode = WSP_SCR_NONE;
+
+#ifdef EVDEV_SUPPORT
+ sc->sc_evdev = evdev_alloc();
+ evdev_set_name(sc->sc_evdev, device_get_desc(dev));
+ evdev_set_phys(sc->sc_evdev, device_get_nameunit(dev));
+ evdev_set_id(sc->sc_evdev, BUS_USB, uaa->info.idVendor,
+ uaa->info.idProduct, 0);
+ evdev_set_serial(sc->sc_evdev, usb_get_serial(uaa->device));
+ evdev_set_methods(sc->sc_evdev, sc, &wsp_evdev_methods);
+ evdev_support_prop(sc->sc_evdev, INPUT_PROP_POINTER);
+ evdev_support_event(sc->sc_evdev, EV_SYN);
+ evdev_support_event(sc->sc_evdev, EV_ABS);
+ evdev_support_event(sc->sc_evdev, EV_KEY);
+
+#define WSP_SUPPORT_ABS(evdev, code, param) \
+ evdev_support_abs((evdev), (code), (param).min, (param).max, \
+ ((param).max - (param).min) / (param).snratio, 0, \
+ (param).size != 0 ? ((param).max - (param).min) / (param).size : 0);
+
+ /* finger position */
+ WSP_SUPPORT_ABS(sc->sc_evdev, ABS_MT_POSITION_X, sc->sc_params->x);
+ WSP_SUPPORT_ABS(sc->sc_evdev, ABS_MT_POSITION_Y, sc->sc_params->y);
+ /* finger pressure */
+ WSP_SUPPORT_ABS(sc->sc_evdev, ABS_MT_PRESSURE, sc->sc_params->p);
+ /* finger major/minor axis */
+ WSP_SUPPORT_ABS(sc->sc_evdev, ABS_MT_TOUCH_MAJOR, sc->sc_params->w);
+ WSP_SUPPORT_ABS(sc->sc_evdev, ABS_MT_TOUCH_MINOR, sc->sc_params->w);
+ /* finger major/minor approach */
+ WSP_SUPPORT_ABS(sc->sc_evdev, ABS_MT_WIDTH_MAJOR, sc->sc_params->w);
+ WSP_SUPPORT_ABS(sc->sc_evdev, ABS_MT_WIDTH_MINOR, sc->sc_params->w);
+ /* finger orientation */
+ WSP_SUPPORT_ABS(sc->sc_evdev, ABS_MT_ORIENTATION, sc->sc_params->o);
+ /* button properties */
+ evdev_support_key(sc->sc_evdev, BTN_LEFT);
+ if ((sc->sc_params->tp->caps & HAS_INTEGRATED_BUTTON) != 0)
+ evdev_support_prop(sc->sc_evdev, INPUT_PROP_BUTTONPAD);
+ /* Enable automatic touch assignment for type B MT protocol */
+ evdev_support_abs(sc->sc_evdev, ABS_MT_SLOT,
+ 0, MAX_FINGERS - 1, 0, 0, 0);
+ evdev_support_abs(sc->sc_evdev, ABS_MT_TRACKING_ID,
+ -1, MAX_FINGERS - 1, 0, 0, 0);
+ evdev_set_flag(sc->sc_evdev, EVDEV_FLAG_MT_TRACK);
+ evdev_set_flag(sc->sc_evdev, EVDEV_FLAG_MT_AUTOREL);
+ /* Synaptics compatibility events */
+ evdev_set_flag(sc->sc_evdev, EVDEV_FLAG_MT_STCOMPAT);
+
+ err = evdev_register(sc->sc_evdev);
+ if (err)
+ goto detach;
+#endif
+
+ return (0);
+
+detach:
+ wsp_detach(dev);
+ return (ENOMEM);
+}
+
+static int
+wsp_detach(device_t dev)
+{
+ struct wsp_softc *sc = device_get_softc(dev);
+
+ (void) wsp_set_device_mode(sc, 0);
+
+ mtx_lock(&sc->sc_mutex);
+ if (sc->sc_state & WSP_ENABLED)
+ wsp_disable(sc);
+ mtx_unlock(&sc->sc_mutex);
+
+ usb_fifo_detach(&sc->sc_fifo);
+
+#ifdef EVDEV_SUPPORT
+ evdev_free(sc->sc_evdev);
+#endif
+
+ usbd_transfer_unsetup(sc->sc_xfer, WSP_N_TRANSFER);
+
+ mtx_destroy(&sc->sc_mutex);
+
+ return (0);
+}
+
+static void
+wsp_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct wsp_softc *sc = usbd_xfer_softc(xfer);
+ const struct wsp_dev_params *params = sc->sc_params;
+ struct usb_page_cache *pc;
+ struct tp_finger *f;
+ struct wsp_tuning tun = wsp_tuning;
+ int ntouch = 0; /* the finger number in touch */
+ int ibt = 0; /* button status */
+ int dx = 0;
+ int dy = 0;
+ int dz = 0;
+ int rdx = 0;
+ int rdy = 0;
+ int rdz = 0;
+ int len;
+ int i;
+#ifdef EVDEV_SUPPORT
+ int slot = 0;
+#endif
+
+ wsp_running_rangecheck(&tun);
+
+ if (sc->dz_count == 0)
+ sc->dz_count = WSP_DZ_MAX_COUNT;
+
+ usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ /* copy out received data */
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, sc->tp_data, len);
+
+ if ((len < params->tp->offset + params->tp->fsize) ||
+ ((len - params->tp->offset) % params->tp->fsize) != 0) {
+ DPRINTFN(WSP_LLEVEL_INFO, "Invalid length: %d, %x, %x\n",
+ len, sc->tp_data[0], sc->tp_data[1]);
+ goto tr_setup;
+ }
+
+ if (len < sc->tp_datalen) {
+ /* make sure we don't process old data */
+ memset(sc->tp_data + len, 0, sc->tp_datalen - len);
+ }
+
+ if (params->tp != wsp_tp + TYPE1) {
+ ibt = sc->tp_data[params->tp->button];
+ ntouch = sc->tp_data[params->tp->button - 1];
+ } else
+ ntouch = (len - params->tp->offset) / params->tp->fsize;
+
+ /* range check */
+ if (ntouch < 0)
+ ntouch = 0;
+ else if (ntouch > MAX_FINGERS)
+ ntouch = MAX_FINGERS;
+
+ for (i = 0; i != ntouch; i++) {
+ f = (struct tp_finger *)(sc->tp_data + params->tp->offset + params->tp->delta + i * params->tp->fsize);
+ /* swap endianness, if any */
+ if (le16toh(0x1234) != 0x1234) {
+ f->origin = le16toh((uint16_t)f->origin);
+ f->abs_x = le16toh((uint16_t)f->abs_x);
+ f->abs_y = le16toh((uint16_t)f->abs_y);
+ f->rel_x = le16toh((uint16_t)f->rel_x);
+ f->rel_y = le16toh((uint16_t)f->rel_y);
+ f->tool_major = le16toh((uint16_t)f->tool_major);
+ f->tool_minor = le16toh((uint16_t)f->tool_minor);
+ f->orientation = le16toh((uint16_t)f->orientation);
+ f->touch_major = le16toh((uint16_t)f->touch_major);
+ f->touch_minor = le16toh((uint16_t)f->touch_minor);
+ f->pressure = le16toh((uint16_t)f->pressure);
+ f->multi = le16toh((uint16_t)f->multi);
+ }
+ DPRINTFN(WSP_LLEVEL_INFO,
+ "[%d]ibt=%d, taps=%d, o=%4d, ax=%5d, ay=%5d, "
+ "rx=%5d, ry=%5d, tlmaj=%4d, tlmin=%4d, ot=%4x, "
+ "tchmaj=%4d, tchmin=%4d, presure=%4d, m=%4x\n",
+ i, ibt, ntouch, f->origin, f->abs_x, f->abs_y,
+ f->rel_x, f->rel_y, f->tool_major, f->tool_minor, f->orientation,
+ f->touch_major, f->touch_minor, f->pressure, f->multi);
+ sc->pos_x[i] = f->abs_x;
+ sc->pos_y[i] = -f->abs_y;
+ sc->index[i] = f;
+#ifdef EVDEV_SUPPORT
+ if (evdev_rcpt_mask & EVDEV_RCPT_HW_MOUSE && f->touch_major != 0) {
+ union evdev_mt_slot slot_data = {
+ .id = slot,
+ .x = f->abs_x,
+ .y = params->y.min + params->y.max - f->abs_y,
+ .p = f->pressure,
+ .maj = f->touch_major << 1,
+ .min = f->touch_minor << 1,
+ .w_maj = f->tool_major << 1,
+ .w_min = f->tool_minor << 1,
+ .ori = params->o.max - f->orientation,
+ };
+ evdev_mt_push_slot(sc->sc_evdev, slot, &slot_data);
+ slot++;
+ }
+#endif
+ }
+
+#ifdef EVDEV_SUPPORT
+ if (evdev_rcpt_mask & EVDEV_RCPT_HW_MOUSE) {
+ evdev_push_key(sc->sc_evdev, BTN_LEFT, ibt);
+ evdev_sync(sc->sc_evdev);
+ }
+#endif
+ sc->sc_status.flags &= ~MOUSE_POSCHANGED;
+ sc->sc_status.flags &= ~MOUSE_STDBUTTONSCHANGED;
+ sc->sc_status.obutton = sc->sc_status.button;
+ sc->sc_status.button = 0;
+
+ if (ntouch == 2) {
+ sc->distance = max(sc->distance, max(
+ abs(sc->pos_x[0] - sc->pos_x[1]),
+ abs(sc->pos_y[0] - sc->pos_y[1])));
+ }
+
+ if (ibt != 0) {
+ if (params->tp->caps & HAS_INTEGRATED_BUTTON) {
+ switch (ntouch) {
+ case 1:
+ sc->sc_status.button |= MOUSE_BUTTON1DOWN;
+ break;
+ case 2:
+ if (sc->distance < tun.max_double_tap_distance && abs(sc->dx_sum) < 5 &&
+ abs(sc->dy_sum) < 5)
+ sc->sc_status.button |= MOUSE_BUTTON3DOWN;
+ else
+ sc->sc_status.button |= MOUSE_BUTTON1DOWN;
+ break;
+ case 3:
+ sc->sc_status.button |= MOUSE_BUTTON2DOWN;
+ break;
+ default:
+ break;
+ }
+ } else {
+ sc->sc_status.button |= MOUSE_BUTTON1DOWN;
+ }
+
+ sc->ibtn = 1;
+ }
+ sc->intr_count++;
+
+ if (sc->ntaps < ntouch) {
+ switch (ntouch) {
+ case 1:
+ if (sc->index[0]->touch_major > tun.pressure_tap_threshold &&
+ sc->index[0]->tool_major <= tun.max_finger_diameter)
+ sc->ntaps = 1;
+ break;
+ case 2:
+ if (sc->index[0]->touch_major > tun.pressure_tap_threshold-30 &&
+ sc->index[1]->touch_major > tun.pressure_tap_threshold-30)
+ sc->ntaps = 2;
+ break;
+ case 3:
+ if (sc->index[0]->touch_major > tun.pressure_tap_threshold-40 &&
+ sc->index[1]->touch_major > tun.pressure_tap_threshold-40 &&
+ sc->index[2]->touch_major > tun.pressure_tap_threshold-40)
+ sc->ntaps = 3;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (sc->index[0]->touch_major < tun.pressure_untouch_threshold &&
+ sc->sc_status.button == 0) {
+ sc->sc_touch = WSP_UNTOUCH;
+ if (sc->intr_count < WSP_TAP_MAX_COUNT &&
+ sc->intr_count > WSP_TAP_THRESHOLD &&
+ sc->ntaps && sc->ibtn == 0) {
+ /*
+ * Add a pair of events (button-down and
+ * button-up).
+ */
+ switch (sc->ntaps) {
+ case 1:
+ if (!(params->tp->caps & HAS_INTEGRATED_BUTTON) || tun.enable_single_tap_clicks) {
+ wsp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON1DOWN);
+ DPRINTFN(WSP_LLEVEL_INFO, "LEFT CLICK!\n");
+ }
+ break;
+ case 2:
+ DPRINTFN(WSP_LLEVEL_INFO, "sum_x=%5d, sum_y=%5d\n",
+ sc->dx_sum, sc->dy_sum);
+ if (sc->distance < tun.max_double_tap_distance && abs(sc->dx_sum) < 5 &&
+ abs(sc->dy_sum) < 5) {
+ wsp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON3DOWN);
+ DPRINTFN(WSP_LLEVEL_INFO, "RIGHT CLICK!\n");
+ }
+ break;
+ case 3:
+ wsp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON2DOWN);
+ break;
+ default:
+ /* we don't handle taps of more than three fingers */
+ break;
+ }
+ wsp_add_to_queue(sc, 0, 0, 0, 0); /* button release */
+ }
+
+ if (sc->scr_mode == WSP_SCR_HOR && sc->ntaps == tun.horizontal_swipe_finger_count
+ && tun.horizontal_swipe_finger_count > 0 && (sc->dt_sum / tun.scr_threshold) != 0) {
+ /*
+ * translate T-axis swipe into button
+ * presses 3 and 4 (forward/back)
+ */
+ if (sc->dt_sum > 0)
+ wsp_add_to_queue(sc, 0, 0, 0, 1UL << 3);
+ else if (sc->dt_sum < 0)
+ wsp_add_to_queue(sc, 0, 0, 0, 1UL << 4);
+ }
+
+ sc->dz_count = WSP_DZ_MAX_COUNT;
+ sc->dz_sum = 0;
+ sc->intr_count = 0;
+ sc->ibtn = 0;
+ sc->ntaps = 0;
+ sc->finger = 0;
+ sc->distance = 0;
+ sc->dt_sum = 0;
+ sc->dx_sum = 0;
+ sc->dy_sum = 0;
+ sc->rdx = 0;
+ sc->rdy = 0;
+ sc->rdz = 0;
+ sc->scr_mode = WSP_SCR_NONE;
+ } else if (sc->index[0]->touch_major >= tun.pressure_touch_threshold &&
+ sc->sc_touch == WSP_UNTOUCH) { /* ignore first touch */
+ sc->sc_touch = WSP_FIRST_TOUCH;
+ } else if (sc->index[0]->touch_major >= tun.pressure_touch_threshold &&
+ sc->sc_touch == WSP_FIRST_TOUCH) { /* ignore second touch */
+ sc->sc_touch = WSP_SECOND_TOUCH;
+ DPRINTFN(WSP_LLEVEL_INFO, "First pre_x[0]=%5d, pre_y[0]=%5d\n",
+ sc->pre_pos_x[0], sc->pre_pos_y[0]);
+ } else {
+ if (sc->sc_touch == WSP_SECOND_TOUCH)
+ sc->sc_touch = WSP_TOUCHING;
+
+ if (ntouch != 0 &&
+ sc->index[0]->touch_major >= tun.pressure_touch_threshold) {
+ dx = sc->pos_x[0] - sc->pre_pos_x[0];
+ dy = sc->pos_y[0] - sc->pre_pos_y[0];
+
+ /* Optionally ignore movement during button is releasing */
+ if (tun.enable_single_tap_movement != 1 && sc->ibtn != 0 && sc->sc_status.button == 0)
+ dx = dy = 0;
+
+ /* Ignore movement if ntouch changed */
+ if (sc->o_ntouch != ntouch)
+ dx = dy = 0;
+
+ /* Ignore unexpected movement when typing (palm detection) */
+ if (ntouch == 1 && sc->index[0]->tool_major > tun.max_finger_diameter)
+ dx = dy = 0;
+
+ if (sc->ibtn != 0 && ntouch == 1 &&
+ sc->intr_count < WSP_TAP_MAX_COUNT &&
+ abs(sc->dx_sum) < 1 && abs(sc->dy_sum) < 1 )
+ dx = dy = 0;
+
+ if (ntouch == 2 && sc->sc_status.button != 0) {
+ dx = sc->pos_x[sc->finger] - sc->pre_pos_x[sc->finger];
+ dy = sc->pos_y[sc->finger] - sc->pre_pos_y[sc->finger];
+
+ /*
+ * Ignore movement of switch finger or
+ * movement from ibt=0 to ibt=1
+ */
+ if (sc->index[0]->origin == 0 || sc->index[1]->origin == 0 ||
+ sc->sc_status.obutton != sc->sc_status.button) {
+ dx = dy = 0;
+ sc->finger = 0;
+ }
+ if ((abs(sc->index[0]->rel_x) + abs(sc->index[0]->rel_y)) <
+ (abs(sc->index[1]->rel_x) + abs(sc->index[1]->rel_y)) &&
+ sc->finger == 0) {
+ sc->sc_touch = WSP_SECOND_TOUCH;
+ dx = dy = 0;
+ sc->finger = 1;
+ }
+ if ((abs(sc->index[0]->rel_x) + abs(sc->index[0]->rel_y)) >=
+ (abs(sc->index[1]->rel_x) + abs(sc->index[1]->rel_y)) &&
+ sc->finger == 1) {
+ sc->sc_touch = WSP_SECOND_TOUCH;
+ dx = dy = 0;
+ sc->finger = 0;
+ }
+ DPRINTFN(WSP_LLEVEL_INFO, "dx=%5d, dy=%5d, mov=%5d\n",
+ dx, dy, sc->finger);
+ }
+ if (sc->dz_count--) {
+ if (sc->scr_mode == WSP_SCR_HOR) {
+ rdz = (dx + sc->rdz) % tun.scale_factor;
+ sc->dz_sum -= (dx + sc->rdz) / tun.scale_factor;
+ } else if (sc->scr_mode == WSP_SCR_VER) {
+ rdz = (dy + sc->rdz) % tun.scale_factor;
+ sc->dz_sum -= (dy + sc->rdz) / tun.scale_factor;
+ }
+ sc->rdz = rdz;
+ }
+ if (sc->scr_mode == WSP_SCR_VER && (tun.z_factor == 0 || (sc->dz_sum / tun.z_factor) != 0))
+ sc->dz_count = 0;
+ else if (sc->scr_mode == WSP_SCR_HOR && (tun.t_factor == 0 || (sc->dz_sum / tun.t_factor) != 0))
+ sc->dz_count = 0;
+ }
+ rdx = (dx + sc->rdx) % tun.scale_factor;
+ dx = (dx + sc->rdx) / tun.scale_factor;
+ sc->rdx = rdx;
+
+ rdy = (dy + sc->rdy) % tun.scale_factor;
+ dy = (dy + sc->rdy) / tun.scale_factor;
+ sc->rdy = rdy;
+
+ sc->dx_sum += dx;
+ sc->dy_sum += dy;
+
+ if (sc->sc_status.button == 0 && ntouch > 0) {
+ if (ntouch == tun.scroll_finger_count || ntouch == tun.horizontal_swipe_finger_count) {
+ if (sc->scr_mode == WSP_SCR_NONE && abs(sc->dx_sum) + abs(sc->dy_sum) > tun.scr_threshold)
+ sc->scr_mode = abs(sc->dx_sum) > abs(sc->dy_sum) * 2 ? WSP_SCR_HOR : WSP_SCR_VER;
+
+ DPRINTFN(WSP_LLEVEL_INFO, "scr_mode=%5d, count=%d, dx_sum=%d, dy_sum=%d\n", sc->scr_mode, sc->intr_count, sc->dx_sum, sc->dy_sum);
+ }
+
+ if (ntouch == tun.scroll_finger_count) { /* preference scrolling over swipe if tun.scroll_finger_count == tun.horizontal_swipe_finger_count */
+ if (sc->scr_mode == WSP_SCR_HOR) {
+ sc->sc_status.button = 1 << 5;
+ }
+ dx = dy = dz = 0;
+ dz = 0;
+ sc->dt_sum = 0;
+ if (sc->distance <= tun.max_scroll_finger_distance && sc->dz_count == 0) {
+ if (sc->scr_mode == WSP_SCR_VER) {
+ if (tun.z_factor > 0)
+ dz = (sc->dz_sum / tun.z_factor) * (tun.z_invert ? -1 : 1);
+ } else if (sc->scr_mode == WSP_SCR_HOR) {
+ if (tun.t_factor > 0)
+ dz = (sc->dz_sum / tun.t_factor) * (tun.t_invert ? -1 : 1);
+ }
+ }
+ } else if (ntouch == tun.horizontal_swipe_finger_count) {
+ if (sc->scr_mode == WSP_SCR_HOR) {
+ sc->dt_sum += dx * (tun.t_invert ? -1 : 1);
+ } else {
+ sc->dt_sum = 0;
+ }
+ dx = dy = dz = 0;
+ }
+ }
+
+ if (ntouch == 3)
+ dx = dy = dz = 0;
+
+ if (ntouch != tun.horizontal_swipe_finger_count)
+ sc->dt_sum = 0;
+
+ if (ntouch == 0)
+ sc->scr_mode = WSP_SCR_NONE;
+
+ if (sc->intr_count < WSP_TAP_MAX_COUNT &&
+ abs(dx) < 3 && abs(dy) < 3 && abs(dz) < 3)
+ dx = dy = dz = 0;
+ else
+ sc->intr_count = WSP_TAP_MAX_COUNT;
+ if (dx || dy || dz)
+ sc->sc_status.flags |= MOUSE_POSCHANGED;
+ DPRINTFN(WSP_LLEVEL_INFO, "dx=%5d, dy=%5d, dz=%5d, sc_touch=%x, btn=%x\n",
+ dx, dy, dz, sc->sc_touch, sc->sc_status.button);
+ sc->sc_status.dx += dx;
+ sc->sc_status.dy += dy;
+ sc->sc_status.dz += dz;
+
+ wsp_add_to_queue(sc, dx, -dy, dz, sc->sc_status.button);
+ if (sc->dz_count == 0) {
+ sc->dz_sum = 0;
+ sc->rdz = 0;
+ }
+ }
+ sc->pre_pos_x[0] = sc->pos_x[0];
+ sc->pre_pos_y[0] = sc->pos_y[0];
+
+ if (ntouch == 2 && sc->sc_status.button != 0) {
+ sc->pre_pos_x[sc->finger] = sc->pos_x[sc->finger];
+ sc->pre_pos_y[sc->finger] = sc->pos_y[sc->finger];
+ }
+ sc->o_ntouch = ntouch;
+
+ case USB_ST_SETUP:
+tr_setup:
+ /* check if we can put more data into the FIFO */
+ if (usb_fifo_put_bytes_max(
+ sc->sc_fifo.fp[USB_FIFO_RX]) != 0) {
+ usbd_xfer_set_frame_len(xfer, 0,
+ sc->tp_datalen);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+wsp_add_to_queue(struct wsp_softc *sc, int dx, int dy, int dz,
+ uint32_t buttons_in)
+{
+ uint32_t buttons_out;
+ uint8_t buf[8];
+
+ dx = imin(dx, 254);
+ dx = imax(dx, -256);
+ dy = imin(dy, 254);
+ dy = imax(dy, -256);
+ dz = imin(dz, 126);
+ dz = imax(dz, -128);
+
+ buttons_out = MOUSE_MSC_BUTTONS;
+ if (buttons_in & MOUSE_BUTTON1DOWN)
+ buttons_out &= ~MOUSE_MSC_BUTTON1UP;
+ else if (buttons_in & MOUSE_BUTTON2DOWN)
+ buttons_out &= ~MOUSE_MSC_BUTTON2UP;
+ else if (buttons_in & MOUSE_BUTTON3DOWN)
+ buttons_out &= ~MOUSE_MSC_BUTTON3UP;
+
+ /* Encode the mouse data in standard format; refer to mouse(4) */
+ buf[0] = sc->sc_mode.syncmask[1];
+ buf[0] |= buttons_out;
+ buf[1] = dx >> 1;
+ buf[2] = dy >> 1;
+ buf[3] = dx - (dx >> 1);
+ buf[4] = dy - (dy >> 1);
+ /* Encode extra bytes for level 1 */
+ if (sc->sc_mode.level == 1) {
+ buf[5] = dz >> 1; /* dz / 2 */
+ buf[6] = dz - (dz >> 1);/* dz - (dz / 2) */
+ buf[7] = (((~buttons_in) >> 3) & MOUSE_SYS_EXTBUTTONS);
+ }
+
+ usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf,
+ sc->sc_mode.packetsize, 1);
+}
+
+static void
+wsp_reset_buf(struct wsp_softc *sc)
+{
+ /* reset read queue */
+ usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]);
+}
+
+static void
+wsp_start_read(struct wsp_softc *sc)
+{
+ int rate;
+
+ /* Check if we should override the default polling interval */
+ rate = sc->sc_pollrate;
+ /* Range check rate */
+ if (rate > 1000)
+ rate = 1000;
+ /* Check for set rate */
+ if ((rate > 0) && (sc->sc_xfer[WSP_INTR_DT] != NULL)) {
+ /* Stop current transfer, if any */
+ usbd_transfer_stop(sc->sc_xfer[WSP_INTR_DT]);
+ /* Set new interval */
+ usbd_xfer_set_interval(sc->sc_xfer[WSP_INTR_DT], 1000 / rate);
+ /* Only set pollrate once */
+ sc->sc_pollrate = 0;
+ }
+ usbd_transfer_start(sc->sc_xfer[WSP_INTR_DT]);
+}
+
+static void
+wsp_stop_read(struct wsp_softc *sc)
+{
+ usbd_transfer_stop(sc->sc_xfer[WSP_INTR_DT]);
+}
+
+static int
+wsp_open(struct usb_fifo *fifo, int fflags)
+{
+ struct wsp_softc *sc = usb_fifo_softc(fifo);
+ int rc = 0;
+
+ DPRINTFN(WSP_LLEVEL_INFO, "\n");
+
+ if (sc->sc_fflags & fflags)
+ return (EBUSY);
+
+ if (fflags & FREAD) {
+ if (usb_fifo_alloc_buffer(fifo,
+ WSP_FIFO_BUF_SIZE, WSP_FIFO_QUEUE_MAXLEN)) {
+ return (ENOMEM);
+ }
+#ifdef EVDEV_SUPPORT
+ if ((sc->sc_state & WSP_EVDEV_OPENED) == 0)
+#endif
+ rc = wsp_enable(sc);
+ if (rc != 0) {
+ usb_fifo_free_buffer(fifo);
+ return (rc);
+ }
+ }
+ sc->sc_fflags |= fflags & (FREAD | FWRITE);
+ return (0);
+}
+
+static void
+wsp_close(struct usb_fifo *fifo, int fflags)
+{
+ struct wsp_softc *sc = usb_fifo_softc(fifo);
+
+ if (fflags & FREAD) {
+#ifdef EVDEV_SUPPORT
+ if ((sc->sc_state & WSP_EVDEV_OPENED) == 0)
+#endif
+ wsp_disable(sc);
+ usb_fifo_free_buffer(fifo);
+ }
+
+ sc->sc_fflags &= ~(fflags & (FREAD | FWRITE));
+}
+
+static void
+wsp_fifo_start_read(struct usb_fifo *fifo)
+{
+ struct wsp_softc *sc = usb_fifo_softc(fifo);
+
+ wsp_start_read(sc);
+}
+
+static void
+wsp_fifo_stop_read(struct usb_fifo *fifo)
+{
+ struct wsp_softc *sc = usb_fifo_softc(fifo);
+
+#ifdef EVDEV_SUPPORT
+ if ((sc->sc_state & WSP_EVDEV_OPENED) == 0)
+#endif
+ wsp_stop_read(sc);
+}
+
+#ifdef EVDEV_SUPPORT
+static int
+wsp_ev_open(struct evdev_dev *evdev)
+{
+ struct wsp_softc *sc = evdev_get_softc(evdev);
+ int rc = 0;
+
+ mtx_lock(&sc->sc_mutex);
+ if (sc->sc_fflags == 0)
+ rc = wsp_enable(sc);
+ if (rc == 0) {
+ wsp_start_read(sc);
+ sc->sc_state |= WSP_EVDEV_OPENED;
+ }
+ mtx_unlock(&sc->sc_mutex);
+
+ return (rc);
+}
+
+static int
+wsp_ev_close(struct evdev_dev *evdev)
+{
+ struct wsp_softc *sc = evdev_get_softc(evdev);
+
+ mtx_lock(&sc->sc_mutex);
+ sc->sc_state &= ~WSP_EVDEV_OPENED;
+ if (sc->sc_fflags == 0)
+ wsp_stop_read(sc);
+ mtx_unlock(&sc->sc_mutex);
+
+ return (0);
+}
+#endif
+
+int
+wsp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
+{
+ struct wsp_softc *sc = usb_fifo_softc(fifo);
+ mousemode_t mode;
+ int error = 0;
+
+ mtx_lock(&sc->sc_mutex);
+
+ switch (cmd) {
+ case MOUSE_GETHWINFO:
+ *(mousehw_t *)addr = sc->sc_hw;
+ break;
+ case MOUSE_GETMODE:
+ *(mousemode_t *)addr = sc->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)) {
+ error = EINVAL;
+ goto done;
+ }
+ sc->sc_mode.level = mode.level;
+ sc->sc_pollrate = mode.rate;
+ sc->sc_hw.buttons = 3;
+
+ if (sc->sc_mode.level == 0) {
+ sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+ sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+ } else if (sc->sc_mode.level == 1) {
+ sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
+ sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+ }
+ wsp_reset_buf(sc);
+ break;
+ case MOUSE_GETLEVEL:
+ *(int *)addr = sc->sc_mode.level;
+ break;
+ case MOUSE_SETLEVEL:
+ if (*(int *)addr < 0 || *(int *)addr > 1) {
+ error = EINVAL;
+ goto done;
+ }
+ sc->sc_mode.level = *(int *)addr;
+ sc->sc_hw.buttons = 3;
+
+ if (sc->sc_mode.level == 0) {
+ sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+ sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+ } else if (sc->sc_mode.level == 1) {
+ sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
+ sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+ sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+ sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+ }
+ wsp_reset_buf(sc);
+ break;
+ case MOUSE_GETSTATUS:{
+ mousestatus_t *status = (mousestatus_t *)addr;
+
+ *status = sc->sc_status;
+ sc->sc_status.obutton = sc->sc_status.button;
+ sc->sc_status.button = 0;
+ sc->sc_status.dx = 0;
+ sc->sc_status.dy = 0;
+ sc->sc_status.dz = 0;
+
+ if (status->dx || status->dy || status->dz)
+ status->flags |= MOUSE_POSCHANGED;
+ if (status->button != status->obutton)
+ status->flags |= MOUSE_BUTTONSCHANGED;
+ break;
+ }
+ default:
+ error = ENOTTY;
+ }
+
+done:
+ mtx_unlock(&sc->sc_mutex);
+ return (error);
+}
+
+static device_method_t wsp_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, wsp_probe),
+ DEVMETHOD(device_attach, wsp_attach),
+ DEVMETHOD(device_detach, wsp_detach),
+ DEVMETHOD_END
+};
+
+static driver_t wsp_driver = {
+ .name = WSP_DRIVER_NAME,
+ .methods = wsp_methods,
+ .size = sizeof(struct wsp_softc)
+};
+
+DRIVER_MODULE(wsp, uhub, wsp_driver, NULL, NULL);
+MODULE_DEPEND(wsp, usb, 1, 1, 1);
+MODULE_DEPEND(wsp, hid, 1, 1, 1);
+#ifdef EVDEV_SUPPORT
+MODULE_DEPEND(wsp, evdev, 1, 1, 1);
+#endif
+MODULE_VERSION(wsp, 1);
+USB_PNP_HOST_INFO(wsp_devs);
diff --git a/sys/dev/usb/misc/cp2112.c b/sys/dev/usb/misc/cp2112.c
new file mode 100644
index 000000000000..d4776ca342cb
--- /dev/null
+++ b/sys/dev/usb/misc/cp2112.c
@@ -0,0 +1,1433 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) Andriy Gapon
+ *
+ * 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.
+ *
+ */
+
+/*
+ * Hardware information links:
+ * - CP2112 Datasheet
+ * https://www.silabs.com/documents/public/data-sheets/cp2112-datasheet.pdf
+ * - AN495: CP2112 Interface Specification
+ * https://www.silabs.com/documents/public/application-notes/an495-cp2112-interface-specification.pdf
+ * - CP2112 Errata
+ * https://www.silabs.com/documents/public/errata/cp2112-errata.pdf
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/condvar.h>
+#include <sys/bus.h>
+#include <sys/gpio.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/sdt.h>
+#include <sys/sx.h>
+
+#include <dev/gpio/gpiobusvar.h>
+
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include "iicbus_if.h"
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR usb_debug
+#include <dev/usb/usb_debug.h>
+
+#define SIZEOF_FIELD(_s, _f) sizeof(((struct _s *)NULL)->_f)
+
+#define CP2112GPIO_LOCK(sc) sx_xlock(&sc->gpio_lock)
+#define CP2112GPIO_UNLOCK(sc) sx_xunlock(&sc->gpio_lock)
+#define CP2112GPIO_LOCKED(sc) sx_assert(&sc->gpio_lock, SX_XLOCKED)
+
+#define CP2112_PART_NUM 0x0c
+#define CP2112_GPIO_COUNT 8
+#define CP2112_REPORT_SIZE 64
+
+#define CP2112_REQ_RESET 0x1
+#define CP2112_REQ_GPIO_CFG 0x2
+#define CP2112_REQ_GPIO_GET 0x3
+#define CP2112_REQ_GPIO_SET 0x4
+#define CP2112_REQ_VERSION 0x5
+#define CP2112_REQ_SMB_CFG 0x6
+
+#define CP2112_REQ_SMB_READ 0x10
+#define CP2112_REQ_SMB_WRITE_READ 0x11
+#define CP2112_REQ_SMB_READ_FORCE_SEND 0x12
+#define CP2112_REQ_SMB_READ_RESPONSE 0x13
+#define CP2112_REQ_SMB_WRITE 0x14
+#define CP2112_REQ_SMB_XFER_STATUS_REQ 0x15
+#define CP2112_REQ_SMB_XFER_STATUS_RESP 0x16
+#define CP2112_REQ_SMB_CANCEL 0x17
+
+#define CP2112_REQ_LOCK 0x20
+#define CP2112_REQ_USB_CFG 0x21
+
+#define CP2112_IIC_MAX_READ_LEN 512
+#define CP2112_IIC_REPSTART_VER 2 /* Erratum CP2112_E10. */
+
+#define CP2112_GPIO_SPEC_CLK7 1 /* Pin 7 is clock output. */
+#define CP2112_GPIO_SPEC_TX0 2 /* Pin 0 pulses on USB TX. */
+#define CP2112_GPIO_SPEC_RX1 4 /* Pin 1 pulses on USB RX. */
+
+#define CP2112_IIC_STATUS0_IDLE 0
+#define CP2112_IIC_STATUS0_BUSY 1
+#define CP2112_IIC_STATUS0_CMP 2
+#define CP2112_IIC_STATUS0_ERROR 3
+
+#define CP2112_IIC_STATUS1_TIMEOUT_NACK 0
+#define CP2112_IIC_STATUS1_TIMEOUT_BUS 1
+#define CP2112_IIC_STATUS1_ARB_LOST 2
+
+/* CP2112_REQ_VERSION */
+struct version_request {
+ uint8_t id;
+ uint8_t part_num;
+ uint8_t version;
+} __packed;
+
+/* CP2112_REQ_GPIO_GET */
+struct gpio_get_req {
+ uint8_t id;
+ uint8_t state;
+} __packed;
+
+/* CP2112_REQ_GPIO_SET */
+struct gpio_set_req {
+ uint8_t id;
+ uint8_t state;
+ uint8_t mask;
+} __packed;
+
+/* CP2112_REQ_GPIO_CFG */
+struct gpio_config_req {
+ uint8_t id;
+ uint8_t output;
+ uint8_t pushpull;
+ uint8_t special;
+ uint8_t divider;
+} __packed;
+
+/* CP2112_REQ_SMB_XFER_STATUS_REQ */
+struct i2c_xfer_status_req {
+ uint8_t id;
+ uint8_t request;
+} __packed;
+
+/* CP2112_REQ_SMB_XFER_STATUS_RESP */
+struct i2c_xfer_status_resp {
+ uint8_t id;
+ uint8_t status0;
+ uint8_t status1;
+ uint16_t status2;
+ uint16_t status3;
+} __packed;
+
+/* CP2112_REQ_SMB_READ_FORCE_SEND */
+struct i2c_data_read_force_send_req {
+ uint8_t id;
+ uint16_t len;
+} __packed;
+
+/* CP2112_REQ_SMB_READ_RESPONSE */
+struct i2c_data_read_resp {
+ uint8_t id;
+ uint8_t status;
+ uint8_t len;
+ uint8_t data[61];
+} __packed;
+
+/* CP2112_REQ_SMB_READ */
+struct i2c_write_read_req {
+ uint8_t id;
+ uint8_t slave;
+ uint16_t rlen;
+ uint8_t wlen;
+ uint8_t wdata[16];
+} __packed;
+
+/* CP2112_REQ_SMB_WRITE */
+struct i2c_read_req {
+ uint8_t id;
+ uint8_t slave;
+ uint16_t len;
+} __packed;
+
+/* CP2112_REQ_SMB_WRITE_READ */
+struct i2c_write_req {
+ uint8_t id;
+ uint8_t slave;
+ uint8_t len;
+ uint8_t data[61];
+} __packed;
+
+/* CP2112_REQ_SMB_CFG */
+struct i2c_cfg_req {
+ uint8_t id;
+ uint32_t speed; /* Hz */
+ uint8_t slave_addr; /* ACK only */
+ uint8_t auto_send_read; /* boolean */
+ uint16_t write_timeout; /* 0-1000 ms, 0 ~ no timeout */
+ uint16_t read_timeout; /* 0-1000 ms, 0 ~ no timeout */
+ uint8_t scl_low_timeout;/* boolean */
+ uint16_t retry_count; /* 1-1000, 0 ~ forever */
+} __packed;
+
+enum cp2112_out_mode {
+ OUT_OD,
+ OUT_PP,
+ OUT_KEEP
+};
+
+enum {
+ CP2112_INTR_OUT = 0,
+ CP2112_INTR_IN,
+ CP2112_N_TRANSFER,
+};
+
+struct cp2112_softc {
+ device_t sc_gpio_dev;
+ device_t sc_iic_dev;
+ struct usb_device *sc_udev;
+ uint8_t sc_iface_index;
+ uint8_t sc_version;
+};
+
+struct cp2112gpio_softc {
+ struct sx gpio_lock;
+ device_t busdev;
+ int gpio_caps;
+ struct gpio_pin pins[CP2112_GPIO_COUNT];
+};
+
+struct cp2112iic_softc {
+ device_t dev;
+ device_t iicbus_dev;
+ struct usb_xfer *xfers[CP2112_N_TRANSFER];
+ u_char own_addr;
+ struct {
+ struct mtx lock;
+ struct cv cv;
+ struct {
+ uint8_t *data;
+ int len;
+ int done;
+ int error;
+ } in;
+ struct {
+ const uint8_t *data;
+ int len;
+ int done;
+ int error;
+ } out;
+ } io;
+};
+
+static int cp2112gpio_detach(device_t dev);
+static int cp2112iic_detach(device_t dev);
+
+static const STRUCT_USB_HOST_ID cp2112_devs[] = {
+ { USB_VP(USB_VENDOR_SILABS, USB_PRODUCT_SILABS_CP2112) },
+ { USB_VP(0x1009, USB_PRODUCT_SILABS_CP2112) }, /* XXX */
+};
+
+static int
+cp2112_get_report(device_t dev, uint8_t id, void *data, uint16_t len)
+{
+ struct cp2112_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+ err = usbd_req_get_report(sc->sc_udev, NULL, data,
+ len, sc->sc_iface_index, UHID_FEATURE_REPORT, id);
+ return (err);
+}
+
+static int
+cp2112_set_report(device_t dev, uint8_t id, void *data, uint16_t len)
+{
+ struct cp2112_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+ *(uint8_t *)data = id;
+ err = usbd_req_set_report(sc->sc_udev, NULL, data,
+ len, sc->sc_iface_index, UHID_FEATURE_REPORT, id);
+ return (err);
+}
+
+static int
+cp2112_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa;
+
+ uaa = device_get_ivars(dev);
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bInterfaceClass != UICLASS_HID)
+ return (ENXIO);
+
+ if (usbd_lookup_id_by_uaa(cp2112_devs, sizeof(cp2112_devs), uaa) == 0)
+ return (BUS_PROBE_DEFAULT);
+ return (ENXIO);
+}
+
+static int
+cp2112_attach(device_t dev)
+{
+ struct version_request vdata;
+ struct usb_attach_arg *uaa;
+ struct cp2112_softc *sc;
+ int err;
+
+ uaa = device_get_ivars(dev);
+ sc = device_get_softc(dev);
+
+ device_set_usb_desc(dev);
+
+ sc->sc_udev = uaa->device;
+ sc->sc_iface_index = uaa->info.bIfaceIndex;
+
+ err = cp2112_get_report(dev, CP2112_REQ_VERSION, &vdata, sizeof(vdata));
+ if (err != 0)
+ goto detach;
+ device_printf(dev, "part number 0x%02x, version 0x%02x\n",
+ vdata.part_num, vdata.version);
+ if (vdata.part_num != CP2112_PART_NUM) {
+ device_printf(dev, "unsupported part number\n");
+ goto detach;
+ }
+ sc->sc_version = vdata.version;
+ sc->sc_gpio_dev = device_add_child(dev, "gpio", DEVICE_UNIT_ANY);
+ if (sc->sc_gpio_dev != NULL) {
+ err = device_probe_and_attach(sc->sc_gpio_dev);
+ if (err != 0) {
+ device_printf(dev, "failed to attach gpio child\n");
+ }
+ } else {
+ device_printf(dev, "failed to create gpio child\n");
+ }
+
+ sc->sc_iic_dev = device_add_child(dev, "iichb", DEVICE_UNIT_ANY);
+ if (sc->sc_iic_dev != NULL) {
+ err = device_probe_and_attach(sc->sc_iic_dev);
+ if (err != 0) {
+ device_printf(dev, "failed to attach iic child\n");
+ }
+ } else {
+ device_printf(dev, "failed to create iic child\n");
+ }
+
+ return (0);
+
+detach:
+ bus_generic_detach(dev);
+ return (ENXIO);
+}
+
+static int
+cp2112_gpio_read_pin(device_t dev, uint32_t pin_num, bool *on)
+{
+ struct gpio_get_req data;
+ struct cp2112gpio_softc *sc __diagused;
+ int err;
+
+ sc = device_get_softc(dev);
+ CP2112GPIO_LOCKED(sc);
+
+ err = cp2112_get_report(device_get_parent(dev),
+ CP2112_REQ_GPIO_GET, &data, sizeof(data));
+ if (err != 0)
+ return (err);
+ *on = (data.state & ((uint8_t)1 << pin_num)) != 0;
+ return (0);
+
+}
+
+static int
+cp2112_gpio_write_pin(device_t dev, uint32_t pin_num, bool on)
+{
+ struct gpio_set_req data;
+ struct cp2112gpio_softc *sc __diagused;
+ int err;
+ bool actual;
+
+ sc = device_get_softc(dev);
+ CP2112GPIO_LOCKED(sc);
+
+ data.state = (uint8_t)on << pin_num;
+ data.mask = (uint8_t)1 << pin_num;
+ err = cp2112_set_report(device_get_parent(dev),
+ CP2112_REQ_GPIO_SET, &data, sizeof(data));
+ if (err != 0)
+ return (err);
+ err = cp2112_gpio_read_pin(dev, pin_num, &actual);
+ if (err != 0)
+ return (err);
+ if (actual != on)
+ return (EIO);
+ return (0);
+}
+
+static int
+cp2112_gpio_configure_write_pin(device_t dev, uint32_t pin_num,
+ bool output, enum cp2112_out_mode *mode)
+{
+ struct gpio_config_req data;
+ struct cp2112gpio_softc *sc __diagused;
+ int err;
+ uint8_t mask;
+
+ sc = device_get_softc(dev);
+ CP2112GPIO_LOCKED(sc);
+
+ err = cp2112_get_report(device_get_parent(dev),
+ CP2112_REQ_GPIO_CFG, &data, sizeof(data));
+ if (err != 0)
+ return (err);
+
+ mask = (uint8_t)1 << pin_num;
+ if (output) {
+ data.output |= mask;
+ switch (*mode) {
+ case OUT_PP:
+ data.pushpull |= mask;
+ break;
+ case OUT_OD:
+ data.pushpull &= ~mask;
+ break;
+ default:
+ break;
+ }
+ } else {
+ data.output &= ~mask;
+ }
+
+ err = cp2112_set_report(device_get_parent(dev),
+ CP2112_REQ_GPIO_CFG, &data, sizeof(data));
+ if (err != 0)
+ return (err);
+
+ /* Read back and verify. */
+ err = cp2112_get_report(device_get_parent(dev),
+ CP2112_REQ_GPIO_CFG, &data, sizeof(data));
+ if (err != 0)
+ return (err);
+
+ if (((data.output & mask) != 0) != output)
+ return (EIO);
+ if (output) {
+ switch (*mode) {
+ case OUT_PP:
+ if ((data.pushpull & mask) == 0)
+ return (EIO);
+ break;
+ case OUT_OD:
+ if ((data.pushpull & mask) != 0)
+ return (EIO);
+ break;
+ default:
+ *mode = (data.pushpull & mask) != 0 ?
+ OUT_PP : OUT_OD;
+ break;
+ }
+ }
+ return (0);
+}
+
+static device_t
+cp2112_gpio_get_bus(device_t dev)
+{
+ struct cp2112gpio_softc *sc;
+
+ sc = device_get_softc(dev);
+ return (sc->busdev);
+}
+
+static int
+cp2112_gpio_pin_max(device_t dev, int *maxpin)
+{
+
+ *maxpin = CP2112_GPIO_COUNT - 1;
+ return (0);
+}
+
+static int
+cp2112_gpio_pin_set(device_t dev, uint32_t pin_num, uint32_t pin_value)
+{
+ struct cp2112gpio_softc *sc;
+ int err;
+
+ if (pin_num >= CP2112_GPIO_COUNT)
+ return (EINVAL);
+
+ sc = device_get_softc(dev);
+ CP2112GPIO_LOCK(sc);
+ err = cp2112_gpio_write_pin(dev, pin_num, pin_value != 0);
+ CP2112GPIO_UNLOCK(sc);
+
+ return (err);
+}
+
+static int
+cp2112_gpio_pin_get(device_t dev, uint32_t pin_num, uint32_t *pin_value)
+{
+ struct cp2112gpio_softc *sc;
+ int err;
+ bool on;
+
+ if (pin_num >= CP2112_GPIO_COUNT)
+ return (EINVAL);
+
+ sc = device_get_softc(dev);
+ CP2112GPIO_LOCK(sc);
+ err = cp2112_gpio_read_pin(dev, pin_num, &on);
+ CP2112GPIO_UNLOCK(sc);
+
+ if (err == 0)
+ *pin_value = on;
+ return (err);
+}
+
+static int
+cp2112_gpio_pin_toggle(device_t dev, uint32_t pin_num)
+{
+ struct cp2112gpio_softc *sc;
+ int err;
+ bool on;
+
+ if (pin_num >= CP2112_GPIO_COUNT)
+ return (EINVAL);
+
+ sc = device_get_softc(dev);
+ CP2112GPIO_LOCK(sc);
+ err = cp2112_gpio_read_pin(dev, pin_num, &on);
+ if (err == 0)
+ err = cp2112_gpio_write_pin(dev, pin_num, !on);
+ CP2112GPIO_UNLOCK(sc);
+
+ return (err);
+}
+
+static int
+cp2112_gpio_pin_getcaps(device_t dev, uint32_t pin_num, uint32_t *caps)
+{
+ struct cp2112gpio_softc *sc;
+
+ if (pin_num >= CP2112_GPIO_COUNT)
+ return (EINVAL);
+
+ sc = device_get_softc(dev);
+ CP2112GPIO_LOCK(sc);
+ *caps = sc->gpio_caps;
+ CP2112GPIO_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+cp2112_gpio_pin_getflags(device_t dev, uint32_t pin_num, uint32_t *flags)
+{
+ struct cp2112gpio_softc *sc;
+
+ if (pin_num >= CP2112_GPIO_COUNT)
+ return (EINVAL);
+
+ sc = device_get_softc(dev);
+ CP2112GPIO_LOCK(sc);
+ *flags = sc->pins[pin_num].gp_flags;
+ CP2112GPIO_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+cp2112_gpio_pin_getname(device_t dev, uint32_t pin_num, char *name)
+{
+ struct cp2112gpio_softc *sc;
+
+ if (pin_num >= CP2112_GPIO_COUNT)
+ return (EINVAL);
+
+ sc = device_get_softc(dev);
+ CP2112GPIO_LOCK(sc);
+ memcpy(name, sc->pins[pin_num].gp_name, GPIOMAXNAME);
+ CP2112GPIO_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+cp2112_gpio_pin_setflags(device_t dev, uint32_t pin_num, uint32_t flags)
+{
+ struct cp2112gpio_softc *sc;
+ struct gpio_pin *pin;
+ enum cp2112_out_mode out_mode;
+ int err;
+
+ if (pin_num >= CP2112_GPIO_COUNT)
+ return (EINVAL);
+
+ sc = device_get_softc(dev);
+ if ((flags & sc->gpio_caps) != flags)
+ return (EINVAL);
+
+ if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == 0)
+ return (EINVAL);
+ if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) ==
+ (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) {
+ return (EINVAL);
+ }
+ if ((flags & GPIO_PIN_INPUT) != 0) {
+ if ((flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) != 0)
+ return (EINVAL);
+ } else {
+ if ((flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) ==
+ (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL))
+ return (EINVAL);
+ }
+
+ /*
+ * If neither push-pull or open-drain is explicitly requested, then
+ * preserve the current state.
+ */
+ out_mode = OUT_KEEP;
+ if ((flags & GPIO_PIN_OUTPUT) != 0) {
+ if ((flags & GPIO_PIN_OPENDRAIN) != 0)
+ out_mode = OUT_OD;
+ if ((flags & GPIO_PIN_PUSHPULL) != 0)
+ out_mode = OUT_PP;
+ }
+
+ CP2112GPIO_LOCK(sc);
+ pin = &sc->pins[pin_num];
+ err = cp2112_gpio_configure_write_pin(dev, pin_num,
+ (flags & GPIO_PIN_OUTPUT) != 0, &out_mode);
+ if (err == 0) {
+ /*
+ * If neither open-drain or push-pull was requested, then see
+ * what hardware actually had. Otherwise, it has been
+ * reconfigured as requested.
+ */
+ if ((flags & GPIO_PIN_OUTPUT) != 0 &&
+ (flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) == 0) {
+ KASSERT(out_mode != OUT_KEEP,
+ ("impossible current output mode"));
+ if (out_mode == OUT_OD)
+ flags |= GPIO_PIN_OPENDRAIN;
+ else
+ flags |= GPIO_PIN_PUSHPULL;
+ }
+ pin->gp_flags = flags;
+ }
+ CP2112GPIO_UNLOCK(sc);
+
+ return (err);
+}
+
+static int
+cp2112gpio_probe(device_t dev)
+{
+ device_set_desc(dev, "CP2112 GPIO interface");
+ return (BUS_PROBE_SPECIFIC);
+}
+
+static int
+cp2112gpio_attach(device_t dev)
+{
+ struct gpio_config_req data;
+ struct cp2112gpio_softc *sc;
+ device_t cp2112;
+ int err;
+ int i;
+ uint8_t mask;
+
+ cp2112 = device_get_parent(dev);
+ sc = device_get_softc(dev);
+ sx_init(&sc->gpio_lock, "cp2112 lock");
+
+ sc->gpio_caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN |
+ GPIO_PIN_PUSHPULL;
+
+ err = cp2112_get_report(cp2112, CP2112_REQ_GPIO_CFG,
+ &data, sizeof(data));
+ if (err != 0)
+ goto detach;
+
+ for (i = 0; i < CP2112_GPIO_COUNT; i++) {
+ struct gpio_pin *pin;
+
+ mask = (uint8_t)1 << i;
+ pin = &sc->pins[i];
+ pin->gp_flags = 0;
+
+ snprintf(pin->gp_name, GPIOMAXNAME, "GPIO%u", i);
+ pin->gp_name[GPIOMAXNAME - 1] = '\0';
+
+ if ((i == 0 && (data.special & CP2112_GPIO_SPEC_TX0) != 0) ||
+ (i == 1 && (data.special & CP2112_GPIO_SPEC_RX1) != 0) ||
+ (i == 7 && (data.special & CP2112_GPIO_SPEC_CLK7) != 0)) {
+ /* Special mode means that a pin is not for GPIO. */
+ } else if ((data.output & mask) != 0) {
+ pin->gp_flags |= GPIO_PIN_OUTPUT;
+ if ((data.pushpull & mask) != 0)
+ pin->gp_flags |= GPIO_PIN_PUSHPULL;
+ else
+ pin->gp_flags |= GPIO_PIN_OPENDRAIN;
+ } else {
+ pin->gp_flags |= GPIO_PIN_INPUT;
+ }
+ }
+
+ sc->busdev = gpiobus_attach_bus(dev);
+ if (sc->busdev == NULL) {
+ device_printf(dev, "gpiobus_attach_bus failed\n");
+ goto detach;
+ }
+ return (0);
+
+detach:
+ cp2112gpio_detach(dev);
+ return (ENXIO);
+}
+
+static int
+cp2112gpio_detach(device_t dev)
+{
+ struct cp2112gpio_softc *sc;
+
+ sc = device_get_softc(dev);
+ if (sc->busdev != NULL)
+ gpiobus_detach_bus(dev);
+ sx_destroy(&sc->gpio_lock);
+ return (0);
+}
+
+static void
+cp2112iic_intr_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct cp2112iic_softc *sc;
+ struct usb_page_cache *pc;
+
+ sc = usbd_xfer_softc(xfer);
+
+ mtx_assert(&sc->io.lock, MA_OWNED);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, sc->io.out.data, sc->io.out.len);
+ usbd_xfer_set_frame_len(xfer, 0, sc->io.out.len);
+ usbd_xfer_set_frames(xfer, 1);
+ usbd_transfer_submit(xfer);
+ break;
+ case USB_ST_TRANSFERRED:
+ sc->io.out.error = 0;
+ sc->io.out.done = 1;
+ cv_signal(&sc->io.cv);
+ break;
+ default: /* Error */
+ device_printf(sc->dev, "write intr state %d error %d\n",
+ USB_GET_STATE(xfer), error);
+ sc->io.out.error = IIC_EBUSERR;
+ cv_signal(&sc->io.cv);
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ }
+ break;
+ }
+}
+
+static void
+cp2112iic_intr_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct cp2112iic_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int act_len, len;
+
+ mtx_assert(&sc->io.lock, MA_OWNED);
+ usbd_xfer_status(xfer, &act_len, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (sc->io.in.done) {
+ device_printf(sc->dev,
+ "interrupt while previous is pending, ignored\n");
+ } else if (sc->io.in.len == 0) {
+ uint8_t buf[8];
+
+ /*
+ * There is a spurious Transfer Status Response and
+ * zero-length Read Response during hardware
+ * configuration. Possibly they carry some information
+ * about the initial bus state.
+ */
+ if (device_is_attached(sc->dev)) {
+ device_printf(sc->dev,
+ "unsolicited interrupt, ignored\n");
+ if (bootverbose) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ len = MIN(sizeof(buf), act_len);
+ usbd_copy_out(pc, 0, buf, len);
+ device_printf(sc->dev, "data: %*D\n",
+ len, buf, " ");
+ }
+ } else {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ len = MIN(sizeof(buf), act_len);
+ usbd_copy_out(pc, 0, buf, len);
+ if (buf[0] == CP2112_REQ_SMB_XFER_STATUS_RESP) {
+ device_printf(sc->dev,
+ "initial bus status0 = 0x%02x, "
+ "status1 = 0x%02x\n",
+ buf[1], buf[2]);
+ }
+ }
+ } else if (act_len == CP2112_REPORT_SIZE) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, sc->io.in.data, sc->io.in.len);
+ sc->io.in.error = 0;
+ sc->io.in.done = 1;
+ } else {
+ device_printf(sc->dev,
+ "unexpected input report length %u\n", act_len);
+ sc->io.in.error = IIC_EBUSERR;
+ sc->io.in.done = 1;
+ }
+ cv_signal(&sc->io.cv);
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ device_printf(sc->dev, "read intr state %d error %d\n",
+ USB_GET_STATE(xfer), error);
+
+ sc->io.in.error = IIC_EBUSERR;
+ sc->io.in.done = 1;
+ cv_signal(&sc->io.cv);
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static const struct usb_config cp2112iic_config[CP2112_N_TRANSFER] = {
+ [CP2112_INTR_OUT] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .flags = { .pipe_bof = 1, .no_pipe_ok = 1, },
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &cp2112iic_intr_write_callback,
+ },
+ [CP2112_INTR_IN] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = { .pipe_bof = 1, .short_xfer_ok = 1, },
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &cp2112iic_intr_read_callback,
+ },
+};
+
+static int
+cp2112iic_send_req(struct cp2112iic_softc *sc, const void *data,
+ uint16_t len)
+{
+ int err;
+
+ mtx_assert(&sc->io.lock, MA_OWNED);
+ KASSERT(sc->io.out.done == 0, ("%s: conflicting request", __func__));
+
+ sc->io.out.data = data;
+ sc->io.out.len = len;
+
+ DTRACE_PROBE1(send__req, uint8_t, *(const uint8_t *)data);
+
+ usbd_transfer_start(sc->xfers[CP2112_INTR_OUT]);
+
+ while (!sc->io.out.done)
+ cv_wait(&sc->io.cv, &sc->io.lock);
+
+ usbd_transfer_stop(sc->xfers[CP2112_INTR_OUT]);
+
+ sc->io.out.done = 0;
+ sc->io.out.data = NULL;
+ sc->io.out.len = 0;
+ err = sc->io.out.error;
+ if (err != 0) {
+ device_printf(sc->dev, "output report 0x%02x failed: %d\n",
+ *(const uint8_t*)data, err);
+ }
+ return (err);
+}
+
+static int
+cp2112iic_req_resp(struct cp2112iic_softc *sc, const void *req_data,
+ uint16_t req_len, void *resp_data, uint16_t resp_len)
+{
+ int err;
+
+ mtx_assert(&sc->io.lock, MA_OWNED);
+
+ /*
+ * Prepare to receive a response interrupt even before the
+ * request transfer is confirmed (USB_ST_TRANSFERED).
+ */
+ KASSERT(sc->io.in.done == 0, ("%s: conflicting request", __func__));
+ sc->io.in.len = resp_len;
+ sc->io.in.data = resp_data;
+
+ err = cp2112iic_send_req(sc, req_data, req_len);
+ if (err != 0) {
+ sc->io.in.len = 0;
+ sc->io.in.data = NULL;
+ return (err);
+ }
+
+ while (!sc->io.in.done)
+ cv_wait(&sc->io.cv, &sc->io.lock);
+
+ err = sc->io.in.error;
+ sc->io.in.done = 0;
+ sc->io.in.error = 0;
+ sc->io.in.len = 0;
+ sc->io.in.data = NULL;
+ return (err);
+}
+
+static int
+cp2112iic_check_req_status(struct cp2112iic_softc *sc)
+{
+ struct i2c_xfer_status_req xfer_status_req;
+ struct i2c_xfer_status_resp xfer_status_resp;
+ int err;
+
+ mtx_assert(&sc->io.lock, MA_OWNED);
+
+ do {
+ xfer_status_req.id = CP2112_REQ_SMB_XFER_STATUS_REQ;
+ xfer_status_req.request = 1;
+ err = cp2112iic_req_resp(sc,
+ &xfer_status_req, sizeof(xfer_status_req),
+ &xfer_status_resp, sizeof(xfer_status_resp));
+
+ if (xfer_status_resp.id != CP2112_REQ_SMB_XFER_STATUS_RESP) {
+ device_printf(sc->dev,
+ "unexpected response 0x%02x to status request\n",
+ xfer_status_resp.id);
+ err = IIC_EBUSERR;
+ goto out;
+ }
+
+ DTRACE_PROBE4(xfer__status, uint8_t, xfer_status_resp.status0,
+ uint8_t, xfer_status_resp.status1,
+ uint16_t, be16toh(xfer_status_resp.status2),
+ uint16_t, be16toh(xfer_status_resp.status3));
+
+ switch (xfer_status_resp.status0) {
+ case CP2112_IIC_STATUS0_IDLE:
+ err = IIC_ESTATUS;
+ break;
+ case CP2112_IIC_STATUS0_BUSY:
+ err = ERESTART; /* non-I2C, special handling */
+ break;
+ case CP2112_IIC_STATUS0_CMP:
+ err = IIC_NOERR;
+ break;
+ case CP2112_IIC_STATUS0_ERROR:
+ switch (xfer_status_resp.status1) {
+ case CP2112_IIC_STATUS1_TIMEOUT_NACK:
+ err = IIC_ENOACK;
+ break;
+ case CP2112_IIC_STATUS1_TIMEOUT_BUS:
+ err = IIC_ETIMEOUT;
+ break;
+ case CP2112_IIC_STATUS1_ARB_LOST:
+ err = IIC_EBUSBSY;
+ break;
+ default:
+ device_printf(sc->dev,
+ "i2c error, status = 0x%02x\n",
+ xfer_status_resp.status1);
+ err = IIC_ESTATUS;
+ break;
+ }
+ break;
+ default:
+ device_printf(sc->dev,
+ "unknown i2c xfer status0 0x%02x\n",
+ xfer_status_resp.status0);
+ err = IIC_EBUSERR;
+ break;
+ }
+
+ } while (err == ERESTART);
+out:
+ return (err);
+}
+
+static int
+cp2112iic_read_data(struct cp2112iic_softc *sc, void *data, uint16_t in_len,
+ uint16_t *out_len)
+{
+ struct i2c_data_read_force_send_req data_read_force_send;
+ struct i2c_data_read_resp data_read_resp;
+ int err;
+
+ mtx_assert(&sc->io.lock, MA_OWNED);
+
+ /*
+ * Prepare to receive a response interrupt even before the request
+ * transfer is confirmed (USB_ST_TRANSFERED).
+ */
+
+ if (in_len > sizeof(data_read_resp.data))
+ in_len = sizeof(data_read_resp.data);
+ data_read_force_send.id = CP2112_REQ_SMB_READ_FORCE_SEND;
+ data_read_force_send.len = htobe16(in_len);
+ err = cp2112iic_req_resp(sc,
+ &data_read_force_send, sizeof(data_read_force_send),
+ &data_read_resp, sizeof(data_read_resp));
+ if (err != 0)
+ goto out;
+
+ if (data_read_resp.id != CP2112_REQ_SMB_READ_RESPONSE) {
+ device_printf(sc->dev,
+ "unexpected response 0x%02x to data read request\n",
+ data_read_resp.id);
+ err = IIC_EBUSERR;
+ goto out;
+ }
+
+ DTRACE_PROBE2(read__response, uint8_t, data_read_resp.status,
+ uint8_t, data_read_resp.len);
+
+ /*
+ * We expect either the request completed status or, more typical for
+ * this driver, the bus idle status because of the preceding
+ * Force Read Status command (which is not an I2C request).
+ */
+ if (data_read_resp.status != CP2112_IIC_STATUS0_CMP &&
+ data_read_resp.status != CP2112_IIC_STATUS0_IDLE) {
+ err = IIC_EBUSERR;
+ goto out;
+ }
+ if (data_read_resp.len > in_len) {
+ device_printf(sc->dev, "device returns more data than asked\n");
+ err = IIC_EOVERFLOW;
+ goto out;
+ }
+
+ *out_len = data_read_resp.len;
+ if (*out_len > 0)
+ memcpy(data, data_read_resp.data, *out_len);
+out:
+ return (err);
+}
+
+static int
+cp2112iic_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
+{
+ struct cp2112iic_softc *sc = device_get_softc(dev);
+ struct cp2112_softc *psc = device_get_softc(device_get_parent(dev));
+ const char *reason = NULL;
+ uint32_t i;
+ uint16_t read_off, to_read;
+ int err;
+
+ /*
+ * The hardware interface imposes limits on allowed I2C messages.
+ * It is not possible to explicitly send a start or stop.
+ * It is not possible to do a zero length transfer.
+ * For this reason it's impossible to send a message with no data
+ * at all (like an SMBus quick message).
+ * Each read or write transfer beginning with the start condition
+ * and ends with the stop condition. The only exception is that
+ * it is possible to have a write transfer followed by a read
+ * transfer to the same slave with the repeated start condition
+ * between them.
+ */
+ for (i = 0; i < nmsgs; i++) {
+ if (i == 0 && (msgs[i].flags & IIC_M_NOSTART) != 0) {
+ reason = "first message without start";
+ break;
+ }
+ if (i == nmsgs - 1 && (msgs[i].flags & IIC_M_NOSTOP) != 0) {
+ reason = "last message without stop";
+ break;
+ }
+ if (msgs[i].len == 0) {
+ reason = "message with no data";
+ break;
+ }
+ if ((msgs[i].flags & IIC_M_RD) != 0 &&
+ msgs[i].len > CP2112_IIC_MAX_READ_LEN) {
+ reason = "too long read";
+ break;
+ }
+ if ((msgs[i].flags & IIC_M_RD) == 0 &&
+ msgs[i].len > SIZEOF_FIELD(i2c_write_req, data)) {
+ reason = "too long write";
+ break;
+ }
+ if ((msgs[i].flags & IIC_M_NOSTART) != 0) {
+ reason = "message without start or repeated start";
+ break;
+ }
+ if ((msgs[i].flags & IIC_M_NOSTOP) != 0 &&
+ (msgs[i].flags & IIC_M_RD) != 0) {
+ reason = "read without stop";
+ break;
+ }
+ if ((msgs[i].flags & IIC_M_NOSTOP) != 0 &&
+ psc->sc_version < CP2112_IIC_REPSTART_VER) {
+ reason = "write without stop";
+ break;
+ }
+ if ((msgs[i].flags & IIC_M_NOSTOP) != 0 &&
+ msgs[i].len > SIZEOF_FIELD(i2c_write_read_req, wdata)) {
+ reason = "too long write without stop";
+ break;
+ }
+ if (i > 0) {
+ if ((msgs[i - 1].flags & IIC_M_NOSTOP) != 0 &&
+ msgs[i].slave != msgs[i - 1].slave) {
+ reason = "change of slave without stop";
+ break;
+ }
+ if ((msgs[i - 1].flags & IIC_M_NOSTOP) != 0 &&
+ (msgs[i].flags & IIC_M_RD) == 0) {
+ reason = "write after repeated start";
+ break;
+ }
+ }
+ }
+ if (reason != NULL) {
+ if (bootverbose)
+ device_printf(dev, "unsupported i2c message: %s\n",
+ reason);
+ return (IIC_ENOTSUPP);
+ }
+
+ mtx_lock(&sc->io.lock);
+
+ for (i = 0; i < nmsgs; i++) {
+ if (i + 1 < nmsgs && (msgs[i].flags & IIC_M_NOSTOP) != 0) {
+ /*
+ * Combine <write><repeated start><read> into a single
+ * CP2112 operation.
+ */
+ struct i2c_write_read_req req;
+
+ KASSERT((msgs[i].flags & IIC_M_RD) == 0,
+ ("read without stop"));
+ KASSERT((msgs[i + 1].flags & IIC_M_RD) != 0,
+ ("write after write without stop"));
+ req.id = CP2112_REQ_SMB_WRITE_READ;
+ req.slave = msgs[i].slave & ~LSB;
+ to_read = msgs[i + 1].len;
+ req.rlen = htobe16(to_read);
+ req.wlen = msgs[i].len;
+ memcpy(req.wdata, msgs[i].buf, msgs[i].len);
+ err = cp2112iic_send_req(sc, &req, msgs[i].len + 5);
+
+ /*
+ * The next message is already handled.
+ * Also needed for read data to go into the right msg.
+ */
+ i++;
+ } else if ((msgs[i].flags & IIC_M_RD) != 0) {
+ struct i2c_read_req req;
+
+ req.id = CP2112_REQ_SMB_READ;
+ req.slave = msgs[i].slave & ~LSB;
+ to_read = msgs[i].len;
+ req.len = htobe16(to_read);
+ err = cp2112iic_send_req(sc, &req, sizeof(req));
+ } else {
+ struct i2c_write_req req;
+
+ req.id = CP2112_REQ_SMB_WRITE;
+ req.slave = msgs[i].slave & ~LSB;
+ req.len = msgs[i].len;
+ memcpy(req.data, msgs[i].buf, msgs[i].len);
+ to_read = 0;
+ err = cp2112iic_send_req(sc, &req, msgs[i].len + 3);
+ }
+ if (err != 0)
+ break;
+
+ err = cp2112iic_check_req_status(sc);
+ if (err != 0)
+ break;
+
+ read_off = 0;
+ while (to_read > 0) {
+ uint16_t act_read;
+
+ err = cp2112iic_read_data(sc, msgs[i].buf + read_off,
+ to_read, &act_read);
+ if (err != 0)
+ break;
+ KASSERT(act_read <= to_read, ("cp2112iic_read_data "
+ "returned more data than asked"));
+ read_off += act_read;
+ to_read -= act_read;
+ }
+ if (err != 0)
+ break;
+ }
+
+ mtx_unlock(&sc->io.lock);
+ return (err);
+}
+
+static int
+cp2112iic_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
+{
+ struct i2c_cfg_req i2c_cfg;
+ struct cp2112iic_softc *sc;
+ device_t cp2112;
+ u_int busfreq;
+ int err;
+
+ sc = device_get_softc(dev);
+ cp2112 = device_get_parent(dev);
+ if (sc->iicbus_dev == NULL)
+ busfreq = 100000;
+ else
+ busfreq = IICBUS_GET_FREQUENCY(sc->iicbus_dev, speed);
+
+ err = cp2112_get_report(cp2112, CP2112_REQ_SMB_CFG,
+ &i2c_cfg, sizeof(i2c_cfg));
+ if (err != 0) {
+ device_printf(dev, "failed to get CP2112_REQ_SMB_CFG report\n");
+ return (err);
+ }
+
+ if (oldaddr != NULL)
+ *oldaddr = i2c_cfg.slave_addr;
+ /*
+ * For simplicity we do not enable Auto Send Read
+ * because of erratum CP2112_E101 (fixed in version 3).
+ *
+ * TODO: set I2C parameters based on configuration preferences:
+ * - read and write timeouts (no timeout by default),
+ * - SCL low timeout (disabled by default),
+ * etc.
+ *
+ * TODO: should the device reset request (0x01) be sent?
+ * If the device disconnects as a result, then no.
+ */
+ i2c_cfg.speed = htobe32(busfreq);
+ if (addr != 0)
+ i2c_cfg.slave_addr = addr;
+ i2c_cfg.auto_send_read = 0;
+ i2c_cfg.retry_count = htobe16(1);
+ i2c_cfg.scl_low_timeout = 0;
+ if (bootverbose) {
+ device_printf(dev, "speed %d Hz\n", be32toh(i2c_cfg.speed));
+ device_printf(dev, "slave addr 0x%02x\n", i2c_cfg.slave_addr);
+ device_printf(dev, "auto send read %s\n",
+ i2c_cfg.auto_send_read ? "on" : "off");
+ device_printf(dev, "write timeout %d ms (0 - disabled)\n",
+ be16toh(i2c_cfg.write_timeout));
+ device_printf(dev, "read timeout %d ms (0 - disabled)\n",
+ be16toh(i2c_cfg.read_timeout));
+ device_printf(dev, "scl low timeout %s\n",
+ i2c_cfg.scl_low_timeout ? "on" : "off");
+ device_printf(dev, "retry count %d (0 - no limit)\n",
+ be16toh(i2c_cfg.retry_count));
+ }
+ err = cp2112_set_report(cp2112, CP2112_REQ_SMB_CFG,
+ &i2c_cfg, sizeof(i2c_cfg));
+ if (err != 0) {
+ device_printf(dev, "failed to set CP2112_REQ_SMB_CFG report\n");
+ return (err);
+ }
+ return (0);
+}
+
+static int
+cp2112iic_probe(device_t dev)
+{
+ device_set_desc(dev, "CP2112 I2C interface");
+ return (BUS_PROBE_SPECIFIC);
+}
+
+static int
+cp2112iic_attach(device_t dev)
+{
+ struct cp2112iic_softc *sc;
+ struct cp2112_softc *psc;
+ device_t cp2112;
+ int err;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ cp2112 = device_get_parent(dev);
+ psc = device_get_softc(cp2112);
+
+ mtx_init(&sc->io.lock, "cp2112iic lock", NULL, MTX_DEF | MTX_RECURSE);
+ cv_init(&sc->io.cv, "cp2112iic cv");
+
+ err = usbd_transfer_setup(psc->sc_udev,
+ &psc->sc_iface_index, sc->xfers, cp2112iic_config,
+ nitems(cp2112iic_config), sc, &sc->io.lock);
+ if (err != 0) {
+ device_printf(dev, "usbd_transfer_setup failed %d\n", err);
+ goto detach;
+ }
+
+ /* Prepare to receive interrupts. */
+ mtx_lock(&sc->io.lock);
+ usbd_transfer_start(sc->xfers[CP2112_INTR_IN]);
+ mtx_unlock(&sc->io.lock);
+
+ sc->iicbus_dev = device_add_child(dev, "iicbus", DEVICE_UNIT_ANY);
+ if (sc->iicbus_dev == NULL) {
+ device_printf(dev, "iicbus creation failed\n");
+ err = ENXIO;
+ goto detach;
+ }
+ bus_attach_children(dev);
+ return (0);
+
+detach:
+ cp2112iic_detach(dev);
+ return (err);
+}
+
+static int
+cp2112iic_detach(device_t dev)
+{
+ struct cp2112iic_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+ err = bus_generic_detach(dev);
+ if (err != 0)
+ return (err);
+
+ mtx_lock(&sc->io.lock);
+ usbd_transfer_stop(sc->xfers[CP2112_INTR_IN]);
+ mtx_unlock(&sc->io.lock);
+ usbd_transfer_unsetup(sc->xfers, nitems(cp2112iic_config));
+
+ cv_destroy(&sc->io.cv);
+ mtx_destroy(&sc->io.lock);
+
+ return (0);
+}
+
+static device_method_t cp2112hid_methods[] = {
+ DEVMETHOD(device_probe, cp2112_probe),
+ DEVMETHOD(device_attach, cp2112_attach),
+ DEVMETHOD(device_detach, bus_generic_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t cp2112hid_driver = {
+ .name = "cp2112hid",
+ .methods = cp2112hid_methods,
+ .size = sizeof(struct cp2112_softc),
+};
+
+DRIVER_MODULE(cp2112hid, uhub, cp2112hid_driver, NULL, NULL);
+MODULE_DEPEND(cp2112hid, usb, 1, 1, 1);
+MODULE_VERSION(cp2112hid, 1);
+USB_PNP_HOST_INFO(cp2112_devs);
+
+static device_method_t cp2112gpio_methods[] = {
+ /* Device */
+ DEVMETHOD(device_probe, cp2112gpio_probe),
+ DEVMETHOD(device_attach, cp2112gpio_attach),
+ DEVMETHOD(device_detach, cp2112gpio_detach),
+
+ /* GPIO */
+ DEVMETHOD(gpio_get_bus, cp2112_gpio_get_bus),
+ DEVMETHOD(gpio_pin_max, cp2112_gpio_pin_max),
+ DEVMETHOD(gpio_pin_get, cp2112_gpio_pin_get),
+ DEVMETHOD(gpio_pin_set, cp2112_gpio_pin_set),
+ DEVMETHOD(gpio_pin_toggle, cp2112_gpio_pin_toggle),
+ DEVMETHOD(gpio_pin_getname, cp2112_gpio_pin_getname),
+ DEVMETHOD(gpio_pin_getcaps, cp2112_gpio_pin_getcaps),
+ DEVMETHOD(gpio_pin_getflags, cp2112_gpio_pin_getflags),
+ DEVMETHOD(gpio_pin_setflags, cp2112_gpio_pin_setflags),
+
+ DEVMETHOD_END
+};
+
+static driver_t cp2112gpio_driver = {
+ .name = "gpio",
+ .methods = cp2112gpio_methods,
+ .size = sizeof(struct cp2112gpio_softc),
+};
+
+DRIVER_MODULE(cp2112gpio, cp2112hid, cp2112gpio_driver, NULL, NULL);
+MODULE_DEPEND(cp2112gpio, cp2112hid, 1, 1, 1);
+MODULE_DEPEND(cp2112gpio, gpiobus, 1, 1, 1);
+MODULE_VERSION(cp2112gpio, 1);
+
+static device_method_t cp2112iic_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, cp2112iic_probe),
+ DEVMETHOD(device_attach, cp2112iic_attach),
+ DEVMETHOD(device_detach, cp2112iic_detach),
+
+ /* I2C methods */
+ DEVMETHOD(iicbus_transfer, cp2112iic_transfer),
+ DEVMETHOD(iicbus_reset, cp2112iic_reset),
+ DEVMETHOD(iicbus_callback, iicbus_null_callback),
+
+ DEVMETHOD_END
+};
+
+static driver_t cp2112iic_driver = {
+ "iichb",
+ cp2112iic_methods,
+ sizeof(struct cp2112iic_softc)
+};
+
+DRIVER_MODULE(cp2112iic, cp2112hid, cp2112iic_driver, NULL, NULL);
+MODULE_DEPEND(cp2112iic, cp2112hid, 1, 1, 1);
+MODULE_DEPEND(cp2112iic, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
+MODULE_VERSION(cp2112iic, 1);
diff --git a/sys/dev/usb/misc/i2ctinyusb.c b/sys/dev/usb/misc/i2ctinyusb.c
new file mode 100644
index 000000000000..c6e8f946d78e
--- /dev/null
+++ b/sys/dev/usb/misc/i2ctinyusb.c
@@ -0,0 +1,301 @@
+/*-
+ * Copyright (c) 2024 Denis Bodor <dbodor@rollmops.ninja>
+ * 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.
+ */
+
+/*
+ * i2c-tiny-usb, DIY USB to IIC bridge (using AVR or RP2040) from
+ * Till Harbaum & Nicolai Electronics
+ * See :
+ * https://github.com/harbaum/I2C-Tiny-USB
+ * and
+ * https://github.com/Nicolai-Electronics/rp2040-i2c-interface
+ */
+
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/unistd.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbhid.h>
+#include <dev/usb/usb_device.h>
+
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+#include "iicbus_if.h"
+
+// commands via USB, must match command ids in the firmware
+#define CMD_ECHO 0
+#define CMD_GET_FUNC 1
+#define CMD_SET_DELAY 2
+#define CMD_GET_STATUS 3
+#define CMD_I2C_IO 4
+#define CMD_SET_LED 8
+#define CMD_I2C_IO_BEGIN (1 << 0)
+#define CMD_I2C_IO_END (1 << 1)
+#define STATUS_IDLE 0
+#define STATUS_ADDRESS_ACK 1
+#define STATUS_ADDRESS_NAK 2
+
+struct i2ctinyusb_softc {
+ struct usb_device *sc_udev;
+ device_t sc_iic_dev;
+ device_t iicbus_dev;
+ struct mtx sc_mtx;
+};
+
+#define USB_VENDOR_EZPROTOTYPES 0x1c40
+#define USB_VENDOR_FTDI 0x0403
+
+static const STRUCT_USB_HOST_ID i2ctinyusb_devs[] = {
+ { USB_VPI(USB_VENDOR_EZPROTOTYPES, 0x0534, 0) },
+ { USB_VPI(USB_VENDOR_FTDI, 0xc631, 0) },
+};
+
+/* Prototypes. */
+static int i2ctinyusb_probe(device_t dev);
+static int i2ctinyusb_attach(device_t dev);
+static int i2ctinyusb_detach(device_t dev);
+static int i2ctinyusb_transfer(device_t dev, struct iic_msg *msgs,
+ uint32_t nmsgs);
+static int i2ctinyusb_reset(device_t dev, u_char speed, u_char addr,
+ u_char *oldaddr);
+
+static int
+usb_read(struct i2ctinyusb_softc *sc, int cmd, int value, int index,
+ void *data, int len)
+{
+ int error;
+ struct usb_device_request req;
+ uint16_t actlen;
+
+ req.bmRequestType = UT_READ_VENDOR_INTERFACE;
+ req.bRequest = cmd;
+ USETW(req.wValue, value);
+ USETW(req.wIndex, (index >> 1));
+ USETW(req.wLength, len);
+
+ error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, data, 0,
+ &actlen, 2000);
+
+ if (error)
+ actlen = -1;
+
+ return (actlen);
+}
+
+static int
+usb_write(struct i2ctinyusb_softc *sc, int cmd, int value, int index,
+ void *data, int len)
+{
+ int error;
+ struct usb_device_request req;
+ uint16_t actlen;
+
+ req.bmRequestType = UT_WRITE_VENDOR_INTERFACE;
+ req.bRequest = cmd;
+ USETW(req.wValue, value);
+ USETW(req.wIndex, (index >> 1));
+ USETW(req.wLength, len);
+
+ error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, data, 0,
+ &actlen, 2000);
+
+ if (error) {
+ actlen = -1;
+ }
+
+ return (actlen);
+}
+
+static int
+i2ctinyusb_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa;
+
+ uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ if (usbd_lookup_id_by_uaa(i2ctinyusb_devs, sizeof(i2ctinyusb_devs),
+ uaa) == 0) {
+ device_set_desc(dev, "I2C-Tiny-USB I2C interface");
+ return (BUS_PROBE_DEFAULT);
+ }
+
+ return (ENXIO);
+}
+
+static int
+i2ctinyusb_attach(device_t dev)
+{
+ struct i2ctinyusb_softc *sc;
+ struct usb_attach_arg *uaa;
+ int err;
+
+ sc = device_get_softc(dev);
+
+ uaa = device_get_ivars(dev);
+ device_set_usb_desc(dev);
+
+ sc->sc_udev = uaa->device;
+ mtx_init(&sc->sc_mtx, "i2ctinyusb lock", NULL, MTX_DEF | MTX_RECURSE);
+
+ sc->iicbus_dev = device_add_child(dev, "iicbus", DEVICE_UNIT_ANY);
+ if (sc->iicbus_dev == NULL) {
+ device_printf(dev, "iicbus creation failed\n");
+ err = ENXIO;
+ goto detach;
+ }
+ bus_attach_children(dev);
+
+ return (0);
+
+detach:
+ i2ctinyusb_detach(dev);
+ return (err);
+}
+
+static int
+i2ctinyusb_detach(device_t dev)
+{
+ struct i2ctinyusb_softc *sc;
+ int err;
+
+ sc = device_get_softc(dev);
+
+ err = bus_generic_detach(dev);
+ if (err != 0)
+ return (err);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static int
+i2ctinyusb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
+{
+ struct i2ctinyusb_softc *sc;
+ uint32_t i;
+ int ret = 0;
+ int cmd = CMD_I2C_IO;
+ struct iic_msg *pmsg;
+ unsigned char pstatus;
+
+ sc = device_get_softc(dev);
+
+ mtx_lock(&sc->sc_mtx);
+
+ for (i = 0; i < nmsgs; i++) {
+ pmsg = &msgs[i];
+ if (i == 0)
+ cmd |= CMD_I2C_IO_BEGIN;
+ if (i == nmsgs - 1)
+ cmd |= CMD_I2C_IO_END;
+
+ if ((msgs[i].flags & IIC_M_RD) != 0) {
+ if ((ret = usb_read(sc, cmd, pmsg->flags, pmsg->slave, pmsg->buf,
+ pmsg->len)) != pmsg->len) {
+ printf("Read error: got %u\n", ret);
+ ret = EIO;
+ goto out;
+ }
+ } else {
+ if ((ret = usb_write(sc, cmd, pmsg->flags, pmsg->slave, pmsg->buf,
+ pmsg->len)) != pmsg->len) {
+ printf("Write error: got %u\n", ret);
+ ret = EIO;
+ goto out;
+ }
+
+ }
+ // check status
+ if ((ret = usb_read(sc, CMD_GET_STATUS, 0, 0, &pstatus, 1)) != 1) {
+ ret = EIO;
+ goto out;
+ }
+
+ if (pstatus == STATUS_ADDRESS_NAK) {
+ ret = EIO;
+ goto out;
+ }
+ }
+
+ ret = 0;
+
+out:
+ mtx_unlock(&sc->sc_mtx);
+ return (ret);
+}
+
+static int
+i2ctinyusb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
+{
+ struct i2ctinyusb_softc *sc;
+ int ret;
+
+ sc = device_get_softc(dev);
+
+ mtx_lock(&sc->sc_mtx);
+ ret = usb_write(sc, CMD_SET_DELAY, 10, 0, NULL, 0);
+ mtx_unlock(&sc->sc_mtx);
+
+ if (ret < 0)
+ printf("i2ctinyusb_reset error!\n");
+
+ return (0);
+}
+
+static device_method_t i2ctinyusb_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, i2ctinyusb_probe),
+ DEVMETHOD(device_attach, i2ctinyusb_attach),
+ DEVMETHOD(device_detach, i2ctinyusb_detach),
+
+ /* I2C methods */
+ DEVMETHOD(iicbus_transfer, i2ctinyusb_transfer),
+ DEVMETHOD(iicbus_reset, i2ctinyusb_reset),
+ DEVMETHOD(iicbus_callback, iicbus_null_callback),
+
+ DEVMETHOD_END
+};
+
+static driver_t i2ctinyusb_driver = {
+ .name = "iichb",
+ .methods = i2ctinyusb_methods,
+ .size = sizeof(struct i2ctinyusb_softc),
+};
+
+DRIVER_MODULE(i2ctinyusb, uhub, i2ctinyusb_driver, NULL, NULL);
+MODULE_DEPEND(i2ctinyusb, usb, 1, 1, 1);
+MODULE_DEPEND(i2ctinyusb, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
+MODULE_VERSION(i2ctinyusb, 1);
+
+/* vi: set ts=8 sw=8: */
diff --git a/sys/dev/usb/misc/udbp.c b/sys/dev/usb/misc/udbp.c
new file mode 100644
index 000000000000..1d348557e1a1
--- /dev/null
+++ b/sys/dev/usb/misc/udbp.c
@@ -0,0 +1,851 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * 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.
+ *
+ */
+
+/* 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
+ * streaming, and <5k to avoid overflowing the system with small TDs).
+ */
+
+/* probe/attach/detach:
+ * Connect the driver to the hardware and netgraph
+ *
+ * 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR udbp_debug
+#include <dev/usb/usb_debug.h>
+
+#include <sys/mbuf.h>
+
+#include <netgraph/ng_message.h>
+#include <netgraph/netgraph.h>
+#include <netgraph/ng_parse.h>
+#include <netgraph/bluetooth/include/ng_bluetooth.h>
+
+#include <dev/usb/misc/udbp.h>
+
+#ifdef USB_DEBUG
+static int udbp_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, udbp, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB udbp");
+SYSCTL_INT(_hw_usb_udbp, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &udbp_debug, 0, "udbp debug level");
+#endif
+
+#define UDBP_TIMEOUT 2000 /* timeout on outbound transfers, in
+ * msecs */
+#define UDBP_BUFFERSIZE MCLBYTES /* maximum number of bytes in one
+ * transfer */
+#define UDBP_T_WR 0
+#define UDBP_T_RD 1
+#define UDBP_T_WR_CS 2
+#define UDBP_T_RD_CS 3
+#define UDBP_T_MAX 4
+#define UDBP_Q_MAXLEN 50
+
+struct udbp_softc {
+ struct mtx sc_mtx;
+ struct ng_bt_mbufq sc_xmitq_hipri; /* hi-priority transmit queue */
+ struct ng_bt_mbufq sc_xmitq; /* low-priority transmit queue */
+
+ struct usb_xfer *sc_xfer[UDBP_T_MAX];
+ node_p sc_node; /* back pointer to node */
+ hook_p sc_hook; /* pointer to the hook */
+ struct mbuf *sc_bulk_in_buffer;
+
+ uint32_t sc_packets_in; /* packets in from downstream */
+ uint32_t sc_packets_out; /* packets out towards downstream */
+
+ uint8_t sc_flags;
+#define UDBP_FLAG_READ_STALL 0x01 /* read transfer stalled */
+#define UDBP_FLAG_WRITE_STALL 0x02 /* write transfer stalled */
+
+ uint8_t sc_name[16];
+};
+
+/* prototypes */
+
+static int udbp_modload(module_t mod, int event, void *data);
+
+static device_probe_t udbp_probe;
+static device_attach_t udbp_attach;
+static device_detach_t udbp_detach;
+
+static usb_callback_t udbp_bulk_read_callback;
+static usb_callback_t udbp_bulk_read_clear_stall_callback;
+static usb_callback_t udbp_bulk_write_callback;
+static usb_callback_t udbp_bulk_write_clear_stall_callback;
+
+static void udbp_bulk_read_complete(node_p, hook_p, void *, int);
+
+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,
+};
+
+/* USB config */
+static const struct usb_config udbp_config[UDBP_T_MAX] = {
+ [UDBP_T_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UDBP_BUFFERSIZE,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &udbp_bulk_write_callback,
+ .timeout = UDBP_TIMEOUT,
+ },
+
+ [UDBP_T_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UDBP_BUFFERSIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &udbp_bulk_read_callback,
+ },
+
+ [UDBP_T_WR_CS] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &udbp_bulk_write_clear_stall_callback,
+ .timeout = 1000, /* 1 second */
+ .interval = 50, /* 50ms */
+ },
+
+ [UDBP_T_RD_CS] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &udbp_bulk_read_clear_stall_callback,
+ .timeout = 1000, /* 1 second */
+ .interval = 50, /* 50ms */
+ },
+};
+
+static device_method_t udbp_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, udbp_probe),
+ DEVMETHOD(device_attach, udbp_attach),
+ DEVMETHOD(device_detach, udbp_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t udbp_driver = {
+ .name = "udbp",
+ .methods = udbp_methods,
+ .size = sizeof(struct udbp_softc),
+};
+
+static const STRUCT_USB_HOST_ID udbp_devs[] = {
+ {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U258, 0)},
+ {USB_VPI(USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_TURBOCONNECT, 0)},
+ {USB_VPI(USB_VENDOR_NETCHIP, USB_PRODUCT_NETCHIP_GADGETZERO, 0)},
+ {USB_VPI(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2301, 0)},
+ {USB_VPI(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2302, 0)},
+ {USB_VPI(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL27A1, 0)},
+ {USB_VPI(USB_VENDOR_ANCHOR, USB_PRODUCT_ANCHOR_EZLINK, 0)},
+ {USB_VPI(USB_VENDOR_GENESYS, USB_PRODUCT_GENESYS_GL620USB, 0)},
+};
+
+DRIVER_MODULE(udbp, uhub, udbp_driver, udbp_modload, NULL);
+MODULE_DEPEND(udbp, netgraph, NG_ABI_VERSION, NG_ABI_VERSION, NG_ABI_VERSION);
+MODULE_DEPEND(udbp, usb, 1, 1, 1);
+MODULE_VERSION(udbp, 1);
+USB_PNP_HOST_INFO(udbp_devs);
+
+static int
+udbp_modload(module_t mod, int event, void *data)
+{
+ int error;
+
+ switch (event) {
+ case MOD_LOAD:
+ error = ng_newtype(&ng_udbp_typestruct);
+ if (error != 0) {
+ printf("%s: Could not register "
+ "Netgraph node type, error=%d\n",
+ NG_UDBP_NODE_TYPE, error);
+ }
+ break;
+
+ case MOD_UNLOAD:
+ error = ng_rmtype(&ng_udbp_typestruct);
+ break;
+
+ default:
+ error = EOPNOTSUPP;
+ break;
+ }
+ return (error);
+}
+
+static int
+udbp_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != 0)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != 0)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(udbp_devs, sizeof(udbp_devs), uaa));
+}
+
+static int
+udbp_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct udbp_softc *sc = device_get_softc(dev);
+ int error;
+
+ device_set_usb_desc(dev);
+
+ snprintf(sc->sc_name, sizeof(sc->sc_name),
+ "%s", device_get_nameunit(dev));
+
+ mtx_init(&sc->sc_mtx, "udbp lock", NULL, MTX_DEF | MTX_RECURSE);
+
+ error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex,
+ sc->sc_xfer, udbp_config, UDBP_T_MAX, sc, &sc->sc_mtx);
+ if (error) {
+ DPRINTF("error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+ NG_BT_MBUFQ_INIT(&sc->sc_xmitq, UDBP_Q_MAXLEN);
+
+ NG_BT_MBUFQ_INIT(&sc->sc_xmitq_hipri, UDBP_Q_MAXLEN);
+
+ /* create Netgraph node */
+
+ if (ng_make_node_common(&ng_udbp_typestruct, &sc->sc_node) != 0) {
+ printf("%s: Could not create Netgraph node\n",
+ sc->sc_name);
+ sc->sc_node = NULL;
+ goto detach;
+ }
+ /* name node */
+
+ if (ng_name_node(sc->sc_node, sc->sc_name) != 0) {
+ printf("%s: Could not name node\n",
+ sc->sc_name);
+ NG_NODE_UNREF(sc->sc_node);
+ sc->sc_node = NULL;
+ goto detach;
+ }
+ NG_NODE_SET_PRIVATE(sc->sc_node, sc);
+
+ /* the device is now operational */
+
+ return (0); /* success */
+
+detach:
+ udbp_detach(dev);
+ return (ENOMEM); /* failure */
+}
+
+static int
+udbp_detach(device_t dev)
+{
+ struct udbp_softc *sc = device_get_softc(dev);
+
+ /* destroy Netgraph node */
+
+ if (sc->sc_node != NULL) {
+ NG_NODE_SET_PRIVATE(sc->sc_node, NULL);
+ ng_rmnode_self(sc->sc_node);
+ sc->sc_node = NULL;
+ }
+ /* free USB transfers, if any */
+
+ usbd_transfer_unsetup(sc->sc_xfer, UDBP_T_MAX);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ /* destroy queues */
+
+ NG_BT_MBUFQ_DESTROY(&sc->sc_xmitq);
+ NG_BT_MBUFQ_DESTROY(&sc->sc_xmitq_hipri);
+
+ /* extra check */
+
+ if (sc->sc_bulk_in_buffer) {
+ m_freem(sc->sc_bulk_in_buffer);
+ sc->sc_bulk_in_buffer = NULL;
+ }
+ return (0); /* success */
+}
+
+static void
+udbp_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct udbp_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ /* allocate new mbuf */
+
+ MGETHDR(m, M_NOWAIT, MT_DATA);
+
+ if (m == NULL) {
+ goto tr_setup;
+ }
+
+ if (!(MCLGET(m, M_NOWAIT))) {
+ m_freem(m);
+ goto tr_setup;
+ }
+ m->m_pkthdr.len = m->m_len = actlen;
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, m->m_data, actlen);
+
+ sc->sc_bulk_in_buffer = m;
+
+ DPRINTF("received package %d bytes\n", actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ if (sc->sc_bulk_in_buffer) {
+ ng_send_fn(sc->sc_node, NULL, &udbp_bulk_read_complete, NULL, 0);
+ return;
+ }
+ if (sc->sc_flags & UDBP_FLAG_READ_STALL) {
+ usbd_transfer_start(sc->sc_xfer[UDBP_T_RD_CS]);
+ return;
+ }
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ sc->sc_flags |= UDBP_FLAG_READ_STALL;
+ usbd_transfer_start(sc->sc_xfer[UDBP_T_RD_CS]);
+ }
+ return;
+ }
+}
+
+static void
+udbp_bulk_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct udbp_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_xfer *xfer_other = sc->sc_xfer[UDBP_T_RD];
+
+ if (usbd_clear_stall_callback(xfer, xfer_other)) {
+ DPRINTF("stall cleared\n");
+ sc->sc_flags &= ~UDBP_FLAG_READ_STALL;
+ usbd_transfer_start(xfer_other);
+ }
+}
+
+static void
+udbp_bulk_read_complete(node_p node, hook_p hook, void *arg1, int arg2)
+{
+ struct udbp_softc *sc = NG_NODE_PRIVATE(node);
+ struct mbuf *m;
+ int error;
+
+ if (sc == NULL) {
+ return;
+ }
+ mtx_lock(&sc->sc_mtx);
+
+ m = sc->sc_bulk_in_buffer;
+
+ if (m) {
+ sc->sc_bulk_in_buffer = NULL;
+
+ if ((sc->sc_hook == NULL) ||
+ NG_HOOK_NOT_VALID(sc->sc_hook)) {
+ DPRINTF("No upstream hook\n");
+ goto done;
+ }
+ sc->sc_packets_in++;
+
+ NG_SEND_DATA_ONLY(error, sc->sc_hook, m);
+
+ m = NULL;
+ }
+done:
+ if (m) {
+ m_freem(m);
+ }
+ /* start USB bulk-in transfer, if not already started */
+
+ usbd_transfer_start(sc->sc_xfer[UDBP_T_RD]);
+
+ mtx_unlock(&sc->sc_mtx);
+}
+
+static void
+udbp_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct udbp_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ sc->sc_packets_out++;
+
+ case USB_ST_SETUP:
+ if (sc->sc_flags & UDBP_FLAG_WRITE_STALL) {
+ usbd_transfer_start(sc->sc_xfer[UDBP_T_WR_CS]);
+ return;
+ }
+ /* get next mbuf, if any */
+
+ NG_BT_MBUFQ_DEQUEUE(&sc->sc_xmitq_hipri, m);
+ if (m == NULL) {
+ NG_BT_MBUFQ_DEQUEUE(&sc->sc_xmitq, m);
+ if (m == NULL) {
+ DPRINTF("Data queue is empty\n");
+ return;
+ }
+ }
+ if (m->m_pkthdr.len > MCLBYTES) {
+ DPRINTF("truncating large packet "
+ "from %d to %d bytes\n", m->m_pkthdr.len,
+ MCLBYTES);
+ m->m_pkthdr.len = MCLBYTES;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len);
+
+ usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len);
+
+ DPRINTF("packet out: %d bytes\n", m->m_pkthdr.len);
+
+ m_freem(m);
+
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ sc->sc_flags |= UDBP_FLAG_WRITE_STALL;
+ usbd_transfer_start(sc->sc_xfer[UDBP_T_WR_CS]);
+ }
+ return;
+ }
+}
+
+static void
+udbp_bulk_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct udbp_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_xfer *xfer_other = sc->sc_xfer[UDBP_T_WR];
+
+ if (usbd_clear_stall_callback(xfer, xfer_other)) {
+ DPRINTF("stall cleared\n");
+ sc->sc_flags &= ~UDBP_FLAG_WRITE_STALL;
+ usbd_transfer_start(xfer_other);
+ }
+}
+
+/***********************************************************************
+ * 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)
+{
+ struct udbp_softc *sc = NG_NODE_PRIVATE(node);
+ int32_t error = 0;
+
+ if (strcmp(name, NG_UDBP_HOOK_NAME)) {
+ return (EINVAL);
+ }
+ mtx_lock(&sc->sc_mtx);
+
+ if (sc->sc_hook != NULL) {
+ error = EISCONN;
+ } else {
+ sc->sc_hook = hook;
+ NG_HOOK_SET_PRIVATE(hook, NULL);
+ }
+
+ mtx_unlock(&sc->sc_mtx);
+
+ return (error);
+}
+
+/*
+ * 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)
+{
+ struct udbp_softc *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;
+ mtx_lock(&sc->sc_mtx);
+ stats->packets_in = sc->sc_packets_in;
+ stats->packets_out = sc->sc_packets_out;
+ mtx_unlock(&sc->sc_mtx);
+ break;
+ }
+ case NGM_UDBP_SET_FLAG:
+ if (msg->header.arglen != sizeof(uint32_t)) {
+ error = EINVAL;
+ break;
+ }
+ DPRINTF("flags = 0x%08x\n",
+ *((uint32_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)
+{
+ struct udbp_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
+ struct ng_bt_mbufq *queue_ptr;
+ struct mbuf *m;
+ struct ng_tag_prio *ptag;
+ int error;
+
+ if (sc == NULL) {
+ NG_FREE_ITEM(item);
+ return (EHOSTDOWN);
+ }
+ NGI_GET_M(item, m);
+ NG_FREE_ITEM(item);
+
+ /*
+ * Now queue the data for when it can be sent
+ */
+ ptag = (void *)m_tag_locate(m, NGM_GENERIC_COOKIE,
+ NG_TAG_PRIO, NULL);
+
+ if (ptag && (ptag->priority > NG_PRIO_CUTOFF))
+ queue_ptr = &sc->sc_xmitq_hipri;
+ else
+ queue_ptr = &sc->sc_xmitq;
+
+ mtx_lock(&sc->sc_mtx);
+
+ if (NG_BT_MBUFQ_FULL(queue_ptr)) {
+ NG_BT_MBUFQ_DROP(queue_ptr);
+ NG_FREE_M(m);
+ error = ENOBUFS;
+ } else {
+ NG_BT_MBUFQ_ENQUEUE(queue_ptr, m);
+ /*
+ * start bulk-out transfer, if not already started:
+ */
+ usbd_transfer_start(sc->sc_xfer[UDBP_T_WR]);
+ error = 0;
+ }
+
+ mtx_unlock(&sc->sc_mtx);
+
+ return (error);
+}
+
+/*
+ * Do local shutdown processing..
+ * We are a persistent device, we refuse to go away, and
+ * only remove our links and reset ourself.
+ */
+static int
+ng_udbp_rmnode(node_p node)
+{
+ struct udbp_softc *sc = NG_NODE_PRIVATE(node);
+
+ /* Let old node go */
+ NG_NODE_SET_PRIVATE(node, NULL);
+ NG_NODE_UNREF(node); /* forget it ever existed */
+
+ if (sc == NULL) {
+ goto done;
+ }
+ /* Create Netgraph node */
+ if (ng_make_node_common(&ng_udbp_typestruct, &sc->sc_node) != 0) {
+ printf("%s: Could not create Netgraph node\n",
+ sc->sc_name);
+ sc->sc_node = NULL;
+ goto done;
+ }
+ /* Name node */
+ if (ng_name_node(sc->sc_node, sc->sc_name) != 0) {
+ printf("%s: Could not name Netgraph node\n",
+ sc->sc_name);
+ NG_NODE_UNREF(sc->sc_node);
+ sc->sc_node = NULL;
+ goto done;
+ }
+ NG_NODE_SET_PRIVATE(sc->sc_node, sc);
+
+done:
+ if (sc) {
+ mtx_unlock(&sc->sc_mtx);
+ }
+ return (0);
+}
+
+/*
+ * 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)
+{
+ struct udbp_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
+
+ /* force outward queueing */
+ NG_HOOK_FORCE_QUEUE(NG_HOOK_PEER(hook));
+
+ mtx_lock(&sc->sc_mtx);
+
+ sc->sc_flags |= (UDBP_FLAG_READ_STALL |
+ UDBP_FLAG_WRITE_STALL);
+
+ /* start bulk-in transfer */
+ usbd_transfer_start(sc->sc_xfer[UDBP_T_RD]);
+
+ /* start bulk-out transfer */
+ usbd_transfer_start(sc->sc_xfer[UDBP_T_WR]);
+
+ mtx_unlock(&sc->sc_mtx);
+
+ return (0);
+}
+
+/*
+ * Dook disconnection
+ *
+ * For this type, removal of the last link destroys the node
+ */
+static int
+ng_udbp_disconnect(hook_p hook)
+{
+ struct udbp_softc *sc = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
+ int error = 0;
+
+ if (sc != NULL) {
+ mtx_lock(&sc->sc_mtx);
+
+ if (hook != sc->sc_hook) {
+ error = EINVAL;
+ } else {
+ /* stop bulk-in transfer */
+ usbd_transfer_stop(sc->sc_xfer[UDBP_T_RD_CS]);
+ usbd_transfer_stop(sc->sc_xfer[UDBP_T_RD]);
+
+ /* stop bulk-out transfer */
+ usbd_transfer_stop(sc->sc_xfer[UDBP_T_WR_CS]);
+ usbd_transfer_stop(sc->sc_xfer[UDBP_T_WR]);
+
+ /* cleanup queues */
+ NG_BT_MBUFQ_DRAIN(&sc->sc_xmitq);
+ NG_BT_MBUFQ_DRAIN(&sc->sc_xmitq_hipri);
+
+ if (sc->sc_bulk_in_buffer) {
+ m_freem(sc->sc_bulk_in_buffer);
+ sc->sc_bulk_in_buffer = NULL;
+ }
+ sc->sc_hook = NULL;
+ }
+
+ mtx_unlock(&sc->sc_mtx);
+ }
+ 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 (error);
+}
diff --git a/sys/dev/usb/misc/udbp.h b/sys/dev/usb/misc/udbp.h
new file mode 100644
index 000000000000..a12cc739ef80
--- /dev/null
+++ b/sys/dev/usb/misc/udbp.h
@@ -0,0 +1,77 @@
+/*-
+ * 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.
+ */
+
+#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 {
+ uint32_t packets_in; /* packets in from downstream */
+ uint32_t packets_out; /* packets out towards downstream */
+};
+
+/*
+ * This is used to define the 'parse type' for a struct ngudbpstat, which
+ * is basically 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/dev/usb/misc/ugold.c b/sys/dev/usb/misc/ugold.c
new file mode 100644
index 000000000000..1b5f54bc679b
--- /dev/null
+++ b/sys/dev/usb/misc/ugold.c
@@ -0,0 +1,402 @@
+/* $OpenBSD: ugold.c,v 1.7 2014/12/11 18:39:27 mpi Exp $ */
+
+/*
+ * Copyright (c) 2013 Takayoshi SASANO <sasano@openbsd.org>
+ * Copyright (c) 2013 Martin Pieuchot <mpi@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 DISCAIMS 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.
+ */
+
+/* Driver for Microdia's HID based TEMPer Temperature sensor */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/conf.h>
+
+#include <dev/hid/hid.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbhid.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR usb_debug
+#include <dev/usb/usb_debug.h>
+
+#define UGOLD_INNER 0
+#define UGOLD_OUTER 1
+#define UGOLD_MAX_SENSORS 2
+
+#define UGOLD_CMD_DATA 0x80
+#define UGOLD_CMD_INIT 0x82
+
+enum {
+ UGOLD_INTR_DT,
+ UGOLD_N_TRANSFER,
+};
+
+/*
+ * This driver only uses two of the three known commands for the
+ * TEMPerV1.2 device.
+ *
+ * The first byte of the answer corresponds to the command and the
+ * second one seems to be the size (in bytes) of the answer.
+ *
+ * The device always sends 8 bytes and if the length of the answer
+ * is less than that, it just leaves the last bytes untouched. That
+ * is why most of the time the last n bytes of the answers are the
+ * same.
+ *
+ * The third command below seems to generate two answers with a
+ * string corresponding to the device, for example:
+ * 'TEMPer1F' and '1.1Per1F' (here Per1F is repeated).
+ */
+static uint8_t cmd_data[8] = {0x01, 0x80, 0x33, 0x01, 0x00, 0x00, 0x00, 0x00};
+static uint8_t cmd_init[8] = {0x01, 0x82, 0x77, 0x01, 0x00, 0x00, 0x00, 0x00};
+
+#if 0
+static uint8_t cmd_type[8] = {0x01, 0x86, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00};
+
+#endif
+
+struct ugold_softc;
+struct ugold_readout_msg {
+ struct usb_proc_msg hdr;
+ struct ugold_softc *sc;
+};
+
+struct ugold_softc {
+ struct usb_device *sc_udev;
+ struct usb_xfer *sc_xfer[UGOLD_N_TRANSFER];
+
+ struct callout sc_callout;
+ struct mtx sc_mtx;
+ struct ugold_readout_msg sc_readout_msg[2];
+
+ int sc_num_sensors;
+ int sc_sensor[UGOLD_MAX_SENSORS];
+ int sc_calib[UGOLD_MAX_SENSORS];
+ int sc_valid[UGOLD_MAX_SENSORS];
+ uint8_t sc_report_id;
+ uint8_t sc_iface_index[2];
+};
+
+/* prototypes */
+
+static device_probe_t ugold_probe;
+static device_attach_t ugold_attach;
+static device_detach_t ugold_detach;
+
+static usb_proc_callback_t ugold_readout_msg;
+
+static usb_callback_t ugold_intr_callback;
+
+static device_method_t ugold_methods[] = {
+ DEVMETHOD(device_probe, ugold_probe),
+ DEVMETHOD(device_attach, ugold_attach),
+ DEVMETHOD(device_detach, ugold_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t ugold_driver = {
+ .name = "ugold",
+ .methods = ugold_methods,
+ .size = sizeof(struct ugold_softc),
+};
+
+static const STRUCT_USB_HOST_ID ugold_devs[] = {
+ {USB_VPI(USB_VENDOR_CHICONY2, USB_PRODUCT_CHICONY2_TEMPER, 0)},
+};
+
+DRIVER_MODULE(ugold, uhub, ugold_driver, NULL, NULL);
+MODULE_DEPEND(ugold, usb, 1, 1, 1);
+MODULE_DEPEND(ugold, hid, 1, 1, 1);
+MODULE_VERSION(ugold, 1);
+USB_PNP_HOST_INFO(ugold_devs);
+
+static const struct usb_config ugold_config[UGOLD_N_TRANSFER] = {
+ [UGOLD_INTR_DT] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &ugold_intr_callback,
+ .if_index = 1,
+ },
+};
+
+static void
+ugold_timeout(void *arg)
+{
+ struct ugold_softc *sc = arg;
+
+ usb_proc_explore_lock(sc->sc_udev);
+ (void)usb_proc_explore_msignal(sc->sc_udev,
+ &sc->sc_readout_msg[0], &sc->sc_readout_msg[1]);
+ usb_proc_explore_unlock(sc->sc_udev);
+
+ callout_reset(&sc->sc_callout, 6 * hz, &ugold_timeout, sc);
+}
+
+static int
+ugold_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa;
+
+ uaa = device_get_ivars(dev);
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bInterfaceClass != UICLASS_HID)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != 0)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(ugold_devs, sizeof(ugold_devs), uaa));
+}
+
+static int
+ugold_attach(device_t dev)
+{
+ struct ugold_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct sysctl_oid *sensor_tree;
+ uint16_t d_len;
+ void *d_ptr;
+ int error;
+ int i;
+
+ sc->sc_udev = uaa->device;
+ sc->sc_readout_msg[0].hdr.pm_callback = &ugold_readout_msg;
+ sc->sc_readout_msg[0].sc = sc;
+ sc->sc_readout_msg[1].hdr.pm_callback = &ugold_readout_msg;
+ sc->sc_readout_msg[1].sc = sc;
+ sc->sc_iface_index[0] = uaa->info.bIfaceIndex;
+ sc->sc_iface_index[1] = uaa->info.bIfaceIndex + 1;
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "ugold lock", NULL, MTX_DEF | MTX_RECURSE);
+ callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0);
+
+ /* grab all interfaces from other drivers */
+ for (i = 0;; i++) {
+ if (i == uaa->info.bIfaceIndex)
+ continue;
+ if (usbd_get_iface(uaa->device, i) == NULL)
+ break;
+
+ usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex);
+ }
+
+ /* figure out report ID */
+ error = usbd_req_get_hid_desc(uaa->device, NULL,
+ &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex);
+
+ if (error)
+ goto detach;
+
+ (void)hid_report_size_max(d_ptr, d_len, hid_input, &sc->sc_report_id);
+
+ free(d_ptr, M_TEMP);
+
+ error = usbd_transfer_setup(uaa->device,
+ sc->sc_iface_index, sc->sc_xfer, ugold_config,
+ UGOLD_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error)
+ goto detach;
+
+ sensor_tree = SYSCTL_ADD_NODE(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensors",
+ CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "");
+
+ if (sensor_tree == NULL) {
+ error = ENOMEM;
+ goto detach;
+ }
+ SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(sensor_tree),
+ OID_AUTO, "inner", CTLFLAG_RD, &sc->sc_sensor[UGOLD_INNER], 0,
+ "Inner temperature in microCelsius");
+
+ SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(sensor_tree),
+ OID_AUTO, "inner_valid", CTLFLAG_RD, &sc->sc_valid[UGOLD_INNER], 0,
+ "Inner temperature is valid");
+
+ SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(sensor_tree),
+ OID_AUTO, "inner_calib", CTLFLAG_RWTUN, &sc->sc_calib[UGOLD_INNER], 0,
+ "Inner calibration temperature in microCelsius");
+
+ SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(sensor_tree),
+ OID_AUTO, "outer", CTLFLAG_RD, &sc->sc_sensor[UGOLD_OUTER], 0,
+ "Outer temperature in microCelsius");
+
+ SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(sensor_tree),
+ OID_AUTO, "outer_calib", CTLFLAG_RWTUN, &sc->sc_calib[UGOLD_OUTER], 0,
+ "Outer calibration temperature in microCelsius");
+
+ SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(sensor_tree),
+ OID_AUTO, "outer_valid", CTLFLAG_RD, &sc->sc_valid[UGOLD_OUTER], 0,
+ "Outer temperature is valid");
+
+ mtx_lock(&sc->sc_mtx);
+ usbd_transfer_start(sc->sc_xfer[UGOLD_INTR_DT]);
+ ugold_timeout(sc);
+ mtx_unlock(&sc->sc_mtx);
+
+ return (0);
+
+detach:
+ DPRINTF("error=%s\n", usbd_errstr(error));
+ ugold_detach(dev);
+ return (error);
+}
+
+static int
+ugold_detach(device_t dev)
+{
+ struct ugold_softc *sc = device_get_softc(dev);
+
+ callout_drain(&sc->sc_callout);
+
+ usb_proc_explore_lock(sc->sc_udev);
+ usb_proc_explore_mwait(sc->sc_udev,
+ &sc->sc_readout_msg[0], &sc->sc_readout_msg[1]);
+ usb_proc_explore_unlock(sc->sc_udev);
+
+ usbd_transfer_unsetup(sc->sc_xfer, UGOLD_N_TRANSFER);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static int
+ugold_ds75_temp(uint8_t msb, uint8_t lsb)
+{
+ /* DS75: 12bit precision mode : 0.0625 degrees Celsius ticks */
+ /* NOTE: MSB has a sign bit for negative temperatures */
+ int32_t temp = (msb << 24) | ((lsb & 0xF0) << 16);
+ return (((int64_t)temp * (int64_t)1000000LL) >> 24);
+}
+
+static void
+ugold_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ugold_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint8_t buf[8];
+ int temp;
+ int len;
+
+ usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ memset(buf, 0, sizeof(buf));
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, buf, MIN(len, sizeof(buf)));
+
+ switch (buf[0]) {
+ case UGOLD_CMD_INIT:
+ if (sc->sc_num_sensors)
+ break;
+
+ sc->sc_num_sensors = MIN(buf[1], UGOLD_MAX_SENSORS) /* XXX */ ;
+
+ DPRINTF("%d sensor%s type ds75/12bit (temperature)\n",
+ sc->sc_num_sensors, (sc->sc_num_sensors == 1) ? "" : "s");
+ break;
+ case UGOLD_CMD_DATA:
+ switch (buf[1]) {
+ case 4:
+ temp = ugold_ds75_temp(buf[4], buf[5]);
+ sc->sc_sensor[UGOLD_OUTER] = temp + sc->sc_calib[UGOLD_OUTER];
+ sc->sc_valid[UGOLD_OUTER] = 1;
+ /* FALLTHROUGH */
+ case 2:
+ temp = ugold_ds75_temp(buf[2], buf[3]);
+ sc->sc_sensor[UGOLD_INNER] = temp + sc->sc_calib[UGOLD_INNER];
+ sc->sc_valid[UGOLD_INNER] = 1;
+ break;
+ default:
+ DPRINTF("invalid data length (%d bytes)\n", buf[1]);
+ }
+ break;
+ default:
+ DPRINTF("unknown command 0x%02x\n", buf[0]);
+ break;
+ }
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static int
+ugold_issue_cmd(struct ugold_softc *sc, uint8_t *cmd, int len)
+{
+ return (usbd_req_set_report(sc->sc_udev, &sc->sc_mtx, cmd, len,
+ sc->sc_iface_index[1], UHID_OUTPUT_REPORT, sc->sc_report_id));
+}
+
+static void
+ugold_readout_msg(struct usb_proc_msg *pm)
+{
+ struct ugold_softc *sc = ((struct ugold_readout_msg *)pm)->sc;
+
+ usb_proc_explore_unlock(sc->sc_udev);
+
+ mtx_lock(&sc->sc_mtx);
+ if (sc->sc_num_sensors == 0)
+ ugold_issue_cmd(sc, cmd_init, sizeof(cmd_init));
+
+ ugold_issue_cmd(sc, cmd_data, sizeof(cmd_data));
+ mtx_unlock(&sc->sc_mtx);
+
+ usb_proc_explore_lock(sc->sc_udev);
+}
diff --git a/sys/dev/usb/misc/uled.c b/sys/dev/usb/misc/uled.c
new file mode 100644
index 000000000000..7608524cddee
--- /dev/null
+++ b/sys/dev/usb/misc/uled.c
@@ -0,0 +1,291 @@
+/*-
+ * Copyright (c) 2014, 2017 Kevin Lo
+ * 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbhid.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR usb_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/uled_ioctl.h>
+
+struct uled_softc {
+ struct usb_fifo_sc sc_fifo;
+ struct mtx sc_mtx;
+
+ struct usb_device *sc_udev;
+ struct uled_color sc_color;
+
+ uint8_t sc_state;
+#define ULED_ENABLED 0x01
+
+ int sc_flags;
+#define ULED_FLAG_BLINK1 0x0001
+};
+
+/* Initial commands. */
+static uint8_t blink1[] = { 0x1, 'v', 0, 0, 0, 0, 0, 0 };
+static uint8_t dl100b[] = { 0x1f, 0x2, 0, 0x5f, 0, 0, 0x1a, 0x3 };
+
+/* Prototypes. */
+static device_probe_t uled_probe;
+static device_attach_t uled_attach;
+static device_detach_t uled_detach;
+
+static usb_fifo_open_t uled_open;
+static usb_fifo_close_t uled_close;
+static usb_fifo_ioctl_t uled_ioctl;
+
+static struct usb_fifo_methods uled_fifo_methods = {
+ .f_open = &uled_open,
+ .f_close = &uled_close,
+ .f_ioctl = &uled_ioctl,
+ .basename[0] = "uled",
+};
+
+static usb_error_t uled_ctrl_msg(struct uled_softc *, uint8_t, uint8_t,
+ uint16_t, uint16_t, void *, uint16_t);
+static int uled_enable(struct uled_softc *);
+
+static device_method_t uled_methods[] = {
+ DEVMETHOD(device_probe, uled_probe),
+ DEVMETHOD(device_attach, uled_attach),
+ DEVMETHOD(device_detach, uled_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t uled_driver = {
+ .name = "uled",
+ .methods = uled_methods,
+ .size = sizeof(struct uled_softc),
+};
+
+static const STRUCT_USB_HOST_ID uled_devs[] = {
+#define ULED_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
+ ULED_DEV(DREAMLINK, DL100B, 0),
+ ULED_DEV(THINGM, BLINK1, ULED_FLAG_BLINK1),
+#undef ULED_DEV
+};
+
+DRIVER_MODULE(uled, uhub, uled_driver, NULL, NULL);
+MODULE_DEPEND(uled, usb, 1, 1, 1);
+MODULE_VERSION(uled, 1);
+USB_PNP_HOST_INFO(uled_devs);
+
+static int
+uled_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa;
+
+ uaa = device_get_ivars(dev);
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bInterfaceClass != UICLASS_HID)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(uled_devs, sizeof(uled_devs), uaa));
+}
+
+static int
+uled_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa;
+ struct uled_softc *sc;
+ int unit;
+ usb_error_t error;
+
+ uaa = device_get_ivars(dev);
+ sc = device_get_softc(dev);
+ unit = device_get_unit(dev);
+ sc->sc_flags = USB_GET_DRIVER_INFO(uaa);
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "uled lock", NULL, MTX_DEF | MTX_RECURSE);
+
+ sc->sc_udev = uaa->device;
+
+ error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx,
+ &uled_fifo_methods, &sc->sc_fifo, unit, -1,
+ uaa->info.bIfaceIndex, UID_ROOT, GID_OPERATOR, 0644);
+ if (error != 0)
+ goto detach;
+
+ sc->sc_color.red = 0;
+ sc->sc_color.green = 0;
+ sc->sc_color.blue = 0;
+
+ return (0);
+
+detach:
+ uled_detach(dev);
+ return (ENOMEM);
+}
+
+static int
+uled_detach(device_t dev)
+{
+ struct uled_softc *sc;
+
+ sc = device_get_softc(dev);
+ usb_fifo_detach(&sc->sc_fifo);
+ mtx_destroy(&sc->sc_mtx);
+ return (0);
+}
+
+static usb_error_t
+uled_ctrl_msg(struct uled_softc *sc, uint8_t rt, uint8_t reqno,
+ uint16_t value, uint16_t index, void *buf, uint16_t buflen)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = rt;
+ req.bRequest = reqno;
+ USETW(req.wValue, value);
+ USETW(req.wIndex, index);
+ USETW(req.wLength, buflen);
+
+ return (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, buf,
+ 0, NULL, 2000));
+}
+
+static int
+uled_enable(struct uled_softc *sc)
+{
+ uint8_t *cmdbuf;
+ int error;
+
+ cmdbuf = (sc->sc_flags & ULED_FLAG_BLINK1) ? blink1 : dl100b;
+
+ sc->sc_state |= ULED_ENABLED;
+ mtx_lock(&sc->sc_mtx);
+ error = uled_ctrl_msg(sc, UT_WRITE_CLASS_INTERFACE, UR_SET_REPORT,
+ 0x200, 0, cmdbuf, sizeof(cmdbuf));
+ mtx_unlock(&sc->sc_mtx);
+ return (error);
+}
+
+static int
+uled_open(struct usb_fifo *fifo, int fflags)
+{
+ if (fflags & FREAD) {
+ struct uled_softc *sc;
+ int rc;
+
+ sc = usb_fifo_softc(fifo);
+ if (sc->sc_state & ULED_ENABLED)
+ return (EBUSY);
+ if ((rc = uled_enable(sc)) != 0)
+ return (rc);
+ }
+ return (0);
+}
+
+static void
+uled_close(struct usb_fifo *fifo, int fflags)
+{
+ if (fflags & FREAD) {
+ struct uled_softc *sc;
+
+ sc = usb_fifo_softc(fifo);
+ sc->sc_state &= ~ULED_ENABLED;
+ }
+}
+
+static int
+uled_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
+{
+ struct uled_softc *sc;
+ struct uled_color color;
+ int error;
+
+ sc = usb_fifo_softc(fifo);
+ error = 0;
+
+ mtx_lock(&sc->sc_mtx);
+
+ switch(cmd) {
+ case ULED_GET_COLOR:
+ *(struct uled_color *)addr = sc->sc_color;
+ break;
+ case ULED_SET_COLOR:
+ color = *(struct uled_color *)addr;
+ uint8_t buf[8];
+
+ sc->sc_color.red = color.red;
+ sc->sc_color.green = color.green;
+ sc->sc_color.blue = color.blue;
+
+ if (sc->sc_flags & ULED_FLAG_BLINK1) {
+ buf[0] = 0x1;
+ buf[1] = 'n';
+ buf[2] = color.red;
+ buf[3] = color.green;
+ buf[4] = color.blue;
+ buf[5] = buf[6] = buf[7] = 0;
+ } else {
+ buf[0] = color.red;
+ buf[1] = color.green;
+ buf[2] = color.blue;
+ buf[3] = buf[4] = buf[5] = 0;
+ buf[6] = 0x1a;
+ buf[7] = 0x05;
+ }
+ error = uled_ctrl_msg(sc, UT_WRITE_CLASS_INTERFACE,
+ UR_SET_REPORT, 0x200, 0, buf, sizeof(buf));
+ break;
+ default:
+ error = ENOTTY;
+ break;
+ }
+
+ mtx_unlock(&sc->sc_mtx);
+ return (error);
+}
diff --git a/sys/dev/usb/net/if_aue.c b/sys/dev/usb/net/if_aue.c
new file mode 100644
index 000000000000..84268c60a780
--- /dev/null
+++ b/sys/dev/usb/net/if_aue.c
@@ -0,0 +1,1066 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * 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.
+ */
+
+/*
+ * 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 uether_do_request(). Packet
+ * transfers are done using usbd_transfer() and friends.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_media.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR aue_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/net/usb_ethernet.h>
+#include <dev/usb/net/if_auereg.h>
+
+#include "miibus_if.h"
+
+#ifdef USB_DEBUG
+static int aue_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, aue, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB aue");
+SYSCTL_INT(_hw_usb_aue, OID_AUTO, debug, CTLFLAG_RWTUN, &aue_debug, 0,
+ "Debug level");
+#endif
+
+/*
+ * Various supported device vendors/products.
+ */
+static const STRUCT_USB_HOST_ID aue_devs[] = {
+#define AUE_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
+ AUE_DEV(3COM, 3C460B, AUE_FLAG_PII),
+ AUE_DEV(ABOCOM, DSB650TX_PNA, 0),
+ AUE_DEV(ABOCOM, UFE1000, AUE_FLAG_LSYS),
+ AUE_DEV(ABOCOM, XX10, 0),
+ AUE_DEV(ABOCOM, XX1, AUE_FLAG_PNA | AUE_FLAG_PII),
+ AUE_DEV(ABOCOM, XX2, AUE_FLAG_PII),
+ AUE_DEV(ABOCOM, XX4, AUE_FLAG_PNA),
+ AUE_DEV(ABOCOM, XX5, AUE_FLAG_PNA),
+ AUE_DEV(ABOCOM, XX6, AUE_FLAG_PII),
+ AUE_DEV(ABOCOM, XX7, AUE_FLAG_PII),
+ AUE_DEV(ABOCOM, XX8, AUE_FLAG_PII),
+ AUE_DEV(ABOCOM, XX9, AUE_FLAG_PNA),
+ AUE_DEV(ACCTON, SS1001, AUE_FLAG_PII),
+ AUE_DEV(ACCTON, USB320_EC, 0),
+ AUE_DEV(ADMTEK, PEGASUSII_2, AUE_FLAG_PII),
+ AUE_DEV(ADMTEK, PEGASUSII_3, AUE_FLAG_PII),
+ AUE_DEV(ADMTEK, PEGASUSII_4, AUE_FLAG_PII),
+ AUE_DEV(ADMTEK, PEGASUSII, AUE_FLAG_PII),
+ AUE_DEV(ADMTEK, PEGASUS, AUE_FLAG_PNA | AUE_FLAG_DUAL_PHY),
+ AUE_DEV(AEI, FASTETHERNET, AUE_FLAG_PII),
+ AUE_DEV(ALLIEDTELESYN, ATUSB100, AUE_FLAG_PII),
+ AUE_DEV(ATEN, UC110T, AUE_FLAG_PII),
+ AUE_DEV(BELKIN, USB2LAN, AUE_FLAG_PII),
+ AUE_DEV(BILLIONTON, USB100, 0),
+ AUE_DEV(BILLIONTON, USBE100, AUE_FLAG_PII),
+ AUE_DEV(BILLIONTON, USBEL100, 0),
+ AUE_DEV(BILLIONTON, USBLP100, AUE_FLAG_PNA),
+ AUE_DEV(COREGA, FETHER_USB_TXS, AUE_FLAG_PII),
+ AUE_DEV(COREGA, FETHER_USB_TX, 0),
+ AUE_DEV(DLINK, DSB650TX1, AUE_FLAG_LSYS),
+ AUE_DEV(DLINK, DSB650TX2, AUE_FLAG_LSYS | AUE_FLAG_PII),
+ AUE_DEV(DLINK, DSB650TX3, AUE_FLAG_LSYS | AUE_FLAG_PII),
+ AUE_DEV(DLINK, DSB650TX4, AUE_FLAG_LSYS | AUE_FLAG_PII),
+ AUE_DEV(DLINK, DSB650TX_PNA, AUE_FLAG_PNA),
+ AUE_DEV(DLINK, DSB650TX, AUE_FLAG_LSYS),
+ AUE_DEV(DLINK, DSB650, AUE_FLAG_LSYS),
+ AUE_DEV(ELCON, PLAN, AUE_FLAG_PNA | AUE_FLAG_PII),
+ AUE_DEV(ELECOM, LDUSB20, AUE_FLAG_PII),
+ AUE_DEV(ELECOM, LDUSBLTX, AUE_FLAG_PII),
+ AUE_DEV(ELECOM, LDUSBTX0, 0),
+ AUE_DEV(ELECOM, LDUSBTX1, AUE_FLAG_LSYS),
+ AUE_DEV(ELECOM, LDUSBTX2, 0),
+ AUE_DEV(ELECOM, LDUSBTX3, AUE_FLAG_LSYS),
+ AUE_DEV(ELSA, USB2ETHERNET, 0),
+ AUE_DEV(GIGABYTE, GNBR402W, 0),
+ AUE_DEV(HAWKING, UF100, AUE_FLAG_PII),
+ AUE_DEV(HP, HN210E, AUE_FLAG_PII),
+ AUE_DEV(IODATA, USBETTXS, AUE_FLAG_PII),
+ AUE_DEV(IODATA, USBETTX, 0),
+ AUE_DEV(KINGSTON, KNU101TX, 0),
+ AUE_DEV(LINKSYS, USB100H1, AUE_FLAG_LSYS | AUE_FLAG_PNA),
+ AUE_DEV(LINKSYS, USB100TX, AUE_FLAG_LSYS),
+ AUE_DEV(LINKSYS, USB10TA, AUE_FLAG_LSYS),
+ AUE_DEV(LINKSYS, USB10TX1, AUE_FLAG_LSYS | AUE_FLAG_PII),
+ AUE_DEV(LINKSYS, USB10TX2, AUE_FLAG_LSYS | AUE_FLAG_PII),
+ AUE_DEV(LINKSYS, USB10T, AUE_FLAG_LSYS),
+ AUE_DEV(MELCO, LUA2TX5, AUE_FLAG_PII),
+ AUE_DEV(MELCO, LUATX1, 0),
+ AUE_DEV(MELCO, LUATX5, 0),
+ AUE_DEV(MICROSOFT, MN110, AUE_FLAG_PII),
+ AUE_DEV(NETGEAR, FA101, AUE_FLAG_PII),
+ AUE_DEV(SIEMENS, SPEEDSTREAM, AUE_FLAG_PII),
+ AUE_DEV(SIIG2, USBTOETHER, AUE_FLAG_PII),
+ AUE_DEV(SMARTBRIDGES, SMARTNIC, AUE_FLAG_PII),
+ AUE_DEV(SMC, 2202USB, 0),
+ AUE_DEV(SMC, 2206USB, AUE_FLAG_PII),
+ AUE_DEV(SOHOWARE, NUB100, 0),
+ AUE_DEV(SOHOWARE, NUB110, AUE_FLAG_PII),
+#undef AUE_DEV
+};
+
+/* prototypes */
+
+static device_probe_t aue_probe;
+static device_attach_t aue_attach;
+static device_detach_t aue_detach;
+static miibus_readreg_t aue_miibus_readreg;
+static miibus_writereg_t aue_miibus_writereg;
+static miibus_statchg_t aue_miibus_statchg;
+
+static usb_callback_t aue_intr_callback;
+static usb_callback_t aue_bulk_read_callback;
+static usb_callback_t aue_bulk_write_callback;
+
+static uether_fn_t aue_attach_post;
+static uether_fn_t aue_init;
+static uether_fn_t aue_stop;
+static uether_fn_t aue_start;
+static uether_fn_t aue_tick;
+static uether_fn_t aue_setmulti;
+static uether_fn_t aue_setpromisc;
+
+static uint8_t aue_csr_read_1(struct aue_softc *, uint16_t);
+static uint16_t aue_csr_read_2(struct aue_softc *, uint16_t);
+static void aue_csr_write_1(struct aue_softc *, uint16_t, uint8_t);
+static void aue_csr_write_2(struct aue_softc *, uint16_t, uint16_t);
+static uint16_t aue_eeprom_getword(struct aue_softc *, int);
+static void aue_reset(struct aue_softc *);
+static void aue_reset_pegasus_II(struct aue_softc *);
+
+static int aue_ifmedia_upd(if_t);
+static void aue_ifmedia_sts(if_t, struct ifmediareq *);
+
+static const struct usb_config aue_config[AUE_N_TRANSFER] = {
+ [AUE_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = (MCLBYTES + 2),
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = aue_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ },
+
+ [AUE_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = (MCLBYTES + 4 + ETHER_CRC_LEN),
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = aue_bulk_read_callback,
+ },
+
+ [AUE_INTR_DT_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = aue_intr_callback,
+ },
+};
+
+static device_method_t aue_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, aue_probe),
+ DEVMETHOD(device_attach, aue_attach),
+ DEVMETHOD(device_detach, aue_detach),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, aue_miibus_readreg),
+ DEVMETHOD(miibus_writereg, aue_miibus_writereg),
+ DEVMETHOD(miibus_statchg, aue_miibus_statchg),
+
+ DEVMETHOD_END
+};
+
+static driver_t aue_driver = {
+ .name = "aue",
+ .methods = aue_methods,
+ .size = sizeof(struct aue_softc)
+};
+
+DRIVER_MODULE(aue, uhub, aue_driver, NULL, NULL);
+DRIVER_MODULE(miibus, aue, miibus_driver, 0, 0);
+MODULE_DEPEND(aue, uether, 1, 1, 1);
+MODULE_DEPEND(aue, usb, 1, 1, 1);
+MODULE_DEPEND(aue, ether, 1, 1, 1);
+MODULE_DEPEND(aue, miibus, 1, 1, 1);
+MODULE_VERSION(aue, 1);
+USB_PNP_HOST_INFO(aue_devs);
+
+static const struct usb_ether_methods aue_ue_methods = {
+ .ue_attach_post = aue_attach_post,
+ .ue_start = aue_start,
+ .ue_init = aue_init,
+ .ue_stop = aue_stop,
+ .ue_tick = aue_tick,
+ .ue_setmulti = aue_setmulti,
+ .ue_setpromisc = aue_setpromisc,
+ .ue_mii_upd = aue_ifmedia_upd,
+ .ue_mii_sts = aue_ifmedia_sts,
+};
+
+#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 uint8_t
+aue_csr_read_1(struct aue_softc *sc, uint16_t reg)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+ uint8_t val;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = AUE_UR_READREG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 1);
+
+ err = uether_do_request(&sc->sc_ue, &req, &val, 1000);
+ if (err)
+ return (0);
+ return (val);
+}
+
+static uint16_t
+aue_csr_read_2(struct aue_softc *sc, uint16_t reg)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+ uint16_t val;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = AUE_UR_READREG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 2);
+
+ err = uether_do_request(&sc->sc_ue, &req, &val, 1000);
+ if (err)
+ return (0);
+ return (le16toh(val));
+}
+
+static void
+aue_csr_write_1(struct aue_softc *sc, uint16_t reg, uint8_t val)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = AUE_UR_WRITEREG;
+ req.wValue[0] = val;
+ req.wValue[1] = 0;
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 1);
+
+ if (uether_do_request(&sc->sc_ue, &req, &val, 1000)) {
+ /* error ignored */
+ }
+}
+
+static void
+aue_csr_write_2(struct aue_softc *sc, uint16_t reg, uint16_t val)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = AUE_UR_WRITEREG;
+ USETW(req.wValue, val);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 2);
+
+ val = htole16(val);
+
+ if (uether_do_request(&sc->sc_ue, &req, &val, 1000)) {
+ /* error ignored */
+ }
+}
+
+/*
+ * Read a word of data stored in the EEPROM at address 'addr.'
+ */
+static uint16_t
+aue_eeprom_getword(struct aue_softc *sc, int addr)
+{
+ int i;
+
+ 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 (uether_pause(&sc->sc_ue, hz / 100))
+ break;
+ }
+
+ if (i == AUE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev, "EEPROM read timed out\n");
+
+ return (aue_csr_read_2(sc, AUE_EE_DATA));
+}
+
+/*
+ * Read station address(offset 0) from the EEPROM.
+ */
+static void
+aue_read_mac(struct aue_softc *sc, uint8_t *eaddr)
+{
+ int i, offset;
+ uint16_t word;
+
+ for (i = 0, offset = 0; i < ETHER_ADDR_LEN / 2; i++) {
+ word = aue_eeprom_getword(sc, offset + i);
+ eaddr[i * 2] = (uint8_t)word;
+ eaddr[i * 2 + 1] = (uint8_t)(word >> 8);
+ }
+}
+
+static int
+aue_miibus_readreg(device_t dev, int phy, int reg)
+{
+ struct aue_softc *sc = device_get_softc(dev);
+ int i, locked;
+ uint16_t val = 0;
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ AUE_LOCK(sc);
+
+ /*
+ * 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->sc_flags & AUE_FLAG_DUAL_PHY) {
+ if (phy == 3)
+ goto done;
+#if 0
+ if (phy != 1)
+ goto done;
+#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 (uether_pause(&sc->sc_ue, hz / 100))
+ break;
+ }
+
+ if (i == AUE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev, "MII read timed out\n");
+
+ val = aue_csr_read_2(sc, AUE_PHY_DATA);
+
+done:
+ if (!locked)
+ AUE_UNLOCK(sc);
+ 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;
+ int locked;
+
+ if (phy == 3)
+ return (0);
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ AUE_LOCK(sc);
+
+ 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 (uether_pause(&sc->sc_ue, hz / 100))
+ break;
+ }
+
+ if (i == AUE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev, "MII write timed out\n");
+
+ if (!locked)
+ AUE_UNLOCK(sc);
+ 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);
+ int locked;
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ AUE_LOCK(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->sc_flags & AUE_FLAG_LSYS) {
+ uint16_t auxmode;
+
+ auxmode = aue_miibus_readreg(dev, 0, 0x1b);
+ aue_miibus_writereg(dev, 0, 0x1b, auxmode | 0x04);
+ }
+ if (!locked)
+ AUE_UNLOCK(sc);
+}
+
+#define AUE_BITS 6
+static u_int
+aue_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
+{
+ uint8_t *hashtbl = arg;
+ uint32_t h;
+
+ h = ether_crc32_le(LLADDR(sdl), ETHER_ADDR_LEN) & ((1 << AUE_BITS) - 1);
+ hashtbl[(h >> 3)] |= 1 << (h & 0x7);
+
+ return (1);
+}
+
+static void
+aue_setmulti(struct usb_ether *ue)
+{
+ struct aue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ uint32_t i;
+ uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ AUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (if_getflags(ifp) & IFF_ALLMULTI || if_getflags(ifp) & IFF_PROMISC) {
+ AUE_SETBIT(sc, AUE_CTL0, AUE_CTL0_ALLMULTI);
+ return;
+ }
+
+ AUE_CLRBIT(sc, AUE_CTL0, AUE_CTL0_ALLMULTI);
+
+ /* now program new ones */
+ if_foreach_llmaddr(ifp, aue_hash_maddr, hashtbl);
+
+ /* write the hashtable */
+ for (i = 0; i != 8; i++)
+ aue_csr_write_1(sc, AUE_MAR0 + i, hashtbl[i]);
+}
+
+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->sc_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_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 (uether_pause(&sc->sc_ue, hz / 100))
+ break;
+ }
+
+ if (i == AUE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev, "reset failed\n");
+
+ /*
+ * 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 used to force all of the GPIO pins low first and then
+ * enable the ones we want. This has been changed to better
+ * match the ADMtek's reference design to avoid setting the
+ * power-down configuration line of the PHY at the same time
+ * it is reset.
+ */
+ 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->sc_flags & AUE_FLAG_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->sc_flags & AUE_FLAG_PII)
+ aue_reset_pegasus_II(sc);
+
+ /* Wait a little while for the chip to get its brains in order: */
+ uether_pause(&sc->sc_ue, hz / 100);
+}
+
+static void
+aue_attach_post(struct usb_ether *ue)
+{
+ struct aue_softc *sc = uether_getsc(ue);
+
+ /* reset the adapter */
+ aue_reset(sc);
+
+ /* get station address from the EEPROM */
+ aue_read_mac(sc, ue->ue_eaddr);
+}
+
+/*
+ * Probe for a Pegasus chip.
+ */
+static int
+aue_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != AUE_CONFIG_INDEX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != AUE_IFACE_IDX)
+ return (ENXIO);
+ /*
+ * 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->info.idVendor == USB_VENDOR_BELKIN &&
+ uaa->info.idProduct == USB_PRODUCT_BELKIN_F8T012 &&
+ uaa->info.bcdDevice == 0x0413)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(aue_devs, sizeof(aue_devs), uaa));
+}
+
+/*
+ * Attach the interface. Allocate softc structures, do ifmedia
+ * setup and ethernet/BPF attach.
+ */
+static int
+aue_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct aue_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+ uint8_t iface_index;
+ int error;
+
+ sc->sc_flags = USB_GET_DRIVER_INFO(uaa);
+
+ if (uaa->info.bcdDevice >= 0x0201) {
+ /* XXX currently undocumented */
+ sc->sc_flags |= AUE_FLAG_VER_2;
+ }
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ iface_index = AUE_IFACE_IDX;
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, aue_config, AUE_N_TRANSFER,
+ sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "allocating USB transfers failed\n");
+ goto detach;
+ }
+
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &aue_ue_methods;
+
+ error = uether_ifattach(ue);
+ if (error) {
+ device_printf(dev, "could not attach interface\n");
+ goto detach;
+ }
+ return (0); /* success */
+
+detach:
+ aue_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+aue_detach(device_t dev)
+{
+ struct aue_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ usbd_transfer_unsetup(sc->sc_xfer, AUE_N_TRANSFER);
+ uether_ifdetach(ue);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+aue_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct aue_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct aue_intrpkt pkt;
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) &&
+ actlen >= (int)sizeof(pkt)) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &pkt, sizeof(pkt));
+
+ if (pkt.aue_txstat0)
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ if (pkt.aue_txstat0 & (AUE_TXSTAT0_LATECOLL |
+ AUE_TXSTAT0_EXCESSCOLL))
+ if_inc_counter(ifp, IFCOUNTER_COLLISIONS, 1);
+ }
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+aue_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct aue_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_ether *ue = &sc->sc_ue;
+ if_t ifp = uether_getifp(ue);
+ struct aue_rxpkt stat;
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+ pc = usbd_xfer_get_frame(xfer, 0);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "received %d bytes\n", actlen);
+
+ if (sc->sc_flags & AUE_FLAG_VER_2) {
+ if (actlen == 0) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ } else {
+ if (actlen <= (int)(sizeof(stat) + ETHER_CRC_LEN)) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ usbd_copy_out(pc, actlen - sizeof(stat), &stat,
+ sizeof(stat));
+
+ /*
+ * turn off all the non-error bits in the rx status
+ * word:
+ */
+ stat.aue_rxstat &= AUE_RXSTAT_MASK;
+ if (stat.aue_rxstat) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ /* No errors; receive the packet. */
+ actlen -= (sizeof(stat) + ETHER_CRC_LEN);
+ }
+ uether_rxbuf(ue, pc, 0, actlen);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ uether_rxflush(ue);
+ return;
+
+ default: /* Error */
+ DPRINTF("bulk read error, %s\n",
+ usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+aue_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct aue_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ uint8_t buf[2];
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+ pc = usbd_xfer_get_frame(xfer, 0);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "transfer of %d bytes complete\n", actlen);
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ if ((sc->sc_flags & AUE_FLAG_LINK) == 0) {
+ /*
+ * don't send anything if there is no link !
+ */
+ return;
+ }
+ m = if_dequeue(ifp);
+
+ if (m == NULL)
+ return;
+ if (m->m_pkthdr.len > MCLBYTES)
+ m->m_pkthdr.len = MCLBYTES;
+ if (sc->sc_flags & AUE_FLAG_VER_2) {
+ usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len);
+
+ usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len);
+
+ } else {
+ usbd_xfer_set_frame_len(xfer, 0, (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.
+ */
+ buf[0] = (uint8_t)(m->m_pkthdr.len);
+ buf[1] = (uint8_t)(m->m_pkthdr.len >> 8);
+
+ usbd_copy_in(pc, 0, buf, 2);
+ usbd_m_copy_in(pc, 2, m, 0, m->m_pkthdr.len);
+ }
+
+ /*
+ * if there's a BPF listener, bounce a copy
+ * of this frame to him:
+ */
+ BPF_MTAP(ifp, m);
+
+ m_freem(m);
+
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ DPRINTFN(11, "transfer error, %s\n",
+ usbd_errstr(error));
+
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+aue_tick(struct usb_ether *ue)
+{
+ struct aue_softc *sc = uether_getsc(ue);
+ struct mii_data *mii = GET_MII(sc);
+
+ AUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ mii_tick(mii);
+ if ((sc->sc_flags & AUE_FLAG_LINK) == 0
+ && mii->mii_media_status & IFM_ACTIVE &&
+ IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) {
+ sc->sc_flags |= AUE_FLAG_LINK;
+ aue_start(ue);
+ }
+}
+
+static void
+aue_start(struct usb_ether *ue)
+{
+ struct aue_softc *sc = uether_getsc(ue);
+
+ /*
+ * start the USB transfers, if not already started:
+ */
+ usbd_transfer_start(sc->sc_xfer[AUE_INTR_DT_RD]);
+ usbd_transfer_start(sc->sc_xfer[AUE_BULK_DT_RD]);
+ usbd_transfer_start(sc->sc_xfer[AUE_BULK_DT_WR]);
+}
+
+static void
+aue_init(struct usb_ether *ue)
+{
+ struct aue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ int i;
+
+ AUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ /*
+ * Cancel pending I/O
+ */
+ aue_reset(sc);
+
+ /* Set MAC address */
+ for (i = 0; i != ETHER_ADDR_LEN; i++)
+ aue_csr_write_1(sc, AUE_PAR0 + i, if_getlladdr(ifp)[i]);
+
+ /* update promiscuous setting */
+ aue_setpromisc(ue);
+
+ /* Load the multicast filter. */
+ aue_setmulti(ue);
+
+ /* 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);
+
+ usbd_xfer_set_stall(sc->sc_xfer[AUE_BULK_DT_WR]);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+ aue_start(ue);
+}
+
+static void
+aue_setpromisc(struct usb_ether *ue)
+{
+ struct aue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ AUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* if we want promiscuous mode, set the allframes bit: */
+ if (if_getflags(ifp) & IFF_PROMISC)
+ AUE_SETBIT(sc, AUE_CTL2, AUE_CTL2_RX_PROMISC);
+ else
+ AUE_CLRBIT(sc, AUE_CTL2, AUE_CTL2_RX_PROMISC);
+}
+
+/*
+ * Set media options.
+ */
+static int
+aue_ifmedia_upd(if_t ifp)
+{
+ struct aue_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = GET_MII(sc);
+ struct mii_softc *miisc;
+ int error;
+
+ AUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ sc->sc_flags &= ~AUE_FLAG_LINK;
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list)
+ PHY_RESET(miisc);
+ error = mii_mediachg(mii);
+ return (error);
+}
+
+/*
+ * Report current media status.
+ */
+static void
+aue_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct aue_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = GET_MII(sc);
+
+ AUE_LOCK(sc);
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+ AUE_UNLOCK(sc);
+}
+
+/*
+ * Stop the adapter and free any mbufs allocated to the
+ * RX and TX lists.
+ */
+static void
+aue_stop(struct usb_ether *ue)
+{
+ struct aue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ AUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+ sc->sc_flags &= ~AUE_FLAG_LINK;
+
+ /*
+ * stop all the transfers, if not already stopped:
+ */
+ usbd_transfer_stop(sc->sc_xfer[AUE_BULK_DT_WR]);
+ usbd_transfer_stop(sc->sc_xfer[AUE_BULK_DT_RD]);
+ usbd_transfer_stop(sc->sc_xfer[AUE_INTR_DT_RD]);
+
+ aue_csr_write_1(sc, AUE_CTL0, 0);
+ aue_csr_write_1(sc, AUE_CTL1, 0);
+ aue_reset(sc);
+}
diff --git a/sys/dev/usb/net/if_auereg.h b/sys/dev/usb/net/if_auereg.h
new file mode 100644
index 000000000000..7f2d56dccdd5
--- /dev/null
+++ b/sys/dev/usb/net/if_auereg.h
@@ -0,0 +1,220 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 1997, 1998, 1999
+ * 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.
+ */
+
+/*
+ * 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.
+ */
+
+#define AUE_UR_READREG 0xF0
+#define AUE_UR_WRITEREG 0xF1
+
+#define AUE_CONFIG_INDEX 0 /* config number 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.
+ */
+enum {
+ AUE_BULK_DT_WR,
+ AUE_BULK_DT_RD,
+ AUE_INTR_DT_RD,
+ AUE_N_TRANSFER,
+};
+
+#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
+
+#define AUE_TIMEOUT 100 /* 10*ms */
+#define AUE_MIN_FRAMELEN 60
+
+#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 GET_MII(sc) uether_getmii(&(sc)->sc_ue)
+
+struct aue_intrpkt {
+ uint8_t aue_txstat0;
+ uint8_t aue_txstat1;
+ uint8_t aue_rxstat;
+ uint8_t aue_rxlostpkt0;
+ uint8_t aue_rxlostpkt1;
+ uint8_t aue_wakeupstat;
+ uint8_t aue_rsvd;
+} __packed;
+
+struct aue_rxpkt {
+ uint16_t aue_pktlen;
+ uint8_t aue_rxstat;
+ uint8_t pad;
+} __packed;
+
+struct aue_softc {
+ struct usb_ether sc_ue;
+ struct mtx sc_mtx;
+ struct usb_xfer *sc_xfer[AUE_N_TRANSFER];
+
+ int sc_flags;
+#define AUE_FLAG_LSYS 0x0001 /* use Linksys reset */
+#define AUE_FLAG_PNA 0x0002 /* has Home PNA */
+#define AUE_FLAG_PII 0x0004 /* Pegasus II chip */
+#define AUE_FLAG_LINK 0x0008 /* wait for link to come up */
+#define AUE_FLAG_VER_2 0x0200 /* chip is version 2 */
+#define AUE_FLAG_DUAL_PHY 0x0400 /* chip has two transcivers */
+};
+
+#define AUE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define AUE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define AUE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t)
diff --git a/sys/dev/usb/net/if_axe.c b/sys/dev/usb/net/if_axe.c
new file mode 100644
index 000000000000..117a3daa170f
--- /dev/null
+++ b/sys/dev/usb/net/if_axe.c
@@ -0,0 +1,1499 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * 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.
+ */
+
+/*
+ * 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 manually
+ * set it).
+ *
+ * (Adam Weinberger wanted me to name this driver if_gir.c.)
+ */
+
+/*
+ * Ax88178 and Ax88772 support backported from the OpenBSD driver.
+ * 2007/02/12, J.R. Oldroyd, fbsd@opal.com
+ *
+ * Manual here:
+ * http://www.asix.com.tw/FrootAttach/datasheet/AX88178_datasheet_Rev10.pdf
+ * http://www.asix.com.tw/FrootAttach/datasheet/AX88772_datasheet_Rev10.pdf
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/condvar.h>
+#include <sys/endian.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/ethernet.h>
+#include <net/if_types.h>
+#include <net/if_media.h>
+#include <net/if_vlan_var.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR axe_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/net/usb_ethernet.h>
+#include <dev/usb/net/if_axereg.h>
+
+#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
+
+#define AXE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP)
+
+#ifdef USB_DEBUG
+static int axe_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, axe, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB axe");
+SYSCTL_INT(_hw_usb_axe, OID_AUTO, debug, CTLFLAG_RWTUN, &axe_debug, 0,
+ "Debug level");
+#endif
+
+/*
+ * Various supported device vendors/products.
+ */
+static const STRUCT_USB_HOST_ID axe_devs[] = {
+#define AXE_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
+ AXE_DEV(ABOCOM, UF200, 0),
+ AXE_DEV(ACERCM, EP1427X2, 0),
+ AXE_DEV(APPLE, ETHERNET, AXE_FLAG_772),
+ AXE_DEV(ASIX, AX88172, 0),
+ AXE_DEV(ASIX, AX88178, AXE_FLAG_178),
+ AXE_DEV(ASIX, AX88772, AXE_FLAG_772),
+ AXE_DEV(ASIX, AX88772A, AXE_FLAG_772A),
+ AXE_DEV(ASIX, AX88772B, AXE_FLAG_772B),
+ AXE_DEV(ASIX, AX88772B_1, AXE_FLAG_772B),
+ AXE_DEV(ATEN, UC210T, 0),
+ AXE_DEV(BELKIN, F5D5055, AXE_FLAG_178),
+ AXE_DEV(BILLIONTON, USB2AR, 0),
+ AXE_DEV(CISCOLINKSYS, USB200MV2, AXE_FLAG_772A),
+ AXE_DEV(COREGA, FETHER_USB2_TX, 0),
+ AXE_DEV(DLINK, DUBE100, 0),
+ AXE_DEV(DLINK, DUBE100B1, AXE_FLAG_772),
+ AXE_DEV(DLINK, DUBE100C1, AXE_FLAG_772B),
+ AXE_DEV(GOODWAY, GWUSB2E, 0),
+ AXE_DEV(IODATA, ETGUS2, AXE_FLAG_178),
+ AXE_DEV(JVC, MP_PRX1, 0),
+ AXE_DEV(LENOVO, ETHERNET, AXE_FLAG_772B),
+ AXE_DEV(LINKSYS2, USB200M, 0),
+ AXE_DEV(LINKSYS4, USB1000, AXE_FLAG_178),
+ AXE_DEV(LOGITEC, LAN_GTJU2A, AXE_FLAG_178),
+ AXE_DEV(MELCO, LUAU2KTX, 0),
+ AXE_DEV(MELCO, LUA3U2AGT, AXE_FLAG_178),
+ AXE_DEV(NETGEAR, FA120, 0),
+ AXE_DEV(OQO, ETHER01PLUS, AXE_FLAG_772),
+ AXE_DEV(PLANEX3, GU1000T, AXE_FLAG_178),
+ AXE_DEV(SITECOM, LN029, 0),
+ AXE_DEV(SITECOMEU, LN028, AXE_FLAG_178),
+ AXE_DEV(SITECOMEU, LN031, AXE_FLAG_178),
+ AXE_DEV(SYSTEMTALKS, SGCX2UL, 0),
+#undef AXE_DEV
+};
+
+static device_probe_t axe_probe;
+static device_attach_t axe_attach;
+static device_detach_t axe_detach;
+
+static usb_callback_t axe_bulk_read_callback;
+static usb_callback_t axe_bulk_write_callback;
+
+static miibus_readreg_t axe_miibus_readreg;
+static miibus_writereg_t axe_miibus_writereg;
+static miibus_statchg_t axe_miibus_statchg;
+
+static uether_fn_t axe_attach_post;
+static uether_fn_t axe_init;
+static uether_fn_t axe_stop;
+static uether_fn_t axe_start;
+static uether_fn_t axe_tick;
+static uether_fn_t axe_setmulti;
+static uether_fn_t axe_setpromisc;
+
+static int axe_attach_post_sub(struct usb_ether *);
+static int axe_ifmedia_upd(if_t);
+static void axe_ifmedia_sts(if_t, struct ifmediareq *);
+static int axe_cmd(struct axe_softc *, int, int, int, void *);
+static void axe_ax88178_init(struct axe_softc *);
+static void axe_ax88772_init(struct axe_softc *);
+static void axe_ax88772_phywake(struct axe_softc *);
+static void axe_ax88772a_init(struct axe_softc *);
+static void axe_ax88772b_init(struct axe_softc *);
+static int axe_get_phyno(struct axe_softc *, int);
+static int axe_ioctl(if_t, u_long, caddr_t);
+static int axe_rx_frame(struct usb_ether *, struct usb_page_cache *, int);
+static int axe_rxeof(struct usb_ether *, struct usb_page_cache *,
+ unsigned offset, unsigned, struct axe_csum_hdr *);
+static void axe_csum_cfg(struct usb_ether *);
+
+static const struct usb_config axe_config[AXE_N_TRANSFER] = {
+ [AXE_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .frames = 16,
+ .bufsize = 16 * MCLBYTES,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = axe_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ },
+
+ [AXE_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = 16384, /* bytes */
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = axe_bulk_read_callback,
+ .timeout = 0, /* no timeout */
+ },
+};
+
+static const struct ax88772b_mfb ax88772b_mfb_table[] = {
+ { 0x8000, 0x8001, 2048 },
+ { 0x8100, 0x8147, 4096},
+ { 0x8200, 0x81EB, 6144},
+ { 0x8300, 0x83D7, 8192},
+ { 0x8400, 0x851E, 16384},
+ { 0x8500, 0x8666, 20480},
+ { 0x8600, 0x87AE, 24576},
+ { 0x8700, 0x8A3D, 32768}
+};
+
+static device_method_t axe_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, axe_probe),
+ DEVMETHOD(device_attach, axe_attach),
+ DEVMETHOD(device_detach, axe_detach),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, axe_miibus_readreg),
+ DEVMETHOD(miibus_writereg, axe_miibus_writereg),
+ DEVMETHOD(miibus_statchg, axe_miibus_statchg),
+
+ DEVMETHOD_END
+};
+
+static driver_t axe_driver = {
+ .name = "axe",
+ .methods = axe_methods,
+ .size = sizeof(struct axe_softc),
+};
+
+DRIVER_MODULE(axe, uhub, axe_driver, NULL, NULL);
+DRIVER_MODULE(miibus, axe, miibus_driver, 0, 0);
+MODULE_DEPEND(axe, uether, 1, 1, 1);
+MODULE_DEPEND(axe, usb, 1, 1, 1);
+MODULE_DEPEND(axe, ether, 1, 1, 1);
+MODULE_DEPEND(axe, miibus, 1, 1, 1);
+MODULE_VERSION(axe, 1);
+USB_PNP_HOST_INFO(axe_devs);
+
+static const struct usb_ether_methods axe_ue_methods = {
+ .ue_attach_post = axe_attach_post,
+ .ue_attach_post_sub = axe_attach_post_sub,
+ .ue_start = axe_start,
+ .ue_init = axe_init,
+ .ue_stop = axe_stop,
+ .ue_tick = axe_tick,
+ .ue_setmulti = axe_setmulti,
+ .ue_setpromisc = axe_setpromisc,
+ .ue_mii_upd = axe_ifmedia_upd,
+ .ue_mii_sts = axe_ifmedia_sts,
+};
+
+static int
+axe_cmd(struct axe_softc *sc, int cmd, int index, int val, void *buf)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+
+ AXE_LOCK_ASSERT(sc, MA_OWNED);
+
+ req.bmRequestType = (AXE_CMD_IS_WRITE(cmd) ?
+ UT_WRITE_VENDOR_DEVICE :
+ 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 = uether_do_request(&sc->sc_ue, &req, buf, 1000);
+
+ return (err);
+}
+
+static int
+axe_miibus_readreg(device_t dev, int phy, int reg)
+{
+ struct axe_softc *sc = device_get_softc(dev);
+ uint16_t val;
+ int locked;
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ AXE_LOCK(sc);
+
+ axe_cmd(sc, AXE_CMD_MII_OPMODE_SW, 0, 0, NULL);
+ axe_cmd(sc, AXE_CMD_MII_READ_REG, reg, phy, &val);
+ axe_cmd(sc, AXE_CMD_MII_OPMODE_HW, 0, 0, NULL);
+
+ val = le16toh(val);
+ if (AXE_IS_772(sc) && reg == MII_BMSR) {
+ /*
+ * BMSR of AX88772 indicates that it supports extended
+ * capability but the extended status register is
+ * revered for embedded ethernet PHY. So clear the
+ * extended capability bit of BMSR.
+ */
+ val &= ~BMSR_EXTCAP;
+ }
+
+ if (!locked)
+ AXE_UNLOCK(sc);
+ return (val);
+}
+
+static int
+axe_miibus_writereg(device_t dev, int phy, int reg, int val)
+{
+ struct axe_softc *sc = device_get_softc(dev);
+ int locked;
+
+ val = htole32(val);
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ AXE_LOCK(sc);
+
+ axe_cmd(sc, AXE_CMD_MII_OPMODE_SW, 0, 0, NULL);
+ axe_cmd(sc, AXE_CMD_MII_WRITE_REG, reg, phy, &val);
+ axe_cmd(sc, AXE_CMD_MII_OPMODE_HW, 0, 0, NULL);
+
+ if (!locked)
+ AXE_UNLOCK(sc);
+ 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);
+ if_t ifp;
+ uint16_t val;
+ int err, locked;
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ AXE_LOCK(sc);
+
+ ifp = uether_getifp(&sc->sc_ue);
+ if (mii == NULL || ifp == NULL ||
+ (if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0)
+ goto done;
+
+ sc->sc_flags &= ~AXE_FLAG_LINK;
+ if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) ==
+ (IFM_ACTIVE | IFM_AVALID)) {
+ switch (IFM_SUBTYPE(mii->mii_media_active)) {
+ case IFM_10_T:
+ case IFM_100_TX:
+ sc->sc_flags |= AXE_FLAG_LINK;
+ break;
+ case IFM_1000_T:
+ if ((sc->sc_flags & AXE_FLAG_178) == 0)
+ break;
+ sc->sc_flags |= AXE_FLAG_LINK;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* Lost link, do nothing. */
+ if ((sc->sc_flags & AXE_FLAG_LINK) == 0)
+ goto done;
+
+ val = 0;
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) {
+ val |= AXE_MEDIA_FULL_DUPLEX;
+ if (AXE_IS_178_FAMILY(sc)) {
+ if ((IFM_OPTIONS(mii->mii_media_active) &
+ IFM_ETH_TXPAUSE) != 0)
+ val |= AXE_178_MEDIA_TXFLOW_CONTROL_EN;
+ if ((IFM_OPTIONS(mii->mii_media_active) &
+ IFM_ETH_RXPAUSE) != 0)
+ val |= AXE_178_MEDIA_RXFLOW_CONTROL_EN;
+ }
+ }
+ if (AXE_IS_178_FAMILY(sc)) {
+ val |= AXE_178_MEDIA_RX_EN | AXE_178_MEDIA_MAGIC;
+ if ((sc->sc_flags & AXE_FLAG_178) != 0)
+ val |= AXE_178_MEDIA_ENCK;
+ 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);
+done:
+ if (!locked)
+ AXE_UNLOCK(sc);
+}
+
+/*
+ * Set media options.
+ */
+static int
+axe_ifmedia_upd(if_t ifp)
+{
+ struct axe_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = GET_MII(sc);
+ struct mii_softc *miisc;
+ int error;
+
+ AXE_LOCK_ASSERT(sc, MA_OWNED);
+
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list)
+ PHY_RESET(miisc);
+ error = mii_mediachg(mii);
+ return (error);
+}
+
+/*
+ * Report current media status.
+ */
+static void
+axe_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct axe_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = GET_MII(sc);
+
+ AXE_LOCK(sc);
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+ AXE_UNLOCK(sc);
+}
+
+static u_int
+axe_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
+{
+ uint8_t *hashtbl = arg;
+ uint32_t h;
+
+ h = ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 26;
+ hashtbl[h / 8] |= 1 << (h % 8);
+
+ return (1);
+}
+
+static void
+axe_setmulti(struct usb_ether *ue)
+{
+ struct axe_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ uint16_t rxmode;
+ uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ AXE_LOCK_ASSERT(sc, MA_OWNED);
+
+ axe_cmd(sc, AXE_CMD_RXCTL_READ, 0, 0, &rxmode);
+ rxmode = le16toh(rxmode);
+
+ if (if_getflags(ifp) & (IFF_ALLMULTI | IFF_PROMISC)) {
+ rxmode |= AXE_RXCMD_ALLMULTI;
+ axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL);
+ return;
+ }
+ rxmode &= ~AXE_RXCMD_ALLMULTI;
+
+ if_foreach_llmaddr(ifp, axe_hash_maddr, &hashtbl);
+
+ axe_cmd(sc, AXE_CMD_WRITE_MCAST, 0, 0, (void *)&hashtbl);
+ axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL);
+}
+
+static int
+axe_get_phyno(struct axe_softc *sc, int sel)
+{
+ int phyno;
+
+ switch (AXE_PHY_TYPE(sc->sc_phyaddrs[sel])) {
+ case PHY_TYPE_100_HOME:
+ case PHY_TYPE_GIG:
+ phyno = AXE_PHY_NO(sc->sc_phyaddrs[sel]);
+ break;
+ case PHY_TYPE_SPECIAL:
+ /* FALLTHROUGH */
+ case PHY_TYPE_RSVD:
+ /* FALLTHROUGH */
+ case PHY_TYPE_NON_SUP:
+ /* FALLTHROUGH */
+ default:
+ phyno = -1;
+ break;
+ }
+
+ return (phyno);
+}
+
+#define AXE_GPIO_WRITE(x, y) do { \
+ axe_cmd(sc, AXE_CMD_WRITE_GPIO, 0, (x), NULL); \
+ uether_pause(ue, (y)); \
+} while (0)
+
+static void
+axe_ax88178_init(struct axe_softc *sc)
+{
+ struct usb_ether *ue;
+ int gpio0, ledmode, phymode;
+ uint16_t eeprom, val;
+
+ ue = &sc->sc_ue;
+ 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 = AXE_PHY_MODE_MARVELL;
+ gpio0 = 1;
+ ledmode = 0;
+ } else {
+ phymode = eeprom & 0x7f;
+ gpio0 = (eeprom & 0x80) ? 0 : 1;
+ ledmode = eeprom >> 8;
+ }
+
+ if (bootverbose)
+ device_printf(sc->sc_ue.ue_dev,
+ "EEPROM data : 0x%04x, phymode : 0x%02x\n", eeprom,
+ phymode);
+ /* Program GPIOs depending on PHY hardware. */
+ switch (phymode) {
+ case AXE_PHY_MODE_MARVELL:
+ if (gpio0 == 1) {
+ AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO0_EN,
+ hz / 32);
+ AXE_GPIO_WRITE(AXE_GPIO0_EN | AXE_GPIO2 | AXE_GPIO2_EN,
+ hz / 32);
+ AXE_GPIO_WRITE(AXE_GPIO0_EN | AXE_GPIO2_EN, hz / 4);
+ AXE_GPIO_WRITE(AXE_GPIO0_EN | AXE_GPIO2 | AXE_GPIO2_EN,
+ hz / 32);
+ } else {
+ AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO1 |
+ AXE_GPIO1_EN, hz / 3);
+ if (ledmode == 1) {
+ AXE_GPIO_WRITE(AXE_GPIO1_EN, hz / 3);
+ AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN,
+ hz / 3);
+ } else {
+ AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN |
+ AXE_GPIO2 | AXE_GPIO2_EN, hz / 32);
+ AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN |
+ AXE_GPIO2_EN, hz / 4);
+ AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN |
+ AXE_GPIO2 | AXE_GPIO2_EN, hz / 32);
+ }
+ }
+ break;
+ case AXE_PHY_MODE_CICADA:
+ case AXE_PHY_MODE_CICADA_V2:
+ case AXE_PHY_MODE_CICADA_V2_ASIX:
+ if (gpio0 == 1)
+ AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO0 |
+ AXE_GPIO0_EN, hz / 32);
+ else
+ AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO1 |
+ AXE_GPIO1_EN, hz / 32);
+ break;
+ case AXE_PHY_MODE_AGERE:
+ AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM | AXE_GPIO1 |
+ AXE_GPIO1_EN, hz / 32);
+ AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | AXE_GPIO2 |
+ AXE_GPIO2_EN, hz / 32);
+ AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | AXE_GPIO2_EN, hz / 4);
+ AXE_GPIO_WRITE(AXE_GPIO1 | AXE_GPIO1_EN | AXE_GPIO2 |
+ AXE_GPIO2_EN, hz / 32);
+ break;
+ case AXE_PHY_MODE_REALTEK_8211CL:
+ case AXE_PHY_MODE_REALTEK_8211BN:
+ case AXE_PHY_MODE_REALTEK_8251CL:
+ val = gpio0 == 1 ? AXE_GPIO0 | AXE_GPIO0_EN :
+ AXE_GPIO1 | AXE_GPIO1_EN;
+ AXE_GPIO_WRITE(val, hz / 32);
+ AXE_GPIO_WRITE(val | AXE_GPIO2 | AXE_GPIO2_EN, hz / 32);
+ AXE_GPIO_WRITE(val | AXE_GPIO2_EN, hz / 4);
+ AXE_GPIO_WRITE(val | AXE_GPIO2 | AXE_GPIO2_EN, hz / 32);
+ if (phymode == AXE_PHY_MODE_REALTEK_8211CL) {
+ axe_miibus_writereg(ue->ue_dev, sc->sc_phyno,
+ 0x1F, 0x0005);
+ axe_miibus_writereg(ue->ue_dev, sc->sc_phyno,
+ 0x0C, 0x0000);
+ val = axe_miibus_readreg(ue->ue_dev, sc->sc_phyno,
+ 0x0001);
+ axe_miibus_writereg(ue->ue_dev, sc->sc_phyno,
+ 0x01, val | 0x0080);
+ axe_miibus_writereg(ue->ue_dev, sc->sc_phyno,
+ 0x1F, 0x0000);
+ }
+ break;
+ default:
+ /* Unknown PHY model or no need to program GPIOs. */
+ break;
+ }
+
+ /* soft reset */
+ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_CLEAR, NULL);
+ uether_pause(ue, hz / 4);
+
+ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0,
+ AXE_SW_RESET_PRL | AXE_178_RESET_MAGIC, NULL);
+ uether_pause(ue, hz / 4);
+ /* Enable MII/GMII/RGMII interface to work with external PHY. */
+ axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0, NULL);
+ uether_pause(ue, hz / 4);
+
+ 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);
+ uether_pause(&sc->sc_ue, hz / 16);
+
+ if (sc->sc_phyno == AXE_772_PHY_NO_EPHY) {
+ /* ask for the embedded PHY */
+ axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, 0x01, NULL);
+ uether_pause(&sc->sc_ue, hz / 64);
+
+ /* power down and reset state, pin reset state */
+ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0,
+ AXE_SW_RESET_CLEAR, NULL);
+ uether_pause(&sc->sc_ue, hz / 16);
+
+ /* 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);
+ uether_pause(&sc->sc_ue, hz / 4);
+
+ /* 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);
+ uether_pause(&sc->sc_ue, hz / 64);
+
+ /* power down internal PHY */
+ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0,
+ AXE_SW_RESET_IPPD | AXE_SW_RESET_PRL, NULL);
+ }
+
+ uether_pause(&sc->sc_ue, hz / 4);
+ axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL);
+}
+
+static void
+axe_ax88772_phywake(struct axe_softc *sc)
+{
+ if (sc->sc_phyno == AXE_772_PHY_NO_EPHY) {
+ /* Manually select internal(embedded) PHY - MAC mode. */
+ axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, AXE_SW_PHY_SELECT_SS_ENB |
+ AXE_SW_PHY_SELECT_EMBEDDED | AXE_SW_PHY_SELECT_SS_MII,
+ NULL);
+ uether_pause(&sc->sc_ue, hz / 32);
+ } else {
+ /*
+ * Manually select external PHY - MAC mode.
+ * Reverse MII/RMII is for AX88772A PHY mode.
+ */
+ axe_cmd(sc, AXE_CMD_SW_PHY_SELECT, 0, AXE_SW_PHY_SELECT_SS_ENB |
+ AXE_SW_PHY_SELECT_EXT | AXE_SW_PHY_SELECT_SS_MII, NULL);
+ uether_pause(&sc->sc_ue, hz / 32);
+ }
+ /* Take PHY out of power down. */
+ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_IPPD |
+ AXE_SW_RESET_IPRL, NULL);
+ uether_pause(&sc->sc_ue, hz / 4);
+ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_IPRL, NULL);
+ uether_pause(&sc->sc_ue, hz);
+ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_CLEAR, NULL);
+ uether_pause(&sc->sc_ue, hz / 32);
+ axe_cmd(sc, AXE_CMD_SW_RESET_REG, 0, AXE_SW_RESET_IPRL, NULL);
+ uether_pause(&sc->sc_ue, hz / 32);
+}
+
+static void
+axe_ax88772a_init(struct axe_softc *sc)
+{
+ struct usb_ether *ue;
+
+ ue = &sc->sc_ue;
+ /* Reload EEPROM. */
+ AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM, hz / 32);
+ axe_ax88772_phywake(sc);
+ /* Stop MAC. */
+ axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL);
+}
+
+static void
+axe_ax88772b_init(struct axe_softc *sc)
+{
+ struct usb_ether *ue;
+ uint16_t eeprom;
+ uint8_t *eaddr;
+ int i;
+
+ ue = &sc->sc_ue;
+ /* Reload EEPROM. */
+ AXE_GPIO_WRITE(AXE_GPIO_RELOAD_EEPROM, hz / 32);
+ /*
+ * Save PHY power saving configuration(high byte) and
+ * clear EEPROM checksum value(low byte).
+ */
+ axe_cmd(sc, AXE_CMD_SROM_READ, 0, AXE_EEPROM_772B_PHY_PWRCFG, &eeprom);
+ sc->sc_pwrcfg = le16toh(eeprom) & 0xFF00;
+
+ /*
+ * Auto-loaded default station address from internal ROM is
+ * 00:00:00:00:00:00 such that an explicit access to EEPROM
+ * is required to get real station address.
+ */
+ eaddr = ue->ue_eaddr;
+ for (i = 0; i < ETHER_ADDR_LEN / 2; i++) {
+ axe_cmd(sc, AXE_CMD_SROM_READ, 0, AXE_EEPROM_772B_NODE_ID + i,
+ &eeprom);
+ eeprom = le16toh(eeprom);
+ *eaddr++ = (uint8_t)(eeprom & 0xFF);
+ *eaddr++ = (uint8_t)((eeprom >> 8) & 0xFF);
+ }
+ /* Wakeup PHY. */
+ axe_ax88772_phywake(sc);
+ /* Stop MAC. */
+ axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, 0, NULL);
+}
+
+#undef AXE_GPIO_WRITE
+
+static void
+axe_reset(struct axe_softc *sc)
+{
+ struct usb_config_descriptor *cd;
+ usb_error_t err;
+
+ cd = usbd_get_config_descriptor(sc->sc_ue.ue_udev);
+
+ err = usbd_req_set_config(sc->sc_ue.ue_udev, &sc->sc_mtx,
+ cd->bConfigurationValue);
+ if (err)
+ DPRINTF("reset failed (ignored)\n");
+
+ /* Wait a little while for the chip to get its brains in order. */
+ uether_pause(&sc->sc_ue, hz / 100);
+
+ /* Reinitialize controller to achieve full reset. */
+ if (sc->sc_flags & AXE_FLAG_178)
+ axe_ax88178_init(sc);
+ else if (sc->sc_flags & AXE_FLAG_772)
+ axe_ax88772_init(sc);
+ else if (sc->sc_flags & AXE_FLAG_772A)
+ axe_ax88772a_init(sc);
+ else if (sc->sc_flags & AXE_FLAG_772B)
+ axe_ax88772b_init(sc);
+}
+
+static void
+axe_attach_post(struct usb_ether *ue)
+{
+ struct axe_softc *sc = uether_getsc(ue);
+
+ /*
+ * Load PHY indexes first. Needed by axe_xxx_init().
+ */
+ axe_cmd(sc, AXE_CMD_READ_PHYID, 0, 0, sc->sc_phyaddrs);
+ if (bootverbose)
+ device_printf(sc->sc_ue.ue_dev, "PHYADDR 0x%02x:0x%02x\n",
+ sc->sc_phyaddrs[0], sc->sc_phyaddrs[1]);
+ sc->sc_phyno = axe_get_phyno(sc, AXE_PHY_SEL_PRI);
+ if (sc->sc_phyno == -1)
+ sc->sc_phyno = axe_get_phyno(sc, AXE_PHY_SEL_SEC);
+ if (sc->sc_phyno == -1) {
+ device_printf(sc->sc_ue.ue_dev,
+ "no valid PHY address found, assuming PHY address 0\n");
+ sc->sc_phyno = 0;
+ }
+
+ /* Initialize controller and get station address. */
+ if (sc->sc_flags & AXE_FLAG_178) {
+ axe_ax88178_init(sc);
+ axe_cmd(sc, AXE_178_CMD_READ_NODEID, 0, 0, ue->ue_eaddr);
+ } else if (sc->sc_flags & AXE_FLAG_772) {
+ axe_ax88772_init(sc);
+ axe_cmd(sc, AXE_178_CMD_READ_NODEID, 0, 0, ue->ue_eaddr);
+ } else if (sc->sc_flags & AXE_FLAG_772A) {
+ axe_ax88772a_init(sc);
+ axe_cmd(sc, AXE_178_CMD_READ_NODEID, 0, 0, ue->ue_eaddr);
+ } else if (sc->sc_flags & AXE_FLAG_772B) {
+ axe_ax88772b_init(sc);
+ } else
+ axe_cmd(sc, AXE_172_CMD_READ_NODEID, 0, 0, ue->ue_eaddr);
+
+ /*
+ * Fetch IPG values.
+ */
+ if (sc->sc_flags & (AXE_FLAG_772A | AXE_FLAG_772B)) {
+ /* Set IPG values. */
+ sc->sc_ipgs[0] = 0x15;
+ sc->sc_ipgs[1] = 0x16;
+ sc->sc_ipgs[2] = 0x1A;
+ } else
+ axe_cmd(sc, AXE_CMD_READ_IPG012, 0, 0, sc->sc_ipgs);
+}
+
+static int
+axe_attach_post_sub(struct usb_ether *ue)
+{
+ struct axe_softc *sc;
+ if_t ifp;
+ u_int adv_pause;
+ int error;
+
+ sc = uether_getsc(ue);
+ ifp = ue->ue_ifp;
+ if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
+ if_setstartfn(ifp, uether_start);
+ if_setioctlfn(ifp, axe_ioctl);
+ if_setinitfn(ifp, uether_init);
+ if_setsendqlen(ifp, ifqmaxlen);
+ if_setsendqready(ifp);
+
+ if (AXE_IS_178_FAMILY(sc))
+ if_setcapabilitiesbit(ifp, IFCAP_VLAN_MTU, 0);
+ if (sc->sc_flags & AXE_FLAG_772B) {
+ if_setcapabilitiesbit(ifp, IFCAP_TXCSUM | IFCAP_RXCSUM, 0);
+ if_sethwassist(ifp, AXE_CSUM_FEATURES);
+ /*
+ * Checksum offloading of AX88772B also works with VLAN
+ * tagged frames but there is no way to take advantage
+ * of the feature because vlan(4) assumes
+ * IFCAP_VLAN_HWTAGGING is prerequisite condition to
+ * support checksum offloading with VLAN. VLAN hardware
+ * tagging support of AX88772B is very limited so it's
+ * not possible to announce IFCAP_VLAN_HWTAGGING.
+ */
+ }
+ if_setcapenable(ifp, if_getcapabilities(ifp));
+ if (sc->sc_flags & (AXE_FLAG_772A | AXE_FLAG_772B | AXE_FLAG_178))
+ adv_pause = MIIF_DOPAUSE;
+ else
+ adv_pause = 0;
+ bus_topo_lock();
+ error = mii_attach(ue->ue_dev, &ue->ue_miibus, ifp,
+ uether_ifmedia_upd, ue->ue_methods->ue_mii_sts,
+ BMSR_DEFCAPMASK, sc->sc_phyno, MII_OFFSET_ANY, adv_pause);
+ bus_topo_unlock();
+
+ return (error);
+}
+
+/*
+ * Probe for a AX88172 chip.
+ */
+static int
+axe_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != AXE_CONFIG_IDX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != AXE_IFACE_IDX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(axe_devs, sizeof(axe_devs), uaa));
+}
+
+/*
+ * Attach the interface. Allocate softc structures, do ifmedia
+ * setup and ethernet/BPF attach.
+ */
+static int
+axe_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct axe_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+ uint8_t iface_index;
+ int error;
+
+ sc->sc_flags = USB_GET_DRIVER_INFO(uaa);
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ iface_index = AXE_IFACE_IDX;
+ error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer,
+ axe_config, AXE_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "allocating USB transfers failed\n");
+ goto detach;
+ }
+
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &axe_ue_methods;
+
+ error = uether_ifattach(ue);
+ if (error) {
+ device_printf(dev, "could not attach interface\n");
+ goto detach;
+ }
+ return (0); /* success */
+
+detach:
+ axe_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+axe_detach(device_t dev)
+{
+ struct axe_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ usbd_transfer_unsetup(sc->sc_xfer, AXE_N_TRANSFER);
+ uether_ifdetach(ue);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+#if (AXE_BULK_BUF_SIZE >= 0x10000)
+#error "Please update axe_bulk_read_callback()!"
+#endif
+
+static void
+axe_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct axe_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_ether *ue = &sc->sc_ue;
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ axe_rx_frame(ue, pc, actlen);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ uether_rxflush(ue);
+ return;
+
+ default: /* Error */
+ DPRINTF("bulk read error, %s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static int
+axe_rx_frame(struct usb_ether *ue, struct usb_page_cache *pc, int actlen)
+{
+ struct axe_softc *sc;
+ struct axe_sframe_hdr hdr;
+ struct axe_csum_hdr csum_hdr;
+ int error, len, pos;
+
+ sc = uether_getsc(ue);
+ pos = 0;
+ len = 0;
+ error = 0;
+ if ((sc->sc_flags & AXE_FLAG_STD_FRAME) != 0) {
+ while (pos < actlen) {
+ if ((int)(pos + sizeof(hdr)) > actlen) {
+ /* too little data */
+ error = EINVAL;
+ break;
+ }
+ usbd_copy_out(pc, pos, &hdr, sizeof(hdr));
+
+ if ((hdr.len ^ hdr.ilen) != sc->sc_lenmask) {
+ /* we lost sync */
+ error = EINVAL;
+ break;
+ }
+ pos += sizeof(hdr);
+ len = le16toh(hdr.len);
+ if (pos + len > actlen) {
+ /* invalid length */
+ error = EINVAL;
+ break;
+ }
+ axe_rxeof(ue, pc, pos, len, NULL);
+ pos += len + (len % 2);
+ }
+ } else if ((sc->sc_flags & AXE_FLAG_CSUM_FRAME) != 0) {
+ while (pos < actlen) {
+ if ((int)(pos + sizeof(csum_hdr)) > actlen) {
+ /* too little data */
+ error = EINVAL;
+ break;
+ }
+ usbd_copy_out(pc, pos, &csum_hdr, sizeof(csum_hdr));
+
+ csum_hdr.len = le16toh(csum_hdr.len);
+ csum_hdr.ilen = le16toh(csum_hdr.ilen);
+ csum_hdr.cstatus = le16toh(csum_hdr.cstatus);
+ if ((AXE_CSUM_RXBYTES(csum_hdr.len) ^
+ AXE_CSUM_RXBYTES(csum_hdr.ilen)) !=
+ sc->sc_lenmask) {
+ /* we lost sync */
+ error = EINVAL;
+ break;
+ }
+ /*
+ * Get total transferred frame length including
+ * checksum header. The length should be multiple
+ * of 4.
+ */
+ len = sizeof(csum_hdr) + AXE_CSUM_RXBYTES(csum_hdr.len);
+ len = (len + 3) & ~3;
+ if (pos + len > actlen) {
+ /* invalid length */
+ error = EINVAL;
+ break;
+ }
+ axe_rxeof(ue, pc, pos + sizeof(csum_hdr),
+ AXE_CSUM_RXBYTES(csum_hdr.len), &csum_hdr);
+ pos += len;
+ }
+ } else
+ axe_rxeof(ue, pc, 0, actlen, NULL);
+
+ if (error != 0)
+ if_inc_counter(ue->ue_ifp, IFCOUNTER_IERRORS, 1);
+ return (error);
+}
+
+static int
+axe_rxeof(struct usb_ether *ue, struct usb_page_cache *pc, unsigned offset,
+ unsigned len, struct axe_csum_hdr *csum_hdr)
+{
+ if_t ifp = ue->ue_ifp;
+ struct mbuf *m;
+
+ if (len < ETHER_HDR_LEN || len > MCLBYTES - ETHER_ALIGN) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ return (EINVAL);
+ }
+
+ m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (m == NULL) {
+ if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
+ return (ENOMEM);
+ }
+ m->m_len = m->m_pkthdr.len = MCLBYTES;
+ m_adj(m, ETHER_ALIGN);
+
+ usbd_copy_out(pc, offset, mtod(m, uint8_t *), len);
+
+ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
+ m->m_pkthdr.rcvif = ifp;
+ m->m_pkthdr.len = m->m_len = len;
+
+ if (csum_hdr != NULL && csum_hdr->cstatus & AXE_CSUM_HDR_L3_TYPE_IPV4) {
+ if ((csum_hdr->cstatus & (AXE_CSUM_HDR_L4_CSUM_ERR |
+ AXE_CSUM_HDR_L3_CSUM_ERR)) == 0) {
+ m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED |
+ CSUM_IP_VALID;
+ if ((csum_hdr->cstatus & AXE_CSUM_HDR_L4_TYPE_MASK) ==
+ AXE_CSUM_HDR_L4_TYPE_TCP ||
+ (csum_hdr->cstatus & AXE_CSUM_HDR_L4_TYPE_MASK) ==
+ AXE_CSUM_HDR_L4_TYPE_UDP) {
+ m->m_pkthdr.csum_flags |=
+ CSUM_DATA_VALID | CSUM_PSEUDO_HDR;
+ m->m_pkthdr.csum_data = 0xffff;
+ }
+ }
+ }
+
+ (void)mbufq_enqueue(&ue->ue_rxq, m);
+ return (0);
+}
+
+#if ((AXE_BULK_BUF_SIZE >= 0x10000) || (AXE_BULK_BUF_SIZE < (MCLBYTES+4)))
+#error "Please update axe_bulk_write_callback()!"
+#endif
+
+static void
+axe_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct axe_softc *sc = usbd_xfer_softc(xfer);
+ struct axe_sframe_hdr hdr;
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ int nframes, pos;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "transfer complete\n");
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ if ((sc->sc_flags & AXE_FLAG_LINK) == 0 ||
+ (if_getdrvflags(ifp) & IFF_DRV_OACTIVE) != 0) {
+ /*
+ * Don't send anything if there is no link or
+ * controller is busy.
+ */
+ return;
+ }
+
+ for (nframes = 0; nframes < 16 &&
+ !if_sendq_empty(ifp); nframes++) {
+ m = if_dequeue(ifp);
+ if (m == NULL)
+ break;
+ usbd_xfer_set_frame_offset(xfer, nframes * MCLBYTES,
+ nframes);
+ pos = 0;
+ pc = usbd_xfer_get_frame(xfer, nframes);
+ if (AXE_IS_178_FAMILY(sc)) {
+ hdr.len = htole16(m->m_pkthdr.len);
+ hdr.ilen = ~hdr.len;
+ /*
+ * If upper stack computed checksum, driver
+ * should tell controller not to insert
+ * computed checksum for checksum offloading
+ * enabled controller.
+ */
+ if (if_getcapabilities(ifp) & IFCAP_TXCSUM) {
+ if ((m->m_pkthdr.csum_flags &
+ AXE_CSUM_FEATURES) != 0)
+ hdr.len |= htole16(
+ AXE_TX_CSUM_PSEUDO_HDR);
+ else
+ hdr.len |= htole16(
+ AXE_TX_CSUM_DIS);
+ }
+ usbd_copy_in(pc, pos, &hdr, sizeof(hdr));
+ pos += sizeof(hdr);
+ usbd_m_copy_in(pc, pos, m, 0, m->m_pkthdr.len);
+ pos += m->m_pkthdr.len;
+ if ((pos % 512) == 0) {
+ hdr.len = 0;
+ hdr.ilen = 0xffff;
+ usbd_copy_in(pc, pos, &hdr,
+ sizeof(hdr));
+ pos += sizeof(hdr);
+ }
+ } else {
+ usbd_m_copy_in(pc, pos, m, 0, m->m_pkthdr.len);
+ pos += m->m_pkthdr.len;
+ }
+
+ /*
+ * XXX
+ * Update TX packet counter here. This is not
+ * correct way but it seems that there is no way
+ * to know how many packets are sent at the end
+ * of transfer because controller combines
+ * multiple writes into single one if there is
+ * room in TX buffer of controller.
+ */
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /*
+ * if there's a BPF listener, bounce a copy
+ * of this frame to him:
+ */
+ BPF_MTAP(ifp, m);
+
+ m_freem(m);
+
+ /* Set frame length. */
+ usbd_xfer_set_frame_len(xfer, nframes, pos);
+ }
+ if (nframes != 0) {
+ usbd_xfer_set_frames(xfer, nframes);
+ usbd_transfer_submit(xfer);
+ if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0);
+ }
+ return;
+ /* NOTREACHED */
+ default: /* Error */
+ DPRINTFN(11, "transfer error, %s\n",
+ usbd_errstr(error));
+
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+axe_tick(struct usb_ether *ue)
+{
+ struct axe_softc *sc = uether_getsc(ue);
+ struct mii_data *mii = GET_MII(sc);
+
+ AXE_LOCK_ASSERT(sc, MA_OWNED);
+
+ mii_tick(mii);
+ if ((sc->sc_flags & AXE_FLAG_LINK) == 0) {
+ axe_miibus_statchg(ue->ue_dev);
+ if ((sc->sc_flags & AXE_FLAG_LINK) != 0)
+ axe_start(ue);
+ }
+}
+
+static void
+axe_start(struct usb_ether *ue)
+{
+ struct axe_softc *sc = uether_getsc(ue);
+
+ /*
+ * start the USB transfers, if not already started:
+ */
+ usbd_transfer_start(sc->sc_xfer[AXE_BULK_DT_RD]);
+ usbd_transfer_start(sc->sc_xfer[AXE_BULK_DT_WR]);
+}
+
+static void
+axe_csum_cfg(struct usb_ether *ue)
+{
+ struct axe_softc *sc;
+ if_t ifp;
+ uint16_t csum1, csum2;
+
+ sc = uether_getsc(ue);
+ AXE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if ((sc->sc_flags & AXE_FLAG_772B) != 0) {
+ ifp = uether_getifp(ue);
+ csum1 = 0;
+ csum2 = 0;
+ if ((if_getcapenable(ifp) & IFCAP_TXCSUM) != 0)
+ csum1 |= AXE_TXCSUM_IP | AXE_TXCSUM_TCP |
+ AXE_TXCSUM_UDP;
+ axe_cmd(sc, AXE_772B_CMD_WRITE_TXCSUM, csum2, csum1, NULL);
+ csum1 = 0;
+ csum2 = 0;
+ if ((if_getcapenable(ifp) & IFCAP_RXCSUM) != 0)
+ csum1 |= AXE_RXCSUM_IP | AXE_RXCSUM_IPVE |
+ AXE_RXCSUM_TCP | AXE_RXCSUM_UDP | AXE_RXCSUM_ICMP |
+ AXE_RXCSUM_IGMP;
+ axe_cmd(sc, AXE_772B_CMD_WRITE_RXCSUM, csum2, csum1, NULL);
+ }
+}
+
+static void
+axe_init(struct usb_ether *ue)
+{
+ struct axe_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ uint16_t rxmode;
+
+ AXE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0)
+ return;
+
+ /* Cancel pending I/O */
+ axe_stop(ue);
+
+ axe_reset(sc);
+
+ /* Set MAC address and transmitter IPG values. */
+ if (AXE_IS_178_FAMILY(sc)) {
+ axe_cmd(sc, AXE_178_CMD_WRITE_NODEID, 0, 0, if_getlladdr(ifp));
+ axe_cmd(sc, AXE_178_CMD_WRITE_IPG012, sc->sc_ipgs[2],
+ (sc->sc_ipgs[1] << 8) | (sc->sc_ipgs[0]), NULL);
+ } else {
+ axe_cmd(sc, AXE_172_CMD_WRITE_NODEID, 0, 0, if_getlladdr(ifp));
+ axe_cmd(sc, AXE_172_CMD_WRITE_IPG0, 0, sc->sc_ipgs[0], NULL);
+ axe_cmd(sc, AXE_172_CMD_WRITE_IPG1, 0, sc->sc_ipgs[1], NULL);
+ axe_cmd(sc, AXE_172_CMD_WRITE_IPG2, 0, sc->sc_ipgs[2], NULL);
+ }
+
+ if (AXE_IS_178_FAMILY(sc)) {
+ sc->sc_flags &= ~(AXE_FLAG_STD_FRAME | AXE_FLAG_CSUM_FRAME);
+ if ((sc->sc_flags & AXE_FLAG_772B) != 0 &&
+ (if_getcapenable(ifp) & IFCAP_RXCSUM) != 0) {
+ sc->sc_lenmask = AXE_CSUM_HDR_LEN_MASK;
+ sc->sc_flags |= AXE_FLAG_CSUM_FRAME;
+ } else {
+ sc->sc_lenmask = AXE_HDR_LEN_MASK;
+ sc->sc_flags |= AXE_FLAG_STD_FRAME;
+ }
+ }
+
+ /* Configure TX/RX checksum offloading. */
+ axe_csum_cfg(ue);
+
+ if (sc->sc_flags & AXE_FLAG_772B) {
+ /* AX88772B uses different maximum frame burst configuration. */
+ axe_cmd(sc, AXE_772B_CMD_RXCTL_WRITE_CFG,
+ ax88772b_mfb_table[AX88772B_MFB_16K].threshold,
+ ax88772b_mfb_table[AX88772B_MFB_16K].byte_cnt, NULL);
+ }
+
+ /* Enable receiver, set RX mode. */
+ rxmode = (AXE_RXCMD_MULTICAST | AXE_RXCMD_ENABLE);
+ if (AXE_IS_178_FAMILY(sc)) {
+ if (sc->sc_flags & AXE_FLAG_772B) {
+ /*
+ * Select RX header format type 1. Aligning IP
+ * header on 4 byte boundary is not needed when
+ * checksum offloading feature is not used
+ * because we always copy the received frame in
+ * RX handler. When RX checksum offloading is
+ * active, aligning IP header is required to
+ * reflect actual frame length including RX
+ * header size.
+ */
+ rxmode |= AXE_772B_RXCMD_HDR_TYPE_1;
+ if ((if_getcapenable(ifp) & IFCAP_RXCSUM) != 0)
+ rxmode |= AXE_772B_RXCMD_IPHDR_ALIGN;
+ } else {
+ /*
+ * Default Rx buffer size is too small to get
+ * maximum performance.
+ */
+ rxmode |= AXE_178_RXCMD_MFB_16384;
+ }
+ } else {
+ rxmode |= AXE_172_RXCMD_UNICAST;
+ }
+
+ /* If we want promiscuous mode, set the allframes bit. */
+ if (if_getflags(ifp) & IFF_PROMISC)
+ rxmode |= AXE_RXCMD_PROMISC;
+
+ if (if_getflags(ifp) & IFF_BROADCAST)
+ rxmode |= AXE_RXCMD_BROADCAST;
+
+ axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL);
+
+ /* Load the multicast filter. */
+ axe_setmulti(ue);
+
+ usbd_xfer_set_stall(sc->sc_xfer[AXE_BULK_DT_WR]);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+ /* Switch to selected media. */
+ axe_ifmedia_upd(ifp);
+}
+
+static void
+axe_setpromisc(struct usb_ether *ue)
+{
+ struct axe_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ uint16_t rxmode;
+
+ axe_cmd(sc, AXE_CMD_RXCTL_READ, 0, 0, &rxmode);
+
+ rxmode = le16toh(rxmode);
+
+ if (if_getflags(ifp) & IFF_PROMISC) {
+ rxmode |= AXE_RXCMD_PROMISC;
+ } else {
+ rxmode &= ~AXE_RXCMD_PROMISC;
+ }
+
+ axe_cmd(sc, AXE_CMD_RXCTL_WRITE, 0, rxmode, NULL);
+
+ axe_setmulti(ue);
+}
+
+static void
+axe_stop(struct usb_ether *ue)
+{
+ struct axe_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ AXE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, 0, (IFF_DRV_RUNNING | IFF_DRV_OACTIVE));
+ sc->sc_flags &= ~AXE_FLAG_LINK;
+
+ /*
+ * stop all the transfers, if not already stopped:
+ */
+ usbd_transfer_stop(sc->sc_xfer[AXE_BULK_DT_WR]);
+ usbd_transfer_stop(sc->sc_xfer[AXE_BULK_DT_RD]);
+}
+
+static int
+axe_ioctl(if_t ifp, u_long cmd, caddr_t data)
+{
+ struct usb_ether *ue = if_getsoftc(ifp);
+ struct axe_softc *sc;
+ struct ifreq *ifr;
+ int error, mask, reinit;
+
+ sc = uether_getsc(ue);
+ ifr = (struct ifreq *)data;
+ error = 0;
+ reinit = 0;
+ if (cmd == SIOCSIFCAP) {
+ AXE_LOCK(sc);
+ mask = ifr->ifr_reqcap ^ if_getcapenable(ifp);
+ if ((mask & IFCAP_TXCSUM) != 0 &&
+ (if_getcapabilities(ifp) & IFCAP_TXCSUM) != 0) {
+ if_togglecapenable(ifp, IFCAP_TXCSUM);
+ if ((if_getcapenable(ifp) & IFCAP_TXCSUM) != 0)
+ if_sethwassistbits(ifp, AXE_CSUM_FEATURES, 0);
+ else
+ if_sethwassistbits(ifp, 0, AXE_CSUM_FEATURES);
+ reinit++;
+ }
+ if ((mask & IFCAP_RXCSUM) != 0 &&
+ (if_getcapabilities(ifp) & IFCAP_RXCSUM) != 0) {
+ if_togglecapenable(ifp, IFCAP_RXCSUM);
+ reinit++;
+ }
+ if (reinit > 0 && if_getdrvflags(ifp) & IFF_DRV_RUNNING)
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+ else
+ reinit = 0;
+ AXE_UNLOCK(sc);
+ if (reinit > 0)
+ uether_init(ue);
+ } else
+ error = uether_ioctl(ifp, cmd, data);
+
+ return (error);
+}
diff --git a/sys/dev/usb/net/if_axereg.h b/sys/dev/usb/net/if_axereg.h
new file mode 100644
index 000000000000..4b47fd4b8a62
--- /dev/null
+++ b/sys/dev/usb/net/if_axereg.h
@@ -0,0 +1,363 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * 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.
+ */
+
+/*
+ * Definitions for the ASIX Electronics AX88172, AX88178
+ * and AX88772 to ethernet controllers.
+ */
+
+/*
+ * 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 bits, 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_IS_WRITE(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_182_CMD_WRITE_RXTX_SRAM 0x8103
+#define AXE_172_CMD_WRITE_TX_SRAM 0x0104
+#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_178_CMD_WRITE_IPG012 0x0112
+#define AXE_172_CMD_WRITE_IPG1 0x0113
+#define AXE_178_CMD_READ_NODEID 0x6013
+#define AXE_172_CMD_WRITE_IPG2 0x0114
+#define AXE_178_CMD_WRITE_NODEID 0x6114
+#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_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
+
+/* AX88772A and AX88772B only. */
+#define AXE_CMD_READ_VLAN_CTRL 0x4027
+#define AXE_CMD_WRITE_VLAN_CTRL 0x4028
+
+#define AXE_772B_CMD_RXCTL_WRITE_CFG 0x012A
+#define AXE_772B_CMD_READ_RXCSUM 0x002B
+#define AXE_772B_CMD_WRITE_RXCSUM 0x012C
+#define AXE_772B_CMD_READ_TXCSUM 0x002D
+#define AXE_772B_CMD_WRITE_TXCSUM 0x012E
+
+#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_RXCMD_ACCEPT_RUNT 0x0040 /* AX88772B */
+#define AXE_RXCMD_ENABLE 0x0080
+#define AXE_178_RXCMD_MFB_MASK 0x0300
+#define AXE_178_RXCMD_MFB_2048 0x0000
+#define AXE_178_RXCMD_MFB_4096 0x0100
+#define AXE_178_RXCMD_MFB_8192 0x0200
+#define AXE_178_RXCMD_MFB_16384 0x0300
+#define AXE_772B_RXCMD_HDR_TYPE_0 0x0000
+#define AXE_772B_RXCMD_HDR_TYPE_1 0x0100
+#define AXE_772B_RXCMD_IPHDR_ALIGN 0x0200
+#define AXE_772B_RXCMD_ADD_CHKSUM 0x0400
+#define AXE_RXCMD_LOOPBACK 0x1000 /* AX88772A/AX88772B */
+
+#define AXE_PHY_SEL_PRI 1
+#define AXE_PHY_SEL_SEC 0
+#define AXE_PHY_TYPE_MASK 0xE0
+#define AXE_PHY_TYPE_SHIFT 5
+#define AXE_PHY_TYPE(x) \
+ (((x) & AXE_PHY_TYPE_MASK) >> AXE_PHY_TYPE_SHIFT)
+
+#define PHY_TYPE_100_HOME 0 /* 10/100 or 1M HOME PHY */
+#define PHY_TYPE_GIG 1 /* Gigabit PHY */
+#define PHY_TYPE_SPECIAL 4 /* Special case */
+#define PHY_TYPE_RSVD 5 /* Reserved */
+#define PHY_TYPE_NON_SUP 7 /* Non-supported PHY */
+
+#define AXE_PHY_NO_MASK 0x1F
+#define AXE_PHY_NO(x) ((x) & AXE_PHY_NO_MASK)
+
+#define AXE_772_PHY_NO_EPHY 0x10 /* Embedded 10/100 PHY of AX88772 */
+
+#define AXE_GPIO0_EN 0x01
+#define AXE_GPIO0 0x02
+#define AXE_GPIO1_EN 0x04
+#define AXE_GPIO1 0x08
+#define AXE_GPIO2_EN 0x10
+#define AXE_GPIO2 0x20
+#define AXE_GPIO_RELOAD_EEPROM 0x80
+
+#define AXE_PHY_MODE_MARVELL 0x00
+#define AXE_PHY_MODE_CICADA 0x01
+#define AXE_PHY_MODE_AGERE 0x02
+#define AXE_PHY_MODE_CICADA_V2 0x05
+#define AXE_PHY_MODE_AGERE_GMII 0x06
+#define AXE_PHY_MODE_CICADA_V2_ASIX 0x09
+#define AXE_PHY_MODE_REALTEK_8211CL 0x0C
+#define AXE_PHY_MODE_REALTEK_8211BN 0x0D
+#define AXE_PHY_MODE_REALTEK_8251CL 0x0E
+#define AXE_PHY_MODE_ATTANSIC 0x40
+
+/* AX88772A/AX88772B only. */
+#define AXE_SW_PHY_SELECT_EXT 0x0000
+#define AXE_SW_PHY_SELECT_EMBEDDED 0x0001
+#define AXE_SW_PHY_SELECT_AUTO 0x0002
+#define AXE_SW_PHY_SELECT_SS_MII 0x0004
+#define AXE_SW_PHY_SELECT_SS_RVRS_MII 0x0008
+#define AXE_SW_PHY_SELECT_SS_RVRS_RMII 0x000C
+#define AXE_SW_PHY_SELECT_SS_ENB 0x0010
+
+/* AX88772A/AX88772B VLAN control. */
+#define AXE_VLAN_CTRL_ENB 0x00001000
+#define AXE_VLAN_CTRL_STRIP 0x00002000
+#define AXE_VLAN_CTRL_VID1_MASK 0x00000FFF
+#define AXE_VLAN_CTRL_VID2_MASK 0x0FFF0000
+
+#define AXE_RXCSUM_IP 0x0001
+#define AXE_RXCSUM_IPVE 0x0002
+#define AXE_RXCSUM_IPV6E 0x0004
+#define AXE_RXCSUM_TCP 0x0008
+#define AXE_RXCSUM_UDP 0x0010
+#define AXE_RXCSUM_ICMP 0x0020
+#define AXE_RXCSUM_IGMP 0x0040
+#define AXE_RXCSUM_ICMP6 0x0080
+#define AXE_RXCSUM_TCPV6 0x0100
+#define AXE_RXCSUM_UDPV6 0x0200
+#define AXE_RXCSUM_ICMPV6 0x0400
+#define AXE_RXCSUM_IGMPV6 0x0800
+#define AXE_RXCSUM_ICMP6V6 0x1000
+#define AXE_RXCSUM_FOPC 0x8000
+
+#define AXE_RXCSUM_64TE 0x0100
+#define AXE_RXCSUM_PPPOE 0x0200
+#define AXE_RXCSUM_RPCE 0x8000
+
+#define AXE_TXCSUM_IP 0x0001
+#define AXE_TXCSUM_TCP 0x0002
+#define AXE_TXCSUM_UDP 0x0004
+#define AXE_TXCSUM_ICMP 0x0008
+#define AXE_TXCSUM_IGMP 0x0010
+#define AXE_TXCSUM_ICMP6 0x0020
+#define AXE_TXCSUM_TCPV6 0x0100
+#define AXE_TXCSUM_UDPV6 0x0200
+#define AXE_TXCSUM_ICMPV6 0x0400
+#define AXE_TXCSUM_IGMPV6 0x0800
+#define AXE_TXCSUM_ICMP6V6 0x1000
+
+#define AXE_TXCSUM_64TE 0x0001
+#define AXE_TXCSUM_PPPOE 0x0002
+
+#define AXE_BULK_BUF_SIZE 16384 /* bytes */
+
+#define AXE_CTL_READ 0x01
+#define AXE_CTL_WRITE 0x02
+
+#define AXE_CONFIG_IDX 0 /* config number 1 */
+#define AXE_IFACE_IDX 0
+
+/* EEPROM Map. */
+#define AXE_EEPROM_772B_NODE_ID 0x04
+#define AXE_EEPROM_772B_PHY_PWRCFG 0x18
+
+struct ax88772b_mfb {
+ int byte_cnt;
+ int threshold;
+ int size;
+};
+#define AX88772B_MFB_2K 0
+#define AX88772B_MFB_4K 1
+#define AX88772B_MFB_6K 2
+#define AX88772B_MFB_8K 3
+#define AX88772B_MFB_16K 4
+#define AX88772B_MFB_20K 5
+#define AX88772B_MFB_24K 6
+#define AX88772B_MFB_32K 7
+
+struct axe_sframe_hdr {
+ uint16_t len;
+#define AXE_HDR_LEN_MASK 0xFFFF
+ uint16_t ilen;
+} __packed;
+
+#define AXE_TX_CSUM_PSEUDO_HDR 0x4000
+#define AXE_TX_CSUM_DIS 0x8000
+
+/*
+ * When RX checksum offloading is enabled, AX88772B uses new RX header
+ * format and it's not compatible with previous RX header format. In
+ * addition, IP header align option should be enabled to get correct
+ * frame size including RX header. Total transferred size including
+ * the RX header is multiple of 4 and controller will pad necessary
+ * bytes if the length is not multiple of 4.
+ * This driver does not enable partial checksum feature which will
+ * compute 16bit checksum from 14th byte to the end of the frame. If
+ * this feature is enabled, computed checksum value is embedded into
+ * RX header which in turn means it uses different RX header format.
+ */
+struct axe_csum_hdr {
+ uint16_t len;
+#define AXE_CSUM_HDR_LEN_MASK 0x07FF
+#define AXE_CSUM_HDR_CRC_ERR 0x1000
+#define AXE_CSUM_HDR_MII_ERR 0x2000
+#define AXE_CSUM_HDR_RUNT 0x4000
+#define AXE_CSUM_HDR_BMCAST 0x8000
+ uint16_t ilen;
+ uint16_t cstatus;
+#define AXE_CSUM_HDR_VLAN_MASK 0x0007
+#define AXE_CSUM_HDR_VLAN_STRIP 0x0008
+#define AXE_CSUM_HDR_VLAN_PRI_MASK 0x0070
+#define AXE_CSUM_HDR_L4_CSUM_ERR 0x0100
+#define AXE_CSUM_HDR_L3_CSUM_ERR 0x0200
+#define AXE_CSUM_HDR_L4_TYPE_UDP 0x0400
+#define AXE_CSUM_HDR_L4_TYPE_ICMP 0x0800
+#define AXE_CSUM_HDR_L4_TYPE_IGMP 0x0C00
+#define AXE_CSUM_HDR_L4_TYPE_TCP 0x1000
+#define AXE_CSUM_HDR_L4_TYPE_TCPV6 0x1400
+#define AXE_CSUM_HDR_L4_TYPE_MASK 0x1C00
+#define AXE_CSUM_HDR_L3_TYPE_IPV4 0x2000
+#define AXE_CSUM_HDR_L3_TYPE_IPV6 0x4000
+
+#ifdef AXE_APPEND_PARTIAL_CSUM
+ /*
+ * These members present only when partial checksum
+ * offloading is enabled. The checksum value is simple
+ * 16bit sum of received frame starting at offset 14 of
+ * the frame to the end of the frame excluding FCS bytes.
+ */
+ uint16_t csum_value;
+ uint16_t dummy;
+#endif
+} __packed;
+
+#define AXE_CSUM_RXBYTES(x) ((x) & AXE_CSUM_HDR_LEN_MASK)
+
+#define GET_MII(sc) uether_getmii(&(sc)->sc_ue)
+
+/* The interrupt endpoint is currently unused by the ASIX part. */
+enum {
+ AXE_BULK_DT_WR,
+ AXE_BULK_DT_RD,
+ AXE_N_TRANSFER,
+};
+
+struct axe_softc {
+ struct usb_ether sc_ue;
+ struct mtx sc_mtx;
+ struct usb_xfer *sc_xfer[AXE_N_TRANSFER];
+ int sc_phyno;
+
+ int sc_flags;
+#define AXE_FLAG_LINK 0x0001
+#define AXE_FLAG_STD_FRAME 0x0010
+#define AXE_FLAG_CSUM_FRAME 0x0020
+#define AXE_FLAG_772 0x1000 /* AX88772 */
+#define AXE_FLAG_772A 0x2000 /* AX88772A */
+#define AXE_FLAG_772B 0x4000 /* AX88772B */
+#define AXE_FLAG_178 0x8000 /* AX88178 */
+
+ uint8_t sc_ipgs[3];
+ uint8_t sc_phyaddrs[2];
+ uint16_t sc_pwrcfg;
+ uint16_t sc_lenmask;
+};
+
+#define AXE_IS_178_FAMILY(sc) \
+ ((sc)->sc_flags & (AXE_FLAG_772 | AXE_FLAG_772A | AXE_FLAG_772B | \
+ AXE_FLAG_178))
+
+#define AXE_IS_772(sc) \
+ ((sc)->sc_flags & (AXE_FLAG_772 | AXE_FLAG_772A | AXE_FLAG_772B))
+
+#define AXE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define AXE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define AXE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t)
diff --git a/sys/dev/usb/net/if_axge.c b/sys/dev/usb/net/if_axge.c
new file mode 100644
index 000000000000..cb8f0fafff45
--- /dev/null
+++ b/sys/dev/usb/net/if_axge.c
@@ -0,0 +1,1100 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013-2014 Kevin Lo
+ * 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.
+ */
+
+/*
+ * ASIX Electronics AX88178A/AX88179/AX88179A USB 2.0/3.0 gigabit ethernet
+ * driver.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/condvar.h>
+#include <sys/endian.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/unistd.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_media.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR axge_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/net/usb_ethernet.h>
+#include <dev/usb/net/if_axgereg.h>
+
+#include "miibus_if.h"
+
+/*
+ * Various supported device vendors/products.
+ */
+
+static const STRUCT_USB_HOST_ID axge_devs[] = {
+#define AXGE_DEV(v,p,i,...) \
+ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i), __VA_ARGS__ }
+ AXGE_DEV(ASIX, AX88178A, AXGE_FLAG_178A),
+ AXGE_DEV(ASIX, AX88179, AXGE_FLAG_179, USB_DEV_BCD_LTEQ(0x0100)),
+ AXGE_DEV(ASIX, AX88179, AXGE_FLAG_179A, USB_DEV_BCD_GTEQ(0x0200)),
+ AXGE_DEV(BELKIN, B2B128, AXGE_FLAG_179),
+ AXGE_DEV(DLINK, DUB1312, AXGE_FLAG_179),
+ AXGE_DEV(LENOVO, GIGALAN, AXGE_FLAG_179),
+ AXGE_DEV(SITECOMEU, LN032, AXGE_FLAG_179),
+#undef AXGE_DEV
+};
+
+static const struct {
+ uint8_t ctrl;
+ uint8_t timer_l;
+ uint8_t timer_h;
+ uint8_t size;
+ uint8_t ifg;
+} __packed axge_bulk_size[] = {
+ { 7, 0x4f, 0x00, 0x12, 0xff },
+ { 7, 0x20, 0x03, 0x16, 0xff },
+ { 7, 0xae, 0x07, 0x18, 0xff },
+ { 7, 0xcc, 0x4c, 0x18, 0x08 }
+};
+
+/* prototypes */
+
+static device_probe_t axge_probe;
+static device_attach_t axge_attach;
+static device_detach_t axge_detach;
+
+static usb_callback_t axge_bulk_read_callback;
+static usb_callback_t axge_bulk_write_callback;
+
+static miibus_readreg_t axge_miibus_readreg;
+static miibus_writereg_t axge_miibus_writereg;
+static miibus_statchg_t axge_miibus_statchg;
+
+static uether_fn_t axge_attach_post;
+static uether_fn_t axge_init;
+static uether_fn_t axge_stop;
+static uether_fn_t axge_start;
+static uether_fn_t axge_tick;
+static uether_fn_t axge_rxfilter;
+
+static int axge_read_mem(struct axge_softc *, uint8_t, uint16_t,
+ uint16_t, void *, int);
+static void axge_write_mem(struct axge_softc *, uint8_t, uint16_t,
+ uint16_t, void *, int);
+static uint8_t axge_read_cmd_1(struct axge_softc *, uint8_t, uint16_t);
+static uint16_t axge_read_cmd_2(struct axge_softc *, uint8_t, uint16_t,
+ uint16_t);
+static void axge_write_cmd_1(struct axge_softc *, uint8_t, uint16_t,
+ uint8_t);
+static void axge_write_cmd_2(struct axge_softc *, uint8_t, uint16_t,
+ uint16_t, uint16_t);
+static void axge_chip_init(struct axge_softc *);
+static void axge_reset(struct axge_softc *);
+
+static int axge_attach_post_sub(struct usb_ether *);
+static int axge_ifmedia_upd(if_t);
+static void axge_ifmedia_sts(if_t, struct ifmediareq *);
+static int axge_ioctl(if_t, u_long, caddr_t);
+static void axge_rx_frame(struct usb_ether *, struct usb_page_cache *, int);
+static void axge_rxeof(struct usb_ether *, struct usb_page_cache *,
+ unsigned, unsigned, uint32_t);
+static void axge_csum_cfg(struct usb_ether *);
+
+#define AXGE_CSUM_FEATURES (CSUM_IP | CSUM_TCP | CSUM_UDP)
+
+#ifdef USB_DEBUG
+static int axge_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, axge, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB axge");
+SYSCTL_INT(_hw_usb_axge, OID_AUTO, debug, CTLFLAG_RWTUN, &axge_debug, 0,
+ "Debug level");
+#endif
+
+static const struct usb_config axge_config[AXGE_N_TRANSFER] = {
+ [AXGE_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .frames = AXGE_N_FRAMES,
+ .bufsize = AXGE_N_FRAMES * MCLBYTES,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = axge_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ },
+ [AXGE_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = 65536,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = axge_bulk_read_callback,
+ .timeout = 0, /* no timeout */
+ },
+};
+
+static device_method_t axge_methods[] = {
+ /* Device interface. */
+ DEVMETHOD(device_probe, axge_probe),
+ DEVMETHOD(device_attach, axge_attach),
+ DEVMETHOD(device_detach, axge_detach),
+
+ /* MII interface. */
+ DEVMETHOD(miibus_readreg, axge_miibus_readreg),
+ DEVMETHOD(miibus_writereg, axge_miibus_writereg),
+ DEVMETHOD(miibus_statchg, axge_miibus_statchg),
+
+ DEVMETHOD_END
+};
+
+static driver_t axge_driver = {
+ .name = "axge",
+ .methods = axge_methods,
+ .size = sizeof(struct axge_softc),
+};
+
+DRIVER_MODULE(axge, uhub, axge_driver, NULL, NULL);
+DRIVER_MODULE(miibus, axge, miibus_driver, NULL, NULL);
+MODULE_DEPEND(axge, uether, 1, 1, 1);
+MODULE_DEPEND(axge, usb, 1, 1, 1);
+MODULE_DEPEND(axge, ether, 1, 1, 1);
+MODULE_DEPEND(axge, miibus, 1, 1, 1);
+MODULE_VERSION(axge, 1);
+USB_PNP_HOST_INFO(axge_devs);
+
+static const struct usb_ether_methods axge_ue_methods = {
+ .ue_attach_post = axge_attach_post,
+ .ue_attach_post_sub = axge_attach_post_sub,
+ .ue_start = axge_start,
+ .ue_init = axge_init,
+ .ue_stop = axge_stop,
+ .ue_tick = axge_tick,
+ .ue_setmulti = axge_rxfilter,
+ .ue_setpromisc = axge_rxfilter,
+ .ue_mii_upd = axge_ifmedia_upd,
+ .ue_mii_sts = axge_ifmedia_sts,
+};
+
+static int
+axge_read_mem(struct axge_softc *sc, uint8_t cmd, uint16_t index,
+ uint16_t val, void *buf, int len)
+{
+ struct usb_device_request req;
+
+ AXGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = cmd;
+ USETW(req.wValue, val);
+ USETW(req.wIndex, index);
+ USETW(req.wLength, len);
+
+ return (uether_do_request(&sc->sc_ue, &req, buf, 1000));
+}
+
+static void
+axge_write_mem(struct axge_softc *sc, uint8_t cmd, uint16_t index,
+ uint16_t val, void *buf, int len)
+{
+ struct usb_device_request req;
+
+ AXGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = cmd;
+ USETW(req.wValue, val);
+ USETW(req.wIndex, index);
+ USETW(req.wLength, len);
+
+ if (uether_do_request(&sc->sc_ue, &req, buf, 1000)) {
+ /* Error ignored. */
+ }
+}
+
+static uint8_t
+axge_read_cmd_1(struct axge_softc *sc, uint8_t cmd, uint16_t reg)
+{
+ uint8_t val;
+
+ axge_read_mem(sc, cmd, 1, reg, &val, 1);
+ return (val);
+}
+
+static uint16_t
+axge_read_cmd_2(struct axge_softc *sc, uint8_t cmd, uint16_t index,
+ uint16_t reg)
+{
+ uint8_t val[2];
+
+ axge_read_mem(sc, cmd, index, reg, &val, 2);
+ return (UGETW(val));
+}
+
+static void
+axge_write_cmd_1(struct axge_softc *sc, uint8_t cmd, uint16_t reg, uint8_t val)
+{
+ axge_write_mem(sc, cmd, 1, reg, &val, 1);
+}
+
+static void
+axge_write_cmd_2(struct axge_softc *sc, uint8_t cmd, uint16_t index,
+ uint16_t reg, uint16_t val)
+{
+ uint8_t temp[2];
+
+ USETW(temp, val);
+ axge_write_mem(sc, cmd, index, reg, &temp, 2);
+}
+
+static int
+axge_miibus_readreg(device_t dev, int phy, int reg)
+{
+ struct axge_softc *sc;
+ uint16_t val;
+ int locked;
+
+ sc = device_get_softc(dev);
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ AXGE_LOCK(sc);
+
+ val = axge_read_cmd_2(sc, AXGE_ACCESS_PHY, reg, phy);
+
+ if (!locked)
+ AXGE_UNLOCK(sc);
+
+ return (val);
+}
+
+static int
+axge_miibus_writereg(device_t dev, int phy, int reg, int val)
+{
+ struct axge_softc *sc;
+ int locked;
+
+ sc = device_get_softc(dev);
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ AXGE_LOCK(sc);
+
+ axge_write_cmd_2(sc, AXGE_ACCESS_PHY, reg, phy, val);
+
+ if (!locked)
+ AXGE_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+axge_miibus_statchg(device_t dev)
+{
+ struct axge_softc *sc;
+ struct mii_data *mii;
+ if_t ifp;
+ uint8_t link_status, tmp[5];
+ uint16_t val;
+ int locked;
+
+ sc = device_get_softc(dev);
+ mii = GET_MII(sc);
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ AXGE_LOCK(sc);
+
+ ifp = uether_getifp(&sc->sc_ue);
+ if (mii == NULL || ifp == NULL ||
+ (if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0)
+ goto done;
+
+ sc->sc_flags &= ~AXGE_FLAG_LINK;
+ if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) ==
+ (IFM_ACTIVE | IFM_AVALID)) {
+ switch (IFM_SUBTYPE(mii->mii_media_active)) {
+ case IFM_10_T:
+ case IFM_100_TX:
+ case IFM_1000_T:
+ sc->sc_flags |= AXGE_FLAG_LINK;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* Lost link, do nothing. */
+ if ((sc->sc_flags & AXGE_FLAG_LINK) == 0)
+ goto done;
+
+ link_status = axge_read_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_PLSR);
+
+ val = 0;
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) {
+ val |= MSR_FD;
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0)
+ val |= MSR_TFC;
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0)
+ val |= MSR_RFC;
+ }
+ val |= MSR_RE;
+ switch (IFM_SUBTYPE(mii->mii_media_active)) {
+ case IFM_1000_T:
+ val |= MSR_GM | MSR_EN_125MHZ;
+ if (link_status & PLSR_USB_SS)
+ memcpy(tmp, &axge_bulk_size[0], 5);
+ else if (link_status & PLSR_USB_HS)
+ memcpy(tmp, &axge_bulk_size[1], 5);
+ else
+ memcpy(tmp, &axge_bulk_size[3], 5);
+ break;
+ case IFM_100_TX:
+ val |= MSR_PS;
+ if (link_status & (PLSR_USB_SS | PLSR_USB_HS))
+ memcpy(tmp, &axge_bulk_size[2], 5);
+ else
+ memcpy(tmp, &axge_bulk_size[3], 5);
+ break;
+ case IFM_10_T:
+ memcpy(tmp, &axge_bulk_size[3], 5);
+ break;
+ }
+ /* Rx bulk configuration. */
+ axge_write_mem(sc, AXGE_ACCESS_MAC, 5, AXGE_RX_BULKIN_QCTRL, tmp, 5);
+ axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_MSR, val);
+done:
+ if (!locked)
+ AXGE_UNLOCK(sc);
+}
+
+static void
+axge_chip_init(struct axge_softc *sc)
+{
+ /* Power up ethernet PHY. */
+ axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_EPPRCR, 0);
+ axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_EPPRCR, EPPRCR_IPRL);
+ uether_pause(&sc->sc_ue, hz / 4);
+ axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_CLK_SELECT,
+ AXGE_CLK_SELECT_ACS | AXGE_CLK_SELECT_BCS);
+ uether_pause(&sc->sc_ue, hz / 10);
+
+ if ((sc->sc_flags & AXGE_FLAG_179A) != 0) {
+ /*
+ * 179A chip has two firmware modes that each use different
+ * transfer layouts for Ethernet over USB. The newer fw mode has
+ * larger rx packet headers which seem to
+ * accomodate for ethernet frames up to 9K length and a VLAN
+ * field for hardware tagging, but is not backward compatible
+ * with 178A/179 bulk transfer code due to the change in size
+ * and field alignments. The other fw mode uses the same packet
+ * headers as the older 178A/179 chips, which this driver uses.
+ *
+ * As we do not currently have VLAN hw tagging or jumbo support
+ * in this driver anyway, we're ok forcing 179A into its compat
+ * mode by default.
+ */
+ axge_write_cmd_1(sc, AXGE_FW_MODE, AXGE_FW_MODE_178A179, 0);
+ }
+}
+
+static void
+axge_reset(struct axge_softc *sc)
+{
+ struct usb_config_descriptor *cd;
+ usb_error_t err;
+
+ cd = usbd_get_config_descriptor(sc->sc_ue.ue_udev);
+
+ err = usbd_req_set_config(sc->sc_ue.ue_udev, &sc->sc_mtx,
+ cd->bConfigurationValue);
+ if (err)
+ DPRINTF("reset failed (ignored)\n");
+
+ /* Wait a little while for the chip to get its brains in order. */
+ uether_pause(&sc->sc_ue, hz / 100);
+
+ /* Reinitialize controller to achieve full reset. */
+ axge_chip_init(sc);
+}
+
+static void
+axge_attach_post(struct usb_ether *ue)
+{
+ struct axge_softc *sc;
+
+ sc = uether_getsc(ue);
+
+ /* Initialize controller and get station address. */
+ axge_chip_init(sc);
+ axge_read_mem(sc, AXGE_ACCESS_MAC, ETHER_ADDR_LEN, AXGE_NIDR,
+ ue->ue_eaddr, ETHER_ADDR_LEN);
+}
+
+static int
+axge_attach_post_sub(struct usb_ether *ue)
+{
+ if_t ifp;
+ int error;
+
+ ifp = ue->ue_ifp;
+ if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
+ if_setstartfn(ifp, uether_start);
+ if_setioctlfn(ifp, axge_ioctl);
+ if_setinitfn(ifp, uether_init);
+ if_setsendqlen(ifp, ifqmaxlen);
+ if_setsendqready(ifp);
+
+ if_setcapabilitiesbit(ifp, IFCAP_VLAN_MTU | IFCAP_TXCSUM | IFCAP_RXCSUM, 0);
+ if_sethwassist(ifp, AXGE_CSUM_FEATURES);
+ if_setcapenable(ifp, if_getcapabilities(ifp));
+
+ bus_topo_lock();
+ error = mii_attach(ue->ue_dev, &ue->ue_miibus, ifp,
+ uether_ifmedia_upd, ue->ue_methods->ue_mii_sts,
+ BMSR_DEFCAPMASK, AXGE_PHY_ADDR, MII_OFFSET_ANY, MIIF_DOPAUSE);
+ bus_topo_unlock();
+
+ return (error);
+}
+
+/*
+ * Set media options.
+ */
+static int
+axge_ifmedia_upd(if_t ifp)
+{
+ struct axge_softc *sc;
+ struct mii_data *mii;
+ struct mii_softc *miisc;
+ int error;
+
+ sc = if_getsoftc(ifp);
+ mii = GET_MII(sc);
+ AXGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list)
+ PHY_RESET(miisc);
+ error = mii_mediachg(mii);
+
+ return (error);
+}
+
+/*
+ * Report current media status.
+ */
+static void
+axge_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct axge_softc *sc;
+ struct mii_data *mii;
+
+ sc = if_getsoftc(ifp);
+ mii = GET_MII(sc);
+ AXGE_LOCK(sc);
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+ AXGE_UNLOCK(sc);
+}
+
+/*
+ * Probe for a AX88179 chip.
+ */
+static int
+axge_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa;
+
+ uaa = device_get_ivars(dev);
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != AXGE_CONFIG_IDX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != AXGE_IFACE_IDX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(axge_devs, sizeof(axge_devs), uaa));
+}
+
+/*
+ * Attach the interface. Allocate softc structures, do ifmedia
+ * setup and ethernet/BPF attach.
+ */
+static int
+axge_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa;
+ struct axge_softc *sc;
+ struct usb_ether *ue;
+ uint8_t iface_index;
+ int error;
+
+ uaa = device_get_ivars(dev);
+ sc = device_get_softc(dev);
+ ue = &sc->sc_ue;
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ sc->sc_flags = USB_GET_DRIVER_INFO(uaa);
+
+ iface_index = AXGE_IFACE_IDX;
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, axge_config, AXGE_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "allocating USB transfers failed\n");
+ mtx_destroy(&sc->sc_mtx);
+ return (ENXIO);
+ }
+
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &axge_ue_methods;
+
+ error = uether_ifattach(ue);
+ if (error) {
+ device_printf(dev, "could not attach interface\n");
+ goto detach;
+ }
+ return (0); /* success */
+
+detach:
+ axge_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+axge_detach(device_t dev)
+{
+ struct axge_softc *sc;
+ struct usb_ether *ue;
+ uint16_t val;
+
+ sc = device_get_softc(dev);
+ ue = &sc->sc_ue;
+ if (device_is_attached(dev)) {
+ /* wait for any post attach or other command to complete */
+ usb_proc_drain(&ue->ue_tq);
+
+ AXGE_LOCK(sc);
+ /*
+ * XXX
+ * ether_ifdetach(9) should be called first.
+ */
+ axge_stop(ue);
+ /* Force bulk-in to return a zero-length USB packet. */
+ val = axge_read_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_EPPRCR);
+ val |= EPPRCR_BZ | EPPRCR_IPRL;
+ axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_EPPRCR, val);
+ /* Change clock. */
+ axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_CLK_SELECT, 0);
+ /* Disable MAC. */
+ axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_RCR, 0);
+ AXGE_UNLOCK(sc);
+ }
+ usbd_transfer_unsetup(sc->sc_xfer, AXGE_N_TRANSFER);
+ uether_ifdetach(ue);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+axge_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct axge_softc *sc;
+ struct usb_ether *ue;
+ struct usb_page_cache *pc;
+ int actlen;
+
+ sc = usbd_xfer_softc(xfer);
+ ue = &sc->sc_ue;
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ axge_rx_frame(ue, pc, actlen);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ uether_rxflush(ue);
+ break;
+
+ default:
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+axge_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct axge_softc *sc;
+ if_t ifp;
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ struct axge_frame_txhdr txhdr;
+ int nframes, pos;
+
+ sc = usbd_xfer_softc(xfer);
+ ifp = uether_getifp(&sc->sc_ue);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ if ((sc->sc_flags & AXGE_FLAG_LINK) == 0 ||
+ (if_getdrvflags(ifp) & IFF_DRV_OACTIVE) != 0) {
+ /*
+ * Don't send anything if there is no link or
+ * controller is busy.
+ */
+ return;
+ }
+
+ for (nframes = 0; nframes < AXGE_N_FRAMES &&
+ !if_sendq_empty(ifp); nframes++) {
+ m = if_dequeue(ifp);
+ if (m == NULL)
+ break;
+ usbd_xfer_set_frame_offset(xfer, nframes * MCLBYTES,
+ nframes);
+ pc = usbd_xfer_get_frame(xfer, nframes);
+ txhdr.mss = 0;
+ txhdr.len = htole32(AXGE_TXBYTES(m->m_pkthdr.len));
+ if ((if_getcapenable(ifp) & IFCAP_TXCSUM) != 0 &&
+ (m->m_pkthdr.csum_flags & AXGE_CSUM_FEATURES) == 0)
+ txhdr.len |= htole32(AXGE_CSUM_DISABLE);
+
+ pos = 0;
+ usbd_copy_in(pc, pos, &txhdr, sizeof(txhdr));
+ pos += sizeof(txhdr);
+ usbd_m_copy_in(pc, pos, m, 0, m->m_pkthdr.len);
+ pos += m->m_pkthdr.len;
+
+ /*
+ * if there's a BPF listener, bounce a copy
+ * of this frame to him:
+ */
+ BPF_MTAP(ifp, m);
+
+ m_freem(m);
+
+ /* Set frame length. */
+ usbd_xfer_set_frame_len(xfer, nframes, pos);
+ }
+ if (nframes != 0) {
+ /*
+ * XXX
+ * Update TX packet counter here. This is not
+ * correct way but it seems that there is no way
+ * to know how many packets are sent at the end
+ * of transfer because controller combines
+ * multiple writes into single one if there is
+ * room in TX buffer of controller.
+ */
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, nframes);
+ usbd_xfer_set_frames(xfer, nframes);
+ usbd_transfer_submit(xfer);
+ if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0);
+ }
+ return;
+ /* NOTREACHED */
+ default:
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+axge_tick(struct usb_ether *ue)
+{
+ struct axge_softc *sc;
+ struct mii_data *mii;
+
+ sc = uether_getsc(ue);
+ mii = GET_MII(sc);
+ AXGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ mii_tick(mii);
+}
+
+static u_int
+axge_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
+{
+ uint8_t *hashtbl = arg;
+ uint32_t h;
+
+ h = ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 26;
+ hashtbl[h / 8] |= 1 << (h % 8);
+
+ return (1);
+}
+
+static void
+axge_rxfilter(struct usb_ether *ue)
+{
+ struct axge_softc *sc;
+ if_t ifp;
+ uint16_t rxmode;
+ uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ sc = uether_getsc(ue);
+ ifp = uether_getifp(ue);
+ AXGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ /*
+ * Configure RX settings.
+ * Don't set RCR_IPE(IP header alignment on 32bit boundary) to disable
+ * inserting extra padding bytes. This wastes ethernet to USB host
+ * bandwidth as well as complicating RX handling logic. Current USB
+ * framework requires copying RX frames to mbufs so there is no need
+ * to worry about alignment.
+ */
+ rxmode = RCR_DROP_CRCERR | RCR_START;
+ if (if_getflags(ifp) & IFF_BROADCAST)
+ rxmode |= RCR_ACPT_BCAST;
+ if (if_getflags(ifp) & (IFF_ALLMULTI | IFF_PROMISC)) {
+ if (if_getflags(ifp) & IFF_PROMISC)
+ rxmode |= RCR_PROMISC;
+ rxmode |= RCR_ACPT_ALL_MCAST;
+ axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_RCR, rxmode);
+ return;
+ }
+
+ rxmode |= RCR_ACPT_MCAST;
+ if_foreach_llmaddr(ifp, axge_hash_maddr, &hashtbl);
+
+ axge_write_mem(sc, AXGE_ACCESS_MAC, 8, AXGE_MFA, (void *)&hashtbl, 8);
+ axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_RCR, rxmode);
+}
+
+static void
+axge_start(struct usb_ether *ue)
+{
+ struct axge_softc *sc;
+
+ sc = uether_getsc(ue);
+ /*
+ * Start the USB transfers, if not already started.
+ */
+ usbd_transfer_start(sc->sc_xfer[AXGE_BULK_DT_RD]);
+ usbd_transfer_start(sc->sc_xfer[AXGE_BULK_DT_WR]);
+}
+
+static void
+axge_init(struct usb_ether *ue)
+{
+ struct axge_softc *sc;
+ if_t ifp;
+
+ sc = uether_getsc(ue);
+ ifp = uether_getifp(ue);
+ AXGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0)
+ return;
+
+ /*
+ * Cancel pending I/O and free all RX/TX buffers.
+ */
+ axge_stop(ue);
+
+ axge_reset(sc);
+
+ /* Set MAC address. */
+ axge_write_mem(sc, AXGE_ACCESS_MAC, ETHER_ADDR_LEN, AXGE_NIDR,
+ if_getlladdr(ifp), ETHER_ADDR_LEN);
+
+ axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_PWLLR, 0x34);
+ axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_PWLHR, 0x52);
+
+ /* Configure TX/RX checksum offloading. */
+ axge_csum_cfg(ue);
+
+ /* Configure RX filters. */
+ axge_rxfilter(ue);
+
+ /*
+ * XXX
+ * Controller supports wakeup on link change detection,
+ * magic packet and wakeup frame recpetion. But it seems
+ * there is no framework for USB ethernet suspend/wakeup.
+ * Disable all wakeup functions.
+ */
+ axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_MMSR, 0);
+ (void)axge_read_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_MMSR);
+
+ /* Configure default medium type. */
+ axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_MSR, MSR_GM | MSR_FD |
+ MSR_RFC | MSR_TFC | MSR_RE);
+
+ usbd_xfer_set_stall(sc->sc_xfer[AXGE_BULK_DT_WR]);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+ /* Switch to selected media. */
+ axge_ifmedia_upd(ifp);
+}
+
+static void
+axge_stop(struct usb_ether *ue)
+{
+ struct axge_softc *sc;
+ if_t ifp;
+ uint16_t val;
+
+ sc = uether_getsc(ue);
+ ifp = uether_getifp(ue);
+
+ AXGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ val = axge_read_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_MSR);
+ val &= ~MSR_RE;
+ axge_write_cmd_2(sc, AXGE_ACCESS_MAC, 2, AXGE_MSR, val);
+
+ if (ifp != NULL)
+ if_setdrvflagbits(ifp, 0, (IFF_DRV_RUNNING | IFF_DRV_OACTIVE));
+ sc->sc_flags &= ~AXGE_FLAG_LINK;
+
+ /*
+ * Stop all the transfers, if not already stopped:
+ */
+ usbd_transfer_stop(sc->sc_xfer[AXGE_BULK_DT_WR]);
+ usbd_transfer_stop(sc->sc_xfer[AXGE_BULK_DT_RD]);
+}
+
+static int
+axge_ioctl(if_t ifp, u_long cmd, caddr_t data)
+{
+ struct usb_ether *ue;
+ struct axge_softc *sc;
+ struct ifreq *ifr;
+ int error, mask, reinit;
+
+ ue = if_getsoftc(ifp);
+ sc = uether_getsc(ue);
+ ifr = (struct ifreq *)data;
+ error = 0;
+ reinit = 0;
+ if (cmd == SIOCSIFCAP) {
+ AXGE_LOCK(sc);
+ mask = ifr->ifr_reqcap ^ if_getcapenable(ifp);
+ if ((mask & IFCAP_TXCSUM) != 0 &&
+ (if_getcapabilities(ifp) & IFCAP_TXCSUM) != 0) {
+ if_togglecapenable(ifp, IFCAP_TXCSUM);
+ if ((if_getcapenable(ifp) & IFCAP_TXCSUM) != 0)
+ if_sethwassistbits(ifp, AXGE_CSUM_FEATURES, 0);
+ else
+ if_sethwassistbits(ifp, 0, AXGE_CSUM_FEATURES);
+ reinit++;
+ }
+ if ((mask & IFCAP_RXCSUM) != 0 &&
+ (if_getcapabilities(ifp) & IFCAP_RXCSUM) != 0) {
+ if_togglecapenable(ifp, IFCAP_RXCSUM);
+ reinit++;
+ }
+ if (reinit > 0 && if_getdrvflags(ifp) & IFF_DRV_RUNNING)
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+ else
+ reinit = 0;
+ AXGE_UNLOCK(sc);
+ if (reinit > 0)
+ uether_init(ue);
+ } else
+ error = uether_ioctl(ifp, cmd, data);
+
+ return (error);
+}
+
+static void
+axge_rx_frame(struct usb_ether *ue, struct usb_page_cache *pc, int actlen)
+{
+ struct axge_frame_rxhdr pkt_hdr;
+ uint32_t rxhdr;
+ uint32_t pos;
+ uint32_t pkt_cnt, pkt_end;
+ uint32_t hdr_off;
+ uint32_t pktlen;
+
+ /* verify we have enough data */
+ if (actlen < (int)sizeof(rxhdr))
+ return;
+
+ pos = 0;
+
+ usbd_copy_out(pc, actlen - sizeof(rxhdr), &rxhdr, sizeof(rxhdr));
+ rxhdr = le32toh(rxhdr);
+
+ pkt_cnt = rxhdr & 0xFFFF;
+ hdr_off = pkt_end = (rxhdr >> 16) & 0xFFFF;
+
+ /*
+ * On older firmware:
+ * <----------------------- actlen ------------------------>
+ * [frame #0]...[frame #N][pkt_hdr #0]...[pkt_hdr #N][rxhdr]
+ *
+ * On newer firmware:
+ * <----------------------- actlen -----------------
+ * [frame #0]...[frame #N][pkt_hdr #0][dummy_hdr]...
+ * -------------------------------->
+ * ...[pkt_hdr #N][dummy_hdr][rxhdr]
+ *
+ * Each RX frame would be aligned on 8 bytes boundary. If
+ * RCR_IPE bit is set in AXGE_RCR register, there would be 2
+ * padding bytes and 6 dummy bytes(as the padding also should
+ * be aligned on 8 bytes boundary) for each RX frame to align
+ * IP header on 32bits boundary. Driver don't set RCR_IPE bit
+ * of AXGE_RCR register, so there should be no padding bytes
+ * which simplifies RX logic a lot.
+ *
+ * Further, newer firmware interweaves dummy headers that have
+ * pktlen == 0 and should be skipped without being seen as
+ * dropped frames.
+ */
+ while (pkt_cnt--) {
+ /* verify the header offset */
+ if ((int)(hdr_off + sizeof(pkt_hdr)) > actlen) {
+ DPRINTF("End of packet headers\n");
+ break;
+ }
+ usbd_copy_out(pc, hdr_off, &pkt_hdr, sizeof(pkt_hdr));
+ pkt_hdr.status = le32toh(pkt_hdr.status);
+ pktlen = AXGE_RXBYTES(pkt_hdr.status);
+ hdr_off += sizeof(pkt_hdr);
+
+ /* Skip dummy packet header. */
+ if (pktlen == 0)
+ continue;
+
+ if (pos + pktlen > pkt_end) {
+ DPRINTF("Data position reached end\n");
+ break;
+ }
+
+ if (AXGE_RX_ERR(pkt_hdr.status) != 0) {
+ DPRINTF("Dropped a packet\n");
+ if_inc_counter(ue->ue_ifp, IFCOUNTER_IERRORS, 1);
+ } else
+ axge_rxeof(ue, pc, pos, pktlen, pkt_hdr.status);
+ pos += (pktlen + 7) & ~7;
+ }
+}
+
+static void
+axge_rxeof(struct usb_ether *ue, struct usb_page_cache *pc, unsigned offset,
+ unsigned len, uint32_t status)
+{
+ if_t ifp;
+ struct mbuf *m;
+
+ ifp = ue->ue_ifp;
+ if (len < ETHER_HDR_LEN || len > MCLBYTES - ETHER_ALIGN) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ return;
+ }
+
+ if (len > MHLEN - ETHER_ALIGN)
+ m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ else
+ m = m_gethdr(M_NOWAIT, MT_DATA);
+ if (m == NULL) {
+ if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
+ return;
+ }
+ m->m_pkthdr.rcvif = ifp;
+ m->m_len = m->m_pkthdr.len = len;
+ m->m_data += ETHER_ALIGN;
+
+ usbd_copy_out(pc, offset, mtod(m, uint8_t *), len);
+
+ if ((if_getcapenable(ifp) & IFCAP_RXCSUM) != 0) {
+ if ((status & AXGE_RX_L3_CSUM_ERR) == 0 &&
+ (status & AXGE_RX_L3_TYPE_MASK) == AXGE_RX_L3_TYPE_IPV4)
+ m->m_pkthdr.csum_flags |= CSUM_IP_CHECKED |
+ CSUM_IP_VALID;
+ if ((status & AXGE_RX_L4_CSUM_ERR) == 0 &&
+ ((status & AXGE_RX_L4_TYPE_MASK) == AXGE_RX_L4_TYPE_UDP ||
+ (status & AXGE_RX_L4_TYPE_MASK) == AXGE_RX_L4_TYPE_TCP)) {
+ m->m_pkthdr.csum_flags |= CSUM_DATA_VALID |
+ CSUM_PSEUDO_HDR;
+ m->m_pkthdr.csum_data = 0xffff;
+ }
+ }
+ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
+
+ (void)mbufq_enqueue(&ue->ue_rxq, m);
+}
+
+static void
+axge_csum_cfg(struct usb_ether *ue)
+{
+ struct axge_softc *sc;
+ if_t ifp;
+ uint8_t csum;
+
+ sc = uether_getsc(ue);
+ AXGE_LOCK_ASSERT(sc, MA_OWNED);
+ ifp = uether_getifp(ue);
+
+ csum = 0;
+ if ((if_getcapenable(ifp) & IFCAP_TXCSUM) != 0)
+ csum |= CTCR_IP | CTCR_TCP | CTCR_UDP;
+ axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_CTCR, csum);
+
+ csum = 0;
+ if ((if_getcapenable(ifp) & IFCAP_RXCSUM) != 0)
+ csum |= CRCR_IP | CRCR_TCP | CRCR_UDP;
+ axge_write_cmd_1(sc, AXGE_ACCESS_MAC, AXGE_CRCR, csum);
+}
diff --git a/sys/dev/usb/net/if_axgereg.h b/sys/dev/usb/net/if_axgereg.h
new file mode 100644
index 000000000000..87e662b6cbc1
--- /dev/null
+++ b/sys/dev/usb/net/if_axgereg.h
@@ -0,0 +1,216 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013-2014 Kevin Lo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#define AXGE_ACCESS_MAC 0x01
+#define AXGE_ACCESS_PHY 0x02
+#define AXGE_ACCESS_WAKEUP 0x03
+#define AXGE_ACCESS_EEPROM 0x04
+#define AXGE_ACCESS_EFUSE 0x05
+#define AXGE_RELOAD_EEPROM_EFUSE 0x06
+#define AXGE_FW_MODE 0x08
+#define AXGE_WRITE_EFUSE_EN 0x09
+#define AXGE_WRITE_EFUSE_DIS 0x0A
+#define AXGE_ACCESS_MFAB 0x10
+
+#define AXGE_FW_MODE_178A179 0x0000
+#define AXGE_FW_MODE_179A 0x0001
+
+/* Physical link status register */
+#define AXGE_PLSR 0x02
+#define PLSR_USB_FS 0x01
+#define PLSR_USB_HS 0x02
+#define PLSR_USB_SS 0x04
+
+/* EEPROM address register */
+#define AXGE_EAR 0x07
+
+/* EEPROM data low register */
+#define AXGE_EDLR 0x08
+
+/* EEPROM data high register */
+#define AXGE_EDHR 0x09
+
+/* EEPROM command register */
+#define AXGE_ECR 0x0a
+
+/* Rx control register */
+#define AXGE_RCR 0x0b
+#define RCR_STOP 0x0000
+#define RCR_PROMISC 0x0001
+#define RCR_ACPT_ALL_MCAST 0x0002
+#define RCR_AUTOPAD_BNDRY 0x0004
+#define RCR_ACPT_BCAST 0x0008
+#define RCR_ACPT_MCAST 0x0010
+#define RCR_ACPT_PHY_MCAST 0x0020
+#define RCR_START 0x0080
+#define RCR_DROP_CRCERR 0x0100
+#define RCR_IPE 0x0200
+#define RCR_TX_CRC_PAD 0x0400
+
+/* Node id register */
+#define AXGE_NIDR 0x10
+
+/* Multicast filter array */
+#define AXGE_MFA 0x16
+
+/* Medium status register */
+#define AXGE_MSR 0x22
+#define MSR_GM 0x0001
+#define MSR_FD 0x0002
+#define MSR_EN_125MHZ 0x0008
+#define MSR_RFC 0x0010
+#define MSR_TFC 0x0020
+#define MSR_RE 0x0100
+#define MSR_PS 0x0200
+
+/* Monitor mode status register */
+#define AXGE_MMSR 0x24
+#define MMSR_RWLC 0x02
+#define MMSR_RWMP 0x04
+#define MMSR_RWWF 0x08
+#define MMSR_RW_FLAG 0x10
+#define MMSR_PME_POL 0x20
+#define MMSR_PME_TYPE 0x40
+#define MMSR_PME_IND 0x80
+
+/* GPIO control/status register */
+#define AXGE_GPIOCR 0x25
+
+/* Ethernet PHY power & reset control register */
+#define AXGE_EPPRCR 0x26
+#define EPPRCR_BZ 0x0010
+#define EPPRCR_IPRL 0x0020
+#define EPPRCR_AUTODETACH 0x1000
+
+#define AXGE_RX_BULKIN_QCTRL 0x2e
+
+#define AXGE_CLK_SELECT 0x33
+#define AXGE_CLK_SELECT_BCS 0x01
+#define AXGE_CLK_SELECT_ACS 0x02
+#define AXGE_CLK_SELECT_ACSREQ 0x10
+#define AXGE_CLK_SELECT_ULR 0x08
+
+/* COE Rx control register */
+#define AXGE_CRCR 0x34
+#define CRCR_IP 0x01
+#define CRCR_TCP 0x02
+#define CRCR_UDP 0x04
+#define CRCR_ICMP 0x08
+#define CRCR_IGMP 0x10
+#define CRCR_TCPV6 0x20
+#define CRCR_UDPV6 0x40
+#define CRCR_ICMPV6 0x80
+
+/* COE Tx control register */
+#define AXGE_CTCR 0x35
+#define CTCR_IP 0x01
+#define CTCR_TCP 0x02
+#define CTCR_UDP 0x04
+#define CTCR_ICMP 0x08
+#define CTCR_IGMP 0x10
+#define CTCR_TCPV6 0x20
+#define CTCR_UDPV6 0x40
+#define CTCR_ICMPV6 0x80
+
+/* Pause water level high register */
+#define AXGE_PWLHR 0x54
+
+/* Pause water level low register */
+#define AXGE_PWLLR 0x55
+
+#define AXGE_CONFIG_IDX 0 /* config number 1 */
+#define AXGE_IFACE_IDX 0
+
+#define GET_MII(sc) uether_getmii(&(sc)->sc_ue)
+
+/* The interrupt endpoint is currently unused by the ASIX part. */
+enum {
+ AXGE_BULK_DT_WR,
+ AXGE_BULK_DT_RD,
+ AXGE_N_TRANSFER,
+};
+
+#define AXGE_N_FRAMES 16
+
+struct axge_frame_txhdr {
+ uint32_t len;
+#define AXGE_TXLEN_MASK 0x0001FFFF
+#define AXGE_VLAN_INSERT 0x20000000
+#define AXGE_CSUM_DISABLE 0x80000000
+ uint32_t mss;
+#define AXGE_MSS_MASK 0x00003FFF
+#define AXGE_PADDING 0x80008000
+#define AXGE_VLAN_TAG_MASK 0xFFFF0000
+} __packed;
+
+#define AXGE_TXBYTES(x) ((x) & AXGE_TXLEN_MASK)
+
+#define AXGE_PHY_ADDR 3
+
+struct axge_frame_rxhdr {
+ uint32_t status;
+#define AXGE_RX_L4_CSUM_ERR 0x00000001
+#define AXGE_RX_L3_CSUM_ERR 0x00000002
+#define AXGE_RX_L4_TYPE_UDP 0x00000004
+#define AXGE_RX_L4_TYPE_ICMP 0x00000008
+#define AXGE_RX_L4_TYPE_IGMP 0x0000000C
+#define AXGE_RX_L4_TYPE_TCP 0x00000010
+#define AXGE_RX_L4_TYPE_MASK 0x0000001C
+#define AXGE_RX_L3_TYPE_IPV4 0x00000020
+#define AXGE_RX_L3_TYPE_IPV6 0x00000040
+#define AXGE_RX_L3_TYPE_MASK 0x00000060
+#define AXGE_RX_VLAN_IND_MASK 0x00000700
+#define AXGE_RX_GOOD_PKT 0x00000800
+#define AXGE_RX_VLAN_PRI_MASK 0x00007000
+#define AXGE_RX_MBCAST 0x00008000
+#define AXGE_RX_LEN_MASK 0x1FFF0000
+#define AXGE_RX_CRC_ERR 0x20000000
+#define AXGE_RX_MII_ERR 0x40000000
+#define AXGE_RX_DROP_PKT 0x80000000
+#define AXGE_RX_LEN_SHIFT 16
+} __packed;
+
+#define AXGE_RXBYTES(x) (((x) & AXGE_RX_LEN_MASK) >> AXGE_RX_LEN_SHIFT)
+#define AXGE_RX_ERR(x) \
+ ((x) & (AXGE_RX_CRC_ERR | AXGE_RX_MII_ERR | AXGE_RX_DROP_PKT))
+
+struct axge_softc {
+ struct usb_ether sc_ue;
+ struct mtx sc_mtx;
+ struct usb_xfer *sc_xfer[AXGE_N_TRANSFER];
+
+ int sc_flags;
+#define AXGE_FLAG_LINK 0x0001 /* got a link */
+#define AXGE_FLAG_178A 0x1000 /* AX88178A */
+#define AXGE_FLAG_179 0x2000 /* AX88179 */
+#define AXGE_FLAG_179A 0x4000 /* AX88179A */
+};
+
+#define AXGE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define AXGE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define AXGE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t)
diff --git a/sys/dev/usb/net/if_cdce.c b/sys/dev/usb/net/if_cdce.c
new file mode 100644
index 000000000000..25697c8ec4c9
--- /dev/null
+++ b/sys/dev/usb/net/if_cdce.c
@@ -0,0 +1,1745 @@
+/* $NetBSD: if_cdce.c,v 1.4 2004/10/24 12:50:54 augustss Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 1997, 1998, 1999, 2000-2003 Bill Paul <wpaul@windriver.com>
+ * Copyright (c) 2003-2005 Craig Boston
+ * Copyright (c) 2004 Daniel Hartmeier
+ * Copyright (c) 2009 Hans Petter Selasky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Bill Paul.
+ * 4. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul, THE VOICES IN HIS HEAD OR
+ * THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * USB Communication Device Class (Ethernet Networking Control Model)
+ * http://www.usb.org/developers/devclass_docs/usbcdc11.pdf
+ */
+
+/*
+ * USB Network Control Model (NCM)
+ * http://www.usb.org/developers/devclass_docs/NCM10.zip
+ */
+
+#include <sys/gsb_crc32.h>
+#include <sys/eventhandler.h>
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_cdc.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR cdce_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_msctest.h>
+#include "usb_if.h"
+
+#include <dev/usb/net/usb_ethernet.h>
+#include <dev/usb/net/if_cdcereg.h>
+
+static device_probe_t cdce_probe;
+static device_attach_t cdce_attach;
+static device_detach_t cdce_detach;
+static device_suspend_t cdce_suspend;
+static device_resume_t cdce_resume;
+static usb_handle_request_t cdce_handle_request;
+
+static usb_callback_t cdce_bulk_write_callback;
+static usb_callback_t cdce_bulk_read_callback;
+static usb_callback_t cdce_intr_read_callback;
+static usb_callback_t cdce_intr_write_callback;
+
+#if CDCE_HAVE_NCM
+static usb_callback_t cdce_ncm_bulk_write_callback;
+static usb_callback_t cdce_ncm_bulk_read_callback;
+#endif
+
+static uether_fn_t cdce_attach_post;
+static uether_fn_t cdce_init;
+static uether_fn_t cdce_stop;
+static uether_fn_t cdce_start;
+static uether_fn_t cdce_setmulti;
+static uether_fn_t cdce_setpromisc;
+static int cdce_attach_post_sub(struct usb_ether *);
+static int cdce_ioctl(if_t, u_long, caddr_t);
+static int cdce_media_change_cb(if_t);
+static void cdce_media_status_cb(if_t, struct ifmediareq *);
+
+static uint32_t cdce_m_crc32(struct mbuf *, uint32_t, uint32_t);
+static void cdce_set_filter(struct usb_ether *);
+
+#ifdef USB_DEBUG
+static int cdce_debug = 0;
+static int cdce_tx_interval = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, cdce, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB CDC-Ethernet");
+SYSCTL_INT(_hw_usb_cdce, OID_AUTO, debug, CTLFLAG_RWTUN, &cdce_debug, 0,
+ "Debug level");
+SYSCTL_INT(_hw_usb_cdce, OID_AUTO, interval, CTLFLAG_RWTUN, &cdce_tx_interval, 0,
+ "NCM transmit interval in ms");
+#else
+#define cdce_debug 0
+#endif
+
+static const struct usb_config cdce_config[CDCE_N_TRANSFER] = {
+ [CDCE_BULK_RX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_RX,
+ .if_index = 0,
+ .frames = CDCE_FRAMES_MAX,
+ .bufsize = (CDCE_FRAMES_MAX * MCLBYTES),
+ .flags = {.pipe_bof = 1,.short_frames_ok = 1,.short_xfer_ok = 1,.ext_buffer = 1,},
+ .callback = cdce_bulk_read_callback,
+ .timeout = 0, /* no timeout */
+ .usb_mode = USB_MODE_DUAL, /* both modes */
+ },
+
+ [CDCE_BULK_TX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .if_index = 0,
+ .frames = CDCE_FRAMES_MAX,
+ .bufsize = (CDCE_FRAMES_MAX * MCLBYTES),
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1,},
+ .callback = cdce_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ .usb_mode = USB_MODE_DUAL, /* both modes */
+ },
+
+ [CDCE_INTR_RX] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_RX,
+ .if_index = 1,
+ .bufsize = CDCE_IND_SIZE_MAX,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,},
+ .callback = cdce_intr_read_callback,
+ .timeout = 0,
+ .usb_mode = USB_MODE_HOST,
+ },
+
+ [CDCE_INTR_TX] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .if_index = 1,
+ .bufsize = CDCE_IND_SIZE_MAX,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,},
+ .callback = cdce_intr_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ .usb_mode = USB_MODE_DEVICE,
+ },
+};
+
+#if CDCE_HAVE_NCM
+static const struct usb_config cdce_ncm_config[CDCE_N_TRANSFER] = {
+ [CDCE_BULK_RX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_RX,
+ .if_index = 0,
+ .frames = CDCE_NCM_RX_FRAMES_MAX,
+ .bufsize = (CDCE_NCM_RX_FRAMES_MAX * CDCE_NCM_RX_MAXLEN),
+ .flags = {.pipe_bof = 1,.short_frames_ok = 1,.short_xfer_ok = 1,},
+ .callback = cdce_ncm_bulk_read_callback,
+ .timeout = 0, /* no timeout */
+ .usb_mode = USB_MODE_DUAL, /* both modes */
+ },
+
+ [CDCE_BULK_TX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .if_index = 0,
+ .frames = CDCE_NCM_TX_FRAMES_MAX,
+ .bufsize = (CDCE_NCM_TX_FRAMES_MAX * CDCE_NCM_TX_MAXLEN),
+ .flags = {.pipe_bof = 1,},
+ .callback = cdce_ncm_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ .usb_mode = USB_MODE_DUAL, /* both modes */
+ },
+
+ [CDCE_INTR_RX] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_RX,
+ .if_index = 1,
+ .bufsize = CDCE_IND_SIZE_MAX,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,},
+ .callback = cdce_intr_read_callback,
+ .timeout = 0,
+ .usb_mode = USB_MODE_HOST,
+ },
+
+ [CDCE_INTR_TX] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .if_index = 1,
+ .bufsize = CDCE_IND_SIZE_MAX,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,},
+ .callback = cdce_intr_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ .usb_mode = USB_MODE_DEVICE,
+ },
+};
+#endif
+
+static device_method_t cdce_methods[] = {
+ /* USB interface */
+ DEVMETHOD(usb_handle_request, cdce_handle_request),
+
+ /* Device interface */
+ DEVMETHOD(device_probe, cdce_probe),
+ DEVMETHOD(device_attach, cdce_attach),
+ DEVMETHOD(device_detach, cdce_detach),
+ DEVMETHOD(device_suspend, cdce_suspend),
+ DEVMETHOD(device_resume, cdce_resume),
+
+ DEVMETHOD_END
+};
+
+static driver_t cdce_driver = {
+ .name = "cdce",
+ .methods = cdce_methods,
+ .size = sizeof(struct cdce_softc),
+};
+
+static eventhandler_tag cdce_etag;
+
+static int cdce_driver_loaded(struct module *, int, void *);
+
+static const STRUCT_USB_HOST_ID cdce_switch_devs[] = {
+ {USB_VPI(USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_E3272_INIT, MSC_EJECT_HUAWEI2)},
+ {USB_VPI(USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_E3372v153_INIT, MSC_EJECT_HUAWEI2)},
+ {USB_VPI(USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_E3372_INIT, MSC_EJECT_HUAWEI4)},
+ {USB_VPI(USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_E5573Cs322_ECM, MSC_EJECT_HUAWEI3)},
+};
+
+static const STRUCT_USB_HOST_ID cdce_host_devs[] = {
+ {USB_VPI(USB_VENDOR_ACERLABS, USB_PRODUCT_ACERLABS_M5632, CDCE_FLAG_NO_UNION)},
+ {USB_VPI(USB_VENDOR_AMBIT, USB_PRODUCT_AMBIT_NTL_250, CDCE_FLAG_NO_UNION)},
+ {USB_VPI(USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_IPAQLINUX, CDCE_FLAG_NO_UNION)},
+ {USB_VPI(USB_VENDOR_GMATE, USB_PRODUCT_GMATE_YP3X00, CDCE_FLAG_NO_UNION)},
+ {USB_VPI(USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)},
+ {USB_VPI(USB_VENDOR_MOTOROLA2, USB_PRODUCT_MOTOROLA2_USBLAN2, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)},
+ {USB_VPI(USB_VENDOR_PROLIFIC, USB_PRODUCT_PROLIFIC_PL2501, CDCE_FLAG_NO_UNION)},
+ {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5500, CDCE_FLAG_ZAURUS)},
+ {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SL5600, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)},
+ {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLA300, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)},
+ {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC700, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)},
+ {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_SLC750, CDCE_FLAG_ZAURUS | CDCE_FLAG_NO_UNION)},
+
+ {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x16),
+ USB_DRIVER_INFO(0)},
+ {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x46),
+ USB_DRIVER_INFO(0)},
+ {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x76),
+ USB_DRIVER_INFO(0)},
+ {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(0x03), USB_IFACE_PROTOCOL(0x16),
+ USB_DRIVER_INFO(0)},
+};
+
+static const STRUCT_USB_DUAL_ID cdce_dual_devs[] = {
+ {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL, 0)},
+ {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_MOBILE_DIRECT_LINE_MODEL, 0)},
+ {USB_IF_CSI(UICLASS_CDC, UISUBCLASS_NETWORK_CONTROL_MODEL, 0)},
+};
+
+DRIVER_MODULE(cdce, uhub, cdce_driver, cdce_driver_loaded, NULL);
+MODULE_VERSION(cdce, 1);
+MODULE_DEPEND(cdce, uether, 1, 1, 1);
+MODULE_DEPEND(cdce, usb, 1, 1, 1);
+MODULE_DEPEND(cdce, ether, 1, 1, 1);
+USB_PNP_DEVICE_INFO(cdce_switch_devs);
+USB_PNP_HOST_INFO(cdce_host_devs);
+USB_PNP_DUAL_INFO(cdce_dual_devs);
+
+static const struct usb_ether_methods cdce_ue_methods = {
+ .ue_attach_post = cdce_attach_post,
+ .ue_attach_post_sub = cdce_attach_post_sub,
+ .ue_start = cdce_start,
+ .ue_init = cdce_init,
+ .ue_stop = cdce_stop,
+ .ue_setmulti = cdce_setmulti,
+ .ue_setpromisc = cdce_setpromisc,
+};
+
+#if CDCE_HAVE_NCM
+/*------------------------------------------------------------------------*
+ * cdce_ncm_init
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+cdce_ncm_init(struct cdce_softc *sc)
+{
+ struct usb_ncm_parameters temp;
+ struct usb_device_request req;
+ struct usb_ncm_func_descriptor *ufd;
+ uint8_t value[8];
+ int err;
+
+ ufd = usbd_find_descriptor(sc->sc_ue.ue_udev, NULL,
+ sc->sc_ifaces_index[1], UDESC_CS_INTERFACE, 0xFF,
+ UCDC_NCM_FUNC_DESC_SUBTYPE, 0xFF);
+
+ /* verify length of NCM functional descriptor */
+ if (ufd != NULL) {
+ if (ufd->bLength < sizeof(*ufd))
+ ufd = NULL;
+ else
+ DPRINTFN(1, "Found NCM functional descriptor.\n");
+ }
+
+ req.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.bRequest = UCDC_NCM_GET_NTB_PARAMETERS;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_ifaces_index[1];
+ req.wIndex[1] = 0;
+ USETW(req.wLength, sizeof(temp));
+
+ err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req,
+ &temp, 0, NULL, 1000 /* ms */);
+ if (err)
+ return (1);
+
+ /* Read correct set of parameters according to device mode */
+
+ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) {
+ sc->sc_ncm.rx_max = UGETDW(temp.dwNtbInMaxSize);
+ sc->sc_ncm.tx_max = UGETDW(temp.dwNtbOutMaxSize);
+ sc->sc_ncm.tx_remainder = UGETW(temp.wNdpOutPayloadRemainder);
+ sc->sc_ncm.tx_modulus = UGETW(temp.wNdpOutDivisor);
+ sc->sc_ncm.tx_struct_align = UGETW(temp.wNdpOutAlignment);
+ sc->sc_ncm.tx_nframe = UGETW(temp.wNtbOutMaxDatagrams);
+ } else {
+ sc->sc_ncm.rx_max = UGETDW(temp.dwNtbOutMaxSize);
+ sc->sc_ncm.tx_max = UGETDW(temp.dwNtbInMaxSize);
+ sc->sc_ncm.tx_remainder = UGETW(temp.wNdpInPayloadRemainder);
+ sc->sc_ncm.tx_modulus = UGETW(temp.wNdpInDivisor);
+ sc->sc_ncm.tx_struct_align = UGETW(temp.wNdpInAlignment);
+ sc->sc_ncm.tx_nframe = UGETW(temp.wNtbOutMaxDatagrams);
+ }
+
+ /* Verify maximum receive length */
+
+ if ((sc->sc_ncm.rx_max < 32) ||
+ (sc->sc_ncm.rx_max > CDCE_NCM_RX_MAXLEN)) {
+ DPRINTFN(1, "Using default maximum receive length\n");
+ sc->sc_ncm.rx_max = CDCE_NCM_RX_MAXLEN;
+ }
+
+ /* Verify maximum transmit length */
+
+ if ((sc->sc_ncm.tx_max < 32) ||
+ (sc->sc_ncm.tx_max > CDCE_NCM_TX_MAXLEN)) {
+ DPRINTFN(1, "Using default maximum transmit length\n");
+ sc->sc_ncm.tx_max = CDCE_NCM_TX_MAXLEN;
+ }
+
+ /*
+ * Verify that the structure alignment is:
+ * - power of two
+ * - not greater than the maximum transmit length
+ * - not less than four bytes
+ */
+ if ((sc->sc_ncm.tx_struct_align < 4) ||
+ (sc->sc_ncm.tx_struct_align !=
+ ((-sc->sc_ncm.tx_struct_align) & sc->sc_ncm.tx_struct_align)) ||
+ (sc->sc_ncm.tx_struct_align >= sc->sc_ncm.tx_max)) {
+ DPRINTFN(1, "Using default other alignment: 4 bytes\n");
+ sc->sc_ncm.tx_struct_align = 4;
+ }
+
+ /*
+ * Verify that the payload alignment is:
+ * - power of two
+ * - not greater than the maximum transmit length
+ * - not less than four bytes
+ */
+ if ((sc->sc_ncm.tx_modulus < 4) ||
+ (sc->sc_ncm.tx_modulus !=
+ ((-sc->sc_ncm.tx_modulus) & sc->sc_ncm.tx_modulus)) ||
+ (sc->sc_ncm.tx_modulus >= sc->sc_ncm.tx_max)) {
+ DPRINTFN(1, "Using default transmit modulus: 4 bytes\n");
+ sc->sc_ncm.tx_modulus = 4;
+ }
+
+ /* Verify that the payload remainder */
+
+ if ((sc->sc_ncm.tx_remainder >= sc->sc_ncm.tx_modulus)) {
+ DPRINTFN(1, "Using default transmit remainder: 0 bytes\n");
+ sc->sc_ncm.tx_remainder = 0;
+ }
+
+ /*
+ * Offset the TX remainder so that IP packet payload starts at
+ * the tx_modulus. This is not too clear in the specification.
+ */
+
+ sc->sc_ncm.tx_remainder =
+ (sc->sc_ncm.tx_remainder - ETHER_HDR_LEN) &
+ (sc->sc_ncm.tx_modulus - 1);
+
+ /* Verify max datagrams */
+
+ if (sc->sc_ncm.tx_nframe == 0 ||
+ sc->sc_ncm.tx_nframe > (CDCE_NCM_SUBFRAMES_MAX - 1)) {
+ DPRINTFN(1, "Using default max "
+ "subframes: %u units\n", CDCE_NCM_SUBFRAMES_MAX - 1);
+ /* need to reserve one entry for zero padding */
+ sc->sc_ncm.tx_nframe = (CDCE_NCM_SUBFRAMES_MAX - 1);
+ }
+
+ /* Additional configuration, will fail in device side mode, which is OK. */
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_NCM_SET_NTB_INPUT_SIZE;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_ifaces_index[1];
+ req.wIndex[1] = 0;
+
+ if (ufd != NULL &&
+ (ufd->bmNetworkCapabilities & UCDC_NCM_CAP_MAX_DGRAM)) {
+ USETW(req.wLength, 8);
+ USETDW(value, sc->sc_ncm.rx_max);
+ USETW(value + 4, (CDCE_NCM_SUBFRAMES_MAX - 1));
+ USETW(value + 6, 0);
+ } else {
+ USETW(req.wLength, 4);
+ USETDW(value, sc->sc_ncm.rx_max);
+ }
+
+ err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req,
+ &value, 0, NULL, 1000 /* ms */);
+ if (err) {
+ DPRINTFN(1, "Setting input size "
+ "to %u failed.\n", sc->sc_ncm.rx_max);
+ }
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_NCM_SET_CRC_MODE;
+ USETW(req.wValue, 0); /* no CRC */
+ req.wIndex[0] = sc->sc_ifaces_index[1];
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req,
+ NULL, 0, NULL, 1000 /* ms */);
+ if (err) {
+ DPRINTFN(1, "Setting CRC mode to off failed.\n");
+ }
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_NCM_SET_NTB_FORMAT;
+ USETW(req.wValue, 0); /* NTB-16 */
+ req.wIndex[0] = sc->sc_ifaces_index[1];
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ err = usbd_do_request_flags(sc->sc_ue.ue_udev, NULL, &req,
+ NULL, 0, NULL, 1000 /* ms */);
+ if (err) {
+ DPRINTFN(1, "Setting NTB format to 16-bit failed.\n");
+ }
+
+ return (0); /* success */
+}
+#endif
+
+static void
+cdce_test_autoinst(void *arg, struct usb_device *udev,
+ struct usb_attach_arg *uaa)
+{
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *id;
+
+ if (uaa->dev_state != UAA_DEV_READY)
+ return;
+
+ iface = usbd_get_iface(udev, 0);
+ if (iface == NULL)
+ return;
+ id = iface->idesc;
+ if (id == NULL || id->bInterfaceClass != UICLASS_MASS)
+ return;
+ if (usbd_lookup_id_by_uaa(cdce_switch_devs, sizeof(cdce_switch_devs), uaa))
+ return; /* no device match */
+
+ if (usb_msc_eject(udev, 0, USB_GET_DRIVER_INFO(uaa)) == 0) {
+ /* success, mark the udev as disappearing */
+ uaa->dev_state = UAA_DEV_EJECTING;
+ }
+}
+
+static int
+cdce_driver_loaded(struct module *mod, int what, void *arg)
+{
+ switch (what) {
+ case MOD_LOAD:
+ /* register our autoinstall handler */
+ cdce_etag = EVENTHANDLER_REGISTER(usb_dev_configured,
+ cdce_test_autoinst, NULL, EVENTHANDLER_PRI_ANY);
+ return (0);
+ case MOD_UNLOAD:
+ EVENTHANDLER_DEREGISTER(usb_dev_configured, cdce_etag);
+ return (0);
+ default:
+ return (EOPNOTSUPP);
+ }
+}
+
+static int
+cdce_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ int error;
+
+ error = usbd_lookup_id_by_uaa(cdce_host_devs, sizeof(cdce_host_devs), uaa);
+ if (error)
+ error = usbd_lookup_id_by_uaa(cdce_dual_devs, sizeof(cdce_dual_devs), uaa);
+ return (error);
+}
+
+static void
+cdce_attach_post(struct usb_ether *ue)
+{
+ /* no-op */
+ return;
+}
+
+static int
+cdce_attach_post_sub(struct usb_ether *ue)
+{
+ struct cdce_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ /* mostly copied from usb_ethernet.c */
+ if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
+ if_setstartfn(ifp, uether_start);
+ if_setioctlfn(ifp, cdce_ioctl);
+ if_setinitfn(ifp, uether_init);
+ if_setsendqlen(ifp, ifqmaxlen);
+ if_setsendqready(ifp);
+
+ if ((sc->sc_flags & CDCE_FLAG_VLAN) == CDCE_FLAG_VLAN)
+ if_setcapabilitiesbit(ifp, IFCAP_VLAN_MTU, 0);
+
+ if_setcapabilitiesbit(ifp, IFCAP_LINKSTATE, 0);
+ if_setcapenable(ifp, if_getcapabilities(ifp));
+
+ ifmedia_init(&sc->sc_media, IFM_IMASK, cdce_media_change_cb,
+ cdce_media_status_cb);
+ ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_AUTO, 0, NULL);
+ ifmedia_set(&sc->sc_media, IFM_ETHER | IFM_AUTO);
+ sc->sc_media.ifm_media = sc->sc_media.ifm_cur->ifm_media;
+ CDCE_LOCK(sc);
+ cdce_set_filter(ue);
+ CDCE_UNLOCK(sc);
+
+ return 0;
+}
+
+static int
+cdce_attach(device_t dev)
+{
+ struct cdce_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct usb_interface *iface;
+ const struct usb_cdc_union_descriptor *ud;
+ const struct usb_interface_descriptor *id;
+ const struct usb_cdc_ethernet_descriptor *ued;
+ const struct usb_config *pcfg;
+ uint32_t seed;
+ int error;
+ uint8_t i;
+ uint8_t data_iface_no;
+ char eaddr_str[5 * ETHER_ADDR_LEN]; /* approx */
+
+ sc->sc_flags = USB_GET_DRIVER_INFO(uaa);
+ sc->sc_ue.ue_udev = uaa->device;
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ ud = usbd_find_descriptor
+ (uaa->device, NULL, uaa->info.bIfaceIndex,
+ UDESC_CS_INTERFACE, 0xFF, UDESCSUB_CDC_UNION, 0xFF);
+
+ if ((ud == NULL) || (ud->bLength < sizeof(*ud)) ||
+ (sc->sc_flags & CDCE_FLAG_NO_UNION)) {
+ DPRINTFN(1, "No union descriptor!\n");
+ sc->sc_ifaces_index[0] = uaa->info.bIfaceIndex;
+ sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex;
+ goto alloc_transfers;
+ }
+ data_iface_no = ud->bSlaveInterface[0];
+
+ for (i = 0;; i++) {
+ iface = usbd_get_iface(uaa->device, i);
+
+ if (iface) {
+ id = usbd_get_interface_descriptor(iface);
+
+ if (id && (id->bInterfaceNumber == data_iface_no)) {
+ sc->sc_ifaces_index[0] = i;
+ sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex;
+ usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex);
+ break;
+ }
+ } else {
+ device_printf(dev, "no data interface found\n");
+ goto detach;
+ }
+ }
+
+ /*
+ * <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.
+ */
+
+alloc_transfers:
+
+ pcfg = cdce_config; /* Default Configuration */
+
+ for (i = 0; i != 32; i++) {
+ error = usbd_set_alt_interface_index(uaa->device,
+ sc->sc_ifaces_index[0], i);
+ if (error)
+ break;
+#if CDCE_HAVE_NCM
+ if ((i == 0) && (cdce_ncm_init(sc) == 0))
+ pcfg = cdce_ncm_config;
+#endif
+ error = usbd_transfer_setup(uaa->device,
+ sc->sc_ifaces_index, sc->sc_xfer,
+ pcfg, CDCE_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error == 0)
+ break;
+ }
+
+ if (error || (i == 32)) {
+ device_printf(dev, "No valid alternate "
+ "setting found\n");
+ goto detach;
+ }
+
+ ued = usbd_find_descriptor
+ (uaa->device, NULL, uaa->info.bIfaceIndex,
+ UDESC_CS_INTERFACE, 0xFF, UDESCSUB_CDC_ENF, 0xFF);
+
+ if ((ued == NULL) || (ued->bLength < sizeof(*ued))) {
+ error = USB_ERR_INVAL;
+ } else {
+ /*
+ * ECM 1.2 doesn't say it excludes the CRC, but states that it's
+ * normally 1514, which excludes the CRC.
+ */
+ DPRINTF("max segsize: %d\n", UGETW(ued->wMaxSegmentSize));
+ if (UGETW(ued->wMaxSegmentSize) >= (ETHER_MAX_LEN - ETHER_CRC_LEN + ETHER_VLAN_ENCAP_LEN))
+ sc->sc_flags |= CDCE_FLAG_VLAN;
+
+ error = usbd_req_get_string_any(uaa->device, NULL,
+ eaddr_str, sizeof(eaddr_str), ued->iMacAddress);
+ }
+
+ if (error) {
+ /* fake MAC address */
+
+ device_printf(dev, "faking MAC address\n");
+ seed = ticks;
+ sc->sc_ue.ue_eaddr[0] = 0x2a;
+ memcpy(&sc->sc_ue.ue_eaddr[1], &seed, sizeof(uint32_t));
+ sc->sc_ue.ue_eaddr[5] = device_get_unit(dev);
+
+ } else {
+ memset(sc->sc_ue.ue_eaddr, 0, sizeof(sc->sc_ue.ue_eaddr));
+
+ for (i = 0; i != (ETHER_ADDR_LEN * 2); i++) {
+ char c = eaddr_str[i];
+
+ if ('0' <= c && c <= '9')
+ c -= '0';
+ else if (c != 0)
+ c -= 'A' - 10;
+ else
+ break;
+
+ c &= 0xf;
+
+ if ((i & 1) == 0)
+ c <<= 4;
+ sc->sc_ue.ue_eaddr[i / 2] |= c;
+ }
+
+ if (uaa->usb_mode == USB_MODE_DEVICE) {
+ /*
+ * Do not use the same MAC address like the peer !
+ */
+ sc->sc_ue.ue_eaddr[5] ^= 0xFF;
+ }
+ }
+
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &cdce_ue_methods;
+
+ error = uether_ifattach(ue);
+ if (error) {
+ device_printf(dev, "could not attach interface\n");
+ goto detach;
+ }
+ return (0); /* success */
+
+detach:
+ cdce_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+cdce_detach(device_t dev)
+{
+ struct cdce_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ /* stop all USB transfers first */
+ usbd_transfer_unsetup(sc->sc_xfer, CDCE_N_TRANSFER);
+ uether_ifdetach(ue);
+ mtx_destroy(&sc->sc_mtx);
+
+ ifmedia_removeall(&sc->sc_media);
+
+ return (0);
+}
+
+static void
+cdce_start(struct usb_ether *ue)
+{
+ struct cdce_softc *sc = uether_getsc(ue);
+
+ /*
+ * Start the USB transfers, if not already started:
+ */
+ usbd_transfer_start(sc->sc_xfer[CDCE_BULK_TX]);
+ usbd_transfer_start(sc->sc_xfer[CDCE_BULK_RX]);
+}
+
+static int
+cdce_ioctl(if_t ifp, u_long command, caddr_t data)
+{
+ struct usb_ether *ue = if_getsoftc(ifp);
+ struct cdce_softc *sc = uether_getsc(ue);
+ struct ifreq *ifr = (struct ifreq *)data;
+ int error;
+
+ error = 0;
+
+ switch(command) {
+ case SIOCGIFMEDIA:
+ case SIOCSIFMEDIA:
+ error = ifmedia_ioctl(ifp, ifr, &sc->sc_media, command);
+ break;
+ default:
+ error = uether_ioctl(ifp, command, data);
+ break;
+ }
+
+ return (error);
+}
+
+static void
+cdce_free_queue(struct mbuf **ppm, uint8_t n)
+{
+ uint8_t x;
+ for (x = 0; x != n; x++) {
+ if (ppm[x] != NULL) {
+ m_freem(ppm[x]);
+ ppm[x] = NULL;
+ }
+ }
+}
+
+static int
+cdce_media_change_cb(if_t ifp)
+{
+
+ return (EOPNOTSUPP);
+}
+
+static void
+cdce_media_status_cb(if_t ifp, struct ifmediareq *ifmr)
+{
+
+ if ((if_getflags(ifp) & IFF_UP) == 0)
+ return;
+
+ ifmr->ifm_active = IFM_ETHER;
+ ifmr->ifm_status = IFM_AVALID;
+ ifmr->ifm_status |=
+ if_getlinkstate(ifp) == LINK_STATE_UP ? IFM_ACTIVE : 0;
+}
+
+static void
+cdce_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct cdce_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct mbuf *m;
+ struct mbuf *mt;
+ uint32_t crc;
+ uint8_t x;
+ int actlen, aframes;
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ DPRINTFN(1, "\n");
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "transfer complete: %u bytes in %u frames\n",
+ actlen, aframes);
+
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /* free all previous TX buffers */
+ cdce_free_queue(sc->sc_tx_buf, CDCE_FRAMES_MAX);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ for (x = 0; x != CDCE_FRAMES_MAX; x++) {
+ m = if_dequeue(ifp);
+
+ if (m == NULL)
+ break;
+
+ if (sc->sc_flags & CDCE_FLAG_ZAURUS) {
+ /*
+ * Zaurus wants a 32-bit CRC appended
+ * to every frame
+ */
+
+ crc = cdce_m_crc32(m, 0, m->m_pkthdr.len);
+ crc = htole32(crc);
+
+ if (!m_append(m, 4, (void *)&crc)) {
+ m_freem(m);
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ continue;
+ }
+ }
+ if (m->m_len != m->m_pkthdr.len) {
+ mt = m_defrag(m, M_NOWAIT);
+ if (mt == NULL) {
+ m_freem(m);
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ continue;
+ }
+ m = mt;
+ }
+ if (m->m_pkthdr.len > MCLBYTES) {
+ m->m_pkthdr.len = MCLBYTES;
+ }
+ sc->sc_tx_buf[x] = m;
+ usbd_xfer_set_frame_data(xfer, x, m->m_data, m->m_len);
+
+ /*
+ * If there's a BPF listener, bounce a copy of
+ * this frame to him:
+ */
+ BPF_MTAP(ifp, m);
+ }
+ if (x != 0) {
+ usbd_xfer_set_frames(xfer, x);
+
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ DPRINTFN(11, "transfer error, %s\n",
+ usbd_errstr(error));
+
+ /* free all previous TX buffers */
+ cdce_free_queue(sc->sc_tx_buf, CDCE_FRAMES_MAX);
+
+ /* count output errors */
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+
+ if (error != USB_ERR_CANCELLED) {
+ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ }
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static int32_t
+cdce_m_crc32_cb(void *arg, void *src, uint32_t count)
+{
+ uint32_t *p_crc = arg;
+
+ *p_crc = crc32_raw(src, count, *p_crc);
+ return (0);
+}
+
+static uint32_t
+cdce_m_crc32(struct mbuf *m, uint32_t src_offset, uint32_t src_len)
+{
+ uint32_t crc = 0xFFFFFFFF;
+
+ (void)m_apply(m, src_offset, src_len, cdce_m_crc32_cb, &crc);
+ return (crc ^ 0xFFFFFFFF);
+}
+
+static void
+cdce_init(struct usb_ether *ue)
+{
+ struct cdce_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ CDCE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+
+ /* start interrupt transfer */
+ usbd_transfer_start(sc->sc_xfer[CDCE_INTR_RX]);
+ usbd_transfer_start(sc->sc_xfer[CDCE_INTR_TX]);
+
+ /*
+ * Stall data write direction, which depends on USB mode.
+ *
+ * Some USB host stacks (e.g. Mac OS X) don't clears stall
+ * bit as it should, so set it in our host mode only.
+ */
+ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST)
+ usbd_xfer_set_stall(sc->sc_xfer[CDCE_BULK_TX]);
+
+ /* start data transfers */
+ cdce_start(ue);
+}
+
+static void
+cdce_stop(struct usb_ether *ue)
+{
+ struct cdce_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ CDCE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+
+ /*
+ * stop all the transfers, if not already stopped:
+ */
+ usbd_transfer_stop(sc->sc_xfer[CDCE_BULK_RX]);
+ usbd_transfer_stop(sc->sc_xfer[CDCE_BULK_TX]);
+ usbd_transfer_stop(sc->sc_xfer[CDCE_INTR_RX]);
+ usbd_transfer_stop(sc->sc_xfer[CDCE_INTR_TX]);
+}
+
+static void
+cdce_setmulti(struct usb_ether *ue)
+{
+
+ cdce_set_filter(ue);
+}
+
+static void
+cdce_setpromisc(struct usb_ether *ue)
+{
+
+ cdce_set_filter(ue);
+}
+
+static void
+cdce_set_filter(struct usb_ether *ue)
+{
+ struct cdce_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ struct usb_device_request req;
+ uint16_t value;
+
+ value = CDC_PACKET_TYPE_DIRECTED | CDC_PACKET_TYPE_BROADCAST;
+ if (if_getflags(ifp) & IFF_PROMISC)
+ value |= CDC_PACKET_TYPE_PROMISC;
+ if (if_getflags(ifp) & IFF_ALLMULTI)
+ value |= CDC_PACKET_TYPE_ALL_MULTICAST;
+
+ req.bmRequestType = UT_CLASS | UT_INTERFACE;
+ req.bRequest = CDC_SET_ETHERNET_PACKET_FILTER;
+ USETW(req.wValue, value);
+ req.wIndex[0] = sc->sc_ifaces_index[1];
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ /*
+ * Function below will drop the sc mutex.
+ * We can do that since we're called from a separate task,
+ * that simply wraps the setpromisc/setmulti methods.
+ */
+ usbd_do_request(sc->sc_ue.ue_udev, &sc->sc_mtx, &req, NULL);
+}
+
+static int
+cdce_suspend(device_t dev)
+{
+ device_printf(dev, "Suspending\n");
+ return (0);
+}
+
+static int
+cdce_resume(device_t dev)
+{
+ device_printf(dev, "Resuming\n");
+ return (0);
+}
+
+static void
+cdce_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct cdce_softc *sc = usbd_xfer_softc(xfer);
+ struct mbuf *m;
+ uint8_t x;
+ int actlen;
+ int aframes;
+ int len;
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ DPRINTF("received %u bytes in %u frames\n", actlen, aframes);
+
+ for (x = 0; x != aframes; x++) {
+ m = sc->sc_rx_buf[x];
+ sc->sc_rx_buf[x] = NULL;
+ len = usbd_xfer_frame_len(xfer, x);
+
+ /* Strip off CRC added by Zaurus, if any */
+ if ((sc->sc_flags & CDCE_FLAG_ZAURUS) && len >= 14)
+ len -= 4;
+
+ if (len < (int)sizeof(struct ether_header)) {
+ m_freem(m);
+ continue;
+ }
+ /* queue up mbuf */
+ uether_rxmbuf(&sc->sc_ue, m, len);
+ }
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+ /*
+ * TODO: Implement support for multi frame transfers,
+ * when the USB hardware supports it.
+ */
+ for (x = 0; x != 1; x++) {
+ if (sc->sc_rx_buf[x] == NULL) {
+ m = uether_newbuf();
+ if (m == NULL)
+ goto tr_stall;
+ sc->sc_rx_buf[x] = m;
+ } else {
+ m = sc->sc_rx_buf[x];
+ }
+
+ usbd_xfer_set_frame_data(xfer, x, m->m_data, m->m_len);
+ }
+ /* set number of frames and start hardware */
+ usbd_xfer_set_frames(xfer, x);
+ usbd_transfer_submit(xfer);
+ /* flush any received frames */
+ uether_rxflush(&sc->sc_ue);
+ break;
+
+ default: /* Error */
+ DPRINTF("error = %s\n",
+ usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+tr_stall:
+ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ usbd_xfer_set_frames(xfer, 0);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+ }
+
+ /* need to free the RX-mbufs when we are cancelled */
+ cdce_free_queue(sc->sc_rx_buf, CDCE_FRAMES_MAX);
+ break;
+ }
+}
+
+static void
+cdce_intr_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ u_char buf[CDCE_IND_SIZE_MAX];
+ struct usb_cdc_notification ucn;
+ struct cdce_softc *sc;
+ if_t ifp;
+ struct usb_page_cache *pc;
+ int off, actlen;
+ uint32_t downrate, uprate;
+
+ sc = usbd_xfer_softc(xfer);
+ ifp = uether_getifp(&sc->sc_ue);
+ pc = usbd_xfer_get_frame(xfer, 0);
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (USB_DEBUG_VAR)
+ usbd_copy_out(pc, 0, buf, MIN(actlen, sizeof buf));
+ DPRINTF("Received %d bytes: %*D\n", actlen,
+ (int)MIN(actlen, sizeof buf), buf, "");
+
+ off = 0;
+ while (actlen - off >= UCDC_NOTIFICATION_LENGTH) {
+ usbd_copy_out(pc, off, &ucn, UCDC_NOTIFICATION_LENGTH);
+
+ do {
+ if (ucn.bmRequestType != 0xa1)
+ break;
+
+ switch (ucn.bNotification) {
+ case UCDC_N_NETWORK_CONNECTION:
+ DPRINTF("changing link state: %d\n",
+ UGETW(ucn.wValue));
+ if_link_state_change(ifp,
+ UGETW(ucn.wValue) ? LINK_STATE_UP :
+ LINK_STATE_DOWN);
+ break;
+
+ case UCDC_N_CONNECTION_SPEED_CHANGE:
+ if (UGETW(ucn.wLength) != 8)
+ break;
+
+ usbd_copy_out(pc, off +
+ UCDC_NOTIFICATION_LENGTH,
+ &ucn.data, UGETW(ucn.wLength));
+ downrate = UGETDW(ucn.data);
+ uprate = UGETDW(ucn.data);
+ if (downrate != uprate)
+ break;
+
+ /* set rate */
+ DPRINTF("changing baudrate: %u\n",
+ downrate);
+ if_setbaudrate(ifp, downrate);
+ break;
+
+ default:
+ break;
+ }
+ } while (0);
+
+ off += UCDC_NOTIFICATION_LENGTH + UGETW(ucn.wLength);
+ }
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* start clear stall */
+ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST)
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+cdce_intr_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct cdce_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_cdc_notification req;
+ struct usb_page_cache *pc;
+ uint32_t speed;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ DPRINTF("Transferred %d bytes\n", actlen);
+
+ switch (sc->sc_notify_state) {
+ case CDCE_NOTIFY_NETWORK_CONNECTION:
+ sc->sc_notify_state = CDCE_NOTIFY_SPEED_CHANGE;
+ break;
+ case CDCE_NOTIFY_SPEED_CHANGE:
+ sc->sc_notify_state = CDCE_NOTIFY_DONE;
+ break;
+ default:
+ break;
+ }
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ /*
+ * Inform host about connection. Required according to USB CDC
+ * specification and communicating to Mac OS X USB host stack.
+ * Some of the values seems ignored by Mac OS X though.
+ */
+ if (sc->sc_notify_state == CDCE_NOTIFY_NETWORK_CONNECTION) {
+ req.bmRequestType = UCDC_NOTIFICATION;
+ req.bNotification = UCDC_N_NETWORK_CONNECTION;
+ req.wIndex[0] = sc->sc_ifaces_index[1];
+ req.wIndex[1] = 0;
+ USETW(req.wValue, 1); /* Connected */
+ USETW(req.wLength, 0);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frames(xfer, 1);
+ usbd_transfer_submit(xfer);
+
+ } else if (sc->sc_notify_state == CDCE_NOTIFY_SPEED_CHANGE) {
+ req.bmRequestType = UCDC_NOTIFICATION;
+ req.bNotification = UCDC_N_CONNECTION_SPEED_CHANGE;
+ req.wIndex[0] = sc->sc_ifaces_index[1];
+ req.wIndex[1] = 0;
+ USETW(req.wValue, 0);
+ USETW(req.wLength, 8);
+
+ /* Peak theoretical bulk trasfer rate in bits/s */
+ if (usbd_get_speed(sc->sc_ue.ue_udev) != USB_SPEED_FULL)
+ speed = (13 * 512 * 8 * 1000 * 8);
+ else
+ speed = (19 * 64 * 1 * 1000 * 8);
+
+ USETDW(req.data + 0, speed); /* Upstream bit rate */
+ USETDW(req.data + 4, speed); /* Downstream bit rate */
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frames(xfer, 1);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) {
+ /* start clear stall */
+ usbd_xfer_set_stall(xfer);
+ }
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static int
+cdce_handle_request(device_t dev,
+ const void *preq, void **pptr, uint16_t *plen,
+ uint16_t offset, uint8_t *pstate)
+{
+ struct cdce_softc *sc = device_get_softc(dev);
+ const struct usb_device_request *req = preq;
+ uint8_t is_complete = *pstate;
+
+ /*
+ * When Mac OS X resumes after suspending it expects
+ * to be notified again after this request.
+ */
+ if (req->bmRequestType == UT_WRITE_CLASS_INTERFACE && \
+ req->bRequest == UCDC_NCM_SET_ETHERNET_PACKET_FILTER) {
+ if (is_complete == 1) {
+ mtx_lock(&sc->sc_mtx);
+ sc->sc_notify_state = CDCE_NOTIFY_SPEED_CHANGE;
+ usbd_transfer_start(sc->sc_xfer[CDCE_INTR_TX]);
+ mtx_unlock(&sc->sc_mtx);
+ }
+
+ return (0);
+ }
+
+ return (ENXIO); /* use builtin handler */
+}
+
+#if CDCE_HAVE_NCM
+static void
+cdce_ncm_tx_zero(struct usb_page_cache *pc,
+ uint32_t start, uint32_t end)
+{
+ if (start >= CDCE_NCM_TX_MAXLEN)
+ return;
+ if (end > CDCE_NCM_TX_MAXLEN)
+ end = CDCE_NCM_TX_MAXLEN;
+
+ usbd_frame_zero(pc, start, end - start);
+}
+
+static uint8_t
+cdce_ncm_fill_tx_frames(struct usb_xfer *xfer, uint8_t index)
+{
+ struct cdce_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, index);
+ struct mbuf *m;
+ uint32_t rem;
+ uint32_t offset;
+ uint32_t last_offset;
+ uint16_t n;
+ uint8_t retval;
+
+ usbd_xfer_set_frame_offset(xfer, index * CDCE_NCM_TX_MAXLEN, index);
+
+ offset = sizeof(sc->sc_ncm.hdr) +
+ sizeof(sc->sc_ncm.dpt) + sizeof(sc->sc_ncm.dp);
+
+ /* Store last valid offset before alignment */
+ last_offset = offset;
+
+ /* Align offset */
+ offset = CDCE_NCM_ALIGN(sc->sc_ncm.tx_remainder,
+ offset, sc->sc_ncm.tx_modulus);
+
+ /* Zero pad */
+ cdce_ncm_tx_zero(pc, last_offset, offset);
+
+ /* buffer full */
+ retval = 2;
+
+ for (n = 0; n != sc->sc_ncm.tx_nframe; n++) {
+ /* check if end of transmit buffer is reached */
+
+ if (offset >= sc->sc_ncm.tx_max)
+ break;
+
+ /* compute maximum buffer size */
+
+ rem = sc->sc_ncm.tx_max - offset;
+
+ m = if_dequeue(ifp);
+
+ if (m == NULL) {
+ /* buffer not full */
+ retval = 1;
+ break;
+ }
+
+ if (m->m_pkthdr.len > (int)rem) {
+ if (n == 0) {
+ /* The frame won't fit in our buffer */
+ DPRINTFN(1, "Frame too big to be transmitted!\n");
+ m_freem(m);
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ n--;
+ continue;
+ }
+ /* Wait till next buffer becomes ready */
+ if_sendq_prepend(ifp, m);
+ break;
+ }
+ usbd_m_copy_in(pc, offset, m, 0, m->m_pkthdr.len);
+
+ USETW(sc->sc_ncm.dp[n].wFrameLength, m->m_pkthdr.len);
+ USETW(sc->sc_ncm.dp[n].wFrameIndex, offset);
+
+ /* Update offset */
+ offset += m->m_pkthdr.len;
+
+ /* Store last valid offset before alignment */
+ last_offset = offset;
+
+ /* Align offset */
+ offset = CDCE_NCM_ALIGN(sc->sc_ncm.tx_remainder,
+ offset, sc->sc_ncm.tx_modulus);
+
+ /* Zero pad */
+ cdce_ncm_tx_zero(pc, last_offset, offset);
+
+ /*
+ * If there's a BPF listener, bounce a copy
+ * of this frame to him:
+ */
+ BPF_MTAP(ifp, m);
+
+ /* Free mbuf */
+
+ m_freem(m);
+
+ /* Pre-increment interface counter */
+
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+ }
+
+ if (n == 0)
+ return (0);
+
+ rem = (sizeof(sc->sc_ncm.dpt) + (4 * n) + 4);
+
+ USETW(sc->sc_ncm.dpt.wLength, rem);
+
+ /* zero the rest of the data pointer entries */
+ for (; n != CDCE_NCM_SUBFRAMES_MAX; n++) {
+ USETW(sc->sc_ncm.dp[n].wFrameLength, 0);
+ USETW(sc->sc_ncm.dp[n].wFrameIndex, 0);
+ }
+
+ offset = last_offset;
+
+ /* Align offset */
+ offset = CDCE_NCM_ALIGN(0, offset, CDCE_NCM_TX_MINLEN);
+
+ /* Optimise, save bandwidth and force short termination */
+ if (offset >= sc->sc_ncm.tx_max)
+ offset = sc->sc_ncm.tx_max;
+ else
+ offset ++;
+
+ /* Zero pad */
+ cdce_ncm_tx_zero(pc, last_offset, offset);
+
+ /* set frame length */
+ usbd_xfer_set_frame_len(xfer, index, offset);
+
+ /* Fill out 16-bit header */
+ sc->sc_ncm.hdr.dwSignature[0] = 'N';
+ sc->sc_ncm.hdr.dwSignature[1] = 'C';
+ sc->sc_ncm.hdr.dwSignature[2] = 'M';
+ sc->sc_ncm.hdr.dwSignature[3] = 'H';
+ USETW(sc->sc_ncm.hdr.wHeaderLength, sizeof(sc->sc_ncm.hdr));
+ USETW(sc->sc_ncm.hdr.wBlockLength, offset);
+ USETW(sc->sc_ncm.hdr.wSequence, sc->sc_ncm.tx_seq);
+ USETW(sc->sc_ncm.hdr.wDptIndex, sizeof(sc->sc_ncm.hdr));
+
+ sc->sc_ncm.tx_seq++;
+
+ /* Fill out 16-bit frame table header */
+ sc->sc_ncm.dpt.dwSignature[0] = 'N';
+ sc->sc_ncm.dpt.dwSignature[1] = 'C';
+ sc->sc_ncm.dpt.dwSignature[2] = 'M';
+ sc->sc_ncm.dpt.dwSignature[3] = '0';
+ USETW(sc->sc_ncm.dpt.wNextNdpIndex, 0); /* reserved */
+
+ usbd_copy_in(pc, 0, &(sc->sc_ncm.hdr), sizeof(sc->sc_ncm.hdr));
+ usbd_copy_in(pc, sizeof(sc->sc_ncm.hdr), &(sc->sc_ncm.dpt),
+ sizeof(sc->sc_ncm.dpt));
+ usbd_copy_in(pc, sizeof(sc->sc_ncm.hdr) + sizeof(sc->sc_ncm.dpt),
+ &(sc->sc_ncm.dp), sizeof(sc->sc_ncm.dp));
+ return (retval);
+}
+
+static void
+cdce_ncm_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct cdce_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ uint16_t x;
+ uint8_t temp;
+ int actlen;
+ int aframes;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ DPRINTFN(10, "transfer complete: "
+ "%u bytes in %u frames\n", actlen, aframes);
+
+ case USB_ST_SETUP:
+ for (x = 0; x != CDCE_NCM_TX_FRAMES_MAX; x++) {
+ temp = cdce_ncm_fill_tx_frames(xfer, x);
+ if (temp == 0)
+ break;
+ if (temp == 1) {
+ x++;
+ break;
+ }
+ }
+
+ if (x != 0) {
+#ifdef USB_DEBUG
+ usbd_xfer_set_interval(xfer, cdce_tx_interval);
+#endif
+ usbd_xfer_set_frames(xfer, x);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ DPRINTFN(10, "Transfer error: %s\n",
+ usbd_errstr(error));
+
+ /* update error counter */
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+
+ if (error != USB_ERR_CANCELLED) {
+ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ usbd_xfer_set_frames(xfer, 0);
+ usbd_transfer_submit(xfer);
+ }
+ }
+ break;
+ }
+}
+
+static void
+cdce_ncm_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct cdce_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, 0);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct mbuf *m;
+ int sumdata __usbdebug_used;
+ int sumlen;
+ int actlen;
+ int aframes;
+ int temp;
+ int nframes;
+ int x;
+ int offset;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, &aframes, NULL);
+
+ DPRINTFN(1, "received %u bytes in %u frames\n",
+ actlen, aframes);
+
+ if (actlen < (int)(sizeof(sc->sc_ncm.hdr) +
+ sizeof(sc->sc_ncm.dpt))) {
+ DPRINTFN(1, "frame too short\n");
+ goto tr_setup;
+ }
+ usbd_copy_out(pc, 0, &(sc->sc_ncm.hdr),
+ sizeof(sc->sc_ncm.hdr));
+
+ if ((sc->sc_ncm.hdr.dwSignature[0] != 'N') ||
+ (sc->sc_ncm.hdr.dwSignature[1] != 'C') ||
+ (sc->sc_ncm.hdr.dwSignature[2] != 'M') ||
+ (sc->sc_ncm.hdr.dwSignature[3] != 'H')) {
+ DPRINTFN(1, "invalid HDR signature: "
+ "0x%02x:0x%02x:0x%02x:0x%02x\n",
+ sc->sc_ncm.hdr.dwSignature[0],
+ sc->sc_ncm.hdr.dwSignature[1],
+ sc->sc_ncm.hdr.dwSignature[2],
+ sc->sc_ncm.hdr.dwSignature[3]);
+ goto tr_stall;
+ }
+ temp = UGETW(sc->sc_ncm.hdr.wBlockLength);
+ if (temp > sumlen) {
+ DPRINTFN(1, "unsupported block length %u/%u\n",
+ temp, sumlen);
+ goto tr_stall;
+ }
+ temp = UGETW(sc->sc_ncm.hdr.wDptIndex);
+ if ((int)(temp + sizeof(sc->sc_ncm.dpt)) > actlen) {
+ DPRINTFN(1, "invalid DPT index: 0x%04x\n", temp);
+ goto tr_stall;
+ }
+ usbd_copy_out(pc, temp, &(sc->sc_ncm.dpt),
+ sizeof(sc->sc_ncm.dpt));
+
+ if ((sc->sc_ncm.dpt.dwSignature[0] != 'N') ||
+ (sc->sc_ncm.dpt.dwSignature[1] != 'C') ||
+ (sc->sc_ncm.dpt.dwSignature[2] != 'M') ||
+ (sc->sc_ncm.dpt.dwSignature[3] != '0')) {
+ DPRINTFN(1, "invalid DPT signature"
+ "0x%02x:0x%02x:0x%02x:0x%02x\n",
+ sc->sc_ncm.dpt.dwSignature[0],
+ sc->sc_ncm.dpt.dwSignature[1],
+ sc->sc_ncm.dpt.dwSignature[2],
+ sc->sc_ncm.dpt.dwSignature[3]);
+ goto tr_stall;
+ }
+ nframes = UGETW(sc->sc_ncm.dpt.wLength) / 4;
+
+ /* Subtract size of header and last zero padded entry */
+ if (nframes >= (2 + 1))
+ nframes -= (2 + 1);
+ else
+ nframes = 0;
+
+ DPRINTFN(1, "nframes = %u\n", nframes);
+
+ temp += sizeof(sc->sc_ncm.dpt);
+
+ if ((temp + (4 * nframes)) > actlen)
+ goto tr_stall;
+
+ if (nframes > CDCE_NCM_SUBFRAMES_MAX) {
+ DPRINTFN(1, "Truncating number of frames from %u to %u\n",
+ nframes, CDCE_NCM_SUBFRAMES_MAX);
+ nframes = CDCE_NCM_SUBFRAMES_MAX;
+ }
+ usbd_copy_out(pc, temp, &(sc->sc_ncm.dp), (4 * nframes));
+
+ sumdata = 0;
+
+ for (x = 0; x != nframes; x++) {
+ offset = UGETW(sc->sc_ncm.dp[x].wFrameIndex);
+ temp = UGETW(sc->sc_ncm.dp[x].wFrameLength);
+
+ if ((offset == 0) ||
+ (temp < (int)sizeof(struct ether_header)) ||
+ (temp > (MCLBYTES - ETHER_ALIGN))) {
+ DPRINTFN(1, "NULL frame detected at %d\n", x);
+ m = NULL;
+ /* silently ignore this frame */
+ continue;
+ } else if ((offset + temp) > actlen) {
+ DPRINTFN(1, "invalid frame "
+ "detected at %d\n", x);
+ m = NULL;
+ /* silently ignore this frame */
+ continue;
+ } else if (temp > (int)(MHLEN - ETHER_ALIGN)) {
+ m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ } else {
+ m = m_gethdr(M_NOWAIT, MT_DATA);
+ }
+
+ DPRINTFN(16, "frame %u, offset = %u, length = %u \n",
+ x, offset, temp);
+
+ /* check if we have a buffer */
+ if (m) {
+ m->m_len = m->m_pkthdr.len = temp + ETHER_ALIGN;
+ m_adj(m, ETHER_ALIGN);
+
+ usbd_copy_out(pc, offset, m->m_data, temp);
+
+ /* enqueue */
+ uether_rxmbuf(&sc->sc_ue, m, temp);
+
+ sumdata += temp;
+ } else {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ }
+ }
+
+ DPRINTFN(1, "Efficiency: %u/%u bytes\n", sumdata, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, sc->sc_ncm.rx_max);
+ usbd_xfer_set_frames(xfer, 1);
+ usbd_transfer_submit(xfer);
+ uether_rxflush(&sc->sc_ue); /* must be last */
+ break;
+
+ default: /* Error */
+ DPRINTFN(1, "error = %s\n",
+ usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+tr_stall:
+ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ usbd_xfer_set_frames(xfer, 0);
+ usbd_transfer_submit(xfer);
+ }
+ }
+ break;
+ }
+}
+#endif
diff --git a/sys/dev/usb/net/if_cdceem.c b/sys/dev/usb/net/if_cdceem.c
new file mode 100644
index 000000000000..b4978e5ea394
--- /dev/null
+++ b/sys/dev/usb/net/if_cdceem.c
@@ -0,0 +1,873 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2012 Ben Gray <bgray@freebsd.org>.
+ * Copyright (C) 2018 The FreeBSD Foundation.
+ * Copyright (c) 2019 Edward Tomasz Napierala <trasz@FreeBSD.org>
+ *
+ * This software was developed by Arshan Khanifar <arshankhanifar@gmail.com>
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * 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.
+ */
+
+/*
+ * Universal Serial Bus Communications Class Subclass Specification
+ * for Ethernet Emulation Model Devices:
+ *
+ * https://usb.org/sites/default/files/CDC_EEM10.pdf
+ */
+
+#include <sys/gsb_crc32.h>
+#include <sys/eventhandler.h>
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_cdc.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR cdceem_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_msctest.h>
+#include "usb_if.h"
+
+#include <dev/usb/net/usb_ethernet.h>
+
+#define CDCEEM_FRAMES_MAX 1
+#define CDCEEM_ECHO_MAX 1024
+
+#define CDCEEM_ECHO_PAYLOAD \
+ "ICH DALEKOPIS FALSZUJE GDY PROBY XQV NIE WYTRZYMUJE 1234567890"
+
+enum {
+ CDCEEM_BULK_RX,
+ CDCEEM_BULK_TX,
+ CDCEEM_N_TRANSFER,
+};
+
+struct cdceem_softc {
+ struct usb_ether sc_ue;
+ struct mtx sc_mtx;
+ int sc_flags;
+ struct usb_xfer *sc_xfer[CDCEEM_N_TRANSFER];
+ size_t sc_echo_len;
+ char sc_echo_buffer[CDCEEM_ECHO_MAX];
+};
+
+#define CDCEEM_SC_FLAGS_ECHO_RESPONSE_PENDING 0x1
+#define CDCEEM_SC_FLAGS_ECHO_PENDING 0x2
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, cdceem, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB CDC EEM");
+static int cdceem_debug = 1;
+SYSCTL_INT(_hw_usb_cdceem, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &cdceem_debug, 0, "Debug level");
+static int cdceem_send_echoes = 0;
+SYSCTL_INT(_hw_usb_cdceem, OID_AUTO, send_echoes, CTLFLAG_RWTUN,
+ &cdceem_send_echoes, 0, "Send an Echo command");
+static int cdceem_send_fake_crc = 0;
+SYSCTL_INT(_hw_usb_cdceem, OID_AUTO, send_fake_crc, CTLFLAG_RWTUN,
+ &cdceem_send_fake_crc, 0, "Use 0xdeadbeef instead of CRC");
+
+#define CDCEEM_DEBUG(S, X, ...) \
+ do { \
+ if (cdceem_debug > 1) { \
+ device_printf(S->sc_ue.ue_dev, "%s: " X "\n", \
+ __func__, ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define CDCEEM_WARN(S, X, ...) \
+ do { \
+ if (cdceem_debug > 0) { \
+ device_printf(S->sc_ue.ue_dev, \
+ "WARNING: %s: " X "\n", \
+ __func__, ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define CDCEEM_LOCK(X) mtx_lock(&(X)->sc_mtx)
+#define CDCEEM_UNLOCK(X) mtx_unlock(&(X)->sc_mtx)
+
+#define CDCEEM_TYPE_CMD (0x1 << 15)
+
+#define CDCEEM_CMD_MASK (0x7 << 11)
+
+#define CDCEEM_CMD_ECHO (0x0 << 11)
+#define CDCEEM_CMD_ECHO_RESPONSE (0x1 << 11)
+#define CDCEEM_CMD_SUSPEND_HINT (0x2 << 11)
+#define CDCEEM_CMD_RESPONSE_HINT (0x3 << 11)
+#define CDCEEM_CMD_RESPONSE_COMPLETE_HINT (0x4 << 11)
+#define CDCEEM_CMD_TICKLE (0x5 << 11)
+
+#define CDCEEM_CMD_RESERVED (0x1 << 14)
+
+#define CDCEEM_ECHO_LEN_MASK 0x3ff
+
+#define CDCEEM_DATA_CRC (0x1 << 14)
+#define CDCEEM_DATA_LEN_MASK 0x3fff
+
+static device_probe_t cdceem_probe;
+static device_attach_t cdceem_attach;
+static device_detach_t cdceem_detach;
+static device_suspend_t cdceem_suspend;
+static device_resume_t cdceem_resume;
+
+static usb_callback_t cdceem_bulk_write_callback;
+static usb_callback_t cdceem_bulk_read_callback;
+
+static uether_fn_t cdceem_attach_post;
+static uether_fn_t cdceem_init;
+static uether_fn_t cdceem_stop;
+static uether_fn_t cdceem_start;
+static uether_fn_t cdceem_setmulti;
+static uether_fn_t cdceem_setpromisc;
+
+static uint32_t cdceem_m_crc32(struct mbuf *, uint32_t, uint32_t);
+
+static const struct usb_config cdceem_config[CDCEEM_N_TRANSFER] = {
+ [CDCEEM_BULK_TX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .bufsize = 16 * (MCLBYTES + 16),
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = cdceem_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ .usb_mode = USB_MODE_DUAL,
+ },
+
+ [CDCEEM_BULK_RX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_RX,
+ .bufsize = 20480, /* bytes */
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = cdceem_bulk_read_callback,
+ .timeout = 0, /* no timeout */
+ .usb_mode = USB_MODE_DUAL,
+ },
+};
+
+static device_method_t cdceem_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, cdceem_probe),
+ DEVMETHOD(device_attach, cdceem_attach),
+ DEVMETHOD(device_detach, cdceem_detach),
+ DEVMETHOD(device_suspend, cdceem_suspend),
+ DEVMETHOD(device_resume, cdceem_resume),
+
+ DEVMETHOD_END
+};
+
+static driver_t cdceem_driver = {
+ .name = "cdceem",
+ .methods = cdceem_methods,
+ .size = sizeof(struct cdceem_softc),
+};
+
+static const STRUCT_USB_DUAL_ID cdceem_dual_devs[] = {
+ {USB_IFACE_CLASS(UICLASS_CDC),
+ USB_IFACE_SUBCLASS(UISUBCLASS_ETHERNET_EMULATION_MODEL),
+ 0},
+};
+
+DRIVER_MODULE(cdceem, uhub, cdceem_driver, NULL, NULL);
+MODULE_VERSION(cdceem, 1);
+MODULE_DEPEND(cdceem, uether, 1, 1, 1);
+MODULE_DEPEND(cdceem, usb, 1, 1, 1);
+MODULE_DEPEND(cdceem, ether, 1, 1, 1);
+USB_PNP_DUAL_INFO(cdceem_dual_devs);
+
+static const struct usb_ether_methods cdceem_ue_methods = {
+ .ue_attach_post = cdceem_attach_post,
+ .ue_start = cdceem_start,
+ .ue_init = cdceem_init,
+ .ue_stop = cdceem_stop,
+ .ue_setmulti = cdceem_setmulti,
+ .ue_setpromisc = cdceem_setpromisc,
+};
+
+static int
+cdceem_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa;
+ int error;
+
+ uaa = device_get_ivars(dev);
+ error = usbd_lookup_id_by_uaa(cdceem_dual_devs,
+ sizeof(cdceem_dual_devs), uaa);
+
+ return (error);
+}
+
+static void
+cdceem_attach_post(struct usb_ether *ue)
+{
+
+ return;
+}
+
+static int
+cdceem_attach(device_t dev)
+{
+ struct cdceem_softc *sc;
+ struct usb_ether *ue;
+ struct usb_attach_arg *uaa;
+ int error;
+ uint8_t iface_index;
+
+ sc = device_get_softc(dev);
+ ue = &sc->sc_ue;
+ uaa = device_get_ivars(dev);
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ /* Setup the endpoints. */
+ iface_index = 0;
+ error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer,
+ cdceem_config, CDCEEM_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error != 0) {
+ CDCEEM_WARN(sc,
+ "allocating USB transfers failed, error %d", error);
+ mtx_destroy(&sc->sc_mtx);
+ return (error);
+ }
+
+ /* Random MAC address. */
+ arc4rand(ue->ue_eaddr, ETHER_ADDR_LEN, 0);
+ ue->ue_eaddr[0] &= ~0x01; /* unicast */
+ ue->ue_eaddr[0] |= 0x02; /* locally administered */
+
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &cdceem_ue_methods;
+
+ error = uether_ifattach(ue);
+ if (error != 0) {
+ CDCEEM_WARN(sc, "could not attach interface, error %d", error);
+ usbd_transfer_unsetup(sc->sc_xfer, CDCEEM_N_TRANSFER);
+ mtx_destroy(&sc->sc_mtx);
+ return (error);
+ }
+
+ return (0);
+}
+
+static int
+cdceem_detach(device_t dev)
+{
+ struct cdceem_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ /* Stop all USB transfers first. */
+ usbd_transfer_unsetup(sc->sc_xfer, CDCEEM_N_TRANSFER);
+ uether_ifdetach(ue);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+cdceem_handle_cmd(struct usb_xfer *xfer, uint16_t hdr, int *offp)
+{
+ struct cdceem_softc *sc;
+ struct usb_page_cache *pc;
+ int actlen, off;
+ uint16_t pktlen;
+
+ off = *offp;
+ sc = usbd_xfer_softc(xfer);
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ if (hdr & CDCEEM_CMD_RESERVED) {
+ CDCEEM_WARN(sc, "received command header %#x "
+ "with Reserved bit set; ignoring", hdr);
+ return;
+ }
+
+ switch (hdr & CDCEEM_CMD_MASK) {
+ case CDCEEM_CMD_ECHO:
+ pktlen = hdr & CDCEEM_ECHO_LEN_MASK;
+ CDCEEM_DEBUG(sc, "received Echo, length %d", pktlen);
+
+ if (pktlen > (actlen - off)) {
+ CDCEEM_WARN(sc,
+ "bad Echo length %d, should be at most %d",
+ pktlen, actlen - off);
+ break;
+ }
+
+ if (pktlen > sizeof(sc->sc_echo_buffer)) {
+ CDCEEM_WARN(sc,
+ "Echo length %u too big, must be less than %zd",
+ pktlen, sizeof(sc->sc_echo_buffer));
+ break;
+ }
+
+ sc->sc_flags |= CDCEEM_SC_FLAGS_ECHO_RESPONSE_PENDING;
+ sc->sc_echo_len = pktlen;
+ usbd_copy_out(pc, off, sc->sc_echo_buffer, pktlen);
+ off += pktlen;
+ break;
+
+ case CDCEEM_CMD_ECHO_RESPONSE:
+ pktlen = hdr & CDCEEM_ECHO_LEN_MASK;
+ CDCEEM_DEBUG(sc, "received Echo Response, length %d", pktlen);
+
+ if (pktlen > (actlen - off)) {
+ CDCEEM_WARN(sc,
+ "bad Echo Response length %d, "
+ "should be at most %d",
+ pktlen, actlen - off);
+ break;
+ }
+
+ if (pktlen != sizeof(CDCEEM_ECHO_PAYLOAD)) {
+ CDCEEM_WARN(sc, "received Echo Response with bad "
+ "length %hu, should be %zd",
+ pktlen, sizeof(CDCEEM_ECHO_PAYLOAD));
+ break;
+ }
+
+ usbd_copy_out(pc, off, sc->sc_echo_buffer, pktlen);
+ off += pktlen;
+
+ if (memcmp(sc->sc_echo_buffer, CDCEEM_ECHO_PAYLOAD,
+ sizeof(CDCEEM_ECHO_PAYLOAD)) != 0) {
+ CDCEEM_WARN(sc,
+ "received Echo Response payload does not match");
+ } else {
+ CDCEEM_DEBUG(sc, "received Echo Response is valid");
+ }
+ break;
+
+ case CDCEEM_CMD_SUSPEND_HINT:
+ CDCEEM_DEBUG(sc, "received SuspendHint; ignoring");
+ break;
+
+ case CDCEEM_CMD_RESPONSE_HINT:
+ CDCEEM_DEBUG(sc, "received ResponseHint; ignoring");
+ break;
+
+ case CDCEEM_CMD_RESPONSE_COMPLETE_HINT:
+ CDCEEM_DEBUG(sc, "received ResponseCompleteHint; ignoring");
+ break;
+
+ case CDCEEM_CMD_TICKLE:
+ CDCEEM_DEBUG(sc, "received Tickle; ignoring");
+ break;
+
+ default:
+ CDCEEM_WARN(sc,
+ "received unknown command %u, header %#x; ignoring",
+ (hdr & CDCEEM_CMD_MASK >> 11), hdr);
+ break;
+ }
+
+ *offp = off;
+}
+
+static void
+cdceem_handle_data(struct usb_xfer *xfer, uint16_t hdr, int *offp)
+{
+ struct cdceem_softc *sc;
+ struct usb_page_cache *pc;
+ struct usb_ether *ue;
+ if_t ifp;
+ struct mbuf *m;
+ uint32_t computed_crc, received_crc;
+ int pktlen;
+ int actlen;
+ int off;
+
+ off = *offp;
+ sc = usbd_xfer_softc(xfer);
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ue = &sc->sc_ue;
+ ifp = uether_getifp(ue);
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ pktlen = hdr & CDCEEM_DATA_LEN_MASK;
+ CDCEEM_DEBUG(sc, "received Data, CRC %s, length %d",
+ (hdr & CDCEEM_DATA_CRC) ? "valid" : "absent",
+ pktlen);
+
+ if (pktlen < (ETHER_HDR_LEN + 4)) {
+ CDCEEM_WARN(sc,
+ "bad ethernet frame length %d, should be at least %d",
+ pktlen, ETHER_HDR_LEN);
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ return;
+ }
+
+ if (pktlen > (actlen - off)) {
+ CDCEEM_WARN(sc,
+ "bad ethernet frame length %d, should be at most %d",
+ pktlen, actlen - off);
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ return;
+ }
+
+ m = uether_newbuf();
+ if (m == NULL) {
+ CDCEEM_WARN(sc, "uether_newbuf() failed");
+ if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
+ return;
+ }
+
+ pktlen -= 4; /* Subtract the CRC. */
+
+ if (pktlen > m->m_len) {
+ CDCEEM_WARN(sc, "buffer too small %d vs %d bytes",
+ pktlen, m->m_len);
+ if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
+ m_freem(m);
+ return;
+ }
+ usbd_copy_out(pc, off, mtod(m, uint8_t *), pktlen);
+ off += pktlen;
+
+ usbd_copy_out(pc, off, &received_crc, sizeof(received_crc));
+ off += sizeof(received_crc);
+
+ if (hdr & CDCEEM_DATA_CRC) {
+ computed_crc = cdceem_m_crc32(m, 0, pktlen);
+ } else {
+ computed_crc = be32toh(0xdeadbeef);
+ }
+
+ if (received_crc != computed_crc) {
+ CDCEEM_WARN(sc,
+ "received Data packet with wrong CRC %#x, expected %#x",
+ received_crc, computed_crc);
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ m_freem(m);
+ return;
+ } else {
+ CDCEEM_DEBUG(sc, "received correct CRC %#x", received_crc);
+ }
+
+ uether_rxmbuf(ue, m, pktlen);
+ *offp = off;
+}
+
+static void
+cdceem_bulk_read_callback(struct usb_xfer *xfer, usb_error_t usb_error)
+{
+ struct cdceem_softc *sc;
+ struct usb_page_cache *pc;
+ int actlen, aframes, off;
+ uint16_t hdr;
+
+ sc = usbd_xfer_softc(xfer);
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ CDCEEM_DEBUG(sc,
+ "received %u bytes in %u frames", actlen, aframes);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ off = 0;
+
+ while ((off + sizeof(hdr)) <= actlen) {
+ usbd_copy_out(pc, off, &hdr, sizeof(hdr));
+ CDCEEM_DEBUG(sc, "hdr = %#x", hdr);
+ off += sizeof(hdr);
+
+ if (hdr == 0) {
+ CDCEEM_DEBUG(sc, "received Zero Length EEM");
+ continue;
+ }
+
+ hdr = le16toh(hdr);
+
+ if ((hdr & CDCEEM_TYPE_CMD) != 0) {
+ cdceem_handle_cmd(xfer, hdr, &off);
+ } else {
+ cdceem_handle_data(xfer, hdr, &off);
+ }
+
+ KASSERT(off <= actlen,
+ ("%s: went past the buffer, off %d, actlen %d",
+ __func__, off, actlen));
+ }
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+ CDCEEM_DEBUG(sc, "setup");
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ uether_rxflush(&sc->sc_ue);
+ break;
+
+ default:
+ CDCEEM_WARN(sc, "USB_ST_ERROR: %s", usbd_errstr(usb_error));
+
+ if (usb_error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+cdceem_send_echo(struct usb_xfer *xfer, int *offp)
+{
+ struct cdceem_softc *sc;
+ struct usb_page_cache *pc;
+ int maxlen __diagused, off;
+ uint16_t hdr;
+
+ off = *offp;
+ sc = usbd_xfer_softc(xfer);
+ pc = usbd_xfer_get_frame(xfer, 0);
+ maxlen = usbd_xfer_max_len(xfer);
+
+ CDCEEM_DEBUG(sc, "sending Echo, length %zd",
+ sizeof(CDCEEM_ECHO_PAYLOAD));
+
+ KASSERT(off + sizeof(hdr) + sizeof(CDCEEM_ECHO_PAYLOAD) < maxlen,
+ ("%s: out of space; have %d, need %zd", __func__, maxlen,
+ off + sizeof(hdr) + sizeof(CDCEEM_ECHO_PAYLOAD)));
+
+ hdr = 0;
+ hdr |= CDCEEM_TYPE_CMD;
+ hdr |= CDCEEM_CMD_ECHO;
+ hdr |= sizeof(CDCEEM_ECHO_PAYLOAD);
+ CDCEEM_DEBUG(sc, "hdr = %#x", hdr);
+ hdr = htole16(hdr);
+
+ usbd_copy_in(pc, off, &hdr, sizeof(hdr));
+ off += sizeof(hdr);
+
+ usbd_copy_in(pc, off, CDCEEM_ECHO_PAYLOAD,
+ sizeof(CDCEEM_ECHO_PAYLOAD));
+ off += sizeof(CDCEEM_ECHO_PAYLOAD);
+
+ sc->sc_flags &= ~CDCEEM_SC_FLAGS_ECHO_PENDING;
+
+ *offp = off;
+}
+
+static void
+cdceem_send_echo_response(struct usb_xfer *xfer, int *offp)
+{
+ struct cdceem_softc *sc;
+ struct usb_page_cache *pc;
+ int maxlen __diagused, off;
+ uint16_t hdr;
+
+ off = *offp;
+ sc = usbd_xfer_softc(xfer);
+ pc = usbd_xfer_get_frame(xfer, 0);
+ maxlen = usbd_xfer_max_len(xfer);
+
+ KASSERT(off + sizeof(hdr) + sc->sc_echo_len < maxlen,
+ ("%s: out of space; have %d, need %zd", __func__, maxlen,
+ off + sizeof(hdr) + sc->sc_echo_len));
+
+ CDCEEM_DEBUG(sc, "sending Echo Response, length %zd", sc->sc_echo_len);
+
+ hdr = 0;
+ hdr |= CDCEEM_TYPE_CMD;
+ hdr |= CDCEEM_CMD_ECHO_RESPONSE;
+ hdr |= sc->sc_echo_len;
+ CDCEEM_DEBUG(sc, "hdr = %#x", hdr);
+ hdr = htole16(hdr);
+
+ usbd_copy_in(pc, off, &hdr, sizeof(hdr));
+ off += sizeof(hdr);
+
+ usbd_copy_in(pc, off, sc->sc_echo_buffer, sc->sc_echo_len);
+ off += sc->sc_echo_len;
+
+ sc->sc_flags &= ~CDCEEM_SC_FLAGS_ECHO_RESPONSE_PENDING;
+ sc->sc_echo_len = 0;
+
+ *offp = off;
+}
+
+static void
+cdceem_send_data(struct usb_xfer *xfer, int *offp)
+{
+ struct cdceem_softc *sc;
+ struct usb_page_cache *pc;
+ if_t ifp;
+ struct mbuf *m;
+ int maxlen __diagused, off;
+ uint32_t crc;
+ uint16_t hdr;
+
+ off = *offp;
+ sc = usbd_xfer_softc(xfer);
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ifp = uether_getifp(&sc->sc_ue);
+ maxlen = usbd_xfer_max_len(xfer);
+
+ m = if_dequeue(ifp);
+ if (m == NULL) {
+ CDCEEM_DEBUG(sc, "no Data packets to send");
+ return;
+ }
+
+ KASSERT((m->m_pkthdr.len & CDCEEM_DATA_LEN_MASK) == m->m_pkthdr.len,
+ ("%s: packet too long: %d, should be %d\n", __func__,
+ m->m_pkthdr.len, m->m_pkthdr.len & CDCEEM_DATA_LEN_MASK));
+ KASSERT(off + sizeof(hdr) + m->m_pkthdr.len + 4 < maxlen,
+ ("%s: out of space; have %d, need %zd", __func__, maxlen,
+ off + sizeof(hdr) + m->m_pkthdr.len + 4));
+
+ CDCEEM_DEBUG(sc, "sending Data, length %d + 4", m->m_pkthdr.len);
+
+ hdr = 0;
+ if (!cdceem_send_fake_crc)
+ hdr |= CDCEEM_DATA_CRC;
+ hdr |= (m->m_pkthdr.len + 4); /* +4 for CRC */
+ CDCEEM_DEBUG(sc, "hdr = %#x", hdr);
+ hdr = htole16(hdr);
+
+ usbd_copy_in(pc, off, &hdr, sizeof(hdr));
+ off += sizeof(hdr);
+
+ usbd_m_copy_in(pc, off, m, 0, m->m_pkthdr.len);
+ off += m->m_pkthdr.len;
+
+ if (cdceem_send_fake_crc) {
+ crc = htobe32(0xdeadbeef);
+ } else {
+ crc = cdceem_m_crc32(m, 0, m->m_pkthdr.len);
+ }
+ CDCEEM_DEBUG(sc, "CRC = %#x", crc);
+
+ usbd_copy_in(pc, off, &crc, sizeof(crc));
+ off += sizeof(crc);
+
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /*
+ * If there's a BPF listener, bounce a copy of this frame to it.
+ */
+ BPF_MTAP(ifp, m);
+ m_freem(m);
+
+ *offp = off;
+}
+
+static void
+cdceem_bulk_write_callback(struct usb_xfer *xfer, usb_error_t usb_error)
+{
+ struct cdceem_softc *sc;
+ if_t ifp;
+ int actlen, aframes, maxlen __diagused, off;
+
+ sc = usbd_xfer_softc(xfer);
+ maxlen = usbd_xfer_max_len(xfer);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+ CDCEEM_DEBUG(sc, "transferred %u bytes in %u frames",
+ actlen, aframes);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+ CDCEEM_DEBUG(sc, "setup");
+tr_setup:
+
+ off = 0;
+ usbd_xfer_set_frame_offset(xfer, 0, 0);
+
+ if (sc->sc_flags & CDCEEM_SC_FLAGS_ECHO_PENDING) {
+ cdceem_send_echo(xfer, &off);
+ } else if (sc->sc_flags & CDCEEM_SC_FLAGS_ECHO_RESPONSE_PENDING) {
+ cdceem_send_echo_response(xfer, &off);
+ } else {
+ cdceem_send_data(xfer, &off);
+ }
+
+ KASSERT(off <= maxlen,
+ ("%s: went past the buffer, off %d, maxlen %d",
+ __func__, off, maxlen));
+
+ if (off > 0) {
+ CDCEEM_DEBUG(sc, "starting transfer, length %d", off);
+ usbd_xfer_set_frame_len(xfer, 0, off);
+ usbd_transfer_submit(xfer);
+ } else {
+ CDCEEM_DEBUG(sc, "nothing to transfer");
+ }
+
+ break;
+
+ default:
+ CDCEEM_WARN(sc, "USB_ST_ERROR: %s", usbd_errstr(usb_error));
+
+ ifp = uether_getifp(&sc->sc_ue);
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+
+ if (usb_error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static int32_t
+cdceem_m_crc32_cb(void *arg, void *src, uint32_t count)
+{
+ uint32_t *p_crc = arg;
+
+ *p_crc = crc32_raw(src, count, *p_crc);
+ return (0);
+}
+
+static uint32_t
+cdceem_m_crc32(struct mbuf *m, uint32_t src_offset, uint32_t src_len)
+{
+ uint32_t crc = 0xFFFFFFFF;
+
+ m_apply(m, src_offset, src_len, cdceem_m_crc32_cb, &crc);
+ return (crc ^ 0xFFFFFFFF);
+}
+
+static void
+cdceem_start(struct usb_ether *ue)
+{
+ struct cdceem_softc *sc;
+
+ sc = uether_getsc(ue);
+
+ /*
+ * Start the USB transfers, if not already started.
+ */
+ usbd_transfer_start(sc->sc_xfer[CDCEEM_BULK_RX]);
+ usbd_transfer_start(sc->sc_xfer[CDCEEM_BULK_TX]);
+}
+
+static void
+cdceem_init(struct usb_ether *ue)
+{
+ struct cdceem_softc *sc;
+ if_t ifp;
+
+ sc = uether_getsc(ue);
+ ifp = uether_getifp(ue);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+
+ if (cdceem_send_echoes)
+ sc->sc_flags = CDCEEM_SC_FLAGS_ECHO_PENDING;
+ else
+ sc->sc_flags = 0;
+
+ /*
+ * Stall data write direction, which depends on USB mode.
+ *
+ * Some USB host stacks (e.g. Mac OS X) don't clears stall
+ * bit as it should, so set it in our host mode only.
+ */
+ if (usbd_get_mode(sc->sc_ue.ue_udev) == USB_MODE_HOST)
+ usbd_xfer_set_stall(sc->sc_xfer[CDCEEM_BULK_TX]);
+
+ cdceem_start(ue);
+}
+
+static void
+cdceem_stop(struct usb_ether *ue)
+{
+ struct cdceem_softc *sc;
+ if_t ifp;
+
+ sc = uether_getsc(ue);
+ ifp = uether_getifp(ue);
+
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+
+ usbd_transfer_stop(sc->sc_xfer[CDCEEM_BULK_RX]);
+ usbd_transfer_stop(sc->sc_xfer[CDCEEM_BULK_TX]);
+}
+
+static void
+cdceem_setmulti(struct usb_ether *ue)
+{
+ /* no-op */
+ return;
+}
+
+static void
+cdceem_setpromisc(struct usb_ether *ue)
+{
+ /* no-op */
+ return;
+}
+
+static int
+cdceem_suspend(device_t dev)
+{
+ struct cdceem_softc *sc = device_get_softc(dev);
+
+ CDCEEM_DEBUG(sc, "go");
+ return (0);
+}
+
+static int
+cdceem_resume(device_t dev)
+{
+ struct cdceem_softc *sc = device_get_softc(dev);
+
+ CDCEEM_DEBUG(sc, "go");
+ return (0);
+}
diff --git a/sys/dev/usb/net/if_cdcereg.h b/sys/dev/usb/net/if_cdcereg.h
new file mode 100644
index 000000000000..dcc8e0a816d0
--- /dev/null
+++ b/sys/dev/usb/net/if_cdcereg.h
@@ -0,0 +1,123 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * 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.
+ */
+
+#ifndef _USB_IF_CDCEREG_H_
+#define _USB_IF_CDCEREG_H_
+
+#define CDCE_BIT(x) (1 << (x))
+
+#define CDCE_FRAMES_MAX 8 /* units */
+#define CDCE_IND_SIZE_MAX 32 /* bytes */
+
+#define CDCE_NCM_TX_MINLEN 512 /* bytes, must be power of two */
+#define CDCE_NCM_TX_MAXLEN (16384 + 4) /* bytes, must be short terminated */
+#define CDCE_NCM_TX_FRAMES_MAX 8 /* units */
+
+#define CDCE_NCM_RX_MAXLEN (1UL << 14) /* bytes */
+#define CDCE_NCM_RX_FRAMES_MAX 1 /* units */
+
+#define CDCE_NCM_SUBFRAMES_MAX 32 /* units */
+
+#define CDCE_NCM_ALIGN(rem,off,mod) \
+ ((uint32_t)(((uint32_t)(rem)) - \
+ ((uint32_t)((-(uint32_t)(off)) & (-(uint32_t)(mod))))))
+
+#ifndef CDCE_HAVE_NCM
+#define CDCE_HAVE_NCM 1
+#endif
+
+enum {
+ CDCE_BULK_RX,
+ CDCE_BULK_TX,
+ CDCE_INTR_RX,
+ CDCE_INTR_TX,
+ CDCE_N_TRANSFER,
+};
+
+struct cdce_ncm {
+ struct usb_ncm16_hdr hdr;
+ struct usb_ncm16_dpt dpt;
+ struct usb_ncm16_dp dp[CDCE_NCM_SUBFRAMES_MAX];
+ uint32_t rx_max;
+ uint32_t tx_max;
+ uint16_t tx_remainder;
+ uint16_t tx_modulus;
+ uint16_t tx_struct_align;
+ uint16_t tx_seq;
+ uint16_t tx_nframe;
+};
+
+struct cdce_softc {
+ struct usb_ether sc_ue;
+ struct mtx sc_mtx;
+#if CDCE_HAVE_NCM
+ struct cdce_ncm sc_ncm;
+#endif
+ struct usb_xfer *sc_xfer[CDCE_N_TRANSFER];
+ struct mbuf *sc_rx_buf[CDCE_FRAMES_MAX];
+ struct mbuf *sc_tx_buf[CDCE_FRAMES_MAX];
+
+ struct ifmedia sc_media;
+
+ int sc_flags;
+#define CDCE_FLAG_ZAURUS 0x0001
+#define CDCE_FLAG_NO_UNION 0x0002
+#define CDCE_FLAG_RX_DATA 0x0010
+#define CDCE_FLAG_VLAN 0x0020
+
+ uint8_t sc_eaddr_str_index;
+ uint8_t sc_ifaces_index[2];
+ uint8_t sc_notify_state;
+#define CDCE_NOTIFY_NETWORK_CONNECTION 0
+#define CDCE_NOTIFY_SPEED_CHANGE 1
+#define CDCE_NOTIFY_DONE 2
+};
+
+/*
+ * Taken from USB CDC Subclass Specification for Ethernet Devices v1.2,
+ * section 6.2.4.
+ */
+
+#define CDC_SET_ETHERNET_PACKET_FILTER 0x43 /* Command code. */
+
+#define CDC_PACKET_TYPE_PROMISC CDCE_BIT(0)
+#define CDC_PACKET_TYPE_ALL_MULTICAST CDCE_BIT(1) /* Allmulti. */
+#define CDC_PACKET_TYPE_DIRECTED CDCE_BIT(2) /* Filter unicast by mac. */
+#define CDC_PACKET_TYPE_BROADCAST CDCE_BIT(3)
+#define CDC_PACKET_TYPE_MULTICAST CDCE_BIT(4) /* Multicast filtering, not supported. */
+
+#define CDCE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define CDCE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define CDCE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t)
+#endif /* _USB_IF_CDCEREG_H_ */
diff --git a/sys/dev/usb/net/if_cue.c b/sys/dev/usb/net/if_cue.c
new file mode 100644
index 000000000000..a65bafee066f
--- /dev/null
+++ b/sys/dev/usb/net/if_cue.c
@@ -0,0 +1,654 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * 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.
+ */
+
+/*
+ * 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 transferred using a single bulk
+ * transaction, which helps performance a great deal.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR cue_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/net/usb_ethernet.h>
+#include <dev/usb/net/if_cuereg.h>
+
+/*
+ * Various supported device vendors/products.
+ */
+
+/* Belkin F5U111 adapter covered by NETMATE entry */
+
+static const STRUCT_USB_HOST_ID cue_devs[] = {
+#define CUE_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) }
+ CUE_DEV(CATC, NETMATE),
+ CUE_DEV(CATC, NETMATE2),
+ CUE_DEV(SMARTBRIDGES, SMARTLINK),
+#undef CUE_DEV
+};
+
+/* prototypes */
+
+static device_probe_t cue_probe;
+static device_attach_t cue_attach;
+static device_detach_t cue_detach;
+
+static usb_callback_t cue_bulk_read_callback;
+static usb_callback_t cue_bulk_write_callback;
+
+static uether_fn_t cue_attach_post;
+static uether_fn_t cue_init;
+static uether_fn_t cue_stop;
+static uether_fn_t cue_start;
+static uether_fn_t cue_tick;
+static uether_fn_t cue_setmulti;
+static uether_fn_t cue_setpromisc;
+
+static uint8_t cue_csr_read_1(struct cue_softc *, uint16_t);
+static uint16_t cue_csr_read_2(struct cue_softc *, uint8_t);
+static int cue_csr_write_1(struct cue_softc *, uint16_t, uint16_t);
+static int cue_mem(struct cue_softc *, uint8_t, uint16_t, void *, int);
+static int cue_getmac(struct cue_softc *, void *);
+static uint32_t cue_mchash(const uint8_t *);
+static void cue_reset(struct cue_softc *);
+
+#ifdef USB_DEBUG
+static int cue_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, cue, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB cue");
+SYSCTL_INT(_hw_usb_cue, OID_AUTO, debug, CTLFLAG_RWTUN, &cue_debug, 0,
+ "Debug level");
+#endif
+
+static const struct usb_config cue_config[CUE_N_TRANSFER] = {
+ [CUE_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = (MCLBYTES + 2),
+ .flags = {.pipe_bof = 1,},
+ .callback = cue_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ },
+
+ [CUE_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = (MCLBYTES + 2),
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = cue_bulk_read_callback,
+ },
+};
+
+static device_method_t cue_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, cue_probe),
+ DEVMETHOD(device_attach, cue_attach),
+ DEVMETHOD(device_detach, cue_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t cue_driver = {
+ .name = "cue",
+ .methods = cue_methods,
+ .size = sizeof(struct cue_softc),
+};
+
+DRIVER_MODULE(cue, uhub, cue_driver, NULL, NULL);
+MODULE_DEPEND(cue, uether, 1, 1, 1);
+MODULE_DEPEND(cue, usb, 1, 1, 1);
+MODULE_DEPEND(cue, ether, 1, 1, 1);
+MODULE_VERSION(cue, 1);
+USB_PNP_HOST_INFO(cue_devs);
+
+static const struct usb_ether_methods cue_ue_methods = {
+ .ue_attach_post = cue_attach_post,
+ .ue_start = cue_start,
+ .ue_init = cue_init,
+ .ue_stop = cue_stop,
+ .ue_tick = cue_tick,
+ .ue_setmulti = cue_setmulti,
+ .ue_setpromisc = cue_setpromisc,
+};
+
+#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 uint8_t
+cue_csr_read_1(struct cue_softc *sc, uint16_t reg)
+{
+ struct usb_device_request req;
+ uint8_t val;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = CUE_CMD_READREG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 1);
+
+ if (uether_do_request(&sc->sc_ue, &req, &val, 1000)) {
+ /* ignore any errors */
+ }
+ return (val);
+}
+
+static uint16_t
+cue_csr_read_2(struct cue_softc *sc, uint8_t reg)
+{
+ struct usb_device_request req;
+ uint16_t val;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = CUE_CMD_READREG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 2);
+
+ (void)uether_do_request(&sc->sc_ue, &req, &val, 1000);
+ return (le16toh(val));
+}
+
+static int
+cue_csr_write_1(struct cue_softc *sc, uint16_t reg, uint16_t val)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = CUE_CMD_WRITEREG;
+ USETW(req.wValue, val);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 0);
+
+ return (uether_do_request(&sc->sc_ue, &req, NULL, 1000));
+}
+
+static int
+cue_mem(struct cue_softc *sc, uint8_t cmd, uint16_t addr, void *buf, int len)
+{
+ struct usb_device_request req;
+
+ 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);
+
+ return (uether_do_request(&sc->sc_ue, &req, buf, 1000));
+}
+
+static int
+cue_getmac(struct cue_softc *sc, void *buf)
+{
+ struct usb_device_request req;
+
+ 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);
+
+ return (uether_do_request(&sc->sc_ue, &req, buf, 1000));
+}
+
+#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_setpromisc(struct usb_ether *ue)
+{
+ struct cue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ CUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* if we want promiscuous mode, set the allframes bit */
+ if (if_getflags(ifp) & IFF_PROMISC)
+ CUE_SETBIT(sc, CUE_ETHCTL, CUE_ETHCTL_PROMISC);
+ else
+ CUE_CLRBIT(sc, CUE_ETHCTL, CUE_ETHCTL_PROMISC);
+
+ /* write multicast hash-bits */
+ cue_setmulti(ue);
+}
+
+static u_int
+cue_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
+{
+ uint8_t *hashtbl = arg;
+ uint32_t h;
+
+ h = cue_mchash(LLADDR(sdl));
+ hashtbl[h >> 3] |= 1 << (h & 0x7);
+
+ return (1);
+}
+
+static void
+cue_setmulti(struct usb_ether *ue)
+{
+ struct cue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ uint32_t h, i;
+ uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ CUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (if_getflags(ifp) & IFF_ALLMULTI || if_getflags(ifp) & IFF_PROMISC) {
+ for (i = 0; i < 8; i++)
+ hashtbl[i] = 0xff;
+ cue_mem(sc, CUE_CMD_WRITESRAM, CUE_MCAST_TABLE_ADDR,
+ &hashtbl, 8);
+ return;
+ }
+
+ /* now program new ones */
+ if_foreach_llmaddr(ifp, cue_hash_maddr, hashtbl);
+
+ /*
+ * Also include the broadcast address in the filter
+ * so we can receive broadcast frames.
+ */
+ if (if_getflags(ifp) & IFF_BROADCAST) {
+ h = cue_mchash(if_getbroadcastaddr(ifp));
+ hashtbl[h >> 3] |= 1 << (h & 0x7);
+ }
+
+ cue_mem(sc, CUE_CMD_WRITESRAM, CUE_MCAST_TABLE_ADDR, &hashtbl, 8);
+}
+
+static void
+cue_reset(struct cue_softc *sc)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = CUE_CMD_RESET;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+
+ if (uether_do_request(&sc->sc_ue, &req, NULL, 1000)) {
+ /* ignore any errors */
+ }
+
+ /*
+ * wait a little while for the chip to get its brains in order:
+ */
+ uether_pause(&sc->sc_ue, hz / 100);
+}
+
+static void
+cue_attach_post(struct usb_ether *ue)
+{
+ struct cue_softc *sc = uether_getsc(ue);
+
+ cue_getmac(sc, ue->ue_eaddr);
+}
+
+static int
+cue_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != CUE_CONFIG_IDX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != CUE_IFACE_IDX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(cue_devs, sizeof(cue_devs), uaa));
+}
+
+/*
+ * Attach the interface. Allocate softc structures, do ifmedia
+ * setup and ethernet/BPF attach.
+ */
+static int
+cue_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct cue_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+ uint8_t iface_index;
+ int error;
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ iface_index = CUE_IFACE_IDX;
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, cue_config, CUE_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "allocating USB transfers failed\n");
+ goto detach;
+ }
+
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &cue_ue_methods;
+
+ error = uether_ifattach(ue);
+ if (error) {
+ device_printf(dev, "could not attach interface\n");
+ goto detach;
+ }
+ return (0); /* success */
+
+detach:
+ cue_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+cue_detach(device_t dev)
+{
+ struct cue_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ usbd_transfer_unsetup(sc->sc_xfer, CUE_N_TRANSFER);
+ uether_ifdetach(ue);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+cue_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct cue_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_ether *ue = &sc->sc_ue;
+ if_t ifp = uether_getifp(ue);
+ struct usb_page_cache *pc;
+ uint8_t buf[2];
+ int len;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ if (actlen <= (int)(2 + sizeof(struct ether_header))) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, buf, 2);
+ actlen -= 2;
+ len = buf[0] | (buf[1] << 8);
+ len = min(actlen, len);
+
+ uether_rxbuf(ue, pc, 2, len);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ uether_rxflush(ue);
+ return;
+
+ default: /* Error */
+ DPRINTF("bulk read error, %s\n",
+ usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+cue_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct cue_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ uint8_t buf[2];
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "transfer complete\n");
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ m = if_dequeue(ifp);
+
+ if (m == NULL)
+ return;
+ if (m->m_pkthdr.len > MCLBYTES)
+ m->m_pkthdr.len = MCLBYTES;
+ usbd_xfer_set_frame_len(xfer, 0, (m->m_pkthdr.len + 2));
+
+ /* the first two bytes are the frame length */
+
+ buf[0] = (uint8_t)(m->m_pkthdr.len);
+ buf[1] = (uint8_t)(m->m_pkthdr.len >> 8);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, buf, 2);
+ usbd_m_copy_in(pc, 2, m, 0, m->m_pkthdr.len);
+
+ /*
+ * If there's a BPF listener, bounce a copy of this frame
+ * to him.
+ */
+ BPF_MTAP(ifp, m);
+
+ m_freem(m);
+
+ usbd_transfer_submit(xfer);
+
+ return;
+
+ default: /* Error */
+ DPRINTFN(11, "transfer error, %s\n",
+ usbd_errstr(error));
+
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+cue_tick(struct usb_ether *ue)
+{
+ struct cue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ CUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_inc_counter(ifp, IFCOUNTER_COLLISIONS, cue_csr_read_2(sc, CUE_TX_SINGLECOLL));
+ if_inc_counter(ifp, IFCOUNTER_COLLISIONS, cue_csr_read_2(sc, CUE_TX_MULTICOLL));
+ if_inc_counter(ifp, IFCOUNTER_COLLISIONS, cue_csr_read_2(sc, CUE_TX_EXCESSCOLL));
+
+ if (cue_csr_read_2(sc, CUE_RX_FRAMEERR))
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+}
+
+static void
+cue_start(struct usb_ether *ue)
+{
+ struct cue_softc *sc = uether_getsc(ue);
+
+ /*
+ * start the USB transfers, if not already started:
+ */
+ usbd_transfer_start(sc->sc_xfer[CUE_BULK_DT_RD]);
+ usbd_transfer_start(sc->sc_xfer[CUE_BULK_DT_WR]);
+}
+
+static void
+cue_init(struct usb_ether *ue)
+{
+ struct cue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ int i;
+
+ CUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ /*
+ * Cancel pending I/O and free all RX/TX buffers.
+ */
+ cue_stop(ue);
+#if 0
+ cue_reset(sc);
+#endif
+ /* Set MAC address */
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ cue_csr_write_1(sc, CUE_PAR0 - i, if_getlladdr(ifp)[i]);
+
+ /* Enable RX logic. */
+ cue_csr_write_1(sc, CUE_ETHCTL, CUE_ETHCTL_RX_ON | CUE_ETHCTL_MCAST_ON);
+
+ /* Load the multicast filter */
+ cue_setpromisc(ue);
+
+ /*
+ * 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);
+
+ usbd_xfer_set_stall(sc->sc_xfer[CUE_BULK_DT_WR]);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+ cue_start(ue);
+}
+
+/*
+ * Stop the adapter and free any mbufs allocated to the
+ * RX and TX lists.
+ */
+static void
+cue_stop(struct usb_ether *ue)
+{
+ struct cue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ CUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+
+ /*
+ * stop all the transfers, if not already stopped:
+ */
+ usbd_transfer_stop(sc->sc_xfer[CUE_BULK_DT_WR]);
+ usbd_transfer_stop(sc->sc_xfer[CUE_BULK_DT_RD]);
+
+ cue_csr_write_1(sc, CUE_ETHCTL, 0);
+ cue_reset(sc);
+}
diff --git a/sys/dev/usb/net/if_cuereg.h b/sys/dev/usb/net/if_cuereg.h
new file mode 100644
index 000000000000..bb1e99c32e04
--- /dev/null
+++ b/sys/dev/usb/net/if_cuereg.h
@@ -0,0 +1,132 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * 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.
+ */
+
+/*
+ * 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_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_IDX 0 /* config number 1 */
+#define CUE_IFACE_IDX 0
+
+/* The interrupt endpoint is currently unused by the CATC part. */
+enum {
+ CUE_BULK_DT_WR,
+ CUE_BULK_DT_RD,
+ CUE_N_TRANSFER,
+};
+
+struct cue_softc {
+ struct usb_ether sc_ue;
+ struct mtx sc_mtx;
+ struct usb_xfer *sc_xfer[CUE_N_TRANSFER];
+
+ int sc_flags;
+#define CUE_FLAG_LINK 0x0001 /* got a link */
+};
+
+#define CUE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define CUE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define CUE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t)
diff --git a/sys/dev/usb/net/if_ipheth.c b/sys/dev/usb/net/if_ipheth.c
new file mode 100644
index 000000000000..f70113c53eb4
--- /dev/null
+++ b/sys/dev/usb/net/if_ipheth.c
@@ -0,0 +1,541 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 2009 Diego Giagio. 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.
+ */
+
+/*
+ * Thanks to Diego Giagio for figuring out the programming details for
+ * the Apple iPhone Ethernet driver.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR ipheth_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/net/usb_ethernet.h>
+#include <dev/usb/net/if_iphethvar.h>
+
+static device_probe_t ipheth_probe;
+static device_attach_t ipheth_attach;
+static device_detach_t ipheth_detach;
+
+static usb_callback_t ipheth_bulk_write_callback;
+static usb_callback_t ipheth_bulk_read_callback;
+
+static uether_fn_t ipheth_attach_post;
+static uether_fn_t ipheth_tick;
+static uether_fn_t ipheth_init;
+static uether_fn_t ipheth_stop;
+static uether_fn_t ipheth_start;
+static uether_fn_t ipheth_setmulti;
+static uether_fn_t ipheth_setpromisc;
+
+#ifdef USB_DEBUG
+static int ipheth_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ipheth, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB iPhone ethernet");
+SYSCTL_INT(_hw_usb_ipheth, OID_AUTO, debug, CTLFLAG_RWTUN, &ipheth_debug, 0, "Debug level");
+#endif
+
+static const struct usb_config ipheth_config[IPHETH_N_TRANSFER] = {
+ [IPHETH_BULK_RX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_RX,
+ .frames = IPHETH_RX_FRAMES_MAX,
+ .bufsize = (IPHETH_RX_FRAMES_MAX * MCLBYTES),
+ .flags = {.short_frames_ok = 1,.short_xfer_ok = 1,.ext_buffer = 1,},
+ .callback = ipheth_bulk_read_callback,
+ .timeout = 0, /* no timeout */
+ },
+
+ [IPHETH_BULK_TX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .frames = IPHETH_TX_FRAMES_MAX,
+ .bufsize = (IPHETH_TX_FRAMES_MAX * IPHETH_BUF_SIZE),
+ .flags = {.force_short_xfer = 1,},
+ .callback = ipheth_bulk_write_callback,
+ .timeout = IPHETH_TX_TIMEOUT,
+ },
+};
+
+static device_method_t ipheth_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, ipheth_probe),
+ DEVMETHOD(device_attach, ipheth_attach),
+ DEVMETHOD(device_detach, ipheth_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t ipheth_driver = {
+ .name = "ipheth",
+ .methods = ipheth_methods,
+ .size = sizeof(struct ipheth_softc),
+};
+
+static const STRUCT_USB_HOST_ID ipheth_devs[] = {
+#if 0
+ {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE,
+ IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS,
+ IPHETH_USBINTF_PROTO)},
+ {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_3G,
+ IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS,
+ IPHETH_USBINTF_PROTO)},
+ {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_3GS,
+ IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS,
+ IPHETH_USBINTF_PROTO)},
+ {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_4,
+ IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS,
+ IPHETH_USBINTF_PROTO)},
+ {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_4S,
+ IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS,
+ IPHETH_USBINTF_PROTO)},
+ {IPHETH_ID(USB_VENDOR_APPLE, USB_PRODUCT_APPLE_IPHONE_5,
+ IPHETH_USBINTF_CLASS, IPHETH_USBINTF_SUBCLASS,
+ IPHETH_USBINTF_PROTO)},
+#else
+ /* product agnostic interface match */
+ {USB_VENDOR(USB_VENDOR_APPLE),
+ USB_IFACE_CLASS(IPHETH_USBINTF_CLASS),
+ USB_IFACE_SUBCLASS(IPHETH_USBINTF_SUBCLASS),
+ USB_IFACE_PROTOCOL(IPHETH_USBINTF_PROTO)},
+#endif
+};
+
+DRIVER_MODULE(ipheth, uhub, ipheth_driver, NULL, NULL);
+MODULE_VERSION(ipheth, 1);
+MODULE_DEPEND(ipheth, uether, 1, 1, 1);
+MODULE_DEPEND(ipheth, usb, 1, 1, 1);
+MODULE_DEPEND(ipheth, ether, 1, 1, 1);
+USB_PNP_HOST_INFO(ipheth_devs);
+
+static const struct usb_ether_methods ipheth_ue_methods = {
+ .ue_attach_post = ipheth_attach_post,
+ .ue_start = ipheth_start,
+ .ue_init = ipheth_init,
+ .ue_tick = ipheth_tick,
+ .ue_stop = ipheth_stop,
+ .ue_setmulti = ipheth_setmulti,
+ .ue_setpromisc = ipheth_setpromisc,
+};
+
+#define IPHETH_ID(v,p,c,sc,pt) \
+ USB_VENDOR(v), USB_PRODUCT(p), \
+ USB_IFACE_CLASS(c), USB_IFACE_SUBCLASS(sc), \
+ USB_IFACE_PROTOCOL(pt)
+
+static int
+ipheth_get_mac_addr(struct ipheth_softc *sc)
+{
+ struct usb_device_request req;
+ int error;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = IPHETH_CMD_GET_MACADDR;
+ req.wValue[0] = 0;
+ req.wValue[1] = 0;
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ req.wLength[0] = ETHER_ADDR_LEN;
+ req.wLength[1] = 0;
+
+ error = usbd_do_request(sc->sc_ue.ue_udev, NULL, &req, sc->sc_data);
+
+ if (error)
+ return (error);
+
+ memcpy(sc->sc_ue.ue_eaddr, sc->sc_data, ETHER_ADDR_LEN);
+
+ return (0);
+}
+
+static int
+ipheth_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(ipheth_devs, sizeof(ipheth_devs), uaa));
+}
+
+static int
+ipheth_attach(device_t dev)
+{
+ struct ipheth_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ int error;
+
+ sc->sc_iface_no = uaa->info.bIfaceIndex;
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ error = usbd_set_alt_interface_index(uaa->device,
+ uaa->info.bIfaceIndex, IPHETH_ALT_INTFNUM);
+ if (error) {
+ device_printf(dev, "Cannot set alternate setting\n");
+ goto detach;
+ }
+ error = usbd_transfer_setup(uaa->device, &sc->sc_iface_no,
+ sc->sc_xfer, ipheth_config, IPHETH_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "Cannot setup USB transfers\n");
+ goto detach;
+ }
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &ipheth_ue_methods;
+
+ error = ipheth_get_mac_addr(sc);
+ if (error) {
+ device_printf(dev, "Cannot get MAC address\n");
+ goto detach;
+ }
+
+ error = uether_ifattach(ue);
+ if (error) {
+ device_printf(dev, "could not attach interface\n");
+ goto detach;
+ }
+ return (0); /* success */
+
+detach:
+ ipheth_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+ipheth_detach(device_t dev)
+{
+ struct ipheth_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ /* stop all USB transfers first */
+ usbd_transfer_unsetup(sc->sc_xfer, IPHETH_N_TRANSFER);
+
+ uether_ifdetach(ue);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+ipheth_start(struct usb_ether *ue)
+{
+ struct ipheth_softc *sc = uether_getsc(ue);
+
+ /*
+ * Start the USB transfers, if not already started:
+ */
+ usbd_transfer_start(sc->sc_xfer[IPHETH_BULK_TX]);
+ usbd_transfer_start(sc->sc_xfer[IPHETH_BULK_RX]);
+}
+
+static void
+ipheth_stop(struct usb_ether *ue)
+{
+ struct ipheth_softc *sc = uether_getsc(ue);
+
+ /*
+ * Stop the USB transfers, if not already stopped:
+ */
+ usbd_transfer_stop(sc->sc_xfer[IPHETH_BULK_TX]);
+ usbd_transfer_stop(sc->sc_xfer[IPHETH_BULK_RX]);
+}
+
+static void
+ipheth_tick(struct usb_ether *ue)
+{
+ struct ipheth_softc *sc = uether_getsc(ue);
+ struct usb_device_request req;
+ int error;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = IPHETH_CMD_CARRIER_CHECK;
+ req.wValue[0] = 0;
+ req.wValue[1] = 0;
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ req.wLength[0] = IPHETH_CTRL_BUF_SIZE;
+ req.wLength[1] = 0;
+
+ error = uether_do_request(ue, &req, sc->sc_data, IPHETH_CTRL_TIMEOUT);
+
+ if (error)
+ return;
+
+ sc->sc_carrier_on =
+ (sc->sc_data[0] == IPHETH_CARRIER_ON);
+}
+
+static void
+ipheth_attach_post(struct usb_ether *ue)
+{
+
+}
+
+static void
+ipheth_init(struct usb_ether *ue)
+{
+ struct ipheth_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ IPHETH_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+
+ /* stall data write direction, which depends on USB mode */
+ usbd_xfer_set_stall(sc->sc_xfer[IPHETH_BULK_TX]);
+
+ /* start data transfers */
+ ipheth_start(ue);
+}
+
+static void
+ipheth_setmulti(struct usb_ether *ue)
+{
+
+}
+
+static void
+ipheth_setpromisc(struct usb_ether *ue)
+{
+
+}
+
+static void
+ipheth_free_queue(struct mbuf **ppm, uint8_t n)
+{
+ uint8_t x;
+
+ for (x = 0; x != n; x++) {
+ if (ppm[x] != NULL) {
+ m_freem(ppm[x]);
+ ppm[x] = NULL;
+ }
+ }
+}
+
+static void
+ipheth_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ipheth_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ uint8_t x;
+ int actlen;
+ int aframes;
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ DPRINTFN(1, "\n");
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "transfer complete: %u bytes in %u frames\n",
+ actlen, aframes);
+
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /* free all previous TX buffers */
+ ipheth_free_queue(sc->sc_tx_buf, IPHETH_TX_FRAMES_MAX);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ for (x = 0; x != IPHETH_TX_FRAMES_MAX; x++) {
+ m = if_dequeue(ifp);
+
+ if (m == NULL)
+ break;
+
+ usbd_xfer_set_frame_offset(xfer,
+ x * IPHETH_BUF_SIZE, x);
+
+ pc = usbd_xfer_get_frame(xfer, x);
+
+ sc->sc_tx_buf[x] = m;
+
+ if (m->m_pkthdr.len > IPHETH_BUF_SIZE)
+ m->m_pkthdr.len = IPHETH_BUF_SIZE;
+
+ usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len);
+
+ usbd_xfer_set_frame_len(xfer, x, IPHETH_BUF_SIZE);
+
+ if (IPHETH_BUF_SIZE != m->m_pkthdr.len) {
+ usbd_frame_zero(pc, m->m_pkthdr.len,
+ IPHETH_BUF_SIZE - m->m_pkthdr.len);
+ }
+
+ /*
+ * If there's a BPF listener, bounce a copy of
+ * this frame to him:
+ */
+ BPF_MTAP(ifp, m);
+ }
+ if (x != 0) {
+ usbd_xfer_set_frames(xfer, x);
+
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ DPRINTFN(11, "transfer error, %s\n",
+ usbd_errstr(error));
+
+ /* free all previous TX buffers */
+ ipheth_free_queue(sc->sc_tx_buf, IPHETH_TX_FRAMES_MAX);
+
+ /* count output errors */
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+ipheth_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ipheth_softc *sc = usbd_xfer_softc(xfer);
+ struct mbuf *m;
+ uint8_t x;
+ int actlen;
+ int aframes;
+ int len;
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ DPRINTF("received %u bytes in %u frames\n", actlen, aframes);
+
+ for (x = 0; x != aframes; x++) {
+ m = sc->sc_rx_buf[x];
+ sc->sc_rx_buf[x] = NULL;
+ len = usbd_xfer_frame_len(xfer, x);
+
+ if (len < (int)(sizeof(struct ether_header) +
+ IPHETH_RX_ADJ)) {
+ m_freem(m);
+ continue;
+ }
+
+ m_adj(m, IPHETH_RX_ADJ);
+
+ /* queue up mbuf */
+ uether_rxmbuf(&sc->sc_ue, m, len - IPHETH_RX_ADJ);
+ }
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+
+ for (x = 0; x != IPHETH_RX_FRAMES_MAX; x++) {
+ if (sc->sc_rx_buf[x] == NULL) {
+ m = uether_newbuf();
+ if (m == NULL)
+ goto tr_stall;
+
+ /* cancel alignment for ethernet */
+ m_adj(m, ETHER_ALIGN);
+
+ sc->sc_rx_buf[x] = m;
+ } else {
+ m = sc->sc_rx_buf[x];
+ }
+
+ usbd_xfer_set_frame_data(xfer, x, m->m_data, m->m_len);
+ }
+ /* set number of frames and start hardware */
+ usbd_xfer_set_frames(xfer, x);
+ usbd_transfer_submit(xfer);
+ /* flush any received frames */
+ uether_rxflush(&sc->sc_ue);
+ break;
+
+ default: /* Error */
+ DPRINTF("error = %s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ tr_stall:
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ usbd_xfer_set_frames(xfer, 0);
+ usbd_transfer_submit(xfer);
+ break;
+ }
+ /* need to free the RX-mbufs when we are cancelled */
+ ipheth_free_queue(sc->sc_rx_buf, IPHETH_RX_FRAMES_MAX);
+ break;
+ }
+}
diff --git a/sys/dev/usb/net/if_iphethvar.h b/sys/dev/usb/net/if_iphethvar.h
new file mode 100644
index 000000000000..203bb96b6f22
--- /dev/null
+++ b/sys/dev/usb/net/if_iphethvar.h
@@ -0,0 +1,85 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 2009 Diego Giagio. 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.
+ */
+
+/*
+ * Thanks to Diego Giagio for figuring out the programming details for
+ * the Apple iPhone Ethernet driver.
+ */
+
+#ifndef _IF_IPHETHVAR_H_
+#define _IF_IPHETHVAR_H_
+
+#define IPHETH_USBINTF_CLASS 255
+#define IPHETH_USBINTF_SUBCLASS 253
+#define IPHETH_USBINTF_PROTO 1
+
+#define IPHETH_BUF_SIZE 1514
+#define IPHETH_TX_TIMEOUT 5000 /* ms */
+
+#define IPHETH_RX_FRAMES_MAX 1
+#define IPHETH_TX_FRAMES_MAX 8
+
+#define IPHETH_RX_ADJ 2
+
+#define IPHETH_CFG_INDEX 0
+#define IPHETH_IF_INDEX 2
+#define IPHETH_ALT_INTFNUM 1
+
+#define IPHETH_CTRL_ENDP 0x00
+#define IPHETH_CTRL_BUF_SIZE 0x40
+#define IPHETH_CTRL_TIMEOUT 5000 /* ms */
+
+#define IPHETH_CMD_GET_MACADDR 0x00
+#define IPHETH_CMD_CARRIER_CHECK 0x45
+
+#define IPHETH_CARRIER_ON 0x04
+
+enum {
+ IPHETH_BULK_TX,
+ IPHETH_BULK_RX,
+ IPHETH_N_TRANSFER,
+};
+
+struct ipheth_softc {
+ struct usb_ether sc_ue;
+ struct mtx sc_mtx;
+
+ struct usb_xfer *sc_xfer[IPHETH_N_TRANSFER];
+ struct mbuf *sc_rx_buf[IPHETH_RX_FRAMES_MAX];
+ struct mbuf *sc_tx_buf[IPHETH_TX_FRAMES_MAX];
+
+ uint8_t sc_data[IPHETH_CTRL_BUF_SIZE];
+ uint8_t sc_iface_no;
+ uint8_t sc_carrier_on;
+};
+
+#define IPHETH_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define IPHETH_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define IPHETH_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t)
+
+#endif /* _IF_IPHETHVAR_H_ */
diff --git a/sys/dev/usb/net/if_kue.c b/sys/dev/usb/net/if_kue.c
new file mode 100644
index 000000000000..55c531e278fb
--- /dev/null
+++ b/sys/dev/usb/net/if_kue.c
@@ -0,0 +1,702 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * 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.
+ */
+
+/*
+ * 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR kue_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/net/usb_ethernet.h>
+#include <dev/usb/net/if_kuereg.h>
+#include <dev/usb/net/if_kuefw.h>
+
+/*
+ * Various supported device vendors/products.
+ */
+static const STRUCT_USB_HOST_ID kue_devs[] = {
+#define KUE_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) }
+ KUE_DEV(3COM, 3C19250),
+ KUE_DEV(3COM, 3C460),
+ KUE_DEV(ABOCOM, URE450),
+ KUE_DEV(ADS, UBS10BT),
+ KUE_DEV(ADS, UBS10BTX),
+ KUE_DEV(AOX, USB101),
+ KUE_DEV(ASANTE, EA),
+ KUE_DEV(ATEN, DSB650C),
+ KUE_DEV(ATEN, UC10T),
+ KUE_DEV(COREGA, ETHER_USB_T),
+ KUE_DEV(DLINK, DSB650C),
+ KUE_DEV(ENTREGA, E45),
+ KUE_DEV(ENTREGA, XX1),
+ KUE_DEV(ENTREGA, XX2),
+ KUE_DEV(IODATA, USBETT),
+ KUE_DEV(JATON, EDA),
+ KUE_DEV(KINGSTON, XX1),
+ KUE_DEV(KLSI, DUH3E10BT),
+ KUE_DEV(KLSI, DUH3E10BTN),
+ KUE_DEV(LINKSYS, USB10T),
+ KUE_DEV(MOBILITY, EA),
+ KUE_DEV(NETGEAR, EA101),
+ KUE_DEV(NETGEAR, EA101X),
+ KUE_DEV(PERACOM, ENET),
+ KUE_DEV(PERACOM, ENET2),
+ KUE_DEV(PERACOM, ENET3),
+ KUE_DEV(PORTGEAR, EA8),
+ KUE_DEV(PORTGEAR, EA9),
+ KUE_DEV(PORTSMITH, EEA),
+ KUE_DEV(SHARK, PA),
+ KUE_DEV(SILICOM, GPE),
+ KUE_DEV(SILICOM, U2E),
+ KUE_DEV(SMC, 2102USB),
+#undef KUE_DEV
+};
+
+/* prototypes */
+
+static device_probe_t kue_probe;
+static device_attach_t kue_attach;
+static device_detach_t kue_detach;
+
+static usb_callback_t kue_bulk_read_callback;
+static usb_callback_t kue_bulk_write_callback;
+
+static uether_fn_t kue_attach_post;
+static uether_fn_t kue_init;
+static uether_fn_t kue_stop;
+static uether_fn_t kue_start;
+static uether_fn_t kue_setmulti;
+static uether_fn_t kue_setpromisc;
+
+static int kue_do_request(struct kue_softc *,
+ struct usb_device_request *, void *);
+static int kue_setword(struct kue_softc *, uint8_t, uint16_t);
+static int kue_ctl(struct kue_softc *, uint8_t, uint8_t, uint16_t,
+ void *, int);
+static int kue_load_fw(struct kue_softc *);
+static void kue_reset(struct kue_softc *);
+
+#ifdef USB_DEBUG
+static int kue_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, kue, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB kue");
+SYSCTL_INT(_hw_usb_kue, OID_AUTO, debug, CTLFLAG_RWTUN, &kue_debug, 0,
+ "Debug level");
+#endif
+
+static const struct usb_config kue_config[KUE_N_TRANSFER] = {
+ [KUE_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = (MCLBYTES + 2 + 64),
+ .flags = {.pipe_bof = 1,},
+ .callback = kue_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ },
+
+ [KUE_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = (MCLBYTES + 2),
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = kue_bulk_read_callback,
+ .timeout = 0, /* no timeout */
+ },
+};
+
+static device_method_t kue_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, kue_probe),
+ DEVMETHOD(device_attach, kue_attach),
+ DEVMETHOD(device_detach, kue_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t kue_driver = {
+ .name = "kue",
+ .methods = kue_methods,
+ .size = sizeof(struct kue_softc),
+};
+
+DRIVER_MODULE(kue, uhub, kue_driver, NULL, NULL);
+MODULE_DEPEND(kue, uether, 1, 1, 1);
+MODULE_DEPEND(kue, usb, 1, 1, 1);
+MODULE_DEPEND(kue, ether, 1, 1, 1);
+MODULE_VERSION(kue, 1);
+USB_PNP_HOST_INFO(kue_devs);
+
+static const struct usb_ether_methods kue_ue_methods = {
+ .ue_attach_post = kue_attach_post,
+ .ue_start = kue_start,
+ .ue_init = kue_init,
+ .ue_stop = kue_stop,
+ .ue_setmulti = kue_setmulti,
+ .ue_setpromisc = kue_setpromisc,
+};
+
+/*
+ * 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 int
+kue_do_request(struct kue_softc *sc, struct usb_device_request *req,
+ void *data)
+{
+ usb_error_t err;
+
+ err = uether_do_request(&sc->sc_ue, req, data, 60000);
+
+ return (err);
+}
+
+static int
+kue_setword(struct kue_softc *sc, uint8_t breq, uint16_t word)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = breq;
+ USETW(req.wValue, word);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+
+ return (kue_do_request(sc, &req, NULL));
+}
+
+static int
+kue_ctl(struct kue_softc *sc, uint8_t rw, uint8_t breq,
+ uint16_t val, void *data, int len)
+{
+ struct usb_device_request req;
+
+ 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);
+
+ return (kue_do_request(sc, &req, data));
+}
+
+static int
+kue_load_fw(struct kue_softc *sc)
+{
+ struct usb_device_descriptor *dd;
+ uint16_t hwrev;
+ usb_error_t err;
+
+ dd = usbd_get_device_descriptor(sc->sc_ue.ue_udev);
+ 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->sc_ue.ue_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->sc_ue.ue_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->sc_ue.ue_dev, "failed to load trigger segment: %s\n",
+ usbd_errstr(err));
+ return(ENXIO);
+ }
+
+ return (0);
+}
+
+static void
+kue_setpromisc(struct usb_ether *ue)
+{
+ struct kue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ KUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (if_getflags(ifp) & IFF_PROMISC)
+ sc->sc_rxfilt |= KUE_RXFILT_PROMISC;
+ else
+ sc->sc_rxfilt &= ~KUE_RXFILT_PROMISC;
+
+ kue_setword(sc, KUE_CMD_SET_PKT_FILTER, sc->sc_rxfilt);
+}
+
+static u_int
+kue_copy_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
+{
+ struct kue_softc *sc = arg;
+
+ if (cnt >= KUE_MCFILTCNT(sc))
+ return (1);
+
+ memcpy(KUE_MCFILT(sc, cnt), LLADDR(sdl), ETHER_ADDR_LEN);
+
+ return (1);
+}
+
+static void
+kue_setmulti(struct usb_ether *ue)
+{
+ struct kue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ int i;
+
+ KUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (if_getflags(ifp) & IFF_ALLMULTI || if_getflags(ifp) & IFF_PROMISC) {
+ sc->sc_rxfilt |= KUE_RXFILT_ALLMULTI;
+ sc->sc_rxfilt &= ~KUE_RXFILT_MULTICAST;
+ kue_setword(sc, KUE_CMD_SET_PKT_FILTER, sc->sc_rxfilt);
+ return;
+ }
+
+ sc->sc_rxfilt &= ~KUE_RXFILT_ALLMULTI;
+
+ i = if_foreach_llmaddr(ifp, kue_copy_maddr, sc);
+
+ if (i >= KUE_MCFILTCNT(sc))
+ sc->sc_rxfilt |= KUE_RXFILT_ALLMULTI;
+ else {
+ sc->sc_rxfilt |= KUE_RXFILT_MULTICAST;
+ kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SET_MCAST_FILTERS,
+ i, sc->sc_mcfilters, i * ETHER_ADDR_LEN);
+ }
+
+ kue_setword(sc, KUE_CMD_SET_PKT_FILTER, sc->sc_rxfilt);
+}
+
+/*
+ * 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)
+{
+ struct usb_config_descriptor *cd;
+ usb_error_t err;
+
+ cd = usbd_get_config_descriptor(sc->sc_ue.ue_udev);
+
+ err = usbd_req_set_config(sc->sc_ue.ue_udev, &sc->sc_mtx,
+ cd->bConfigurationValue);
+ if (err)
+ DPRINTF("reset failed (ignored)\n");
+
+ /* wait a little while for the chip to get its brains in order */
+ uether_pause(&sc->sc_ue, hz / 100);
+}
+
+static void
+kue_attach_post(struct usb_ether *ue)
+{
+ struct kue_softc *sc = uether_getsc(ue);
+ int error;
+
+ /* load the firmware into the NIC */
+ error = kue_load_fw(sc);
+ if (error) {
+ device_printf(sc->sc_ue.ue_dev, "could not load firmware\n");
+ /* ignore the error */
+ }
+
+ /* reset the adapter */
+ kue_reset(sc);
+
+ /* read ethernet descriptor */
+ kue_ctl(sc, KUE_CTL_READ, KUE_CMD_GET_ETHER_DESCRIPTOR,
+ 0, &sc->sc_desc, sizeof(sc->sc_desc));
+
+ /* copy in ethernet address */
+ memcpy(ue->ue_eaddr, sc->sc_desc.kue_macaddr, sizeof(ue->ue_eaddr));
+}
+
+/*
+ * Probe for a KLSI chip.
+ */
+static int
+kue_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != KUE_CONFIG_IDX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != KUE_IFACE_IDX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(kue_devs, sizeof(kue_devs), uaa));
+}
+
+/*
+ * Attach the interface. Allocate softc structures, do
+ * setup and ethernet/BPF attach.
+ */
+static int
+kue_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct kue_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+ uint8_t iface_index;
+ int error;
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ iface_index = KUE_IFACE_IDX;
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, kue_config, KUE_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "allocating USB transfers failed\n");
+ goto detach;
+ }
+
+ sc->sc_mcfilters = malloc(KUE_MCFILTCNT(sc) * ETHER_ADDR_LEN,
+ M_USBDEV, M_WAITOK);
+ if (sc->sc_mcfilters == NULL) {
+ device_printf(dev, "failed allocating USB memory\n");
+ goto detach;
+ }
+
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &kue_ue_methods;
+
+ error = uether_ifattach(ue);
+ if (error) {
+ device_printf(dev, "could not attach interface\n");
+ goto detach;
+ }
+ return (0); /* success */
+
+detach:
+ kue_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+kue_detach(device_t dev)
+{
+ struct kue_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ usbd_transfer_unsetup(sc->sc_xfer, KUE_N_TRANSFER);
+ uether_ifdetach(ue);
+ mtx_destroy(&sc->sc_mtx);
+ free(sc->sc_mcfilters, M_USBDEV);
+
+ return (0);
+}
+
+/*
+ * A frame has been uploaded: pass the resulting mbuf chain up to
+ * the higher level protocols.
+ */
+static void
+kue_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct kue_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_ether *ue = &sc->sc_ue;
+ if_t ifp = uether_getifp(ue);
+ struct usb_page_cache *pc;
+ uint8_t buf[2];
+ int len;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ if (actlen <= (int)(2 + sizeof(struct ether_header))) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, buf, 2);
+ actlen -= 2;
+ len = buf[0] | (buf[1] << 8);
+ len = min(actlen, len);
+
+ uether_rxbuf(ue, pc, 2, len);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ uether_rxflush(ue);
+ return;
+
+ default: /* Error */
+ DPRINTF("bulk read error, %s\n",
+ usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+kue_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct kue_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ int total_len;
+ int temp_len;
+ uint8_t buf[2];
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "transfer complete\n");
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ m = if_dequeue(ifp);
+
+ if (m == NULL)
+ return;
+ if (m->m_pkthdr.len > MCLBYTES)
+ m->m_pkthdr.len = MCLBYTES;
+ temp_len = (m->m_pkthdr.len + 2);
+ total_len = (temp_len + (64 - (temp_len % 64)));
+
+ /* the first two bytes are the frame length */
+
+ buf[0] = (uint8_t)(m->m_pkthdr.len);
+ buf[1] = (uint8_t)(m->m_pkthdr.len >> 8);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, buf, 2);
+ usbd_m_copy_in(pc, 2, m, 0, m->m_pkthdr.len);
+
+ usbd_frame_zero(pc, temp_len, total_len - temp_len);
+ usbd_xfer_set_frame_len(xfer, 0, total_len);
+
+ /*
+ * if there's a BPF listener, bounce a copy
+ * of this frame to him:
+ */
+ BPF_MTAP(ifp, m);
+
+ m_freem(m);
+
+ usbd_transfer_submit(xfer);
+
+ return;
+
+ default: /* Error */
+ DPRINTFN(11, "transfer error, %s\n",
+ usbd_errstr(error));
+
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+kue_start(struct usb_ether *ue)
+{
+ struct kue_softc *sc = uether_getsc(ue);
+
+ /*
+ * start the USB transfers, if not already started:
+ */
+ usbd_transfer_start(sc->sc_xfer[KUE_BULK_DT_RD]);
+ usbd_transfer_start(sc->sc_xfer[KUE_BULK_DT_WR]);
+}
+
+static void
+kue_init(struct usb_ether *ue)
+{
+ struct kue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ KUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* set MAC address */
+ kue_ctl(sc, KUE_CTL_WRITE, KUE_CMD_SET_MAC,
+ 0, if_getlladdr(ifp), ETHER_ADDR_LEN);
+
+ /* I'm not sure how to tune these. */
+#if 0
+ /*
+ * 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);
+
+ /* load the multicast filter */
+ kue_setpromisc(ue);
+
+ usbd_xfer_set_stall(sc->sc_xfer[KUE_BULK_DT_WR]);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+ kue_start(ue);
+}
+
+static void
+kue_stop(struct usb_ether *ue)
+{
+ struct kue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ KUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+
+ /*
+ * stop all the transfers, if not already stopped:
+ */
+ usbd_transfer_stop(sc->sc_xfer[KUE_BULK_DT_WR]);
+ usbd_transfer_stop(sc->sc_xfer[KUE_BULK_DT_RD]);
+}
diff --git a/sys/dev/usb/net/if_kuefw.h b/sys/dev/usb/net/if_kuefw.h
new file mode 100644
index 000000000000..c1f2aa109036
--- /dev/null
+++ b/sys/dev/usb/net/if_kuefw.h
@@ -0,0 +1,685 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * 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.
+ */
+
+/*
+ * 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/dev/usb/net/if_kuereg.h b/sys/dev/usb/net/if_kuereg.h
new file mode 100644
index 000000000000..1844251cba30
--- /dev/null
+++ b/sys/dev/usb/net/if_kuereg.h
@@ -0,0 +1,141 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * 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.
+ */
+
+/*
+ * 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 {
+ uint8_t kue_len;
+ uint8_t kue_rsvd0;
+ uint8_t kue_rsvd1;
+ uint8_t kue_macaddr[ETHER_ADDR_LEN];
+ uint8_t kue_etherstats[4];
+ uint8_t kue_maxseg[2];
+ uint8_t kue_mcastfilt[2];
+ uint8_t kue_rsvd2;
+} __packed;
+
+#define KUE_ETHERSTATS(x) UGETDW((x)->sc_desc.kue_etherstats)
+#define KUE_MAXSEG(x) UGETW((x)->sc_desc.kue_maxseg)
+#define KUE_MCFILTCNT(x) (UGETW((x)->sc_desc.kue_mcastfilt) & 0x7FFF)
+#define KUE_MCFILT(x, y) \
+ (char *)&(sc->sc_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_IDX 0 /* config number 1 */
+#define KUE_IFACE_IDX 0
+
+/* The interrupt endpoint is currently unused by the KLSI part. */
+#define KUE_ENDPT_MAX 4
+enum {
+ KUE_BULK_DT_WR,
+ KUE_BULK_DT_RD,
+ KUE_N_TRANSFER,
+};
+
+struct kue_softc {
+ struct usb_ether sc_ue;
+ struct mtx sc_mtx;
+ struct kue_ether_desc sc_desc;
+ struct usb_xfer *sc_xfer[KUE_N_TRANSFER];
+ uint8_t *sc_mcfilters;
+
+ int sc_flags;
+#define KUE_FLAG_LINK 0x0001
+
+ uint16_t sc_rxfilt;
+};
+
+#define KUE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define KUE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define KUE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t)
diff --git a/sys/dev/usb/net/if_mos.c b/sys/dev/usb/net/if_mos.c
new file mode 100644
index 000000000000..41881fb778a5
--- /dev/null
+++ b/sys/dev/usb/net/if_mos.c
@@ -0,0 +1,1012 @@
+/* SPDX-License-Identifier: ISC AND BSD-4-Clause */
+
+/*-
+ * Copyright (c) 2011 Rick van der Zwet <info@rickvanderzwet.nl>
+ *
+ * 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.
+ */
+
+/*-
+ * Copyright (c) 2008 Johann Christian Rode <jcrode@gmx.net>
+ *
+ * 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.
+ */
+
+/*-
+ * Copyright (c) 2005, 2006, 2007 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.
+ */
+
+/*-
+ * 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.
+ */
+
+/*
+ * Moschip MCS7730/MCS7830/MCS7832 USB to Ethernet controller
+ * The datasheet is available at the following URL:
+ * http://www.moschip.com/data/products/MCS7830/Data%20Sheet_7830.pdf
+ */
+
+/*
+ * The FreeBSD if_mos.c driver is based on various different sources:
+ * The vendor provided driver at the following URL:
+ * http://www.moschip.com/data/products/MCS7830/Driver_FreeBSD_7830.tar.gz
+ *
+ * Mixed together with the OpenBSD if_mos.c driver for validation and checking
+ * and the FreeBSD if_reu.c as reference for the USB Ethernet framework.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_media.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR mos_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/net/usb_ethernet.h>
+
+#include "miibus_if.h"
+
+//#include <dev/usb/net/if_mosreg.h>
+#include "if_mosreg.h"
+
+#ifdef USB_DEBUG
+static int mos_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, mos, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB mos");
+SYSCTL_INT(_hw_usb_mos, OID_AUTO, debug, CTLFLAG_RWTUN, &mos_debug, 0,
+ "Debug level");
+#endif
+
+#define MOS_DPRINTFN(fmt,...) \
+ DPRINTF("mos: %s: " fmt "\n",__FUNCTION__,## __VA_ARGS__)
+
+#define USB_PRODUCT_MOSCHIP_MCS7730 0x7730
+#define USB_PRODUCT_SITECOMEU_LN030 0x0021
+
+/* Various supported device vendors/products. */
+static const STRUCT_USB_HOST_ID mos_devs[] = {
+ {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7730, MCS7730)},
+ {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7830, MCS7830)},
+ {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7832, MCS7832)},
+ {USB_VPI(USB_VENDOR_SITECOMEU, USB_PRODUCT_SITECOMEU_LN030, MCS7830)},
+};
+
+static int mos_probe(device_t dev);
+static int mos_attach(device_t dev);
+static void mos_attach_post(struct usb_ether *ue);
+static int mos_detach(device_t dev);
+
+static void mos_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error);
+static void mos_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error);
+static void mos_intr_callback(struct usb_xfer *xfer, usb_error_t error);
+static void mos_tick(struct usb_ether *);
+static void mos_start(struct usb_ether *);
+static void mos_init(struct usb_ether *);
+static void mos_chip_init(struct mos_softc *);
+static void mos_stop(struct usb_ether *);
+static int mos_miibus_readreg(device_t, int, int);
+static int mos_miibus_writereg(device_t, int, int, int);
+static void mos_miibus_statchg(device_t);
+static int mos_ifmedia_upd(if_t);
+static void mos_ifmedia_sts(if_t, struct ifmediareq *);
+static void mos_reset(struct mos_softc *sc);
+
+static int mos_reg_read_1(struct mos_softc *, int);
+static int mos_reg_read_2(struct mos_softc *, int);
+static int mos_reg_write_1(struct mos_softc *, int, int);
+static int mos_reg_write_2(struct mos_softc *, int, int);
+static int mos_readmac(struct mos_softc *, uint8_t *);
+static int mos_writemac(struct mos_softc *, uint8_t *);
+static int mos_write_mcast(struct mos_softc *, u_char *);
+
+static void mos_setmulti(struct usb_ether *);
+static void mos_setpromisc(struct usb_ether *);
+
+static const struct usb_config mos_config[MOS_ENDPT_MAX] = {
+ [MOS_ENDPT_TX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = (MCLBYTES + 2),
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = mos_bulk_write_callback,
+ .timeout = 10000,
+ },
+
+ [MOS_ENDPT_RX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = (MCLBYTES + 4 + ETHER_CRC_LEN),
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = mos_bulk_read_callback,
+ },
+
+ [MOS_ENDPT_INTR] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0,
+ .callback = mos_intr_callback,
+ },
+};
+
+static device_method_t mos_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, mos_probe),
+ DEVMETHOD(device_attach, mos_attach),
+ DEVMETHOD(device_detach, mos_detach),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, mos_miibus_readreg),
+ DEVMETHOD(miibus_writereg, mos_miibus_writereg),
+ DEVMETHOD(miibus_statchg, mos_miibus_statchg),
+
+ DEVMETHOD_END
+};
+
+static driver_t mos_driver = {
+ .name = "mos",
+ .methods = mos_methods,
+ .size = sizeof(struct mos_softc)
+};
+
+DRIVER_MODULE(mos, uhub, mos_driver, NULL, NULL);
+DRIVER_MODULE(miibus, mos, miibus_driver, 0, 0);
+MODULE_DEPEND(mos, uether, 1, 1, 1);
+MODULE_DEPEND(mos, usb, 1, 1, 1);
+MODULE_DEPEND(mos, ether, 1, 1, 1);
+MODULE_DEPEND(mos, miibus, 1, 1, 1);
+USB_PNP_HOST_INFO(mos_devs);
+
+static const struct usb_ether_methods mos_ue_methods = {
+ .ue_attach_post = mos_attach_post,
+ .ue_start = mos_start,
+ .ue_init = mos_init,
+ .ue_stop = mos_stop,
+ .ue_tick = mos_tick,
+ .ue_setmulti = mos_setmulti,
+ .ue_setpromisc = mos_setpromisc,
+ .ue_mii_upd = mos_ifmedia_upd,
+ .ue_mii_sts = mos_ifmedia_sts,
+};
+
+static int
+mos_reg_read_1(struct mos_softc *sc, int reg)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+ uByte val = 0;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = MOS_UR_READREG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 1);
+
+ err = uether_do_request(&sc->sc_ue, &req, &val, 1000);
+
+ if (err) {
+ MOS_DPRINTFN("mos_reg_read_1 error, reg: %d\n", reg);
+ return (-1);
+ }
+ return (val);
+}
+
+static int
+mos_reg_read_2(struct mos_softc *sc, int reg)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+ uWord val;
+
+ USETW(val, 0);
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = MOS_UR_READREG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 2);
+
+ err = uether_do_request(&sc->sc_ue, &req, &val, 1000);
+
+ if (err) {
+ MOS_DPRINTFN("mos_reg_read_2 error, reg: %d", reg);
+ return (-1);
+ }
+ return (UGETW(val));
+}
+
+static int
+mos_reg_write_1(struct mos_softc *sc, int reg, int aval)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+ uByte val;
+ val = aval;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = MOS_UR_WRITEREG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 1);
+
+ err = uether_do_request(&sc->sc_ue, &req, &val, 1000);
+
+ if (err) {
+ MOS_DPRINTFN("mos_reg_write_1 error, reg: %d", reg);
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+mos_reg_write_2(struct mos_softc *sc, int reg, int aval)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+ uWord val;
+
+ USETW(val, aval);
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = MOS_UR_WRITEREG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 2);
+
+ err = uether_do_request(&sc->sc_ue, &req, &val, 1000);
+
+ if (err) {
+ MOS_DPRINTFN("mos_reg_write_2 error, reg: %d", reg);
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+mos_readmac(struct mos_softc *sc, u_char *mac)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = MOS_UR_READREG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, MOS_MAC);
+ USETW(req.wLength, ETHER_ADDR_LEN);
+
+ err = uether_do_request(&sc->sc_ue, &req, mac, 1000);
+
+ if (err) {
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+mos_writemac(struct mos_softc *sc, uint8_t *mac)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = MOS_UR_WRITEREG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, MOS_MAC);
+ USETW(req.wLength, ETHER_ADDR_LEN);
+
+ err = uether_do_request(&sc->sc_ue, &req, mac, 1000);
+
+ if (err) {
+ MOS_DPRINTFN("mos_writemac error");
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+mos_write_mcast(struct mos_softc *sc, u_char *hashtbl)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = MOS_UR_WRITEREG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, MOS_MCAST_TABLE);
+ USETW(req.wLength, 8);
+
+ err = uether_do_request(&sc->sc_ue, &req, hashtbl, 1000);
+
+ if (err) {
+ MOS_DPRINTFN("mos_reg_mcast error");
+ return (-1);
+ }
+ return (0);
+}
+
+static int
+mos_miibus_readreg(device_t dev, int phy, int reg)
+{
+ struct mos_softc *sc = device_get_softc(dev);
+ int i, res, locked;
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ MOS_LOCK(sc);
+
+ mos_reg_write_2(sc, MOS_PHY_DATA, 0);
+ mos_reg_write_1(sc, MOS_PHY_CTL, (phy & MOS_PHYCTL_PHYADDR) |
+ MOS_PHYCTL_READ);
+ mos_reg_write_1(sc, MOS_PHY_STS, (reg & MOS_PHYSTS_PHYREG) |
+ MOS_PHYSTS_PENDING);
+
+ for (i = 0; i < MOS_TIMEOUT; i++) {
+ if (mos_reg_read_1(sc, MOS_PHY_STS) & MOS_PHYSTS_READY)
+ break;
+ }
+ if (i == MOS_TIMEOUT) {
+ MOS_DPRINTFN("MII read timeout");
+ }
+ res = mos_reg_read_2(sc, MOS_PHY_DATA);
+
+ if (!locked)
+ MOS_UNLOCK(sc);
+ return (res);
+}
+
+static int
+mos_miibus_writereg(device_t dev, int phy, int reg, int val)
+{
+ struct mos_softc *sc = device_get_softc(dev);
+ int i, locked;
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ MOS_LOCK(sc);
+
+ mos_reg_write_2(sc, MOS_PHY_DATA, val);
+ mos_reg_write_1(sc, MOS_PHY_CTL, (phy & MOS_PHYCTL_PHYADDR) |
+ MOS_PHYCTL_WRITE);
+ mos_reg_write_1(sc, MOS_PHY_STS, (reg & MOS_PHYSTS_PHYREG) |
+ MOS_PHYSTS_PENDING);
+
+ for (i = 0; i < MOS_TIMEOUT; i++) {
+ if (mos_reg_read_1(sc, MOS_PHY_STS) & MOS_PHYSTS_READY)
+ break;
+ }
+ if (i == MOS_TIMEOUT)
+ MOS_DPRINTFN("MII write timeout");
+
+ if (!locked)
+ MOS_UNLOCK(sc);
+ return 0;
+}
+
+static void
+mos_miibus_statchg(device_t dev)
+{
+ struct mos_softc *sc = device_get_softc(dev);
+ struct mii_data *mii = GET_MII(sc);
+ int val, err, locked;
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ MOS_LOCK(sc);
+
+ /* disable RX, TX prior to changing FDX, SPEEDSEL */
+ val = mos_reg_read_1(sc, MOS_CTL);
+ val &= ~(MOS_CTL_TX_ENB | MOS_CTL_RX_ENB);
+ mos_reg_write_1(sc, MOS_CTL, val);
+
+ /* reset register which counts dropped frames */
+ mos_reg_write_1(sc, MOS_FRAME_DROP_CNT, 0);
+
+ if ((mii->mii_media_active & IFM_GMASK) == IFM_FDX)
+ val |= MOS_CTL_FDX_ENB;
+ else
+ val &= ~(MOS_CTL_FDX_ENB);
+
+ switch (IFM_SUBTYPE(mii->mii_media_active)) {
+ case IFM_100_TX:
+ val |= MOS_CTL_SPEEDSEL;
+ break;
+ case IFM_10_T:
+ val &= ~(MOS_CTL_SPEEDSEL);
+ break;
+ }
+
+ /* re-enable TX, RX */
+ val |= (MOS_CTL_TX_ENB | MOS_CTL_RX_ENB);
+ err = mos_reg_write_1(sc, MOS_CTL, val);
+
+ if (err)
+ MOS_DPRINTFN("media change failed");
+
+ if (!locked)
+ MOS_UNLOCK(sc);
+}
+
+/*
+ * Set media options.
+ */
+static int
+mos_ifmedia_upd(if_t ifp)
+{
+ struct mos_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = GET_MII(sc);
+ struct mii_softc *miisc;
+ int error;
+
+ MOS_LOCK_ASSERT(sc, MA_OWNED);
+
+ sc->mos_link = 0;
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list)
+ PHY_RESET(miisc);
+ error = mii_mediachg(mii);
+ return (error);
+}
+
+/*
+ * Report current media status.
+ */
+static void
+mos_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct mos_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = GET_MII(sc);
+
+ MOS_LOCK(sc);
+ mii_pollstat(mii);
+
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+ MOS_UNLOCK(sc);
+}
+
+static void
+mos_setpromisc(struct usb_ether *ue)
+{
+ struct mos_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ uint8_t rxmode;
+
+ MOS_LOCK_ASSERT(sc, MA_OWNED);
+
+ rxmode = mos_reg_read_1(sc, MOS_CTL);
+
+ /* If we want promiscuous mode, set the allframes bit. */
+ if (if_getflags(ifp) & IFF_PROMISC) {
+ rxmode |= MOS_CTL_RX_PROMISC;
+ } else {
+ rxmode &= ~MOS_CTL_RX_PROMISC;
+ }
+
+ mos_reg_write_1(sc, MOS_CTL, rxmode);
+}
+
+static u_int
+mos_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
+{
+ uint8_t *hashtbl = arg;
+ uint32_t h;
+
+ h = ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 26;
+ hashtbl[h / 8] |= 1 << (h % 8);
+
+ return (1);
+}
+
+static void
+mos_setmulti(struct usb_ether *ue)
+{
+ struct mos_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ uint8_t rxmode;
+ uint8_t hashtbl[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+ int allmulti = 0;
+
+ MOS_LOCK_ASSERT(sc, MA_OWNED);
+
+ rxmode = mos_reg_read_1(sc, MOS_CTL);
+
+ if (if_getflags(ifp) & IFF_ALLMULTI || if_getflags(ifp) & IFF_PROMISC)
+ allmulti = 1;
+
+ /* get all new ones */
+ if_foreach_llmaddr(ifp, mos_hash_maddr, &hashtbl);
+
+ /* now program new ones */
+ if (allmulti == 1) {
+ rxmode |= MOS_CTL_ALLMULTI;
+ mos_reg_write_1(sc, MOS_CTL, rxmode);
+ } else {
+ rxmode &= ~MOS_CTL_ALLMULTI;
+ mos_write_mcast(sc, (void *)&hashtbl);
+ mos_reg_write_1(sc, MOS_CTL, rxmode);
+ }
+}
+
+static void
+mos_reset(struct mos_softc *sc)
+{
+ uint8_t ctl;
+
+ ctl = mos_reg_read_1(sc, MOS_CTL);
+ ctl &= ~(MOS_CTL_RX_PROMISC | MOS_CTL_ALLMULTI | MOS_CTL_TX_ENB |
+ MOS_CTL_RX_ENB);
+ /* Disable RX, TX, promiscuous and allmulticast mode */
+ mos_reg_write_1(sc, MOS_CTL, ctl);
+
+ /* Reset frame drop counter register to zero */
+ mos_reg_write_1(sc, MOS_FRAME_DROP_CNT, 0);
+
+ /* Wait a little while for the chip to get its brains in order. */
+ usb_pause_mtx(&sc->sc_mtx, hz / 128);
+ return;
+}
+
+static void
+mos_chip_init(struct mos_softc *sc)
+{
+ int i;
+
+ /*
+ * Rev.C devices have a pause threshold register which needs to be set
+ * at startup.
+ */
+ if (mos_reg_read_1(sc, MOS_PAUSE_TRHD) != -1) {
+ for (i = 0; i < MOS_PAUSE_REWRITES; i++)
+ mos_reg_write_1(sc, MOS_PAUSE_TRHD, 0);
+ }
+ sc->mos_phyaddrs[0] = 1;
+ sc->mos_phyaddrs[1] = 0xFF;
+}
+
+/*
+ * Probe for a MCS7x30 chip.
+ */
+static int
+mos_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ int retval;
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != MOS_CONFIG_IDX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != MOS_IFACE_IDX)
+ return (ENXIO);
+
+ retval = usbd_lookup_id_by_uaa(mos_devs, sizeof(mos_devs), uaa);
+ return (retval);
+}
+
+/*
+ * Attach the interface. Allocate softc structures, do ifmedia
+ * setup and ethernet/BPF attach.
+ */
+static int
+mos_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct mos_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+ uint8_t iface_index;
+ int error;
+
+ sc->mos_flags = USB_GET_DRIVER_INFO(uaa);
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ iface_index = MOS_IFACE_IDX;
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, mos_config, MOS_ENDPT_MAX,
+ sc, &sc->sc_mtx);
+
+ if (error) {
+ device_printf(dev, "allocating USB transfers failed\n");
+ goto detach;
+ }
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &mos_ue_methods;
+
+ if (sc->mos_flags & MCS7730) {
+ MOS_DPRINTFN("model: MCS7730");
+ } else if (sc->mos_flags & MCS7830) {
+ MOS_DPRINTFN("model: MCS7830");
+ } else if (sc->mos_flags & MCS7832) {
+ MOS_DPRINTFN("model: MCS7832");
+ }
+ error = uether_ifattach(ue);
+ if (error) {
+ device_printf(dev, "could not attach interface\n");
+ goto detach;
+ }
+ return (0);
+
+detach:
+ mos_detach(dev);
+ return (ENXIO);
+}
+
+static void
+mos_attach_post(struct usb_ether *ue)
+{
+ struct mos_softc *sc = uether_getsc(ue);
+ int err;
+
+ /* Read MAC address, inform the world. */
+ err = mos_readmac(sc, ue->ue_eaddr);
+
+ if (err)
+ MOS_DPRINTFN("couldn't get MAC address");
+
+ MOS_DPRINTFN("address: %s", ether_sprintf(ue->ue_eaddr));
+
+ mos_chip_init(sc);
+}
+
+static int
+mos_detach(device_t dev)
+{
+ struct mos_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ usbd_transfer_unsetup(sc->sc_xfer, MOS_ENDPT_MAX);
+ uether_ifdetach(ue);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+/*
+ * A frame has been uploaded: pass the resulting mbuf chain up to
+ * the higher level protocols.
+ */
+static void
+mos_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct mos_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_ether *ue = &sc->sc_ue;
+ if_t ifp = uether_getifp(ue);
+
+ uint8_t rxstat = 0;
+ uint32_t actlen;
+ uint16_t pktlen = 0;
+ struct usb_page_cache *pc;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+ pc = usbd_xfer_get_frame(xfer, 0);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ MOS_DPRINTFN("actlen : %d", actlen);
+ if (actlen <= 1) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ /* evaluate status byte at the end */
+ usbd_copy_out(pc, actlen - sizeof(rxstat), &rxstat,
+ sizeof(rxstat));
+
+ if (rxstat != MOS_RXSTS_VALID) {
+ MOS_DPRINTFN("erroneous frame received");
+ if (rxstat & MOS_RXSTS_SHORT_FRAME)
+ MOS_DPRINTFN("frame size less than 64 bytes");
+ if (rxstat & MOS_RXSTS_LARGE_FRAME) {
+ MOS_DPRINTFN("frame size larger than "
+ "1532 bytes");
+ }
+ if (rxstat & MOS_RXSTS_CRC_ERROR)
+ MOS_DPRINTFN("CRC error");
+ if (rxstat & MOS_RXSTS_ALIGN_ERROR)
+ MOS_DPRINTFN("alignment error");
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ /* Remember the last byte was used for the status fields */
+ pktlen = actlen - 1;
+ if (pktlen < sizeof(struct ether_header)) {
+ MOS_DPRINTFN("error: pktlen %d is smaller "
+ "than ether_header %zd", pktlen,
+ sizeof(struct ether_header));
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ uether_rxbuf(ue, pc, 0, actlen);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ uether_rxflush(ue);
+ return;
+ default:
+ MOS_DPRINTFN("bulk read error, %s", usbd_errstr(error));
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ MOS_DPRINTFN("start rx %i", usbd_xfer_max_len(xfer));
+ return;
+ }
+}
+
+/*
+ * A frame was downloaded to the chip. It's safe for us to clean up
+ * the list buffers.
+ */
+static void
+mos_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct mos_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ MOS_DPRINTFN("transfer of complete");
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ /*
+ * XXX: don't send anything if there is no link?
+ */
+ m = if_dequeue(ifp);
+ if (m == NULL)
+ return;
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len);
+
+ usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len);
+
+ /*
+ * if there's a BPF listener, bounce a copy
+ * of this frame to him:
+ */
+ BPF_MTAP(ifp, m);
+
+ m_freem(m);
+
+ usbd_transfer_submit(xfer);
+
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+ return;
+ default:
+ MOS_DPRINTFN("usb error on tx: %s\n", usbd_errstr(error));
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+mos_tick(struct usb_ether *ue)
+{
+ struct mos_softc *sc = uether_getsc(ue);
+ struct mii_data *mii = GET_MII(sc);
+
+ MOS_LOCK_ASSERT(sc, MA_OWNED);
+
+ mii_tick(mii);
+ if (!sc->mos_link && mii->mii_media_status & IFM_ACTIVE &&
+ IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) {
+ MOS_DPRINTFN("got link");
+ sc->mos_link++;
+ mos_start(ue);
+ }
+}
+
+static void
+mos_start(struct usb_ether *ue)
+{
+ struct mos_softc *sc = uether_getsc(ue);
+
+ /*
+ * start the USB transfers, if not already started:
+ */
+ usbd_transfer_start(sc->sc_xfer[MOS_ENDPT_TX]);
+ usbd_transfer_start(sc->sc_xfer[MOS_ENDPT_RX]);
+ usbd_transfer_start(sc->sc_xfer[MOS_ENDPT_INTR]);
+}
+
+static void
+mos_init(struct usb_ether *ue)
+{
+ struct mos_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ uint8_t rxmode;
+
+ MOS_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* Cancel pending I/O and free all RX/TX buffers. */
+ mos_reset(sc);
+
+ /* Write MAC address */
+ mos_writemac(sc, if_getlladdr(ifp));
+
+ /* Read and set transmitter IPG values */
+ sc->mos_ipgs[0] = mos_reg_read_1(sc, MOS_IPG0);
+ sc->mos_ipgs[1] = mos_reg_read_1(sc, MOS_IPG1);
+ mos_reg_write_1(sc, MOS_IPG0, sc->mos_ipgs[0]);
+ mos_reg_write_1(sc, MOS_IPG1, sc->mos_ipgs[1]);
+
+ /*
+ * Enable receiver and transmitter, bridge controls speed/duplex
+ * mode
+ */
+ rxmode = mos_reg_read_1(sc, MOS_CTL);
+ rxmode |= MOS_CTL_RX_ENB | MOS_CTL_TX_ENB | MOS_CTL_BS_ENB;
+ rxmode &= ~(MOS_CTL_SLEEP);
+
+ mos_setpromisc(ue);
+
+ /* XXX: broadcast mode? */
+ mos_reg_write_1(sc, MOS_CTL, rxmode);
+
+ /* Load the multicast filter. */
+ mos_setmulti(ue);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+ mos_start(ue);
+}
+
+static void
+mos_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct mos_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct usb_page_cache *pc;
+ uint32_t pkt;
+ int actlen;
+
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+ MOS_DPRINTFN("actlen %i", actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &pkt, sizeof(pkt));
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ return;
+ default:
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+/*
+ * Stop the adapter and free any mbufs allocated to the
+ * RX and TX lists.
+ */
+static void
+mos_stop(struct usb_ether *ue)
+{
+ struct mos_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ mos_reset(sc);
+
+ MOS_LOCK_ASSERT(sc, MA_OWNED);
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+
+ /* stop all the transfers, if not already stopped */
+ usbd_transfer_stop(sc->sc_xfer[MOS_ENDPT_TX]);
+ usbd_transfer_stop(sc->sc_xfer[MOS_ENDPT_RX]);
+ usbd_transfer_stop(sc->sc_xfer[MOS_ENDPT_INTR]);
+
+ sc->mos_link = 0;
+}
diff --git a/sys/dev/usb/net/if_mosreg.h b/sys/dev/usb/net/if_mosreg.h
new file mode 100644
index 000000000000..5fea82587308
--- /dev/null
+++ b/sys/dev/usb/net/if_mosreg.h
@@ -0,0 +1,176 @@
+/*-
+ * Copyright (c) 2010, 2011 Rick van der Zwet <info@rickvanderzwet.nl>
+ *
+ * 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.
+ */
+
+/*-
+ * Copyright (c) 2008 Johann Christian Rode <jcrode@gmx.net>
+ *
+ * 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.
+ */
+
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * 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 Ravikanth.
+ * 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.
+ *
+ */
+
+/*
+ * Register definitions for the Moschip MCS7x30 ethernet controller.
+ */
+#define MOS_MCAST_TABLE 0x00
+#define MOS_IPG0 0x08
+#define MOS_IPG1 0x09
+#define MOS_PHY_DATA0 0x0a
+#define MOS_PHY_DATA1 0x0b
+#define MOS_PHY_CTL 0x0c
+#define MOS_PHY_STS 0x0d
+#define MOS_PHY_DATA MOS_PHY_DATA0
+#define MOS_CTL 0x0e
+#define MOS_MAC0 0x0f
+#define MOS_MAC1 0x10
+#define MOS_MAC2 0x11
+#define MOS_MAC3 0x12
+#define MOS_MAC4 0x13
+#define MOS_MAC5 0x14
+#define MOS_MAC MOS_MAC0
+/* apparently only available on hardware rev. C */
+#define MOS_FRAME_DROP_CNT 0x15
+#define MOS_PAUSE_TRHD 0x16
+
+#define MOS_PHYCTL_PHYADDR 0x1f
+#define MOS_PHYCTL_WRITE 0x20
+#define MOS_PHYCTL_READ 0x40
+
+#define MOS_PHYSTS_PHYREG 0x1f
+#define MOS_PHYSTS_READY 0x40
+#define MOS_PHYSTS_PENDING 0x80
+
+#define MOS_CTL_RX_PROMISC 0x01
+#define MOS_CTL_ALLMULTI 0x02
+#define MOS_CTL_SLEEP 0x04
+#define MOS_CTL_TX_ENB 0x08
+/*
+ * The documentation calls this bit 'reserved', but in the FreeBSD driver
+ * provided by the vendor, this enables the receiver.
+ */
+#define MOS_CTL_RX_ENB 0x10
+#define MOS_CTL_FDX_ENB 0x20
+/* 0 = 10 Mbps, 1 = 100 Mbps */
+#define MOS_CTL_SPEEDSEL 0x40
+/* 0 = PHY controls speed/duplex mode, 1 = bridge controls speed/duplex mode */
+#define MOS_CTL_BS_ENB 0x80
+
+#define MOS_RXSTS_SHORT_FRAME 0x01
+#define MOS_RXSTS_LENGTH_ERROR 0x02
+#define MOS_RXSTS_ALIGN_ERROR 0x04
+#define MOS_RXSTS_CRC_ERROR 0x08
+#define MOS_RXSTS_LARGE_FRAME 0x10
+#define MOS_RXSTS_VALID 0x20
+/*
+ * The EtherType field of an Ethernet frame can contain values other than
+ * the frame length, hence length errors are ignored.
+ */
+#define MOS_RXSTS_MASK 0x3d
+
+#define MOS_PAUSE_TRHD_DEFAULT 0
+#define MOS_PAUSE_REWRITES 3
+
+#define MOS_TIMEOUT 1000
+
+#define MOS_RX_LIST_CNT 1
+#define MOS_TX_LIST_CNT 1
+
+/* Maximum size of a fast ethernet frame plus one byte for the status */
+#define MOS_BUFSZ (ETHER_MAX_LEN+1)
+
+/*
+ * USB endpoints.
+ */
+#define MOS_ENDPT_RX 0
+#define MOS_ENDPT_TX 1
+#define MOS_ENDPT_INTR 2
+#define MOS_ENDPT_MAX 3
+
+/*
+ * USB vendor requests.
+ */
+#define MOS_UR_READREG 0x0e
+#define MOS_UR_WRITEREG 0x0d
+
+#define MOS_CONFIG_IDX 0
+#define MOS_IFACE_IDX 0
+
+#define MCS7730 0x0001
+#define MCS7830 0x0002
+#define MCS7832 0x0004
+
+#define MOS_INC(x, y) (x) = (x + 1) % y
+
+struct mos_softc {
+ struct usb_ether sc_ue;
+
+ struct mtx sc_mtx;
+ struct usb_xfer *sc_xfer[MOS_ENDPT_MAX];
+
+ uint16_t mos_flags;
+
+ int mos_link;
+ unsigned char mos_ipgs[2];
+ unsigned char mos_phyaddrs[2];
+};
+
+#define GET_MII(sc) uether_getmii(&(sc)->sc_ue)
+#define MOS_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define MOS_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define MOS_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t)
diff --git a/sys/dev/usb/net/if_muge.c b/sys/dev/usb/net/if_muge.c
new file mode 100644
index 000000000000..a39343b2e3c9
--- /dev/null
+++ b/sys/dev/usb/net/if_muge.c
@@ -0,0 +1,2268 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2012 Ben Gray <bgray@freebsd.org>.
+ * Copyright (C) 2018 The FreeBSD Foundation.
+ *
+ * This software was developed by Arshan Khanifar <arshankhanifar@gmail.com>
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * USB-To-Ethernet adapter driver for Microchip's LAN78XX and related families.
+ *
+ * USB 3.1 to 10/100/1000 Mbps Ethernet
+ * LAN7800 http://www.microchip.com/wwwproducts/en/LAN7800
+ *
+ * USB 2.0 to 10/100/1000 Mbps Ethernet
+ * LAN7850 http://www.microchip.com/wwwproducts/en/LAN7850
+ *
+ * USB 2 to 10/100/1000 Mbps Ethernet with built-in USB hub
+ * LAN7515 (no datasheet available, but probes and functions as LAN7800)
+ *
+ * This driver is based on the if_smsc driver, with lan78xx-specific
+ * functionality modelled on Microchip's Linux lan78xx driver.
+ *
+ * UNIMPLEMENTED FEATURES
+ * ------------------
+ * A number of features supported by the lan78xx are not yet implemented in
+ * this driver:
+ *
+ * - TX checksum offloading: Nothing has been implemented yet.
+ * - Direct address translation filtering: Implemented but untested.
+ * - VLAN tag removal.
+ * - Support for USB interrupt endpoints.
+ * - Latency Tolerance Messaging (LTM) support.
+ * - TCP LSO support.
+ *
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/callout.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/priv.h>
+#include <sys/queue.h>
+#include <sys/random.h>
+#include <sys/socket.h>
+#include <sys/stddef.h>
+#include <sys/stdint.h>
+#include <sys/sx.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/unistd.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_media.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#include "opt_platform.h"
+
+#ifdef FDT
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/usb/usb_fdt_support.h>
+#endif
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR lan78xx_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/net/usb_ethernet.h>
+
+#include <dev/usb/net/if_mugereg.h>
+
+#include "miibus_if.h"
+
+#ifdef USB_DEBUG
+static int muge_debug = 0;
+
+SYSCTL_NODE(_hw_usb, OID_AUTO, muge, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "Microchip LAN78xx USB-GigE");
+SYSCTL_INT(_hw_usb_muge, OID_AUTO, debug, CTLFLAG_RWTUN, &muge_debug, 0,
+ "Debug level");
+#endif
+
+#define MUGE_DEFAULT_TX_CSUM_ENABLE (false)
+#define MUGE_DEFAULT_TSO_ENABLE (false)
+
+/* Supported Vendor and Product IDs. */
+static const struct usb_device_id lan78xx_devs[] = {
+#define MUGE_DEV(p,i) { USB_VPI(USB_VENDOR_SMC2, USB_PRODUCT_SMC2_##p, i) }
+ MUGE_DEV(LAN7800_ETH, 0),
+ MUGE_DEV(LAN7801_ETH, 0),
+ MUGE_DEV(LAN7850_ETH, 0),
+#undef MUGE_DEV
+};
+
+#ifdef USB_DEBUG
+#define muge_dbg_printf(sc, fmt, args...) \
+do { \
+ if (muge_debug > 0) \
+ device_printf((sc)->sc_ue.ue_dev, "debug: " fmt, ##args); \
+} while(0)
+#else
+#define muge_dbg_printf(sc, fmt, args...) do { } while (0)
+#endif
+
+#define muge_warn_printf(sc, fmt, args...) \
+ device_printf((sc)->sc_ue.ue_dev, "warning: " fmt, ##args)
+
+#define muge_err_printf(sc, fmt, args...) \
+ device_printf((sc)->sc_ue.ue_dev, "error: " fmt, ##args)
+
+#define ETHER_IS_VALID(addr) \
+ (!ETHER_IS_MULTICAST(addr) && !ETHER_IS_ZERO(addr))
+
+/* USB endpoints. */
+
+enum {
+ MUGE_BULK_DT_RD,
+ MUGE_BULK_DT_WR,
+#if 0 /* Ignore interrupt endpoints for now as we poll on MII status. */
+ MUGE_INTR_DT_WR,
+ MUGE_INTR_DT_RD,
+#endif
+ MUGE_N_TRANSFER,
+};
+
+struct muge_softc {
+ struct usb_ether sc_ue;
+ struct mtx sc_mtx;
+ struct usb_xfer *sc_xfer[MUGE_N_TRANSFER];
+ int sc_phyno;
+ uint32_t sc_leds;
+ uint16_t sc_led_modes;
+ uint16_t sc_led_modes_mask;
+
+ /* Settings for the mac control (MAC_CSR) register. */
+ uint32_t sc_rfe_ctl;
+ uint32_t sc_mdix_ctl;
+ uint16_t chipid;
+ uint16_t chiprev;
+ uint32_t sc_mchash_table[ETH_DP_SEL_VHF_HASH_LEN];
+ uint32_t sc_pfilter_table[MUGE_NUM_PFILTER_ADDRS_][2];
+
+ uint32_t sc_flags;
+#define MUGE_FLAG_LINK 0x0001
+#define MUGE_FLAG_INIT_DONE 0x0002
+};
+
+#define MUGE_IFACE_IDX 0
+
+#define MUGE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define MUGE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define MUGE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t)
+
+static device_probe_t muge_probe;
+static device_attach_t muge_attach;
+static device_detach_t muge_detach;
+
+static usb_callback_t muge_bulk_read_callback;
+static usb_callback_t muge_bulk_write_callback;
+
+static miibus_readreg_t lan78xx_miibus_readreg;
+static miibus_writereg_t lan78xx_miibus_writereg;
+static miibus_statchg_t lan78xx_miibus_statchg;
+
+static int muge_attach_post_sub(struct usb_ether *ue);
+static uether_fn_t muge_attach_post;
+static uether_fn_t muge_init;
+static uether_fn_t muge_stop;
+static uether_fn_t muge_start;
+static uether_fn_t muge_tick;
+static uether_fn_t muge_setmulti;
+static uether_fn_t muge_setpromisc;
+
+static int muge_ifmedia_upd(if_t);
+static void muge_ifmedia_sts(if_t, struct ifmediareq *);
+
+static int lan78xx_chip_init(struct muge_softc *sc);
+static int muge_ioctl(if_t ifp, u_long cmd, caddr_t data);
+
+static const struct usb_config muge_config[MUGE_N_TRANSFER] = {
+ [MUGE_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .frames = 16,
+ .bufsize = 16 * (MCLBYTES + 16),
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = muge_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ },
+
+ [MUGE_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = 20480, /* bytes */
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = muge_bulk_read_callback,
+ .timeout = 0, /* no timeout */
+ },
+ /*
+ * The chip supports interrupt endpoints, however they aren't
+ * needed as we poll on the MII status.
+ */
+};
+
+static const struct usb_ether_methods muge_ue_methods = {
+ .ue_attach_post = muge_attach_post,
+ .ue_attach_post_sub = muge_attach_post_sub,
+ .ue_start = muge_start,
+ .ue_ioctl = muge_ioctl,
+ .ue_init = muge_init,
+ .ue_stop = muge_stop,
+ .ue_tick = muge_tick,
+ .ue_setmulti = muge_setmulti,
+ .ue_setpromisc = muge_setpromisc,
+ .ue_mii_upd = muge_ifmedia_upd,
+ .ue_mii_sts = muge_ifmedia_sts,
+};
+
+/**
+ * lan78xx_read_reg - Read a 32-bit register on the device
+ * @sc: driver soft context
+ * @off: offset of the register
+ * @data: pointer a value that will be populated with the register value
+ *
+ * LOCKING:
+ * The device lock must be held before calling this function.
+ *
+ * RETURNS:
+ * 0 on success, a USB_ERR_?? error code on failure.
+ */
+static int
+lan78xx_read_reg(struct muge_softc *sc, uint32_t off, uint32_t *data)
+{
+ struct usb_device_request req;
+ uint32_t buf;
+ usb_error_t err;
+
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = UVR_READ_REG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, off);
+ USETW(req.wLength, 4);
+
+ err = uether_do_request(&sc->sc_ue, &req, &buf, 1000);
+ if (err != 0)
+ muge_warn_printf(sc, "Failed to read register 0x%0x\n", off);
+ *data = le32toh(buf);
+ return (err);
+}
+
+/**
+ * lan78xx_write_reg - Write a 32-bit register on the device
+ * @sc: driver soft context
+ * @off: offset of the register
+ * @data: the 32-bit value to write into the register
+ *
+ * LOCKING:
+ * The device lock must be held before calling this function.
+ *
+ * RETURNS:
+ * 0 on success, a USB_ERR_?? error code on failure.
+ */
+static int
+lan78xx_write_reg(struct muge_softc *sc, uint32_t off, uint32_t data)
+{
+ struct usb_device_request req;
+ uint32_t buf;
+ usb_error_t err;
+
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ buf = htole32(data);
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = UVR_WRITE_REG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, off);
+ USETW(req.wLength, 4);
+
+ err = uether_do_request(&sc->sc_ue, &req, &buf, 1000);
+ if (err != 0)
+ muge_warn_printf(sc, "Failed to write register 0x%0x\n", off);
+ return (err);
+}
+
+/**
+ * lan78xx_wait_for_bits - Poll on a register value until bits are cleared
+ * @sc: soft context
+ * @reg: offset of the register
+ * @bits: if the bits are clear the function returns
+ *
+ * LOCKING:
+ * The device lock must be held before calling this function.
+ *
+ * RETURNS:
+ * 0 on success, or a USB_ERR_?? error code on failure.
+ */
+static int
+lan78xx_wait_for_bits(struct muge_softc *sc, uint32_t reg, uint32_t bits)
+{
+ usb_ticks_t start_ticks;
+ const usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000);
+ uint32_t val;
+ int err;
+
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ start_ticks = (usb_ticks_t)ticks;
+ do {
+ if ((err = lan78xx_read_reg(sc, reg, &val)) != 0)
+ return (err);
+ if (!(val & bits))
+ return (0);
+ uether_pause(&sc->sc_ue, hz / 100);
+ } while (((usb_ticks_t)(ticks - start_ticks)) < max_ticks);
+
+ return (USB_ERR_TIMEOUT);
+}
+
+/**
+ * lan78xx_eeprom_read_raw - Read the attached EEPROM
+ * @sc: soft context
+ * @off: the eeprom address offset
+ * @buf: stores the bytes
+ * @buflen: the number of bytes to read
+ *
+ * Simply reads bytes from an attached eeprom.
+ *
+ * LOCKING:
+ * The function takes and releases the device lock if not already held.
+ *
+ * RETURNS:
+ * 0 on success, or a USB_ERR_?? error code on failure.
+ */
+static int
+lan78xx_eeprom_read_raw(struct muge_softc *sc, uint16_t off, uint8_t *buf,
+ uint16_t buflen)
+{
+ usb_ticks_t start_ticks;
+ const usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000);
+ int err;
+ uint32_t val, saved;
+ uint16_t i;
+ bool locked;
+
+ locked = mtx_owned(&sc->sc_mtx); /* XXX */
+ if (!locked)
+ MUGE_LOCK(sc);
+
+ if (sc->chipid == ETH_ID_REV_CHIP_ID_7800_) {
+ /* EEDO/EECLK muxed with LED0/LED1 on LAN7800. */
+ err = lan78xx_read_reg(sc, ETH_HW_CFG, &val);
+ saved = val;
+
+ val &= ~(ETH_HW_CFG_LEDO_EN_ | ETH_HW_CFG_LED1_EN_);
+ err = lan78xx_write_reg(sc, ETH_HW_CFG, val);
+ }
+
+ err = lan78xx_wait_for_bits(sc, ETH_E2P_CMD, ETH_E2P_CMD_BUSY_);
+ if (err != 0) {
+ muge_warn_printf(sc, "eeprom busy, failed to read data\n");
+ goto done;
+ }
+
+ /* Start reading the bytes, one at a time. */
+ for (i = 0; i < buflen; i++) {
+ val = ETH_E2P_CMD_BUSY_ | ETH_E2P_CMD_READ_;
+ val |= (ETH_E2P_CMD_ADDR_MASK_ & (off + i));
+ if ((err = lan78xx_write_reg(sc, ETH_E2P_CMD, val)) != 0)
+ goto done;
+
+ start_ticks = (usb_ticks_t)ticks;
+ do {
+ if ((err = lan78xx_read_reg(sc, ETH_E2P_CMD, &val)) !=
+ 0)
+ goto done;
+ if (!(val & ETH_E2P_CMD_BUSY_) ||
+ (val & ETH_E2P_CMD_TIMEOUT_))
+ break;
+
+ uether_pause(&sc->sc_ue, hz / 100);
+ } while (((usb_ticks_t)(ticks - start_ticks)) < max_ticks);
+
+ if (val & (ETH_E2P_CMD_BUSY_ | ETH_E2P_CMD_TIMEOUT_)) {
+ muge_warn_printf(sc, "eeprom command failed\n");
+ err = USB_ERR_IOERROR;
+ break;
+ }
+
+ if ((err = lan78xx_read_reg(sc, ETH_E2P_DATA, &val)) != 0)
+ goto done;
+
+ buf[i] = (val & 0xff);
+ }
+
+done:
+ if (!locked)
+ MUGE_UNLOCK(sc);
+ if (sc->chipid == ETH_ID_REV_CHIP_ID_7800_) {
+ /* Restore saved LED configuration. */
+ lan78xx_write_reg(sc, ETH_HW_CFG, saved);
+ }
+ return (err);
+}
+
+static bool
+lan78xx_eeprom_present(struct muge_softc *sc)
+{
+ int ret;
+ uint8_t sig;
+
+ ret = lan78xx_eeprom_read_raw(sc, ETH_E2P_INDICATOR_OFFSET, &sig, 1);
+ return (ret == 0 && sig == ETH_E2P_INDICATOR);
+}
+
+/**
+ * lan78xx_otp_read_raw
+ * @sc: soft context
+ * @off: the otp address offset
+ * @buf: stores the bytes
+ * @buflen: the number of bytes to read
+ *
+ * Simply reads bytes from the OTP.
+ *
+ * LOCKING:
+ * The function takes and releases the device lock if not already held.
+ *
+ * RETURNS:
+ * 0 on success, or a USB_ERR_?? error code on failure.
+ *
+ */
+static int
+lan78xx_otp_read_raw(struct muge_softc *sc, uint16_t off, uint8_t *buf,
+ uint16_t buflen)
+{
+ int err;
+ uint32_t val;
+ uint16_t i;
+ bool locked;
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ MUGE_LOCK(sc);
+
+ err = lan78xx_read_reg(sc, OTP_PWR_DN, &val);
+
+ /* Checking if bit is set. */
+ if (val & OTP_PWR_DN_PWRDN_N) {
+ /* Clear it, then wait for it to be cleared. */
+ lan78xx_write_reg(sc, OTP_PWR_DN, 0);
+ err = lan78xx_wait_for_bits(sc, OTP_PWR_DN, OTP_PWR_DN_PWRDN_N);
+ if (err != 0) {
+ muge_warn_printf(sc, "OTP off? failed to read data\n");
+ goto done;
+ }
+ }
+ /* Start reading the bytes, one at a time. */
+ for (i = 0; i < buflen; i++) {
+ err = lan78xx_write_reg(sc, OTP_ADDR1,
+ ((off + i) >> 8) & OTP_ADDR1_15_11);
+ err = lan78xx_write_reg(sc, OTP_ADDR2,
+ ((off + i) & OTP_ADDR2_10_3));
+ err = lan78xx_write_reg(sc, OTP_FUNC_CMD, OTP_FUNC_CMD_READ_);
+ err = lan78xx_write_reg(sc, OTP_CMD_GO, OTP_CMD_GO_GO_);
+
+ err = lan78xx_wait_for_bits(sc, OTP_STATUS, OTP_STATUS_BUSY_);
+ if (err != 0) {
+ muge_warn_printf(sc, "OTP busy failed to read data\n");
+ goto done;
+ }
+
+ if ((err = lan78xx_read_reg(sc, OTP_RD_DATA, &val)) != 0)
+ goto done;
+
+ buf[i] = (uint8_t)(val & 0xff);
+ }
+
+done:
+ if (!locked)
+ MUGE_UNLOCK(sc);
+ return (err);
+}
+
+/**
+ * lan78xx_otp_read
+ * @sc: soft context
+ * @off: the otp address offset
+ * @buf: stores the bytes
+ * @buflen: the number of bytes to read
+ *
+ * Simply reads bytes from the otp.
+ *
+ * LOCKING:
+ * The function takes and releases device lock if it is not already held.
+ *
+ * RETURNS:
+ * 0 on success, or a USB_ERR_?? error code on failure.
+ */
+static int
+lan78xx_otp_read(struct muge_softc *sc, uint16_t off, uint8_t *buf,
+ uint16_t buflen)
+{
+ uint8_t sig;
+ int err;
+
+ err = lan78xx_otp_read_raw(sc, OTP_INDICATOR_OFFSET, &sig, 1);
+ if (err == 0) {
+ if (sig == OTP_INDICATOR_1) {
+ } else if (sig == OTP_INDICATOR_2) {
+ off += 0x100; /* XXX */
+ } else {
+ err = -EINVAL;
+ }
+ if (!err)
+ err = lan78xx_otp_read_raw(sc, off, buf, buflen);
+ }
+ return (err);
+}
+
+/**
+ * lan78xx_setmacaddress - Set the mac address in the device
+ * @sc: driver soft context
+ * @addr: pointer to array contain at least 6 bytes of the mac
+ *
+ * LOCKING:
+ * Should be called with the MUGE lock held.
+ *
+ * RETURNS:
+ * Returns 0 on success or a negative error code.
+ */
+static int
+lan78xx_setmacaddress(struct muge_softc *sc, const uint8_t *addr)
+{
+ int err;
+ uint32_t val;
+
+ muge_dbg_printf(sc,
+ "setting mac address to %02x:%02x:%02x:%02x:%02x:%02x\n",
+ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ val = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
+ if ((err = lan78xx_write_reg(sc, ETH_RX_ADDRL, val)) != 0)
+ goto done;
+
+ val = (addr[5] << 8) | addr[4];
+ err = lan78xx_write_reg(sc, ETH_RX_ADDRH, val);
+
+done:
+ return (err);
+}
+
+/**
+ * lan78xx_set_rx_max_frame_length
+ * @sc: driver soft context
+ * @size: pointer to array contain at least 6 bytes of the mac
+ *
+ * Sets the maximum frame length to be received. Frames bigger than
+ * this size are aborted.
+ *
+ * RETURNS:
+ * Returns 0 on success or a negative error code.
+ */
+static int
+lan78xx_set_rx_max_frame_length(struct muge_softc *sc, int size)
+{
+ uint32_t buf;
+ bool rxenabled;
+
+ /* First we have to disable rx before changing the length. */
+ lan78xx_read_reg(sc, ETH_MAC_RX, &buf);
+ rxenabled = ((buf & ETH_MAC_RX_EN_) != 0);
+
+ if (rxenabled) {
+ buf &= ~ETH_MAC_RX_EN_;
+ lan78xx_write_reg(sc, ETH_MAC_RX, buf);
+ }
+
+ /* Setting max frame length. */
+ buf &= ~ETH_MAC_RX_MAX_FR_SIZE_MASK_;
+ buf |= (((size + 4) << ETH_MAC_RX_MAX_FR_SIZE_SHIFT_) &
+ ETH_MAC_RX_MAX_FR_SIZE_MASK_);
+ lan78xx_write_reg(sc, ETH_MAC_RX, buf);
+
+ /* If it were enabled before, we enable it back. */
+
+ if (rxenabled) {
+ buf |= ETH_MAC_RX_EN_;
+ lan78xx_write_reg(sc, ETH_MAC_RX, buf);
+ }
+
+ return (0);
+}
+
+/**
+ * lan78xx_miibus_readreg - Read a MII/MDIO register
+ * @dev: usb ether device
+ * @phy: the number of phy reading from
+ * @reg: the register address
+ *
+ * LOCKING:
+ * Takes and releases the device mutex lock if not already held.
+ *
+ * RETURNS:
+ * Returns the 16-bits read from the MII register, if this function fails
+ * 0 is returned.
+ */
+static int
+lan78xx_miibus_readreg(device_t dev, int phy, int reg)
+{
+ struct muge_softc *sc = device_get_softc(dev);
+ uint32_t addr, val;
+ bool locked;
+
+ val = 0;
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ MUGE_LOCK(sc);
+
+ if (lan78xx_wait_for_bits(sc, ETH_MII_ACC, ETH_MII_ACC_MII_BUSY_) !=
+ 0) {
+ muge_warn_printf(sc, "MII is busy\n");
+ goto done;
+ }
+
+ addr = (phy << 11) | (reg << 6) |
+ ETH_MII_ACC_MII_READ_ | ETH_MII_ACC_MII_BUSY_;
+ lan78xx_write_reg(sc, ETH_MII_ACC, addr);
+
+ if (lan78xx_wait_for_bits(sc, ETH_MII_ACC, ETH_MII_ACC_MII_BUSY_) !=
+ 0) {
+ muge_warn_printf(sc, "MII read timeout\n");
+ goto done;
+ }
+
+ lan78xx_read_reg(sc, ETH_MII_DATA, &val);
+ val = le32toh(val);
+
+done:
+ if (!locked)
+ MUGE_UNLOCK(sc);
+
+ return (val & 0xFFFF);
+}
+
+/**
+ * lan78xx_miibus_writereg - Writes a MII/MDIO register
+ * @dev: usb ether device
+ * @phy: the number of phy writing to
+ * @reg: the register address
+ * @val: the value to write
+ *
+ * Attempts to write a PHY register through the usb controller registers.
+ *
+ * LOCKING:
+ * Takes and releases the device mutex lock if not already held.
+ *
+ * RETURNS:
+ * Always returns 0 regardless of success or failure.
+ */
+static int
+lan78xx_miibus_writereg(device_t dev, int phy, int reg, int val)
+{
+ struct muge_softc *sc = device_get_softc(dev);
+ uint32_t addr;
+ bool locked;
+
+ if (sc->sc_phyno != phy)
+ return (0);
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ MUGE_LOCK(sc);
+
+ if (lan78xx_wait_for_bits(sc, ETH_MII_ACC, ETH_MII_ACC_MII_BUSY_) !=
+ 0) {
+ muge_warn_printf(sc, "MII is busy\n");
+ goto done;
+ }
+
+ val = htole32(val);
+ lan78xx_write_reg(sc, ETH_MII_DATA, val);
+
+ addr = (phy << 11) | (reg << 6) |
+ ETH_MII_ACC_MII_WRITE_ | ETH_MII_ACC_MII_BUSY_;
+ lan78xx_write_reg(sc, ETH_MII_ACC, addr);
+
+ if (lan78xx_wait_for_bits(sc, ETH_MII_ACC, ETH_MII_ACC_MII_BUSY_) != 0)
+ muge_warn_printf(sc, "MII write timeout\n");
+
+done:
+ if (!locked)
+ MUGE_UNLOCK(sc);
+ return (0);
+}
+
+/*
+ * lan78xx_miibus_statchg - Called to detect phy status change
+ * @dev: usb ether device
+ *
+ * This function is called periodically by the system to poll for status
+ * changes of the link.
+ *
+ * LOCKING:
+ * Takes and releases the device mutex lock if not already held.
+ */
+static void
+lan78xx_miibus_statchg(device_t dev)
+{
+ struct muge_softc *sc = device_get_softc(dev);
+ struct mii_data *mii = uether_getmii(&sc->sc_ue);
+ if_t ifp;
+ int err;
+ uint32_t flow = 0;
+ uint32_t fct_flow = 0;
+ bool locked;
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ MUGE_LOCK(sc);
+
+ ifp = uether_getifp(&sc->sc_ue);
+ if (mii == NULL || ifp == NULL ||
+ (if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0)
+ goto done;
+
+ /* Use the MII status to determine link status */
+ sc->sc_flags &= ~MUGE_FLAG_LINK;
+ if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) ==
+ (IFM_ACTIVE | IFM_AVALID)) {
+ muge_dbg_printf(sc, "media is active\n");
+ switch (IFM_SUBTYPE(mii->mii_media_active)) {
+ case IFM_10_T:
+ case IFM_100_TX:
+ sc->sc_flags |= MUGE_FLAG_LINK;
+ muge_dbg_printf(sc, "10/100 ethernet\n");
+ break;
+ case IFM_1000_T:
+ sc->sc_flags |= MUGE_FLAG_LINK;
+ muge_dbg_printf(sc, "Gigabit ethernet\n");
+ break;
+ default:
+ break;
+ }
+ }
+ /* Lost link, do nothing. */
+ if ((sc->sc_flags & MUGE_FLAG_LINK) == 0) {
+ muge_dbg_printf(sc, "link flag not set\n");
+ goto done;
+ }
+
+ err = lan78xx_read_reg(sc, ETH_FCT_FLOW, &fct_flow);
+ if (err) {
+ muge_warn_printf(sc,
+ "failed to read initial flow control thresholds, error %d\n",
+ err);
+ goto done;
+ }
+
+ /* Enable/disable full duplex operation and TX/RX pause. */
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) {
+ muge_dbg_printf(sc, "full duplex operation\n");
+
+ /* Enable transmit MAC flow control function. */
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0)
+ flow |= ETH_FLOW_CR_TX_FCEN_ | 0xFFFF;
+
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0)
+ flow |= ETH_FLOW_CR_RX_FCEN_;
+ }
+
+ /* XXX Flow control settings obtained from Microchip's driver. */
+ switch(usbd_get_speed(sc->sc_ue.ue_udev)) {
+ case USB_SPEED_SUPER:
+ fct_flow = 0x817;
+ break;
+ case USB_SPEED_HIGH:
+ fct_flow = 0x211;
+ break;
+ default:
+ break;
+ }
+
+ err += lan78xx_write_reg(sc, ETH_FLOW, flow);
+ err += lan78xx_write_reg(sc, ETH_FCT_FLOW, fct_flow);
+ if (err)
+ muge_warn_printf(sc, "media change failed, error %d\n", err);
+
+done:
+ if (!locked)
+ MUGE_UNLOCK(sc);
+}
+
+/*
+ * lan78xx_set_mdix_auto - Configure the device to enable automatic
+ * crossover and polarity detection. LAN7800 provides HP Auto-MDIX
+ * functionality for seamless crossover and polarity detection.
+ *
+ * @sc: driver soft context
+ *
+ * LOCKING:
+ * Takes and releases the device mutex lock if not already held.
+ */
+static void
+lan78xx_set_mdix_auto(struct muge_softc *sc)
+{
+ uint32_t buf, err;
+
+ err = lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno,
+ MUGE_EXT_PAGE_ACCESS, MUGE_EXT_PAGE_SPACE_1);
+
+ buf = lan78xx_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno,
+ MUGE_EXT_MODE_CTRL);
+ buf &= ~MUGE_EXT_MODE_CTRL_MDIX_MASK_;
+ buf |= MUGE_EXT_MODE_CTRL_AUTO_MDIX_;
+
+ lan78xx_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR);
+ err += lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno,
+ MUGE_EXT_MODE_CTRL, buf);
+
+ err += lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno,
+ MUGE_EXT_PAGE_ACCESS, MUGE_EXT_PAGE_SPACE_0);
+
+ if (err != 0)
+ muge_warn_printf(sc, "error setting PHY's MDIX status\n");
+
+ sc->sc_mdix_ctl = buf;
+}
+
+/**
+ * lan78xx_phy_init - Initialises the in-built MUGE phy
+ * @sc: driver soft context
+ *
+ * Resets the PHY part of the chip and then initialises it to default
+ * values. The 'link down' and 'auto-negotiation complete' interrupts
+ * from the PHY are also enabled, however we don't monitor the interrupt
+ * endpoints for the moment.
+ *
+ * RETURNS:
+ * Returns 0 on success or EIO if failed to reset the PHY.
+ */
+static int
+lan78xx_phy_init(struct muge_softc *sc)
+{
+ muge_dbg_printf(sc, "Initializing PHY.\n");
+ uint16_t bmcr, lmsr;
+ usb_ticks_t start_ticks;
+ uint32_t hw_reg;
+ const usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000);
+
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* Reset phy and wait for reset to complete. */
+ lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR,
+ BMCR_RESET);
+
+ start_ticks = ticks;
+ do {
+ uether_pause(&sc->sc_ue, hz / 100);
+ bmcr = lan78xx_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno,
+ MII_BMCR);
+ } while ((bmcr & BMCR_RESET) && ((ticks - start_ticks) < max_ticks));
+
+ if (((usb_ticks_t)(ticks - start_ticks)) >= max_ticks) {
+ muge_err_printf(sc, "PHY reset timed-out\n");
+ return (EIO);
+ }
+
+ /* Setup phy to interrupt upon link down or autoneg completion. */
+ lan78xx_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno,
+ MUGE_PHY_INTR_STAT);
+ lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno,
+ MUGE_PHY_INTR_MASK,
+ (MUGE_PHY_INTR_ANEG_COMP | MUGE_PHY_INTR_LINK_CHANGE));
+
+ /* Enable Auto-MDIX for crossover and polarity detection. */
+ lan78xx_set_mdix_auto(sc);
+
+ /* Enable all modes. */
+ lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_ANAR,
+ ANAR_10 | ANAR_10_FD | ANAR_TX | ANAR_TX_FD |
+ ANAR_CSMA | ANAR_FC | ANAR_PAUSE_ASYM);
+
+ /* Restart auto-negotiation. */
+ bmcr |= BMCR_STARTNEG;
+ bmcr |= BMCR_AUTOEN;
+ lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR, bmcr);
+ bmcr = lan78xx_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR);
+
+ /* Configure LED Modes. */
+ if (sc->sc_led_modes_mask != 0) {
+ lmsr = lan78xx_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno,
+ MUGE_PHY_LED_MODE);
+ lmsr &= ~sc->sc_led_modes_mask;
+ lmsr |= sc->sc_led_modes;
+ lan78xx_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno,
+ MUGE_PHY_LED_MODE, lmsr);
+ }
+
+ /* Enable appropriate LEDs. */
+ if (sc->sc_leds != 0 &&
+ lan78xx_read_reg(sc, ETH_HW_CFG, &hw_reg) == 0) {
+ hw_reg &= ~(ETH_HW_CFG_LEDO_EN_ | ETH_HW_CFG_LED1_EN_ |
+ ETH_HW_CFG_LED2_EN_ | ETH_HW_CFG_LED3_EN_ );
+ hw_reg |= sc->sc_leds;
+ lan78xx_write_reg(sc, ETH_HW_CFG, hw_reg);
+ }
+ return (0);
+}
+
+/**
+ * lan78xx_chip_init - Initialises the chip after power on
+ * @sc: driver soft context
+ *
+ * This initialisation sequence is modelled on the procedure in the Linux
+ * driver.
+ *
+ * RETURNS:
+ * Returns 0 on success or an error code on failure.
+ */
+static int
+lan78xx_chip_init(struct muge_softc *sc)
+{
+ int err;
+ uint32_t buf;
+ uint32_t burst_cap;
+
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* Enter H/W config mode. */
+ lan78xx_write_reg(sc, ETH_HW_CFG, ETH_HW_CFG_LRST_);
+
+ if ((err = lan78xx_wait_for_bits(sc, ETH_HW_CFG, ETH_HW_CFG_LRST_)) !=
+ 0) {
+ muge_warn_printf(sc,
+ "timed-out waiting for lite reset to complete\n");
+ goto init_failed;
+ }
+
+ /* Set the mac address. */
+ if ((err = lan78xx_setmacaddress(sc, sc->sc_ue.ue_eaddr)) != 0) {
+ muge_warn_printf(sc, "failed to set the MAC address\n");
+ goto init_failed;
+ }
+
+ /* Read and display the revision register. */
+ if ((err = lan78xx_read_reg(sc, ETH_ID_REV, &buf)) < 0) {
+ muge_warn_printf(sc, "failed to read ETH_ID_REV (err = %d)\n",
+ err);
+ goto init_failed;
+ }
+ sc->chipid = (buf & ETH_ID_REV_CHIP_ID_MASK_) >> 16;
+ sc->chiprev = buf & ETH_ID_REV_CHIP_REV_MASK_;
+ switch (sc->chipid) {
+ case ETH_ID_REV_CHIP_ID_7800_:
+ case ETH_ID_REV_CHIP_ID_7850_:
+ break;
+ default:
+ muge_warn_printf(sc, "Chip ID 0x%04x not yet supported\n",
+ sc->chipid);
+ goto init_failed;
+ }
+ device_printf(sc->sc_ue.ue_dev, "Chip ID 0x%04x rev %04x\n", sc->chipid,
+ sc->chiprev);
+
+ /* Respond to BULK-IN tokens with a NAK when RX FIFO is empty. */
+ if ((err = lan78xx_read_reg(sc, ETH_USB_CFG0, &buf)) != 0) {
+ muge_warn_printf(sc, "failed to read ETH_USB_CFG0 (err=%d)\n", err);
+ goto init_failed;
+ }
+ buf |= ETH_USB_CFG_BIR_;
+ lan78xx_write_reg(sc, ETH_USB_CFG0, buf);
+
+ /*
+ * XXX LTM support will go here.
+ */
+
+ /* Configuring the burst cap. */
+ switch (usbd_get_speed(sc->sc_ue.ue_udev)) {
+ case USB_SPEED_SUPER:
+ burst_cap = MUGE_DEFAULT_BURST_CAP_SIZE/MUGE_SS_USB_PKT_SIZE;
+ break;
+ case USB_SPEED_HIGH:
+ burst_cap = MUGE_DEFAULT_BURST_CAP_SIZE/MUGE_HS_USB_PKT_SIZE;
+ break;
+ default:
+ burst_cap = MUGE_DEFAULT_BURST_CAP_SIZE/MUGE_FS_USB_PKT_SIZE;
+ }
+
+ lan78xx_write_reg(sc, ETH_BURST_CAP, burst_cap);
+
+ /* Set the default bulk in delay (same value from Linux driver). */
+ lan78xx_write_reg(sc, ETH_BULK_IN_DLY, MUGE_DEFAULT_BULK_IN_DELAY);
+
+ /* Multiple ethernet frames per USB packets. */
+ err = lan78xx_read_reg(sc, ETH_HW_CFG, &buf);
+ buf |= ETH_HW_CFG_MEF_;
+ err = lan78xx_write_reg(sc, ETH_HW_CFG, buf);
+
+ /* Enable burst cap. */
+ if ((err = lan78xx_read_reg(sc, ETH_USB_CFG0, &buf)) < 0) {
+ muge_warn_printf(sc, "failed to read ETH_USB_CFG0 (err=%d)\n",
+ err);
+ goto init_failed;
+ }
+ buf |= ETH_USB_CFG_BCE_;
+ err = lan78xx_write_reg(sc, ETH_USB_CFG0, buf);
+
+ /*
+ * Set FCL's RX and TX FIFO sizes: according to data sheet this is
+ * already the default value. But we initialize it to the same value
+ * anyways, as that's what the Linux driver does.
+ *
+ */
+ buf = (MUGE_MAX_RX_FIFO_SIZE - 512) / 512;
+ err = lan78xx_write_reg(sc, ETH_FCT_RX_FIFO_END, buf);
+
+ buf = (MUGE_MAX_TX_FIFO_SIZE - 512) / 512;
+ err = lan78xx_write_reg(sc, ETH_FCT_TX_FIFO_END, buf);
+
+ /* Enabling interrupts. (Not using them for now) */
+ err = lan78xx_write_reg(sc, ETH_INT_STS, ETH_INT_STS_CLEAR_ALL_);
+
+ /*
+ * Initializing flow control registers to 0. These registers are
+ * properly set is handled in link-reset function in the Linux driver.
+ */
+ err = lan78xx_write_reg(sc, ETH_FLOW, 0);
+ err = lan78xx_write_reg(sc, ETH_FCT_FLOW, 0);
+
+ /*
+ * Settings for the RFE, we enable broadcast and destination address
+ * perfect filtering.
+ */
+ err = lan78xx_read_reg(sc, ETH_RFE_CTL, &buf);
+ buf |= ETH_RFE_CTL_BCAST_EN_ | ETH_RFE_CTL_DA_PERFECT_;
+ err = lan78xx_write_reg(sc, ETH_RFE_CTL, buf);
+
+ /*
+ * At this point the Linux driver writes multicast tables, and enables
+ * checksum engines. But in FreeBSD that gets done in muge_init,
+ * which gets called when the interface is brought up.
+ */
+
+ /* Reset the PHY. */
+ lan78xx_write_reg(sc, ETH_PMT_CTL, ETH_PMT_CTL_PHY_RST_);
+ if ((err = lan78xx_wait_for_bits(sc, ETH_PMT_CTL,
+ ETH_PMT_CTL_PHY_RST_)) != 0) {
+ muge_warn_printf(sc,
+ "timed-out waiting for phy reset to complete\n");
+ goto init_failed;
+ }
+
+ err = lan78xx_read_reg(sc, ETH_MAC_CR, &buf);
+ if (sc->chipid == ETH_ID_REV_CHIP_ID_7800_ &&
+ !lan78xx_eeprom_present(sc)) {
+ /* Set automatic duplex and speed on LAN7800 without EEPROM. */
+ buf |= ETH_MAC_CR_AUTO_DUPLEX_ | ETH_MAC_CR_AUTO_SPEED_;
+ }
+ err = lan78xx_write_reg(sc, ETH_MAC_CR, buf);
+
+ /*
+ * Enable PHY interrupts (Not really getting used for now)
+ * ETH_INT_EP_CTL: interrupt endpoint control register
+ * phy events cause interrupts to be issued
+ */
+ err = lan78xx_read_reg(sc, ETH_INT_EP_CTL, &buf);
+ buf |= ETH_INT_ENP_PHY_INT;
+ err = lan78xx_write_reg(sc, ETH_INT_EP_CTL, buf);
+
+ /*
+ * Enables mac's transmitter. It will transmit frames from the buffer
+ * onto the cable.
+ */
+ err = lan78xx_read_reg(sc, ETH_MAC_TX, &buf);
+ buf |= ETH_MAC_TX_TXEN_;
+ err = lan78xx_write_reg(sc, ETH_MAC_TX, buf);
+
+ /* FIFO is capable of transmitting frames to MAC. */
+ err = lan78xx_read_reg(sc, ETH_FCT_TX_CTL, &buf);
+ buf |= ETH_FCT_TX_CTL_EN_;
+ err = lan78xx_write_reg(sc, ETH_FCT_TX_CTL, buf);
+
+ /*
+ * Set max frame length. In linux this is dev->mtu (which by default
+ * is 1500) + VLAN_ETH_HLEN = 1518.
+ */
+ err = lan78xx_set_rx_max_frame_length(sc, ETHER_MAX_LEN);
+
+ /* Initialise the PHY. */
+ if ((err = lan78xx_phy_init(sc)) != 0)
+ goto init_failed;
+
+ /* Enable MAC RX. */
+ err = lan78xx_read_reg(sc, ETH_MAC_RX, &buf);
+ buf |= ETH_MAC_RX_EN_;
+ err = lan78xx_write_reg(sc, ETH_MAC_RX, buf);
+
+ /* Enable FIFO controller RX. */
+ err = lan78xx_read_reg(sc, ETH_FCT_RX_CTL, &buf);
+ buf |= ETH_FCT_TX_CTL_EN_;
+ err = lan78xx_write_reg(sc, ETH_FCT_RX_CTL, buf);
+
+ sc->sc_flags |= MUGE_FLAG_INIT_DONE;
+ return (0);
+
+init_failed:
+ muge_err_printf(sc, "lan78xx_chip_init failed (err=%d)\n", err);
+ return (err);
+}
+
+static void
+muge_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct muge_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_ether *ue = &sc->sc_ue;
+ if_t ifp = uether_getifp(ue);
+ struct mbuf *m;
+ struct usb_page_cache *pc;
+ uint32_t rx_cmd_a, rx_cmd_b;
+ uint16_t rx_cmd_c;
+ int pktlen;
+ int off;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+ muge_dbg_printf(sc, "rx : actlen %d\n", actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ /*
+ * There is always a zero length frame after bringing the
+ * interface up.
+ */
+ if (actlen < (sizeof(rx_cmd_a) + ETHER_CRC_LEN))
+ goto tr_setup;
+
+ /*
+ * There may be multiple packets in the USB frame. Each will
+ * have a header and each needs to have its own mbuf allocated
+ * and populated for it.
+ */
+ pc = usbd_xfer_get_frame(xfer, 0);
+ off = 0;
+
+ while (off < actlen) {
+ /* The frame header is aligned on a 4 byte boundary. */
+ off = ((off + 0x3) & ~0x3);
+
+ /* Extract RX CMD A. */
+ if (off + sizeof(rx_cmd_a) > actlen)
+ goto tr_setup;
+ usbd_copy_out(pc, off, &rx_cmd_a, sizeof(rx_cmd_a));
+ off += (sizeof(rx_cmd_a));
+ rx_cmd_a = le32toh(rx_cmd_a);
+
+ /* Extract RX CMD B. */
+ if (off + sizeof(rx_cmd_b) > actlen)
+ goto tr_setup;
+ usbd_copy_out(pc, off, &rx_cmd_b, sizeof(rx_cmd_b));
+ off += (sizeof(rx_cmd_b));
+ rx_cmd_b = le32toh(rx_cmd_b);
+
+ /* Extract RX CMD C. */
+ if (off + sizeof(rx_cmd_c) > actlen)
+ goto tr_setup;
+ usbd_copy_out(pc, off, &rx_cmd_c, sizeof(rx_cmd_c));
+ off += (sizeof(rx_cmd_c));
+ rx_cmd_c = le16toh(rx_cmd_c);
+
+ if (off > actlen)
+ goto tr_setup;
+
+ pktlen = (rx_cmd_a & RX_CMD_A_LEN_MASK_);
+
+ muge_dbg_printf(sc,
+ "rx_cmd_a 0x%08x rx_cmd_b 0x%08x rx_cmd_c 0x%04x "
+ " pktlen %d actlen %d off %d\n",
+ rx_cmd_a, rx_cmd_b, rx_cmd_c, pktlen, actlen, off);
+
+ if (rx_cmd_a & RX_CMD_A_RED_) {
+ muge_dbg_printf(sc,
+ "rx error (hdr 0x%08x)\n", rx_cmd_a);
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ } else {
+ /* Ethernet frame too big or too small? */
+ if ((pktlen < ETHER_HDR_LEN) ||
+ (pktlen > (actlen - off)))
+ goto tr_setup;
+
+ /* Create a new mbuf to store the packet. */
+ m = uether_newbuf();
+ if (m == NULL) {
+ muge_warn_printf(sc,
+ "failed to create new mbuf\n");
+ if_inc_counter(ifp, IFCOUNTER_IQDROPS,
+ 1);
+ goto tr_setup;
+ }
+ if (pktlen > m->m_len) {
+ muge_dbg_printf(sc,
+ "buffer too small %d vs %d bytes",
+ pktlen, m->m_len);
+ if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
+ m_freem(m);
+ goto tr_setup;
+ }
+ usbd_copy_out(pc, off, mtod(m, uint8_t *),
+ pktlen);
+
+ /*
+ * Check if RX checksums are computed, and
+ * offload them
+ */
+ if ((if_getcapenable(ifp) & IFCAP_RXCSUM) &&
+ !(rx_cmd_a & RX_CMD_A_ICSM_)) {
+ /*
+ * Remove the extra 2 bytes of the csum
+ *
+ * The checksum appears to be
+ * simplistically calculated over the
+ * protocol headers up to the end of the
+ * eth frame. Which means if the eth
+ * frame is padded the csum calculation
+ * is incorrectly performed over the
+ * padding bytes as well. Therefore to
+ * be safe we ignore the H/W csum on
+ * frames less than or equal to
+ * 64 bytes.
+ *
+ * Protocols checksummed:
+ * TCP, UDP, ICMP, IGMP, IP
+ */
+ if (pktlen > ETHER_MIN_LEN) {
+ m->m_pkthdr.csum_flags |=
+ CSUM_DATA_VALID |
+ CSUM_PSEUDO_HDR;
+
+ /*
+ * Copy the checksum from the
+ * last 2 bytes of the transfer
+ * and put in the csum_data
+ * field.
+ */
+ usbd_copy_out(pc,
+ (off + pktlen),
+ &m->m_pkthdr.csum_data, 2);
+
+ /*
+ * The data is copied in network
+ * order, but the csum algorithm
+ * in the kernel expects it to
+ * be in host network order.
+ */
+ m->m_pkthdr.csum_data =
+ ntohs(0xffff);
+
+ muge_dbg_printf(sc,
+ "RX checksum offloaded (0x%04x)\n",
+ m->m_pkthdr.csum_data);
+ }
+ }
+
+ /* Enqueue the mbuf on the receive queue. */
+ if (pktlen < (4 + ETHER_HDR_LEN)) {
+ m_freem(m);
+ goto tr_setup;
+ }
+ /* Remove 4 trailing bytes */
+ uether_rxmbuf(ue, m, pktlen - 4);
+ }
+
+ /*
+ * Update the offset to move to the next potential
+ * packet.
+ */
+ off += pktlen;
+ }
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ uether_rxflush(ue);
+ return;
+ default:
+ if (error != USB_ERR_CANCELLED) {
+ muge_warn_printf(sc, "bulk read error, %s\n",
+ usbd_errstr(error));
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+/**
+ * muge_bulk_write_callback - Write callback used to send ethernet frame(s)
+ * @xfer: the USB transfer
+ * @error: error code if the transfers is in an errored state
+ *
+ * The main write function that pulls ethernet frames off the queue and
+ * sends them out.
+ *
+ */
+static void
+muge_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct muge_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ int nframes;
+ uint32_t frm_len = 0, tx_cmd_a = 0, tx_cmd_b = 0;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ muge_dbg_printf(sc,
+ "USB TRANSFER status: USB_ST_TRANSFERRED\n");
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+ muge_dbg_printf(sc, "USB TRANSFER status: USB_ST_SETUP\n");
+tr_setup:
+ if ((sc->sc_flags & MUGE_FLAG_LINK) == 0 ||
+ (if_getdrvflags(ifp) & IFF_DRV_OACTIVE) != 0) {
+ muge_dbg_printf(sc,
+ "sc->sc_flags & MUGE_FLAG_LINK: %d\n",
+ (sc->sc_flags & MUGE_FLAG_LINK));
+ muge_dbg_printf(sc,
+ "if_getdrvflags(ifp) & IFF_DRV_OACTIVE: %d",
+ (if_getdrvflags(ifp) & IFF_DRV_OACTIVE));
+ muge_dbg_printf(sc,
+ "USB TRANSFER not sending: no link or controller is busy \n");
+ /*
+ * Don't send anything if there is no link or
+ * controller is busy.
+ */
+ return;
+ }
+ for (nframes = 0;
+ nframes < 16 && !if_sendq_empty(ifp);
+ nframes++) {
+ m = if_dequeue(ifp);
+ if (m == NULL)
+ break;
+ usbd_xfer_set_frame_offset(xfer, nframes * MCLBYTES,
+ nframes);
+ frm_len = 0;
+ pc = usbd_xfer_get_frame(xfer, nframes);
+
+ /*
+ * Each frame is prefixed with two 32-bit values
+ * describing the length of the packet and buffer.
+ */
+ tx_cmd_a = (m->m_pkthdr.len & TX_CMD_A_LEN_MASK_) |
+ TX_CMD_A_FCS_;
+ tx_cmd_a = htole32(tx_cmd_a);
+ usbd_copy_in(pc, 0, &tx_cmd_a, sizeof(tx_cmd_a));
+
+ tx_cmd_b = 0;
+
+ /* TCP LSO Support will probably be implemented here. */
+ tx_cmd_b = htole32(tx_cmd_b);
+ usbd_copy_in(pc, 4, &tx_cmd_b, sizeof(tx_cmd_b));
+
+ frm_len += 8;
+
+ /* Next copy in the actual packet */
+ usbd_m_copy_in(pc, frm_len, m, 0, m->m_pkthdr.len);
+ frm_len += m->m_pkthdr.len;
+
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /*
+ * If there's a BPF listener, bounce a copy of this
+ * frame to it.
+ */
+ BPF_MTAP(ifp, m);
+ m_freem(m);
+
+ /* Set frame length. */
+ usbd_xfer_set_frame_len(xfer, nframes, frm_len);
+ }
+
+ muge_dbg_printf(sc, "USB TRANSFER nframes: %d\n", nframes);
+ if (nframes != 0) {
+ muge_dbg_printf(sc, "USB TRANSFER submit attempt\n");
+ usbd_xfer_set_frames(xfer, nframes);
+ usbd_transfer_submit(xfer);
+ if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0);
+ }
+ return;
+
+ default:
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+
+ if (error != USB_ERR_CANCELLED) {
+ muge_err_printf(sc,
+ "usb error on tx: %s\n", usbd_errstr(error));
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+/**
+ * muge_set_mac_addr - Initiailizes NIC MAC address
+ * @ue: the USB ethernet device
+ *
+ * Tries to obtain MAC address from number of sources: registers,
+ * EEPROM, DTB blob. If all sources fail - generates random MAC.
+ */
+static void
+muge_set_mac_addr(struct usb_ether *ue)
+{
+ struct muge_softc *sc = uether_getsc(ue);
+ uint32_t mac_h, mac_l;
+
+ memset(ue->ue_eaddr, 0xff, ETHER_ADDR_LEN);
+
+ uint32_t val;
+ lan78xx_read_reg(sc, 0, &val);
+
+ /* Read current MAC address from RX_ADDRx registers. */
+ if ((lan78xx_read_reg(sc, ETH_RX_ADDRL, &mac_l) == 0) &&
+ (lan78xx_read_reg(sc, ETH_RX_ADDRH, &mac_h) == 0)) {
+ ue->ue_eaddr[5] = (uint8_t)((mac_h >> 8) & 0xff);
+ ue->ue_eaddr[4] = (uint8_t)((mac_h) & 0xff);
+ ue->ue_eaddr[3] = (uint8_t)((mac_l >> 24) & 0xff);
+ ue->ue_eaddr[2] = (uint8_t)((mac_l >> 16) & 0xff);
+ ue->ue_eaddr[1] = (uint8_t)((mac_l >> 8) & 0xff);
+ ue->ue_eaddr[0] = (uint8_t)((mac_l) & 0xff);
+ }
+
+ /*
+ * If RX_ADDRx did not provide a valid MAC address, try EEPROM. If that
+ * doesn't work, try OTP. Whether any of these methods work or not, try
+ * FDT data, because it is allowed to override the EEPROM/OTP values.
+ */
+ if (ETHER_IS_VALID(ue->ue_eaddr)) {
+ muge_dbg_printf(sc, "MAC assigned from registers\n");
+ } else if (lan78xx_eeprom_present(sc) && lan78xx_eeprom_read_raw(sc,
+ ETH_E2P_MAC_OFFSET, ue->ue_eaddr, ETHER_ADDR_LEN) == 0 &&
+ ETHER_IS_VALID(ue->ue_eaddr)) {
+ muge_dbg_printf(sc, "MAC assigned from EEPROM\n");
+ } else if (lan78xx_otp_read(sc, OTP_MAC_OFFSET, ue->ue_eaddr,
+ ETHER_ADDR_LEN) == 0 && ETHER_IS_VALID(ue->ue_eaddr)) {
+ muge_dbg_printf(sc, "MAC assigned from OTP\n");
+ }
+
+#ifdef FDT
+ /* ue->ue_eaddr modified only if config exists for this dev instance. */
+ usb_fdt_get_mac_addr(ue->ue_dev, ue);
+ if (ETHER_IS_VALID(ue->ue_eaddr)) {
+ muge_dbg_printf(sc, "MAC assigned from FDT data\n");
+ }
+#endif
+
+ if (!ETHER_IS_VALID(ue->ue_eaddr)) {
+ muge_dbg_printf(sc, "MAC assigned randomly\n");
+ arc4rand(ue->ue_eaddr, ETHER_ADDR_LEN, 0);
+ ue->ue_eaddr[0] &= ~0x01; /* unicast */
+ ue->ue_eaddr[0] |= 0x02; /* locally administered */
+ }
+}
+
+/**
+ * muge_set_leds - Initializes NIC LEDs pattern
+ * @ue: the USB ethernet device
+ *
+ * Tries to store the LED modes.
+ * Supports only DTB blob like the Linux driver does.
+ */
+static void
+muge_set_leds(struct usb_ether *ue)
+{
+#ifdef FDT
+ struct muge_softc *sc = uether_getsc(ue);
+ phandle_t node;
+ pcell_t modes[4]; /* 4 LEDs are possible */
+ ssize_t proplen;
+ uint32_t count;
+
+ if ((node = usb_fdt_get_node(ue->ue_dev, ue->ue_udev)) != -1 &&
+ (proplen = OF_getencprop(node, "microchip,led-modes", modes,
+ sizeof(modes))) > 0) {
+ count = proplen / sizeof( uint32_t );
+ sc->sc_leds = (count > 0) * ETH_HW_CFG_LEDO_EN_ |
+ (count > 1) * ETH_HW_CFG_LED1_EN_ |
+ (count > 2) * ETH_HW_CFG_LED2_EN_ |
+ (count > 3) * ETH_HW_CFG_LED3_EN_;
+ while (count-- > 0) {
+ sc->sc_led_modes |= (modes[count] & 0xf) << (4 * count);
+ sc->sc_led_modes_mask |= 0xf << (4 * count);
+ }
+ muge_dbg_printf(sc, "LED modes set from FDT data\n");
+ }
+#endif
+}
+
+/**
+ * muge_attach_post - Called after the driver attached to the USB interface
+ * @ue: the USB ethernet device
+ *
+ * This is where the chip is intialised for the first time. This is
+ * different from the muge_init() function in that that one is designed to
+ * setup the H/W to match the UE settings and can be called after a reset.
+ *
+ */
+static void
+muge_attach_post(struct usb_ether *ue)
+{
+ struct muge_softc *sc = uether_getsc(ue);
+
+ muge_dbg_printf(sc, "Calling muge_attach_post.\n");
+
+ /* Setup some of the basics */
+ sc->sc_phyno = 1;
+
+ muge_set_mac_addr(ue);
+ muge_set_leds(ue);
+
+ /* Initialise the chip for the first time */
+ lan78xx_chip_init(sc);
+}
+
+/**
+ * muge_attach_post_sub - Called after attach to the USB interface
+ * @ue: the USB ethernet device
+ *
+ * Most of this is boilerplate code and copied from the base USB ethernet
+ * driver. It has been overridden so that we can indicate to the system
+ * that the chip supports H/W checksumming.
+ *
+ * RETURNS:
+ * Returns 0 on success or a negative error code.
+ */
+static int
+muge_attach_post_sub(struct usb_ether *ue)
+{
+ struct muge_softc *sc;
+ if_t ifp;
+
+ sc = uether_getsc(ue);
+ muge_dbg_printf(sc, "Calling muge_attach_post_sub.\n");
+ ifp = ue->ue_ifp;
+ if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
+ if_setstartfn(ifp, uether_start);
+ if_setioctlfn(ifp, muge_ioctl);
+ if_setinitfn(ifp, uether_init);
+ if_setsendqlen(ifp, ifqmaxlen);
+ if_setsendqready(ifp);
+
+ /*
+ * The chip supports TCP/UDP checksum offloading on TX and RX paths,
+ * however currently only RX checksum is supported in the driver
+ * (see top of file).
+ */
+ if_setcapabilitiesbit(ifp, IFCAP_VLAN_MTU, 0);
+ if_sethwassist(ifp, 0);
+ if_setcapabilitiesbit(ifp, IFCAP_RXCSUM, 0);
+
+ if (MUGE_DEFAULT_TX_CSUM_ENABLE)
+ if_setcapabilitiesbit(ifp, IFCAP_TXCSUM, 0);
+
+ /*
+ * In the Linux driver they also enable scatter/gather (NETIF_F_SG)
+ * here, that's something related to socket buffers used in Linux.
+ * FreeBSD doesn't have that as an interface feature.
+ */
+ if (MUGE_DEFAULT_TSO_ENABLE)
+ if_setcapabilitiesbit(ifp, IFCAP_TSO4 | IFCAP_TSO6, 0);
+
+#if 0
+ /* TX checksuming is disabled since not yet implemented. */
+ if_setcapabilitiesbit(ifp, IFCAP_TXCSUM, 0);
+ if_setcapenablebit(ifp, IFCAP_TXCSUM, 0);
+ if_sethwassist(ifp, CSUM_TCP | CSUM_UDP);
+#endif
+
+ if_setcapenable(ifp, if_getcapabilities(ifp));
+
+ bus_topo_lock();
+ mii_attach(ue->ue_dev, &ue->ue_miibus, ifp, uether_ifmedia_upd,
+ ue->ue_methods->ue_mii_sts, BMSR_DEFCAPMASK, sc->sc_phyno,
+ MII_OFFSET_ANY, 0);
+ bus_topo_unlock();
+
+ return (0);
+}
+
+/**
+ * muge_start - Starts communication with the LAN78xx chip
+ * @ue: USB ether interface
+ */
+static void
+muge_start(struct usb_ether *ue)
+{
+ struct muge_softc *sc = uether_getsc(ue);
+
+ /*
+ * Start the USB transfers, if not already started.
+ */
+ usbd_transfer_start(sc->sc_xfer[MUGE_BULK_DT_RD]);
+ usbd_transfer_start(sc->sc_xfer[MUGE_BULK_DT_WR]);
+}
+
+/**
+ * muge_ioctl - ioctl function for the device
+ * @ifp: interface pointer
+ * @cmd: the ioctl command
+ * @data: data passed in the ioctl call, typically a pointer to struct
+ * ifreq.
+ *
+ * The ioctl routine is overridden to detect change requests for the H/W
+ * checksum capabilities.
+ *
+ * RETURNS:
+ * 0 on success and an error code on failure.
+ */
+static int
+muge_ioctl(if_t ifp, u_long cmd, caddr_t data)
+{
+ struct usb_ether *ue = if_getsoftc(ifp);
+ struct muge_softc *sc;
+ struct ifreq *ifr;
+ int rc;
+ int mask;
+ int reinit;
+
+ if (cmd == SIOCSIFCAP) {
+ sc = uether_getsc(ue);
+ ifr = (struct ifreq *)data;
+
+ MUGE_LOCK(sc);
+
+ rc = 0;
+ reinit = 0;
+
+ mask = ifr->ifr_reqcap ^ if_getcapenable(ifp);
+
+ /* Modify the RX CSUM enable bits. */
+ if ((mask & IFCAP_RXCSUM) != 0 &&
+ (if_getcapabilities(ifp) & IFCAP_RXCSUM) != 0) {
+ if_togglecapenable(ifp, IFCAP_RXCSUM);
+
+ if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) {
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+ reinit = 1;
+ }
+ }
+
+ MUGE_UNLOCK(sc);
+ if (reinit)
+ uether_init(ue);
+ } else {
+ rc = uether_ioctl(ifp, cmd, data);
+ }
+
+ return (rc);
+}
+
+/**
+ * muge_reset - Reset the SMSC chip
+ * @sc: device soft context
+ *
+ * LOCKING:
+ * Should be called with the SMSC lock held.
+ */
+static void
+muge_reset(struct muge_softc *sc)
+{
+ struct usb_config_descriptor *cd;
+ usb_error_t err;
+
+ cd = usbd_get_config_descriptor(sc->sc_ue.ue_udev);
+
+ err = usbd_req_set_config(sc->sc_ue.ue_udev, &sc->sc_mtx,
+ cd->bConfigurationValue);
+ if (err)
+ muge_warn_printf(sc, "reset failed (ignored)\n");
+
+ /* Wait a little while for the chip to get its brains in order. */
+ uether_pause(&sc->sc_ue, hz / 100);
+
+ /* Reinitialize controller to achieve full reset. */
+ lan78xx_chip_init(sc);
+}
+
+/**
+ * muge_set_addr_filter
+ *
+ * @sc: device soft context
+ * @index: index of the entry to the perfect address table
+ * @addr: address to be written
+ *
+ */
+static void
+muge_set_addr_filter(struct muge_softc *sc, int index,
+ uint8_t addr[ETHER_ADDR_LEN])
+{
+ uint32_t tmp;
+
+ if ((sc) && (index > 0) && (index < MUGE_NUM_PFILTER_ADDRS_)) {
+ tmp = addr[3];
+ tmp |= addr[2] | (tmp << 8);
+ tmp |= addr[1] | (tmp << 8);
+ tmp |= addr[0] | (tmp << 8);
+ sc->sc_pfilter_table[index][1] = tmp;
+ tmp = addr[5];
+ tmp |= addr[4] | (tmp << 8);
+ tmp |= ETH_MAF_HI_VALID_ | ETH_MAF_HI_TYPE_DST_;
+ sc->sc_pfilter_table[index][0] = tmp;
+ }
+}
+
+/**
+ * lan78xx_dataport_write - write to the selected RAM
+ * @sc: The device soft context.
+ * @ram_select: Select which RAM to access.
+ * @addr: Starting address to write to.
+ * @buf: word-sized buffer to write to RAM, starting at @addr.
+ * @length: length of @buf
+ *
+ *
+ * RETURNS:
+ * 0 if write successful.
+ */
+static int
+lan78xx_dataport_write(struct muge_softc *sc, uint32_t ram_select,
+ uint32_t addr, uint32_t length, uint32_t *buf)
+{
+ uint32_t dp_sel;
+ int i, ret;
+
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+ ret = lan78xx_wait_for_bits(sc, ETH_DP_SEL, ETH_DP_SEL_DPRDY_);
+ if (ret < 0)
+ goto done;
+
+ ret = lan78xx_read_reg(sc, ETH_DP_SEL, &dp_sel);
+
+ dp_sel &= ~ETH_DP_SEL_RSEL_MASK_;
+ dp_sel |= ram_select;
+
+ ret = lan78xx_write_reg(sc, ETH_DP_SEL, dp_sel);
+
+ for (i = 0; i < length; i++) {
+ ret = lan78xx_write_reg(sc, ETH_DP_ADDR, addr + i);
+ ret = lan78xx_write_reg(sc, ETH_DP_DATA, buf[i]);
+ ret = lan78xx_write_reg(sc, ETH_DP_CMD, ETH_DP_CMD_WRITE_);
+ ret = lan78xx_wait_for_bits(sc, ETH_DP_SEL, ETH_DP_SEL_DPRDY_);
+ if (ret != 0)
+ goto done;
+ }
+
+done:
+ return (ret);
+}
+
+/**
+ * muge_multicast_write
+ * @sc: device's soft context
+ *
+ * Writes perfect address filters and hash address filters to their
+ * corresponding registers and RAMs.
+ *
+ */
+static void
+muge_multicast_write(struct muge_softc *sc)
+{
+ int i;
+ lan78xx_dataport_write(sc, ETH_DP_SEL_RSEL_VLAN_DA_,
+ ETH_DP_SEL_VHF_VLAN_LEN, ETH_DP_SEL_VHF_HASH_LEN,
+ sc->sc_mchash_table);
+
+ for (i = 1; i < MUGE_NUM_PFILTER_ADDRS_; i++) {
+ lan78xx_write_reg(sc, PFILTER_HI(i), 0);
+ lan78xx_write_reg(sc, PFILTER_LO(i),
+ sc->sc_pfilter_table[i][1]);
+ lan78xx_write_reg(sc, PFILTER_HI(i),
+ sc->sc_pfilter_table[i][0]);
+ }
+}
+
+/**
+ * muge_hash - Calculate the hash of a mac address
+ * @addr: The mac address to calculate the hash on
+ *
+ * This function is used when configuring a range of multicast mac
+ * addresses to filter on. The hash of the mac address is put in the
+ * device's mac hash table.
+ *
+ * RETURNS:
+ * Returns a value from 0-63 value which is the hash of the mac address.
+ */
+static inline uint32_t
+muge_hash(uint8_t addr[ETHER_ADDR_LEN])
+{
+ return (ether_crc32_be(addr, ETHER_ADDR_LEN) >> 23) & 0x1ff;
+}
+
+static u_int
+muge_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
+{
+ struct muge_softc *sc = arg;
+ uint32_t bitnum;
+
+ /* First fill up the perfect address table. */
+ if (cnt < 32 /* XXX */)
+ muge_set_addr_filter(sc, cnt + 1, LLADDR(sdl));
+ else {
+ bitnum = muge_hash(LLADDR(sdl));
+ sc->sc_mchash_table[bitnum / 32] |= (1 << (bitnum % 32));
+ sc->sc_rfe_ctl |= ETH_RFE_CTL_MCAST_HASH_;
+ }
+
+ return (1);
+}
+
+/**
+ * muge_setmulti - Setup multicast
+ * @ue: usb ethernet device context
+ *
+ * Tells the device to either accept frames with a multicast mac address,
+ * a select group of m'cast mac addresses or just the devices mac address.
+ *
+ * LOCKING:
+ * Should be called with the MUGE lock held.
+ */
+static void
+muge_setmulti(struct usb_ether *ue)
+{
+ struct muge_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ uint8_t i;
+
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ sc->sc_rfe_ctl &= ~(ETH_RFE_CTL_UCAST_EN_ | ETH_RFE_CTL_MCAST_EN_ |
+ ETH_RFE_CTL_DA_PERFECT_ | ETH_RFE_CTL_MCAST_HASH_);
+
+ /* Initialize hash filter table. */
+ for (i = 0; i < ETH_DP_SEL_VHF_HASH_LEN; i++)
+ sc->sc_mchash_table[i] = 0;
+
+ /* Initialize perfect filter table. */
+ for (i = 1; i < MUGE_NUM_PFILTER_ADDRS_; i++) {
+ sc->sc_pfilter_table[i][0] = sc->sc_pfilter_table[i][1] = 0;
+ }
+
+ sc->sc_rfe_ctl |= ETH_RFE_CTL_BCAST_EN_;
+
+ if (if_getflags(ifp) & IFF_PROMISC) {
+ muge_dbg_printf(sc, "promiscuous mode enabled\n");
+ sc->sc_rfe_ctl |= ETH_RFE_CTL_MCAST_EN_ | ETH_RFE_CTL_UCAST_EN_;
+ } else if (if_getflags(ifp) & IFF_ALLMULTI) {
+ muge_dbg_printf(sc, "receive all multicast enabled\n");
+ sc->sc_rfe_ctl |= ETH_RFE_CTL_MCAST_EN_;
+ } else {
+ if_foreach_llmaddr(ifp, muge_hash_maddr, sc);
+ muge_multicast_write(sc);
+ }
+ lan78xx_write_reg(sc, ETH_RFE_CTL, sc->sc_rfe_ctl);
+}
+
+/**
+ * muge_setpromisc - Enables/disables promiscuous mode
+ * @ue: usb ethernet device context
+ *
+ * LOCKING:
+ * Should be called with the MUGE lock held.
+ */
+static void
+muge_setpromisc(struct usb_ether *ue)
+{
+ struct muge_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ muge_dbg_printf(sc, "promiscuous mode %sabled\n",
+ (if_getflags(ifp) & IFF_PROMISC) ? "en" : "dis");
+
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (if_getflags(ifp) & IFF_PROMISC)
+ sc->sc_rfe_ctl |= ETH_RFE_CTL_MCAST_EN_ | ETH_RFE_CTL_UCAST_EN_;
+ else
+ sc->sc_rfe_ctl &= ~(ETH_RFE_CTL_MCAST_EN_);
+
+ lan78xx_write_reg(sc, ETH_RFE_CTL, sc->sc_rfe_ctl);
+}
+
+/**
+ * muge_sethwcsum - Enable or disable H/W UDP and TCP checksumming
+ * @sc: driver soft context
+ *
+ * LOCKING:
+ * Should be called with the MUGE lock held.
+ *
+ * RETURNS:
+ * Returns 0 on success or a negative error code.
+ */
+static int
+muge_sethwcsum(struct muge_softc *sc)
+{
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ int err;
+
+ if (!ifp)
+ return (-EIO);
+
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (if_getcapenable(ifp) & IFCAP_RXCSUM) {
+ sc->sc_rfe_ctl |= ETH_RFE_CTL_IGMP_COE_ | ETH_RFE_CTL_ICMP_COE_;
+ sc->sc_rfe_ctl |= ETH_RFE_CTL_TCPUDP_COE_ | ETH_RFE_CTL_IP_COE_;
+ } else {
+ sc->sc_rfe_ctl &=
+ ~(ETH_RFE_CTL_IGMP_COE_ | ETH_RFE_CTL_ICMP_COE_);
+ sc->sc_rfe_ctl &=
+ ~(ETH_RFE_CTL_TCPUDP_COE_ | ETH_RFE_CTL_IP_COE_);
+ }
+
+ sc->sc_rfe_ctl &= ~ETH_RFE_CTL_VLAN_FILTER_;
+
+ err = lan78xx_write_reg(sc, ETH_RFE_CTL, sc->sc_rfe_ctl);
+
+ if (err != 0) {
+ muge_warn_printf(sc, "failed to write ETH_RFE_CTL (err=%d)\n",
+ err);
+ return (err);
+ }
+
+ return (0);
+}
+
+/**
+ * muge_ifmedia_upd - Set media options
+ * @ifp: interface pointer
+ *
+ * Basically boilerplate code that simply calls the mii functions to set
+ * the media options.
+ *
+ * LOCKING:
+ * The device lock must be held before this function is called.
+ *
+ * RETURNS:
+ * Returns 0 on success or a negative error code.
+ */
+static int
+muge_ifmedia_upd(if_t ifp)
+{
+ struct muge_softc *sc = if_getsoftc(ifp);
+ muge_dbg_printf(sc, "Calling muge_ifmedia_upd.\n");
+ struct mii_data *mii = uether_getmii(&sc->sc_ue);
+ struct mii_softc *miisc;
+ int err;
+
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list)
+ PHY_RESET(miisc);
+ err = mii_mediachg(mii);
+ return (err);
+}
+
+/**
+ * muge_init - Initialises the LAN95xx chip
+ * @ue: USB ether interface
+ *
+ * Called when the interface is brought up (i.e. ifconfig ue0 up), this
+ * initialise the interface and the rx/tx pipes.
+ *
+ * LOCKING:
+ * Should be called with the MUGE lock held.
+ */
+static void
+muge_init(struct usb_ether *ue)
+{
+ struct muge_softc *sc = uether_getsc(ue);
+ muge_dbg_printf(sc, "Calling muge_init.\n");
+ if_t ifp = uether_getifp(ue);
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (lan78xx_setmacaddress(sc, if_getlladdr(ifp)))
+ muge_dbg_printf(sc, "setting MAC address failed\n");
+
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0)
+ return;
+
+ /* Cancel pending I/O. */
+ muge_stop(ue);
+
+ /* Reset the ethernet interface. */
+ muge_reset(sc);
+
+ /* Load the multicast filter. */
+ muge_setmulti(ue);
+
+ /* TCP/UDP checksum offload engines. */
+ muge_sethwcsum(sc);
+
+ usbd_xfer_set_stall(sc->sc_xfer[MUGE_BULK_DT_WR]);
+
+ /* Indicate we are up and running. */
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+
+ /* Switch to selected media. */
+ muge_ifmedia_upd(ifp);
+ muge_start(ue);
+}
+
+/**
+ * muge_stop - Stops communication with the LAN78xx chip
+ * @ue: USB ether interface
+ */
+static void
+muge_stop(struct usb_ether *ue)
+{
+ struct muge_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, 0, (IFF_DRV_RUNNING | IFF_DRV_OACTIVE));
+ sc->sc_flags &= ~MUGE_FLAG_LINK;
+
+ /*
+ * Stop all the transfers, if not already stopped.
+ */
+ usbd_transfer_stop(sc->sc_xfer[MUGE_BULK_DT_WR]);
+ usbd_transfer_stop(sc->sc_xfer[MUGE_BULK_DT_RD]);
+}
+
+/**
+ * muge_tick - Called periodically to monitor the state of the LAN95xx chip
+ * @ue: USB ether interface
+ *
+ * Simply calls the mii status functions to check the state of the link.
+ *
+ * LOCKING:
+ * Should be called with the MUGE lock held.
+ */
+static void
+muge_tick(struct usb_ether *ue)
+{
+
+ struct muge_softc *sc = uether_getsc(ue);
+ struct mii_data *mii = uether_getmii(&sc->sc_ue);
+
+ MUGE_LOCK_ASSERT(sc, MA_OWNED);
+
+ mii_tick(mii);
+ if ((sc->sc_flags & MUGE_FLAG_LINK) == 0) {
+ lan78xx_miibus_statchg(ue->ue_dev);
+ if ((sc->sc_flags & MUGE_FLAG_LINK) != 0)
+ muge_start(ue);
+ }
+}
+
+/**
+ * muge_ifmedia_sts - Report current media status
+ * @ifp: inet interface pointer
+ * @ifmr: interface media request
+ *
+ * Call the mii functions to get the media status.
+ *
+ * LOCKING:
+ * Internally takes and releases the device lock.
+ */
+static void
+muge_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct muge_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = uether_getmii(&sc->sc_ue);
+
+ MUGE_LOCK(sc);
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+ MUGE_UNLOCK(sc);
+}
+
+/**
+ * muge_probe - Probe the interface.
+ * @dev: muge device handle
+ *
+ * Checks if the device is a match for this driver.
+ *
+ * RETURNS:
+ * Returns 0 on success or an error code on failure.
+ */
+static int
+muge_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != MUGE_CONFIG_INDEX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != MUGE_IFACE_IDX)
+ return (ENXIO);
+ return (usbd_lookup_id_by_uaa(lan78xx_devs, sizeof(lan78xx_devs), uaa));
+}
+
+/**
+ * muge_attach - Attach the interface.
+ * @dev: muge device handle
+ *
+ * Allocate softc structures, do ifmedia setup and ethernet/BPF attach.
+ *
+ * RETURNS:
+ * Returns 0 on success or a negative error code.
+ */
+static int
+muge_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct muge_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+ uint8_t iface_index;
+ int err;
+
+ sc->sc_flags = USB_GET_DRIVER_INFO(uaa);
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ /* Setup the endpoints for the Microchip LAN78xx device. */
+ iface_index = MUGE_IFACE_IDX;
+ err = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer,
+ muge_config, MUGE_N_TRANSFER, sc, &sc->sc_mtx);
+ if (err) {
+ device_printf(dev, "error: allocating USB transfers failed\n");
+ goto err;
+ }
+
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &muge_ue_methods;
+
+ err = uether_ifattach(ue);
+ if (err) {
+ device_printf(dev, "error: could not attach interface\n");
+ goto err_usbd;
+ }
+
+ /* Wait for lan78xx_chip_init from post-attach callback to complete. */
+ uether_ifattach_wait(ue);
+ if (!(sc->sc_flags & MUGE_FLAG_INIT_DONE))
+ goto err_attached;
+
+ return (0);
+
+err_attached:
+ uether_ifdetach(ue);
+err_usbd:
+ usbd_transfer_unsetup(sc->sc_xfer, MUGE_N_TRANSFER);
+err:
+ mtx_destroy(&sc->sc_mtx);
+ return (ENXIO);
+}
+
+/**
+ * muge_detach - Detach the interface.
+ * @dev: muge device handle
+ *
+ * RETURNS:
+ * Returns 0.
+ */
+static int
+muge_detach(device_t dev)
+{
+
+ struct muge_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ usbd_transfer_unsetup(sc->sc_xfer, MUGE_N_TRANSFER);
+ uether_ifdetach(ue);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static device_method_t muge_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, muge_probe),
+ DEVMETHOD(device_attach, muge_attach),
+ DEVMETHOD(device_detach, muge_detach),
+
+ /* Bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+ DEVMETHOD(bus_driver_added, bus_generic_driver_added),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, lan78xx_miibus_readreg),
+ DEVMETHOD(miibus_writereg, lan78xx_miibus_writereg),
+ DEVMETHOD(miibus_statchg, lan78xx_miibus_statchg),
+
+ DEVMETHOD_END
+};
+
+static driver_t muge_driver = {
+ .name = "muge",
+ .methods = muge_methods,
+ .size = sizeof(struct muge_softc),
+};
+
+DRIVER_MODULE(muge, uhub, muge_driver, NULL, NULL);
+DRIVER_MODULE(miibus, muge, miibus_driver, NULL, NULL);
+MODULE_DEPEND(muge, uether, 1, 1, 1);
+MODULE_DEPEND(muge, usb, 1, 1, 1);
+MODULE_DEPEND(muge, ether, 1, 1, 1);
+MODULE_DEPEND(muge, miibus, 1, 1, 1);
+MODULE_VERSION(muge, 1);
+USB_PNP_HOST_INFO(lan78xx_devs);
diff --git a/sys/dev/usb/net/if_mugereg.h b/sys/dev/usb/net/if_mugereg.h
new file mode 100644
index 000000000000..da1fa70abb78
--- /dev/null
+++ b/sys/dev/usb/net/if_mugereg.h
@@ -0,0 +1,376 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (C) 2018 The FreeBSD Foundation.
+ *
+ * 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.
+ */
+
+/*
+ * Definitions for the Microchip LAN78xx USB-to-Ethernet controllers.
+ *
+ * This information was mostly taken from the LAN7800 manual, but some
+ * undocumented registers are based on the Linux driver.
+ *
+ */
+
+#ifndef _IF_MUGEREG_H_
+#define _IF_MUGEREG_H_
+
+/* USB Vendor Requests */
+#define UVR_WRITE_REG 0xA0
+#define UVR_READ_REG 0xA1
+#define UVR_GET_STATS 0xA2
+
+/* Device ID and revision register */
+#define ETH_ID_REV 0x000
+#define ETH_ID_REV_CHIP_ID_MASK_ 0xFFFF0000UL
+#define ETH_ID_REV_CHIP_REV_MASK_ 0x0000FFFFUL
+#define ETH_ID_REV_CHIP_ID_7800_ 0x7800
+#define ETH_ID_REV_CHIP_ID_7801_ 0x7801
+#define ETH_ID_REV_CHIP_ID_7850_ 0x7850
+
+/* Device interrupt status register. */
+#define ETH_INT_STS 0x00C
+#define ETH_INT_STS_CLEAR_ALL_ 0xFFFFFFFFUL
+
+/* Hardware Configuration Register. */
+#define ETH_HW_CFG 0x010
+#define ETH_HW_CFG_LED3_EN_ (0x1UL << 23)
+#define ETH_HW_CFG_LED2_EN_ (0x1UL << 22)
+#define ETH_HW_CFG_LED1_EN_ (0x1UL << 21)
+#define ETH_HW_CFG_LEDO_EN_ (0x1UL << 20)
+#define ETH_HW_CFG_MEF_ (0x1UL << 4)
+#define ETH_HW_CFG_ETC_ (0x1UL << 3)
+#define ETH_HW_CFG_LRST_ (0x1UL << 1) /* Lite reset */
+#define ETH_HW_CFG_SRST_ (0x1UL << 0) /* Soft reset */
+
+/* Power Management Control Register. */
+#define ETH_PMT_CTL 0x014
+#define ETH_PMT_CTL_PHY_RST_ (0x1UL << 4) /* PHY reset */
+#define ETH_PMT_CTL_WOL_EN_ (0x1UL << 3) /* PHY wake-on-lan */
+#define ETH_PMT_CTL_PHY_WAKE_EN_ (0x1UL << 2) /* PHY int wake */
+
+/* GPIO Configuration 0 Register. */
+#define ETH_GPIO_CFG0 0x018
+
+/* GPIO Configuration 1 Register. */
+#define ETH_GPIO_CFG1 0x01C
+
+/* GPIO wake enable and polarity register. */
+#define ETH_GPIO_WAKE 0x020
+
+/* RX Command A */
+#define RX_CMD_A_RED_ (0x1UL << 22) /* Receive Error Det */
+#define RX_CMD_A_ICSM_ (0x1UL << 14)
+#define RX_CMD_A_LEN_MASK_ 0x00003FFFUL
+
+/* TX Command A */
+#define TX_CMD_A_LEN_MASK_ 0x000FFFFFUL
+#define TX_CMD_A_FCS_ (0x1UL << 22)
+
+/* Data Port Select Register */
+#define ETH_DP_SEL 0x024
+#define ETH_DP_SEL_DPRDY_ (0x1UL << 31)
+#define ETH_DP_SEL_RSEL_VLAN_DA_ (0x1UL << 0) /* RFE VLAN/DA Hash */
+#define ETH_DP_SEL_RSEL_MASK_ 0x0000000F
+#define ETH_DP_SEL_VHF_HASH_LEN 16
+#define ETH_DP_SEL_VHF_VLAN_LEN 128
+
+/* Data Port Command Register */
+#define ETH_DP_CMD 0x028
+#define ETH_DP_CMD_WRITE_ (0x1UL << 0) /* 1 for write */
+#define ETH_DP_CMD_READ_ (0x0UL << 0) /* 0 for read */
+
+/* Data Port Address Register */
+#define ETH_DP_ADDR 0x02C
+
+/* Data Port Data Register */
+#define ETH_DP_DATA 0x030
+
+/* EEPROM Command Register */
+#define ETH_E2P_CMD 0x040
+#define ETH_E2P_CMD_MASK_ 0x70000000UL
+#define ETH_E2P_CMD_ADDR_MASK_ 0x000001FFUL
+#define ETH_E2P_CMD_BUSY_ (0x1UL << 31)
+#define ETH_E2P_CMD_READ_ (0x0UL << 28)
+#define ETH_E2P_CMD_WRITE_ (0x3UL << 28)
+#define ETH_E2P_CMD_ERASE_ (0x5UL << 28)
+#define ETH_E2P_CMD_RELOAD_ (0x7UL << 28)
+#define ETH_E2P_CMD_TIMEOUT_ (0x1UL << 10)
+#define ETH_E2P_MAC_OFFSET 0x01
+#define ETH_E2P_INDICATOR_OFFSET 0x00
+
+/* EEPROM Data Register */
+#define ETH_E2P_DATA 0x044
+#define ETH_E2P_INDICATOR 0xA5 /* EEPROM is present */
+
+/* Packet sizes. */
+#define MUGE_SS_USB_PKT_SIZE 1024
+#define MUGE_HS_USB_PKT_SIZE 512
+#define MUGE_FS_USB_PKT_SIZE 64
+
+/* Receive Filtering Engine Control Register */
+#define ETH_RFE_CTL 0x0B0
+#define ETH_RFE_CTL_IGMP_COE_ (0x1U << 14)
+#define ETH_RFE_CTL_ICMP_COE_ (0x1U << 13)
+#define ETH_RFE_CTL_TCPUDP_COE_ (0x1U << 12)
+#define ETH_RFE_CTL_IP_COE_ (0x1U << 11)
+#define ETH_RFE_CTL_BCAST_EN_ (0x1U << 10)
+#define ETH_RFE_CTL_MCAST_EN_ (0x1U << 9)
+#define ETH_RFE_CTL_UCAST_EN_ (0x1U << 8)
+#define ETH_RFE_CTL_VLAN_FILTER_ (0x1U << 5)
+#define ETH_RFE_CTL_MCAST_HASH_ (0x1U << 3)
+#define ETH_RFE_CTL_DA_PERFECT_ (0x1U << 1)
+
+/* End address of the RX FIFO */
+#define ETH_FCT_RX_FIFO_END 0x0C8
+#define ETH_FCT_RX_FIFO_END_MASK_ 0x0000007FUL
+#define MUGE_MAX_RX_FIFO_SIZE (12 * 1024)
+
+/* End address of the TX FIFO */
+#define ETH_FCT_TX_FIFO_END 0x0CC
+#define ETH_FCT_TX_FIFO_END_MASK_ 0x0000003FUL
+#define MUGE_MAX_TX_FIFO_SIZE (12 * 1024)
+
+/* USB Configuration Register 0 */
+#define ETH_USB_CFG0 0x080
+#define ETH_USB_CFG_BIR_ (0x1U << 6) /* Bulk-In Empty resp */
+#define ETH_USB_CFG_BCE_ (0x1U << 5) /* Burst Cap Enable */
+
+/* USB Configuration Register 1 */
+#define ETH_USB_CFG1 0x084
+
+/* USB Configuration Register 2 */
+#define ETH_USB_CFG2 0x088
+
+/* USB bConfigIndex: it only has one configuration. */
+#define MUGE_CONFIG_INDEX 0
+
+/* Burst Cap Register */
+#define ETH_BURST_CAP 0x090
+#define MUGE_DEFAULT_BURST_CAP_SIZE MUGE_MAX_TX_FIFO_SIZE
+
+/* Bulk-In Delay Register */
+#define ETH_BULK_IN_DLY 0x094
+#define MUGE_DEFAULT_BULK_IN_DELAY 0x0800
+
+/* Interrupt Endpoint Control Register */
+#define ETH_INT_EP_CTL 0x098
+#define ETH_INT_ENP_PHY_INT (0x1U << 17) /* PHY Enable */
+
+/* Registers on the phy, accessed via MII/MDIO */
+#define MUGE_PHY_INTR_STAT 25
+#define MUGE_PHY_INTR_MASK 26
+#define MUGE_PHY_INTR_LINK_CHANGE (0x1U << 13)
+#define MUGE_PHY_INTR_ANEG_COMP (0x1U << 10)
+#define MUGE_EXT_PAGE_ACCESS 0x1F
+#define MUGE_EXT_PAGE_SPACE_0 0x0000
+#define MUGE_EXT_PAGE_SPACE_1 0x0001
+#define MUGE_EXT_PAGE_SPACE_2 0x0002
+
+#define MUGE_PHY_LED_MODE 29
+
+/* Extended Register Page 1 Space */
+#define MUGE_EXT_MODE_CTRL 0x0013
+#define MUGE_EXT_MODE_CTRL_MDIX_MASK_ 0x000C
+#define MUGE_EXT_MODE_CTRL_AUTO_MDIX_ 0x0000
+
+/* FCT Flow Control Threshold Register */
+#define ETH_FCT_FLOW 0x0D0
+
+/* FCT RX FIFO Control Register */
+#define ETH_FCT_RX_CTL 0x0C0
+
+/* FCT TX FIFO Control Register */
+#define ETH_FCT_TX_CTL 0x0C4
+#define ETH_FCT_TX_CTL_EN_ (0x1U << 31)
+
+/* MAC Control Register */
+#define ETH_MAC_CR 0x100
+#define ETH_MAC_CR_GMII_EN_ (0x1U << 19) /* GMII Enable */
+#define ETH_MAC_CR_AUTO_DUPLEX_ (0x1U << 12)
+#define ETH_MAC_CR_AUTO_SPEED_ (0x1U << 11)
+
+/* MAC Receive Register */
+#define ETH_MAC_RX 0x104
+#define ETH_MAC_RX_MAX_FR_SIZE_MASK_ 0x3FFF0000
+#define ETH_MAC_RX_MAX_FR_SIZE_SHIFT_ 16
+#define ETH_MAC_RX_EN_ (0x1U << 0) /* Enable Receiver */
+
+/* MAC Transmit Register */
+#define ETH_MAC_TX 0x108
+#define ETH_MAC_TX_TXEN_ (0x1U << 0) /* Enable Transmitter */
+
+/* Flow Control Register */
+#define ETH_FLOW 0x10C
+#define ETH_FLOW_CR_TX_FCEN_ (0x1U << 30) /* TX FC Enable */
+#define ETH_FLOW_CR_RX_FCEN_ (0x1U << 29) /* RX FC Enable */
+
+/* MAC Receive Address Registers */
+#define ETH_RX_ADDRH 0x118 /* High */
+#define ETH_RX_ADDRL 0x11C /* Low */
+
+/* MII Access Register */
+#define ETH_MII_ACC 0x120
+#define ETH_MII_ACC_MII_BUSY_ (0x1UL << 0)
+#define ETH_MII_ACC_MII_READ_ (0x0UL << 1)
+#define ETH_MII_ACC_MII_WRITE_ (0x1UL << 1)
+
+/* MII Data Register */
+#define ETH_MII_DATA 0x124
+
+ /* MAC address perfect filter registers (ADDR_FILTx) */
+#define ETH_MAF_BASE 0x400
+#define ETH_MAF_HIx 0x00
+#define ETH_MAF_LOx 0x04
+#define MUGE_NUM_PFILTER_ADDRS_ 33
+#define ETH_MAF_HI_VALID_ (0x1UL << 31)
+#define ETH_MAF_HI_TYPE_SRC_ (0x1UL << 30)
+#define ETH_MAF_HI_TYPE_DST_ (0x0UL << 30)
+#define PFILTER_HI(index) (ETH_MAF_BASE + (8 * (index)) + (ETH_MAF_HIx))
+#define PFILTER_LO(index) (ETH_MAF_BASE + (8 * (index)) + (ETH_MAF_LOx))
+
+/*
+ * These registers are not documented in the datasheet, and are based on
+ * the Linux driver.
+ */
+#define OTP_BASE_ADDR 0x01000
+#define OTP_PWR_DN (OTP_BASE_ADDR + 4 * 0x00)
+#define OTP_PWR_DN_PWRDN_N 0x01
+#define OTP_ADDR1 (OTP_BASE_ADDR + 4 * 0x01)
+#define OTP_ADDR1_15_11 0x1F
+#define OTP_ADDR2 (OTP_BASE_ADDR + 4 * 0x02)
+#define OTP_ADDR2_10_3 0xFF
+#define OTP_ADDR3 (OTP_BASE_ADDR + 4 * 0x03)
+#define OTP_ADDR3_2_0 0x03
+#define OTP_RD_DATA (OTP_BASE_ADDR + 4 * 0x06)
+#define OTP_FUNC_CMD (OTP_BASE_ADDR + 4 * 0x08)
+#define OTP_FUNC_CMD_RESET 0x04
+#define OTP_FUNC_CMD_PROGRAM_ 0x02
+#define OTP_FUNC_CMD_READ_ 0x01
+#define OTP_MAC_OFFSET 0x01
+#define OTP_INDICATOR_OFFSET 0x00
+#define OTP_INDICATOR_1 0xF3
+#define OTP_INDICATOR_2 0xF7
+#define OTP_CMD_GO (OTP_BASE_ADDR + 4 * 0x0A)
+#define OTP_CMD_GO_GO_ 0x01
+#define OTP_STATUS (OTP_BASE_ADDR + 4 * 0x0A)
+#define OTP_STATUS_OTP_LOCK_ 0x10
+#define OTP_STATUS_BUSY_ 0x01
+
+/* Some unused registers, from the data sheet. */
+#if 0
+#define ETH_BOS_ATTR 0x050
+#define ETH_SS_ATTR 0x054
+#define ETH_HS_ATTR 0x058
+#define ETH_FS_ATTR 0x05C
+#define ETH_STRNG_ATTR0 0x060
+#define ETH_STRNG_ATTR1 0x064
+#define ETH_STRNGFLAG_ATTR 0x068
+#define ETH_SW_GP_0 0x06C
+#define ETH_SW_GP_1 0x070
+#define ETH_SW_GP_2 0x074
+#define ETH_VLAN_TYPE 0x0B4
+#define ETH_RX_DP_STOR 0x0D4
+#define ETH_TX_DP_STOR 0x0D8
+#define ETH_LTM_BELT_IDLE0 0x0E0
+#define ETH_LTM_BELT_IDLE1 0x0E4
+#define ETH_LTM_BELT_ACT0 0x0E8
+#define ETH_LTM_BELT_ACT1 0x0EC
+#define ETH_LTM_INACTIVE0 0x0F0
+#define ETH_LTM_INACTIVE1 0x0F4
+
+#define ETH_RAND_SEED 0x110
+#define ETH_ERR_STS 0x114
+
+#define ETH_EEE_TX_LPI_REQ_DLY 0x130
+#define ETH_EEE_TW_TX_SYS 0x134
+#define ETH_EEE_TX_LPI_REM_DLY 0x138
+
+#define ETH_WUCSR1 0x140
+#define ETH_WK_SRC 0x144
+#define ETH_WUF_CFGx 0x150
+#define ETH_WUF_MASKx 0x200
+#define ETH_WUCSR2 0x600
+
+#define ETH_NS1_IPV6_ADDR_DEST0 0x610
+#define ETH_NS1_IPV6_ADDR_DEST1 0x614
+#define ETH_NS1_IPV6_ADDR_DEST2 0x618
+#define ETH_NS1_IPV6_ADDR_DEST3 0x61C
+
+#define ETH_NS1_IPV6_ADDR_SRC0 0x620
+#define ETH_NS1_IPV6_ADDR_SRC1 0x624
+#define ETH_NS1_IPV6_ADDR_SRC2 0x628
+#define ETH_NS1_IPV6_ADDR_SRC3 0x62C
+
+#define ETH_NS1_ICMPV6_ADDR0_0 0x630
+#define ETH_NS1_ICMPV6_ADDR0_1 0x634
+#define ETH_NS1_ICMPV6_ADDR0_2 0x638
+#define ETH_NS1_ICMPV6_ADDR0_3 0x63C
+
+#define ETH_NS1_ICMPV6_ADDR1_0 0x640
+#define ETH_NS1_ICMPV6_ADDR1_1 0x644
+#define ETH_NS1_ICMPV6_ADDR1_2 0x648
+#define ETH_NS1_ICMPV6_ADDR1_3 0x64C
+
+#define ETH_NS2_IPV6_ADDR_DEST0 0x650
+#define ETH_NS2_IPV6_ADDR_DEST1 0x654
+#define ETH_NS2_IPV6_ADDR_DEST2 0x658
+#define ETH_NS2_IPV6_ADDR_DEST3 0x65C
+
+#define ETH_NS2_IPV6_ADDR_SRC0 0x660
+#define ETH_NS2_IPV6_ADDR_SRC1 0x664
+#define ETH_NS2_IPV6_ADDR_SRC2 0x668
+#define ETH_NS2_IPV6_ADDR_SRC3 0x66C
+
+#define ETH_NS2_ICMPV6_ADDR0_0 0x670
+#define ETH_NS2_ICMPV6_ADDR0_1 0x674
+#define ETH_NS2_ICMPV6_ADDR0_2 0x678
+#define ETH_NS2_ICMPV6_ADDR0_3 0x67C
+
+#define ETH_NS2_ICMPV6_ADDR1_0 0x680
+#define ETH_NS2_ICMPV6_ADDR1_1 0x684
+#define ETH_NS2_ICMPV6_ADDR1_2 0x688
+#define ETH_NS2_ICMPV6_ADDR1_3 0x68C
+
+#define ETH_SYN_IPV4_ADDR_SRC 0x690
+#define ETH_SYN_IPV4_ADDR_DEST 0x694
+#define ETH_SYN_IPV4_TCP_PORTS 0x698
+
+#define ETH_SYN_IPV6_ADDR_SRC0 0x69C
+#define ETH_SYN_IPV6_ADDR_SRC1 0x6A0
+#define ETH_SYN_IPV6_ADDR_SRC2 0x6A4
+#define ETH_SYN_IPV6_ADDR_SRC3 0x6A8
+
+#define ETH_SYN_IPV6_ADDR_DEST0 0x6AC
+#define ETH_SYN_IPV6_ADDR_DEST1 0x6B0
+#define ETH_SYN_IPV6_ADDR_DEST2 0x6B4
+#define ETH_SYN_IPV6_ADDR_DEST3 0x6B8
+
+#define ETH_SYN_IPV6_TCP_PORTS 0x6BC
+#define ETH_ARP_SPA 0x6C0
+#define ETH_ARP_TPA 0x6C4
+#define ETH_PHY_DEV_ID 0x700
+#endif
+
+#endif /* _IF_MUGEREG_H_ */
diff --git a/sys/dev/usb/net/if_rue.c b/sys/dev/usb/net/if_rue.c
new file mode 100644
index 000000000000..d1b46887cd20
--- /dev/null
+++ b/sys/dev/usb/net/if_rue.c
@@ -0,0 +1,923 @@
+/*-
+ * 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.
+ */
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause AND BSD-2-Clause
+ *
+ * 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.
+ */
+
+/*
+ * RealTek RTL8150 USB to fast ethernet controller driver.
+ * Datasheet is available from
+ * ftp://ftp.realtek.com.tw/lancard/data_sheet/8150/.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_media.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR rue_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/net/usb_ethernet.h>
+#include <dev/usb/net/if_ruereg.h>
+
+#include "miibus_if.h"
+
+#ifdef USB_DEBUG
+static int rue_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, rue, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB rue");
+SYSCTL_INT(_hw_usb_rue, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &rue_debug, 0, "Debug level");
+#endif
+
+/*
+ * Various supported device vendors/products.
+ */
+
+static const STRUCT_USB_HOST_ID rue_devs[] = {
+ {USB_VPI(USB_VENDOR_MELCO, USB_PRODUCT_MELCO_LUAKTX, 0)},
+ {USB_VPI(USB_VENDOR_REALTEK, USB_PRODUCT_REALTEK_USBKR100, 0)},
+ {USB_VPI(USB_VENDOR_OQO, USB_PRODUCT_OQO_ETHER01, 0)},
+};
+
+/* prototypes */
+
+static device_probe_t rue_probe;
+static device_attach_t rue_attach;
+static device_detach_t rue_detach;
+
+static miibus_readreg_t rue_miibus_readreg;
+static miibus_writereg_t rue_miibus_writereg;
+static miibus_statchg_t rue_miibus_statchg;
+
+static usb_callback_t rue_intr_callback;
+static usb_callback_t rue_bulk_read_callback;
+static usb_callback_t rue_bulk_write_callback;
+
+static uether_fn_t rue_attach_post;
+static uether_fn_t rue_init;
+static uether_fn_t rue_stop;
+static uether_fn_t rue_start;
+static uether_fn_t rue_tick;
+static uether_fn_t rue_setmulti;
+static uether_fn_t rue_setpromisc;
+
+static int rue_read_mem(struct rue_softc *, uint16_t, void *, int);
+static int rue_write_mem(struct rue_softc *, uint16_t, void *, int);
+static uint8_t rue_csr_read_1(struct rue_softc *, uint16_t);
+static uint16_t rue_csr_read_2(struct rue_softc *, uint16_t);
+static int rue_csr_write_1(struct rue_softc *, uint16_t, uint8_t);
+static int rue_csr_write_2(struct rue_softc *, uint16_t, uint16_t);
+static int rue_csr_write_4(struct rue_softc *, int, uint32_t);
+
+static void rue_reset(struct rue_softc *);
+static int rue_ifmedia_upd(if_t);
+static void rue_ifmedia_sts(if_t, struct ifmediareq *);
+
+static const struct usb_config rue_config[RUE_N_TRANSFER] = {
+ [RUE_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = MCLBYTES,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = rue_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ },
+
+ [RUE_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = (MCLBYTES + 4),
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = rue_bulk_read_callback,
+ .timeout = 0, /* no timeout */
+ },
+
+ [RUE_INTR_DT_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = rue_intr_callback,
+ },
+};
+
+static device_method_t rue_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, rue_probe),
+ DEVMETHOD(device_attach, rue_attach),
+ DEVMETHOD(device_detach, rue_detach),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, rue_miibus_readreg),
+ DEVMETHOD(miibus_writereg, rue_miibus_writereg),
+ DEVMETHOD(miibus_statchg, rue_miibus_statchg),
+
+ DEVMETHOD_END
+};
+
+static driver_t rue_driver = {
+ .name = "rue",
+ .methods = rue_methods,
+ .size = sizeof(struct rue_softc),
+};
+
+DRIVER_MODULE_ORDERED(rue, uhub, rue_driver, NULL, NULL, SI_ORDER_ANY);
+DRIVER_MODULE(miibus, rue, miibus_driver, NULL, NULL);
+MODULE_DEPEND(rue, uether, 1, 1, 1);
+MODULE_DEPEND(rue, usb, 1, 1, 1);
+MODULE_DEPEND(rue, ether, 1, 1, 1);
+MODULE_DEPEND(rue, miibus, 1, 1, 1);
+MODULE_VERSION(rue, 1);
+USB_PNP_HOST_INFO(rue_devs);
+
+static const struct usb_ether_methods rue_ue_methods = {
+ .ue_attach_post = rue_attach_post,
+ .ue_start = rue_start,
+ .ue_init = rue_init,
+ .ue_stop = rue_stop,
+ .ue_tick = rue_tick,
+ .ue_setmulti = rue_setmulti,
+ .ue_setpromisc = rue_setpromisc,
+ .ue_mii_upd = rue_ifmedia_upd,
+ .ue_mii_sts = rue_ifmedia_sts,
+};
+
+#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))
+
+static int
+rue_read_mem(struct rue_softc *sc, uint16_t addr, void *buf, int len)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = UR_SET_ADDRESS;
+ USETW(req.wValue, addr);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, len);
+
+ return (uether_do_request(&sc->sc_ue, &req, buf, 1000));
+}
+
+static int
+rue_write_mem(struct rue_softc *sc, uint16_t addr, void *buf, int len)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = UR_SET_ADDRESS;
+ USETW(req.wValue, addr);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, len);
+
+ return (uether_do_request(&sc->sc_ue, &req, buf, 1000));
+}
+
+static uint8_t
+rue_csr_read_1(struct rue_softc *sc, uint16_t reg)
+{
+ uint8_t val;
+
+ rue_read_mem(sc, reg, &val, 1);
+ return (val);
+}
+
+static uint16_t
+rue_csr_read_2(struct rue_softc *sc, uint16_t reg)
+{
+ uint8_t val[2];
+
+ rue_read_mem(sc, reg, &val, 2);
+ return (UGETW(val));
+}
+
+static int
+rue_csr_write_1(struct rue_softc *sc, uint16_t reg, uint8_t val)
+{
+ return (rue_write_mem(sc, reg, &val, 1));
+}
+
+static int
+rue_csr_write_2(struct rue_softc *sc, uint16_t reg, uint16_t val)
+{
+ uint8_t temp[2];
+
+ USETW(temp, val);
+ return (rue_write_mem(sc, reg, &temp, 2));
+}
+
+static int
+rue_csr_write_4(struct rue_softc *sc, int reg, uint32_t val)
+{
+ uint8_t temp[4];
+
+ USETDW(temp, val);
+ return (rue_write_mem(sc, reg, &temp, 4));
+}
+
+static int
+rue_miibus_readreg(device_t dev, int phy, int reg)
+{
+ struct rue_softc *sc = device_get_softc(dev);
+ uint16_t rval;
+ uint16_t ruereg;
+ int locked;
+
+ if (phy != 0) /* RTL8150 supports PHY == 0, only */
+ return (0);
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ RUE_LOCK(sc);
+
+ 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:
+ rval = 0;
+ goto done;
+ default:
+ if (RUE_REG_MIN <= reg && reg <= RUE_REG_MAX) {
+ rval = rue_csr_read_1(sc, reg);
+ goto done;
+ }
+ device_printf(sc->sc_ue.ue_dev, "bad phy register\n");
+ rval = 0;
+ goto done;
+ }
+
+ rval = rue_csr_read_2(sc, ruereg);
+done:
+ if (!locked)
+ RUE_UNLOCK(sc);
+ return (rval);
+}
+
+static int
+rue_miibus_writereg(device_t dev, int phy, int reg, int data)
+{
+ struct rue_softc *sc = device_get_softc(dev);
+ uint16_t ruereg;
+ int locked;
+
+ if (phy != 0) /* RTL8150 supports PHY == 0, only */
+ return (0);
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ RUE_LOCK(sc);
+
+ 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:
+ goto done;
+ default:
+ if (RUE_REG_MIN <= reg && reg <= RUE_REG_MAX) {
+ rue_csr_write_1(sc, reg, data);
+ goto done;
+ }
+ device_printf(sc->sc_ue.ue_dev, " bad phy register\n");
+ goto done;
+ }
+ rue_csr_write_2(sc, ruereg, data);
+done:
+ if (!locked)
+ RUE_UNLOCK(sc);
+ 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);
+ uint16_t bmcr;
+ int locked;
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ RUE_LOCK(sc);
+
+ 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));
+
+ if (!locked)
+ RUE_UNLOCK(sc);
+#endif
+}
+
+static void
+rue_setpromisc(struct usb_ether *ue)
+{
+ struct rue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ RUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* If we want promiscuous mode, set the allframes bit. */
+ if (if_getflags(ifp) & IFF_PROMISC)
+ RUE_SETBIT(sc, RUE_RCR, RUE_RCR_AAP);
+ else
+ RUE_CLRBIT(sc, RUE_RCR, RUE_RCR_AAP);
+}
+
+static u_int
+rue_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
+{
+ uint32_t *hashes = arg;
+ int h;
+
+ h = ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 26;
+ if (h < 32)
+ hashes[0] |= (1 << h);
+ else
+ hashes[1] |= (1 << (h - 32));
+
+ return (1);
+}
+
+/*
+ * Program the 64-bit multicast hash filter.
+ */
+static void
+rue_setmulti(struct usb_ether *ue)
+{
+ struct rue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ uint16_t rxcfg;
+ uint32_t hashes[2] = { 0, 0 };
+ int mcnt;
+
+ RUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ rxcfg = rue_csr_read_2(sc, RUE_RCR);
+
+ if (if_getflags(ifp) & IFF_ALLMULTI || if_getflags(ifp) & 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 */
+ mcnt = if_foreach_llmaddr(ifp, rue_hash_maddr, &hashes);
+
+ 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++) {
+ if (uether_pause(&sc->sc_ue, hz / 1000))
+ break;
+ if (!(rue_csr_read_1(sc, RUE_CR) & RUE_CR_SOFT_RST))
+ break;
+ }
+ if (i == RUE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev, "reset never completed\n");
+
+ uether_pause(&sc->sc_ue, hz / 100);
+}
+
+static void
+rue_attach_post(struct usb_ether *ue)
+{
+ struct rue_softc *sc = uether_getsc(ue);
+
+ /* reset the adapter */
+ rue_reset(sc);
+
+ /* get station address from the EEPROM */
+ rue_read_mem(sc, RUE_EEPROM_IDR0, ue->ue_eaddr, ETHER_ADDR_LEN);
+}
+
+/*
+ * Probe for a RTL8150 chip.
+ */
+static int
+rue_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != RUE_CONFIG_IDX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != RUE_IFACE_IDX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(rue_devs, sizeof(rue_devs), uaa));
+}
+
+/*
+ * Attach the interface. Allocate softc structures, do ifmedia
+ * setup and ethernet/BPF attach.
+ */
+static int
+rue_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct rue_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+ uint8_t iface_index;
+ int error;
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ iface_index = RUE_IFACE_IDX;
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, rue_config, RUE_N_TRANSFER,
+ sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "allocating USB transfers failed\n");
+ goto detach;
+ }
+
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &rue_ue_methods;
+
+ error = uether_ifattach(ue);
+ if (error) {
+ device_printf(dev, "could not attach interface\n");
+ goto detach;
+ }
+ return (0); /* success */
+
+detach:
+ rue_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+rue_detach(device_t dev)
+{
+ struct rue_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ usbd_transfer_unsetup(sc->sc_xfer, RUE_N_TRANSFER);
+ uether_ifdetach(ue);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+rue_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct rue_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct rue_intrpkt pkt;
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ if (ifp && (if_getdrvflags(ifp) & IFF_DRV_RUNNING) &&
+ actlen >= (int)sizeof(pkt)) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &pkt, sizeof(pkt));
+
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, pkt.rue_rxlost_cnt);
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, pkt.rue_crcerr_cnt);
+ if_inc_counter(ifp, IFCOUNTER_COLLISIONS, pkt.rue_col_cnt);
+ }
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+rue_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct rue_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_ether *ue = &sc->sc_ue;
+ if_t ifp = uether_getifp(ue);
+ struct usb_page_cache *pc;
+ uint16_t status;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ if (actlen < 4) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, actlen - 4, &status, sizeof(status));
+ actlen -= 4;
+
+ /* check receive packet was valid or not */
+ status = le16toh(status);
+ if ((status & RUE_RXSTAT_VALID) == 0) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ uether_rxbuf(ue, pc, 0, actlen);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ uether_rxflush(ue);
+ return;
+
+ default: /* Error */
+ DPRINTF("bulk read error, %s\n",
+ usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+rue_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct rue_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ int temp_len;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "transfer complete\n");
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ if ((sc->sc_flags & RUE_FLAG_LINK) == 0) {
+ /*
+ * don't send anything if there is no link !
+ */
+ return;
+ }
+ m = if_dequeue(ifp);
+
+ if (m == NULL)
+ return;
+ if (m->m_pkthdr.len > MCLBYTES)
+ m->m_pkthdr.len = MCLBYTES;
+ temp_len = m->m_pkthdr.len;
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_m_copy_in(pc, 0, m, 0, 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 (temp_len < RUE_MIN_FRAMELEN) {
+ usbd_frame_zero(pc, temp_len,
+ RUE_MIN_FRAMELEN - temp_len);
+ temp_len = RUE_MIN_FRAMELEN;
+ }
+ usbd_xfer_set_frame_len(xfer, 0, temp_len);
+
+ /*
+ * if there's a BPF listener, bounce a copy
+ * of this frame to him:
+ */
+ BPF_MTAP(ifp, m);
+
+ m_freem(m);
+
+ usbd_transfer_submit(xfer);
+
+ return;
+
+ default: /* Error */
+ DPRINTFN(11, "transfer error, %s\n",
+ usbd_errstr(error));
+
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+rue_tick(struct usb_ether *ue)
+{
+ struct rue_softc *sc = uether_getsc(ue);
+ struct mii_data *mii = GET_MII(sc);
+
+ RUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ mii_tick(mii);
+ if ((sc->sc_flags & RUE_FLAG_LINK) == 0
+ && mii->mii_media_status & IFM_ACTIVE &&
+ IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) {
+ sc->sc_flags |= RUE_FLAG_LINK;
+ rue_start(ue);
+ }
+}
+
+static void
+rue_start(struct usb_ether *ue)
+{
+ struct rue_softc *sc = uether_getsc(ue);
+
+ /*
+ * start the USB transfers, if not already started:
+ */
+ usbd_transfer_start(sc->sc_xfer[RUE_INTR_DT_RD]);
+ usbd_transfer_start(sc->sc_xfer[RUE_BULK_DT_RD]);
+ usbd_transfer_start(sc->sc_xfer[RUE_BULK_DT_WR]);
+}
+
+static void
+rue_init(struct usb_ether *ue)
+{
+ struct rue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ RUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ /*
+ * Cancel pending I/O
+ */
+ rue_reset(sc);
+
+ /* Set MAC address */
+ rue_write_mem(sc, RUE_IDR0, if_getlladdr(ifp), ETHER_ADDR_LEN);
+
+ rue_stop(ue);
+
+ /*
+ * Set the initial TX and RX configuration.
+ */
+ rue_csr_write_1(sc, RUE_TCR, RUE_TCR_CONFIG);
+ rue_csr_write_2(sc, RUE_RCR, RUE_RCR_CONFIG|RUE_RCR_AB);
+
+ /* Load the multicast filter */
+ rue_setpromisc(ue);
+ /* Load the multicast filter. */
+ rue_setmulti(ue);
+
+ /* Enable RX and TX */
+ rue_csr_write_1(sc, RUE_CR, (RUE_CR_TE | RUE_CR_RE | RUE_CR_EP3CLREN));
+
+ usbd_xfer_set_stall(sc->sc_xfer[RUE_BULK_DT_WR]);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+ rue_start(ue);
+}
+
+/*
+ * Set media options.
+ */
+static int
+rue_ifmedia_upd(if_t ifp)
+{
+ struct rue_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = GET_MII(sc);
+ struct mii_softc *miisc;
+ int error;
+
+ RUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ sc->sc_flags &= ~RUE_FLAG_LINK;
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list)
+ PHY_RESET(miisc);
+ error = mii_mediachg(mii);
+ return (error);
+}
+
+/*
+ * Report current media status.
+ */
+static void
+rue_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct rue_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = GET_MII(sc);
+
+ RUE_LOCK(sc);
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+ RUE_UNLOCK(sc);
+}
+
+static void
+rue_stop(struct usb_ether *ue)
+{
+ struct rue_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ RUE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+ sc->sc_flags &= ~RUE_FLAG_LINK;
+
+ /*
+ * stop all the transfers, if not already stopped:
+ */
+ usbd_transfer_stop(sc->sc_xfer[RUE_BULK_DT_WR]);
+ usbd_transfer_stop(sc->sc_xfer[RUE_BULK_DT_RD]);
+ usbd_transfer_stop(sc->sc_xfer[RUE_INTR_DT_RD]);
+
+ rue_csr_write_1(sc, RUE_CR, 0x00);
+
+ rue_reset(sc);
+}
diff --git a/sys/dev/usb/net/if_ruereg.h b/sys/dev/usb/net/if_ruereg.h
new file mode 100644
index 000000000000..50528d547819
--- /dev/null
+++ b/sys/dev/usb/net/if_ruereg.h
@@ -0,0 +1,178 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ */
+
+#define RUE_CONFIG_IDX 0 /* config number 1 */
+#define RUE_IFACE_IDX 0
+
+#define RUE_INTR_PKTLEN 0x8
+
+#define RUE_TIMEOUT 50
+#define RUE_MIN_FRAMELEN 60
+
+/* 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)
+
+#define RUE_RXSTAT_VALID (0x01 << 12)
+#define RUE_RXSTAT_RUNT (0x02 << 12)
+#define RUE_RXSTAT_PMATCH (0x04 << 12)
+#define RUE_RXSTAT_MCAST (0x08 << 12)
+
+#define GET_MII(sc) uether_getmii(&(sc)->sc_ue)
+
+struct rue_intrpkt {
+ uint8_t rue_tsr;
+ uint8_t rue_rsr;
+ uint8_t rue_gep_msr;
+ uint8_t rue_waksr;
+ uint8_t rue_txok_cnt;
+ uint8_t rue_rxlost_cnt;
+ uint8_t rue_crcerr_cnt;
+ uint8_t rue_col_cnt;
+} __packed;
+
+enum {
+ RUE_BULK_DT_WR,
+ RUE_BULK_DT_RD,
+ RUE_INTR_DT_RD,
+ RUE_N_TRANSFER,
+};
+
+struct rue_softc {
+ struct usb_ether sc_ue;
+ struct mtx sc_mtx;
+ struct usb_xfer *sc_xfer[RUE_N_TRANSFER];
+
+ int sc_flags;
+#define RUE_FLAG_LINK 0x0001
+};
+
+#define RUE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define RUE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define RUE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t)
diff --git a/sys/dev/usb/net/if_smsc.c b/sys/dev/usb/net/if_smsc.c
new file mode 100644
index 000000000000..0ebbf8482446
--- /dev/null
+++ b/sys/dev/usb/net/if_smsc.c
@@ -0,0 +1,1855 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012
+ * Ben Gray <bgray@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 ``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.
+ */
+
+/*
+ * Microchip LAN9xxx devices (https://www.microchip.com/en-us/product/lan9500a)
+ *
+ * The LAN9500 & LAN9500A devices are stand-alone USB to Ethernet chips that
+ * support USB 2.0 and 10/100 Mbps Ethernet.
+ *
+ * The LAN951x devices are an integrated USB hub and USB to Ethernet adapter.
+ * The driver only covers the Ethernet part, the standard USB hub driver
+ * supports the hub part.
+ *
+ * This driver is closely modelled on the Linux driver written and copyrighted
+ * by SMSC (later acquired by Microchip).
+ *
+ *
+ *
+ *
+ * H/W TCP & UDP Checksum Offloading
+ * ---------------------------------
+ * The chip supports both tx and rx offloading of UDP & TCP checksums, this
+ * feature can be dynamically enabled/disabled.
+ *
+ * RX checksuming is performed across bytes after the IPv4 header to the end of
+ * the Ethernet frame, this means if the frame is padded with non-zero values
+ * the H/W checksum will be incorrect, however the rx code compensates for this.
+ *
+ * TX checksuming is more complicated, the device requires a special header to
+ * be prefixed onto the start of the frame which indicates the start and end
+ * positions of the UDP or TCP frame. This requires the driver to manually
+ * go through the packet data and decode the headers prior to sending.
+ * On Linux they generally provide cues to the location of the csum and the
+ * area to calculate it over, on FreeBSD we seem to have to do it all ourselves,
+ * hence this is not as optimal and therefore h/w tX checksum is currently not
+ * implemented.
+ *
+ */
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/random.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_media.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#include "opt_platform.h"
+
+#ifdef FDT
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/usb/usb_fdt_support.h>
+#endif
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR smsc_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/net/usb_ethernet.h>
+
+#include <dev/usb/net/if_smscreg.h>
+
+#include "miibus_if.h"
+
+SYSCTL_NODE(_hw_usb, OID_AUTO, smsc, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB smsc");
+
+static bool smsc_rx_packet_batching = 1;
+
+SYSCTL_BOOL(_hw_usb_smsc, OID_AUTO, smsc_rx_packet_batching, CTLFLAG_RDTUN,
+ &smsc_rx_packet_batching, 0,
+ "If set, allows packet batching to increase throughput and latency. "
+ "Else throughput and latency is decreased.");
+
+#ifdef USB_DEBUG
+static int smsc_debug = 0;
+
+SYSCTL_INT(_hw_usb_smsc, OID_AUTO, debug, CTLFLAG_RWTUN, &smsc_debug, 0,
+ "Debug level");
+#endif
+
+/*
+ * Various supported device vendors/products.
+ */
+static const struct usb_device_id smsc_devs[] = {
+#define SMSC_DEV(p,i) { USB_VPI(USB_VENDOR_SMC2, USB_PRODUCT_SMC2_##p, i) }
+ SMSC_DEV(LAN89530_ETH, 0),
+ SMSC_DEV(LAN9500_ETH, 0),
+ SMSC_DEV(LAN9500_ETH_2, 0),
+ SMSC_DEV(LAN9500A_ETH, 0),
+ SMSC_DEV(LAN9500A_ETH_2, 0),
+ SMSC_DEV(LAN9505_ETH, 0),
+ SMSC_DEV(LAN9505A_ETH, 0),
+ SMSC_DEV(LAN9514_ETH, 0),
+ SMSC_DEV(LAN9514_ETH_2, 0),
+ SMSC_DEV(LAN9530_ETH, 0),
+ SMSC_DEV(LAN9730_ETH, 0),
+ SMSC_DEV(LAN9500_SAL10, 0),
+ SMSC_DEV(LAN9505_SAL10, 0),
+ SMSC_DEV(LAN9500A_SAL10, 0),
+ SMSC_DEV(LAN9505A_SAL10, 0),
+ SMSC_DEV(LAN9514_SAL10, 0),
+ SMSC_DEV(LAN9500A_HAL, 0),
+ SMSC_DEV(LAN9505A_HAL, 0),
+#undef SMSC_DEV
+};
+
+#ifdef USB_DEBUG
+#define smsc_dbg_printf(sc, fmt, args...) \
+ do { \
+ if (smsc_debug > 0) \
+ device_printf((sc)->sc_ue.ue_dev, "debug: " fmt, ##args); \
+ } while(0)
+#else
+#define smsc_dbg_printf(sc, fmt, args...) do { } while (0)
+#endif
+
+#define smsc_warn_printf(sc, fmt, args...) \
+ device_printf((sc)->sc_ue.ue_dev, "warning: " fmt, ##args)
+
+#define smsc_err_printf(sc, fmt, args...) \
+ device_printf((sc)->sc_ue.ue_dev, "error: " fmt, ##args)
+
+#define ETHER_IS_VALID(addr) \
+ (!ETHER_IS_MULTICAST(addr) && !ETHER_IS_ZERO(addr))
+
+#define BOOTARGS_SMSC95XX "smsc95xx.macaddr"
+
+static device_probe_t smsc_probe;
+static device_attach_t smsc_attach;
+static device_detach_t smsc_detach;
+
+static usb_callback_t smsc_bulk_read_callback;
+static usb_callback_t smsc_bulk_write_callback;
+
+static miibus_readreg_t smsc_miibus_readreg;
+static miibus_writereg_t smsc_miibus_writereg;
+static miibus_statchg_t smsc_miibus_statchg;
+
+static int smsc_attach_post_sub(struct usb_ether *ue);
+static uether_fn_t smsc_attach_post;
+static uether_fn_t smsc_init;
+static uether_fn_t smsc_stop;
+static uether_fn_t smsc_start;
+static uether_fn_t smsc_tick;
+static uether_fn_t smsc_setmulti;
+static uether_fn_t smsc_setpromisc;
+
+static int smsc_ifmedia_upd(if_t);
+static void smsc_ifmedia_sts(if_t, struct ifmediareq *);
+
+static int smsc_chip_init(struct smsc_softc *sc);
+static int smsc_ioctl(if_t ifp, u_long cmd, caddr_t data);
+
+static const struct usb_config smsc_config[SMSC_N_TRANSFER] = {
+ [SMSC_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .frames = 16,
+ .bufsize = 16 * (MCLBYTES + 16),
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = smsc_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ },
+
+ [SMSC_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = 20480, /* bytes */
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = smsc_bulk_read_callback,
+ .timeout = 0, /* no timeout */
+ },
+
+ /* The SMSC chip supports an interrupt endpoints, however they aren't
+ * needed as we poll on the MII status.
+ */
+};
+
+static const struct usb_ether_methods smsc_ue_methods = {
+ .ue_attach_post = smsc_attach_post,
+ .ue_attach_post_sub = smsc_attach_post_sub,
+ .ue_start = smsc_start,
+ .ue_ioctl = smsc_ioctl,
+ .ue_init = smsc_init,
+ .ue_stop = smsc_stop,
+ .ue_tick = smsc_tick,
+ .ue_setmulti = smsc_setmulti,
+ .ue_setpromisc = smsc_setpromisc,
+ .ue_mii_upd = smsc_ifmedia_upd,
+ .ue_mii_sts = smsc_ifmedia_sts,
+};
+
+/**
+ * smsc_read_reg - Reads a 32-bit register on the device
+ * @sc: driver soft context
+ * @off: offset of the register
+ * @data: pointer a value that will be populated with the register value
+ *
+ * LOCKING:
+ * The device lock must be held before calling this function.
+ *
+ * RETURNS:
+ * 0 on success, a USB_ERR_?? error code on failure.
+ */
+static int
+smsc_read_reg(struct smsc_softc *sc, uint32_t off, uint32_t *data)
+{
+ struct usb_device_request req;
+ uint32_t buf;
+ usb_error_t err;
+
+ SMSC_LOCK_ASSERT(sc, MA_OWNED);
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = SMSC_UR_READ_REG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, off);
+ USETW(req.wLength, 4);
+
+ err = uether_do_request(&sc->sc_ue, &req, &buf, 1000);
+ if (err != 0)
+ smsc_warn_printf(sc, "Failed to read register 0x%0x\n", off);
+
+ *data = le32toh(buf);
+
+ return (err);
+}
+
+/**
+ * smsc_write_reg - Writes a 32-bit register on the device
+ * @sc: driver soft context
+ * @off: offset of the register
+ * @data: the 32-bit value to write into the register
+ *
+ * LOCKING:
+ * The device lock must be held before calling this function.
+ *
+ * RETURNS:
+ * 0 on success, a USB_ERR_?? error code on failure.
+ */
+static int
+smsc_write_reg(struct smsc_softc *sc, uint32_t off, uint32_t data)
+{
+ struct usb_device_request req;
+ uint32_t buf;
+ usb_error_t err;
+
+ SMSC_LOCK_ASSERT(sc, MA_OWNED);
+
+ buf = htole32(data);
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = SMSC_UR_WRITE_REG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, off);
+ USETW(req.wLength, 4);
+
+ err = uether_do_request(&sc->sc_ue, &req, &buf, 1000);
+ if (err != 0)
+ smsc_warn_printf(sc, "Failed to write register 0x%0x\n", off);
+
+ return (err);
+}
+
+/**
+ * smsc_wait_for_bits - Polls on a register value until bits are cleared
+ * @sc: soft context
+ * @reg: offset of the register
+ * @bits: if the bits are clear the function returns
+ *
+ * LOCKING:
+ * The device lock must be held before calling this function.
+ *
+ * RETURNS:
+ * 0 on success, or a USB_ERR_?? error code on failure.
+ */
+static int
+smsc_wait_for_bits(struct smsc_softc *sc, uint32_t reg, uint32_t bits)
+{
+ usb_ticks_t start_ticks;
+ const usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000);
+ uint32_t val;
+ int err;
+
+ SMSC_LOCK_ASSERT(sc, MA_OWNED);
+
+ start_ticks = (usb_ticks_t)ticks;
+ do {
+ if ((err = smsc_read_reg(sc, reg, &val)) != 0)
+ return (err);
+ if (!(val & bits))
+ return (0);
+
+ uether_pause(&sc->sc_ue, hz / 100);
+ } while (((usb_ticks_t)(ticks - start_ticks)) < max_ticks);
+
+ return (USB_ERR_TIMEOUT);
+}
+
+/**
+ * smsc_eeprom_read - Reads the attached EEPROM
+ * @sc: soft context
+ * @off: the eeprom address offset
+ * @buf: stores the bytes
+ * @buflen: the number of bytes to read
+ *
+ * Simply reads bytes from an attached eeprom.
+ *
+ * LOCKING:
+ * The function takes and releases the device lock if it is not already held.
+ *
+ * RETURNS:
+ * 0 on success, or a USB_ERR_?? error code on failure.
+ */
+static int
+smsc_eeprom_read(struct smsc_softc *sc, uint16_t off, uint8_t *buf, uint16_t buflen)
+{
+ usb_ticks_t start_ticks;
+ const usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000);
+ int err;
+ int locked;
+ uint32_t val;
+ uint16_t i;
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ SMSC_LOCK(sc);
+
+ err = smsc_wait_for_bits(sc, SMSC_EEPROM_CMD, SMSC_EEPROM_CMD_BUSY);
+ if (err != 0) {
+ smsc_warn_printf(sc, "eeprom busy, failed to read data\n");
+ goto done;
+ }
+
+ /* start reading the bytes, one at a time */
+ for (i = 0; i < buflen; i++) {
+ val = SMSC_EEPROM_CMD_BUSY | (SMSC_EEPROM_CMD_ADDR_MASK & (off + i));
+ if ((err = smsc_write_reg(sc, SMSC_EEPROM_CMD, val)) != 0)
+ goto done;
+
+ start_ticks = (usb_ticks_t)ticks;
+ do {
+ if ((err = smsc_read_reg(sc, SMSC_EEPROM_CMD, &val)) != 0)
+ goto done;
+ if (!(val & SMSC_EEPROM_CMD_BUSY) || (val & SMSC_EEPROM_CMD_TIMEOUT))
+ break;
+
+ uether_pause(&sc->sc_ue, hz / 100);
+ } while (((usb_ticks_t)(ticks - start_ticks)) < max_ticks);
+
+ if (val & (SMSC_EEPROM_CMD_BUSY | SMSC_EEPROM_CMD_TIMEOUT)) {
+ smsc_warn_printf(sc, "eeprom command failed\n");
+ err = USB_ERR_IOERROR;
+ break;
+ }
+
+ if ((err = smsc_read_reg(sc, SMSC_EEPROM_DATA, &val)) != 0)
+ goto done;
+
+ buf[i] = (val & 0xff);
+ }
+
+done:
+ if (!locked)
+ SMSC_UNLOCK(sc);
+
+ return (err);
+}
+
+/**
+ * smsc_miibus_readreg - Reads a MII/MDIO register
+ * @dev: usb ether device
+ * @phy: the number of phy reading from
+ * @reg: the register address
+ *
+ * Attempts to read a phy register over the MII bus.
+ *
+ * LOCKING:
+ * Takes and releases the device mutex lock if not already held.
+ *
+ * RETURNS:
+ * Returns the 16-bits read from the MII register, if this function fails 0
+ * is returned.
+ */
+static int
+smsc_miibus_readreg(device_t dev, int phy, int reg)
+{
+ struct smsc_softc *sc = device_get_softc(dev);
+ int locked;
+ uint32_t addr;
+ uint32_t val = 0;
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ SMSC_LOCK(sc);
+
+ if (smsc_wait_for_bits(sc, SMSC_MII_ADDR, SMSC_MII_BUSY) != 0) {
+ smsc_warn_printf(sc, "MII is busy\n");
+ goto done;
+ }
+
+ addr = (phy << 11) | (reg << 6) | SMSC_MII_READ | SMSC_MII_BUSY;
+ smsc_write_reg(sc, SMSC_MII_ADDR, addr);
+
+ if (smsc_wait_for_bits(sc, SMSC_MII_ADDR, SMSC_MII_BUSY) != 0)
+ smsc_warn_printf(sc, "MII read timeout\n");
+
+ smsc_read_reg(sc, SMSC_MII_DATA, &val);
+ val = le32toh(val);
+
+done:
+ if (!locked)
+ SMSC_UNLOCK(sc);
+
+ return (val & 0xFFFF);
+}
+
+/**
+ * smsc_miibus_writereg - Writes a MII/MDIO register
+ * @dev: usb ether device
+ * @phy: the number of phy writing to
+ * @reg: the register address
+ * @val: the value to write
+ *
+ * Attempts to write a phy register over the MII bus.
+ *
+ * LOCKING:
+ * Takes and releases the device mutex lock if not already held.
+ *
+ * RETURNS:
+ * Always returns 0 regardless of success or failure.
+ */
+static int
+smsc_miibus_writereg(device_t dev, int phy, int reg, int val)
+{
+ struct smsc_softc *sc = device_get_softc(dev);
+ int locked;
+ uint32_t addr;
+
+ if (sc->sc_phyno != phy)
+ return (0);
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ SMSC_LOCK(sc);
+
+ if (smsc_wait_for_bits(sc, SMSC_MII_ADDR, SMSC_MII_BUSY) != 0) {
+ smsc_warn_printf(sc, "MII is busy\n");
+ goto done;
+ }
+
+ val = htole32(val);
+ smsc_write_reg(sc, SMSC_MII_DATA, val);
+
+ addr = (phy << 11) | (reg << 6) | SMSC_MII_WRITE | SMSC_MII_BUSY;
+ smsc_write_reg(sc, SMSC_MII_ADDR, addr);
+
+ if (smsc_wait_for_bits(sc, SMSC_MII_ADDR, SMSC_MII_BUSY) != 0)
+ smsc_warn_printf(sc, "MII write timeout\n");
+
+done:
+ if (!locked)
+ SMSC_UNLOCK(sc);
+ return (0);
+}
+
+/**
+ * smsc_miibus_statchg - Called to detect phy status change
+ * @dev: usb ether device
+ *
+ * This function is called periodically by the system to poll for status
+ * changes of the link.
+ *
+ * LOCKING:
+ * Takes and releases the device mutex lock if not already held.
+ */
+static void
+smsc_miibus_statchg(device_t dev)
+{
+ struct smsc_softc *sc = device_get_softc(dev);
+ struct mii_data *mii = uether_getmii(&sc->sc_ue);
+ if_t ifp;
+ int locked;
+ int err;
+ uint32_t flow;
+ uint32_t afc_cfg;
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ SMSC_LOCK(sc);
+
+ ifp = uether_getifp(&sc->sc_ue);
+ if (mii == NULL || ifp == NULL ||
+ (if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0)
+ goto done;
+
+ /* Use the MII status to determine link status */
+ sc->sc_flags &= ~SMSC_FLAG_LINK;
+ if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) ==
+ (IFM_ACTIVE | IFM_AVALID)) {
+ switch (IFM_SUBTYPE(mii->mii_media_active)) {
+ case IFM_10_T:
+ case IFM_100_TX:
+ sc->sc_flags |= SMSC_FLAG_LINK;
+ break;
+ case IFM_1000_T:
+ /* Gigabit ethernet not supported by chipset */
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* Lost link, do nothing. */
+ if ((sc->sc_flags & SMSC_FLAG_LINK) == 0) {
+ smsc_dbg_printf(sc, "link flag not set\n");
+ goto done;
+ }
+
+ err = smsc_read_reg(sc, SMSC_AFC_CFG, &afc_cfg);
+ if (err) {
+ smsc_warn_printf(sc, "failed to read initial AFC_CFG, error %d\n", err);
+ goto done;
+ }
+
+ /* Enable/disable full duplex operation and TX/RX pause */
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) {
+ smsc_dbg_printf(sc, "full duplex operation\n");
+ sc->sc_mac_csr &= ~SMSC_MAC_CSR_RCVOWN;
+ sc->sc_mac_csr |= SMSC_MAC_CSR_FDPX;
+
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE) != 0)
+ flow = 0xffff0002;
+ else
+ flow = 0;
+
+ if ((IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE) != 0)
+ afc_cfg |= 0xf;
+ else
+ afc_cfg &= ~0xf;
+
+ } else {
+ smsc_dbg_printf(sc, "half duplex operation\n");
+ sc->sc_mac_csr &= ~SMSC_MAC_CSR_FDPX;
+ sc->sc_mac_csr |= SMSC_MAC_CSR_RCVOWN;
+
+ flow = 0;
+ afc_cfg |= 0xf;
+ }
+
+ err = smsc_write_reg(sc, SMSC_MAC_CSR, sc->sc_mac_csr);
+ err += smsc_write_reg(sc, SMSC_FLOW, flow);
+ err += smsc_write_reg(sc, SMSC_AFC_CFG, afc_cfg);
+ if (err)
+ smsc_warn_printf(sc, "media change failed, error %d\n", err);
+
+done:
+ if (!locked)
+ SMSC_UNLOCK(sc);
+}
+
+/**
+ * smsc_ifmedia_upd - Set media options
+ * @ifp: interface pointer
+ *
+ * Basically boilerplate code that simply calls the mii functions to set the
+ * media options.
+ *
+ * LOCKING:
+ * The device lock must be held before this function is called.
+ *
+ * RETURNS:
+ * Returns 0 on success or a negative error code.
+ */
+static int
+smsc_ifmedia_upd(if_t ifp)
+{
+ struct smsc_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = uether_getmii(&sc->sc_ue);
+ struct mii_softc *miisc;
+ int err;
+
+ SMSC_LOCK_ASSERT(sc, MA_OWNED);
+
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list)
+ PHY_RESET(miisc);
+ err = mii_mediachg(mii);
+ return (err);
+}
+
+/**
+ * smsc_ifmedia_sts - Report current media status
+ * @ifp: inet interface pointer
+ * @ifmr: interface media request
+ *
+ * Basically boilerplate code that simply calls the mii functions to get the
+ * media status.
+ *
+ * LOCKING:
+ * Internally takes and releases the device lock.
+ */
+static void
+smsc_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct smsc_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = uether_getmii(&sc->sc_ue);
+
+ SMSC_LOCK(sc);
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+ SMSC_UNLOCK(sc);
+}
+
+/**
+ * smsc_hash - Calculate the hash of a mac address
+ * @addr: The mac address to calculate the hash on
+ *
+ * This function is used when configuring a range of m'cast mac addresses to
+ * filter on. The hash of the mac address is put in the device's mac hash
+ * table.
+ *
+ * RETURNS:
+ * Returns a value from 0-63 value which is the hash of the mac address.
+ */
+static inline uint32_t
+smsc_hash(uint8_t addr[ETHER_ADDR_LEN])
+{
+ return (ether_crc32_be(addr, ETHER_ADDR_LEN) >> 26) & 0x3f;
+}
+
+static u_int
+smsc_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
+{
+ uint32_t hash, *hashtbl = arg;
+
+ hash = smsc_hash(LLADDR(sdl));
+ hashtbl[hash >> 5] |= 1 << (hash & 0x1F);
+
+ return (1);
+}
+
+/**
+ * smsc_setmulti - Setup multicast
+ * @ue: usb ethernet device context
+ *
+ * Tells the device to either accept frames with a multicast mac address, a
+ * select group of m'cast mac addresses or just the devices mac address.
+ *
+ * LOCKING:
+ * Should be called with the SMSC lock held.
+ */
+static void
+smsc_setmulti(struct usb_ether *ue)
+{
+ struct smsc_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ uint32_t hashtbl[2] = { 0, 0 };
+
+ SMSC_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (if_getflags(ifp) & (IFF_ALLMULTI | IFF_PROMISC)) {
+ smsc_dbg_printf(sc, "receive all multicast enabled\n");
+ sc->sc_mac_csr |= SMSC_MAC_CSR_MCPAS;
+ sc->sc_mac_csr &= ~SMSC_MAC_CSR_HPFILT;
+
+ } else {
+ if (if_foreach_llmaddr(ifp, smsc_hash_maddr, &hashtbl) > 0) {
+ /* We are filtering on a set of address so calculate
+ * hashes of each of the address and set the
+ * corresponding bits in the register.
+ */
+ sc->sc_mac_csr |= SMSC_MAC_CSR_HPFILT;
+ sc->sc_mac_csr &= ~(SMSC_MAC_CSR_PRMS | SMSC_MAC_CSR_MCPAS);
+ } else {
+ /* Only receive packets with destination set to
+ * our mac address
+ */
+ sc->sc_mac_csr &= ~(SMSC_MAC_CSR_MCPAS | SMSC_MAC_CSR_HPFILT);
+ }
+
+ /* Debug */
+ if (sc->sc_mac_csr & SMSC_MAC_CSR_HPFILT)
+ smsc_dbg_printf(sc, "receive select group of macs\n");
+ else
+ smsc_dbg_printf(sc, "receive own packets only\n");
+ }
+
+ /* Write the hash table and mac control registers */
+ smsc_write_reg(sc, SMSC_HASHH, hashtbl[1]);
+ smsc_write_reg(sc, SMSC_HASHL, hashtbl[0]);
+ smsc_write_reg(sc, SMSC_MAC_CSR, sc->sc_mac_csr);
+}
+
+/**
+ * smsc_setpromisc - Enables/disables promiscuous mode
+ * @ue: usb ethernet device context
+ *
+ * LOCKING:
+ * Should be called with the SMSC lock held.
+ */
+static void
+smsc_setpromisc(struct usb_ether *ue)
+{
+ struct smsc_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ smsc_dbg_printf(sc, "promiscuous mode %sabled\n",
+ (if_getflags(ifp) & IFF_PROMISC) ? "en" : "dis");
+
+ SMSC_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (if_getflags(ifp) & IFF_PROMISC)
+ sc->sc_mac_csr |= SMSC_MAC_CSR_PRMS;
+ else
+ sc->sc_mac_csr &= ~SMSC_MAC_CSR_PRMS;
+
+ smsc_write_reg(sc, SMSC_MAC_CSR, sc->sc_mac_csr);
+}
+
+/**
+ * smsc_sethwcsum - Enable or disable H/W UDP and TCP checksumming
+ * @sc: driver soft context
+ *
+ * LOCKING:
+ * Should be called with the SMSC lock held.
+ *
+ * RETURNS:
+ * Returns 0 on success or a negative error code.
+ */
+static int smsc_sethwcsum(struct smsc_softc *sc)
+{
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ uint32_t val;
+ int err;
+
+ if (!ifp)
+ return (-EIO);
+
+ SMSC_LOCK_ASSERT(sc, MA_OWNED);
+
+ err = smsc_read_reg(sc, SMSC_COE_CTRL, &val);
+ if (err != 0) {
+ smsc_warn_printf(sc, "failed to read SMSC_COE_CTRL (err=%d)\n", err);
+ return (err);
+ }
+
+ /* Enable/disable the Rx checksum */
+ if ((if_getcapabilities(ifp) & if_getcapenable(ifp)) & IFCAP_RXCSUM)
+ val |= SMSC_COE_CTRL_RX_EN;
+ else
+ val &= ~SMSC_COE_CTRL_RX_EN;
+
+ /* Enable/disable the Tx checksum (currently not supported) */
+ if ((if_getcapabilities(ifp) & if_getcapenable(ifp)) & IFCAP_TXCSUM)
+ val |= SMSC_COE_CTRL_TX_EN;
+ else
+ val &= ~SMSC_COE_CTRL_TX_EN;
+
+ err = smsc_write_reg(sc, SMSC_COE_CTRL, val);
+ if (err != 0) {
+ smsc_warn_printf(sc, "failed to write SMSC_COE_CTRL (err=%d)\n", err);
+ return (err);
+ }
+
+ return (0);
+}
+
+/**
+ * smsc_setmacaddress - Sets the mac address in the device
+ * @sc: driver soft context
+ * @addr: pointer to array contain at least 6 bytes of the mac
+ *
+ * Writes the MAC address into the device, usually the MAC is programmed with
+ * values from the EEPROM.
+ *
+ * LOCKING:
+ * Should be called with the SMSC lock held.
+ *
+ * RETURNS:
+ * Returns 0 on success or a negative error code.
+ */
+static int
+smsc_setmacaddress(struct smsc_softc *sc, const uint8_t *addr)
+{
+ int err;
+ uint32_t val;
+
+ smsc_dbg_printf(sc, "setting mac address to %02x:%02x:%02x:%02x:%02x:%02x\n",
+ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+
+ SMSC_LOCK_ASSERT(sc, MA_OWNED);
+
+ val = (addr[3] << 24) | (addr[2] << 16) | (addr[1] << 8) | addr[0];
+ if ((err = smsc_write_reg(sc, SMSC_MAC_ADDRL, val)) != 0)
+ goto done;
+
+ val = (addr[5] << 8) | addr[4];
+ err = smsc_write_reg(sc, SMSC_MAC_ADDRH, val);
+
+done:
+ return (err);
+}
+
+/**
+ * smsc_reset - Reset the SMSC chip
+ * @sc: device soft context
+ *
+ * LOCKING:
+ * Should be called with the SMSC lock held.
+ */
+static void
+smsc_reset(struct smsc_softc *sc)
+{
+ struct usb_config_descriptor *cd;
+ usb_error_t err;
+
+ cd = usbd_get_config_descriptor(sc->sc_ue.ue_udev);
+
+ err = usbd_req_set_config(sc->sc_ue.ue_udev, &sc->sc_mtx,
+ cd->bConfigurationValue);
+ if (err)
+ smsc_warn_printf(sc, "reset failed (ignored)\n");
+
+ /* Wait a little while for the chip to get its brains in order. */
+ uether_pause(&sc->sc_ue, hz / 100);
+
+ /* Reinitialize controller to achieve full reset. */
+ smsc_chip_init(sc);
+}
+
+/**
+ * smsc_init - Initialises the LAN95xx chip
+ * @ue: USB ether interface
+ *
+ * Called when the interface is brought up (i.e. ifconfig ue0 up), this
+ * initialise the interface and the rx/tx pipes.
+ *
+ * LOCKING:
+ * Should be called with the SMSC lock held.
+ */
+static void
+smsc_init(struct usb_ether *ue)
+{
+ struct smsc_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ SMSC_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (smsc_setmacaddress(sc, if_getlladdr(ifp)))
+ smsc_dbg_printf(sc, "setting MAC address failed\n");
+
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0)
+ return;
+
+ /* Cancel pending I/O */
+ smsc_stop(ue);
+
+ /* Reset the ethernet interface. */
+ smsc_reset(sc);
+
+ /* Load the multicast filter. */
+ smsc_setmulti(ue);
+
+ /* TCP/UDP checksum offload engines. */
+ smsc_sethwcsum(sc);
+
+ usbd_xfer_set_stall(sc->sc_xfer[SMSC_BULK_DT_WR]);
+
+ /* Indicate we are up and running. */
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+
+ /* Switch to selected media. */
+ smsc_ifmedia_upd(ifp);
+ smsc_start(ue);
+}
+
+/**
+ * smsc_bulk_read_callback - Read callback used to process the USB URB
+ * @xfer: the USB transfer
+ * @error:
+ *
+ * Reads the URB data which can contain one or more ethernet frames, the
+ * frames are copyed into a mbuf and given to the system.
+ *
+ * LOCKING:
+ * No locking required, doesn't access internal driver settings.
+ */
+static void
+smsc_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct smsc_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_ether *ue = &sc->sc_ue;
+ if_t ifp = uether_getifp(ue);
+ struct mbuf *m;
+ struct usb_page_cache *pc;
+ uint32_t rxhdr;
+ int pktlen;
+ int off;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+ smsc_dbg_printf(sc, "rx : actlen %d\n", actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ /* There is always a zero length frame after bringing the IF up */
+ if (actlen < (sizeof(rxhdr) + ETHER_CRC_LEN))
+ goto tr_setup;
+
+ /* There maybe multiple packets in the USB frame, each will have a
+ * header and each needs to have it's own mbuf allocated and populated
+ * for it.
+ */
+ pc = usbd_xfer_get_frame(xfer, 0);
+ off = 0;
+
+ while (off < actlen) {
+
+ /* The frame header is always aligned on a 4 byte boundary */
+ off = ((off + 0x3) & ~0x3);
+
+ if ((off + sizeof(rxhdr)) > actlen)
+ goto tr_setup;
+
+ usbd_copy_out(pc, off, &rxhdr, sizeof(rxhdr));
+ off += (sizeof(rxhdr) + ETHER_ALIGN);
+ rxhdr = le32toh(rxhdr);
+
+ pktlen = (uint16_t)SMSC_RX_STAT_FRM_LENGTH(rxhdr);
+
+ smsc_dbg_printf(sc, "rx : rxhdr 0x%08x : pktlen %d : actlen %d : "
+ "off %d\n", rxhdr, pktlen, actlen, off);
+
+
+ if (rxhdr & SMSC_RX_STAT_ERROR) {
+ smsc_dbg_printf(sc, "rx error (hdr 0x%08x)\n", rxhdr);
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ if (rxhdr & SMSC_RX_STAT_COLLISION)
+ if_inc_counter(ifp, IFCOUNTER_COLLISIONS, 1);
+ } else {
+ /* Check if the ethernet frame is too big or too small */
+ if ((pktlen < ETHER_HDR_LEN) || (pktlen > (actlen - off)))
+ goto tr_setup;
+
+ /* Create a new mbuf to store the packet in */
+ m = uether_newbuf();
+ if (m == NULL) {
+ smsc_warn_printf(sc, "failed to create new mbuf\n");
+ if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
+ goto tr_setup;
+ }
+ if (pktlen > m->m_len) {
+ smsc_dbg_printf(sc, "buffer too small %d vs %d bytes",
+ pktlen, m->m_len);
+ if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
+ m_freem(m);
+ goto tr_setup;
+ }
+ usbd_copy_out(pc, off, mtod(m, uint8_t *), pktlen);
+
+ /* Check if RX TCP/UDP checksumming is being offloaded */
+ if ((if_getcapenable(ifp) & IFCAP_RXCSUM) != 0) {
+ struct ether_header *eh;
+
+ eh = mtod(m, struct ether_header *);
+
+ /* Remove the extra 2 bytes of the csum */
+ pktlen -= 2;
+
+ /* The checksum appears to be simplistically calculated
+ * over the udp/tcp header and data up to the end of the
+ * eth frame. Which means if the eth frame is padded
+ * the csum calculation is incorrectly performed over
+ * the padding bytes as well. Therefore to be safe we
+ * ignore the H/W csum on frames less than or equal to
+ * 64 bytes.
+ *
+ * Ignore H/W csum for non-IPv4 packets.
+ */
+ if ((be16toh(eh->ether_type) == ETHERTYPE_IP) &&
+ (pktlen > ETHER_MIN_LEN)) {
+ struct ip *ip;
+
+ ip = (struct ip *)(eh + 1);
+ if ((ip->ip_v == IPVERSION) &&
+ ((ip->ip_p == IPPROTO_TCP) ||
+ (ip->ip_p == IPPROTO_UDP))) {
+ /* Indicate the UDP/TCP csum has been calculated */
+ m->m_pkthdr.csum_flags |= CSUM_DATA_VALID;
+
+ /* Copy the TCP/UDP checksum from the last 2 bytes
+ * of the transfer and put in the csum_data field.
+ */
+ usbd_copy_out(pc, (off + pktlen),
+ &m->m_pkthdr.csum_data, 2);
+
+ /* The data is copied in network order, but the
+ * csum algorithm in the kernel expects it to be
+ * in host network order.
+ */
+ m->m_pkthdr.csum_data = ntohs(m->m_pkthdr.csum_data);
+
+ smsc_dbg_printf(sc, "RX checksum offloaded (0x%04x)\n",
+ m->m_pkthdr.csum_data);
+ }
+ }
+
+ /* Need to adjust the offset as well or we'll be off
+ * by 2 because the csum is removed from the packet
+ * length.
+ */
+ off += 2;
+ }
+
+ /* Finally enqueue the mbuf on the receive queue */
+ /* Remove 4 trailing bytes */
+ if (pktlen < (4 + ETHER_HDR_LEN)) {
+ m_freem(m);
+ goto tr_setup;
+ }
+ uether_rxmbuf(ue, m, pktlen - 4);
+ }
+
+ /* Update the offset to move to the next potential packet */
+ off += pktlen;
+ }
+
+ /* FALLTHROUGH */
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ uether_rxflush(ue);
+ return;
+
+ default:
+ if (error != USB_ERR_CANCELLED) {
+ smsc_warn_printf(sc, "bulk read error, %s\n", usbd_errstr(error));
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+/**
+ * smsc_bulk_write_callback - Write callback used to send ethernet frame(s)
+ * @xfer: the USB transfer
+ * @error: error code if the transfers is in an errored state
+ *
+ * The main write function that pulls ethernet frames off the queue and sends
+ * them out.
+ *
+ * LOCKING:
+ *
+ */
+static void
+smsc_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct smsc_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ uint32_t txhdr;
+ uint32_t frm_len = 0;
+ int nframes;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+ /* FALLTHROUGH */
+
+ case USB_ST_SETUP:
+tr_setup:
+ if ((sc->sc_flags & SMSC_FLAG_LINK) == 0 ||
+ (if_getdrvflags(ifp) & IFF_DRV_OACTIVE) != 0) {
+ /* Don't send anything if there is no link or controller is busy. */
+ return;
+ }
+
+ for (nframes = 0; nframes < 16 &&
+ !if_sendq_empty(ifp); nframes++) {
+ m = if_dequeue(ifp);
+ if (m == NULL)
+ break;
+ usbd_xfer_set_frame_offset(xfer, nframes * MCLBYTES,
+ nframes);
+ frm_len = 0;
+ pc = usbd_xfer_get_frame(xfer, nframes);
+
+ /* Each frame is prefixed with two 32-bit values describing the
+ * length of the packet and buffer.
+ */
+ txhdr = SMSC_TX_CTRL_0_BUF_SIZE(m->m_pkthdr.len) |
+ SMSC_TX_CTRL_0_FIRST_SEG | SMSC_TX_CTRL_0_LAST_SEG;
+ txhdr = htole32(txhdr);
+ usbd_copy_in(pc, 0, &txhdr, sizeof(txhdr));
+
+ txhdr = SMSC_TX_CTRL_1_PKT_LENGTH(m->m_pkthdr.len);
+ txhdr = htole32(txhdr);
+ usbd_copy_in(pc, 4, &txhdr, sizeof(txhdr));
+
+ frm_len += 8;
+
+ /* Next copy in the actual packet */
+ usbd_m_copy_in(pc, frm_len, m, 0, m->m_pkthdr.len);
+ frm_len += m->m_pkthdr.len;
+
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /* If there's a BPF listener, bounce a copy of this frame to him */
+ BPF_MTAP(ifp, m);
+
+ m_freem(m);
+
+ /* Set frame length. */
+ usbd_xfer_set_frame_len(xfer, nframes, frm_len);
+ }
+ if (nframes != 0) {
+ usbd_xfer_set_frames(xfer, nframes);
+ usbd_transfer_submit(xfer);
+ if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0);
+ }
+ return;
+
+ default:
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+
+ if (error != USB_ERR_CANCELLED) {
+ smsc_err_printf(sc, "usb error on tx: %s\n", usbd_errstr(error));
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+/**
+ * smsc_tick - Called periodically to monitor the state of the LAN95xx chip
+ * @ue: USB ether interface
+ *
+ * Simply calls the mii status functions to check the state of the link.
+ *
+ * LOCKING:
+ * Should be called with the SMSC lock held.
+ */
+static void
+smsc_tick(struct usb_ether *ue)
+{
+ struct smsc_softc *sc = uether_getsc(ue);
+ struct mii_data *mii = uether_getmii(&sc->sc_ue);
+
+ SMSC_LOCK_ASSERT(sc, MA_OWNED);
+
+ mii_tick(mii);
+ if ((sc->sc_flags & SMSC_FLAG_LINK) == 0) {
+ smsc_miibus_statchg(ue->ue_dev);
+ if ((sc->sc_flags & SMSC_FLAG_LINK) != 0)
+ smsc_start(ue);
+ }
+}
+
+/**
+ * smsc_start - Starts communication with the LAN95xx chip
+ * @ue: USB ether interface
+ *
+ *
+ *
+ */
+static void
+smsc_start(struct usb_ether *ue)
+{
+ struct smsc_softc *sc = uether_getsc(ue);
+
+ /*
+ * start the USB transfers, if not already started:
+ */
+ usbd_transfer_start(sc->sc_xfer[SMSC_BULK_DT_RD]);
+ usbd_transfer_start(sc->sc_xfer[SMSC_BULK_DT_WR]);
+}
+
+/**
+ * smsc_stop - Stops communication with the LAN95xx chip
+ * @ue: USB ether interface
+ *
+ *
+ *
+ */
+static void
+smsc_stop(struct usb_ether *ue)
+{
+ struct smsc_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ SMSC_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, 0, (IFF_DRV_RUNNING | IFF_DRV_OACTIVE));
+ sc->sc_flags &= ~SMSC_FLAG_LINK;
+
+ /*
+ * stop all the transfers, if not already stopped:
+ */
+ usbd_transfer_stop(sc->sc_xfer[SMSC_BULK_DT_WR]);
+ usbd_transfer_stop(sc->sc_xfer[SMSC_BULK_DT_RD]);
+}
+
+/**
+ * smsc_phy_init - Initialises the in-built SMSC phy
+ * @sc: driver soft context
+ *
+ * Resets the PHY part of the chip and then initialises it to default
+ * values. The 'link down' and 'auto-negotiation complete' interrupts
+ * from the PHY are also enabled, however we don't monitor the interrupt
+ * endpoints for the moment.
+ *
+ * RETURNS:
+ * Returns 0 on success or EIO if failed to reset the PHY.
+ */
+static int
+smsc_phy_init(struct smsc_softc *sc)
+{
+ int bmcr;
+ usb_ticks_t start_ticks;
+ const usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000);
+
+ SMSC_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* Reset phy and wait for reset to complete */
+ smsc_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR, BMCR_RESET);
+
+ start_ticks = ticks;
+ do {
+ uether_pause(&sc->sc_ue, hz / 100);
+ bmcr = smsc_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR);
+ } while ((bmcr & BMCR_RESET) && ((ticks - start_ticks) < max_ticks));
+
+ if (((usb_ticks_t)(ticks - start_ticks)) >= max_ticks) {
+ smsc_err_printf(sc, "PHY reset timed-out");
+ return (EIO);
+ }
+
+ smsc_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_ANAR,
+ ANAR_10 | ANAR_10_FD | ANAR_TX | ANAR_TX_FD | /* all modes */
+ ANAR_CSMA |
+ ANAR_FC |
+ ANAR_PAUSE_ASYM);
+
+ /* Setup the phy to interrupt when the link goes down or autoneg completes */
+ smsc_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, SMSC_PHY_INTR_STAT);
+ smsc_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, SMSC_PHY_INTR_MASK,
+ (SMSC_PHY_INTR_ANEG_COMP | SMSC_PHY_INTR_LINK_DOWN));
+
+ /* Restart auto-negotiation */
+ bmcr = smsc_miibus_readreg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR);
+ bmcr |= BMCR_STARTNEG;
+ smsc_miibus_writereg(sc->sc_ue.ue_dev, sc->sc_phyno, MII_BMCR, bmcr);
+
+ return (0);
+}
+
+/**
+ * smsc_chip_init - Initialises the chip after power on
+ * @sc: driver soft context
+ *
+ * This initialisation sequence is modelled on the procedure in the Linux
+ * driver.
+ *
+ * RETURNS:
+ * Returns 0 on success or an error code on failure.
+ */
+static int
+smsc_chip_init(struct smsc_softc *sc)
+{
+ int err;
+ int locked;
+ uint32_t reg_val;
+ int burst_cap;
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ SMSC_LOCK(sc);
+
+ /* Enter H/W config mode */
+ smsc_write_reg(sc, SMSC_HW_CFG, SMSC_HW_CFG_LRST);
+
+ if ((err = smsc_wait_for_bits(sc, SMSC_HW_CFG, SMSC_HW_CFG_LRST)) != 0) {
+ smsc_warn_printf(sc, "timed-out waiting for reset to complete\n");
+ goto init_failed;
+ }
+
+ /* Reset the PHY */
+ smsc_write_reg(sc, SMSC_PM_CTRL, SMSC_PM_CTRL_PHY_RST);
+
+ if ((err = smsc_wait_for_bits(sc, SMSC_PM_CTRL, SMSC_PM_CTRL_PHY_RST)) != 0) {
+ smsc_warn_printf(sc, "timed-out waiting for phy reset to complete\n");
+ goto init_failed;
+ }
+
+ /* Set the mac address */
+ if ((err = smsc_setmacaddress(sc, sc->sc_ue.ue_eaddr)) != 0) {
+ smsc_warn_printf(sc, "failed to set the MAC address\n");
+ goto init_failed;
+ }
+
+ /* Don't know what the HW_CFG_BIR bit is, but following the reset sequence
+ * as used in the Linux driver.
+ */
+ if ((err = smsc_read_reg(sc, SMSC_HW_CFG, &reg_val)) != 0) {
+ smsc_warn_printf(sc, "failed to read HW_CFG: %d\n", err);
+ goto init_failed;
+ }
+ reg_val |= SMSC_HW_CFG_BIR;
+ smsc_write_reg(sc, SMSC_HW_CFG, reg_val);
+
+ /* There is a so called 'turbo mode' that the linux driver supports, it
+ * seems to allow you to jam multiple frames per Rx transaction. By default
+ * this driver supports that and therefore allows multiple frames per URB.
+ *
+ * The xfer buffer size needs to reflect this as well, therefore based on
+ * the calculations in the Linux driver the RX bufsize is set to 18944,
+ * bufsz = (16 * 1024 + 5 * 512)
+ *
+ * Burst capability is the number of URBs that can be in a burst of data/
+ * ethernet frames.
+ */
+ if (!smsc_rx_packet_batching)
+ burst_cap = 0;
+ else if (usbd_get_speed(sc->sc_ue.ue_udev) == USB_SPEED_HIGH)
+ burst_cap = 37;
+ else
+ burst_cap = 128;
+
+ smsc_write_reg(sc, SMSC_BURST_CAP, burst_cap);
+
+ /* Set the default bulk in delay (magic value from Linux driver) */
+ smsc_write_reg(sc, SMSC_BULK_IN_DLY, 0x00002000);
+
+ /*
+ * Initialise the RX interface
+ */
+ if ((err = smsc_read_reg(sc, SMSC_HW_CFG, &reg_val)) < 0) {
+ smsc_warn_printf(sc, "failed to read HW_CFG: (err = %d)\n", err);
+ goto init_failed;
+ }
+
+ /* Adjust the packet offset in the buffer (designed to try and align IP
+ * header on 4 byte boundary)
+ */
+ reg_val &= ~SMSC_HW_CFG_RXDOFF;
+ reg_val |= (ETHER_ALIGN << 9) & SMSC_HW_CFG_RXDOFF;
+
+ /* The following settings are used for 'turbo mode', a.k.a multiple frames
+ * per Rx transaction (again info taken form Linux driver).
+ */
+ if (smsc_rx_packet_batching)
+ reg_val |= (SMSC_HW_CFG_MEF | SMSC_HW_CFG_BCE);
+
+ smsc_write_reg(sc, SMSC_HW_CFG, reg_val);
+
+ /* Clear the status register ? */
+ smsc_write_reg(sc, SMSC_INTR_STATUS, 0xffffffff);
+
+ /* Read and display the revision register */
+ if ((err = smsc_read_reg(sc, SMSC_ID_REV, &sc->sc_rev_id)) < 0) {
+ smsc_warn_printf(sc, "failed to read ID_REV (err = %d)\n", err);
+ goto init_failed;
+ }
+
+ device_printf(sc->sc_ue.ue_dev, "chip 0x%04lx, rev. %04lx\n",
+ (sc->sc_rev_id & SMSC_ID_REV_CHIP_ID_MASK) >> 16,
+ (sc->sc_rev_id & SMSC_ID_REV_CHIP_REV_MASK));
+
+ /* GPIO/LED setup */
+ reg_val = SMSC_LED_GPIO_CFG_SPD_LED | SMSC_LED_GPIO_CFG_LNK_LED |
+ SMSC_LED_GPIO_CFG_FDX_LED;
+ smsc_write_reg(sc, SMSC_LED_GPIO_CFG, reg_val);
+
+ /*
+ * Initialise the TX interface
+ */
+ smsc_write_reg(sc, SMSC_FLOW, 0);
+
+ smsc_write_reg(sc, SMSC_AFC_CFG, AFC_CFG_DEFAULT);
+
+ /* Read the current MAC configuration */
+ if ((err = smsc_read_reg(sc, SMSC_MAC_CSR, &sc->sc_mac_csr)) < 0) {
+ smsc_warn_printf(sc, "failed to read MAC_CSR (err=%d)\n", err);
+ goto init_failed;
+ }
+
+ /* Vlan */
+ smsc_write_reg(sc, SMSC_VLAN1, (uint32_t)ETHERTYPE_VLAN);
+
+ /*
+ * Initialise the PHY
+ */
+ if ((err = smsc_phy_init(sc)) != 0)
+ goto init_failed;
+
+ /*
+ * Start TX
+ */
+ sc->sc_mac_csr |= SMSC_MAC_CSR_TXEN;
+ smsc_write_reg(sc, SMSC_MAC_CSR, sc->sc_mac_csr);
+ smsc_write_reg(sc, SMSC_TX_CFG, SMSC_TX_CFG_ON);
+
+ /*
+ * Start RX
+ */
+ sc->sc_mac_csr |= SMSC_MAC_CSR_RXEN;
+ smsc_write_reg(sc, SMSC_MAC_CSR, sc->sc_mac_csr);
+
+ if (!locked)
+ SMSC_UNLOCK(sc);
+
+ return (0);
+
+init_failed:
+ if (!locked)
+ SMSC_UNLOCK(sc);
+
+ smsc_err_printf(sc, "smsc_chip_init failed (err=%d)\n", err);
+ return (err);
+}
+
+/**
+ * smsc_ioctl - ioctl function for the device
+ * @ifp: interface pointer
+ * @cmd: the ioctl command
+ * @data: data passed in the ioctl call, typically a pointer to struct ifreq.
+ *
+ * The ioctl routine is overridden to detect change requests for the H/W
+ * checksum capabilities.
+ *
+ * RETURNS:
+ * 0 on success and an error code on failure.
+ */
+static int
+smsc_ioctl(if_t ifp, u_long cmd, caddr_t data)
+{
+ struct usb_ether *ue = if_getsoftc(ifp);
+ struct smsc_softc *sc;
+ struct ifreq *ifr;
+ int rc;
+ int mask;
+ int reinit;
+
+ if (cmd == SIOCSIFCAP) {
+ sc = uether_getsc(ue);
+ ifr = (struct ifreq *)data;
+
+ SMSC_LOCK(sc);
+
+ rc = 0;
+ reinit = 0;
+
+ mask = ifr->ifr_reqcap ^ if_getcapenable(ifp);
+
+ /* Modify the RX CSUM enable bits */
+ if ((mask & IFCAP_RXCSUM) != 0 &&
+ (if_getcapabilities(ifp) & IFCAP_RXCSUM) != 0) {
+ if_togglecapenable(ifp, IFCAP_RXCSUM);
+
+ if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) {
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+ reinit = 1;
+ }
+ }
+
+ SMSC_UNLOCK(sc);
+ if (reinit)
+ uether_init(ue);
+
+ } else {
+ rc = uether_ioctl(ifp, cmd, data);
+ }
+
+ return (rc);
+}
+
+#ifdef FDT
+static bool
+smsc_get_smsc95xx_macaddr(char* bootargs, size_t len, struct usb_ether *ue)
+{
+ int values[6];
+ int i;
+ char* p;
+
+ p = strnstr(bootargs, BOOTARGS_SMSC95XX, len);
+ if (p == NULL)
+ return (false);
+
+ if (sscanf(p, BOOTARGS_SMSC95XX "=%x:%x:%x:%x:%x:%x%*c",
+ &values[0], &values[1], &values[2],
+ &values[3], &values[4], &values[5]) != 6) {
+ smsc_warn_printf((struct smsc_softc *)ue->ue_sc,
+ "invalid mac from bootargs '%s'.\n", p);
+ return (false);
+ }
+
+ for (i = 0; i < ETHER_ADDR_LEN; ++i)
+ ue->ue_eaddr[i] = values[i];
+
+ smsc_dbg_printf((struct smsc_softc *)ue->ue_sc,
+ "bootargs mac=%6D.\n", ue->ue_eaddr, ":");
+ return (true);
+}
+
+/**
+ * Raspberry Pi is known to pass smsc95xx.macaddr=XX:XX:XX:XX:XX:XX via
+ * bootargs.
+ */
+static bool
+smsc_bootargs_get_mac_addr(device_t dev, struct usb_ether *ue)
+{
+ char *bootargs;
+ ssize_t len;
+ phandle_t node;
+
+ /* only use bootargs for the first device
+ * to prevent duplicate mac addresses */
+ if (device_get_unit(dev) != 0)
+ return (false);
+ node = OF_finddevice("/chosen");
+ if (node == -1)
+ return (false);
+ if (OF_hasprop(node, "bootargs") == 0) {
+ smsc_dbg_printf((struct smsc_softc *)ue->ue_sc,
+ "bootargs not found");
+ return (false);
+ }
+ len = OF_getprop_alloc(node, "bootargs", (void **)&bootargs);
+ if (len == -1 || bootargs == NULL) {
+ smsc_warn_printf((struct smsc_softc *)ue->ue_sc,
+ "failed alloc for bootargs (%zd)", len);
+ return (false);
+ }
+ smsc_dbg_printf((struct smsc_softc *)ue->ue_sc, "bootargs: %s.\n",
+ bootargs);
+ if (!smsc_get_smsc95xx_macaddr(bootargs, len, ue)) {
+ OF_prop_free(bootargs);
+ return (false);
+ }
+ OF_prop_free(bootargs);
+ device_printf(dev, "MAC address found in bootargs %6D.\n",
+ ue->ue_eaddr, ":");
+ return (true);
+}
+#endif
+
+/**
+ * smsc_attach_post - Called after the driver attached to the USB interface
+ * @ue: the USB ethernet device
+ *
+ * This is where the chip is intialised for the first time. This is different
+ * from the smsc_init() function in that that one is designed to setup the
+ * H/W to match the UE settings and can be called after a reset.
+ *
+ *
+ */
+static void
+smsc_attach_post(struct usb_ether *ue)
+{
+ struct smsc_softc *sc = uether_getsc(ue);
+ struct ether_addr eaddr;
+ uint32_t mac_h, mac_l;
+ int err;
+ int i;
+
+ smsc_dbg_printf(sc, "smsc_attach_post\n");
+
+ /* Setup some of the basics */
+ sc->sc_phyno = 1;
+
+ /* Attempt to get the mac address, if an EEPROM is not attached this
+ * will just return FF:FF:FF:FF:FF:FF, so in such cases we invent a MAC
+ * address based on urandom.
+ */
+ memset(sc->sc_ue.ue_eaddr, 0xff, ETHER_ADDR_LEN);
+
+ /* Check if there is already a MAC address in the register */
+ if ((smsc_read_reg(sc, SMSC_MAC_ADDRL, &mac_l) == 0) &&
+ (smsc_read_reg(sc, SMSC_MAC_ADDRH, &mac_h) == 0)) {
+ sc->sc_ue.ue_eaddr[5] = (uint8_t)((mac_h >> 8) & 0xff);
+ sc->sc_ue.ue_eaddr[4] = (uint8_t)((mac_h) & 0xff);
+ sc->sc_ue.ue_eaddr[3] = (uint8_t)((mac_l >> 24) & 0xff);
+ sc->sc_ue.ue_eaddr[2] = (uint8_t)((mac_l >> 16) & 0xff);
+ sc->sc_ue.ue_eaddr[1] = (uint8_t)((mac_l >> 8) & 0xff);
+ sc->sc_ue.ue_eaddr[0] = (uint8_t)((mac_l) & 0xff);
+ }
+
+ /* MAC address is not set so try to read from EEPROM, if that fails generate
+ * a random MAC address.
+ */
+ if (!ETHER_IS_VALID(sc->sc_ue.ue_eaddr)) {
+ err = smsc_eeprom_read(sc, 0x01, sc->sc_ue.ue_eaddr, ETHER_ADDR_LEN);
+#ifdef FDT
+ if ((err != 0) || (!ETHER_IS_VALID(sc->sc_ue.ue_eaddr)))
+ err = usb_fdt_get_mac_addr(sc->sc_ue.ue_dev, &sc->sc_ue);
+ if ((err != 0) || (!ETHER_IS_VALID(sc->sc_ue.ue_eaddr)))
+ err = smsc_bootargs_get_mac_addr(sc->sc_ue.ue_dev,
+ &sc->sc_ue) ? (0) : (1);
+#endif
+ if ((err != 0) || (!ETHER_IS_VALID(sc->sc_ue.ue_eaddr))) {
+ smsc_dbg_printf(sc, "No MAC address found."
+ " Using ether_gen_addr().\n");
+ ether_gen_addr_byname(device_get_nameunit(ue->ue_dev),
+ &eaddr);
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ sc->sc_ue.ue_eaddr[i] = eaddr.octet[i];
+ }
+ }
+
+ /* Initialise the chip for the first time */
+ smsc_chip_init(sc);
+}
+
+/**
+ * smsc_attach_post_sub - Called after the driver attached to the USB interface
+ * @ue: the USB ethernet device
+ *
+ * Most of this is boilerplate code and copied from the base USB ethernet
+ * driver. It has been overridden so that we can indicate to the system that
+ * the chip supports H/W checksumming.
+ *
+ * RETURNS:
+ * Returns 0 on success or a negative error code.
+ */
+static int
+smsc_attach_post_sub(struct usb_ether *ue)
+{
+ struct smsc_softc *sc;
+ if_t ifp;
+ int error;
+
+ sc = uether_getsc(ue);
+ ifp = ue->ue_ifp;
+ if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
+ if_setstartfn(ifp, uether_start);
+ if_setioctlfn(ifp, smsc_ioctl);
+ if_setinitfn(ifp, uether_init);
+ if_setsendqlen(ifp, ifqmaxlen);
+ if_setsendqready(ifp);
+
+ /* The chip supports TCP/UDP checksum offloading on TX and RX paths, however
+ * currently only RX checksum is supported in the driver (see top of file).
+ */
+ if_setcapabilitiesbit(ifp, IFCAP_RXCSUM | IFCAP_VLAN_MTU, 0);
+ if_sethwassist(ifp, 0);
+
+ /* TX checksuming is disabled (for now?)
+ if_setcapabilitiesbit(ifp, IFCAP_TXCSUM, 0);
+ if_setcapenablebit(ifp, IFCAP_TXCSUM, 0);
+ if_sethwassist(ifp, CSUM_TCP | CSUM_UDP);
+ */
+
+ if_setcapenable(ifp, if_getcapabilities(ifp));
+
+ bus_topo_lock();
+ error = mii_attach(ue->ue_dev, &ue->ue_miibus, ifp,
+ uether_ifmedia_upd, ue->ue_methods->ue_mii_sts,
+ BMSR_DEFCAPMASK, sc->sc_phyno, MII_OFFSET_ANY, 0);
+ bus_topo_unlock();
+
+ return (error);
+}
+
+/**
+ * smsc_probe - Probe the interface.
+ * @dev: smsc device handle
+ *
+ * Checks if the device is a match for this driver.
+ *
+ * RETURNS:
+ * Returns 0 on success or an error code on failure.
+ */
+static int
+smsc_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != SMSC_CONFIG_INDEX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != SMSC_IFACE_IDX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(smsc_devs, sizeof(smsc_devs), uaa));
+}
+
+/**
+ * smsc_attach - Attach the interface.
+ * @dev: smsc device handle
+ *
+ * Allocate softc structures, do ifmedia setup and ethernet/BPF attach.
+ *
+ * RETURNS:
+ * Returns 0 on success or a negative error code.
+ */
+static int
+smsc_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct smsc_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+ uint8_t iface_index;
+ int err;
+
+ sc->sc_flags = USB_GET_DRIVER_INFO(uaa);
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ /* Setup the endpoints for the SMSC LAN95xx device(s) */
+ iface_index = SMSC_IFACE_IDX;
+ err = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer,
+ smsc_config, SMSC_N_TRANSFER, sc, &sc->sc_mtx);
+ if (err) {
+ device_printf(dev, "error: allocating USB transfers failed\n");
+ goto detach;
+ }
+
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &smsc_ue_methods;
+
+ err = uether_ifattach(ue);
+ if (err) {
+ device_printf(dev, "error: could not attach interface\n");
+ goto detach;
+ }
+ return (0); /* success */
+
+detach:
+ smsc_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+/**
+ * smsc_detach - Detach the interface.
+ * @dev: smsc device handle
+ *
+ * RETURNS:
+ * Returns 0.
+ */
+static int
+smsc_detach(device_t dev)
+{
+ struct smsc_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ usbd_transfer_unsetup(sc->sc_xfer, SMSC_N_TRANSFER);
+ uether_ifdetach(ue);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static device_method_t smsc_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, smsc_probe),
+ DEVMETHOD(device_attach, smsc_attach),
+ DEVMETHOD(device_detach, smsc_detach),
+
+ /* bus interface */
+ DEVMETHOD(bus_print_child, bus_generic_print_child),
+ DEVMETHOD(bus_driver_added, bus_generic_driver_added),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, smsc_miibus_readreg),
+ DEVMETHOD(miibus_writereg, smsc_miibus_writereg),
+ DEVMETHOD(miibus_statchg, smsc_miibus_statchg),
+
+ DEVMETHOD_END
+};
+
+static driver_t smsc_driver = {
+ .name = "smsc",
+ .methods = smsc_methods,
+ .size = sizeof(struct smsc_softc),
+};
+
+DRIVER_MODULE(smsc, uhub, smsc_driver, NULL, NULL);
+DRIVER_MODULE(miibus, smsc, miibus_driver, 0, 0);
+MODULE_DEPEND(smsc, uether, 1, 1, 1);
+MODULE_DEPEND(smsc, usb, 1, 1, 1);
+MODULE_DEPEND(smsc, ether, 1, 1, 1);
+MODULE_DEPEND(smsc, miibus, 1, 1, 1);
+MODULE_VERSION(smsc, 1);
+USB_PNP_HOST_INFO(smsc_devs);
diff --git a/sys/dev/usb/net/if_smscreg.h b/sys/dev/usb/net/if_smscreg.h
new file mode 100644
index 000000000000..1d27b661b16e
--- /dev/null
+++ b/sys/dev/usb/net/if_smscreg.h
@@ -0,0 +1,277 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2012
+ * Ben Gray <bgray@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 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.
+ */
+#ifndef _IF_SMSCREG_H_
+#define _IF_SMSCREG_H_
+
+/*
+ * Definitions for the SMSC LAN9514 and LAN9514 USB to ethernet controllers.
+ *
+ * This information was gleaned from the SMSC driver in the linux kernel, where
+ * it is Copyrighted (C) 2007-2008 SMSC.
+ *
+ */
+
+/**
+ * TRANSMIT FRAMES
+ * ---------------
+ * Tx frames are prefixed with an 8-byte header which describes the frame
+ *
+ * 4 bytes 4 bytes variable
+ * +------------+------------+--- . . . . . . . . . . . . ---+
+ * | TX_CTRL_0 | TX_CTRL_1 | Ethernet frame data |
+ * +------------+------------+--- . . . . . . . . . . . . ---+
+ *
+ * Where the headers have the following fields:
+ *
+ * TX_CTRL_0 <20:16> Data offset
+ * TX_CTRL_0 <13> First segment of frame indicator
+ * TX_CTRL_0 <12> Last segment of frame indicator
+ * TX_CTRL_0 <10:0> Buffer size (?)
+ *
+ * TX_CTRL_1 <14> Perform H/W checksuming on IP packets
+ * TX_CTRL_1 <13> Disable automatic ethernet CRC generation
+ * TX_CTRL_1 <12> Disable padding (?)
+ * TX_CTRL_1 <10:0> Packet byte length
+ *
+ */
+#define SMSC_TX_CTRL_0_OFFSET(x) (((x) & 0x1FUL) << 16)
+#define SMSC_TX_CTRL_0_FIRST_SEG (0x1UL << 13)
+#define SMSC_TX_CTRL_0_LAST_SEG (0x1UL << 12)
+#define SMSC_TX_CTRL_0_BUF_SIZE(x) ((x) & 0x000007FFUL)
+
+#define SMSC_TX_CTRL_1_CSUM_ENABLE (0x1UL << 14)
+#define SMSC_TX_CTRL_1_CRC_DISABLE (0x1UL << 13)
+#define SMSC_TX_CTRL_1_PADDING_DISABLE (0x1UL << 12)
+#define SMSC_TX_CTRL_1_PKT_LENGTH(x) ((x) & 0x000007FFUL)
+
+/**
+ * RECEIVE FRAMES
+ * --------------
+ * Rx frames are prefixed with an 4-byte status header which describes any
+ * errors with the frame as well as things like the length
+ *
+ * 4 bytes variable
+ * +------------+--- . . . . . . . . . . . . ---+
+ * | RX_STAT | Ethernet frame data |
+ * +------------+--- . . . . . . . . . . . . ---+
+ *
+ * Where the status header has the following fields:
+ *
+ * RX_STAT <30> Filter Fail
+ * RX_STAT <29:16> Frame Length
+ * RX_STAT <15> Error Summary
+ * RX_STAT <13> Broadcast Frame
+ * RX_STAT <12> Length Error
+ * RX_STAT <11> Runt Frame
+ * RX_STAT <10> Multicast Frame
+ * RX_STAT <7> Frame too long
+ * RX_STAT <6> Collision Seen
+ * RX_STAT <5> Frame Type
+ * RX_STAT <4> Receive Watchdog
+ * RX_STAT <3> Mii Error
+ * RX_STAT <2> Dribbling
+ * RX_STAT <1> CRC Error
+ *
+ */
+#define SMSC_RX_STAT_FILTER_FAIL (0x1UL << 30)
+#define SMSC_RX_STAT_FRM_LENGTH(x) (((x) >> 16) & 0x3FFFUL)
+#define SMSC_RX_STAT_ERROR (0x1UL << 15)
+#define SMSC_RX_STAT_BROADCAST (0x1UL << 13)
+#define SMSC_RX_STAT_LENGTH_ERROR (0x1UL << 12)
+#define SMSC_RX_STAT_RUNT (0x1UL << 11)
+#define SMSC_RX_STAT_MULTICAST (0x1UL << 10)
+#define SMSC_RX_STAT_FRM_TO_LONG (0x1UL << 7)
+#define SMSC_RX_STAT_COLLISION (0x1UL << 6)
+#define SMSC_RX_STAT_FRM_TYPE (0x1UL << 5)
+#define SMSC_RX_STAT_WATCHDOG (0x1UL << 4)
+#define SMSC_RX_STAT_MII_ERROR (0x1UL << 3)
+#define SMSC_RX_STAT_DRIBBLING (0x1UL << 2)
+#define SMSC_RX_STAT_CRC_ERROR (0x1UL << 1)
+
+/**
+ * REGISTERS
+ *
+ */
+#define SMSC_ID_REV 0x000
+#define SMSC_INTR_STATUS 0x008
+#define SMSC_RX_CFG 0x00C
+#define SMSC_TX_CFG 0x010
+#define SMSC_HW_CFG 0x014
+#define SMSC_PM_CTRL 0x020
+#define SMSC_LED_GPIO_CFG 0x024
+#define SMSC_GPIO_CFG 0x028
+#define SMSC_AFC_CFG 0x02C
+#define SMSC_EEPROM_CMD 0x030
+#define SMSC_EEPROM_DATA 0x034
+#define SMSC_BURST_CAP 0x038
+#define SMSC_GPIO_WAKE 0x064
+#define SMSC_INTR_CFG 0x068
+#define SMSC_BULK_IN_DLY 0x06C
+#define SMSC_MAC_CSR 0x100
+#define SMSC_MAC_ADDRH 0x104
+#define SMSC_MAC_ADDRL 0x108
+#define SMSC_HASHH 0x10C
+#define SMSC_HASHL 0x110
+#define SMSC_MII_ADDR 0x114
+#define SMSC_MII_DATA 0x118
+#define SMSC_FLOW 0x11C
+#define SMSC_VLAN1 0x120
+#define SMSC_VLAN2 0x124
+#define SMSC_WUFF 0x128
+#define SMSC_WUCSR 0x12C
+#define SMSC_COE_CTRL 0x130
+
+/* ID / Revision register */
+#define SMSC_ID_REV_CHIP_ID_MASK 0xFFFF0000UL
+#define SMSC_ID_REV_CHIP_REV_MASK 0x0000FFFFUL
+
+#define SMSC_RX_FIFO_FLUSH (0x1UL << 0)
+
+#define SMSC_TX_CFG_ON (0x1UL << 2)
+#define SMSC_TX_CFG_STOP (0x1UL << 1)
+#define SMSC_TX_CFG_FIFO_FLUSH (0x1UL << 0)
+
+#define SMSC_HW_CFG_BIR (0x1UL << 12)
+#define SMSC_HW_CFG_LEDB (0x1UL << 11)
+#define SMSC_HW_CFG_RXDOFF (0x3UL << 9) /* RX pkt alignment */
+#define SMSC_HW_CFG_DRP (0x1UL << 6)
+#define SMSC_HW_CFG_MEF (0x1UL << 5)
+#define SMSC_HW_CFG_LRST (0x1UL << 3) /* Lite reset */
+#define SMSC_HW_CFG_PSEL (0x1UL << 2)
+#define SMSC_HW_CFG_BCE (0x1UL << 1)
+#define SMSC_HW_CFG_SRST (0x1UL << 0)
+
+#define SMSC_PM_CTRL_PHY_RST (0x1UL << 4) /* PHY reset */
+
+#define SMSC_LED_GPIO_CFG_SPD_LED (0x1UL << 24)
+#define SMSC_LED_GPIO_CFG_LNK_LED (0x1UL << 20)
+#define SMSC_LED_GPIO_CFG_FDX_LED (0x1UL << 16)
+
+/* Hi watermark = 15.5Kb (~10 mtu pkts) */
+/* low watermark = 3k (~2 mtu pkts) */
+/* backpressure duration = ~ 350us */
+/* Apply FC on any frame. */
+#define AFC_CFG_DEFAULT (0x00F830A1)
+
+#define SMSC_EEPROM_CMD_BUSY (0x1UL << 31)
+#define SMSC_EEPROM_CMD_MASK (0x7UL << 28)
+#define SMSC_EEPROM_CMD_READ (0x0UL << 28)
+#define SMSC_EEPROM_CMD_WRITE (0x3UL << 28)
+#define SMSC_EEPROM_CMD_ERASE (0x5UL << 28)
+#define SMSC_EEPROM_CMD_RELOAD (0x7UL << 28)
+#define SMSC_EEPROM_CMD_TIMEOUT (0x1UL << 10)
+#define SMSC_EEPROM_CMD_ADDR_MASK 0x000001FFUL
+
+/* MAC Control and Status Register */
+#define SMSC_MAC_CSR_RCVOWN (0x1UL << 23) /* Half duplex */
+#define SMSC_MAC_CSR_LOOPBK (0x1UL << 21) /* Loopback */
+#define SMSC_MAC_CSR_FDPX (0x1UL << 20) /* Full duplex */
+#define SMSC_MAC_CSR_MCPAS (0x1UL << 19) /* Multicast mode */
+#define SMSC_MAC_CSR_PRMS (0x1UL << 18) /* Promiscuous mode */
+#define SMSC_MAC_CSR_INVFILT (0x1UL << 17) /* Inverse filtering */
+#define SMSC_MAC_CSR_PASSBAD (0x1UL << 16) /* Pass on bad frames */
+#define SMSC_MAC_CSR_HPFILT (0x1UL << 13) /* Hash filtering */
+#define SMSC_MAC_CSR_BCAST (0x1UL << 11) /* Broadcast */
+#define SMSC_MAC_CSR_TXEN (0x1UL << 3) /* TX enable */
+#define SMSC_MAC_CSR_RXEN (0x1UL << 2) /* RX enable */
+
+/* Interrupt control register */
+#define SMSC_INTR_NTEP (0x1UL << 31)
+#define SMSC_INTR_MACRTO (0x1UL << 19)
+#define SMSC_INTR_TX_STOP (0x1UL << 17)
+#define SMSC_INTR_RX_STOP (0x1UL << 16)
+#define SMSC_INTR_PHY_INT (0x1UL << 15)
+#define SMSC_INTR_TXE (0x1UL << 14)
+#define SMSC_INTR_TDFU (0x1UL << 13)
+#define SMSC_INTR_TDFO (0x1UL << 12)
+#define SMSC_INTR_RXDF (0x1UL << 11)
+#define SMSC_INTR_GPIOS 0x000007FFUL
+
+/* Phy MII interface register */
+#define SMSC_MII_WRITE (0x1UL << 1)
+#define SMSC_MII_READ (0x0UL << 1)
+#define SMSC_MII_BUSY (0x1UL << 0)
+
+/* H/W checksum register */
+#define SMSC_COE_CTRL_TX_EN (0x1UL << 16) /* Tx H/W csum enable */
+#define SMSC_COE_CTRL_RX_MODE (0x1UL << 1)
+#define SMSC_COE_CTRL_RX_EN (0x1UL << 0) /* Rx H/W csum enable */
+
+/* Registers on the phy, accessed via MII/MDIO */
+#define SMSC_PHY_INTR_STAT (29)
+#define SMSC_PHY_INTR_MASK (30)
+
+#define SMSC_PHY_INTR_ENERGY_ON (0x1U << 7)
+#define SMSC_PHY_INTR_ANEG_COMP (0x1U << 6)
+#define SMSC_PHY_INTR_REMOTE_FAULT (0x1U << 5)
+#define SMSC_PHY_INTR_LINK_DOWN (0x1U << 4)
+
+/* USB Vendor Requests */
+#define SMSC_UR_WRITE_REG 0xA0
+#define SMSC_UR_READ_REG 0xA1
+#define SMSC_UR_GET_STATS 0xA2
+
+#define SMSC_CONFIG_INDEX 0 /* config number 1 */
+#define SMSC_IFACE_IDX 0
+
+/*
+ * USB endpoints.
+ */
+enum {
+ SMSC_BULK_DT_RD,
+ SMSC_BULK_DT_WR,
+ /* the LAN9514 device does support interrupt endpoints, however I couldn't
+ * get then to work reliably and since they are unneeded (poll the mii
+ * status) they are unused.
+ * SMSC_INTR_DT_WR,
+ * SMSC_INTR_DT_RD,
+ */
+ SMSC_N_TRANSFER,
+};
+
+struct smsc_softc {
+ struct usb_ether sc_ue;
+ struct mtx sc_mtx;
+ struct usb_xfer *sc_xfer[SMSC_N_TRANSFER];
+ int sc_phyno;
+
+ /* The following stores the settings in the mac control (MAC_CSR) register */
+ uint32_t sc_mac_csr;
+ uint32_t sc_rev_id;
+
+ uint32_t sc_flags;
+#define SMSC_FLAG_LINK 0x0001
+#define SMSC_FLAG_LAN9514 0x1000 /* LAN9514 */
+};
+
+#define SMSC_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define SMSC_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define SMSC_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t)
+
+#endif /* _IF_SMSCREG_H_ */
diff --git a/sys/dev/usb/net/if_udav.c b/sys/dev/usb/net/if_udav.c
new file mode 100644
index 000000000000..1554f0a4cd57
--- /dev/null
+++ b/sys/dev/usb/net/if_udav.c
@@ -0,0 +1,883 @@
+/* $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 $ */
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * 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://ptm2.cc.utu.fi/ftp/network/cards/DM9601/From_NET/DM9601-DS-P01-930914.pdf
+ */
+
+/*
+ * TODO:
+ * Interrupt Endpoint support
+ * External PHYs
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_media.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#include "miibus_if.h"
+
+#define USB_DEBUG_VAR udav_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/net/usb_ethernet.h>
+#include <dev/usb/net/if_udavreg.h>
+
+/* prototypes */
+
+static device_probe_t udav_probe;
+static device_attach_t udav_attach;
+static device_detach_t udav_detach;
+
+static usb_callback_t udav_bulk_write_callback;
+static usb_callback_t udav_bulk_read_callback;
+static usb_callback_t udav_intr_callback;
+
+static uether_fn_t udav_attach_post;
+static uether_fn_t udav_init;
+static uether_fn_t udav_stop;
+static uether_fn_t udav_start;
+static uether_fn_t udav_tick;
+static uether_fn_t udav_setmulti;
+static uether_fn_t udav_setpromisc;
+
+static int udav_csr_read(struct udav_softc *, uint16_t, void *, int);
+static int udav_csr_write(struct udav_softc *, uint16_t, void *, int);
+static uint8_t udav_csr_read1(struct udav_softc *, uint16_t);
+static int udav_csr_write1(struct udav_softc *, uint16_t, uint8_t);
+static void udav_reset(struct udav_softc *);
+static int udav_ifmedia_upd(if_t);
+static void udav_ifmedia_status(if_t, struct ifmediareq *);
+
+static miibus_readreg_t udav_miibus_readreg;
+static miibus_writereg_t udav_miibus_writereg;
+static miibus_statchg_t udav_miibus_statchg;
+
+static const struct usb_config udav_config[UDAV_N_TRANSFER] = {
+ [UDAV_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = (MCLBYTES + 2),
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = udav_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ },
+
+ [UDAV_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = (MCLBYTES + 3),
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = udav_bulk_read_callback,
+ .timeout = 0, /* no timeout */
+ },
+
+ [UDAV_INTR_DT_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = udav_intr_callback,
+ },
+};
+
+static device_method_t udav_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, udav_probe),
+ DEVMETHOD(device_attach, udav_attach),
+ DEVMETHOD(device_detach, udav_detach),
+
+ /* MII interface */
+ DEVMETHOD(miibus_readreg, udav_miibus_readreg),
+ DEVMETHOD(miibus_writereg, udav_miibus_writereg),
+ DEVMETHOD(miibus_statchg, udav_miibus_statchg),
+
+ DEVMETHOD_END
+};
+
+static driver_t udav_driver = {
+ .name = "udav",
+ .methods = udav_methods,
+ .size = sizeof(struct udav_softc),
+};
+
+static const STRUCT_USB_HOST_ID udav_devs[] = {
+ /* ShanTou DM9601 USB NIC */
+ {USB_VPI(USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_DM9601, 0)},
+ /* ShanTou ST268 USB NIC */
+ {USB_VPI(USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_ST268, 0)},
+ /* Corega USB-TXC */
+ {USB_VPI(USB_VENDOR_COREGA, USB_PRODUCT_COREGA_FETHER_USB_TXC, 0)},
+ /* ShanTou AMD8515 USB NIC */
+ {USB_VPI(USB_VENDOR_SHANTOU, USB_PRODUCT_SHANTOU_ADM8515, 0)},
+ /* Kontron AG USB Ethernet */
+ {USB_VPI(USB_VENDOR_KONTRON, USB_PRODUCT_KONTRON_DM9601, 0)},
+ {USB_VPI(USB_VENDOR_KONTRON, USB_PRODUCT_KONTRON_JP1082,
+ UDAV_FLAG_NO_PHY)},
+};
+
+DRIVER_MODULE(udav, uhub, udav_driver, NULL, NULL);
+DRIVER_MODULE(miibus, udav, miibus_driver, 0, 0);
+MODULE_DEPEND(udav, uether, 1, 1, 1);
+MODULE_DEPEND(udav, usb, 1, 1, 1);
+MODULE_DEPEND(udav, ether, 1, 1, 1);
+MODULE_DEPEND(udav, miibus, 1, 1, 1);
+MODULE_VERSION(udav, 1);
+USB_PNP_HOST_INFO(udav_devs);
+
+static const struct usb_ether_methods udav_ue_methods = {
+ .ue_attach_post = udav_attach_post,
+ .ue_start = udav_start,
+ .ue_init = udav_init,
+ .ue_stop = udav_stop,
+ .ue_tick = udav_tick,
+ .ue_setmulti = udav_setmulti,
+ .ue_setpromisc = udav_setpromisc,
+ .ue_mii_upd = udav_ifmedia_upd,
+ .ue_mii_sts = udav_ifmedia_status,
+};
+
+static const struct usb_ether_methods udav_ue_methods_nophy = {
+ .ue_attach_post = udav_attach_post,
+ .ue_start = udav_start,
+ .ue_init = udav_init,
+ .ue_stop = udav_stop,
+ .ue_setmulti = udav_setmulti,
+ .ue_setpromisc = udav_setpromisc,
+};
+
+#ifdef USB_DEBUG
+static int udav_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, udav, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB udav");
+SYSCTL_INT(_hw_usb_udav, OID_AUTO, debug, CTLFLAG_RWTUN, &udav_debug, 0,
+ "Debug level");
+#endif
+
+#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 void
+udav_attach_post(struct usb_ether *ue)
+{
+ struct udav_softc *sc = uether_getsc(ue);
+
+ /* reset the adapter */
+ udav_reset(sc);
+
+ /* Get Ethernet Address */
+ udav_csr_read(sc, UDAV_PAR, ue->ue_eaddr, ETHER_ADDR_LEN);
+}
+
+static int
+udav_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != UDAV_CONFIG_INDEX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != UDAV_IFACE_INDEX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(udav_devs, sizeof(udav_devs), uaa));
+}
+
+static int
+udav_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct udav_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+ uint8_t iface_index;
+ int error;
+
+ sc->sc_flags = USB_GET_DRIVER_INFO(uaa);
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ iface_index = UDAV_IFACE_INDEX;
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, udav_config, UDAV_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "allocating USB transfers failed\n");
+ goto detach;
+ }
+
+ /*
+ * The JP1082 has an unusable PHY and provides no link information.
+ */
+ if (sc->sc_flags & UDAV_FLAG_NO_PHY) {
+ ue->ue_methods = &udav_ue_methods_nophy;
+ sc->sc_flags |= UDAV_FLAG_LINK;
+ } else {
+ ue->ue_methods = &udav_ue_methods;
+ }
+
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+
+ error = uether_ifattach(ue);
+ if (error) {
+ device_printf(dev, "could not attach interface\n");
+ goto detach;
+ }
+
+ return (0); /* success */
+
+detach:
+ udav_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+udav_detach(device_t dev)
+{
+ struct udav_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ usbd_transfer_unsetup(sc->sc_xfer, UDAV_N_TRANSFER);
+ uether_ifdetach(ue);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+#if 0
+static int
+udav_mem_read(struct udav_softc *sc, uint16_t offset, void *buf,
+ int len)
+{
+ struct usb_device_request req;
+
+ 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);
+
+ return (uether_do_request(&sc->sc_ue, &req, buf, 1000));
+}
+
+static int
+udav_mem_write(struct udav_softc *sc, uint16_t offset, void *buf,
+ int len)
+{
+ struct usb_device_request req;
+
+ 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);
+
+ return (uether_do_request(&sc->sc_ue, &req, buf, 1000));
+}
+
+static int
+udav_mem_write1(struct udav_softc *sc, uint16_t offset,
+ uint8_t ch)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = UDAV_REQ_MEM_WRITE1;
+ USETW(req.wValue, ch);
+ USETW(req.wIndex, offset);
+ USETW(req.wLength, 0x0000);
+
+ return (uether_do_request(&sc->sc_ue, &req, NULL, 1000));
+}
+#endif
+
+static int
+udav_csr_read(struct udav_softc *sc, uint16_t offset, void *buf, int len)
+{
+ struct usb_device_request req;
+
+ 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);
+
+ return (uether_do_request(&sc->sc_ue, &req, buf, 1000));
+}
+
+static int
+udav_csr_write(struct udav_softc *sc, uint16_t offset, void *buf, int len)
+{
+ struct usb_device_request req;
+
+ 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);
+
+ return (uether_do_request(&sc->sc_ue, &req, buf, 1000));
+}
+
+static uint8_t
+udav_csr_read1(struct udav_softc *sc, uint16_t offset)
+{
+ uint8_t val;
+
+ udav_csr_read(sc, offset, &val, 1);
+ return (val);
+}
+
+static int
+udav_csr_write1(struct udav_softc *sc, uint16_t offset,
+ uint8_t ch)
+{
+ struct usb_device_request req;
+
+ 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);
+
+ return (uether_do_request(&sc->sc_ue, &req, NULL, 1000));
+}
+
+static void
+udav_init(struct usb_ether *ue)
+{
+ struct udav_softc *sc = ue->ue_sc;
+ if_t ifp = uether_getifp(&sc->sc_ue);
+
+ UDAV_LOCK_ASSERT(sc, MA_OWNED);
+
+ /*
+ * Cancel pending I/O
+ */
+ udav_stop(ue);
+
+ /* set MAC address */
+ udav_csr_write(sc, UDAV_PAR, if_getlladdr(ifp), 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);
+
+ /* load multicast filter and update promiscious mode bit */
+ udav_setpromisc(ue);
+
+ /* 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);
+
+ usbd_xfer_set_stall(sc->sc_xfer[UDAV_BULK_DT_WR]);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+ udav_start(ue);
+}
+
+static void
+udav_reset(struct udav_softc *sc)
+{
+ int i;
+
+ /* 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;
+ if (uether_pause(&sc->sc_ue, hz / 100))
+ break;
+ }
+
+ uether_pause(&sc->sc_ue, hz / 100);
+}
+
+static u_int
+udav_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
+{
+ uint8_t *hashtbl = arg;
+ int h;
+
+ h = ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 26;
+ hashtbl[h / 8] |= 1 << (h % 8);
+
+ return (1);
+}
+
+static void
+udav_setmulti(struct usb_ether *ue)
+{
+ struct udav_softc *sc = ue->ue_sc;
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ UDAV_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (if_getflags(ifp) & IFF_ALLMULTI || if_getflags(ifp) & IFF_PROMISC) {
+ UDAV_SETBIT(sc, UDAV_RCR, UDAV_RCR_ALL|UDAV_RCR_PRMSC);
+ return;
+ }
+
+ /* first, zot all the existing hash bits */
+ memset(hashtbl, 0x00, sizeof(hashtbl));
+ hashtbl[7] |= 0x80; /* broadcast address */
+ udav_csr_write(sc, UDAV_MAR, hashtbl, sizeof(hashtbl));
+
+ /* now program new ones */
+ if_foreach_llmaddr(ifp, udav_hash_maddr, hashtbl);
+
+ /* disable all multicast */
+ UDAV_CLRBIT(sc, UDAV_RCR, UDAV_RCR_ALL);
+
+ /* write hash value to the register */
+ udav_csr_write(sc, UDAV_MAR, hashtbl, sizeof(hashtbl));
+}
+
+static void
+udav_setpromisc(struct usb_ether *ue)
+{
+ struct udav_softc *sc = ue->ue_sc;
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ uint8_t rxmode;
+
+ rxmode = udav_csr_read1(sc, UDAV_RCR);
+ rxmode &= ~(UDAV_RCR_ALL | UDAV_RCR_PRMSC);
+
+ if (if_getflags(ifp) & IFF_PROMISC)
+ rxmode |= UDAV_RCR_ALL | UDAV_RCR_PRMSC;
+ else if (if_getflags(ifp) & IFF_ALLMULTI)
+ rxmode |= UDAV_RCR_ALL;
+
+ /* write new mode bits */
+ udav_csr_write1(sc, UDAV_RCR, rxmode);
+}
+
+static void
+udav_start(struct usb_ether *ue)
+{
+ struct udav_softc *sc = ue->ue_sc;
+
+ /*
+ * start the USB transfers, if not already started:
+ */
+ usbd_transfer_start(sc->sc_xfer[UDAV_INTR_DT_RD]);
+ usbd_transfer_start(sc->sc_xfer[UDAV_BULK_DT_RD]);
+ usbd_transfer_start(sc->sc_xfer[UDAV_BULK_DT_WR]);
+}
+
+static void
+udav_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct udav_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ int extra_len;
+ int temp_len;
+ uint8_t buf[2];
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "transfer complete\n");
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ if ((sc->sc_flags & UDAV_FLAG_LINK) == 0) {
+ /*
+ * don't send anything if there is no link !
+ */
+ return;
+ }
+ m = if_dequeue(ifp);
+
+ if (m == NULL)
+ return;
+ if (m->m_pkthdr.len > MCLBYTES)
+ m->m_pkthdr.len = MCLBYTES;
+ if (m->m_pkthdr.len < UDAV_MIN_FRAME_LEN) {
+ extra_len = UDAV_MIN_FRAME_LEN - m->m_pkthdr.len;
+ } else {
+ extra_len = 0;
+ }
+
+ temp_len = (m->m_pkthdr.len + extra_len);
+
+ /*
+ * the frame length is specified in the first 2 bytes of the
+ * buffer
+ */
+ buf[0] = (uint8_t)(temp_len);
+ buf[1] = (uint8_t)(temp_len >> 8);
+
+ temp_len += 2;
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, buf, 2);
+ usbd_m_copy_in(pc, 2, m, 0, m->m_pkthdr.len);
+
+ if (extra_len)
+ usbd_frame_zero(pc, temp_len - extra_len, extra_len);
+ /*
+ * if there's a BPF listener, bounce a copy
+ * of this frame to him:
+ */
+ BPF_MTAP(ifp, m);
+
+ m_freem(m);
+
+ usbd_xfer_set_frame_len(xfer, 0, temp_len);
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ DPRINTFN(11, "transfer error, %s\n",
+ usbd_errstr(error));
+
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+udav_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct udav_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_ether *ue = &sc->sc_ue;
+ if_t ifp = uether_getifp(ue);
+ struct usb_page_cache *pc;
+ struct udav_rxpkt stat;
+ int len;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ if (actlen < (int)(sizeof(stat) + ETHER_CRC_LEN)) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &stat, sizeof(stat));
+ actlen -= sizeof(stat);
+ len = min(actlen, le16toh(stat.pktlen));
+ len -= ETHER_CRC_LEN;
+
+ if (stat.rxstat & UDAV_RSR_LCS) {
+ if_inc_counter(ifp, IFCOUNTER_COLLISIONS, 1);
+ goto tr_setup;
+ }
+ if (stat.rxstat & UDAV_RSR_ERR) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ uether_rxbuf(ue, pc, sizeof(stat), len);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ uether_rxflush(ue);
+ return;
+
+ default: /* Error */
+ DPRINTF("bulk read error, %s\n",
+ usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+udav_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+udav_stop(struct usb_ether *ue)
+{
+ struct udav_softc *sc = ue->ue_sc;
+ if_t ifp = uether_getifp(&sc->sc_ue);
+
+ UDAV_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+ if (!(sc->sc_flags & UDAV_FLAG_NO_PHY))
+ sc->sc_flags &= ~UDAV_FLAG_LINK;
+
+ /*
+ * stop all the transfers, if not already stopped:
+ */
+ usbd_transfer_stop(sc->sc_xfer[UDAV_BULK_DT_WR]);
+ usbd_transfer_stop(sc->sc_xfer[UDAV_BULK_DT_RD]);
+ usbd_transfer_stop(sc->sc_xfer[UDAV_INTR_DT_RD]);
+
+ udav_reset(sc);
+}
+
+static int
+udav_ifmedia_upd(if_t ifp)
+{
+ struct udav_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = GET_MII(sc);
+ struct mii_softc *miisc;
+ int error;
+
+ UDAV_LOCK_ASSERT(sc, MA_OWNED);
+
+ sc->sc_flags &= ~UDAV_FLAG_LINK;
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list)
+ PHY_RESET(miisc);
+ error = mii_mediachg(mii);
+ return (error);
+}
+
+static void
+udav_ifmedia_status(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct udav_softc *sc = if_getsoftc(ifp);
+ struct mii_data *mii = GET_MII(sc);
+
+ UDAV_LOCK(sc);
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+ UDAV_UNLOCK(sc);
+}
+
+static void
+udav_tick(struct usb_ether *ue)
+{
+ struct udav_softc *sc = ue->ue_sc;
+ struct mii_data *mii = GET_MII(sc);
+
+ UDAV_LOCK_ASSERT(sc, MA_OWNED);
+
+ mii_tick(mii);
+ if ((sc->sc_flags & UDAV_FLAG_LINK) == 0
+ && mii->mii_media_status & IFM_ACTIVE &&
+ IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) {
+ sc->sc_flags |= UDAV_FLAG_LINK;
+ udav_start(ue);
+ }
+}
+
+static int
+udav_miibus_readreg(device_t dev, int phy, int reg)
+{
+ struct udav_softc *sc = device_get_softc(dev);
+ uint16_t data16;
+ uint8_t val[2];
+ int locked;
+
+ /* XXX: one PHY only for the internal PHY */
+ if (phy != 0)
+ return (0);
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ UDAV_LOCK(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 we 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);
+
+ data16 = (val[0] | (val[1] << 8));
+
+ DPRINTFN(11, "phy=%d reg=0x%04x => 0x%04x\n",
+ phy, reg, data16);
+
+ if (!locked)
+ UDAV_UNLOCK(sc);
+ return (data16);
+}
+
+static int
+udav_miibus_writereg(device_t dev, int phy, int reg, int data)
+{
+ struct udav_softc *sc = device_get_softc(dev);
+ uint8_t val[2];
+ int locked;
+
+ /* XXX: one PHY only for the internal PHY */
+ if (phy != 0)
+ return (0);
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ UDAV_LOCK(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 we wait? */
+
+ /* end write command */
+ UDAV_CLRBIT(sc, UDAV_EPCR, UDAV_EPCR_ERPRW);
+
+ if (!locked)
+ UDAV_UNLOCK(sc);
+ return (0);
+}
+
+static void
+udav_miibus_statchg(device_t dev)
+{
+ /* nothing to do */
+}
diff --git a/sys/dev/usb/net/if_udavreg.h b/sys/dev/usb/net/if_udavreg.h
new file mode 100644
index 000000000000..5e24dddf71fc
--- /dev/null
+++ b/sys/dev/usb/net/if_udavreg.h
@@ -0,0 +1,168 @@
+/* $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 $ */
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * 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_INDEX 0 /* config number 1 */
+
+#define UDAV_TX_TIMEOUT 1000
+#define UDAV_TIMEOUT 10000
+
+#define UDAV_TX_TIMEOUT 1000
+#define UDAV_TIMEOUT 10000
+
+/* 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 */
+
+#define GET_MII(sc) uether_getmii(&(sc)->sc_ue)
+
+struct udav_rxpkt {
+ uint8_t rxstat;
+ uint16_t pktlen;
+} __packed;
+
+enum {
+ UDAV_BULK_DT_WR,
+ UDAV_BULK_DT_RD,
+ UDAV_INTR_DT_RD,
+ UDAV_N_TRANSFER,
+};
+
+struct udav_softc {
+ struct usb_ether sc_ue;
+ struct mtx sc_mtx;
+ struct usb_xfer *sc_xfer[UDAV_N_TRANSFER];
+
+ int sc_flags;
+#define UDAV_FLAG_LINK 0x0001
+#define UDAV_FLAG_EXT_PHY 0x0040
+#define UDAV_FLAG_NO_PHY 0x0080
+};
+
+#define UDAV_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define UDAV_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define UDAV_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t)
diff --git a/sys/dev/usb/net/if_umb.c b/sys/dev/usb/net/if_umb.c
new file mode 100644
index 000000000000..5703bc03dd39
--- /dev/null
+++ b/sys/dev/usb/net/if_umb.c
@@ -0,0 +1,2932 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Original copyright (c) 2016 genua mbH (OpenBSD version)
+ *
+ * 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.
+ *
+ * Copyright (c) 2022 ADISTA SAS (re-write for FreeBSD)
+ *
+ * Re-write for FreeBSD by Pierre Pronchery <pierre@defora.net>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the copyright holder 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
+ *
+ * $NetBSD: if_umb.c,v 1.5 2018/09/20 09:45:16 khorben Exp $
+ * $OpenBSD: if_umb.c,v 1.18 2018/02/19 08:59:52 mpi Exp $
+ */
+
+/*
+ * Mobile Broadband Interface Model specification:
+ * http://www.usb.org/developers/docs/devclass_docs/MBIM10Errata1_073013.zip
+ * Compliance testing guide
+ * http://www.usb.org/developers/docs/devclass_docs/MBIM-Compliance-1.0.pdf
+ */
+
+#include <sys/param.h>
+#include <sys/module.h>
+#include <sys/endian.h>
+#include <sys/kernel.h>
+#include <sys/mbuf.h>
+#include <sys/priv.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/systm.h>
+#include <sys/syslog.h>
+#include <sys/kernel.h>
+#include <sys/queue.h>
+
+#include <sys/conf.h>
+#include <sys/bus.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/taskqueue.h>
+
+#include <machine/_inttypes.h>
+
+#include <net/bpf.h>
+#include <net/if.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+#include <net/if_var.h>
+#include <net/netisr.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_cdc.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usbdi_util.h>
+#include "usb_if.h"
+
+#include "mbim.h"
+#include "if_umbreg.h"
+
+MALLOC_DECLARE(M_MBIM_CID_CONNECT);
+MALLOC_DEFINE(M_MBIM_CID_CONNECT, "mbim_cid_connect",
+ "Connection parameters for MBIM");
+
+#ifdef UMB_DEBUG
+#define DPRINTF(x...) \
+ do { if (umb_debug) log(LOG_DEBUG, x); } while (0)
+
+#define DPRINTFN(n, x...) \
+ do { if (umb_debug >= (n)) log(LOG_DEBUG, x); } while (0)
+
+#define DDUMPN(n, b, l) \
+ do { \
+ if (umb_debug >= (n)) \
+ umb_dump((b), (l)); \
+ } while (0)
+
+const int umb_debug = 1;
+static char *umb_uuid2str(uint8_t [MBIM_UUID_LEN]);
+static void umb_dump(void *, int);
+
+#else
+#define DPRINTF(x...) do { } while (0)
+#define DPRINTFN(n, x...) do { } while (0)
+#define DDUMPN(n, b, l) do { } while (0)
+#endif
+
+#define DEVNAM(sc) device_get_nameunit((sc)->sc_dev)
+
+/*
+ * State change timeout
+ */
+#define UMB_STATE_CHANGE_TIMEOUT 30
+
+/*
+ * State change flags
+ */
+#define UMB_NS_DONT_DROP 0x0001 /* do not drop below current state */
+#define UMB_NS_DONT_RAISE 0x0002 /* do not raise below current state */
+
+/*
+ * Diagnostic macros
+ */
+const struct umb_valdescr umb_regstates[] = MBIM_REGSTATE_DESCRIPTIONS;
+const struct umb_valdescr umb_dataclasses[] = MBIM_DATACLASS_DESCRIPTIONS;
+const struct umb_valdescr umb_simstate[] = MBIM_SIMSTATE_DESCRIPTIONS;
+const struct umb_valdescr umb_messages[] = MBIM_MESSAGES_DESCRIPTIONS;
+const struct umb_valdescr umb_status[] = MBIM_STATUS_DESCRIPTIONS;
+const struct umb_valdescr umb_cids[] = MBIM_CID_DESCRIPTIONS;
+const struct umb_valdescr umb_pktstate[] = MBIM_PKTSRV_STATE_DESCRIPTIONS;
+const struct umb_valdescr umb_actstate[] = MBIM_ACTIVATION_STATE_DESCRIPTIONS;
+const struct umb_valdescr umb_error[] = MBIM_ERROR_DESCRIPTIONS;
+const struct umb_valdescr umb_pintype[] = MBIM_PINTYPE_DESCRIPTIONS;
+const struct umb_valdescr umb_istate[] = UMB_INTERNAL_STATE_DESCRIPTIONS;
+
+#define umb_regstate(c) umb_val2descr(umb_regstates, (c))
+#define umb_dataclass(c) umb_val2descr(umb_dataclasses, (c))
+#define umb_simstate(s) umb_val2descr(umb_simstate, (s))
+#define umb_request2str(m) umb_val2descr(umb_messages, (m))
+#define umb_status2str(s) umb_val2descr(umb_status, (s))
+#define umb_cid2str(c) umb_val2descr(umb_cids, (c))
+#define umb_packet_state(s) umb_val2descr(umb_pktstate, (s))
+#define umb_activation(s) umb_val2descr(umb_actstate, (s))
+#define umb_error2str(e) umb_val2descr(umb_error, (e))
+#define umb_pin_type(t) umb_val2descr(umb_pintype, (t))
+#define umb_istate(s) umb_val2descr(umb_istate, (s))
+
+static device_probe_t umb_probe;
+static device_attach_t umb_attach;
+static device_detach_t umb_detach;
+static device_suspend_t umb_suspend;
+static device_resume_t umb_resume;
+static void umb_attach_task(struct usb_proc_msg *);
+static usb_handle_request_t umb_handle_request;
+static int umb_deactivate(device_t);
+static void umb_ncm_setup(struct umb_softc *, struct usb_config *);
+static void umb_close_bulkpipes(struct umb_softc *);
+static int umb_ioctl(if_t , u_long, caddr_t);
+static void umb_init(void *);
+#ifdef DEV_NETMAP
+static void umb_input(if_t , struct mbuf *);
+#endif
+static int umb_output(if_t , struct mbuf *,
+ const struct sockaddr *, struct route *);
+static void umb_start(if_t );
+static void umb_start_task(struct usb_proc_msg *);
+#if 0
+static void umb_watchdog(if_t );
+#endif
+static void umb_statechg_timeout(void *);
+
+static int umb_mediachange(if_t );
+static void umb_mediastatus(if_t , struct ifmediareq *);
+
+static void umb_add_task(struct umb_softc *sc, usb_proc_callback_t,
+ struct usb_proc_msg *, struct usb_proc_msg *, int);
+static void umb_newstate(struct umb_softc *, enum umb_state, int);
+static void umb_state_task(struct usb_proc_msg *);
+static void umb_up(struct umb_softc *);
+static void umb_down(struct umb_softc *, int);
+
+static void umb_get_response_task(struct usb_proc_msg *);
+
+static void umb_decode_response(struct umb_softc *, void *, int);
+static void umb_handle_indicate_status_msg(struct umb_softc *, void *,
+ int);
+static void umb_handle_opendone_msg(struct umb_softc *, void *, int);
+static void umb_handle_closedone_msg(struct umb_softc *, void *, int);
+static int umb_decode_register_state(struct umb_softc *, void *, int);
+static int umb_decode_devices_caps(struct umb_softc *, void *, int);
+static int umb_decode_subscriber_status(struct umb_softc *, void *, int);
+static int umb_decode_radio_state(struct umb_softc *, void *, int);
+static int umb_decode_pin(struct umb_softc *, void *, int);
+static int umb_decode_packet_service(struct umb_softc *, void *, int);
+static int umb_decode_signal_state(struct umb_softc *, void *, int);
+static int umb_decode_connect_info(struct umb_softc *, void *, int);
+static int umb_decode_ip_configuration(struct umb_softc *, void *, int);
+static void umb_rx(struct umb_softc *);
+static usb_callback_t umb_rxeof;
+static void umb_rxflush(struct umb_softc *);
+static int umb_encap(struct umb_softc *, struct mbuf *, struct usb_xfer *);
+static usb_callback_t umb_txeof;
+static void umb_txflush(struct umb_softc *);
+static void umb_decap(struct umb_softc *, struct usb_xfer *, int);
+
+static usb_error_t umb_send_encap_command(struct umb_softc *, void *, int);
+static int umb_get_encap_response(struct umb_softc *, void *, int *);
+static void umb_ctrl_msg(struct umb_softc *, uint32_t, void *, int);
+
+static void umb_open(struct umb_softc *);
+static void umb_close(struct umb_softc *);
+
+static int umb_setpin(struct umb_softc *, int, int, void *, int, void *,
+ int);
+static void umb_setdataclass(struct umb_softc *);
+static void umb_radio(struct umb_softc *, int);
+static void umb_allocate_cid(struct umb_softc *);
+static void umb_send_fcc_auth(struct umb_softc *);
+static void umb_packet_service(struct umb_softc *, int);
+static void umb_connect(struct umb_softc *);
+static void umb_disconnect(struct umb_softc *);
+static void umb_send_connect(struct umb_softc *, int);
+
+static void umb_qry_ipconfig(struct umb_softc *);
+static void umb_cmd(struct umb_softc *, int, int, const void *, int);
+static void umb_cmd1(struct umb_softc *, int, int, const void *, int, uint8_t *);
+static void umb_command_done(struct umb_softc *, void *, int);
+static void umb_decode_cid(struct umb_softc *, uint32_t, void *, int);
+static void umb_decode_qmi(struct umb_softc *, uint8_t *, int);
+
+static usb_callback_t umb_intr;
+
+static char *umb_ntop(struct sockaddr *);
+
+static const int umb_xfer_tout = USB_DEFAULT_TIMEOUT;
+
+static uint8_t umb_uuid_basic_connect[] = MBIM_UUID_BASIC_CONNECT;
+static uint8_t umb_uuid_context_internet[] = MBIM_UUID_CONTEXT_INTERNET;
+static uint8_t umb_uuid_qmi_mbim[] = MBIM_UUID_QMI_MBIM;
+static uint32_t umb_session_id = 0;
+
+static const struct usb_config umb_config[UMB_N_TRANSFER] = {
+ [UMB_INTR_RX] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .if_index = 1,
+ .callback = umb_intr,
+ .bufsize = sizeof (struct usb_cdc_notification),
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1},
+ .usb_mode = USB_MODE_HOST,
+ },
+ [UMB_BULK_RX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .if_index = 0,
+ .callback = umb_rxeof,
+ .bufsize = 8 * 1024,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.ext_buffer = 1},
+ .usb_mode = USB_MODE_HOST,
+ },
+ [UMB_BULK_TX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .if_index = 0,
+ .callback = umb_txeof,
+ .bufsize = 8 * 1024,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1},
+ .timeout = umb_xfer_tout,
+ .usb_mode = USB_MODE_HOST,
+ },
+};
+
+static device_method_t umb_methods[] = {
+ /* USB interface */
+ DEVMETHOD(usb_handle_request, umb_handle_request),
+
+ /* Device interface */
+ DEVMETHOD(device_probe, umb_probe),
+ DEVMETHOD(device_attach, umb_attach),
+ DEVMETHOD(device_detach, umb_detach),
+ DEVMETHOD(device_suspend, umb_suspend),
+ DEVMETHOD(device_resume, umb_resume),
+
+ DEVMETHOD_END
+};
+
+static driver_t umb_driver = {
+ .name = "umb",
+ .methods = umb_methods,
+ .size = sizeof (struct umb_softc),
+};
+
+MALLOC_DEFINE(M_USB_UMB, "USB UMB", "USB MBIM driver");
+
+const int umb_delay = 4000;
+
+/*
+ * These devices require an "FCC Authentication" command.
+ */
+#ifndef USB_VENDOR_SIERRA
+# define USB_VENDOR_SIERRA 0x1199
+#endif
+#ifndef USB_PRODUCT_SIERRA_EM7455
+# define USB_PRODUCT_SIERRA_EM7455 0x9079
+#endif
+const struct usb_device_id umb_fccauth_devs[] = {
+ {
+ .match_flag_vendor = 1,
+ .match_flag_product = 1,
+ .idVendor = USB_VENDOR_SIERRA,
+ .idProduct = USB_PRODUCT_SIERRA_EM7455
+ }
+};
+
+static const uint8_t umb_qmi_alloc_cid[] = {
+ 0x01,
+ 0x0f, 0x00, /* len */
+ 0x00, /* QMUX flags */
+ 0x00, /* service "ctl" */
+ 0x00, /* CID */
+ 0x00, /* QMI flags */
+ 0x01, /* transaction */
+ 0x22, 0x00, /* msg "Allocate CID" */
+ 0x04, 0x00, /* TLV len */
+ 0x01, 0x01, 0x00, 0x02 /* TLV */
+};
+
+static const uint8_t umb_qmi_fcc_auth[] = {
+ 0x01,
+ 0x0c, 0x00, /* len */
+ 0x00, /* QMUX flags */
+ 0x02, /* service "dms" */
+#define UMB_QMI_CID_OFFS 5
+ 0x00, /* CID (filled in later) */
+ 0x00, /* QMI flags */
+ 0x01, 0x00, /* transaction */
+ 0x5f, 0x55, /* msg "Send FCC Authentication" */
+ 0x00, 0x00 /* TLV len */
+};
+
+static int
+umb_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ usb_interface_descriptor_t *id;
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if ((id = usbd_get_interface_descriptor(uaa->iface)) == NULL)
+ return (ENXIO);
+
+ /*
+ * If this function implements NCM, check if alternate setting
+ * 1 implements MBIM.
+ */
+ if (id->bInterfaceClass == UICLASS_CDC &&
+ id->bInterfaceSubClass ==
+ UISUBCLASS_NETWORK_CONTROL_MODEL) {
+ id = usbd_get_interface_descriptor(
+ usbd_get_iface(uaa->device,
+ uaa->info.bIfaceIndex + 1));
+ if (id == NULL || id->bAlternateSetting != 1)
+ return (ENXIO);
+ }
+
+#ifndef UISUBCLASS_MOBILE_BROADBAND_INTERFACE_MODEL
+# define UISUBCLASS_MOBILE_BROADBAND_INTERFACE_MODEL 14
+#endif
+ if (id->bInterfaceClass == UICLASS_CDC &&
+ id->bInterfaceSubClass ==
+ UISUBCLASS_MOBILE_BROADBAND_INTERFACE_MODEL &&
+ id->bInterfaceProtocol == 0)
+ return (BUS_PROBE_SPECIFIC);
+
+ return (ENXIO);
+}
+
+static int
+umb_attach(device_t dev)
+{
+ struct umb_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct usb_config config[UMB_N_TRANSFER];
+ int v;
+ const struct usb_cdc_union_descriptor *ud;
+ const struct mbim_descriptor *md;
+ int i;
+ usb_interface_descriptor_t *id;
+ struct usb_interface *iface;
+ int data_ifaceno = -1;
+ usb_error_t error;
+
+ sc->sc_dev = dev;
+ sc->sc_udev = uaa->device;
+
+ memcpy(config, umb_config, sizeof (config));
+
+ device_set_usb_desc(dev);
+
+ sc->sc_ctrl_ifaceno = uaa->info.bIfaceNum;
+
+ mtx_init(&sc->sc_mutex, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ /*
+ * Some MBIM hardware does not provide the mandatory CDC Union
+ * Descriptor, so we also look at matching Interface
+ * Association Descriptors to find out the MBIM Data Interface
+ * number.
+ */
+ sc->sc_ver_maj = sc->sc_ver_min = -1;
+ sc->sc_maxpktlen = MBIM_MAXSEGSZ_MINVAL;
+ id = usbd_get_interface_descriptor(uaa->iface);
+
+ ud = usbd_find_descriptor(sc->sc_udev, id, uaa->info.bIfaceIndex,
+ UDESC_CS_INTERFACE, 0xff, UDESCSUB_CDC_UNION, 0xff);
+ if (ud != NULL) {
+ data_ifaceno = ud->bSlaveInterface[0];
+ }
+
+ md = usbd_find_descriptor(sc->sc_udev, id, uaa->info.bIfaceIndex,
+ UDESC_CS_INTERFACE, 0xff, UDESCSUB_MBIM, 0xff);
+ if (md != NULL) {
+ v = UGETW(md->bcdMBIMVersion);
+ sc->sc_ver_maj = MBIM_VER_MAJOR(v);
+ sc->sc_ver_min = MBIM_VER_MINOR(v);
+ sc->sc_ctrl_len = UGETW(md->wMaxControlMessage);
+ /* Never trust a USB device! Could try to exploit us */
+ if (sc->sc_ctrl_len < MBIM_CTRLMSG_MINLEN ||
+ sc->sc_ctrl_len > MBIM_CTRLMSG_MAXLEN) {
+ DPRINTF("control message len %d out of "
+ "bounds [%d .. %d]\n",
+ sc->sc_ctrl_len, MBIM_CTRLMSG_MINLEN,
+ MBIM_CTRLMSG_MAXLEN);
+ /* continue anyway */
+ }
+ sc->sc_maxpktlen = UGETW(md->wMaxSegmentSize);
+ DPRINTFN(2, "ctrl_len=%d, maxpktlen=%d, cap=0x%x\n",
+ sc->sc_ctrl_len, sc->sc_maxpktlen,
+ md->bmNetworkCapabilities);
+ }
+ if (sc->sc_ver_maj < 0) {
+ device_printf(dev, "error: missing MBIM descriptor\n");
+ goto fail;
+ }
+
+ device_printf(dev, "version %d.%d\n", sc->sc_ver_maj,
+ sc->sc_ver_min);
+
+ if (usbd_lookup_id_by_uaa(umb_fccauth_devs, sizeof (umb_fccauth_devs),
+ uaa)) {
+ sc->sc_flags |= UMBFLG_FCC_AUTH_REQUIRED;
+ sc->sc_cid = -1;
+ }
+
+ for (i = 0; i < sc->sc_udev->ifaces_max; i++) {
+ iface = usbd_get_iface(sc->sc_udev, i);
+ id = usbd_get_interface_descriptor(iface);
+ if (id == NULL)
+ break;
+
+ if (id->bInterfaceNumber == data_ifaceno) {
+ sc->sc_data_iface = iface;
+ sc->sc_ifaces_index[0] = i;
+ sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex;
+ break;
+ }
+ }
+ if (sc->sc_data_iface == NULL) {
+ device_printf(dev, "error: no data interface found\n");
+ goto fail;
+ }
+
+ /*
+ * If this is a combined NCM/MBIM function, switch to
+ * alternate setting one to enable MBIM.
+ */
+ id = usbd_get_interface_descriptor(uaa->iface);
+ if (id != NULL && id->bInterfaceClass == UICLASS_CDC &&
+ id->bInterfaceSubClass == UISUBCLASS_NETWORK_CONTROL_MODEL) {
+ device_printf(sc->sc_dev, "combined NCM/MBIM\n");
+ error = usbd_req_set_alt_interface_no(sc->sc_udev,
+ NULL, uaa->info.bIfaceIndex, 1);
+ if (error != USB_ERR_NORMAL_COMPLETION) {
+ device_printf(dev, "error: Could not switch to"
+ " alternate setting for MBIM\n");
+ goto fail;
+ }
+ sc->sc_ifaces_index[1] = uaa->info.bIfaceIndex + 1;
+ }
+
+ if (usb_proc_create(&sc->sc_taskqueue, &sc->sc_mutex,
+ device_get_nameunit(sc->sc_dev),
+ USB_PRI_MED) != 0)
+ goto fail;
+
+ DPRINTFN(2, "ctrl-ifno#%d: data-ifno#%d\n", sc->sc_ctrl_ifaceno,
+ data_ifaceno);
+
+ usb_callout_init_mtx(&sc->sc_statechg_timer, &sc->sc_mutex, 0);
+
+ umb_ncm_setup(sc, config);
+ DPRINTFN(2, "%s: rx/tx size %d/%d\n", DEVNAM(sc),
+ sc->sc_rx_bufsz, sc->sc_tx_bufsz);
+
+ sc->sc_rx_buf = malloc(sc->sc_rx_bufsz, M_DEVBUF, M_WAITOK);
+ sc->sc_tx_buf = malloc(sc->sc_tx_bufsz, M_DEVBUF, M_WAITOK);
+
+ for (i = 0; i != 32; i++) {
+ error = usbd_set_alt_interface_index(sc->sc_udev,
+ sc->sc_ifaces_index[0], i);
+ if (error)
+ break;
+
+ error = usbd_transfer_setup(sc->sc_udev, sc->sc_ifaces_index,
+ sc->sc_xfer, config, UMB_N_TRANSFER,
+ sc, &sc->sc_mutex);
+ if (error == USB_ERR_NORMAL_COMPLETION)
+ break;
+ }
+ if (error || (i == 32)) {
+ device_printf(sc->sc_dev, "error: failed to setup xfers\n");
+ goto fail;
+ }
+
+ sc->sc_resp_buf = malloc(sc->sc_ctrl_len, M_DEVBUF, M_WAITOK);
+ sc->sc_ctrl_msg = malloc(sc->sc_ctrl_len, M_DEVBUF, M_WAITOK);
+
+ sc->sc_info.regstate = MBIM_REGSTATE_UNKNOWN;
+ sc->sc_info.pin_attempts_left = UMB_VALUE_UNKNOWN;
+ sc->sc_info.rssi = UMB_VALUE_UNKNOWN;
+ sc->sc_info.ber = UMB_VALUE_UNKNOWN;
+
+ /* defer attaching the interface */
+ mtx_lock(&sc->sc_mutex);
+ umb_add_task(sc, umb_attach_task,
+ &sc->sc_proc_attach_task[0].hdr,
+ &sc->sc_proc_attach_task[1].hdr, 0);
+ mtx_unlock(&sc->sc_mutex);
+
+ return (0);
+
+fail:
+ umb_detach(sc->sc_dev);
+ return (ENXIO);
+}
+
+static void
+umb_attach_task(struct usb_proc_msg *msg)
+{
+ struct umb_task *task = (struct umb_task *)msg;
+ struct umb_softc *sc = task->sc;
+ if_t ifp;
+
+ mtx_unlock(&sc->sc_mutex);
+
+ CURVNET_SET_QUIET(vnet0);
+
+ /* initialize the interface */
+ sc->sc_if = ifp = if_alloc(IFT_MBIM);
+ if_initname(ifp, "umb", device_get_unit(sc->sc_dev));
+
+ if_setsoftc(ifp, sc);
+ if_setflags(ifp, IFF_SIMPLEX | IFF_MULTICAST | IFF_POINTOPOINT);
+ if_setioctlfn(ifp, umb_ioctl);
+#ifdef DEV_NETMAP
+ if_setinputfn(ifp, umb_input);
+#endif
+ if_setoutputfn(ifp, umb_output);
+ if_setstartfn(ifp, umb_start);
+ if_setinitfn(ifp, umb_init);
+
+#if 0
+ if_setwatchdog(ifp, umb_watchdog);
+#endif
+ if_link_state_change(ifp, LINK_STATE_DOWN);
+ ifmedia_init(&sc->sc_im, 0, umb_mediachange, umb_mediastatus);
+ ifmedia_add(&sc->sc_im, IFM_NONE | IFM_AUTO, 0, NULL);
+
+ if_setifheaderlen(ifp, sizeof (struct ncm_header16) +
+ sizeof (struct ncm_pointer16)); /* XXX - IFAPI */
+ /* XXX hard-coded atm */
+ if_setmtu(ifp, MIN(2048, sc->sc_maxpktlen));
+ if_setsendqlen(ifp, ifqmaxlen);
+ if_setsendqready(ifp);
+
+ /* attach the interface */
+ if_attach(ifp);
+ bpfattach(ifp, DLT_RAW, 0);
+
+ sc->sc_attached = 1;
+
+ CURVNET_RESTORE();
+
+ umb_init(sc);
+ mtx_lock(&sc->sc_mutex);
+}
+
+static int
+umb_detach(device_t dev)
+{
+ struct umb_softc *sc = device_get_softc(dev);
+ if_t ifp = GET_IFP(sc);
+
+ usb_proc_drain(&sc->sc_taskqueue);
+
+ mtx_lock(&sc->sc_mutex);
+ if (ifp != NULL && (if_getdrvflags(ifp) & IFF_DRV_RUNNING))
+ umb_down(sc, 1);
+ umb_close(sc);
+ mtx_unlock(&sc->sc_mutex);
+
+ usbd_transfer_unsetup(sc->sc_xfer, UMB_N_TRANSFER);
+
+ free(sc->sc_tx_buf, M_DEVBUF);
+ free(sc->sc_rx_buf, M_DEVBUF);
+
+ usb_callout_drain(&sc->sc_statechg_timer);
+
+ usb_proc_free(&sc->sc_taskqueue);
+
+ mtx_destroy(&sc->sc_mutex);
+
+ free(sc->sc_ctrl_msg, M_DEVBUF);
+ free(sc->sc_resp_buf, M_DEVBUF);
+
+ if (ifp != NULL && if_getsoftc(ifp)) {
+ ifmedia_removeall(&sc->sc_im);
+ }
+ if (sc->sc_attached) {
+ bpfdetach(ifp);
+ if_detach(ifp);
+ if_free(ifp);
+ sc->sc_if = NULL;
+ }
+
+ return 0;
+}
+
+static void
+umb_ncm_setup(struct umb_softc *sc, struct usb_config * config)
+{
+ usb_device_request_t req;
+ struct ncm_ntb_parameters np;
+ usb_error_t error;
+
+ /* Query NTB tranfers sizes */
+ req.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.bRequest = NCM_GET_NTB_PARAMETERS;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, sc->sc_ctrl_ifaceno);
+ USETW(req.wLength, sizeof (np));
+ mtx_lock(&sc->sc_mutex);
+ error = usbd_do_request(sc->sc_udev, &sc->sc_mutex, &req, &np);
+ mtx_unlock(&sc->sc_mutex);
+ if (error == USB_ERR_NORMAL_COMPLETION &&
+ UGETW(np.wLength) == sizeof (np)) {
+ config[UMB_BULK_RX].bufsize = UGETDW(np.dwNtbInMaxSize);
+ config[UMB_BULK_TX].bufsize = UGETDW(np.dwNtbOutMaxSize);
+ }
+ sc->sc_rx_bufsz = config[UMB_BULK_RX].bufsize;
+ sc->sc_tx_bufsz = config[UMB_BULK_TX].bufsize;
+}
+
+static int
+umb_handle_request(device_t dev,
+ const void *preq, void **pptr, uint16_t *plen,
+ uint16_t offset, uint8_t *pstate)
+{
+ /* FIXME really implement */
+
+ return (ENXIO);
+}
+
+static int
+umb_suspend(device_t dev)
+{
+ device_printf(dev, "Suspending\n");
+ return (0);
+}
+
+static int
+umb_resume(device_t dev)
+{
+ device_printf(dev, "Resuming\n");
+ return (0);
+}
+
+static int
+umb_deactivate(device_t dev)
+{
+ struct umb_softc *sc = device_get_softc(dev);
+ if_t ifp = GET_IFP(sc);
+
+ if (ifp != NULL) {
+ if_dead(ifp);
+ }
+ sc->sc_dying = 1;
+ return 0;
+}
+
+static void
+umb_close_bulkpipes(struct umb_softc *sc)
+{
+ if_t ifp = GET_IFP(sc);
+
+ if_setdrvflagbits(ifp, 0, (IFF_DRV_RUNNING | IFF_DRV_OACTIVE));
+
+ umb_rxflush(sc);
+ umb_txflush(sc);
+
+ usbd_transfer_stop(sc->sc_xfer[UMB_BULK_RX]);
+ usbd_transfer_stop(sc->sc_xfer[UMB_BULK_TX]);
+}
+
+static int
+umb_ioctl(if_t ifp, u_long cmd, caddr_t data)
+{
+ struct umb_softc *sc = if_getsoftc(ifp);
+ struct in_ifaddr *ia = (struct in_ifaddr *)data;
+ struct ifreq *ifr = (struct ifreq *)data;
+ int error = 0;
+ struct umb_parameter mp;
+
+ if (sc->sc_dying)
+ return EIO;
+
+ switch (cmd) {
+ case SIOCSIFADDR:
+ switch (ia->ia_ifa.ifa_addr->sa_family) {
+ case AF_INET:
+ break;
+#ifdef INET6
+ case AF_INET6:
+ break;
+#endif /* INET6 */
+ default:
+ error = EAFNOSUPPORT;
+ break;
+ }
+ break;
+ case SIOCSIFFLAGS:
+ mtx_lock(&sc->sc_mutex);
+ umb_add_task(sc, umb_state_task,
+ &sc->sc_proc_state_task[0].hdr,
+ &sc->sc_proc_state_task[1].hdr, 1);
+ mtx_unlock(&sc->sc_mutex);
+ break;
+ case SIOCGUMBINFO:
+ error = copyout(&sc->sc_info, ifr->ifr_ifru.ifru_data,
+ sizeof (sc->sc_info));
+ break;
+ case SIOCSUMBPARAM:
+ error = priv_check(curthread, PRIV_NET_SETIFPHYS);
+ if (error)
+ break;
+
+ if ((error = copyin(ifr->ifr_ifru.ifru_data, &mp, sizeof (mp))) != 0)
+ break;
+
+ if ((error = umb_setpin(sc, mp.op, mp.is_puk, mp.pin, mp.pinlen,
+ mp.newpin, mp.newpinlen)) != 0)
+ break;
+
+ if (mp.apnlen < 0 || mp.apnlen > sizeof (sc->sc_info.apn)) {
+ error = EINVAL;
+ break;
+ }
+ sc->sc_roaming = mp.roaming ? 1 : 0;
+ memset(sc->sc_info.apn, 0, sizeof (sc->sc_info.apn));
+ memcpy(sc->sc_info.apn, mp.apn, mp.apnlen);
+ sc->sc_info.apnlen = mp.apnlen;
+ memset(sc->sc_info.username, 0, sizeof (sc->sc_info.username));
+ memcpy(sc->sc_info.username, mp.username, mp.usernamelen);
+ sc->sc_info.usernamelen = mp.usernamelen;
+ memset(sc->sc_info.password, 0, sizeof (sc->sc_info.password));
+ memcpy(sc->sc_info.password, mp.password, mp.passwordlen);
+ sc->sc_info.passwordlen = mp.passwordlen;
+ sc->sc_info.preferredclasses = mp.preferredclasses;
+ umb_setdataclass(sc);
+ break;
+ case SIOCGUMBPARAM:
+ memset(&mp, 0, sizeof (mp));
+ memcpy(mp.apn, sc->sc_info.apn, sc->sc_info.apnlen);
+ mp.apnlen = sc->sc_info.apnlen;
+ mp.roaming = sc->sc_roaming;
+ mp.preferredclasses = sc->sc_info.preferredclasses;
+ error = copyout(&mp, ifr->ifr_ifru.ifru_data, sizeof (mp));
+ break;
+ case SIOCSIFMTU:
+ /* Does this include the NCM headers and tail? */
+ if (ifr->ifr_mtu > if_getmtu(ifp)) {
+ error = EINVAL;
+ break;
+ }
+ if_setmtu(ifp, ifr->ifr_mtu);
+ break;
+ case SIOCAIFADDR:
+ case SIOCSIFDSTADDR:
+ case SIOCADDMULTI:
+ case SIOCDELMULTI:
+ break;
+ case SIOCGIFMEDIA:
+ error = ifmedia_ioctl(ifp, ifr, &sc->sc_im, cmd);
+ break;
+ default:
+ error = EINVAL;
+ break;
+ }
+ return (error);
+}
+
+static void
+umb_init(void *arg)
+{
+ struct umb_softc *sc = arg;
+
+ mtx_lock(&sc->sc_mutex);
+ umb_add_task(sc, umb_start_task,
+ &sc->sc_proc_start_task[0].hdr,
+ &sc->sc_proc_start_task[1].hdr, 0);
+ mtx_unlock(&sc->sc_mutex);
+}
+
+static void
+umb_input(if_t ifp, struct mbuf *m)
+{
+ struct mbuf *mn;
+ struct epoch_tracker et;
+
+ while (m) {
+ mn = m->m_nextpkt;
+ m->m_nextpkt = NULL;
+
+ NET_EPOCH_ENTER(et);
+ BPF_MTAP(ifp, m);
+
+ CURVNET_SET_QUIET(if_getvnet(ifp));
+
+ netisr_dispatch(NETISR_IP, m);
+ m = mn;
+
+ CURVNET_RESTORE();
+ NET_EPOCH_EXIT(et);
+ }
+}
+
+static int
+umb_output(if_t ifp, struct mbuf *m, const struct sockaddr *dst,
+ struct route *rtp)
+{
+ int error;
+
+ DPRINTFN(10, "%s: enter\n", __func__);
+
+ switch (dst->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ /* fall through */
+#endif
+ case AF_INET:
+ break;
+
+ /* silently drop dhclient packets */
+ case AF_UNSPEC:
+ m_freem(m);
+ return (0);
+
+ /* drop other packet types */
+ default:
+ m_freem(m);
+ return (EAFNOSUPPORT);
+ }
+
+ /*
+ * Queue message on interface, and start output if interface
+ * not yet active.
+ */
+ error = if_transmit(ifp, m);
+ if (error) {
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ return (ENOBUFS);
+ }
+
+ return (0);
+}
+
+static void
+umb_start(if_t ifp)
+{
+ struct umb_softc *sc = if_getsoftc(ifp);
+
+ if (sc->sc_dying || !(if_getdrvflags(ifp) & IFF_DRV_RUNNING))
+ return;
+
+ mtx_lock(&sc->sc_mutex);
+ usbd_transfer_start(sc->sc_xfer[UMB_BULK_TX]);
+ mtx_unlock(&sc->sc_mutex);
+}
+
+static void
+umb_start_task(struct usb_proc_msg *msg)
+{
+ struct umb_task *task = (struct umb_task *)msg;
+ struct umb_softc *sc = task->sc;
+ if_t ifp = GET_IFP(sc);
+
+ DPRINTF("%s()\n", __func__);
+
+ mtx_assert(&sc->sc_mutex, MA_OWNED);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+
+ /* start interrupt transfer */
+ usbd_transfer_start(sc->sc_xfer[UMB_INTR_RX]);
+
+ umb_open(sc);
+}
+
+#if 0
+static void
+umb_watchdog(if_t ifp)
+{
+ struct umb_softc *sc = if_getsoftc(ifp);
+
+ if (sc->sc_dying)
+ return;
+
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ device_printf(sc->sc_dev, "watchdog timeout\n");
+ usbd_transfer_drain(sc->sc_xfer[UMB_BULK_TX]);
+ return;
+}
+#endif
+
+static void
+umb_statechg_timeout(void *arg)
+{
+ struct umb_softc *sc = arg;
+ if_t ifp = GET_IFP(sc);
+
+ mtx_assert(&sc->sc_mutex, MA_OWNED);
+
+ if (sc->sc_info.regstate != MBIM_REGSTATE_ROAMING || sc->sc_roaming)
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_DEBUG, "%s: state change timeout\n",
+ DEVNAM(sc));
+
+ umb_add_task(sc, umb_state_task,
+ &sc->sc_proc_state_task[0].hdr,
+ &sc->sc_proc_state_task[1].hdr, 0);
+}
+
+static int
+umb_mediachange(if_t ifp)
+{
+ return 0;
+}
+
+static void
+umb_mediastatus(if_t ifp, struct ifmediareq * imr)
+{
+ switch (if_getlinkstate(ifp)) {
+ case LINK_STATE_UP:
+ imr->ifm_status = IFM_AVALID | IFM_ACTIVE;
+ break;
+ case LINK_STATE_DOWN:
+ imr->ifm_status = IFM_AVALID;
+ break;
+ default:
+ imr->ifm_status = 0;
+ break;
+ }
+}
+
+static void
+umb_add_task(struct umb_softc *sc, usb_proc_callback_t callback,
+ struct usb_proc_msg *t0, struct usb_proc_msg *t1, int sync)
+{
+ struct umb_task * task;
+
+ mtx_assert(&sc->sc_mutex, MA_OWNED);
+
+ if (usb_proc_is_gone(&sc->sc_taskqueue)) {
+ return;
+ }
+
+ task = usb_proc_msignal(&sc->sc_taskqueue, t0, t1);
+
+ task->hdr.pm_callback = callback;
+ task->sc = sc;
+
+ if (sync) {
+ usb_proc_mwait(&sc->sc_taskqueue, t0, t1);
+ }
+}
+
+static void
+umb_newstate(struct umb_softc *sc, enum umb_state newstate, int flags)
+{
+ if_t ifp = GET_IFP(sc);
+
+ if (newstate == sc->sc_state)
+ return;
+ if (((flags & UMB_NS_DONT_DROP) && newstate < sc->sc_state) ||
+ ((flags & UMB_NS_DONT_RAISE) && newstate > sc->sc_state))
+ return;
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_DEBUG, "%s: state going %s from '%s' to '%s'\n",
+ DEVNAM(sc), newstate > sc->sc_state ? "up" : "down",
+ umb_istate(sc->sc_state), umb_istate(newstate));
+ sc->sc_state = newstate;
+ umb_add_task(sc, umb_state_task,
+ &sc->sc_proc_state_task[0].hdr,
+ &sc->sc_proc_state_task[1].hdr, 0);
+}
+
+static void
+umb_state_task(struct usb_proc_msg *msg)
+{
+ struct umb_task *task = (struct umb_task *)msg;
+ struct umb_softc *sc = task->sc;
+ if_t ifp = GET_IFP(sc);
+ struct ifreq ifr;
+ int state;
+
+ DPRINTF("%s()\n", __func__);
+
+ if (sc->sc_info.regstate == MBIM_REGSTATE_ROAMING && !sc->sc_roaming) {
+ /*
+ * Query the registration state until we're with the home
+ * network again.
+ */
+ umb_cmd(sc, MBIM_CID_REGISTER_STATE, MBIM_CMDOP_QRY, NULL, 0);
+ return;
+ }
+
+ if (if_getflags(ifp) & IFF_UP)
+ umb_up(sc);
+ else
+ umb_down(sc, 0);
+
+ state = (sc->sc_state == UMB_S_UP) ? LINK_STATE_UP : LINK_STATE_DOWN;
+ if (if_getlinkstate(ifp) != state) {
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_DEBUG, "%s: link state changed from %s to %s\n",
+ DEVNAM(sc),
+ (if_getlinkstate(ifp) == LINK_STATE_UP)
+ ? "up" : "down",
+ (state == LINK_STATE_UP) ? "up" : "down");
+ if_link_state_change(ifp, state); /* XXX - IFAPI */
+ if (state != LINK_STATE_UP) {
+ /*
+ * Purge any existing addresses
+ */
+ memset(sc->sc_info.ipv4dns, 0,
+ sizeof (sc->sc_info.ipv4dns));
+ mtx_unlock(&sc->sc_mutex);
+ CURVNET_SET_QUIET(if_getvnet(ifp));
+ if (in_control(NULL, SIOCGIFADDR, (caddr_t)&ifr, ifp,
+ curthread) == 0 &&
+ satosin(&ifr.ifr_addr)->sin_addr.s_addr !=
+ INADDR_ANY) {
+ in_control(NULL, SIOCDIFADDR, (caddr_t)&ifr,
+ ifp, curthread);
+ }
+ CURVNET_RESTORE();
+ mtx_lock(&sc->sc_mutex);
+ }
+ if_link_state_change(ifp, state);
+ }
+}
+
+static void
+umb_up(struct umb_softc *sc)
+{
+ if_t ifp = GET_IFP(sc);
+
+ switch (sc->sc_state) {
+ case UMB_S_DOWN:
+ DPRINTF("init: opening ...\n");
+ umb_open(sc);
+ break;
+ case UMB_S_OPEN:
+ if (sc->sc_flags & UMBFLG_FCC_AUTH_REQUIRED) {
+ if (sc->sc_cid == -1) {
+ DPRINTF("init: allocating CID ...\n");
+ umb_allocate_cid(sc);
+ break;
+ } else
+ umb_newstate(sc, UMB_S_CID, UMB_NS_DONT_DROP);
+ } else {
+ DPRINTF("init: turning radio on ...\n");
+ umb_radio(sc, 1);
+ break;
+ }
+ /*FALLTHROUGH*/
+ case UMB_S_CID:
+ DPRINTF("init: sending FCC auth ...\n");
+ umb_send_fcc_auth(sc);
+ break;
+ case UMB_S_RADIO:
+ DPRINTF("init: checking SIM state ...\n");
+ umb_cmd(sc, MBIM_CID_SUBSCRIBER_READY_STATUS, MBIM_CMDOP_QRY,
+ NULL, 0);
+ break;
+ case UMB_S_SIMREADY:
+ DPRINTF("init: attaching ...\n");
+ umb_packet_service(sc, 1);
+ break;
+ case UMB_S_ATTACHED:
+ sc->sc_tx_seq = 0;
+ DPRINTF("init: connecting ...\n");
+ umb_connect(sc);
+ break;
+ case UMB_S_CONNECTED:
+ DPRINTF("init: getting IP config ...\n");
+ umb_qry_ipconfig(sc);
+ break;
+ case UMB_S_UP:
+ DPRINTF("init: reached state UP\n");
+ if (!(if_getflags(ifp) & IFF_DRV_RUNNING)) {
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+ umb_rx(sc);
+ }
+ break;
+ }
+ if (sc->sc_state < UMB_S_UP)
+ usb_callout_reset(&sc->sc_statechg_timer,
+ UMB_STATE_CHANGE_TIMEOUT * hz, umb_statechg_timeout, sc);
+ else {
+ usb_callout_stop(&sc->sc_statechg_timer);
+ }
+ return;
+}
+
+static void
+umb_down(struct umb_softc *sc, int force)
+{
+ umb_close_bulkpipes(sc);
+
+ switch (sc->sc_state) {
+ case UMB_S_UP:
+ case UMB_S_CONNECTED:
+ DPRINTF("stop: disconnecting ...\n");
+ umb_disconnect(sc);
+ if (!force)
+ break;
+ /*FALLTHROUGH*/
+ case UMB_S_ATTACHED:
+ DPRINTF("stop: detaching ...\n");
+ umb_packet_service(sc, 0);
+ if (!force)
+ break;
+ /*FALLTHROUGH*/
+ case UMB_S_SIMREADY:
+ case UMB_S_RADIO:
+ DPRINTF("stop: turning radio off ...\n");
+ umb_radio(sc, 0);
+ if (!force)
+ break;
+ /*FALLTHROUGH*/
+ case UMB_S_CID:
+ case UMB_S_OPEN:
+ case UMB_S_DOWN:
+ /* Do not close the device */
+ DPRINTF("stop: reached state DOWN\n");
+ break;
+ }
+ if (force)
+ sc->sc_state = UMB_S_OPEN;
+
+ if (sc->sc_state > UMB_S_OPEN)
+ usb_callout_reset(&sc->sc_statechg_timer,
+ UMB_STATE_CHANGE_TIMEOUT * hz, umb_statechg_timeout, sc);
+ else
+ usb_callout_stop(&sc->sc_statechg_timer);
+}
+
+static void
+umb_get_response_task(struct usb_proc_msg *msg)
+{
+ struct umb_task *task = (struct umb_task *)msg;
+ struct umb_softc *sc = task->sc;
+ int len;
+
+ DPRINTF("%s()\n", __func__);
+ /*
+ * Function is required to send on RESPONSE_AVAILABLE notification for
+ * each encapsulated response that is to be processed by the host.
+ * But of course, we can receive multiple notifications before the
+ * response task is run.
+ */
+ while (sc->sc_nresp > 0) {
+ --sc->sc_nresp;
+ len = sc->sc_ctrl_len;
+ if (umb_get_encap_response(sc, sc->sc_resp_buf, &len))
+ umb_decode_response(sc, sc->sc_resp_buf, len);
+ }
+}
+
+static void
+umb_decode_response(struct umb_softc *sc, void *response, int len)
+{
+ struct mbim_msghdr *hdr = response;
+ struct mbim_fragmented_msg_hdr *fraghdr;
+ uint32_t type;
+
+ DPRINTFN(3, "got response: len %d\n", len);
+ DDUMPN(4, response, len);
+
+ if (len < sizeof (*hdr) || le32toh(hdr->len) != len) {
+ /*
+ * We should probably cancel a transaction, but since the
+ * message is too short, we cannot decode the transaction
+ * id (tid) and hence don't know, whom to cancel. Must wait
+ * for the timeout.
+ */
+ DPRINTF("received short response (len %d)\n",
+ len);
+ return;
+ }
+
+ /*
+ * XXX FIXME: if message is fragmented, store it until last frag
+ * is received and then re-assemble all fragments.
+ */
+ type = le32toh(hdr->type);
+ switch (type) {
+ case MBIM_INDICATE_STATUS_MSG:
+ case MBIM_COMMAND_DONE:
+ fraghdr = response;
+ if (le32toh(fraghdr->frag.nfrag) != 1) {
+ DPRINTF("discarding fragmented messages\n");
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ DPRINTF("<- rcv %s (tid %u)\n", umb_request2str(type),
+ le32toh(hdr->tid));
+ switch (type) {
+ case MBIM_FUNCTION_ERROR_MSG:
+ case MBIM_HOST_ERROR_MSG:
+ {
+ struct mbim_f2h_hosterr *e;
+ int err;
+
+ if (len >= sizeof (*e)) {
+ e = response;
+ err = le32toh(e->err);
+
+ DPRINTF("%s message, error %s (tid %u)\n",
+ umb_request2str(type),
+ umb_error2str(err), le32toh(hdr->tid));
+ if (err == MBIM_ERROR_NOT_OPENED)
+ umb_newstate(sc, UMB_S_DOWN, 0);
+ }
+ break;
+ }
+ case MBIM_INDICATE_STATUS_MSG:
+ umb_handle_indicate_status_msg(sc, response, len);
+ break;
+ case MBIM_OPEN_DONE:
+ umb_handle_opendone_msg(sc, response, len);
+ break;
+ case MBIM_CLOSE_DONE:
+ umb_handle_closedone_msg(sc, response, len);
+ break;
+ case MBIM_COMMAND_DONE:
+ umb_command_done(sc, response, len);
+ break;
+ default:
+ DPRINTF("discard message %s\n",
+ umb_request2str(type));
+ break;
+ }
+}
+
+static void
+umb_handle_indicate_status_msg(struct umb_softc *sc, void *data, int len)
+{
+ struct mbim_f2h_indicate_status *m = data;
+ uint32_t infolen;
+ uint32_t cid;
+
+ if (len < sizeof (*m)) {
+ DPRINTF("discard short %s message\n",
+ umb_request2str(le32toh(m->hdr.type)));
+ return;
+ }
+ if (memcmp(m->devid, umb_uuid_basic_connect, sizeof (m->devid))) {
+ DPRINTF("discard %s message for other UUID '%s'\n",
+ umb_request2str(le32toh(m->hdr.type)),
+ umb_uuid2str(m->devid));
+ return;
+ }
+ infolen = le32toh(m->infolen);
+ if (len < sizeof (*m) + infolen) {
+ DPRINTF("discard truncated %s message (want %d, got %d)\n",
+ umb_request2str(le32toh(m->hdr.type)),
+ (int)sizeof (*m) + infolen, len);
+ return;
+ }
+
+ cid = le32toh(m->cid);
+ DPRINTF("indicate %s status\n", umb_cid2str(cid));
+ umb_decode_cid(sc, cid, m->info, infolen);
+}
+
+static void
+umb_handle_opendone_msg(struct umb_softc *sc, void *data, int len)
+{
+ struct mbim_f2h_openclosedone *resp = data;
+ if_t ifp = GET_IFP(sc);
+ uint32_t status;
+
+ status = le32toh(resp->status);
+ if (status == MBIM_STATUS_SUCCESS) {
+ if (sc->sc_maxsessions == 0) {
+ umb_cmd(sc, MBIM_CID_DEVICE_CAPS, MBIM_CMDOP_QRY, NULL,
+ 0);
+ umb_cmd(sc, MBIM_CID_PIN, MBIM_CMDOP_QRY, NULL, 0);
+ umb_cmd(sc, MBIM_CID_REGISTER_STATE, MBIM_CMDOP_QRY,
+ NULL, 0);
+ }
+ umb_newstate(sc, UMB_S_OPEN, UMB_NS_DONT_DROP);
+ } else if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_ERR, "%s: open error: %s\n", DEVNAM(sc),
+ umb_status2str(status));
+ return;
+}
+
+static void
+umb_handle_closedone_msg(struct umb_softc *sc, void *data, int len)
+{
+ struct mbim_f2h_openclosedone *resp = data;
+ uint32_t status;
+
+ status = le32toh(resp->status);
+ if (status == MBIM_STATUS_SUCCESS)
+ umb_newstate(sc, UMB_S_DOWN, 0);
+ else
+ DPRINTF("close error: %s\n",
+ umb_status2str(status));
+ return;
+}
+
+static inline void
+umb_getinfobuf(char *in, int inlen, uint32_t offs, uint32_t sz,
+ void *out, size_t outlen)
+{
+ offs = le32toh(offs);
+ sz = le32toh(sz);
+ memset(out, 0, outlen);
+ if ((uint64_t)inlen >= (uint64_t)offs + (uint64_t)sz)
+ memcpy(out, in + offs, MIN(sz, outlen));
+}
+
+static inline int
+umb_padding(void *data, int len, size_t sz)
+{
+ char *p = data;
+ int np = 0;
+
+ while (len < sz && (len % 4) != 0) {
+ *p++ = '\0';
+ len++;
+ np++;
+ }
+ return np;
+}
+
+static inline int
+umb_addstr(void *buf, size_t bufsz, int *offs, void *str, int slen,
+ uint32_t *offsmember, uint32_t *sizemember)
+{
+ if (*offs + slen > bufsz)
+ return 0;
+
+ *sizemember = htole32((uint32_t)slen);
+ if (slen && str) {
+ *offsmember = htole32((uint32_t)*offs);
+ memcpy((char *)buf + *offs, str, slen);
+ *offs += slen;
+ *offs += umb_padding(buf, *offs, bufsz);
+ } else
+ *offsmember = htole32(0);
+ return 1;
+}
+
+static void
+umb_in_len2mask(struct in_addr *mask, int len)
+{
+ int i;
+ u_char *p;
+
+ p = (u_char *)mask;
+ memset(mask, 0, sizeof (*mask));
+ for (i = 0; i < len / 8; i++)
+ p[i] = 0xff;
+ if (len % 8)
+ p[i] = (0xff00 >> (len % 8)) & 0xff;
+}
+
+static int
+umb_decode_register_state(struct umb_softc *sc, void *data, int len)
+{
+ struct mbim_cid_registration_state_info *rs = data;
+ if_t ifp = GET_IFP(sc);
+
+ if (len < sizeof (*rs))
+ return 0;
+ sc->sc_info.nwerror = le32toh(rs->nwerror);
+ sc->sc_info.regstate = le32toh(rs->regstate);
+ sc->sc_info.regmode = le32toh(rs->regmode);
+ sc->sc_info.cellclass = le32toh(rs->curcellclass);
+
+ /* XXX should we remember the provider_id? */
+ umb_getinfobuf(data, len, rs->provname_offs, rs->provname_size,
+ sc->sc_info.provider, sizeof (sc->sc_info.provider));
+ umb_getinfobuf(data, len, rs->roamingtxt_offs, rs->roamingtxt_size,
+ sc->sc_info.roamingtxt, sizeof (sc->sc_info.roamingtxt));
+
+ DPRINTFN(2, "%s, availclass 0x%x, class 0x%x, regmode %d\n",
+ umb_regstate(sc->sc_info.regstate),
+ le32toh(rs->availclasses), sc->sc_info.cellclass,
+ sc->sc_info.regmode);
+
+ if (sc->sc_info.regstate == MBIM_REGSTATE_ROAMING &&
+ !sc->sc_roaming &&
+ sc->sc_info.activation == MBIM_ACTIVATION_STATE_ACTIVATED) {
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_INFO,
+ "%s: disconnecting from roaming network\n",
+ DEVNAM(sc));
+ umb_disconnect(sc);
+ }
+ return 1;
+}
+
+static int
+umb_decode_devices_caps(struct umb_softc *sc, void *data, int len)
+{
+ struct mbim_cid_device_caps *dc = data;
+
+ if (len < sizeof (*dc))
+ return 0;
+ sc->sc_maxsessions = le32toh(dc->max_sessions);
+ sc->sc_info.supportedclasses = le32toh(dc->dataclass);
+ umb_getinfobuf(data, len, dc->devid_offs, dc->devid_size,
+ sc->sc_info.devid, sizeof (sc->sc_info.devid));
+ umb_getinfobuf(data, len, dc->fwinfo_offs, dc->fwinfo_size,
+ sc->sc_info.fwinfo, sizeof (sc->sc_info.fwinfo));
+ umb_getinfobuf(data, len, dc->hwinfo_offs, dc->hwinfo_size,
+ sc->sc_info.hwinfo, sizeof (sc->sc_info.hwinfo));
+ DPRINTFN(2, "max sessions %d, supported classes 0x%x\n",
+ sc->sc_maxsessions, sc->sc_info.supportedclasses);
+ return 1;
+}
+
+static int
+umb_decode_subscriber_status(struct umb_softc *sc, void *data, int len)
+{
+ struct mbim_cid_subscriber_ready_info *si = data;
+ if_t ifp = GET_IFP(sc);
+ int npn;
+
+ if (len < sizeof (*si))
+ return 0;
+ sc->sc_info.sim_state = le32toh(si->ready);
+
+ umb_getinfobuf(data, len, si->sid_offs, si->sid_size,
+ sc->sc_info.sid, sizeof (sc->sc_info.sid));
+ umb_getinfobuf(data, len, si->icc_offs, si->icc_size,
+ sc->sc_info.iccid, sizeof (sc->sc_info.iccid));
+
+ npn = le32toh(si->no_pn);
+ if (npn > 0)
+ umb_getinfobuf(data, len, si->pn[0].offs, si->pn[0].size,
+ sc->sc_info.pn, sizeof (sc->sc_info.pn));
+ else
+ memset(sc->sc_info.pn, 0, sizeof (sc->sc_info.pn));
+
+ if (sc->sc_info.sim_state == MBIM_SIMSTATE_LOCKED)
+ sc->sc_info.pin_state = UMB_PIN_REQUIRED;
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_INFO, "%s: SIM %s\n", DEVNAM(sc),
+ umb_simstate(sc->sc_info.sim_state));
+ if (sc->sc_info.sim_state == MBIM_SIMSTATE_INITIALIZED)
+ umb_newstate(sc, UMB_S_SIMREADY, UMB_NS_DONT_DROP);
+ return 1;
+}
+
+static int
+umb_decode_radio_state(struct umb_softc *sc, void *data, int len)
+{
+ struct mbim_cid_radio_state_info *rs = data;
+ if_t ifp = GET_IFP(sc);
+
+ if (len < sizeof (*rs))
+ return 0;
+
+ sc->sc_info.hw_radio_on =
+ (le32toh(rs->hw_state) == MBIM_RADIO_STATE_ON) ? 1 : 0;
+ sc->sc_info.sw_radio_on =
+ (le32toh(rs->sw_state) == MBIM_RADIO_STATE_ON) ? 1 : 0;
+ if (!sc->sc_info.hw_radio_on) {
+ device_printf(sc->sc_dev, "radio is disabled by hardware switch\n");
+ /*
+ * XXX do we need a time to poll the state of the rfkill switch
+ * or will the device send an unsolicited notification
+ * in case the state changes?
+ */
+ umb_newstate(sc, UMB_S_OPEN, 0);
+ } else if (!sc->sc_info.sw_radio_on) {
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_INFO, "%s: radio is off\n", DEVNAM(sc));
+ umb_newstate(sc, UMB_S_OPEN, 0);
+ } else
+ umb_newstate(sc, UMB_S_RADIO, UMB_NS_DONT_DROP);
+ return 1;
+}
+
+static int
+umb_decode_pin(struct umb_softc *sc, void *data, int len)
+{
+ struct mbim_cid_pin_info *pi = data;
+ if_t ifp = GET_IFP(sc);
+ uint32_t attempts_left;
+
+ if (len < sizeof (*pi))
+ return 0;
+
+ attempts_left = le32toh(pi->remaining_attempts);
+ if (attempts_left != 0xffffffff)
+ sc->sc_info.pin_attempts_left = attempts_left;
+
+ switch (le32toh(pi->state)) {
+ case MBIM_PIN_STATE_UNLOCKED:
+ sc->sc_info.pin_state = UMB_PIN_UNLOCKED;
+ break;
+ case MBIM_PIN_STATE_LOCKED:
+ switch (le32toh(pi->type)) {
+ case MBIM_PIN_TYPE_PIN1:
+ sc->sc_info.pin_state = UMB_PIN_REQUIRED;
+ break;
+ case MBIM_PIN_TYPE_PUK1:
+ sc->sc_info.pin_state = UMB_PUK_REQUIRED;
+ break;
+ case MBIM_PIN_TYPE_PIN2:
+ case MBIM_PIN_TYPE_PUK2:
+ /* Assume that PIN1 was accepted */
+ sc->sc_info.pin_state = UMB_PIN_UNLOCKED;
+ break;
+ }
+ break;
+ }
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_INFO, "%s: %s state %s (%d attempts left)\n",
+ DEVNAM(sc), umb_pin_type(le32toh(pi->type)),
+ (le32toh(pi->state) == MBIM_PIN_STATE_UNLOCKED) ?
+ "unlocked" : "locked",
+ le32toh(pi->remaining_attempts));
+
+ /*
+ * In case the PIN was set after IFF_UP, retrigger the state machine
+ */
+ umb_add_task(sc, umb_state_task,
+ &sc->sc_proc_state_task[0].hdr,
+ &sc->sc_proc_state_task[1].hdr, 0);
+ return 1;
+}
+
+static int
+umb_decode_packet_service(struct umb_softc *sc, void *data, int len)
+{
+ struct mbim_cid_packet_service_info *psi = data;
+ int state, highestclass;
+ uint64_t up_speed, down_speed;
+ if_t ifp = GET_IFP(sc);
+
+ if (len < sizeof (*psi))
+ return 0;
+
+ sc->sc_info.nwerror = le32toh(psi->nwerror);
+ state = le32toh(psi->state);
+ highestclass = le32toh(psi->highest_dataclass);
+ up_speed = le64toh(psi->uplink_speed);
+ down_speed = le64toh(psi->downlink_speed);
+ if (sc->sc_info.packetstate != state ||
+ sc->sc_info.uplink_speed != up_speed ||
+ sc->sc_info.downlink_speed != down_speed) {
+ if (if_getflags(ifp) & IFF_DEBUG) {
+ log(LOG_INFO, "%s: packet service ", DEVNAM(sc));
+ if (sc->sc_info.packetstate != state)
+ log(LOG_INFO, "changed from %s to ",
+ umb_packet_state(sc->sc_info.packetstate));
+ log(LOG_INFO, "%s, class %s, speed: %" PRIu64 " up / %" PRIu64 " down\n",
+ umb_packet_state(state),
+ umb_dataclass(highestclass), up_speed, down_speed);
+ }
+ }
+ sc->sc_info.packetstate = state;
+ sc->sc_info.highestclass = highestclass;
+ sc->sc_info.uplink_speed = up_speed;
+ sc->sc_info.downlink_speed = down_speed;
+
+ if (sc->sc_info.regmode == MBIM_REGMODE_AUTOMATIC) {
+ /*
+ * For devices using automatic registration mode, just proceed,
+ * once registration has completed.
+ */
+ if (if_getflags(ifp) & IFF_UP) {
+ switch (sc->sc_info.regstate) {
+ case MBIM_REGSTATE_HOME:
+ case MBIM_REGSTATE_ROAMING:
+ case MBIM_REGSTATE_PARTNER:
+ umb_newstate(sc, UMB_S_ATTACHED,
+ UMB_NS_DONT_DROP);
+ break;
+ default:
+ break;
+ }
+ } else
+ umb_newstate(sc, UMB_S_SIMREADY, UMB_NS_DONT_RAISE);
+ } else switch (sc->sc_info.packetstate) {
+ case MBIM_PKTSERVICE_STATE_ATTACHED:
+ umb_newstate(sc, UMB_S_ATTACHED, UMB_NS_DONT_DROP);
+ break;
+ case MBIM_PKTSERVICE_STATE_DETACHED:
+ umb_newstate(sc, UMB_S_SIMREADY, UMB_NS_DONT_RAISE);
+ break;
+ }
+ return 1;
+}
+
+static int
+umb_decode_signal_state(struct umb_softc *sc, void *data, int len)
+{
+ struct mbim_cid_signal_state *ss = data;
+ if_t ifp = GET_IFP(sc);
+ int rssi;
+
+ if (len < sizeof (*ss))
+ return 0;
+
+ if (le32toh(ss->rssi) == 99)
+ rssi = UMB_VALUE_UNKNOWN;
+ else {
+ rssi = -113 + 2 * le32toh(ss->rssi);
+ if ((if_getflags(ifp) & IFF_DEBUG) && sc->sc_info.rssi != rssi &&
+ sc->sc_state >= UMB_S_CONNECTED)
+ log(LOG_INFO, "%s: rssi %d dBm\n", DEVNAM(sc), rssi);
+ }
+ sc->sc_info.rssi = rssi;
+ sc->sc_info.ber = le32toh(ss->err_rate);
+ if (sc->sc_info.ber == -99)
+ sc->sc_info.ber = UMB_VALUE_UNKNOWN;
+ return 1;
+}
+
+static int
+umb_decode_connect_info(struct umb_softc *sc, void *data, int len)
+{
+ struct mbim_cid_connect_info *ci = data;
+ if_t ifp = GET_IFP(sc);
+ int act;
+
+ if (len < sizeof (*ci))
+ return 0;
+
+ if (le32toh(ci->sessionid) != umb_session_id) {
+ DPRINTF("discard connection info for session %u\n",
+ le32toh(ci->sessionid));
+ return 1;
+ }
+ if (memcmp(ci->context, umb_uuid_context_internet,
+ sizeof (ci->context))) {
+ DPRINTF("discard connection info for other context\n");
+ return 1;
+ }
+ act = le32toh(ci->activation);
+ if (sc->sc_info.activation != act) {
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_INFO, "%s: connection %s\n", DEVNAM(sc),
+ umb_activation(act));
+ if ((if_getflags(ifp) & IFF_DEBUG) &&
+ le32toh(ci->iptype) != MBIM_CONTEXT_IPTYPE_DEFAULT &&
+ le32toh(ci->iptype) != MBIM_CONTEXT_IPTYPE_IPV4)
+ log(LOG_DEBUG, "%s: got iptype %d connection\n",
+ DEVNAM(sc), le32toh(ci->iptype));
+
+ sc->sc_info.activation = act;
+ sc->sc_info.nwerror = le32toh(ci->nwerror);
+
+ if (sc->sc_info.activation == MBIM_ACTIVATION_STATE_ACTIVATED)
+ umb_newstate(sc, UMB_S_CONNECTED, UMB_NS_DONT_DROP);
+ else if (sc->sc_info.activation ==
+ MBIM_ACTIVATION_STATE_DEACTIVATED)
+ umb_newstate(sc, UMB_S_ATTACHED, 0);
+ /* else: other states are purely transitional */
+ }
+ return 1;
+}
+
+static int
+umb_add_inet_config(struct umb_softc *sc, struct in_addr ip, u_int prefixlen,
+ struct in_addr gw)
+{
+ if_t ifp = GET_IFP(sc);
+ struct in_aliasreq ifra;
+ struct sockaddr_in *sin;
+ int rv;
+
+ memset(&ifra, 0, sizeof (ifra));
+ sin = (struct sockaddr_in *)&ifra.ifra_addr;
+ sin->sin_family = AF_INET;
+ sin->sin_len = sizeof (*sin);
+ sin->sin_addr = ip;
+
+ sin = (struct sockaddr_in *)&ifra.ifra_dstaddr;
+ sin->sin_family = AF_INET;
+ sin->sin_len = sizeof (*sin);
+ sin->sin_addr = gw;
+
+ sin = (struct sockaddr_in *)&ifra.ifra_mask;
+ sin->sin_family = AF_INET;
+ sin->sin_len = sizeof (*sin);
+ umb_in_len2mask(&sin->sin_addr,
+ MIN(prefixlen, sizeof (struct in_addr) * 8));
+
+ mtx_unlock(&sc->sc_mutex);
+ CURVNET_SET_QUIET(if_getvnet(ifp));
+ rv = in_control(NULL, SIOCAIFADDR, (caddr_t)&ifra, ifp, curthread);
+ CURVNET_RESTORE();
+ mtx_lock(&sc->sc_mutex);
+ if (rv != 0) {
+ device_printf(sc->sc_dev, "unable to set IPv4 address, error %d\n",
+ rv);
+ return rv;
+ }
+
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_INFO, "%s: IPv4 addr %s, mask %s, "
+ "gateway %s\n", DEVNAM(sc),
+ umb_ntop(sintosa(&ifra.ifra_addr)),
+ umb_ntop(sintosa(&ifra.ifra_mask)),
+ umb_ntop(sintosa(&ifra.ifra_dstaddr)));
+
+ return 0;
+}
+
+static int
+umb_decode_ip_configuration(struct umb_softc *sc, void *data, int len)
+{
+ struct mbim_cid_ip_configuration_info *ic = data;
+ if_t ifp = GET_IFP(sc);
+ uint32_t avail_v4;
+ uint32_t val;
+ int n, i;
+ int off;
+ struct mbim_cid_ipv4_element ipv4elem;
+ struct in_addr addr, gw;
+ int state = -1;
+ int rv;
+
+ if (len < sizeof (*ic))
+ return 0;
+ if (le32toh(ic->sessionid) != umb_session_id) {
+ DPRINTF("ignore IP configuration for session id %d\n",
+ le32toh(ic->sessionid));
+ return 0;
+ }
+
+ /*
+ * IPv4 configuration
+ */
+ avail_v4 = le32toh(ic->ipv4_available);
+ if ((avail_v4 & (MBIM_IPCONF_HAS_ADDRINFO | MBIM_IPCONF_HAS_GWINFO)) ==
+ (MBIM_IPCONF_HAS_ADDRINFO | MBIM_IPCONF_HAS_GWINFO)) {
+ n = le32toh(ic->ipv4_naddr);
+ off = le32toh(ic->ipv4_addroffs);
+
+ if (n == 0 || off + sizeof (ipv4elem) > len)
+ goto tryv6;
+ if (n != 1 && if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_INFO, "%s: more than one IPv4 addr: %d\n",
+ DEVNAM(sc), n);
+
+ /* Only pick the first one */
+ memcpy(&ipv4elem, (char *)data + off, sizeof (ipv4elem));
+ ipv4elem.prefixlen = le32toh(ipv4elem.prefixlen);
+ addr.s_addr = ipv4elem.addr;
+
+ off = le32toh(ic->ipv4_gwoffs);
+ if (off + sizeof (gw) > len)
+ goto done;
+ memcpy(&gw, (char *)data + off, sizeof (gw));
+
+ rv = umb_add_inet_config(sc, addr, ipv4elem.prefixlen, gw);
+ if (rv == 0)
+ state = UMB_S_UP;
+ }
+
+ memset(sc->sc_info.ipv4dns, 0, sizeof (sc->sc_info.ipv4dns));
+ if (avail_v4 & MBIM_IPCONF_HAS_DNSINFO) {
+ n = le32toh(ic->ipv4_ndnssrv);
+ off = le32toh(ic->ipv4_dnssrvoffs);
+ i = 0;
+ while (n-- > 0) {
+ if (off + sizeof (addr) > len)
+ break;
+ memcpy(&addr, (char *)data + off, sizeof(addr));
+ if (i < UMB_MAX_DNSSRV)
+ sc->sc_info.ipv4dns[i++] = addr;
+ off += sizeof(addr);
+ }
+ }
+
+ if ((avail_v4 & MBIM_IPCONF_HAS_MTUINFO)) {
+ val = le32toh(ic->ipv4_mtu);
+ if (if_getmtu(ifp) != val && val <= sc->sc_maxpktlen) {
+ if_setmtu(ifp, val);
+ if (if_getmtu(ifp) > val)
+ if_setmtu(ifp, val);
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_INFO, "%s: MTU %d\n", DEVNAM(sc), val);
+ }
+ }
+
+ avail_v4 = le32toh(ic->ipv6_available);
+ if ((if_getflags(ifp) & IFF_DEBUG) && avail_v4 & MBIM_IPCONF_HAS_ADDRINFO) {
+ /* XXX FIXME: IPv6 configuration missing */
+ log(LOG_INFO, "%s: ignoring IPv6 configuration\n", DEVNAM(sc));
+ }
+ if (state != -1)
+ umb_newstate(sc, state, 0);
+
+tryv6:
+done:
+ return 1;
+}
+
+static void
+umb_rx(struct umb_softc *sc)
+{
+ mtx_assert(&sc->sc_mutex, MA_OWNED);
+
+ usbd_transfer_start(sc->sc_xfer[UMB_BULK_RX]);
+}
+
+static void
+umb_rxeof(struct usb_xfer *xfer, usb_error_t status)
+{
+ struct umb_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = GET_IFP(sc);
+ int actlen;
+ int aframes;
+ int i;
+
+ DPRINTF("%s(%u): state=%u\n", __func__, status, USB_GET_STATE(xfer));
+
+ mtx_assert(&sc->sc_mutex, MA_OWNED);
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTF("received %u bytes in %u frames\n", actlen, aframes);
+
+ if (actlen == 0) {
+ if (sc->sc_rx_nerr >= 4)
+ /* throttle transfers */
+ usbd_xfer_set_interval(xfer, 500);
+ else
+ sc->sc_rx_nerr++;
+ }
+ else {
+ /* disable throttling */
+ usbd_xfer_set_interval(xfer, 0);
+ sc->sc_rx_nerr = 0;
+ }
+
+ for(i = 0; i < aframes; i++) {
+ umb_decap(sc, xfer, i);
+ }
+
+ /* fall through */
+ case USB_ST_SETUP:
+ usbd_xfer_set_frame_data(xfer, 0, sc->sc_rx_buf,
+ sc->sc_rx_bufsz);
+ usbd_xfer_set_frames(xfer, 1);
+ usbd_transfer_submit(xfer);
+
+ umb_rxflush(sc);
+ break;
+ default:
+ DPRINTF("rx error: %s\n", usbd_errstr(status));
+
+ /* disable throttling */
+ usbd_xfer_set_interval(xfer, 0);
+
+ if (status != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ usbd_xfer_set_frames(xfer, 0);
+ usbd_transfer_submit(xfer);
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ }
+ else if (++sc->sc_rx_nerr > 100) {
+ log(LOG_ERR, "%s: too many rx errors, disabling\n",
+ DEVNAM(sc));
+ umb_deactivate(sc->sc_dev);
+ }
+ break;
+ }
+}
+
+static void
+umb_rxflush(struct umb_softc *sc)
+{
+ if_t ifp = GET_IFP(sc);
+ struct mbuf *m;
+
+ mtx_assert(&sc->sc_mutex, MA_OWNED);
+
+ for (;;) {
+ _IF_DEQUEUE(&sc->sc_rx_queue, m);
+ if (m == NULL)
+ break;
+
+ /*
+ * The USB xfer has been resubmitted so it's safe to unlock now.
+ */
+ mtx_unlock(&sc->sc_mutex);
+ CURVNET_SET_QUIET(if_getvnet(ifp));
+ if (if_getdrvflags(ifp) & IFF_DRV_RUNNING)
+ if_input(ifp, m);
+ else
+ m_freem(m);
+ CURVNET_RESTORE();
+ mtx_lock(&sc->sc_mutex);
+ }
+}
+
+static int
+umb_encap(struct umb_softc *sc, struct mbuf *m, struct usb_xfer *xfer)
+{
+ struct ncm_header16 *hdr;
+ struct ncm_pointer16 *ptr;
+ int len;
+
+ KASSERT(sc->sc_tx_m == NULL,
+ ("Assertion failed in umb_encap()"));
+
+ /* All size constraints have been validated by the caller! */
+ hdr = (struct ncm_header16 *)sc->sc_tx_buf;
+ ptr = (struct ncm_pointer16 *)(hdr + 1);
+
+ USETDW(hdr->dwSignature, NCM_HDR16_SIG);
+ USETW(hdr->wHeaderLength, sizeof (*hdr));
+ USETW(hdr->wSequence, sc->sc_tx_seq);
+ sc->sc_tx_seq++;
+ USETW(hdr->wNdpIndex, sizeof (*hdr));
+
+ len = m->m_pkthdr.len;
+ USETDW(ptr->dwSignature, MBIM_NCM_NTH16_SIG(umb_session_id));
+ USETW(ptr->wLength, sizeof (*ptr));
+ USETW(ptr->wNextNdpIndex, 0);
+ USETW(ptr->dgram[0].wDatagramIndex, MBIM_HDR16_LEN);
+ USETW(ptr->dgram[0].wDatagramLen, len);
+ USETW(ptr->dgram[1].wDatagramIndex, 0);
+ USETW(ptr->dgram[1].wDatagramLen, 0);
+
+ KASSERT(len + MBIM_HDR16_LEN <= sc->sc_tx_bufsz,
+ ("Assertion failed in umb_encap()"));
+ m_copydata(m, 0, len, (char *)(ptr + 1));
+ sc->sc_tx_m = m;
+ len += MBIM_HDR16_LEN;
+ USETW(hdr->wBlockLength, len);
+
+ usbd_xfer_set_frame_data(xfer, 0, sc->sc_tx_buf, len);
+ usbd_xfer_set_interval(xfer, 0);
+ usbd_xfer_set_frames(xfer, 1);
+
+ DPRINTFN(3, "%s: encap %d bytes\n", DEVNAM(sc), len);
+ DDUMPN(5, sc->sc_tx_buf, len);
+ return 0;
+}
+
+static void
+umb_txeof(struct usb_xfer *xfer, usb_error_t status)
+{
+ struct umb_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = GET_IFP(sc);
+ struct mbuf *m;
+
+ DPRINTF("%s(%u) state=%u\n", __func__, status, USB_GET_STATE(xfer));
+
+ mtx_assert(&sc->sc_mutex, MA_OWNED);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ umb_txflush(sc);
+
+ /* fall through */
+ case USB_ST_SETUP:
+tr_setup:
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0)
+ break;
+
+ m = if_dequeue(ifp); /* XXX - IFAPI */
+ if (m == NULL)
+ break;
+
+ if (umb_encap(sc, m, xfer)) {
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ umb_txflush(sc);
+ break;
+ }
+
+ BPF_MTAP(ifp, m);
+
+ if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0);
+ usbd_transfer_submit(xfer);
+
+ break;
+
+ default:
+ umb_txflush(sc);
+
+ /* count output errors */
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ DPRINTF("tx error: %s\n",
+ usbd_errstr(status));
+
+ if (status != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+umb_txflush(struct umb_softc *sc)
+{
+ mtx_assert(&sc->sc_mutex, MA_OWNED);
+
+ if (sc->sc_tx_m != NULL) {
+ m_freem(sc->sc_tx_m);
+ sc->sc_tx_m = NULL;
+ }
+}
+
+static void
+umb_decap(struct umb_softc *sc, struct usb_xfer *xfer, int frame)
+{
+ if_t ifp = GET_IFP(sc);
+ char *buf;
+ int len;
+ char *dp;
+ struct ncm_header16 *hdr16;
+ struct ncm_header32 *hdr32;
+ struct ncm_pointer16 *ptr16;
+ struct ncm_pointer16_dgram *dgram16;
+ struct ncm_pointer32_dgram *dgram32;
+ uint32_t hsig, psig;
+ int hlen, blen;
+ int ptrlen, ptroff, dgentryoff;
+ uint32_t doff, dlen;
+ struct mbuf *m;
+
+ usbd_xfer_frame_data(xfer, frame, (void **)&buf, &len);
+ DPRINTFN(4, "recv %d bytes\n", len);
+ DDUMPN(5, buf, len);
+ if (len < sizeof (*hdr16))
+ goto toosmall;
+
+ hdr16 = (struct ncm_header16 *)buf;
+ hsig = UGETDW(hdr16->dwSignature);
+ hlen = UGETW(hdr16->wHeaderLength);
+ if (len < hlen)
+ goto toosmall;
+ if (len > sc->sc_rx_bufsz) {
+ DPRINTF("packet too large (%d)\n", len);
+ goto fail;
+ }
+ switch (hsig) {
+ case NCM_HDR16_SIG:
+ blen = UGETW(hdr16->wBlockLength);
+ ptroff = UGETW(hdr16->wNdpIndex);
+ if (hlen != sizeof (*hdr16)) {
+ DPRINTF("%s: bad header len %d for NTH16 (exp %zu)\n",
+ DEVNAM(sc), hlen, sizeof (*hdr16));
+ goto fail;
+ }
+ break;
+ case NCM_HDR32_SIG:
+ hdr32 = (struct ncm_header32 *)hdr16;
+ blen = UGETDW(hdr32->dwBlockLength);
+ ptroff = UGETDW(hdr32->dwNdpIndex);
+ if (hlen != sizeof (*hdr32)) {
+ DPRINTF("%s: bad header len %d for NTH32 (exp %zu)\n",
+ DEVNAM(sc), hlen, sizeof (*hdr32));
+ goto fail;
+ }
+ break;
+ default:
+ DPRINTF("%s: unsupported NCM header signature (0x%08x)\n",
+ DEVNAM(sc), hsig);
+ goto fail;
+ }
+ if (len < blen) {
+ DPRINTF("%s: bad NTB len (%d) for %d bytes of data\n",
+ DEVNAM(sc), blen, len);
+ goto fail;
+ }
+
+ if (len < ptroff)
+ goto toosmall;
+ ptr16 = (struct ncm_pointer16 *)(buf + ptroff);
+ psig = UGETDW(ptr16->dwSignature);
+ ptrlen = UGETW(ptr16->wLength);
+ if ((uint64_t)len < (uint64_t)ptrlen + (uint64_t)ptroff)
+ goto toosmall;
+ if (!MBIM_NCM_NTH16_ISISG(psig) && !MBIM_NCM_NTH32_ISISG(psig)) {
+ DPRINTF("%s: unsupported NCM pointer signature (0x%08x)\n",
+ DEVNAM(sc), psig);
+ goto fail;
+ }
+
+ switch (hsig) {
+ case NCM_HDR16_SIG:
+ dgentryoff = offsetof(struct ncm_pointer16, dgram);
+ break;
+ case NCM_HDR32_SIG:
+ dgentryoff = offsetof(struct ncm_pointer32, dgram);
+ break;
+ default:
+ goto fail;
+ }
+
+ while (dgentryoff < ptrlen) {
+ switch (hsig) {
+ case NCM_HDR16_SIG:
+ if (ptroff + dgentryoff < sizeof (*dgram16))
+ goto done;
+ dgram16 = (struct ncm_pointer16_dgram *)
+ (buf + ptroff + dgentryoff);
+ dgentryoff += sizeof (*dgram16);
+ dlen = UGETW(dgram16->wDatagramLen);
+ doff = UGETW(dgram16->wDatagramIndex);
+ break;
+ case NCM_HDR32_SIG:
+ if (ptroff + dgentryoff < sizeof (*dgram32))
+ goto done;
+ dgram32 = (struct ncm_pointer32_dgram *)
+ (buf + ptroff + dgentryoff);
+ dgentryoff += sizeof (*dgram32);
+ dlen = UGETDW(dgram32->dwDatagramLen);
+ doff = UGETDW(dgram32->dwDatagramIndex);
+ break;
+ default:
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto done;
+ }
+
+ /* Terminating zero entry */
+ if (dlen == 0 || doff == 0)
+ break;
+ if ((uint64_t)len < (uint64_t)dlen + (uint64_t)doff) {
+ /* Skip giant datagram but continue processing */
+ DPRINTF("%s: datagram too large (%d @ off %d)\n",
+ DEVNAM(sc), dlen, doff);
+ continue;
+ }
+
+ dp = buf + doff;
+ DPRINTFN(3, "%s: decap %d bytes\n", DEVNAM(sc), dlen);
+ m = m_devget(dp, dlen, 0, ifp, NULL);
+ if (m == NULL) {
+ if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
+ continue;
+ }
+
+ /* enqueue for later when the lock can be released */
+ _IF_ENQUEUE(&sc->sc_rx_queue, m);
+
+ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
+
+ }
+done:
+ sc->sc_rx_nerr = 0;
+ return;
+toosmall:
+ DPRINTF("%s: packet too small (%d)\n", DEVNAM(sc), len);
+fail:
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+}
+
+static usb_error_t
+umb_send_encap_command(struct umb_softc *sc, void *data, int len)
+{
+ usb_device_request_t req;
+
+ if (len > sc->sc_ctrl_len)
+ return USB_ERR_INVAL;
+
+ /* XXX FIXME: if (total len > sc->sc_ctrl_len) => must fragment */
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, sc->sc_ctrl_ifaceno);
+ USETW(req.wLength, len);
+ mtx_unlock(&sc->sc_mutex);
+ DELAY(umb_delay);
+ mtx_lock(&sc->sc_mutex);
+ return usbd_do_request_flags(sc->sc_udev, &sc->sc_mutex, &req, data, 0,
+ NULL, umb_xfer_tout);
+}
+
+static int
+umb_get_encap_response(struct umb_softc *sc, void *buf, int *len)
+{
+ usb_device_request_t req;
+ usb_error_t err;
+ uint16_t l = *len;
+
+ req.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, sc->sc_ctrl_ifaceno);
+ USETW(req.wLength, l);
+ /* XXX FIXME: re-assemble fragments */
+
+ mtx_unlock(&sc->sc_mutex);
+ DELAY(umb_delay);
+ mtx_lock(&sc->sc_mutex);
+ err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mutex, &req, buf,
+ USB_SHORT_XFER_OK, &l, umb_xfer_tout);
+ if (err == USB_ERR_NORMAL_COMPLETION) {
+ *len = l;
+ return 1;
+ }
+ DPRINTF("ctrl recv: %s\n", usbd_errstr(err));
+ return 0;
+}
+
+static void
+umb_ctrl_msg(struct umb_softc *sc, uint32_t req, void *data, int len)
+{
+ if_t ifp = GET_IFP(sc);
+ uint32_t tid;
+ struct mbim_msghdr *hdr = data;
+ usb_error_t err;
+
+ if (sc->sc_dying)
+ return;
+ if (len < sizeof (*hdr))
+ return;
+ tid = ++sc->sc_tid;
+
+ hdr->type = htole32(req);
+ hdr->len = htole32(len);
+ hdr->tid = htole32(tid);
+
+#ifdef UMB_DEBUG
+ if (umb_debug) {
+ const char *op, *str;
+ if (req == MBIM_COMMAND_MSG) {
+ struct mbim_h2f_cmd *c = data;
+ if (le32toh(c->op) == MBIM_CMDOP_SET)
+ op = "set";
+ else
+ op = "qry";
+ str = umb_cid2str(le32toh(c->cid));
+ } else {
+ op = "snd";
+ str = umb_request2str(req);
+ }
+ DPRINTF("-> %s %s (tid %u)\n", op, str, tid);
+ }
+#endif
+ err = umb_send_encap_command(sc, data, len);
+ if (err != USB_ERR_NORMAL_COMPLETION) {
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_ERR, "%s: send %s msg (tid %u) failed: %s\n",
+ DEVNAM(sc), umb_request2str(req), tid,
+ usbd_errstr(err));
+
+ /* will affect other transactions, too */
+ usbd_transfer_stop(sc->sc_xfer[UMB_INTR_RX]);
+ } else {
+ DPRINTFN(2, "sent %s (tid %u)\n",
+ umb_request2str(req), tid);
+ DDUMPN(3, data, len);
+ }
+ return;
+}
+
+static void
+umb_open(struct umb_softc *sc)
+{
+ struct mbim_h2f_openmsg msg;
+
+ memset(&msg, 0, sizeof (msg));
+ msg.maxlen = htole32(sc->sc_ctrl_len);
+ umb_ctrl_msg(sc, MBIM_OPEN_MSG, &msg, sizeof (msg));
+ return;
+}
+
+static void
+umb_close(struct umb_softc *sc)
+{
+ struct mbim_h2f_closemsg msg;
+
+ memset(&msg, 0, sizeof (msg));
+ umb_ctrl_msg(sc, MBIM_CLOSE_MSG, &msg, sizeof (msg));
+}
+
+static int
+umb_setpin(struct umb_softc *sc, int op, int is_puk, void *pin, int pinlen,
+ void *newpin, int newpinlen)
+{
+ struct mbim_cid_pin cp;
+ int off;
+
+ if (pinlen == 0)
+ return 0;
+ if (pinlen < 0 || pinlen > MBIM_PIN_MAXLEN ||
+ newpinlen < 0 || newpinlen > MBIM_PIN_MAXLEN ||
+ op < 0 || op > MBIM_PIN_OP_CHANGE ||
+ (is_puk && op != MBIM_PIN_OP_ENTER))
+ return EINVAL;
+
+ memset(&cp, 0, sizeof (cp));
+ cp.type = htole32(is_puk ? MBIM_PIN_TYPE_PUK1 : MBIM_PIN_TYPE_PIN1);
+
+ off = offsetof(struct mbim_cid_pin, data);
+ if (!umb_addstr(&cp, sizeof (cp), &off, pin, pinlen,
+ &cp.pin_offs, &cp.pin_size))
+ return EINVAL;
+
+ cp.op = htole32(op);
+ if (newpinlen) {
+ if (!umb_addstr(&cp, sizeof (cp), &off, newpin, newpinlen,
+ &cp.newpin_offs, &cp.newpin_size))
+ return EINVAL;
+ } else {
+ if ((op == MBIM_PIN_OP_CHANGE) || is_puk)
+ return EINVAL;
+ if (!umb_addstr(&cp, sizeof (cp), &off, NULL, 0,
+ &cp.newpin_offs, &cp.newpin_size))
+ return EINVAL;
+ }
+ mtx_lock(&sc->sc_mutex);
+ umb_cmd(sc, MBIM_CID_PIN, MBIM_CMDOP_SET, &cp, off);
+ mtx_unlock(&sc->sc_mutex);
+ return 0;
+}
+
+static void
+umb_setdataclass(struct umb_softc *sc)
+{
+ struct mbim_cid_registration_state rs;
+ uint32_t classes;
+
+ if (sc->sc_info.supportedclasses == MBIM_DATACLASS_NONE)
+ return;
+
+ memset(&rs, 0, sizeof (rs));
+ rs.regaction = htole32(MBIM_REGACTION_AUTOMATIC);
+ classes = sc->sc_info.supportedclasses;
+ if (sc->sc_info.preferredclasses != MBIM_DATACLASS_NONE)
+ classes &= sc->sc_info.preferredclasses;
+ rs.data_class = htole32(classes);
+ mtx_lock(&sc->sc_mutex);
+ umb_cmd(sc, MBIM_CID_REGISTER_STATE, MBIM_CMDOP_SET, &rs, sizeof (rs));
+ mtx_unlock(&sc->sc_mutex);
+}
+
+static void
+umb_radio(struct umb_softc *sc, int on)
+{
+ struct mbim_cid_radio_state s;
+
+ DPRINTF("set radio %s\n", on ? "on" : "off");
+ memset(&s, 0, sizeof (s));
+ s.state = htole32(on ? MBIM_RADIO_STATE_ON : MBIM_RADIO_STATE_OFF);
+ umb_cmd(sc, MBIM_CID_RADIO_STATE, MBIM_CMDOP_SET, &s, sizeof (s));
+}
+
+static void
+umb_allocate_cid(struct umb_softc *sc)
+{
+ umb_cmd1(sc, MBIM_CID_DEVICE_CAPS, MBIM_CMDOP_SET,
+ umb_qmi_alloc_cid, sizeof (umb_qmi_alloc_cid), umb_uuid_qmi_mbim);
+}
+
+static void
+umb_send_fcc_auth(struct umb_softc *sc)
+{
+ uint8_t fccauth[sizeof (umb_qmi_fcc_auth)];
+
+ if (sc->sc_cid == -1) {
+ DPRINTF("missing CID, cannot send FCC auth\n");
+ umb_allocate_cid(sc);
+ return;
+ }
+ memcpy(fccauth, umb_qmi_fcc_auth, sizeof (fccauth));
+ fccauth[UMB_QMI_CID_OFFS] = sc->sc_cid;
+ umb_cmd1(sc, MBIM_CID_DEVICE_CAPS, MBIM_CMDOP_SET,
+ fccauth, sizeof (fccauth), umb_uuid_qmi_mbim);
+}
+
+static void
+umb_packet_service(struct umb_softc *sc, int attach)
+{
+ struct mbim_cid_packet_service s;
+
+ DPRINTF("%s packet service\n",
+ attach ? "attach" : "detach");
+ memset(&s, 0, sizeof (s));
+ s.action = htole32(attach ?
+ MBIM_PKTSERVICE_ACTION_ATTACH : MBIM_PKTSERVICE_ACTION_DETACH);
+ umb_cmd(sc, MBIM_CID_PACKET_SERVICE, MBIM_CMDOP_SET, &s, sizeof (s));
+}
+
+static void
+umb_connect(struct umb_softc *sc)
+{
+ if_t ifp = GET_IFP(sc);
+
+ if (sc->sc_info.regstate == MBIM_REGSTATE_ROAMING && !sc->sc_roaming) {
+ log(LOG_INFO, "%s: connection disabled in roaming network\n",
+ DEVNAM(sc));
+ return;
+ }
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_DEBUG, "%s: connecting ...\n", DEVNAM(sc));
+ umb_send_connect(sc, MBIM_CONNECT_ACTIVATE);
+}
+
+static void
+umb_disconnect(struct umb_softc *sc)
+{
+ if_t ifp = GET_IFP(sc);
+
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_DEBUG, "%s: disconnecting ...\n", DEVNAM(sc));
+ umb_send_connect(sc, MBIM_CONNECT_DEACTIVATE);
+}
+
+static void
+umb_send_connect(struct umb_softc *sc, int command)
+{
+ struct mbim_cid_connect *c;
+ int off;
+
+ /* Too large for the stack */
+ mtx_unlock(&sc->sc_mutex);
+ c = malloc(sizeof (*c), M_MBIM_CID_CONNECT, M_WAITOK | M_ZERO);
+ mtx_lock(&sc->sc_mutex);
+ c->sessionid = htole32(umb_session_id);
+ c->command = htole32(command);
+ off = offsetof(struct mbim_cid_connect, data);
+ if (!umb_addstr(c, sizeof (*c), &off, sc->sc_info.apn,
+ sc->sc_info.apnlen, &c->access_offs, &c->access_size))
+ goto done;
+ if (!umb_addstr(c, sizeof (*c), &off, sc->sc_info.username,
+ sc->sc_info.usernamelen, &c->user_offs, &c->user_size))
+ goto done;
+ if (!umb_addstr(c, sizeof (*c), &off, sc->sc_info.password,
+ sc->sc_info.passwordlen, &c->passwd_offs, &c->passwd_size))
+ goto done;
+ c->authprot = htole32(MBIM_AUTHPROT_NONE);
+ c->compression = htole32(MBIM_COMPRESSION_NONE);
+ c->iptype = htole32(MBIM_CONTEXT_IPTYPE_IPV4);
+ memcpy(c->context, umb_uuid_context_internet, sizeof (c->context));
+ umb_cmd(sc, MBIM_CID_CONNECT, MBIM_CMDOP_SET, c, off);
+done:
+ free(c, M_MBIM_CID_CONNECT);
+ return;
+}
+
+static void
+umb_qry_ipconfig(struct umb_softc *sc)
+{
+ struct mbim_cid_ip_configuration_info ipc;
+
+ memset(&ipc, 0, sizeof (ipc));
+ ipc.sessionid = htole32(umb_session_id);
+ umb_cmd(sc, MBIM_CID_IP_CONFIGURATION, MBIM_CMDOP_QRY,
+ &ipc, sizeof (ipc));
+}
+
+static void
+umb_cmd(struct umb_softc *sc, int cid, int op, const void *data, int len)
+{
+ umb_cmd1(sc, cid, op, data, len, umb_uuid_basic_connect);
+}
+
+static void
+umb_cmd1(struct umb_softc *sc, int cid, int op, const void *data, int len,
+ uint8_t *uuid)
+{
+ struct mbim_h2f_cmd *cmd;
+ int totlen;
+
+ /* XXX FIXME support sending fragments */
+ if (sizeof (*cmd) + len > sc->sc_ctrl_len) {
+ DPRINTF("set %s msg too long: cannot send\n",
+ umb_cid2str(cid));
+ return;
+ }
+ cmd = sc->sc_ctrl_msg;
+ memset(cmd, 0, sizeof (*cmd));
+ cmd->frag.nfrag = htole32(1);
+ memcpy(cmd->devid, uuid, sizeof (cmd->devid));
+ cmd->cid = htole32(cid);
+ cmd->op = htole32(op);
+ cmd->infolen = htole32(len);
+ totlen = sizeof (*cmd);
+ if (len > 0) {
+ memcpy(cmd + 1, data, len);
+ totlen += len;
+ }
+ umb_ctrl_msg(sc, MBIM_COMMAND_MSG, cmd, totlen);
+}
+
+static void
+umb_command_done(struct umb_softc *sc, void *data, int len)
+{
+ struct mbim_f2h_cmddone *cmd = data;
+ if_t ifp = GET_IFP(sc);
+ uint32_t status;
+ uint32_t cid;
+ uint32_t infolen;
+ int qmimsg = 0;
+
+ if (len < sizeof (*cmd)) {
+ DPRINTF("discard short %s message\n",
+ umb_request2str(le32toh(cmd->hdr.type)));
+ return;
+ }
+ cid = le32toh(cmd->cid);
+ if (memcmp(cmd->devid, umb_uuid_basic_connect, sizeof (cmd->devid))) {
+ if (memcmp(cmd->devid, umb_uuid_qmi_mbim,
+ sizeof (cmd->devid))) {
+ DPRINTF("discard %s message for other UUID '%s'\n",
+ umb_request2str(le32toh(cmd->hdr.type)),
+ umb_uuid2str(cmd->devid));
+ return;
+ } else
+ qmimsg = 1;
+ }
+
+ status = le32toh(cmd->status);
+ switch (status) {
+ case MBIM_STATUS_SUCCESS:
+ break;
+ case MBIM_STATUS_NOT_INITIALIZED:
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_ERR, "%s: SIM not initialized (PIN missing)\n",
+ DEVNAM(sc));
+ return;
+ case MBIM_STATUS_PIN_REQUIRED:
+ sc->sc_info.pin_state = UMB_PIN_REQUIRED;
+ /*FALLTHROUGH*/
+ default:
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_ERR, "%s: set/qry %s failed: %s\n", DEVNAM(sc),
+ umb_cid2str(cid), umb_status2str(status));
+ return;
+ }
+
+ infolen = le32toh(cmd->infolen);
+ if (len < sizeof (*cmd) + infolen) {
+ DPRINTF("discard truncated %s message (want %d, got %d)\n",
+ umb_cid2str(cid),
+ (int)sizeof (*cmd) + infolen, len);
+ return;
+ }
+ if (qmimsg) {
+ if (sc->sc_flags & UMBFLG_FCC_AUTH_REQUIRED)
+ umb_decode_qmi(sc, cmd->info, infolen);
+ } else {
+ DPRINTFN(2, "set/qry %s done\n",
+ umb_cid2str(cid));
+ umb_decode_cid(sc, cid, cmd->info, infolen);
+ }
+}
+
+static void
+umb_decode_cid(struct umb_softc *sc, uint32_t cid, void *data, int len)
+{
+ int ok = 1;
+
+ switch (cid) {
+ case MBIM_CID_DEVICE_CAPS:
+ ok = umb_decode_devices_caps(sc, data, len);
+ break;
+ case MBIM_CID_SUBSCRIBER_READY_STATUS:
+ ok = umb_decode_subscriber_status(sc, data, len);
+ break;
+ case MBIM_CID_RADIO_STATE:
+ ok = umb_decode_radio_state(sc, data, len);
+ break;
+ case MBIM_CID_PIN:
+ ok = umb_decode_pin(sc, data, len);
+ break;
+ case MBIM_CID_REGISTER_STATE:
+ ok = umb_decode_register_state(sc, data, len);
+ break;
+ case MBIM_CID_PACKET_SERVICE:
+ ok = umb_decode_packet_service(sc, data, len);
+ break;
+ case MBIM_CID_SIGNAL_STATE:
+ ok = umb_decode_signal_state(sc, data, len);
+ break;
+ case MBIM_CID_CONNECT:
+ ok = umb_decode_connect_info(sc, data, len);
+ break;
+ case MBIM_CID_IP_CONFIGURATION:
+ ok = umb_decode_ip_configuration(sc, data, len);
+ break;
+ default:
+ /*
+ * Note: the above list is incomplete and only contains
+ * mandatory CIDs from the BASIC_CONNECT set.
+ * So alternate values are not unusual.
+ */
+ DPRINTFN(4, "ignore %s\n", umb_cid2str(cid));
+ break;
+ }
+ if (!ok)
+ DPRINTF("discard %s with bad info length %d\n",
+ umb_cid2str(cid), len);
+ return;
+}
+
+static void
+umb_decode_qmi(struct umb_softc *sc, uint8_t *data, int len)
+{
+ uint8_t srv;
+ uint16_t msg, tlvlen;
+ uint32_t val;
+
+#define UMB_QMI_QMUXLEN 6
+ if (len < UMB_QMI_QMUXLEN)
+ goto tooshort;
+
+ srv = data[4];
+ data += UMB_QMI_QMUXLEN;
+ len -= UMB_QMI_QMUXLEN;
+
+#define UMB_GET16(p) ((uint16_t)*p | (uint16_t)*(p + 1) << 8)
+#define UMB_GET32(p) ((uint32_t)*p | (uint32_t)*(p + 1) << 8 | \
+ (uint32_t)*(p + 2) << 16 |(uint32_t)*(p + 3) << 24)
+ switch (srv) {
+ case 0: /* ctl */
+#define UMB_QMI_CTLLEN 6
+ if (len < UMB_QMI_CTLLEN)
+ goto tooshort;
+ msg = UMB_GET16(&data[2]);
+ tlvlen = UMB_GET16(&data[4]);
+ data += UMB_QMI_CTLLEN;
+ len -= UMB_QMI_CTLLEN;
+ break;
+ case 2: /* dms */
+#define UMB_QMI_DMSLEN 7
+ if (len < UMB_QMI_DMSLEN)
+ goto tooshort;
+ msg = UMB_GET16(&data[3]);
+ tlvlen = UMB_GET16(&data[5]);
+ data += UMB_QMI_DMSLEN;
+ len -= UMB_QMI_DMSLEN;
+ break;
+ default:
+ DPRINTF("discard QMI message for unknown service type %d\n",
+ srv);
+ return;
+ }
+
+ if (len < tlvlen)
+ goto tooshort;
+
+#define UMB_QMI_TLVLEN 3
+ while (len > 0) {
+ if (len < UMB_QMI_TLVLEN)
+ goto tooshort;
+ tlvlen = UMB_GET16(&data[1]);
+ if (len < UMB_QMI_TLVLEN + tlvlen)
+ goto tooshort;
+ switch (data[0]) {
+ case 1: /* allocation info */
+ if (msg == 0x0022) { /* Allocate CID */
+ if (tlvlen != 2 || data[3] != 2) /* dms */
+ break;
+ sc->sc_cid = data[4];
+ DPRINTF("QMI CID %d allocated\n",
+ sc->sc_cid);
+ umb_newstate(sc, UMB_S_CID, UMB_NS_DONT_DROP);
+ }
+ break;
+ case 2: /* response */
+ if (tlvlen != sizeof (val))
+ break;
+ val = UMB_GET32(&data[3]);
+ switch (msg) {
+ case 0x0022: /* Allocate CID */
+ if (val != 0) {
+ log(LOG_ERR, "%s: allocation of QMI CID"
+ " failed, error 0x%x\n", DEVNAM(sc),
+ val);
+ /* XXX how to proceed? */
+ return;
+ }
+ break;
+ case 0x555f: /* Send FCC Authentication */
+ if (val == 0)
+ DPRINTF("%s: send FCC "
+ "Authentication succeeded\n",
+ DEVNAM(sc));
+ else if (val == 0x001a0001)
+ DPRINTF("%s: FCC Authentication "
+ "not required\n", DEVNAM(sc));
+ else
+ log(LOG_INFO, "%s: send FCC "
+ "Authentication failed, "
+ "error 0x%x\n", DEVNAM(sc), val);
+
+ /* FCC Auth is needed only once after power-on*/
+ sc->sc_flags &= ~UMBFLG_FCC_AUTH_REQUIRED;
+
+ /* Try to proceed anyway */
+ DPRINTF("init: turning radio on ...\n");
+ umb_radio(sc, 1);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ data += UMB_QMI_TLVLEN + tlvlen;
+ len -= UMB_QMI_TLVLEN + tlvlen;
+ }
+ return;
+
+tooshort:
+ DPRINTF("discard short QMI message\n");
+ return;
+}
+
+static void
+umb_intr(struct usb_xfer *xfer, usb_error_t status)
+{
+ struct umb_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_cdc_notification notification;
+ struct usb_page_cache *pc;
+ if_t ifp = GET_IFP(sc);
+ int total_len;
+
+ mtx_assert(&sc->sc_mutex, MA_OWNED);
+
+ /* FIXME use actlen or total_len? */
+ usbd_xfer_status(xfer, &total_len, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTF("Received %d bytes\n", total_len);
+
+ if (total_len < UCDC_NOTIFICATION_LENGTH) {
+ DPRINTF("short notification (%d<%d)\n",
+ total_len, UCDC_NOTIFICATION_LENGTH);
+ return;
+ }
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &notification, sizeof (notification));
+
+ if (notification.bmRequestType != UCDC_NOTIFICATION) {
+ DPRINTF("unexpected notification (type=0x%02x)\n",
+ notification.bmRequestType);
+ return;
+ }
+
+ switch (notification.bNotification) {
+ case UCDC_N_NETWORK_CONNECTION:
+ if (if_getflags(ifp) & IFF_DEBUG)
+ log(LOG_DEBUG, "%s: network %sconnected\n",
+ DEVNAM(sc),
+ UGETW(notification.wValue)
+ ? "" : "dis");
+ break;
+ case UCDC_N_RESPONSE_AVAILABLE:
+ DPRINTFN(2, "umb_intr: response available\n");
+ ++sc->sc_nresp;
+ umb_add_task(sc, umb_get_response_task,
+ &sc->sc_proc_get_response_task[0].hdr,
+ &sc->sc_proc_get_response_task[1].hdr,
+ 0);
+ break;
+ case UCDC_N_CONNECTION_SPEED_CHANGE:
+ DPRINTFN(2, "umb_intr: connection speed changed\n");
+ break;
+ default:
+ DPRINTF("unexpected notification (0x%02x)\n",
+ notification.bNotification);
+ break;
+ }
+ /* fallthrough */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ if (status != USB_ERR_CANCELLED) {
+ /* start clear stall */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+/*
+ * Diagnostic routines
+ */
+static char *
+umb_ntop(struct sockaddr *sa)
+{
+#define NUMBUFS 4
+ static char astr[NUMBUFS][INET_ADDRSTRLEN];
+ static unsigned nbuf = 0;
+ char *s;
+
+ s = astr[nbuf++];
+ if (nbuf >= NUMBUFS)
+ nbuf = 0;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ default:
+ inet_ntop(AF_INET, &satosin(sa)->sin_addr, s, sizeof (astr[0]));
+ break;
+ case AF_INET6:
+ inet_ntop(AF_INET6, &satosin6(sa)->sin6_addr, s,
+ sizeof (astr[0]));
+ break;
+ }
+ return s;
+}
+
+#ifdef UMB_DEBUG
+static char *
+umb_uuid2str(uint8_t uuid[MBIM_UUID_LEN])
+{
+ static char uuidstr[2 * MBIM_UUID_LEN + 5];
+
+#define UUID_BFMT "%02X"
+#define UUID_SEP "-"
+ snprintf(uuidstr, sizeof (uuidstr),
+ UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT UUID_SEP
+ UUID_BFMT UUID_BFMT UUID_SEP
+ UUID_BFMT UUID_BFMT UUID_SEP
+ UUID_BFMT UUID_BFMT UUID_SEP
+ UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT UUID_BFMT,
+ uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5],
+ uuid[6], uuid[7], uuid[8], uuid[9], uuid[10], uuid[11],
+ uuid[12], uuid[13], uuid[14], uuid[15]);
+ return uuidstr;
+}
+
+static void
+umb_dump(void *buf, int len)
+{
+ int i = 0;
+ uint8_t *c = buf;
+
+ if (len == 0)
+ return;
+ while (i < len) {
+ if ((i % 16) == 0) {
+ if (i > 0)
+ log(LOG_DEBUG, "\n");
+ log(LOG_DEBUG, "%4d: ", i);
+ }
+ log(LOG_DEBUG, " %02x", *c);
+ c++;
+ i++;
+ }
+ log(LOG_DEBUG, "\n");
+}
+#endif /* UMB_DEBUG */
+
+DRIVER_MODULE(umb, uhub, umb_driver, NULL, NULL);
+MODULE_DEPEND(umb, usb, 1, 1, 1);
diff --git a/sys/dev/usb/net/if_umbreg.h b/sys/dev/usb/net/if_umbreg.h
new file mode 100644
index 000000000000..1f3a4aff5d54
--- /dev/null
+++ b/sys/dev/usb/net/if_umbreg.h
@@ -0,0 +1,443 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Original copyright (c) 2016 genua mbH (OpenBSD version)
+ *
+ * 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.
+ *
+ * Copyright (c) 2022 ADISTA SAS (re-write for FreeBSD)
+ *
+ * Re-write for FreeBSD by Pierre Pronchery <pierre@defora.net>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the copyright holder 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
+ *
+ * $OpenBSD: if_umb.h,v 1.4 2017/04/18 13:27:55 gerhard Exp $
+ */
+
+/*
+ * Mobile Broadband Interface Model
+ * http://www.usb.org/developers/docs/devclass_docs/MBIM-Compliance-1.0.pdf
+ */
+
+struct umb_valdescr {
+ int val;
+ char const *descr;
+};
+
+static const char *
+umb_val2descr(const struct umb_valdescr *vdp, int val)
+{
+ static char sval[32];
+
+ while (vdp->descr != NULL) {
+ if (vdp->val == val)
+ return vdp->descr;
+ vdp++;
+ }
+ snprintf(sval, sizeof (sval), "#%d", val);
+ return sval;
+}
+
+#define MBIM_REGSTATE_DESCRIPTIONS { \
+ { MBIM_REGSTATE_UNKNOWN, "unknown" }, \
+ { MBIM_REGSTATE_DEREGISTERED, "not registered" }, \
+ { MBIM_REGSTATE_SEARCHING, "searching" }, \
+ { MBIM_REGSTATE_HOME, "home network" }, \
+ { MBIM_REGSTATE_ROAMING, "roaming network" }, \
+ { MBIM_REGSTATE_PARTNER, "partner network" }, \
+ { MBIM_REGSTATE_DENIED, "access denied" }, \
+ { 0, NULL } }
+
+#define MBIM_DATACLASS_DESCRIPTIONS { \
+ { MBIM_DATACLASS_NONE, "none" }, \
+ { MBIM_DATACLASS_GPRS, "GPRS" }, \
+ { MBIM_DATACLASS_EDGE, "EDGE" }, \
+ { MBIM_DATACLASS_UMTS, "UMTS" }, \
+ { MBIM_DATACLASS_HSDPA, "HSDPA" }, \
+ { MBIM_DATACLASS_HSUPA, "HSUPA" }, \
+ { MBIM_DATACLASS_HSDPA|MBIM_DATACLASS_HSUPA, "HSPA" }, \
+ { MBIM_DATACLASS_LTE, "LTE" }, \
+ { MBIM_DATACLASS_1XRTT, "CDMA2000" }, \
+ { MBIM_DATACLASS_1XEVDO, "CDMA2000" }, \
+ { MBIM_DATACLASS_1XEVDO_REV_A, "CDMA2000" }, \
+ { MBIM_DATACLASS_1XEVDV, "CDMA2000" }, \
+ { MBIM_DATACLASS_3XRTT, "CDMA2000" }, \
+ { MBIM_DATACLASS_1XEVDO_REV_B, "CDMA2000" }, \
+ { MBIM_DATACLASS_UMB, "CDMA2000" }, \
+ { MBIM_DATACLASS_CUSTOM, "custom" }, \
+ { 0, NULL } }
+
+#define MBIM_1TO1_DESCRIPTION(m) { (m), #m }
+#define MBIM_MESSAGES_DESCRIPTIONS { \
+ MBIM_1TO1_DESCRIPTION(MBIM_OPEN_MSG), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CLOSE_MSG), \
+ MBIM_1TO1_DESCRIPTION(MBIM_COMMAND_MSG), \
+ MBIM_1TO1_DESCRIPTION(MBIM_HOST_ERROR_MSG), \
+ MBIM_1TO1_DESCRIPTION(MBIM_OPEN_DONE), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CLOSE_DONE), \
+ MBIM_1TO1_DESCRIPTION(MBIM_COMMAND_DONE), \
+ MBIM_1TO1_DESCRIPTION(MBIM_FUNCTION_ERROR_MSG), \
+ MBIM_1TO1_DESCRIPTION(MBIM_INDICATE_STATUS_MSG), \
+ { 0, NULL } }
+
+#define MBIM_STATUS_DESCRIPTION(m) { MBIM_STATUS_ ## m, #m }
+#define MBIM_STATUS_DESCRIPTIONS { \
+ MBIM_STATUS_DESCRIPTION(SUCCESS), \
+ MBIM_STATUS_DESCRIPTION(BUSY), \
+ MBIM_STATUS_DESCRIPTION(FAILURE), \
+ MBIM_STATUS_DESCRIPTION(SIM_NOT_INSERTED), \
+ MBIM_STATUS_DESCRIPTION(BAD_SIM), \
+ MBIM_STATUS_DESCRIPTION(PIN_REQUIRED), \
+ MBIM_STATUS_DESCRIPTION(PIN_DISABLED), \
+ MBIM_STATUS_DESCRIPTION(NOT_REGISTERED), \
+ MBIM_STATUS_DESCRIPTION(PROVIDERS_NOT_FOUND), \
+ MBIM_STATUS_DESCRIPTION(NO_DEVICE_SUPPORT), \
+ MBIM_STATUS_DESCRIPTION(PROVIDER_NOT_VISIBLE), \
+ MBIM_STATUS_DESCRIPTION(DATA_CLASS_NOT_AVAILABLE), \
+ MBIM_STATUS_DESCRIPTION(PACKET_SERVICE_DETACHED), \
+ MBIM_STATUS_DESCRIPTION(MAX_ACTIVATED_CONTEXTS), \
+ MBIM_STATUS_DESCRIPTION(NOT_INITIALIZED), \
+ MBIM_STATUS_DESCRIPTION(VOICE_CALL_IN_PROGRESS), \
+ MBIM_STATUS_DESCRIPTION(CONTEXT_NOT_ACTIVATED), \
+ MBIM_STATUS_DESCRIPTION(SERVICE_NOT_ACTIVATED), \
+ MBIM_STATUS_DESCRIPTION(INVALID_ACCESS_STRING), \
+ MBIM_STATUS_DESCRIPTION(INVALID_USER_NAME_PWD), \
+ MBIM_STATUS_DESCRIPTION(RADIO_POWER_OFF), \
+ MBIM_STATUS_DESCRIPTION(INVALID_PARAMETERS), \
+ MBIM_STATUS_DESCRIPTION(READ_FAILURE), \
+ MBIM_STATUS_DESCRIPTION(WRITE_FAILURE), \
+ MBIM_STATUS_DESCRIPTION(NO_PHONEBOOK), \
+ MBIM_STATUS_DESCRIPTION(PARAMETER_TOO_LONG), \
+ MBIM_STATUS_DESCRIPTION(STK_BUSY), \
+ MBIM_STATUS_DESCRIPTION(OPERATION_NOT_ALLOWED), \
+ MBIM_STATUS_DESCRIPTION(MEMORY_FAILURE), \
+ MBIM_STATUS_DESCRIPTION(INVALID_MEMORY_INDEX), \
+ MBIM_STATUS_DESCRIPTION(MEMORY_FULL), \
+ MBIM_STATUS_DESCRIPTION(FILTER_NOT_SUPPORTED), \
+ MBIM_STATUS_DESCRIPTION(DSS_INSTANCE_LIMIT), \
+ MBIM_STATUS_DESCRIPTION(INVALID_DEVICE_SERVICE_OPERATION), \
+ MBIM_STATUS_DESCRIPTION(AUTH_INCORRECT_AUTN), \
+ MBIM_STATUS_DESCRIPTION(AUTH_SYNC_FAILURE), \
+ MBIM_STATUS_DESCRIPTION(AUTH_AMF_NOT_SET), \
+ MBIM_STATUS_DESCRIPTION(CONTEXT_NOT_SUPPORTED), \
+ MBIM_STATUS_DESCRIPTION(SMS_UNKNOWN_SMSC_ADDRESS), \
+ MBIM_STATUS_DESCRIPTION(SMS_NETWORK_TIMEOUT), \
+ MBIM_STATUS_DESCRIPTION(SMS_LANG_NOT_SUPPORTED), \
+ MBIM_STATUS_DESCRIPTION(SMS_ENCODING_NOT_SUPPORTED), \
+ MBIM_STATUS_DESCRIPTION(SMS_FORMAT_NOT_SUPPORTED), \
+ { 0, NULL } }
+
+#define MBIM_ERROR_DESCRIPTION(m) { MBIM_ERROR_ ## m, #m }
+#define MBIM_ERROR_DESCRIPTIONS { \
+ MBIM_ERROR_DESCRIPTION(TIMEOUT_FRAGMENT), \
+ MBIM_ERROR_DESCRIPTION(FRAGMENT_OUT_OF_SEQUENCE), \
+ MBIM_ERROR_DESCRIPTION(LENGTH_MISMATCH), \
+ MBIM_ERROR_DESCRIPTION(DUPLICATED_TID), \
+ MBIM_ERROR_DESCRIPTION(NOT_OPENED), \
+ MBIM_ERROR_DESCRIPTION(UNKNOWN), \
+ MBIM_ERROR_DESCRIPTION(CANCEL), \
+ MBIM_ERROR_DESCRIPTION(MAX_TRANSFER), \
+ { 0, NULL } }
+
+#define MBIM_CID_DESCRIPTIONS { \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_DEVICE_CAPS), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_SUBSCRIBER_READY_STATUS), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_RADIO_STATE), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_PIN), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_PIN_LIST), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_HOME_PROVIDER), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_PREFERRED_PROVIDERS), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_VISIBLE_PROVIDERS), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_REGISTER_STATE), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_PACKET_SERVICE), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_SIGNAL_STATE), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_CONNECT), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_PROVISIONED_CONTEXTS), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_SERVICE_ACTIVATION), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_IP_CONFIGURATION), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_DEVICE_SERVICES), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_DEVICE_SERVICE_SUBSCRIBE_LIST), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_PACKET_STATISTICS), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_NETWORK_IDLE_HINT), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_EMERGENCY_MODE), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_IP_PACKET_FILTERS), \
+ MBIM_1TO1_DESCRIPTION(MBIM_CID_MULTICARRIER_PROVIDERS), \
+ { 0, NULL } }
+
+#define MBIM_SIMSTATE_DESCRIPTIONS { \
+ { MBIM_SIMSTATE_NOTINITIALIZED, "not initialized" }, \
+ { MBIM_SIMSTATE_INITIALIZED, "initialized" }, \
+ { MBIM_SIMSTATE_NOTINSERTED, "not inserted" }, \
+ { MBIM_SIMSTATE_BADSIM, "bad type" }, \
+ { MBIM_SIMSTATE_FAILURE, "failed" }, \
+ { MBIM_SIMSTATE_NOTACTIVATED, "not activated" }, \
+ { MBIM_SIMSTATE_LOCKED, "locked" }, \
+ { 0, NULL } }
+
+#define MBIM_PINTYPE_DESCRIPTIONS { \
+ { MBIM_PIN_TYPE_NONE, "none" }, \
+ { MBIM_PIN_TYPE_CUSTOM, "custom" }, \
+ { MBIM_PIN_TYPE_PIN1, "PIN1" }, \
+ { MBIM_PIN_TYPE_PIN2, "PIN2" }, \
+ { MBIM_PIN_TYPE_DEV_SIM_PIN, "device PIN" }, \
+ { MBIM_PIN_TYPE_DEV_FIRST_SIM_PIN, "device 1st PIN" }, \
+ { MBIM_PIN_TYPE_NETWORK_PIN, "network PIN" }, \
+ { MBIM_PIN_TYPE_NETWORK_SUBSET_PIN, "network subset PIN" }, \
+ { MBIM_PIN_TYPE_SERVICE_PROVIDER_PIN, "provider PIN" }, \
+ { MBIM_PIN_TYPE_CORPORATE_PIN, "corporate PIN" }, \
+ { MBIM_PIN_TYPE_SUBSIDY_LOCK, "subsidy lock" }, \
+ { MBIM_PIN_TYPE_PUK1, "PUK" }, \
+ { MBIM_PIN_TYPE_PUK2, "PUK2" }, \
+ { MBIM_PIN_TYPE_DEV_FIRST_SIM_PUK, "device 1st PUK" }, \
+ { MBIM_PIN_TYPE_NETWORK_PUK, "network PUK" }, \
+ { MBIM_PIN_TYPE_NETWORK_SUBSET_PUK, "network subset PUK" }, \
+ { MBIM_PIN_TYPE_SERVICE_PROVIDER_PUK, "provider PUK" }, \
+ { MBIM_PIN_TYPE_CORPORATE_PUK, "corporate PUK" }, \
+ { 0, NULL } }
+
+#define MBIM_PKTSRV_STATE_DESCRIPTIONS { \
+ { MBIM_PKTSERVICE_STATE_UNKNOWN, "unknown" }, \
+ { MBIM_PKTSERVICE_STATE_ATTACHING, "attaching" }, \
+ { MBIM_PKTSERVICE_STATE_ATTACHED, "attached" }, \
+ { MBIM_PKTSERVICE_STATE_DETACHING, "detaching" }, \
+ { MBIM_PKTSERVICE_STATE_DETACHED, "detached" }, \
+ { 0, NULL } }
+
+#define MBIM_ACTIVATION_STATE_DESCRIPTIONS { \
+ { MBIM_ACTIVATION_STATE_UNKNOWN, "unknown" }, \
+ { MBIM_ACTIVATION_STATE_ACTIVATED, "activated" }, \
+ { MBIM_ACTIVATION_STATE_ACTIVATING, "activating" }, \
+ { MBIM_ACTIVATION_STATE_DEACTIVATED, "deactivated" }, \
+ { MBIM_ACTIVATION_STATE_DEACTIVATING, "deactivating" }, \
+ { 0, NULL } }
+
+/*
+ * Driver internal state
+ */
+enum umb_state {
+ UMB_S_DOWN = 0, /* interface down */
+ UMB_S_OPEN, /* MBIM device has been opened */
+ UMB_S_CID, /* QMI client id allocated */
+ UMB_S_RADIO, /* radio is on */
+ UMB_S_SIMREADY, /* SIM is ready */
+ UMB_S_ATTACHED, /* packet service is attached */
+ UMB_S_CONNECTED, /* connected to provider */
+ UMB_S_UP, /* have IP configuration */
+};
+
+#define UMB_INTERNAL_STATE_DESCRIPTIONS { \
+ { UMB_S_DOWN, "down" }, \
+ { UMB_S_OPEN, "open" }, \
+ { UMB_S_CID, "CID allocated" }, \
+ { UMB_S_RADIO, "radio on" }, \
+ { UMB_S_SIMREADY, "SIM is ready" }, \
+ { UMB_S_ATTACHED, "attached" }, \
+ { UMB_S_CONNECTED, "connected" }, \
+ { UMB_S_UP, "up" }, \
+ { 0, NULL } }
+
+/*
+ * UMB parameters (SIOC[GS]UMBPARAM ioctls)
+ */
+struct umb_parameter {
+ int op;
+ int is_puk;
+ uint16_t pin[MBIM_PIN_MAXLEN];
+ int pinlen;
+
+ uint16_t newpin[MBIM_PIN_MAXLEN];
+ int newpinlen;
+
+#define UMB_APN_MAXLEN 100
+ uint16_t apn[UMB_APN_MAXLEN];
+ int apnlen;
+
+#define UMB_USERNAME_MAXLEN 205
+ uint16_t username[UMB_USERNAME_MAXLEN];
+ int usernamelen;
+
+#define UMB_PASSWORD_MAXLEN 205
+ uint16_t password[UMB_PASSWORD_MAXLEN];
+ int passwordlen;
+
+ int roaming;
+ uint32_t preferredclasses;
+};
+
+/*
+ * UMB device status info (SIOCGUMBINFO ioctl)
+ */
+struct umb_info {
+ enum umb_state state;
+ int enable_roaming;
+#define UMB_PIN_REQUIRED 0
+#define UMB_PIN_UNLOCKED 1
+#define UMB_PUK_REQUIRED 2
+ int pin_state;
+ int pin_attempts_left;
+ int activation;
+ int sim_state;
+ int regstate;
+ int regmode;
+ int nwerror;
+ int packetstate;
+ uint32_t supportedclasses; /* what the hw supports */
+ uint32_t preferredclasses; /* what the user prefers */
+ uint32_t highestclass; /* what the network offers */
+ uint32_t cellclass;
+#define UMB_PROVIDERNAME_MAXLEN 20
+ uint16_t provider[UMB_PROVIDERNAME_MAXLEN];
+#define UMB_PHONENR_MAXLEN 22
+ uint16_t pn[UMB_PHONENR_MAXLEN];
+#define UMB_SUBSCRIBERID_MAXLEN 15
+ uint16_t sid[UMB_SUBSCRIBERID_MAXLEN];
+#define UMB_ICCID_MAXLEN 20
+ uint16_t iccid[UMB_ICCID_MAXLEN];
+#define UMB_ROAMINGTEXT_MAXLEN 63
+ uint16_t roamingtxt[UMB_ROAMINGTEXT_MAXLEN];
+
+#define UMB_DEVID_MAXLEN 18
+ uint16_t devid[UMB_DEVID_MAXLEN];
+#define UMB_FWINFO_MAXLEN 30
+ uint16_t fwinfo[UMB_FWINFO_MAXLEN];
+#define UMB_HWINFO_MAXLEN 30
+ uint16_t hwinfo[UMB_HWINFO_MAXLEN];
+
+ uint16_t apn[UMB_APN_MAXLEN];
+ int apnlen;
+
+ uint16_t username[UMB_USERNAME_MAXLEN];
+ int usernamelen;
+
+ uint16_t password[UMB_PASSWORD_MAXLEN];
+ int passwordlen;
+
+#define UMB_VALUE_UNKNOWN -999
+ int rssi;
+#define UMB_BER_EXCELLENT 0
+#define UMB_BER_VERYGOOD 1
+#define UMB_BER_GOOD 2
+#define UMB_BER_OK 3
+#define UMB_BER_MEDIUM 4
+#define UMB_BER_BAD 5
+#define UMB_BER_VERYBAD 6
+#define UMB_BER_EXTREMELYBAD 7
+ int ber;
+
+ int hw_radio_on;
+ int sw_radio_on;
+
+ uint64_t uplink_speed;
+ uint64_t downlink_speed;
+
+#define UMB_MAX_DNSSRV 2
+ struct in_addr ipv4dns[UMB_MAX_DNSSRV];
+};
+
+#if !defined(ifr_mtu)
+#define ifr_mtu ifr_ifru.ifru_metric
+#endif
+
+#ifdef _KERNEL
+/*
+ * UMB device
+ */
+enum {
+ UMB_INTR_RX,
+ UMB_BULK_RX,
+ UMB_BULK_TX,
+ UMB_N_TRANSFER,
+};
+
+struct umb_task {
+ struct usb_proc_msg hdr;
+ struct umb_softc *sc;
+};
+
+struct umb_softc {
+ device_t sc_dev;
+ struct ifnet *sc_if;
+#define GET_IFP(sc) ((sc)->sc_if)
+ struct ifmedia sc_im;
+ struct usb_device *sc_udev;
+ struct usb_xfer *sc_xfer[UMB_N_TRANSFER];
+ uint8_t sc_ifaces_index[2];
+
+ int sc_ver_maj;
+ int sc_ver_min;
+ int sc_ctrl_len;
+ int sc_maxpktlen;
+ int sc_maxsessions;
+
+#define UMBFLG_FCC_AUTH_REQUIRED 0x0001
+#define UMBFLG_NO_INET6 0x0002
+ uint32_t sc_flags;
+ int sc_cid;
+
+ struct usb_process sc_taskqueue;
+ struct umb_task sc_proc_attach_task[2];
+ struct umb_task sc_proc_start_task[2];
+ struct umb_task sc_proc_state_task[2];
+ struct umb_task sc_proc_get_response_task[2];
+
+ int sc_nresp;
+ struct mtx sc_mutex;
+ struct usb_callout sc_statechg_timer;
+ char sc_dying;
+ char sc_attached;
+
+ uint8_t sc_ctrl_ifaceno;
+ struct usb_interface *sc_data_iface;
+
+ void *sc_resp_buf;
+ void *sc_ctrl_msg;
+
+ void *sc_rx_buf;
+ uint32_t sc_rx_bufsz;
+ unsigned sc_rx_nerr;
+ struct ifqueue sc_rx_queue;
+
+ void *sc_tx_buf;
+ struct mbuf *sc_tx_m;
+ uint32_t sc_tx_bufsz;
+ uint32_t sc_tx_seq;
+
+ uint32_t sc_tid;
+
+#define sc_state sc_info.state
+#define sc_roaming sc_info.enable_roaming
+ struct umb_info sc_info;
+};
+#endif /* _KERNEL */
diff --git a/sys/dev/usb/net/if_ure.c b/sys/dev/usb/net/if_ure.c
new file mode 100644
index 000000000000..c3f7b622d687
--- /dev/null
+++ b/sys/dev/usb/net/if_ure.c
@@ -0,0 +1,2244 @@
+/*-
+ * Copyright (c) 2015-2016 Kevin Lo <kevlo@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/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/sbuf.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <sys/unistd.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_media.h>
+
+/* needed for checksum offload */
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR ure_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/net/usb_ethernet.h>
+#include <dev/usb/net/if_urereg.h>
+
+#include "miibus_if.h"
+
+#include "opt_inet6.h"
+
+#ifdef USB_DEBUG
+static int ure_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ure, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB ure");
+SYSCTL_INT(_hw_usb_ure, OID_AUTO, debug, CTLFLAG_RWTUN, &ure_debug, 0,
+ "Debug level");
+#endif
+
+#ifdef USB_DEBUG_VAR
+#ifdef USB_DEBUG
+#define DEVPRINTFN(n,dev,fmt,...) do { \
+ if ((USB_DEBUG_VAR) >= (n)) { \
+ device_printf((dev), "%s: " fmt, \
+ __FUNCTION__ ,##__VA_ARGS__); \
+ } \
+} while (0)
+#define DEVPRINTF(...) DEVPRINTFN(1, __VA_ARGS__)
+#else
+#define DEVPRINTF(...) do { } while (0)
+#define DEVPRINTFN(...) do { } while (0)
+#endif
+#endif
+
+/*
+ * Various supported device vendors/products.
+ */
+static const STRUCT_USB_HOST_ID ure_devs[] = {
+#define URE_DEV(v,p,i) { \
+ USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i), \
+ USB_IFACE_CLASS(UICLASS_VENDOR), \
+ USB_IFACE_SUBCLASS(UISUBCLASS_VENDOR) }
+ URE_DEV(ELECOM, EDCQUA3C, 0),
+ URE_DEV(LENOVO, RTL8153, URE_FLAG_8153),
+ URE_DEV(LENOVO, TBT3LANGEN2, 0),
+ URE_DEV(LENOVO, ONELINK, 0),
+ URE_DEV(LENOVO, RTL8153_04, URE_FLAG_8153),
+ URE_DEV(LENOVO, ONELINKPLUS, URE_FLAG_8153),
+ URE_DEV(LENOVO, USBCLAN, 0),
+ URE_DEV(LENOVO, USBCLANGEN2, 0),
+ URE_DEV(LENOVO, USBCLANHYBRID, 0),
+ URE_DEV(MICROSOFT, WINDEVETH, 0),
+ URE_DEV(NVIDIA, RTL8153, URE_FLAG_8153),
+ URE_DEV(REALTEK, RTL8152, URE_FLAG_8152),
+ URE_DEV(REALTEK, RTL8153, URE_FLAG_8153),
+ URE_DEV(TPLINK, RTL8153, URE_FLAG_8153),
+ URE_DEV(REALTEK, RTL8156, URE_FLAG_8156),
+#undef URE_DEV
+};
+
+static device_probe_t ure_probe;
+static device_attach_t ure_attach;
+static device_detach_t ure_detach;
+
+static usb_callback_t ure_bulk_read_callback;
+static usb_callback_t ure_bulk_write_callback;
+
+static miibus_readreg_t ure_miibus_readreg;
+static miibus_writereg_t ure_miibus_writereg;
+static miibus_statchg_t ure_miibus_statchg;
+
+static uether_fn_t ure_attach_post;
+static uether_fn_t ure_init;
+static uether_fn_t ure_stop;
+static uether_fn_t ure_start;
+static uether_fn_t ure_tick;
+static uether_fn_t ure_rxfilter;
+
+static int ure_ctl(struct ure_softc *, uint8_t, uint16_t, uint16_t,
+ void *, int);
+static int ure_read_mem(struct ure_softc *, uint16_t, uint16_t, void *,
+ int);
+static int ure_write_mem(struct ure_softc *, uint16_t, uint16_t, void *,
+ int);
+static uint8_t ure_read_1(struct ure_softc *, uint16_t, uint16_t);
+static uint16_t ure_read_2(struct ure_softc *, uint16_t, uint16_t);
+static uint32_t ure_read_4(struct ure_softc *, uint16_t, uint16_t);
+static int ure_write_1(struct ure_softc *, uint16_t, uint16_t, uint32_t);
+static int ure_write_2(struct ure_softc *, uint16_t, uint16_t, uint32_t);
+static int ure_write_4(struct ure_softc *, uint16_t, uint16_t, uint32_t);
+static uint16_t ure_ocp_reg_read(struct ure_softc *, uint16_t);
+static void ure_ocp_reg_write(struct ure_softc *, uint16_t, uint16_t);
+static void ure_sram_write(struct ure_softc *, uint16_t, uint16_t);
+
+static int ure_sysctl_chipver(SYSCTL_HANDLER_ARGS);
+
+static void ure_read_chipver(struct ure_softc *);
+static int ure_attach_post_sub(struct usb_ether *);
+static void ure_reset(struct ure_softc *);
+static int ure_ifmedia_upd(if_t);
+static void ure_ifmedia_sts(if_t, struct ifmediareq *);
+static void ure_add_media_types(struct ure_softc *);
+static void ure_link_state(struct ure_softc *sc);
+static int ure_get_link_status(struct ure_softc *);
+static int ure_ioctl(if_t, u_long, caddr_t);
+static void ure_rtl8152_init(struct ure_softc *);
+static void ure_rtl8152_nic_reset(struct ure_softc *);
+static void ure_rtl8153_init(struct ure_softc *);
+static void ure_rtl8153b_init(struct ure_softc *);
+static void ure_rtl8153b_nic_reset(struct ure_softc *);
+static void ure_disable_teredo(struct ure_softc *);
+static void ure_enable_aldps(struct ure_softc *, bool);
+static uint16_t ure_phy_status(struct ure_softc *, uint16_t);
+static void ure_rxcsum(int capenb, struct ure_rxpkt *rp, struct mbuf *m);
+static int ure_txcsum(struct mbuf *m, int caps, uint32_t *regout);
+
+static device_method_t ure_methods[] = {
+ /* Device interface. */
+ DEVMETHOD(device_probe, ure_probe),
+ DEVMETHOD(device_attach, ure_attach),
+ DEVMETHOD(device_detach, ure_detach),
+
+ /* MII interface. */
+ DEVMETHOD(miibus_readreg, ure_miibus_readreg),
+ DEVMETHOD(miibus_writereg, ure_miibus_writereg),
+ DEVMETHOD(miibus_statchg, ure_miibus_statchg),
+
+ DEVMETHOD_END
+};
+
+static driver_t ure_driver = {
+ .name = "ure",
+ .methods = ure_methods,
+ .size = sizeof(struct ure_softc),
+};
+
+DRIVER_MODULE(ure, uhub, ure_driver, NULL, NULL);
+DRIVER_MODULE(miibus, ure, miibus_driver, NULL, NULL);
+MODULE_DEPEND(ure, uether, 1, 1, 1);
+MODULE_DEPEND(ure, usb, 1, 1, 1);
+MODULE_DEPEND(ure, ether, 1, 1, 1);
+MODULE_DEPEND(ure, miibus, 1, 1, 1);
+MODULE_VERSION(ure, 1);
+USB_PNP_HOST_INFO(ure_devs);
+
+static const struct usb_ether_methods ure_ue_methods = {
+ .ue_attach_post = ure_attach_post,
+ .ue_attach_post_sub = ure_attach_post_sub,
+ .ue_start = ure_start,
+ .ue_init = ure_init,
+ .ue_stop = ure_stop,
+ .ue_tick = ure_tick,
+ .ue_setmulti = ure_rxfilter,
+ .ue_setpromisc = ure_rxfilter,
+ .ue_mii_upd = ure_ifmedia_upd,
+ .ue_mii_sts = ure_ifmedia_sts,
+};
+
+#define URE_SETBIT_1(sc, reg, index, x) \
+ ure_write_1(sc, reg, index, ure_read_1(sc, reg, index) | (x))
+#define URE_SETBIT_2(sc, reg, index, x) \
+ ure_write_2(sc, reg, index, ure_read_2(sc, reg, index) | (x))
+#define URE_SETBIT_4(sc, reg, index, x) \
+ ure_write_4(sc, reg, index, ure_read_4(sc, reg, index) | (x))
+
+#define URE_CLRBIT_1(sc, reg, index, x) \
+ ure_write_1(sc, reg, index, ure_read_1(sc, reg, index) & ~(x))
+#define URE_CLRBIT_2(sc, reg, index, x) \
+ ure_write_2(sc, reg, index, ure_read_2(sc, reg, index) & ~(x))
+#define URE_CLRBIT_4(sc, reg, index, x) \
+ ure_write_4(sc, reg, index, ure_read_4(sc, reg, index) & ~(x))
+
+static int
+ure_ctl(struct ure_softc *sc, uint8_t rw, uint16_t val, uint16_t index,
+ void *buf, int len)
+{
+ struct usb_device_request req;
+
+ URE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (rw == URE_CTL_WRITE)
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ else
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = UR_SET_ADDRESS;
+ USETW(req.wValue, val);
+ USETW(req.wIndex, index);
+ USETW(req.wLength, len);
+
+ return (uether_do_request(&sc->sc_ue, &req, buf, 1000));
+}
+
+static int
+ure_read_mem(struct ure_softc *sc, uint16_t addr, uint16_t index,
+ void *buf, int len)
+{
+
+ return (ure_ctl(sc, URE_CTL_READ, addr, index, buf, len));
+}
+
+static int
+ure_write_mem(struct ure_softc *sc, uint16_t addr, uint16_t index,
+ void *buf, int len)
+{
+
+ return (ure_ctl(sc, URE_CTL_WRITE, addr, index, buf, len));
+}
+
+static uint8_t
+ure_read_1(struct ure_softc *sc, uint16_t reg, uint16_t index)
+{
+ uint32_t val;
+ uint8_t temp[4];
+ uint8_t shift;
+
+ shift = (reg & 3) << 3;
+ reg &= ~3;
+
+ ure_read_mem(sc, reg, index, &temp, 4);
+ val = UGETDW(temp);
+ val >>= shift;
+
+ return (val & 0xff);
+}
+
+static uint16_t
+ure_read_2(struct ure_softc *sc, uint16_t reg, uint16_t index)
+{
+ uint32_t val;
+ uint8_t temp[4];
+ uint8_t shift;
+
+ shift = (reg & 2) << 3;
+ reg &= ~3;
+
+ ure_read_mem(sc, reg, index, &temp, 4);
+ val = UGETDW(temp);
+ val >>= shift;
+
+ return (val & 0xffff);
+}
+
+static uint32_t
+ure_read_4(struct ure_softc *sc, uint16_t reg, uint16_t index)
+{
+ uint8_t temp[4];
+
+ ure_read_mem(sc, reg, index, &temp, 4);
+ return (UGETDW(temp));
+}
+
+static int
+ure_write_1(struct ure_softc *sc, uint16_t reg, uint16_t index, uint32_t val)
+{
+ uint16_t byen;
+ uint8_t temp[4];
+ uint8_t shift;
+
+ byen = URE_BYTE_EN_BYTE;
+ shift = reg & 3;
+ val &= 0xff;
+
+ if (reg & 3) {
+ byen <<= shift;
+ val <<= (shift << 3);
+ reg &= ~3;
+ }
+
+ USETDW(temp, val);
+ return (ure_write_mem(sc, reg, index | byen, &temp, 4));
+}
+
+static int
+ure_write_2(struct ure_softc *sc, uint16_t reg, uint16_t index, uint32_t val)
+{
+ uint16_t byen;
+ uint8_t temp[4];
+ uint8_t shift;
+
+ byen = URE_BYTE_EN_WORD;
+ shift = reg & 2;
+ val &= 0xffff;
+
+ if (reg & 2) {
+ byen <<= shift;
+ val <<= (shift << 3);
+ reg &= ~3;
+ }
+
+ USETDW(temp, val);
+ return (ure_write_mem(sc, reg, index | byen, &temp, 4));
+}
+
+static int
+ure_write_4(struct ure_softc *sc, uint16_t reg, uint16_t index, uint32_t val)
+{
+ uint8_t temp[4];
+
+ USETDW(temp, val);
+ return (ure_write_mem(sc, reg, index | URE_BYTE_EN_DWORD, &temp, 4));
+}
+
+static uint16_t
+ure_ocp_reg_read(struct ure_softc *sc, uint16_t addr)
+{
+ uint16_t reg;
+
+ ure_write_2(sc, URE_PLA_OCP_GPHY_BASE, URE_MCU_TYPE_PLA, addr & 0xf000);
+ reg = (addr & 0x0fff) | 0xb000;
+
+ return (ure_read_2(sc, reg, URE_MCU_TYPE_PLA));
+}
+
+static void
+ure_ocp_reg_write(struct ure_softc *sc, uint16_t addr, uint16_t data)
+{
+ uint16_t reg;
+
+ ure_write_2(sc, URE_PLA_OCP_GPHY_BASE, URE_MCU_TYPE_PLA, addr & 0xf000);
+ reg = (addr & 0x0fff) | 0xb000;
+
+ ure_write_2(sc, reg, URE_MCU_TYPE_PLA, data);
+}
+
+static void
+ure_sram_write(struct ure_softc *sc, uint16_t addr, uint16_t data)
+{
+ ure_ocp_reg_write(sc, URE_OCP_SRAM_ADDR, addr);
+ ure_ocp_reg_write(sc, URE_OCP_SRAM_DATA, data);
+}
+
+static int
+ure_miibus_readreg(device_t dev, int phy, int reg)
+{
+ struct ure_softc *sc;
+ uint16_t val;
+ int locked;
+
+ sc = device_get_softc(dev);
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ URE_LOCK(sc);
+
+ /* Let the rgephy driver read the URE_GMEDIASTAT register. */
+ if (reg == URE_GMEDIASTAT) {
+ if (!locked)
+ URE_UNLOCK(sc);
+ return (ure_read_1(sc, URE_GMEDIASTAT, URE_MCU_TYPE_PLA));
+ }
+
+ val = ure_ocp_reg_read(sc, URE_OCP_BASE_MII + reg * 2);
+
+ if (!locked)
+ URE_UNLOCK(sc);
+ return (val);
+}
+
+static int
+ure_miibus_writereg(device_t dev, int phy, int reg, int val)
+{
+ struct ure_softc *sc;
+ int locked;
+
+ sc = device_get_softc(dev);
+ if (sc->sc_phyno != phy)
+ return (0);
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ URE_LOCK(sc);
+
+ ure_ocp_reg_write(sc, URE_OCP_BASE_MII + reg * 2, val);
+
+ if (!locked)
+ URE_UNLOCK(sc);
+ return (0);
+}
+
+static void
+ure_miibus_statchg(device_t dev)
+{
+ struct ure_softc *sc;
+ struct mii_data *mii;
+ if_t ifp;
+ int locked;
+
+ sc = device_get_softc(dev);
+ mii = GET_MII(sc);
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ URE_LOCK(sc);
+
+ ifp = uether_getifp(&sc->sc_ue);
+ if (mii == NULL || ifp == NULL ||
+ (if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0)
+ goto done;
+
+ sc->sc_flags &= ~URE_FLAG_LINK;
+ if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) ==
+ (IFM_ACTIVE | IFM_AVALID)) {
+ switch (IFM_SUBTYPE(mii->mii_media_active)) {
+ case IFM_10_T:
+ case IFM_100_TX:
+ sc->sc_flags |= URE_FLAG_LINK;
+ sc->sc_rxstarted = 0;
+ break;
+ case IFM_1000_T:
+ if ((sc->sc_flags & URE_FLAG_8152) != 0)
+ break;
+ sc->sc_flags |= URE_FLAG_LINK;
+ sc->sc_rxstarted = 0;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* Lost link, do nothing. */
+ if ((sc->sc_flags & URE_FLAG_LINK) == 0)
+ goto done;
+done:
+ if (!locked)
+ URE_UNLOCK(sc);
+}
+
+/*
+ * Probe for a RTL8152/RTL8153/RTL8156 chip.
+ */
+static int
+ure_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa;
+
+ uaa = device_get_ivars(dev);
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != URE_IFACE_IDX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(ure_devs, sizeof(ure_devs), uaa));
+}
+
+/*
+ * Attach the interface. Allocate softc structures, do ifmedia
+ * setup and ethernet/BPF attach.
+ */
+static int
+ure_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct ure_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+ struct usb_config ure_config_rx[URE_MAX_RX];
+ struct usb_config ure_config_tx[URE_MAX_TX];
+ uint8_t iface_index;
+ int error;
+ int i;
+
+ sc->sc_flags = USB_GET_DRIVER_INFO(uaa);
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ iface_index = URE_IFACE_IDX;
+
+ if (sc->sc_flags & (URE_FLAG_8153 | URE_FLAG_8153B))
+ sc->sc_rxbufsz = URE_8153_RX_BUFSZ;
+ else if (sc->sc_flags & (URE_FLAG_8156 | URE_FLAG_8156B))
+ sc->sc_rxbufsz = URE_8156_RX_BUFSZ;
+ else
+ sc->sc_rxbufsz = URE_8152_RX_BUFSZ;
+
+ for (i = 0; i < URE_MAX_RX; i++) {
+ ure_config_rx[i] = (struct usb_config) {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = sc->sc_rxbufsz,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = ure_bulk_read_callback,
+ .timeout = 0, /* no timeout */
+ };
+ }
+ error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_rx_xfer,
+ ure_config_rx, URE_MAX_RX, sc, &sc->sc_mtx);
+ if (error != 0) {
+ device_printf(dev, "allocating USB RX transfers failed\n");
+ goto detach;
+ }
+
+ for (i = 0; i < URE_MAX_TX; i++) {
+ ure_config_tx[i] = (struct usb_config) {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = URE_TX_BUFSZ,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = ure_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ };
+ }
+ error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_tx_xfer,
+ ure_config_tx, URE_MAX_TX, sc, &sc->sc_mtx);
+ if (error != 0) {
+ usbd_transfer_unsetup(sc->sc_rx_xfer, URE_MAX_RX);
+ device_printf(dev, "allocating USB TX transfers failed\n");
+ goto detach;
+ }
+
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &ure_ue_methods;
+
+ error = uether_ifattach(ue);
+ if (error != 0) {
+ device_printf(dev, "could not attach interface\n");
+ goto detach;
+ }
+ return (0); /* success */
+
+detach:
+ ure_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+ure_detach(device_t dev)
+{
+ struct ure_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ usbd_transfer_unsetup(sc->sc_tx_xfer, URE_MAX_TX);
+ usbd_transfer_unsetup(sc->sc_rx_xfer, URE_MAX_RX);
+ uether_ifdetach(ue);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+/*
+ * Copy from USB buffers to a new mbuf chain with pkt header.
+ *
+ * This will use m_getm2 to get a mbuf chain w/ properly sized mbuf
+ * clusters as necessary.
+ */
+static struct mbuf *
+ure_makembuf(struct usb_page_cache *pc, usb_frlength_t offset,
+ usb_frlength_t len)
+{
+ struct usb_page_search_res;
+ struct mbuf *m, *mb;
+ usb_frlength_t tlen;
+
+ m = m_getm2(NULL, len + ETHER_ALIGN, M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (m == NULL)
+ return (m);
+
+ /* uether_newbuf does this. */
+ m_adj(m, ETHER_ALIGN);
+
+ m->m_pkthdr.len = len;
+
+ for (mb = m; len > 0; mb = mb->m_next) {
+ tlen = MIN(len, M_TRAILINGSPACE(mb));
+
+ usbd_copy_out(pc, offset, mtod(mb, uint8_t *), tlen);
+ mb->m_len = tlen;
+
+ offset += tlen;
+ len -= tlen;
+ }
+
+ return (m);
+}
+
+static void
+ure_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ure_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_ether *ue = &sc->sc_ue;
+ if_t ifp = uether_getifp(ue);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ struct ure_rxpkt pkt;
+ int actlen, off, len;
+ int caps;
+ uint32_t pktcsum;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ off = 0;
+ pc = usbd_xfer_get_frame(xfer, 0);
+ caps = if_getcapenable(ifp);
+ DEVPRINTFN(13, sc->sc_ue.ue_dev, "rcb start\n");
+ while (actlen > 0) {
+ if (actlen < (int)(sizeof(pkt))) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ usbd_copy_out(pc, off, &pkt, sizeof(pkt));
+
+ off += sizeof(pkt);
+ actlen -= sizeof(pkt);
+
+ len = le32toh(pkt.ure_pktlen) & URE_RXPKT_LEN_MASK;
+
+ DEVPRINTFN(13, sc->sc_ue.ue_dev,
+ "rxpkt: %#x, %#x, %#x, %#x, %#x, %#x\n",
+ pkt.ure_pktlen, pkt.ure_csum, pkt.ure_misc,
+ pkt.ure_rsvd2, pkt.ure_rsvd3, pkt.ure_rsvd4);
+ DEVPRINTFN(13, sc->sc_ue.ue_dev, "len: %d\n", len);
+
+ if (len >= URE_RXPKT_LEN_MASK) {
+ /*
+ * drop the rest of this segment. With out
+ * more information, we cannot know where next
+ * packet starts. Blindly continuing would
+ * cause a packet in packet attack, allowing
+ * one VLAN to inject packets w/o a VLAN tag,
+ * or injecting packets into other VLANs.
+ */
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+
+ if (actlen < len) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+
+ if (len >= (ETHER_HDR_LEN + ETHER_CRC_LEN))
+ m = ure_makembuf(pc, off, len - ETHER_CRC_LEN);
+ else
+ m = NULL;
+ if (m == NULL) {
+ if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
+ } else {
+ /* make mbuf and queue */
+ pktcsum = le32toh(pkt.ure_csum);
+ if (caps & IFCAP_VLAN_HWTAGGING &&
+ pktcsum & URE_RXPKT_RX_VLAN_TAG) {
+ m->m_pkthdr.ether_vtag =
+ bswap16(pktcsum &
+ URE_RXPKT_VLAN_MASK);
+ m->m_flags |= M_VLANTAG;
+ }
+
+ /* set the necessary flags for rx checksum */
+ ure_rxcsum(caps, &pkt, m);
+
+ /*
+ * len has been known to be bogus at times,
+ * which leads to problems when passed to
+ * uether_rxmbuf(). Better understanding why we
+ * can get there make for good future work.
+ */
+ uether_rxmbuf(ue, m, 0);
+ }
+
+ off += roundup(len, URE_RXPKT_ALIGN);
+ actlen -= roundup(len, URE_RXPKT_ALIGN);
+ }
+ DEVPRINTFN(13, sc->sc_ue.ue_dev, "rcb end\n");
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ uether_rxflush(ue);
+ return;
+
+ default: /* Error */
+ DPRINTF("bulk read error, %s\n",
+ usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+ure_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ure_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ struct ure_txpkt txpkt;
+ uint32_t regtmp;
+ int len, pos;
+ int rem;
+ int caps;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "transfer complete\n");
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ if ((sc->sc_flags & URE_FLAG_LINK) == 0) {
+ /* don't send anything if there is no link! */
+ break;
+ }
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ caps = if_getcapenable(ifp);
+
+ pos = 0;
+ rem = URE_TX_BUFSZ;
+ while (rem > sizeof(txpkt)) {
+ m = if_dequeue(ifp);
+ if (m == NULL)
+ break;
+
+ /*
+ * make sure we don't ever send too large of a
+ * packet
+ */
+ len = m->m_pkthdr.len;
+ if ((len & URE_TXPKT_LEN_MASK) != len) {
+ device_printf(sc->sc_ue.ue_dev,
+ "pkt len too large: %#x", len);
+pkterror:
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ m_freem(m);
+ continue;
+ }
+
+ if (sizeof(txpkt) +
+ roundup(len, URE_TXPKT_ALIGN) > rem) {
+ /* out of space */
+ if_sendq_prepend(ifp, m);
+ m = NULL;
+ break;
+ }
+
+ txpkt = (struct ure_txpkt){};
+ txpkt.ure_pktlen = htole32((len & URE_TXPKT_LEN_MASK) |
+ URE_TKPKT_TX_FS | URE_TKPKT_TX_LS);
+ if (m->m_flags & M_VLANTAG) {
+ txpkt.ure_csum = htole32(
+ bswap16(m->m_pkthdr.ether_vtag &
+ URE_TXPKT_VLAN_MASK) | URE_TXPKT_VLAN);
+ }
+ if (ure_txcsum(m, caps, &regtmp)) {
+ device_printf(sc->sc_ue.ue_dev,
+ "pkt l4 off too large");
+ goto pkterror;
+ }
+ txpkt.ure_csum |= htole32(regtmp);
+
+ DEVPRINTFN(13, sc->sc_ue.ue_dev,
+ "txpkt: mbflg: %#x, %#x, %#x\n",
+ m->m_pkthdr.csum_flags, le32toh(txpkt.ure_pktlen),
+ le32toh(txpkt.ure_csum));
+
+ usbd_copy_in(pc, pos, &txpkt, sizeof(txpkt));
+
+ pos += sizeof(txpkt);
+ rem -= sizeof(txpkt);
+
+ usbd_m_copy_in(pc, pos, m, 0, len);
+
+ pos += roundup(len, URE_TXPKT_ALIGN);
+ rem -= roundup(len, URE_TXPKT_ALIGN);
+
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /*
+ * If there's a BPF listener, bounce a copy
+ * of this frame to him.
+ */
+ BPF_MTAP(ifp, m);
+
+ m_freem(m);
+ }
+
+ /* no packets to send */
+ if (pos == 0)
+ break;
+
+ /* Set frame length. */
+ usbd_xfer_set_frame_len(xfer, 0, pos);
+
+ usbd_transfer_submit(xfer);
+
+ return;
+
+ default: /* Error */
+ DPRINTFN(11, "transfer error, %s\n",
+ usbd_errstr(error));
+
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+
+ if (error == USB_ERR_TIMEOUT) {
+ DEVPRINTFN(12, sc->sc_ue.ue_dev,
+ "pkt tx timeout\n");
+ }
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ }
+}
+
+static void
+ure_read_chipver(struct ure_softc *sc)
+{
+ uint16_t ver;
+
+ ver = ure_read_2(sc, URE_PLA_TCR1, URE_MCU_TYPE_PLA) & URE_VERSION_MASK;
+ sc->sc_ver = ver;
+ switch (ver) {
+ case 0x4c00:
+ sc->sc_chip |= URE_CHIP_VER_4C00;
+ sc->sc_flags = URE_FLAG_8152;
+ break;
+ case 0x4c10:
+ sc->sc_chip |= URE_CHIP_VER_4C10;
+ sc->sc_flags = URE_FLAG_8152;
+ break;
+ case 0x5c00:
+ sc->sc_chip |= URE_CHIP_VER_5C00;
+ sc->sc_flags = URE_FLAG_8153;
+ break;
+ case 0x5c10:
+ sc->sc_chip |= URE_CHIP_VER_5C10;
+ sc->sc_flags = URE_FLAG_8153;
+ break;
+ case 0x5c20:
+ sc->sc_chip |= URE_CHIP_VER_5C20;
+ sc->sc_flags = URE_FLAG_8153;
+ break;
+ case 0x5c30:
+ sc->sc_chip |= URE_CHIP_VER_5C30;
+ sc->sc_flags = URE_FLAG_8153;
+ break;
+ case 0x6000:
+ sc->sc_flags = URE_FLAG_8153B;
+ sc->sc_chip |= URE_CHIP_VER_6000;
+ break;
+ case 0x6010:
+ sc->sc_flags = URE_FLAG_8153B;
+ sc->sc_chip |= URE_CHIP_VER_6010;
+ break;
+ case 0x7020:
+ sc->sc_flags = URE_FLAG_8156;
+ sc->sc_chip |= URE_CHIP_VER_7020;
+ break;
+ case 0x7030:
+ sc->sc_flags = URE_FLAG_8156;
+ sc->sc_chip |= URE_CHIP_VER_7030;
+ break;
+ case 0x7400:
+ sc->sc_flags = URE_FLAG_8156B;
+ sc->sc_chip |= URE_CHIP_VER_7400;
+ break;
+ case 0x7410:
+ sc->sc_flags = URE_FLAG_8156B;
+ sc->sc_chip |= URE_CHIP_VER_7410;
+ break;
+ default:
+ device_printf(sc->sc_ue.ue_dev,
+ "unknown version 0x%04x\n", ver);
+ break;
+ }
+}
+
+static int
+ure_sysctl_chipver(SYSCTL_HANDLER_ARGS)
+{
+ struct sbuf sb;
+ struct ure_softc *sc = arg1;
+ int error;
+
+ sbuf_new_for_sysctl(&sb, NULL, 0, req);
+
+ sbuf_printf(&sb, "%04x", sc->sc_ver);
+
+ error = sbuf_finish(&sb);
+ sbuf_delete(&sb);
+
+ return (error);
+}
+
+static void
+ure_attach_post(struct usb_ether *ue)
+{
+ struct ure_softc *sc = uether_getsc(ue);
+
+ sc->sc_rxstarted = 0;
+ sc->sc_phyno = 0;
+
+ /* Determine the chip version. */
+ ure_read_chipver(sc);
+
+ /* Initialize controller and get station address. */
+ if (sc->sc_flags & URE_FLAG_8152)
+ ure_rtl8152_init(sc);
+ else if (sc->sc_flags & (URE_FLAG_8153B | URE_FLAG_8156 | URE_FLAG_8156B))
+ ure_rtl8153b_init(sc);
+ else
+ ure_rtl8153_init(sc);
+
+ if ((sc->sc_chip & URE_CHIP_VER_4C00) ||
+ (sc->sc_chip & URE_CHIP_VER_4C10))
+ ure_read_mem(sc, URE_PLA_IDR, URE_MCU_TYPE_PLA,
+ ue->ue_eaddr, 8);
+ else
+ ure_read_mem(sc, URE_PLA_BACKUP, URE_MCU_TYPE_PLA,
+ ue->ue_eaddr, 8);
+
+ if (ETHER_IS_ZERO(sc->sc_ue.ue_eaddr)) {
+ device_printf(sc->sc_ue.ue_dev, "MAC assigned randomly\n");
+ arc4rand(sc->sc_ue.ue_eaddr, ETHER_ADDR_LEN, 0);
+ sc->sc_ue.ue_eaddr[0] &= ~0x01; /* unicast */
+ sc->sc_ue.ue_eaddr[0] |= 0x02; /* locally administered */
+ }
+}
+
+static int
+ure_attach_post_sub(struct usb_ether *ue)
+{
+ struct sysctl_ctx_list *sctx;
+ struct sysctl_oid *soid;
+ struct ure_softc *sc;
+ if_t ifp;
+ int error;
+
+ sc = uether_getsc(ue);
+ ifp = ue->ue_ifp;
+ if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
+ if_setstartfn(ifp, uether_start);
+ if_setioctlfn(ifp, ure_ioctl);
+ if_setinitfn(ifp, uether_init);
+ /*
+ * Try to keep two transfers full at a time.
+ * ~(TRANSFER_SIZE / 80 bytes/pkt * 2 buffers in flight)
+ */
+ if_setsendqlen(ifp, 512);
+ if_setsendqready(ifp);
+
+ if_setcapabilitiesbit(ifp, IFCAP_VLAN_MTU, 0);
+ if_setcapabilitiesbit(ifp, IFCAP_VLAN_HWTAGGING, 0);
+ if_setcapabilitiesbit(ifp, IFCAP_VLAN_HWCSUM|IFCAP_HWCSUM, 0);
+ if_sethwassist(ifp, CSUM_IP|CSUM_IP_UDP|CSUM_IP_TCP);
+#ifdef INET6
+ if_setcapabilitiesbit(ifp, IFCAP_HWCSUM_IPV6, 0);
+#endif
+ if_setcapenable(ifp, if_getcapabilities(ifp));
+
+ if (sc->sc_flags & (URE_FLAG_8156 | URE_FLAG_8156B)) {
+ ifmedia_init(&sc->sc_ifmedia, IFM_IMASK, ure_ifmedia_upd,
+ ure_ifmedia_sts);
+ ure_add_media_types(sc);
+ ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_AUTO, 0, NULL);
+ ifmedia_set(&sc->sc_ifmedia, IFM_ETHER | IFM_AUTO);
+ sc->sc_ifmedia.ifm_media = IFM_ETHER | IFM_AUTO;
+ error = 0;
+ } else {
+ bus_topo_lock();
+ error = mii_attach(ue->ue_dev, &ue->ue_miibus, ifp,
+ uether_ifmedia_upd, ue->ue_methods->ue_mii_sts,
+ BMSR_DEFCAPMASK, sc->sc_phyno, MII_OFFSET_ANY, 0);
+ bus_topo_unlock();
+ }
+
+ sctx = device_get_sysctl_ctx(sc->sc_ue.ue_dev);
+ soid = device_get_sysctl_tree(sc->sc_ue.ue_dev);
+ SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "chipver",
+ CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, sc, 0,
+ ure_sysctl_chipver, "A",
+ "Return string with chip version.");
+
+ return (error);
+}
+
+static void
+ure_init(struct usb_ether *ue)
+{
+ struct ure_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ uint16_t cpcr;
+ uint32_t reg;
+
+ URE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) != 0)
+ return;
+
+ /* Cancel pending I/O. */
+ ure_stop(ue);
+
+ if (sc->sc_flags & (URE_FLAG_8153B | URE_FLAG_8156 | URE_FLAG_8156B))
+ ure_rtl8153b_nic_reset(sc);
+ else
+ ure_reset(sc);
+
+ /* Set MAC address. */
+ ure_write_1(sc, URE_PLA_CRWECR, URE_MCU_TYPE_PLA, URE_CRWECR_CONFIG);
+ ure_write_mem(sc, URE_PLA_IDR, URE_MCU_TYPE_PLA | URE_BYTE_EN_SIX_BYTES,
+ if_getlladdr(ifp), 8);
+ ure_write_1(sc, URE_PLA_CRWECR, URE_MCU_TYPE_PLA, URE_CRWECR_NORAML);
+
+ /* Set RX EARLY timeout and size */
+ if (sc->sc_flags & URE_FLAG_8153) {
+ switch (usbd_get_speed(sc->sc_ue.ue_udev)) {
+ case USB_SPEED_SUPER:
+ reg = URE_COALESCE_SUPER / 8;
+ break;
+ case USB_SPEED_HIGH:
+ reg = URE_COALESCE_HIGH / 8;
+ break;
+ default:
+ reg = URE_COALESCE_SLOW / 8;
+ break;
+ }
+ ure_write_2(sc, URE_USB_RX_EARLY_AGG, URE_MCU_TYPE_USB, reg);
+ reg = URE_8153_RX_BUFSZ - (URE_FRAMELEN(if_getmtu(ifp)) +
+ sizeof(struct ure_rxpkt) + URE_RXPKT_ALIGN);
+ ure_write_2(sc, URE_USB_RX_EARLY_SIZE, URE_MCU_TYPE_USB, reg / 4);
+ } else if (sc->sc_flags & URE_FLAG_8153B) {
+ ure_write_2(sc, URE_USB_RX_EARLY_AGG, URE_MCU_TYPE_USB, 158);
+ ure_write_2(sc, URE_USB_RX_EXTRA_AGG_TMR, URE_MCU_TYPE_USB, 1875);
+ reg = URE_8153_RX_BUFSZ - (URE_FRAMELEN(if_getmtu(ifp)) +
+ sizeof(struct ure_rxpkt) + URE_RXPKT_ALIGN);
+ ure_write_2(sc, URE_USB_RX_EARLY_SIZE, URE_MCU_TYPE_USB, reg / 8);
+ ure_write_1(sc, URE_USB_UPT_RXDMA_OWN, URE_MCU_TYPE_USB,
+ URE_OWN_UPDATE | URE_OWN_CLEAR);
+ } else if (sc->sc_flags & (URE_FLAG_8156 | URE_FLAG_8156B)) {
+ ure_write_2(sc, URE_USB_RX_EARLY_AGG, URE_MCU_TYPE_USB, 80);
+ ure_write_2(sc, URE_USB_RX_EXTRA_AGG_TMR, URE_MCU_TYPE_USB, 1875);
+ reg = URE_8156_RX_BUFSZ - (URE_FRAMELEN(if_getmtu(ifp)) +
+ sizeof(struct ure_rxpkt) + URE_RXPKT_ALIGN);
+ ure_write_2(sc, URE_USB_RX_EARLY_SIZE, URE_MCU_TYPE_USB, reg / 8);
+ ure_write_1(sc, URE_USB_UPT_RXDMA_OWN, URE_MCU_TYPE_USB,
+ URE_OWN_UPDATE | URE_OWN_CLEAR);
+ }
+
+ if (sc->sc_flags & URE_FLAG_8156B) {
+ URE_CLRBIT_2(sc, URE_USB_FW_TASK, URE_MCU_TYPE_USB, URE_FC_PATCH_TASK);
+ uether_pause(&sc->sc_ue, hz / 500);
+ URE_SETBIT_2(sc, URE_USB_FW_TASK, URE_MCU_TYPE_USB, URE_FC_PATCH_TASK);
+ }
+
+ /* Reset the packet filter. */
+ URE_CLRBIT_2(sc, URE_PLA_FMC, URE_MCU_TYPE_PLA, URE_FMC_FCR_MCU_EN);
+ URE_SETBIT_2(sc, URE_PLA_FMC, URE_MCU_TYPE_PLA, URE_FMC_FCR_MCU_EN);
+
+ /* Enable RX VLANs if enabled */
+ cpcr = ure_read_2(sc, URE_PLA_CPCR, URE_MCU_TYPE_PLA);
+ if (if_getcapenable(ifp) & IFCAP_VLAN_HWTAGGING) {
+ DEVPRINTFN(12, sc->sc_ue.ue_dev, "enabled hw vlan tag\n");
+ cpcr |= URE_CPCR_RX_VLAN;
+ } else {
+ DEVPRINTFN(12, sc->sc_ue.ue_dev, "disabled hw vlan tag\n");
+ cpcr &= ~URE_CPCR_RX_VLAN;
+ }
+ ure_write_2(sc, URE_PLA_CPCR, URE_MCU_TYPE_PLA, cpcr);
+
+ /* Enable transmit and receive. */
+ URE_SETBIT_1(sc, URE_PLA_CR, URE_MCU_TYPE_PLA, URE_CR_RE | URE_CR_TE);
+
+ URE_CLRBIT_2(sc, URE_PLA_MISC_1, URE_MCU_TYPE_PLA, URE_RXDY_GATED_EN);
+
+ /* Configure RX filters. */
+ ure_rxfilter(ue);
+
+ usbd_xfer_set_stall(sc->sc_tx_xfer[0]);
+
+ /* Indicate we are up and running. */
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+
+ /* Switch to selected media. */
+ ure_ifmedia_upd(ifp);
+}
+
+static void
+ure_tick(struct usb_ether *ue)
+{
+ struct ure_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ struct mii_data *mii;
+
+ URE_LOCK_ASSERT(sc, MA_OWNED);
+
+ (void)ifp;
+ for (int i = 0; i < URE_MAX_RX; i++)
+ DEVPRINTFN(13, sc->sc_ue.ue_dev,
+ "rx[%d] = %d\n", i, USB_GET_STATE(sc->sc_rx_xfer[i]));
+
+ for (int i = 0; i < URE_MAX_TX; i++)
+ DEVPRINTFN(13, sc->sc_ue.ue_dev,
+ "tx[%d] = %d\n", i, USB_GET_STATE(sc->sc_tx_xfer[i]));
+
+ if (sc->sc_flags & (URE_FLAG_8156 | URE_FLAG_8156B)) {
+ ure_link_state(sc);
+ } else {
+ mii = GET_MII(sc);
+ mii_tick(mii);
+ if ((sc->sc_flags & URE_FLAG_LINK) == 0
+ && mii->mii_media_status & IFM_ACTIVE &&
+ IFM_SUBTYPE(mii->mii_media_active) != IFM_NONE) {
+ sc->sc_flags |= URE_FLAG_LINK;
+ sc->sc_rxstarted = 0;
+ ure_start(ue);
+ }
+ }
+}
+
+static u_int
+ure_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
+{
+ uint32_t h, *hashes = arg;
+
+ h = ether_crc32_be(LLADDR(sdl), ETHER_ADDR_LEN) >> 26;
+ if (h < 32)
+ hashes[0] |= (1 << h);
+ else
+ hashes[1] |= (1 << (h - 32));
+ return (1);
+}
+
+/*
+ * Program the 64-bit multicast hash filter.
+ */
+static void
+ure_rxfilter(struct usb_ether *ue)
+{
+ struct ure_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+ uint32_t rxmode;
+ uint32_t h, hashes[2] = { 0, 0 };
+
+ URE_LOCK_ASSERT(sc, MA_OWNED);
+
+ rxmode = ure_read_4(sc, URE_PLA_RCR, URE_MCU_TYPE_PLA);
+ rxmode &= ~(URE_RCR_AAP | URE_RCR_AM);
+ rxmode |= URE_RCR_APM; /* accept physical match packets */
+ rxmode |= URE_RCR_AB; /* always accept broadcasts */
+ if (if_getflags(ifp) & (IFF_ALLMULTI | IFF_PROMISC)) {
+ if (if_getflags(ifp) & IFF_PROMISC)
+ rxmode |= URE_RCR_AAP;
+ rxmode |= URE_RCR_AM;
+ hashes[0] = hashes[1] = 0xffffffff;
+ goto done;
+ }
+
+ /* calculate multicast masks */
+ if_foreach_llmaddr(ifp, ure_hash_maddr, &hashes);
+
+ h = bswap32(hashes[0]);
+ hashes[0] = bswap32(hashes[1]);
+ hashes[1] = h;
+ rxmode |= URE_RCR_AM; /* accept multicast packets */
+
+done:
+ DEVPRINTFN(14, ue->ue_dev, "rxfilt: RCR: %#x\n",
+ ure_read_4(sc, URE_PLA_RCR, URE_MCU_TYPE_PLA));
+ ure_write_4(sc, URE_PLA_MAR0, URE_MCU_TYPE_PLA, hashes[0]);
+ ure_write_4(sc, URE_PLA_MAR4, URE_MCU_TYPE_PLA, hashes[1]);
+ ure_write_4(sc, URE_PLA_RCR, URE_MCU_TYPE_PLA, rxmode);
+}
+
+static void
+ure_start(struct usb_ether *ue)
+{
+ struct ure_softc *sc = uether_getsc(ue);
+ unsigned i;
+
+ URE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (!sc->sc_rxstarted) {
+ sc->sc_rxstarted = 1;
+ for (i = 0; i != URE_MAX_RX; i++)
+ usbd_transfer_start(sc->sc_rx_xfer[i]);
+ }
+
+ for (i = 0; i != URE_MAX_TX; i++)
+ usbd_transfer_start(sc->sc_tx_xfer[i]);
+}
+
+static void
+ure_reset(struct ure_softc *sc)
+{
+ int i;
+
+ ure_write_1(sc, URE_PLA_CR, URE_MCU_TYPE_PLA, URE_CR_RST);
+
+ for (i = 0; i < URE_TIMEOUT; i++) {
+ if (!(ure_read_1(sc, URE_PLA_CR, URE_MCU_TYPE_PLA) &
+ URE_CR_RST))
+ break;
+ uether_pause(&sc->sc_ue, hz / 100);
+ }
+ if (i == URE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev, "reset never completed\n");
+}
+
+/*
+ * Set media options.
+ */
+static int
+ure_ifmedia_upd(if_t ifp)
+{
+ struct ure_softc *sc = if_getsoftc(ifp);
+ struct ifmedia *ifm;
+ struct mii_data *mii;
+ struct mii_softc *miisc;
+ int gig;
+ int reg;
+ int anar;
+ int locked;
+ int error;
+
+ if (sc->sc_flags & (URE_FLAG_8156 | URE_FLAG_8156B)) {
+ ifm = &sc->sc_ifmedia;
+ if (IFM_TYPE(ifm->ifm_media) != IFM_ETHER)
+ return (EINVAL);
+
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ URE_LOCK(sc);
+ reg = ure_ocp_reg_read(sc, 0xa5d4);
+ reg &= ~URE_ADV_2500TFDX;
+
+ anar = gig = 0;
+ switch (IFM_SUBTYPE(ifm->ifm_media)) {
+ case IFM_AUTO:
+ anar |= ANAR_TX_FD | ANAR_TX | ANAR_10_FD | ANAR_10;
+ gig |= GTCR_ADV_1000TFDX | GTCR_ADV_1000THDX;
+ reg |= URE_ADV_2500TFDX;
+ break;
+ case IFM_2500_T:
+ anar |= ANAR_TX_FD | ANAR_TX | ANAR_10_FD | ANAR_10;
+ gig |= GTCR_ADV_1000TFDX | GTCR_ADV_1000THDX;
+ reg |= URE_ADV_2500TFDX;
+ if_setbaudrate(ifp, IF_Mbps(2500));
+ break;
+ case IFM_1000_T:
+ anar |= ANAR_TX_FD | ANAR_TX | ANAR_10_FD | ANAR_10;
+ gig |= GTCR_ADV_1000TFDX | GTCR_ADV_1000THDX;
+ if_setbaudrate(ifp, IF_Gbps(1));
+ break;
+ case IFM_100_TX:
+ anar |= ANAR_TX | ANAR_TX_FD;
+ if_setbaudrate(ifp, IF_Mbps(100));
+ break;
+ case IFM_10_T:
+ anar |= ANAR_10 | ANAR_10_FD;
+ if_setbaudrate(ifp, IF_Mbps(10));
+ break;
+ default:
+ device_printf(sc->sc_ue.ue_dev, "unsupported media type\n");
+ if (!locked)
+ URE_UNLOCK(sc);
+ return (EINVAL);
+ }
+
+ ure_ocp_reg_write(sc, URE_OCP_BASE_MII + MII_ANAR * 2,
+ anar | ANAR_PAUSE_ASYM | ANAR_FC);
+ ure_ocp_reg_write(sc, URE_OCP_BASE_MII + MII_100T2CR * 2, gig);
+ ure_ocp_reg_write(sc, 0xa5d4, reg);
+ ure_ocp_reg_write(sc, URE_OCP_BASE_MII + MII_BMCR,
+ BMCR_AUTOEN | BMCR_STARTNEG);
+ if (!locked)
+ URE_UNLOCK(sc);
+ return (0);
+ }
+
+ mii = GET_MII(sc);
+
+ URE_LOCK_ASSERT(sc, MA_OWNED);
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list)
+ PHY_RESET(miisc);
+ error = mii_mediachg(mii);
+ return (error);
+}
+
+/*
+ * Report current media status.
+ */
+static void
+ure_ifmedia_sts(if_t ifp, struct ifmediareq *ifmr)
+{
+ struct ure_softc *sc;
+ struct mii_data *mii;
+ uint16_t status;
+
+ sc = if_getsoftc(ifp);
+ if (sc->sc_flags & (URE_FLAG_8156 | URE_FLAG_8156B)) {
+ URE_LOCK(sc);
+ ifmr->ifm_status = IFM_AVALID;
+ if (ure_get_link_status(sc)) {
+ ifmr->ifm_status |= IFM_ACTIVE;
+ status = ure_read_2(sc, URE_PLA_PHYSTATUS,
+ URE_MCU_TYPE_PLA);
+ if ((status & URE_PHYSTATUS_FDX) ||
+ (status & URE_PHYSTATUS_2500MBPS))
+ ifmr->ifm_active |= IFM_FDX;
+ else
+ ifmr->ifm_active |= IFM_HDX;
+ if (status & URE_PHYSTATUS_10MBPS)
+ ifmr->ifm_active |= IFM_10_T;
+ else if (status & URE_PHYSTATUS_100MBPS)
+ ifmr->ifm_active |= IFM_100_TX;
+ else if (status & URE_PHYSTATUS_1000MBPS)
+ ifmr->ifm_active |= IFM_1000_T;
+ else if (status & URE_PHYSTATUS_2500MBPS)
+ ifmr->ifm_active |= IFM_2500_T;
+ }
+ URE_UNLOCK(sc);
+ return;
+ }
+
+ mii = GET_MII(sc);
+
+ URE_LOCK(sc);
+ mii_pollstat(mii);
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+ URE_UNLOCK(sc);
+}
+
+static void
+ure_add_media_types(struct ure_softc *sc)
+{
+ ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_10_T, 0, NULL);
+ ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_10_T | IFM_FDX, 0, NULL);
+ ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_100_TX, 0, NULL);
+ ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_100_TX | IFM_FDX, 0, NULL);
+ ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_1000_T | IFM_FDX, 0, NULL);
+ ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_2500_T | IFM_FDX, 0, NULL);
+}
+
+static void
+ure_link_state(struct ure_softc *sc)
+{
+ if_t ifp = uether_getifp(&sc->sc_ue);
+
+ if (ure_get_link_status(sc)) {
+ if (if_getlinkstate(ifp) != LINK_STATE_UP) {
+ if_link_state_change(ifp, LINK_STATE_UP);
+ /* Enable transmit and receive. */
+ URE_SETBIT_1(sc, URE_PLA_CR, URE_MCU_TYPE_PLA, URE_CR_RE | URE_CR_TE);
+
+ if (ure_read_2(sc, URE_PLA_PHYSTATUS, URE_MCU_TYPE_PLA) &
+ URE_PHYSTATUS_2500MBPS)
+ URE_CLRBIT_2(sc, URE_PLA_MAC_PWR_CTRL4, URE_MCU_TYPE_PLA, 0x40);
+ else
+ URE_SETBIT_2(sc, URE_PLA_MAC_PWR_CTRL4, URE_MCU_TYPE_PLA, 0x40);
+ }
+ } else {
+ if (if_getlinkstate(ifp) != LINK_STATE_DOWN) {
+ if_link_state_change(ifp, LINK_STATE_DOWN);
+ }
+ }
+}
+
+static int
+ure_get_link_status(struct ure_softc *sc)
+{
+ if (ure_read_2(sc, URE_PLA_PHYSTATUS, URE_MCU_TYPE_PLA) &
+ URE_PHYSTATUS_LINK) {
+ sc->sc_flags |= URE_FLAG_LINK;
+ return (1);
+ } else {
+ sc->sc_flags &= ~URE_FLAG_LINK;
+ return (0);
+ }
+}
+
+static int
+ure_ioctl(if_t ifp, u_long cmd, caddr_t data)
+{
+ struct usb_ether *ue = if_getsoftc(ifp);
+ struct ure_softc *sc;
+ struct ifreq *ifr;
+ int error, mask, reinit;
+
+ sc = uether_getsc(ue);
+ ifr = (struct ifreq *)data;
+ error = 0;
+ reinit = 0;
+ switch (cmd) {
+ case SIOCSIFCAP:
+ URE_LOCK(sc);
+ mask = ifr->ifr_reqcap ^ if_getcapenable(ifp);
+ if ((mask & IFCAP_VLAN_HWTAGGING) != 0 &&
+ (if_getcapabilities(ifp) & IFCAP_VLAN_HWTAGGING) != 0) {
+ if_togglecapenable(ifp, IFCAP_VLAN_HWTAGGING);
+ reinit++;
+ }
+ if ((mask & IFCAP_TXCSUM) != 0 &&
+ (if_getcapabilities(ifp) & IFCAP_TXCSUM) != 0) {
+ if_togglecapenable(ifp, IFCAP_TXCSUM);
+ }
+ if ((mask & IFCAP_RXCSUM) != 0 &&
+ (if_getcapabilities(ifp) & IFCAP_RXCSUM) != 0) {
+ if_togglecapenable(ifp, IFCAP_RXCSUM);
+ }
+ if ((mask & IFCAP_TXCSUM_IPV6) != 0 &&
+ (if_getcapabilities(ifp) & IFCAP_TXCSUM_IPV6) != 0) {
+ if_togglecapenable(ifp, IFCAP_TXCSUM_IPV6);
+ }
+ if ((mask & IFCAP_RXCSUM_IPV6) != 0 &&
+ (if_getcapabilities(ifp) & IFCAP_RXCSUM_IPV6) != 0) {
+ if_togglecapenable(ifp, IFCAP_RXCSUM_IPV6);
+ }
+ if (reinit > 0 && if_getdrvflags(ifp) & IFF_DRV_RUNNING)
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+ else
+ reinit = 0;
+ URE_UNLOCK(sc);
+ if (reinit > 0)
+ uether_init(ue);
+ break;
+
+ case SIOCSIFMTU:
+ /*
+ * in testing large MTUs "crashes" the device, it
+ * leaves the device w/ a broken state where link
+ * is in a bad state.
+ */
+ if (ifr->ifr_mtu < ETHERMIN ||
+ ifr->ifr_mtu > (4096 - ETHER_HDR_LEN -
+ ETHER_VLAN_ENCAP_LEN - ETHER_CRC_LEN)) {
+ error = EINVAL;
+ break;
+ }
+ URE_LOCK(sc);
+ if (if_getmtu(ifp) != ifr->ifr_mtu)
+ if_setmtu(ifp, ifr->ifr_mtu);
+ URE_UNLOCK(sc);
+ break;
+
+ case SIOCGIFMEDIA:
+ case SIOCSIFMEDIA:
+ if (sc->sc_flags & (URE_FLAG_8156 | URE_FLAG_8156B))
+ error = ifmedia_ioctl(ifp, ifr, &sc->sc_ifmedia, cmd);
+ else
+ error = uether_ioctl(ifp, cmd, data);
+ break;
+
+ default:
+ error = uether_ioctl(ifp, cmd, data);
+ break;
+ }
+
+ return (error);
+}
+
+static void
+ure_rtl8152_init(struct ure_softc *sc)
+{
+ uint32_t pwrctrl;
+
+ ure_enable_aldps(sc, false);
+
+ if (sc->sc_chip & URE_CHIP_VER_4C00) {
+ URE_CLRBIT_2(sc, URE_PLA_LED_FEATURE, URE_MCU_TYPE_PLA, URE_LED_MODE_MASK);
+ }
+
+ URE_CLRBIT_2(sc, URE_USB_UPS_CTRL, URE_MCU_TYPE_USB, URE_POWER_CUT);
+
+ URE_CLRBIT_2(sc, URE_USB_PM_CTRL_STATUS, URE_MCU_TYPE_USB, URE_RESUME_INDICATE);
+
+ URE_SETBIT_2(sc, URE_PLA_PHY_PWR, URE_MCU_TYPE_PLA, URE_TX_10M_IDLE_EN | URE_PFM_PWM_SWITCH);
+
+ pwrctrl = ure_read_4(sc, URE_PLA_MAC_PWR_CTRL, URE_MCU_TYPE_PLA);
+ pwrctrl &= ~URE_MCU_CLK_RATIO_MASK;
+ pwrctrl |= URE_MCU_CLK_RATIO | URE_D3_CLK_GATED_EN;
+ ure_write_4(sc, URE_PLA_MAC_PWR_CTRL, URE_MCU_TYPE_PLA, pwrctrl);
+ ure_write_2(sc, URE_PLA_GPHY_INTR_IMR, URE_MCU_TYPE_PLA,
+ URE_GPHY_STS_MSK | URE_SPEED_DOWN_MSK | URE_SPDWN_RXDV_MSK |
+ URE_SPDWN_LINKCHG_MSK);
+
+ /* Enable Rx aggregation. */
+ URE_CLRBIT_2(sc, URE_USB_USB_CTRL, URE_MCU_TYPE_USB, URE_RX_AGG_DISABLE | URE_RX_ZERO_EN);
+
+ ure_enable_aldps(sc, false);
+
+ ure_rtl8152_nic_reset(sc);
+
+ ure_write_1(sc, URE_USB_TX_AGG, URE_MCU_TYPE_USB,
+ URE_TX_AGG_MAX_THRESHOLD);
+ ure_write_4(sc, URE_USB_RX_BUF_TH, URE_MCU_TYPE_USB, URE_RX_THR_HIGH);
+ ure_write_4(sc, URE_USB_TX_DMA, URE_MCU_TYPE_USB,
+ URE_TEST_MODE_DISABLE | URE_TX_SIZE_ADJUST1);
+}
+
+static void
+ure_rtl8153_init(struct ure_softc *sc)
+{
+ uint16_t val;
+ uint8_t u1u2[8];
+ int i;
+
+ ure_enable_aldps(sc, false);
+
+ memset(u1u2, 0x00, sizeof(u1u2));
+ ure_write_mem(sc, URE_USB_TOLERANCE,
+ URE_MCU_TYPE_USB | URE_BYTE_EN_SIX_BYTES, u1u2, sizeof(u1u2));
+
+ for (i = 0; i < URE_TIMEOUT; i++) {
+ if (ure_read_2(sc, URE_PLA_BOOT_CTRL, URE_MCU_TYPE_PLA) &
+ URE_AUTOLOAD_DONE)
+ break;
+ uether_pause(&sc->sc_ue, hz / 100);
+ }
+ if (i == URE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev,
+ "timeout waiting for chip autoload\n");
+
+ for (i = 0; i < URE_TIMEOUT; i++) {
+ val = ure_ocp_reg_read(sc, URE_OCP_PHY_STATUS) &
+ URE_PHY_STAT_MASK;
+ if (val == URE_PHY_STAT_LAN_ON || val == URE_PHY_STAT_PWRDN)
+ break;
+ uether_pause(&sc->sc_ue, hz / 100);
+ }
+ if (i == URE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev,
+ "timeout waiting for phy to stabilize\n");
+
+ URE_CLRBIT_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB, URE_U2P3_ENABLE);
+
+ if (sc->sc_chip & URE_CHIP_VER_5C10) {
+ val = ure_read_2(sc, URE_USB_SSPHYLINK2, URE_MCU_TYPE_USB);
+ val &= ~URE_PWD_DN_SCALE_MASK;
+ val |= URE_PWD_DN_SCALE(96);
+ ure_write_2(sc, URE_USB_SSPHYLINK2, URE_MCU_TYPE_USB, val);
+
+ URE_SETBIT_1(sc, URE_USB_USB2PHY, URE_MCU_TYPE_USB, URE_USB2PHY_L1 | URE_USB2PHY_SUSPEND);
+ } else if (sc->sc_chip & URE_CHIP_VER_5C20)
+ URE_CLRBIT_1(sc, URE_PLA_DMY_REG0, URE_MCU_TYPE_PLA, URE_ECM_ALDPS);
+
+ if (sc->sc_chip & (URE_CHIP_VER_5C20 | URE_CHIP_VER_5C30)) {
+ val = ure_read_1(sc, URE_USB_CSR_DUMMY1, URE_MCU_TYPE_USB);
+ if (ure_read_2(sc, URE_USB_BURST_SIZE, URE_MCU_TYPE_USB) ==
+ 0)
+ val &= ~URE_DYNAMIC_BURST;
+ else
+ val |= URE_DYNAMIC_BURST;
+ ure_write_1(sc, URE_USB_CSR_DUMMY1, URE_MCU_TYPE_USB, val);
+ }
+
+ URE_SETBIT_1(sc, URE_USB_CSR_DUMMY2, URE_MCU_TYPE_USB, URE_EP4_FULL_FC);
+
+ URE_CLRBIT_2(sc, URE_USB_WDT11_CTRL, URE_MCU_TYPE_USB, URE_TIMER11_EN);
+
+ URE_CLRBIT_2(sc, URE_PLA_LED_FEATURE, URE_MCU_TYPE_PLA, URE_LED_MODE_MASK);
+
+ if ((sc->sc_chip & URE_CHIP_VER_5C10) &&
+ usbd_get_speed(sc->sc_ue.ue_udev) != USB_SPEED_SUPER)
+ val = URE_LPM_TIMER_500MS;
+ else
+ val = URE_LPM_TIMER_500US;
+ ure_write_1(sc, URE_USB_LPM_CTRL, URE_MCU_TYPE_USB,
+ val | URE_FIFO_EMPTY_1FB | URE_ROK_EXIT_LPM);
+
+ val = ure_read_2(sc, URE_USB_AFE_CTRL2, URE_MCU_TYPE_USB);
+ val &= ~URE_SEN_VAL_MASK;
+ val |= URE_SEN_VAL_NORMAL | URE_SEL_RXIDLE;
+ ure_write_2(sc, URE_USB_AFE_CTRL2, URE_MCU_TYPE_USB, val);
+
+ ure_write_2(sc, URE_USB_CONNECT_TIMER, URE_MCU_TYPE_USB, 0x0001);
+
+ URE_CLRBIT_2(sc, URE_USB_POWER_CUT, URE_MCU_TYPE_USB, URE_PWR_EN | URE_PHASE2_EN);
+
+ URE_CLRBIT_2(sc, URE_USB_MISC_0, URE_MCU_TYPE_USB, URE_PCUT_STATUS);
+
+ memset(u1u2, 0xff, sizeof(u1u2));
+ ure_write_mem(sc, URE_USB_TOLERANCE,
+ URE_MCU_TYPE_USB | URE_BYTE_EN_SIX_BYTES, u1u2, sizeof(u1u2));
+
+ ure_write_2(sc, URE_PLA_MAC_PWR_CTRL, URE_MCU_TYPE_PLA,
+ URE_ALDPS_SPDWN_RATIO);
+ ure_write_2(sc, URE_PLA_MAC_PWR_CTRL2, URE_MCU_TYPE_PLA,
+ URE_EEE_SPDWN_RATIO);
+ ure_write_2(sc, URE_PLA_MAC_PWR_CTRL3, URE_MCU_TYPE_PLA,
+ URE_PKT_AVAIL_SPDWN_EN | URE_SUSPEND_SPDWN_EN |
+ URE_U1U2_SPDWN_EN | URE_L1_SPDWN_EN);
+ ure_write_2(sc, URE_PLA_MAC_PWR_CTRL4, URE_MCU_TYPE_PLA,
+ URE_PWRSAVE_SPDWN_EN | URE_RXDV_SPDWN_EN | URE_TX10MIDLE_EN |
+ URE_TP100_SPDWN_EN | URE_TP500_SPDWN_EN | URE_TP1000_SPDWN_EN |
+ URE_EEE_SPDWN_EN);
+
+ val = ure_read_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB);
+ if (!(sc->sc_chip & (URE_CHIP_VER_5C00 | URE_CHIP_VER_5C10)))
+ val |= URE_U2P3_ENABLE;
+ else
+ val &= ~URE_U2P3_ENABLE;
+ ure_write_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB, val);
+
+ memset(u1u2, 0x00, sizeof(u1u2));
+ ure_write_mem(sc, URE_USB_TOLERANCE,
+ URE_MCU_TYPE_USB | URE_BYTE_EN_SIX_BYTES, u1u2, sizeof(u1u2));
+
+ ure_enable_aldps(sc, false);
+
+ if (sc->sc_chip & (URE_CHIP_VER_5C00 | URE_CHIP_VER_5C10 |
+ URE_CHIP_VER_5C20)) {
+ ure_ocp_reg_write(sc, URE_OCP_ADC_CFG,
+ URE_CKADSEL_L | URE_ADC_EN | URE_EN_EMI_L);
+ }
+ if (sc->sc_chip & URE_CHIP_VER_5C00) {
+ ure_ocp_reg_write(sc, URE_OCP_EEE_CFG,
+ ure_ocp_reg_read(sc, URE_OCP_EEE_CFG) &
+ ~URE_CTAP_SHORT_EN);
+ }
+ ure_ocp_reg_write(sc, URE_OCP_POWER_CFG,
+ ure_ocp_reg_read(sc, URE_OCP_POWER_CFG) |
+ URE_EEE_CLKDIV_EN);
+ ure_ocp_reg_write(sc, URE_OCP_DOWN_SPEED,
+ ure_ocp_reg_read(sc, URE_OCP_DOWN_SPEED) |
+ URE_EN_10M_BGOFF);
+ ure_ocp_reg_write(sc, URE_OCP_POWER_CFG,
+ ure_ocp_reg_read(sc, URE_OCP_POWER_CFG) |
+ URE_EN_10M_PLLOFF);
+ ure_sram_write(sc, URE_SRAM_IMPEDANCE, 0x0b13);
+ URE_SETBIT_2(sc, URE_PLA_PHY_PWR, URE_MCU_TYPE_PLA, URE_PFM_PWM_SWITCH);
+
+ /* Enable LPF corner auto tune. */
+ ure_sram_write(sc, URE_SRAM_LPF_CFG, 0xf70f);
+
+ /* Adjust 10M amplitude. */
+ ure_sram_write(sc, URE_SRAM_10M_AMP1, 0x00af);
+ ure_sram_write(sc, URE_SRAM_10M_AMP2, 0x0208);
+
+ ure_rtl8152_nic_reset(sc);
+
+ /* Enable Rx aggregation. */
+ URE_CLRBIT_2(sc, URE_USB_USB_CTRL, URE_MCU_TYPE_USB, URE_RX_AGG_DISABLE | URE_RX_ZERO_EN);
+
+ val = ure_read_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB);
+ if (!(sc->sc_chip & (URE_CHIP_VER_5C00 | URE_CHIP_VER_5C10)))
+ val |= URE_U2P3_ENABLE;
+ else
+ val &= ~URE_U2P3_ENABLE;
+ ure_write_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB, val);
+
+ memset(u1u2, 0xff, sizeof(u1u2));
+ ure_write_mem(sc, URE_USB_TOLERANCE,
+ URE_MCU_TYPE_USB | URE_BYTE_EN_SIX_BYTES, u1u2, sizeof(u1u2));
+}
+
+static void
+ure_rtl8153b_init(struct ure_softc *sc)
+{
+ uint16_t val;
+ int i;
+
+ if (sc->sc_flags & (URE_FLAG_8156 | URE_FLAG_8156B)) {
+ URE_CLRBIT_1(sc, 0xd26b, URE_MCU_TYPE_USB, 0x01);
+ ure_write_2(sc, 0xd32a, URE_MCU_TYPE_USB, 0);
+ URE_SETBIT_2(sc, 0xcfee, URE_MCU_TYPE_USB, 0x0020);
+ }
+
+ if (sc->sc_flags & URE_FLAG_8156B) {
+ URE_SETBIT_2(sc, 0xb460, URE_MCU_TYPE_USB, 0x08);
+ }
+
+ ure_enable_aldps(sc, false);
+
+ /* Disable U1U2 */
+ URE_CLRBIT_2(sc, URE_USB_LPM_CONFIG, URE_MCU_TYPE_USB, URE_LPM_U1U2_EN);
+
+ /* Wait loading flash */
+ if (sc->sc_chip == URE_CHIP_VER_7410) {
+ if ((ure_read_2(sc, 0xd3ae, URE_MCU_TYPE_PLA) & 0x0002) &&
+ !(ure_read_2(sc, 0xd284, URE_MCU_TYPE_USB) & 0x0020)) {
+ for (i=0; i < 100; i++) {
+ if (ure_read_2(sc, 0xd284, URE_MCU_TYPE_USB) & 0x0004)
+ break;
+ uether_pause(&sc->sc_ue, hz / 1000);
+ }
+ }
+ }
+
+ for (i = 0; i < URE_TIMEOUT; i++) {
+ if (ure_read_2(sc, URE_PLA_BOOT_CTRL, URE_MCU_TYPE_PLA) &
+ URE_AUTOLOAD_DONE)
+ break;
+ uether_pause(&sc->sc_ue, hz / 100);
+ }
+ if (i == URE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev,
+ "timeout waiting for chip autoload\n");
+
+ val = ure_phy_status(sc, 0);
+ if ((val == URE_PHY_STAT_EXT_INIT) &
+ (sc->sc_flags & (URE_FLAG_8156 | URE_FLAG_8156B))) {
+ ure_ocp_reg_write(sc, 0xa468,
+ ure_ocp_reg_read(sc, 0xa468) & ~0x0a);
+ if (sc->sc_flags & URE_FLAG_8156B)
+ ure_ocp_reg_write(sc, 0xa466,
+ ure_ocp_reg_read(sc, 0xa466) & ~0x01);
+ }
+
+ val = ure_ocp_reg_read(sc, URE_OCP_BASE_MII + MII_BMCR);
+ if (val & BMCR_PDOWN) {
+ val &= ~BMCR_PDOWN;
+ ure_ocp_reg_write(sc, URE_OCP_BASE_MII + MII_BMCR, val);
+ }
+
+ ure_phy_status(sc, URE_PHY_STAT_LAN_ON);
+
+ /* Disable U2P3 */
+ URE_CLRBIT_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB, URE_U2P3_ENABLE);
+
+ /* MSC timer, 32760 ms. */
+ ure_write_2(sc, URE_USB_MSC_TIMER, URE_MCU_TYPE_USB, 0x0fff);
+
+ /* U1/U2/L1 idle timer, 500 us. */
+ ure_write_2(sc, URE_USB_U1U2_TIMER, URE_MCU_TYPE_USB, 500);
+
+ /* Disable power cut */
+ URE_CLRBIT_2(sc, URE_USB_POWER_CUT, URE_MCU_TYPE_USB, URE_PWR_EN);
+ URE_CLRBIT_2(sc, URE_USB_MISC_0, URE_MCU_TYPE_USB, URE_PCUT_STATUS);
+
+ /* Disable ups */
+ URE_CLRBIT_1(sc, URE_USB_POWER_CUT, URE_MCU_TYPE_USB, URE_UPS_EN | URE_USP_PREWAKE);
+ URE_CLRBIT_1(sc, 0xcfff, URE_MCU_TYPE_USB, 0x01);
+
+ /* Disable queue wake */
+ URE_CLRBIT_1(sc, URE_PLA_INDICATE_FALG, URE_MCU_TYPE_USB, URE_UPCOMING_RUNTIME_D3);
+ URE_CLRBIT_1(sc, URE_PLA_SUSPEND_FLAG, URE_MCU_TYPE_USB, URE_LINK_CHG_EVENT);
+ URE_CLRBIT_2(sc, URE_PLA_EXTRA_STATUS, URE_MCU_TYPE_USB, URE_LINK_CHANGE_FLAG);
+
+ /* Disable runtime suspend */
+ ure_write_1(sc, URE_PLA_CRWECR, URE_MCU_TYPE_PLA, URE_CRWECR_CONFIG);
+ URE_CLRBIT_2(sc, URE_PLA_CONFIG34, URE_MCU_TYPE_USB, URE_LINK_OFF_WAKE_EN);
+ ure_write_1(sc, URE_PLA_CRWECR, URE_MCU_TYPE_PLA, URE_CRWECR_NORAML);
+
+ /* Enable U1U2 */
+ if (usbd_get_speed(sc->sc_ue.ue_udev) == USB_SPEED_SUPER)
+ URE_SETBIT_2(sc, URE_USB_LPM_CONFIG, URE_MCU_TYPE_USB, URE_LPM_U1U2_EN);
+
+ if (sc->sc_flags & URE_FLAG_8156B) {
+ URE_CLRBIT_2(sc, 0xc010, URE_MCU_TYPE_PLA, 0x0800);
+ URE_SETBIT_2(sc, 0xe854, URE_MCU_TYPE_PLA, 0x0001);
+
+ /* enable fc timer and set timer to 600 ms. */
+ ure_write_2(sc, URE_USB_FC_TIMER, URE_MCU_TYPE_USB, URE_CTRL_TIMER_EN | (600 / 8));
+
+ if (!(ure_read_1(sc, 0xdc6b, URE_MCU_TYPE_PLA) & 0x80)) {
+ val = ure_read_2(sc, URE_USB_FW_CTRL, URE_MCU_TYPE_USB);
+ val |= URE_FLOW_CTRL_PATCH_OPT | 0x0100;
+ val &= ~0x08;
+ ure_write_2(sc, URE_USB_FW_CTRL, URE_MCU_TYPE_USB, val);
+ }
+
+ URE_SETBIT_2(sc, URE_USB_FW_TASK, URE_MCU_TYPE_USB, URE_FC_PATCH_TASK);
+ }
+
+ val = ure_read_2(sc, URE_PLA_EXTRA_STATUS, URE_MCU_TYPE_PLA);
+ if (ure_get_link_status(sc))
+ val |= URE_CUR_LINK_OK;
+ else
+ val &= ~URE_CUR_LINK_OK;
+ val |= URE_POLL_LINK_CHG;
+ ure_write_2(sc, URE_PLA_EXTRA_STATUS, URE_MCU_TYPE_PLA, val);
+
+ /* MAC clock speed down */
+ if (sc->sc_flags & (URE_FLAG_8156 | URE_FLAG_8156B)) {
+ ure_write_2(sc, URE_PLA_MAC_PWR_CTRL, URE_MCU_TYPE_PLA, 0x0403);
+ val = ure_read_2(sc, URE_PLA_MAC_PWR_CTRL2, URE_MCU_TYPE_PLA);
+ val &= ~0xff;
+ val |= URE_MAC_CLK_SPDWN_EN | 0x03;
+ ure_write_2(sc, URE_PLA_MAC_PWR_CTRL2, URE_MCU_TYPE_PLA, val);
+ } else {
+ URE_SETBIT_2(sc, URE_PLA_MAC_PWR_CTRL2, URE_MCU_TYPE_USB, URE_MAC_CLK_SPDWN_EN);
+ }
+ URE_CLRBIT_2(sc, URE_PLA_MAC_PWR_CTRL3, URE_MCU_TYPE_PLA, URE_PLA_MCU_SPDWN_EN);
+
+ /* Enable Rx aggregation. */
+ URE_CLRBIT_2(sc, URE_USB_USB_CTRL, URE_MCU_TYPE_USB, URE_RX_AGG_DISABLE | URE_RX_ZERO_EN);
+
+ if (sc->sc_flags & URE_FLAG_8156)
+ URE_SETBIT_1(sc, 0xd4b4, URE_MCU_TYPE_USB, 0x02);
+
+ /* Reset tally */
+ URE_SETBIT_2(sc, URE_PLA_RSTTALLY, URE_MCU_TYPE_USB, URE_TALLY_RESET);
+}
+
+static void
+ure_rtl8153b_nic_reset(struct ure_softc *sc)
+{
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ uint16_t val;
+ int i;
+
+ /* Disable U1U2 */
+ URE_CLRBIT_2(sc, URE_USB_LPM_CONFIG, URE_MCU_TYPE_USB, URE_LPM_U1U2_EN);
+
+ /* Disable U2P3 */
+ URE_CLRBIT_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB, URE_U2P3_ENABLE);
+
+ ure_enable_aldps(sc, false);
+
+ /* Enable rxdy_gated */
+ URE_SETBIT_2(sc, URE_PLA_MISC_1, URE_MCU_TYPE_PLA, URE_RXDY_GATED_EN);
+
+ /* Disable teredo */
+ ure_disable_teredo(sc);
+
+ DEVPRINTFN(14, sc->sc_ue.ue_dev, "rtl8153b_nic_reset: RCR: %#x\n", ure_read_4(sc, URE_PLA_RCR, URE_MCU_TYPE_PLA));
+ URE_CLRBIT_4(sc, URE_PLA_RCR, URE_MCU_TYPE_PLA, URE_RCR_ACPT_ALL);
+
+ ure_reset(sc);
+
+ /* Reset BMU */
+ URE_CLRBIT_1(sc, URE_USB_BMU_RESET, URE_MCU_TYPE_USB, URE_BMU_RESET_EP_IN | URE_BMU_RESET_EP_OUT);
+ URE_SETBIT_1(sc, URE_USB_BMU_RESET, URE_MCU_TYPE_USB, URE_BMU_RESET_EP_IN | URE_BMU_RESET_EP_OUT);
+
+ URE_CLRBIT_1(sc, URE_PLA_OOB_CTRL, URE_MCU_TYPE_PLA, URE_NOW_IS_OOB);
+ URE_CLRBIT_2(sc, URE_PLA_SFF_STS_7, URE_MCU_TYPE_PLA, URE_MCU_BORW_EN);
+ if (sc->sc_flags & URE_FLAG_8153B) {
+ for (i = 0; i < URE_TIMEOUT; i++) {
+ if (ure_read_1(sc, URE_PLA_OOB_CTRL, URE_MCU_TYPE_PLA) &
+ URE_LINK_LIST_READY)
+ break;
+ uether_pause(&sc->sc_ue, hz / 100);
+ }
+ if (i == URE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev,
+ "timeout waiting for OOB control\n");
+
+ URE_SETBIT_2(sc, URE_PLA_SFF_STS_7, URE_MCU_TYPE_PLA, URE_RE_INIT_LL);
+ for (i = 0; i < URE_TIMEOUT; i++) {
+ if (ure_read_1(sc, URE_PLA_OOB_CTRL, URE_MCU_TYPE_PLA) &
+ URE_LINK_LIST_READY)
+ break;
+ uether_pause(&sc->sc_ue, hz / 100);
+ }
+ if (i == URE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev,
+ "timeout waiting for OOB control\n");
+ }
+
+ /* Configure rxvlan */
+ val = ure_read_2(sc, 0xc012, URE_MCU_TYPE_PLA);
+ val &= ~0x00c0;
+ if (if_getcapabilities(ifp) & IFCAP_VLAN_HWTAGGING)
+ val |= 0x00c0;
+ ure_write_2(sc, 0xc012, URE_MCU_TYPE_PLA, val);
+
+ val = if_getmtu(ifp);
+ ure_write_2(sc, URE_PLA_RMS, URE_MCU_TYPE_PLA, URE_FRAMELEN(val));
+ ure_write_1(sc, URE_PLA_MTPS, URE_MCU_TYPE_PLA, URE_MTPS_JUMBO);
+
+ if (sc->sc_flags & URE_FLAG_8153B) {
+ URE_SETBIT_2(sc, URE_PLA_TCR0, URE_MCU_TYPE_PLA, URE_TCR0_AUTO_FIFO);
+ ure_reset(sc);
+ }
+
+ /* Configure fc parameter */
+ if (sc->sc_flags & URE_FLAG_8156) {
+ ure_write_2(sc, 0xc0a6, URE_MCU_TYPE_PLA, 0x0400);
+ ure_write_2(sc, 0xc0aa, URE_MCU_TYPE_PLA, 0x0800);
+ } else if (sc->sc_flags & URE_FLAG_8156B) {
+ ure_write_2(sc, 0xc0a6, URE_MCU_TYPE_PLA, 0x0200);
+ ure_write_2(sc, 0xc0aa, URE_MCU_TYPE_PLA, 0x0400);
+ }
+
+ /* Configure Rx FIFO threshold. */
+ if (sc->sc_flags & URE_FLAG_8153B) {
+ ure_write_4(sc, URE_PLA_RXFIFO_CTRL0, URE_MCU_TYPE_PLA, URE_RXFIFO_THR1_NORMAL);
+ ure_write_2(sc, URE_PLA_RXFIFO_CTRL1, URE_MCU_TYPE_PLA, URE_RXFIFO_THR2_NORMAL);
+ ure_write_2(sc, URE_PLA_RXFIFO_CTRL2, URE_MCU_TYPE_PLA, URE_RXFIFO_THR3_NORMAL);
+ ure_write_4(sc, URE_USB_RX_BUF_TH, URE_MCU_TYPE_USB, URE_RX_THR_B);
+ } else {
+ ure_write_2(sc, 0xc0a2, URE_MCU_TYPE_PLA,
+ (ure_read_2(sc, 0xc0a2, URE_MCU_TYPE_PLA) & ~0xfff) | 0x08);
+ ure_write_4(sc, URE_USB_RX_BUF_TH, URE_MCU_TYPE_USB, 0x00600400);
+ }
+
+ /* Configure Tx FIFO threshold. */
+ if (sc->sc_flags & URE_FLAG_8153B) {
+ ure_write_4(sc, URE_PLA_TXFIFO_CTRL, URE_MCU_TYPE_PLA, URE_TXFIFO_THR_NORMAL2);
+ } else if (sc->sc_flags & URE_FLAG_8156) {
+ ure_write_2(sc, URE_PLA_TXFIFO_CTRL, URE_MCU_TYPE_PLA, URE_TXFIFO_THR_NORMAL2);
+ URE_SETBIT_2(sc, 0xd4b4, URE_MCU_TYPE_USB, 0x0002);
+ } else if (sc->sc_flags & URE_FLAG_8156B) {
+ ure_write_2(sc, URE_PLA_TXFIFO_CTRL, URE_MCU_TYPE_PLA, 0x0008);
+ ure_write_2(sc, 0xe61a, URE_MCU_TYPE_PLA,
+ (URE_FRAMELEN(val) + 0x100) / 16 );
+ }
+
+ URE_CLRBIT_2(sc, URE_PLA_MAC_PWR_CTRL3, URE_MCU_TYPE_PLA, URE_PLA_MCU_SPDWN_EN);
+
+ if (sc->sc_flags & (URE_FLAG_8156 | URE_FLAG_8156B))
+ URE_CLRBIT_2(sc, 0xd32a, URE_MCU_TYPE_USB, 0x300);
+
+ ure_enable_aldps(sc, true);
+
+ if (sc->sc_flags & (URE_FLAG_8156 | URE_FLAG_8156B)) {
+ /* Enable U2P3 */
+ URE_SETBIT_2(sc, URE_USB_U2P3_CTRL, URE_MCU_TYPE_USB, URE_U2P3_ENABLE);
+ }
+
+ /* Enable U1U2 */
+ if (usbd_get_speed(sc->sc_ue.ue_udev) == USB_SPEED_SUPER)
+ URE_SETBIT_2(sc, URE_USB_LPM_CONFIG, URE_MCU_TYPE_USB, URE_LPM_U1U2_EN);
+}
+
+static void
+ure_stop(struct usb_ether *ue)
+{
+ struct ure_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ URE_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, 0, (IFF_DRV_RUNNING | IFF_DRV_OACTIVE));
+ sc->sc_flags &= ~URE_FLAG_LINK;
+ sc->sc_rxstarted = 0;
+
+ /*
+ * stop all the transfers, if not already stopped:
+ */
+ for (int i = 0; i < URE_MAX_RX; i++)
+ usbd_transfer_stop(sc->sc_rx_xfer[i]);
+ for (int i = 0; i < URE_MAX_TX; i++)
+ usbd_transfer_stop(sc->sc_tx_xfer[i]);
+}
+
+static void
+ure_disable_teredo(struct ure_softc *sc)
+{
+
+ if (sc->sc_flags & (URE_FLAG_8153B | URE_FLAG_8156 | URE_FLAG_8156B))
+ ure_write_1(sc, URE_PLA_TEREDO_CFG, URE_MCU_TYPE_PLA, 0xff);
+ else {
+ URE_CLRBIT_2(sc, URE_PLA_TEREDO_CFG, URE_MCU_TYPE_PLA,
+ (URE_TEREDO_SEL | URE_TEREDO_RS_EVENT_MASK | URE_OOB_TEREDO_EN));
+ }
+ ure_write_2(sc, URE_PLA_WDT6_CTRL, URE_MCU_TYPE_PLA, URE_WDT6_SET_MODE);
+ ure_write_2(sc, URE_PLA_REALWOW_TIMER, URE_MCU_TYPE_PLA, 0);
+ ure_write_4(sc, URE_PLA_TEREDO_TIMER, URE_MCU_TYPE_PLA, 0);
+}
+
+static void
+ure_enable_aldps(struct ure_softc *sc, bool enable)
+{
+ int i;
+
+ if (enable) {
+ ure_ocp_reg_write(sc, URE_OCP_POWER_CFG,
+ ure_ocp_reg_read(sc, URE_OCP_POWER_CFG) | URE_EN_ALDPS);
+ } else {
+ ure_ocp_reg_write(sc, URE_OCP_ALDPS_CONFIG, URE_ENPDNPS | URE_LINKENA |
+ URE_DIS_SDSAVE);
+ for (i = 0; i < 20; i++) {
+ uether_pause(&sc->sc_ue, hz / 1000);
+ if (ure_ocp_reg_read(sc, 0xe000) & 0x0100)
+ break;
+ }
+ }
+}
+
+static uint16_t
+ure_phy_status(struct ure_softc *sc, uint16_t desired)
+{
+ uint16_t val;
+ int i;
+
+ for (i = 0; i < URE_TIMEOUT; i++) {
+ val = ure_ocp_reg_read(sc, URE_OCP_PHY_STATUS) &
+ URE_PHY_STAT_MASK;
+ if (desired) {
+ if (val == desired)
+ break;
+ } else {
+ if (val == URE_PHY_STAT_LAN_ON ||
+ val == URE_PHY_STAT_PWRDN ||
+ val == URE_PHY_STAT_EXT_INIT)
+ break;
+ }
+ uether_pause(&sc->sc_ue, hz / 100);
+ }
+ if (i == URE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev,
+ "timeout waiting for phy to stabilize\n");
+
+ return (val);
+}
+
+static void
+ure_rtl8152_nic_reset(struct ure_softc *sc)
+{
+ uint32_t rx_fifo1, rx_fifo2;
+ int i;
+
+ URE_SETBIT_2(sc, URE_PLA_MISC_1, URE_MCU_TYPE_PLA, URE_RXDY_GATED_EN);
+
+ ure_disable_teredo(sc);
+
+ DEVPRINTFN(14, sc->sc_ue.ue_dev, "rtl8152_nic_reset: RCR: %#x\n", ure_read_4(sc, URE_PLA_RCR, URE_MCU_TYPE_PLA));
+ URE_CLRBIT_4(sc, URE_PLA_RCR, URE_MCU_TYPE_PLA, URE_RCR_ACPT_ALL);
+
+ ure_reset(sc);
+
+ ure_write_1(sc, URE_PLA_CR, URE_MCU_TYPE_PLA, 0);
+
+ URE_CLRBIT_1(sc, URE_PLA_OOB_CTRL, URE_MCU_TYPE_PLA, URE_NOW_IS_OOB);
+
+ URE_CLRBIT_2(sc, URE_PLA_SFF_STS_7, URE_MCU_TYPE_PLA, URE_MCU_BORW_EN);
+ for (i = 0; i < URE_TIMEOUT; i++) {
+ if (ure_read_1(sc, URE_PLA_OOB_CTRL, URE_MCU_TYPE_PLA) &
+ URE_LINK_LIST_READY)
+ break;
+ uether_pause(&sc->sc_ue, hz / 100);
+ }
+ if (i == URE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev,
+ "timeout waiting for OOB control\n");
+ URE_SETBIT_2(sc, URE_PLA_SFF_STS_7, URE_MCU_TYPE_PLA, URE_RE_INIT_LL);
+ for (i = 0; i < URE_TIMEOUT; i++) {
+ if (ure_read_1(sc, URE_PLA_OOB_CTRL, URE_MCU_TYPE_PLA) &
+ URE_LINK_LIST_READY)
+ break;
+ uether_pause(&sc->sc_ue, hz / 100);
+ }
+ if (i == URE_TIMEOUT)
+ device_printf(sc->sc_ue.ue_dev,
+ "timeout waiting for OOB control\n");
+
+ URE_CLRBIT_2(sc, URE_PLA_CPCR, URE_MCU_TYPE_PLA, URE_CPCR_RX_VLAN);
+
+ URE_SETBIT_2(sc, URE_PLA_TCR0, URE_MCU_TYPE_PLA, URE_TCR0_AUTO_FIFO);
+
+ /* Configure Rx FIFO threshold. */
+ ure_write_4(sc, URE_PLA_RXFIFO_CTRL0, URE_MCU_TYPE_PLA,
+ URE_RXFIFO_THR1_NORMAL);
+ if (usbd_get_speed(sc->sc_ue.ue_udev) == USB_SPEED_FULL) {
+ rx_fifo1 = URE_RXFIFO_THR2_FULL;
+ rx_fifo2 = URE_RXFIFO_THR3_FULL;
+ } else {
+ rx_fifo1 = URE_RXFIFO_THR2_HIGH;
+ rx_fifo2 = URE_RXFIFO_THR3_HIGH;
+ }
+ ure_write_4(sc, URE_PLA_RXFIFO_CTRL1, URE_MCU_TYPE_PLA, rx_fifo1);
+ ure_write_4(sc, URE_PLA_RXFIFO_CTRL2, URE_MCU_TYPE_PLA, rx_fifo2);
+
+ /* Configure Tx FIFO threshold. */
+ ure_write_4(sc, URE_PLA_TXFIFO_CTRL, URE_MCU_TYPE_PLA,
+ URE_TXFIFO_THR_NORMAL);
+}
+
+/*
+ * Update mbuf for rx checksum from hardware
+ */
+static void
+ure_rxcsum(int capenb, struct ure_rxpkt *rp, struct mbuf *m)
+{
+ int flags;
+ uint32_t csum, misc;
+ int tcp, udp;
+
+ m->m_pkthdr.csum_flags = 0;
+
+ if (!(capenb & IFCAP_RXCSUM))
+ return;
+
+ csum = le32toh(rp->ure_csum);
+ misc = le32toh(rp->ure_misc);
+
+ tcp = udp = 0;
+
+ flags = 0;
+ if (csum & URE_RXPKT_IPV4_CS)
+ flags |= CSUM_IP_CHECKED;
+ else if (csum & URE_RXPKT_IPV6_CS)
+ flags = 0;
+
+ tcp = rp->ure_csum & URE_RXPKT_TCP_CS;
+ udp = rp->ure_csum & URE_RXPKT_UDP_CS;
+
+ if (__predict_true((flags & CSUM_IP_CHECKED) &&
+ !(misc & URE_RXPKT_IP_F))) {
+ flags |= CSUM_IP_VALID;
+ }
+ if (__predict_true(
+ (tcp && !(misc & URE_RXPKT_TCP_F)) ||
+ (udp && !(misc & URE_RXPKT_UDP_F)))) {
+ flags |= CSUM_DATA_VALID|CSUM_PSEUDO_HDR;
+ m->m_pkthdr.csum_data = 0xFFFF;
+ }
+
+ m->m_pkthdr.csum_flags = flags;
+}
+
+/*
+ * If the L4 checksum offset is larger than 0x7ff (2047), return failure.
+ * We currently restrict MTU such that it can't happen, and even if we
+ * did have a large enough MTU, only a very specially crafted IPv6 packet
+ * with MANY headers could possibly come close.
+ *
+ * Returns 0 for success, and 1 if the packet cannot be checksummed and
+ * should be dropped.
+ */
+static int
+ure_txcsum(struct mbuf *m, int caps, uint32_t *regout)
+{
+ struct ip ip;
+ struct ether_header *eh;
+ int flags;
+ uint32_t data;
+ uint32_t reg;
+ int l3off, l4off;
+ uint16_t type;
+
+ *regout = 0;
+ flags = m->m_pkthdr.csum_flags;
+ if (flags == 0)
+ return (0);
+
+ if (__predict_true(m->m_len >= (int)sizeof(*eh))) {
+ eh = mtod(m, struct ether_header *);
+ type = eh->ether_type;
+ } else
+ m_copydata(m, offsetof(struct ether_header, ether_type),
+ sizeof(type), (caddr_t)&type);
+
+ switch (type = htons(type)) {
+ case ETHERTYPE_IP:
+ case ETHERTYPE_IPV6:
+ l3off = ETHER_HDR_LEN;
+ break;
+ case ETHERTYPE_VLAN:
+ /* XXX - what about QinQ? */
+ l3off = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
+ break;
+ default:
+ return (0);
+ }
+
+ reg = 0;
+
+ if (flags & CSUM_IP)
+ reg |= URE_TXPKT_IPV4_CS;
+
+ data = m->m_pkthdr.csum_data;
+ if (flags & (CSUM_IP_TCP | CSUM_IP_UDP)) {
+ m_copydata(m, l3off, sizeof ip, (caddr_t)&ip);
+ l4off = l3off + (ip.ip_hl << 2) + data;
+ if (__predict_false(l4off > URE_L4_OFFSET_MAX))
+ return (1);
+
+ reg |= URE_TXPKT_IPV4_CS;
+ if (flags & CSUM_IP_TCP)
+ reg |= URE_TXPKT_TCP_CS;
+ else if (flags & CSUM_IP_UDP)
+ reg |= URE_TXPKT_UDP_CS;
+ reg |= l4off << URE_L4_OFFSET_SHIFT;
+ }
+#ifdef INET6
+ else if (flags & (CSUM_IP6_TCP | CSUM_IP6_UDP)) {
+ l4off = l3off + data;
+ if (__predict_false(l4off > URE_L4_OFFSET_MAX))
+ return (1);
+
+ reg |= URE_TXPKT_IPV6_CS;
+ if (flags & CSUM_IP6_TCP)
+ reg |= URE_TXPKT_TCP_CS;
+ else if (flags & CSUM_IP6_UDP)
+ reg |= URE_TXPKT_UDP_CS;
+ reg |= l4off << URE_L4_OFFSET_SHIFT;
+ }
+#endif
+ *regout = reg;
+ return 0;
+}
diff --git a/sys/dev/usb/net/if_urereg.h b/sys/dev/usb/net/if_urereg.h
new file mode 100644
index 000000000000..e4e171f4910e
--- /dev/null
+++ b/sys/dev/usb/net/if_urereg.h
@@ -0,0 +1,620 @@
+/*-
+ * Copyright (c) 2015-2016 Kevin Lo <kevlo@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.
+ */
+
+#ifndef _IF_UREREG_H_
+#define _IF_UREREG_H_
+
+#define URE_CONFIG_IDX 0 /* config number 1 */
+#define URE_IFACE_IDX 0
+
+#define URE_CTL_READ 0x01
+#define URE_CTL_WRITE 0x02
+
+#define URE_TIMEOUT 1000
+#define URE_PHY_TIMEOUT 2000
+
+#define URE_BYTE_EN_DWORD 0xff
+#define URE_BYTE_EN_WORD 0x33
+#define URE_BYTE_EN_BYTE 0x11
+#define URE_BYTE_EN_SIX_BYTES 0x3f
+
+#define URE_FRAMELEN(mtu) ((mtu) + ETHER_HDR_LEN + ETHER_CRC_LEN + ETHER_VLAN_ENCAP_LEN)
+#define URE_MAX_FRAMELEN (ETHER_MAX_LEN + ETHER_VLAN_ENCAP_LEN)
+#define URE_JUMBO_FRAMELEN (9*1024)
+#define URE_JUMBO_MTU (URE_JUMBO_FRAMELEN - ETHER_HDR_LEN - ETHER_CRC_LEN - ETHER_VLAN_ENCAP_LEN)
+
+#define URE_PLA_IDR 0xc000
+#define URE_PLA_RCR 0xc010
+#define URE_PLA_RMS 0xc016
+#define URE_PLA_RXFIFO_CTRL0 0xc0a0
+#define URE_PLA_RXFIFO_CTRL1 0xc0a4
+#define URE_PLA_RXFIFO_CTRL2 0xc0a8
+#define URE_PLA_DMY_REG0 0xc0b0
+#define URE_PLA_FMC 0xc0b4
+#define URE_PLA_CFG_WOL 0xc0b6
+#define URE_PLA_TEREDO_CFG 0xc0bc
+#define URE_PLA_MAR0 0xcd00
+#define URE_PLA_MAR4 0xcd04
+#define URE_PLA_BACKUP 0xd000
+#define URE_PAL_BDC_CR 0xd1a0
+#define URE_PLA_TEREDO_TIMER 0xd2cc
+#define URE_PLA_REALWOW_TIMER 0xd2e8
+#define URE_PLA_SUSPEND_FLAG 0xd38a
+#define URE_PLA_INDICATE_FALG 0xd38c
+#define URE_PLA_EXTRA_STATUS 0xd398
+#define URE_PLA_LEDSEL 0xdd90
+#define URE_PLA_LED_FEATURE 0xdd92
+#define URE_PLA_PHYAR 0xde00
+#define URE_PLA_BOOT_CTRL 0xe004
+#define URE_PLA_GPHY_INTR_IMR 0xe022
+#define URE_PLA_EEE_CR 0xe040
+#define URE_PLA_EEEP_CR 0xe080
+#define URE_PLA_MAC_PWR_CTRL 0xe0c0
+#define URE_PLA_MAC_PWR_CTRL2 0xe0ca
+#define URE_PLA_MAC_PWR_CTRL3 0xe0cc
+#define URE_PLA_MAC_PWR_CTRL4 0xe0ce
+#define URE_PLA_WDT6_CTRL 0xe428
+#define URE_PLA_TCR0 0xe610
+#define URE_PLA_TCR1 0xe612
+#define URE_PLA_MTPS 0xe615
+#define URE_PLA_TXFIFO_CTRL 0xe618
+#define URE_PLA_RSTTALLY 0xe800
+#define URE_PLA_CR 0xe813
+#define URE_PLA_CRWECR 0xe81c
+#define URE_PLA_CONFIG34 0xe820
+#define URE_PLA_CONFIG5 0xe822
+#define URE_PLA_PHY_PWR 0xe84c
+#define URE_PLA_OOB_CTRL 0xe84f
+#define URE_PLA_CPCR 0xe854
+#define URE_PLA_MISC_0 0xe858
+#define URE_PLA_MISC_1 0xe85a
+#define URE_PLA_OCP_GPHY_BASE 0xe86c
+#define URE_PLA_TELLYCNT 0xe890
+#define URE_PLA_SFF_STS_7 0xe8de
+#define URE_PLA_PHYSTATUS 0xe908
+#define URE_GMEDIASTAT 0xe908
+#define URE_PLA_BP_BA 0xfc26
+#define URE_PLA_BP_0 0xfc28
+#define URE_PLA_BP_1 0xfc2a
+#define URE_PLA_BP_2 0xfc2c
+#define URE_PLA_BP_3 0xfc2e
+#define URE_PLA_BP_4 0xfc30
+#define URE_PLA_BP_5 0xfc32
+#define URE_PLA_BP_6 0xfc34
+#define URE_PLA_BP_7 0xfc36
+#define URE_PLA_BP_EN 0xfc38
+
+#define URE_USB_USB2PHY 0xb41e
+#define URE_USB_SSPHYLINK2 0xb428
+#define URE_USB_U2P3_CTRL 0xb460
+#define URE_USB_CSR_DUMMY1 0xb464
+#define URE_USB_CSR_DUMMY2 0xb466
+#define URE_USB_DEV_STAT 0xb808
+#define URE_USB_CONNECT_TIMER 0xcbf8
+#define URE_USB_MSC_TIMER 0xcbfc
+#define URE_USB_BURST_SIZE 0xcfc0
+#define URE_USB_LPM_CONFIG 0xcfd8
+#define URE_USB_FW_CTRL 0xd334 /* RTL8153B */
+#define URE_USB_USB_CTRL 0xd406
+#define URE_USB_PHY_CTRL 0xd408
+#define URE_USB_TX_AGG 0xd40a
+#define URE_USB_RX_BUF_TH 0xd40c
+#define URE_USB_FW_TASK 0xd4e8 /* RTL8153B */
+#define URE_USB_USB_TIMER 0xd428
+#define URE_USB_RX_EARLY_AGG 0xd42c
+#define URE_USB_RX_EARLY_SIZE 0xd42e
+#define URE_USB_PM_CTRL_STATUS 0xd432 /* RTL8153A */
+#define URE_USB_RX_EXTRA_AGG_TMR 0xd432 /* RTL8153B */
+#define URE_USB_TX_DMA 0xd434
+#define URE_USB_UPT_RXDMA_OWN 0xd437
+#define URE_USB_FC_TIMER 0xd340
+#define URE_USB_TOLERANCE 0xd490
+#define URE_USB_LPM_CTRL 0xd41a
+#define URE_USB_BMU_RESET 0xd4b0
+#define URE_USB_U1U2_TIMER 0xd4da
+#define URE_USB_UPS_CTRL 0xd800
+#define URE_USB_POWER_CUT 0xd80a
+#define URE_USB_MISC_0 0xd81a
+#define URE_USB_AFE_CTRL2 0xd824
+#define URE_USB_WDT11_CTRL 0xe43c
+#define URE_USB_BP_BA URE_PLA_BP_BA
+#define URE_USB_BP_0 URE_PLA_BP_0
+#define URE_USB_BP_1 URE_PLA_BP_1
+#define URE_USB_BP_2 URE_PLA_BP_2
+#define URE_USB_BP_3 URE_PLA_BP_3
+#define URE_USB_BP_4 URE_PLA_BP_4
+#define URE_USB_BP_5 URE_PLA_BP_5
+#define URE_USB_BP_6 URE_PLA_BP_6
+#define URE_USB_BP_7 URE_PLA_BP_7
+#define URE_USB_BP_EN URE_PLA_BP_EN /* RTL8153A */
+#define URE_USB_BP_8 0xfc38 /* RTL8153B */
+#define URE_USB_BP_9 0xfc3a
+#define URE_USB_BP_10 0xfc3c
+#define URE_USB_BP_11 0xfc3e
+#define URE_USB_BP_12 0xfc40
+#define URE_USB_BP_13 0xfc42
+#define URE_USB_BP_14 0xfc44
+#define URE_USB_BP_15 0xfc46
+#define URE_USB_BP2_EN 0xfc48
+
+
+/* OCP Registers. */
+#define URE_OCP_ALDPS_CONFIG 0x2010
+#define URE_OCP_EEE_CONFIG1 0x2080
+#define URE_OCP_EEE_CONFIG2 0x2092
+#define URE_OCP_EEE_CONFIG3 0x2094
+#define URE_OCP_BASE_MII 0xa400
+#define URE_OCP_EEE_AR 0xa41a
+#define URE_OCP_EEE_DATA 0xa41c
+#define URE_OCP_PHY_STATUS 0xa420
+#define URE_OCP_POWER_CFG 0xa430
+#define URE_OCP_EEE_CFG 0xa432
+#define URE_OCP_SRAM_ADDR 0xa436
+#define URE_OCP_SRAM_DATA 0xa438
+#define URE_OCP_DOWN_SPEED 0xa442
+#define URE_OCP_EEE_ABLE 0xa5c4
+#define URE_OCP_EEE_ADV 0xa5d0
+#define URE_OCP_EEE_LPABLE 0xa5d2
+#define URE_OCP_PHY_STATE 0xa708
+#define URE_OCP_PHY_PATCH_STAT 0xb800
+#define URE_OCP_PHY_PATCH_CMD 0xb820
+#define URE_OCP_PHY_LOCK 0xb82e
+#define URE_OCP_ADC_CFG 0xbc06
+
+/* SRAM Register. */
+#define URE_SRAM_GREEN_CFG 0x8011
+#define URE_SRAM_LPF_CFG 0x8012
+#define URE_SRAM_GPHY_FW_VER 0x801e
+#define URE_SRAM_10M_AMP1 0x8080
+#define URE_SRAM_10M_AMP2 0x8082
+#define URE_SRAM_IMPEDANCE 0x8084
+#define URE_SRAM_PHY_LOCK 0xb82e
+
+/* PLA_RCR */
+#define URE_RCR_AAP 0x00000001
+#define URE_RCR_APM 0x00000002
+#define URE_RCR_AM 0x00000004
+#define URE_RCR_AB 0x00000008
+#define URE_RCR_AR 0x00000010 /* runt */
+#define URE_RCR_AER 0x00000020 /* error pkts */
+#define URE_RCR_ACPTFLOW 0x00000080
+#define URE_RCR_RXEMPTY 0x00020000
+#define URE_RCR_ACPT_ALL \
+ (URE_RCR_AAP | URE_RCR_APM | URE_RCR_AM | URE_RCR_AB)
+
+/* PLA_RXFIFO_CTRL0 */
+#define URE_RXFIFO_THR1_NORMAL 0x00080002
+#define URE_RXFIFO_THR1_OOB 0x01800003
+
+/* PLA_RXFIFO_CTRL1 */
+#define URE_RXFIFO_THR2_FULL 0x00000060
+#define URE_RXFIFO_THR2_HIGH 0x00000038
+#define URE_RXFIFO_THR2_OOB 0x0000004a
+#define URE_RXFIFO_THR2_NORMAL 0x00a0
+
+/* PLA_RXFIFO_CTRL2 */
+#define URE_RXFIFO_THR3_FULL 0x00000078
+#define URE_RXFIFO_THR3_HIGH 0x00000048
+#define URE_RXFIFO_THR3_OOB 0x0000005a
+#define URE_RXFIFO_THR3_NORMAL 0x0110
+
+/* PLA_TXFIFO_CTRL */
+#define URE_TXFIFO_THR_NORMAL 0x00400008
+#define URE_TXFIFO_THR_NORMAL2 0x01000008
+
+/* PLA_DMY_REG0 */
+#define URE_ECM_ALDPS 0x0002
+
+/* PLA_FMC */
+#define URE_FMC_FCR_MCU_EN 0x0001
+
+/* PLA_EEEP_CR */
+#define URE_EEEP_CR_EEEP_TX 0x0002
+
+/* PLA_WDT6_CTRL */
+#define URE_WDT6_SET_MODE 0x0010
+
+/* PLA_TCR0 */
+#define URE_TCR0_TX_EMPTY 0x0800
+#define URE_TCR0_AUTO_FIFO 0x0080
+
+/* PLA_TCR1 */
+#define URE_VERSION_MASK 0x7cf0
+
+/* PLA_MTPS */
+#define URE_MTPS_DEFAULT 96
+#define URE_MTPS_JUMBO 192
+
+/* PLA_RSTTALLY */
+#define URE_TALLY_RESET 0x0001
+
+/* PLA_CR */
+#define URE_CR_RST 0x10
+#define URE_CR_RE 0x08
+#define URE_CR_TE 0x04
+
+/* PLA_CRWECR */
+#define URE_CRWECR_NORAML 0x00
+#define URE_CRWECR_CONFIG 0xc0
+
+/* PLA_OOB_CTRL */
+#define URE_NOW_IS_OOB 0x80
+#define URE_TXFIFO_EMPTY 0x20
+#define URE_RXFIFO_EMPTY 0x10
+#define URE_LINK_LIST_READY 0x02
+#define URE_DIS_MCU_CLROOB 0x01
+#define URE_FIFO_EMPTY (URE_TXFIFO_EMPTY | URE_RXFIFO_EMPTY)
+
+/* PLA_MISC_1 */
+#define URE_RXDY_GATED_EN 0x0008
+
+/* PLA_SFF_STS_7 */
+#define URE_RE_INIT_LL 0x8000
+#define URE_MCU_BORW_EN 0x4000
+
+/* PLA_CPCR */
+#define URE_CPCR_RX_VLAN 0x0040
+
+/* PLA_TEREDO_CFG */
+#define URE_TEREDO_SEL 0x8000
+#define URE_TEREDO_WAKE_MASK 0x7f00
+#define URE_TEREDO_RS_EVENT_MASK 0x00fe
+#define URE_OOB_TEREDO_EN 0x0001
+
+/* PAL_BDC_CR */
+#define URE_ALDPS_PROXY_MODE 0x0001
+
+/* URE_PLA_CONFIG34 */
+#define URE_LINK_OFF_WAKE_EN 0x0008
+#define URE_LINK_ON_WAKE_EN 0x0010
+
+/* PLA_CONFIG5 */
+#define URE_LAN_WAKE_EN 0x0002
+
+/* PLA_LED_FEATURE */
+#define URE_LED_MODE_MASK 0x0700
+
+/* PLA_PHY_PWR */
+#define URE_TX_10M_IDLE_EN 0x0080
+#define URE_PFM_PWM_SWITCH 0x0040
+
+/* PLA_MAC_PWR_CTRL */
+#define URE_D3_CLK_GATED_EN 0x00004000
+#define URE_MCU_CLK_RATIO 0x07010f07
+#define URE_MCU_CLK_RATIO_MASK 0x0f0f0f0f
+#define URE_ALDPS_SPDWN_RATIO 0x0f87
+
+/* PLA_MAC_PWR_CTRL2 */
+#define URE_MAC_CLK_SPDWN_EN 0x8000
+#define URE_EEE_SPDWN_RATIO 0x8007
+
+/* PLA_MAC_PWR_CTRL3 */
+#define URE_PLA_MCU_SPDWN_EN 0x4000
+#define URE_PKT_AVAIL_SPDWN_EN 0x0100
+#define URE_SUSPEND_SPDWN_EN 0x0004
+#define URE_U1U2_SPDWN_EN 0x0002
+#define URE_L1_SPDWN_EN 0x0001
+
+/* PLA_MAC_PWR_CTRL4 */
+#define URE_PWRSAVE_SPDWN_EN 0x1000
+#define URE_RXDV_SPDWN_EN 0x0800
+#define URE_TX10MIDLE_EN 0x0100
+#define URE_TP100_SPDWN_EN 0x0020
+#define URE_TP500_SPDWN_EN 0x0010
+#define URE_TP1000_SPDWN_EN 0x0008
+#define URE_EEE_SPDWN_EN 0x0001
+
+/* PLA_GPHY_INTR_IMR */
+#define URE_GPHY_STS_MSK 0x0001
+#define URE_SPEED_DOWN_MSK 0x0002
+#define URE_SPDWN_RXDV_MSK 0x0004
+#define URE_SPDWN_LINKCHG_MSK 0x0008
+
+/* PLA_PHYAR */
+#define URE_PHYAR_PHYDATA 0x0000ffff
+#define URE_PHYAR_BUSY 0x80000000
+
+/* PLA_EEE_CR */
+#define URE_EEE_RX_EN 0x0001
+#define URE_EEE_TX_EN 0x0002
+
+/* PLA_BOOT_CTRL */
+#define URE_AUTOLOAD_DONE 0x0002
+
+/* PLA_SUSPEND_FLAG */
+#define URE_LINK_CHG_EVENT 0x01
+
+/* PLA_INDICATE_FALG */
+#define URE_UPCOMING_RUNTIME_D3 0x01
+
+/* PLA_EXTRA_STATUS */
+#define URE_POLL_LINK_CHG 0x0001
+#define URE_LINK_CHANGE_FLAG 0x0100
+#define URE_CUR_LINK_OK 0x8000
+
+/* URE_PLA_PHYSTATUS */
+#define URE_PHYSTATUS_FDX 0x0001
+#define URE_PHYSTATUS_LINK 0x0002
+#define URE_PHYSTATUS_10MBPS 0x0004
+#define URE_PHYSTATUS_100MBPS 0x0008
+#define URE_PHYSTATUS_1000MBPS 0x0010
+#define URE_PHYSTATUS_500MBPS 0x0100
+#define URE_PHYSTATUS_1250MBPS 0x0200
+#define URE_PHYSTATUS_2500MBPS 0x0400
+
+/* USB_USB2PHY */
+#define URE_USB2PHY_SUSPEND 0x0001
+#define URE_USB2PHY_L1 0x0002
+
+/* USB_SSPHYLINK2 */
+#define URE_PWD_DN_SCALE_MASK 0x3ffe
+#define URE_PWD_DN_SCALE(x) ((x) << 1)
+
+/* USB_CSR_DUMMY1 */
+#define URE_DYNAMIC_BURST 0x0001
+
+/* USB_CSR_DUMMY2 */
+#define URE_EP4_FULL_FC 0x0001
+
+/* USB_DEV_STAT */
+#define URE_STAT_SPEED_MASK 0x0006
+#define URE_STAT_SPEED_HIGH 0x0000
+#define URE_STAT_SPEED_FULL 0x0001
+
+/* URE_USB_LPM_CONFIG */
+#define URE_LPM_U1U2_EN 0x0001
+
+/* USB_TX_AGG */
+#define URE_TX_AGG_MAX_THRESHOLD 0x03
+
+/* USB_RX_BUF_TH */
+#define URE_RX_THR_SUPER 0x0c350180
+#define URE_RX_THR_HIGH 0x7a120180
+#define URE_RX_THR_SLOW 0xffff0180
+#define URE_RX_THR_B 0x00010001
+
+/* USB_TX_DMA */
+#define URE_TEST_MODE_DISABLE 0x00000001
+#define URE_TX_SIZE_ADJUST1 0x00000100
+
+/* USB_BMU_RESET */
+#define URE_BMU_RESET_EP_IN 0x01
+#define URE_BMU_RESET_EP_OUT 0x02
+
+/* USB_UPT_RXDMA_OWN */
+#define URE_OWN_UPDATE 0x01
+#define URE_OWN_CLEAR 0x02
+
+/* USB_FW_TASK */
+#define URE_FC_PATCH_TASK 0x0001
+
+/* USB_UPS_CTRL */
+#define URE_POWER_CUT 0x0100
+
+/* USB_PM_CTRL_STATUS */
+#define URE_RESUME_INDICATE 0x0001
+
+/* USB_FW_CTRL */
+#define URE_FLOW_CTRL_PATCH_OPT 0x01
+
+/* USB_FC_TIMER */
+#define URE_CTRL_TIMER_EN 0x8000
+
+/* USB_USB_CTRL */
+#define URE_RX_AGG_DISABLE 0x0010
+#define URE_RX_ZERO_EN 0x0080
+
+/* USB_U2P3_CTRL */
+#define URE_U2P3_ENABLE 0x0001
+
+/* USB_POWER_CUT */
+#define URE_PWR_EN 0x0001
+#define URE_PHASE2_EN 0x0008
+#define URE_UPS_EN 0x0010
+#define URE_USP_PREWAKE 0x0020
+
+/* USB_MISC_0 */
+#define URE_PCUT_STATUS 0x0001
+
+/* USB_RX_EARLY_TIMEOUT */
+#define URE_COALESCE_SUPER 85000U
+#define URE_COALESCE_HIGH 250000U
+#define URE_COALESCE_SLOW 524280U
+
+/* USB_WDT11_CTRL */
+#define URE_TIMER11_EN 0x0001
+
+/* USB_LPM_CTRL */
+#define URE_FIFO_EMPTY_1FB 0x30
+#define URE_LPM_TIMER_MASK 0x0c
+#define URE_LPM_TIMER_500MS 0x04
+#define URE_LPM_TIMER_500US 0x0c
+#define URE_ROK_EXIT_LPM 0x02
+
+/* USB_AFE_CTRL2 */
+#define URE_SEN_VAL_MASK 0xf800
+#define URE_SEN_VAL_NORMAL 0xa000
+#define URE_SEL_RXIDLE 0x0100
+
+/* OCP_ALDPS_CONFIG */
+#define URE_ENPWRSAVE 0x8000
+#define URE_ENPDNPS 0x0200
+#define URE_LINKENA 0x0100
+#define URE_DIS_SDSAVE 0x0010
+
+/* OCP_PHY_STATUS */
+#define URE_PHY_STAT_MASK 0x0007
+#define URE_PHY_STAT_EXT_INIT 2
+#define URE_PHY_STAT_LAN_ON 3
+#define URE_PHY_STAT_PWRDN 5
+
+/* OCP_POWER_CFG */
+#define URE_EEE_CLKDIV_EN 0x8000
+#define URE_EN_ALDPS 0x0004
+#define URE_EN_10M_PLLOFF 0x0001
+
+/* OCP_EEE_CFG */
+#define URE_CTAP_SHORT_EN 0x0040
+#define URE_EEE10_EN 0x0010
+
+/* OCP_DOWN_SPEED */
+#define URE_EN_10M_BGOFF 0x0080
+#define URE_EN_10M_CLKDIV 0x0800
+#define URE_EN_EEE_100 0x1000
+#define URE_EN_EEE_1000 0x2000
+#define URE_EN_EEE_CMODE 0x4000
+
+/* OCP_PHY_STATE */
+#define URE_TXDIS_STATE 0x01
+#define URE_ABD_STATE 0x02
+
+/* OCP_PHY_PATCH_STAT */
+#define URE_PATCH_READY 0x40
+
+/* OCP_PHY_PATCH_CMD */
+#define URE_PATCH_REQUEST 0x10
+
+/* OCP_PHY_LOCK */
+#define URE_PATCH_LOCK 0x01
+
+/* OCP_ADC_CFG */
+#define URE_CKADSEL_L 0x0100
+#define URE_ADC_EN 0x0080
+#define URE_EN_EMI_L 0x0040
+
+/* SRAM_GREEN_CFG */
+#define URE_GREEN_ETH_EN 0x8000
+
+/* SRAM_PHY_LOCK */
+#define URE_PHY_PATCH_LOCK 0x0001
+
+#define URE_ADV_2500TFDX 0x0080
+
+#define URE_MCU_TYPE_PLA 0x0100
+#define URE_MCU_TYPE_USB 0x0000
+
+#define GET_MII(sc) uether_getmii(&(sc)->sc_ue)
+
+struct ure_intrpkt {
+ uint8_t ure_tsr;
+ uint8_t ure_rsr;
+ uint8_t ure_gep_msr;
+ uint8_t ure_waksr;
+ uint8_t ure_txok_cnt;
+ uint8_t ure_rxlost_cnt;
+ uint8_t ure_crcerr_cnt;
+ uint8_t ure_col_cnt;
+} __packed;
+
+#define URE_RXPKT_ALIGN 8
+struct ure_rxpkt {
+ uint32_t ure_pktlen;
+#define URE_RXPKT_LEN_MASK 0x7fff
+ uint32_t ure_csum;
+/* Linux driver has this in ure_misc, but my device has it in ure_csum */
+#define URE_RXPKT_VLAN_MASK 0xffff
+#define URE_RXPKT_RX_VLAN_TAG (1 << 16)
+#define URE_RXPKT_IPV4_CS (1 << 19)
+#define URE_RXPKT_IPV6_CS (1 << 20)
+#define URE_RXPKT_TCP_CS (1 << 22)
+#define URE_RXPKT_UDP_CS (1 << 23)
+ uint32_t ure_misc;
+#define URE_RXPKT_TCP_F (1 << 21)
+#define URE_RXPKT_UDP_F (1 << 22)
+#define URE_RXPKT_IP_F (1 << 23)
+ uint32_t ure_rsvd2;
+ uint32_t ure_rsvd3;
+ uint32_t ure_rsvd4;
+} __packed;
+
+#define URE_TXPKT_ALIGN 4
+struct ure_txpkt {
+ uint32_t ure_pktlen;
+#define URE_TKPKT_TX_FS (1 << 31)
+#define URE_TKPKT_TX_LS (1 << 30)
+#define URE_TXPKT_LEN_MASK 0xffff
+ uint32_t ure_csum;
+#define URE_L4_OFFSET_MAX 0x7ff
+#define URE_L4_OFFSET_SHIFT 17
+#define URE_TXPKT_VLAN_MASK 0xffff
+#define URE_TXPKT_VLAN (1 << 16)
+#define URE_TXPKT_IPV6_CS (1 << 28)
+#define URE_TXPKT_IPV4_CS (1 << 29)
+#define URE_TXPKT_TCP_CS (1 << 30)
+#define URE_TXPKT_UDP_CS (1 << 31)
+/* Lower 12 bits are the VLAN tag */
+} __packed;
+
+#define URE_MAX_TX 4
+#define URE_MAX_RX 4
+
+#define URE_TX_BUFSZ 16384
+#define URE_8152_RX_BUFSZ (16 * 1024)
+#define URE_8153_RX_BUFSZ (32 * 1024)
+#define URE_8156_RX_BUFSZ (48 * 1024)
+
+
+struct ure_softc {
+ struct usb_ether sc_ue;
+ struct ifmedia sc_ifmedia;
+ struct mtx sc_mtx;
+ struct usb_xfer *sc_rx_xfer[URE_MAX_RX];
+ struct usb_xfer *sc_tx_xfer[URE_MAX_TX];
+
+ int sc_rxbufsz;
+ int sc_rxstarted;
+
+ int sc_phyno;
+
+ u_int sc_flags;
+#define URE_FLAG_LINK 0x0001
+#define URE_FLAG_8152 0x0100 /* RTL8152 */
+#define URE_FLAG_8153 0x0200 /* RTL8153 */
+#define URE_FLAG_8153B 0x0400 /* RTL8153B */
+#define URE_FLAG_8156 0x0800 /* RTL8156 */
+#define URE_FLAG_8156B 0x1000 /* RTL8156B */
+
+ u_int sc_chip;
+ u_int sc_ver;
+#define URE_CHIP_VER_4C00 0x0001
+#define URE_CHIP_VER_4C10 0x0002
+#define URE_CHIP_VER_5C00 0x0004
+#define URE_CHIP_VER_5C10 0x0008
+#define URE_CHIP_VER_5C20 0x0010
+#define URE_CHIP_VER_5C30 0x0020
+#define URE_CHIP_VER_6000 0x0040
+#define URE_CHIP_VER_6010 0x0080
+#define URE_CHIP_VER_7020 0x0100
+#define URE_CHIP_VER_7030 0x0200
+#define URE_CHIP_VER_7400 0x0400
+#define URE_CHIP_VER_7410 0x0800
+};
+
+#define URE_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
+#define URE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
+#define URE_LOCK_ASSERT(_sc, t) mtx_assert(&(_sc)->sc_mtx, t)
+
+#endif /* _IF_UREREG_H_ */
diff --git a/sys/dev/usb/net/if_urndis.c b/sys/dev/usb/net/if_urndis.c
new file mode 100644
index 000000000000..4b0582442e30
--- /dev/null
+++ b/sys/dev/usb/net/if_urndis.c
@@ -0,0 +1,1057 @@
+/* $OpenBSD: if_urndis.c,v 1.46 2013/12/09 15:45:29 pirofti Exp $ */
+
+/*
+ * Copyright (c) 2010 Jonathan Armani <armani@openbsd.org>
+ * Copyright (c) 2010 Fabien Romano <fabien@openbsd.org>
+ * Copyright (c) 2010 Michael Knudsen <mk@openbsd.org>
+ * Copyright (c) 2014 Hans Petter Selasky <hselasky@freebsd.org>
+ * All rights reserved.
+ *
+ * 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/rndis.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR urndis_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include "usb_if.h"
+
+#include <dev/usb/net/usb_ethernet.h>
+#include <dev/usb/net/if_urndisreg.h>
+
+#include <dev/usb/usb_cdc.h>
+
+static device_probe_t urndis_probe;
+static device_attach_t urndis_attach;
+static device_detach_t urndis_detach;
+static device_suspend_t urndis_suspend;
+static device_resume_t urndis_resume;
+
+static usb_callback_t urndis_bulk_write_callback;
+static usb_callback_t urndis_bulk_read_callback;
+static usb_callback_t urndis_intr_read_callback;
+
+static uether_fn_t urndis_attach_post;
+static uether_fn_t urndis_init;
+static uether_fn_t urndis_stop;
+static uether_fn_t urndis_start;
+static uether_fn_t urndis_setmulti;
+static uether_fn_t urndis_setpromisc;
+
+static uint32_t urndis_ctrl_query(struct urndis_softc *sc, uint32_t oid,
+ struct rndis_query_req *msg, uint16_t len,
+ const void **rbuf, uint16_t *rbufsz);
+static uint32_t urndis_ctrl_set(struct urndis_softc *sc, uint32_t oid,
+ struct rndis_set_req *msg, uint16_t len);
+static uint32_t urndis_ctrl_handle_init(struct urndis_softc *sc,
+ const struct rndis_comp_hdr *hdr);
+static uint32_t urndis_ctrl_handle_query(struct urndis_softc *sc,
+ const struct rndis_comp_hdr *hdr, const void **buf,
+ uint16_t *bufsz);
+static uint32_t urndis_ctrl_handle_reset(struct urndis_softc *sc,
+ const struct rndis_comp_hdr *hdr);
+static uint32_t urndis_ctrl_init(struct urndis_softc *sc);
+static uint32_t urndis_ctrl_halt(struct urndis_softc *sc);
+
+#ifdef USB_DEBUG
+static int urndis_debug = 0;
+static SYSCTL_NODE(_hw_usb, OID_AUTO, urndis, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB RNDIS-Ethernet");
+SYSCTL_INT(_hw_usb_urndis, OID_AUTO, debug, CTLFLAG_RWTUN, &urndis_debug, 0,
+ "Debug level");
+#endif
+
+static const struct usb_config urndis_config[URNDIS_N_TRANSFER] = {
+ [URNDIS_BULK_RX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_RX,
+ .if_index = 0,
+ .frames = 1,
+ .bufsize = RNDIS_RX_MAXLEN,
+ .flags = {.short_xfer_ok = 1,},
+ .callback = urndis_bulk_read_callback,
+ .timeout = 0, /* no timeout */
+ .usb_mode = USB_MODE_HOST,
+ },
+
+ [URNDIS_BULK_TX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .if_index = 0,
+ .frames = RNDIS_TX_FRAMES_MAX,
+ .bufsize = (RNDIS_TX_FRAMES_MAX * RNDIS_TX_MAXLEN),
+ .flags = {
+ .force_short_xfer = 1,
+ },
+ .callback = urndis_bulk_write_callback,
+ .timeout = 10000, /* 10 seconds */
+ .usb_mode = USB_MODE_HOST,
+ },
+
+ [URNDIS_INTR_RX] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_RX,
+ .if_index = 1,
+ .bufsize = 0, /* use wMaxPacketSize */
+ .flags = {.short_xfer_ok = 1,.no_pipe_ok = 1,},
+ .callback = urndis_intr_read_callback,
+ .timeout = 0,
+ .usb_mode = USB_MODE_HOST,
+ },
+};
+
+static device_method_t urndis_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, urndis_probe),
+ DEVMETHOD(device_attach, urndis_attach),
+ DEVMETHOD(device_detach, urndis_detach),
+ DEVMETHOD(device_suspend, urndis_suspend),
+ DEVMETHOD(device_resume, urndis_resume),
+
+ DEVMETHOD_END
+};
+
+static driver_t urndis_driver = {
+ .name = "urndis",
+ .methods = urndis_methods,
+ .size = sizeof(struct urndis_softc),
+};
+
+static const STRUCT_USB_HOST_ID urndis_host_devs[] = {
+ /* Generic RNDIS class match */
+ {USB_IFACE_CLASS(UICLASS_CDC),
+ USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL),
+ USB_IFACE_PROTOCOL(0xff)},
+ {USB_IFACE_CLASS(UICLASS_WIRELESS), USB_IFACE_SUBCLASS(UISUBCLASS_RF),
+ USB_IFACE_PROTOCOL(UIPROTO_RNDIS)},
+ {USB_IFACE_CLASS(UICLASS_IAD), USB_IFACE_SUBCLASS(UISUBCLASS_SYNC),
+ USB_IFACE_PROTOCOL(UIPROTO_ACTIVESYNC)},
+ /* HP-WebOS */
+ {USB_VENDOR(USB_VENDOR_PALM), USB_IFACE_CLASS(UICLASS_CDC),
+ USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL),
+ USB_IFACE_PROTOCOL(0xff)},
+ /* Nokia 7 plus */
+ {USB_IFACE_CLASS(UICLASS_IAD), USB_IFACE_SUBCLASS(0x4),
+ USB_IFACE_PROTOCOL(UIPROTO_ACTIVESYNC)},
+ /* Novatel Wireless 8800/8000/etc */
+ {USB_IFACE_CLASS(UICLASS_IAD), USB_IFACE_SUBCLASS(0xef),
+ USB_IFACE_PROTOCOL(UIPROTO_RNDIS)},
+};
+
+DRIVER_MODULE(urndis, uhub, urndis_driver, NULL, NULL);
+MODULE_VERSION(urndis, 1);
+MODULE_DEPEND(urndis, uether, 1, 1, 1);
+MODULE_DEPEND(urndis, usb, 1, 1, 1);
+MODULE_DEPEND(urndis, ether, 1, 1, 1);
+USB_PNP_HOST_INFO(urndis_host_devs);
+
+static const struct usb_ether_methods urndis_ue_methods = {
+ .ue_attach_post = urndis_attach_post,
+ .ue_start = urndis_start,
+ .ue_init = urndis_init,
+ .ue_stop = urndis_stop,
+ .ue_setmulti = urndis_setmulti,
+ .ue_setpromisc = urndis_setpromisc,
+};
+
+static int
+urndis_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ return (usbd_lookup_id_by_uaa(urndis_host_devs, sizeof(urndis_host_devs), uaa));
+}
+
+static void
+urndis_attach_post(struct usb_ether *ue)
+{
+
+ /* no-op */
+}
+
+static int
+urndis_attach(device_t dev)
+{
+ union {
+ struct {
+ struct rndis_query_req query;
+ uint8_t addr[ETHER_ADDR_LEN];
+ } eaddr;
+ struct {
+ struct rndis_set_req set;
+ uint32_t filter;
+ } filter;
+ } msg;
+ struct urndis_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct usb_cdc_cm_descriptor *cmd;
+ const void *buf;
+ uint16_t bufsz;
+ uint8_t iface_index[2] = { uaa->info.bIfaceIndex + 1, uaa->info.bIfaceIndex };
+ int error;
+ uint8_t i;
+
+ sc->sc_ue.ue_udev = uaa->device;
+ sc->sc_ifaceno_ctl = uaa->info.bIfaceNum;
+
+ cmd = usbd_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex,
+ UDESC_CS_INTERFACE, 0xFF, UDESCSUB_CDC_CM, 0xFF);
+ if (cmd != NULL) {
+ DPRINTF("Call Mode Descriptor found, dataif=%d\n", cmd->bDataInterface);
+ iface_index[0] = cmd->bDataInterface;
+ }
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ /* scan the alternate settings looking for a valid one */
+ for (i = 0; i != 32; i++) {
+ error = usbd_set_alt_interface_index(uaa->device,
+ iface_index[0], i);
+
+ if (error != 0)
+ break;
+
+ error = usbd_transfer_setup(uaa->device,
+ iface_index, sc->sc_xfer, urndis_config,
+ URNDIS_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error == 0)
+ break;
+ }
+ if ((error != 0) || (i == 32)) {
+ device_printf(dev, "No valid alternate setting found\n");
+ goto detach;
+ }
+
+ /* Initialize device - must be done before even querying it */
+ URNDIS_LOCK(sc);
+ error = urndis_ctrl_init(sc);
+ URNDIS_UNLOCK(sc);
+ if (error != (int)RNDIS_STATUS_SUCCESS) {
+ device_printf(dev, "Unable to initialize hardware\n");
+ goto detach;
+ }
+
+ /* Determine MAC address */
+ memset(msg.eaddr.addr, 0, sizeof(msg.eaddr.addr));
+ URNDIS_LOCK(sc);
+ error = urndis_ctrl_query(sc, OID_802_3_PERMANENT_ADDRESS,
+ (struct rndis_query_req *)&msg.eaddr, sizeof(msg.eaddr),
+ &buf, &bufsz);
+ URNDIS_UNLOCK(sc);
+ if (error != (int)RNDIS_STATUS_SUCCESS) {
+ device_printf(dev, "Unable to get hardware address\n");
+ goto detach;
+ }
+ if (bufsz != ETHER_ADDR_LEN) {
+ device_printf(dev, "Invalid address length: %d bytes\n", bufsz);
+ goto detach;
+ }
+ memcpy(&sc->sc_ue.ue_eaddr, buf, ETHER_ADDR_LEN);
+
+ /* Initialize packet filter */
+ sc->sc_filter = NDIS_PACKET_TYPE_BROADCAST |
+ NDIS_PACKET_TYPE_ALL_MULTICAST;
+ msg.filter.filter = htole32(sc->sc_filter);
+ URNDIS_LOCK(sc);
+ error = urndis_ctrl_set(sc, OID_GEN_CURRENT_PACKET_FILTER,
+ (struct rndis_set_req *)&msg.filter, sizeof(msg.filter));
+ URNDIS_UNLOCK(sc);
+ if (error != (int)RNDIS_STATUS_SUCCESS) {
+ device_printf(dev, "Unable to set data filters\n");
+ goto detach;
+ }
+
+ ue->ue_sc = sc;
+ ue->ue_dev = dev;
+ ue->ue_udev = uaa->device;
+ ue->ue_mtx = &sc->sc_mtx;
+ ue->ue_methods = &urndis_ue_methods;
+
+ error = uether_ifattach(ue);
+ if (error) {
+ device_printf(dev, "Could not attach interface\n");
+ goto detach;
+ }
+
+ URNDIS_LOCK(sc);
+ /* start interrupt endpoint, if any */
+ usbd_transfer_start(sc->sc_xfer[URNDIS_INTR_RX]);
+ URNDIS_UNLOCK(sc);
+
+ return (0); /* success */
+
+detach:
+ (void)urndis_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+urndis_detach(device_t dev)
+{
+ struct urndis_softc *sc = device_get_softc(dev);
+ struct usb_ether *ue = &sc->sc_ue;
+
+ /* stop all USB transfers first */
+ usbd_transfer_unsetup(sc->sc_xfer, URNDIS_N_TRANSFER);
+
+ uether_ifdetach(ue);
+
+ URNDIS_LOCK(sc);
+ (void)urndis_ctrl_halt(sc);
+ URNDIS_UNLOCK(sc);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+urndis_start(struct usb_ether *ue)
+{
+ struct urndis_softc *sc = uether_getsc(ue);
+
+ /*
+ * Start the USB transfers, if not already started:
+ */
+ usbd_transfer_start(sc->sc_xfer[URNDIS_BULK_TX]);
+ usbd_transfer_start(sc->sc_xfer[URNDIS_BULK_RX]);
+}
+
+static void
+urndis_init(struct usb_ether *ue)
+{
+ struct urndis_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ URNDIS_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+
+ /* stall data write direction, which depends on USB mode */
+ usbd_xfer_set_stall(sc->sc_xfer[URNDIS_BULK_TX]);
+
+ /* start data transfers */
+ urndis_start(ue);
+}
+
+static void
+urndis_stop(struct usb_ether *ue)
+{
+ struct urndis_softc *sc = uether_getsc(ue);
+ if_t ifp = uether_getifp(ue);
+
+ URNDIS_LOCK_ASSERT(sc, MA_OWNED);
+
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+
+ /*
+ * stop all the transfers, if not already stopped:
+ */
+ usbd_transfer_stop(sc->sc_xfer[URNDIS_BULK_RX]);
+ usbd_transfer_stop(sc->sc_xfer[URNDIS_BULK_TX]);
+}
+
+static void
+urndis_setmulti(struct usb_ether *ue)
+{
+
+ /* no-op */
+}
+
+static void
+urndis_setpromisc(struct usb_ether *ue)
+{
+
+ /* no-op */
+}
+
+static int
+urndis_suspend(device_t dev)
+{
+
+ device_printf(dev, "Suspending\n");
+ return (0);
+}
+
+static int
+urndis_resume(device_t dev)
+{
+
+ device_printf(dev, "Resuming\n");
+ return (0);
+}
+
+static usb_error_t
+urndis_ctrl_msg(struct urndis_softc *sc, uint8_t rt, uint8_t r,
+ uint16_t index, uint16_t value, void *buf, uint16_t buflen)
+{
+ usb_device_request_t req;
+
+ req.bmRequestType = rt;
+ req.bRequest = r;
+ USETW(req.wValue, value);
+ USETW(req.wIndex, index);
+ USETW(req.wLength, buflen);
+
+ return (usbd_do_request_flags(sc->sc_ue.ue_udev,
+ &sc->sc_mtx, &req, buf, (rt & UT_READ) ?
+ USB_SHORT_XFER_OK : 0, NULL, 2000 /* ms */ ));
+}
+
+static usb_error_t
+urndis_ctrl_send(struct urndis_softc *sc, void *buf, uint16_t len)
+{
+ usb_error_t err;
+
+ err = urndis_ctrl_msg(sc, UT_WRITE_CLASS_INTERFACE,
+ UCDC_SEND_ENCAPSULATED_COMMAND, sc->sc_ifaceno_ctl, 0, buf, len);
+
+ DPRINTF("%s\n", usbd_errstr(err));
+
+ return (err);
+}
+
+static struct rndis_comp_hdr *
+urndis_ctrl_recv(struct urndis_softc *sc)
+{
+ struct rndis_comp_hdr *hdr;
+ usb_error_t err;
+
+ err = urndis_ctrl_msg(sc, UT_READ_CLASS_INTERFACE,
+ UCDC_GET_ENCAPSULATED_RESPONSE, sc->sc_ifaceno_ctl, 0,
+ sc->sc_response_buf, RNDIS_RESPONSE_LEN);
+
+ if (err != USB_ERR_NORMAL_COMPLETION)
+ return (NULL);
+
+ hdr = (struct rndis_comp_hdr *)sc->sc_response_buf;
+
+ DPRINTF("type 0x%x len %u\n", le32toh(hdr->rm_type),
+ le32toh(hdr->rm_len));
+
+ if (le32toh(hdr->rm_len) > RNDIS_RESPONSE_LEN) {
+ DPRINTF("ctrl message error: wrong size %u > %u\n",
+ le32toh(hdr->rm_len), RNDIS_RESPONSE_LEN);
+ return (NULL);
+ }
+ return (hdr);
+}
+
+static uint32_t
+urndis_ctrl_handle(struct urndis_softc *sc, struct rndis_comp_hdr *hdr,
+ const void **buf, uint16_t *bufsz)
+{
+ uint32_t rval;
+
+ DPRINTF("\n");
+
+ if (buf != NULL && bufsz != NULL) {
+ *buf = NULL;
+ *bufsz = 0;
+ }
+ switch (le32toh(hdr->rm_type)) {
+ case REMOTE_NDIS_INITIALIZE_CMPLT:
+ rval = urndis_ctrl_handle_init(sc, hdr);
+ break;
+
+ case REMOTE_NDIS_QUERY_CMPLT:
+ rval = urndis_ctrl_handle_query(sc, hdr, buf, bufsz);
+ break;
+
+ case REMOTE_NDIS_RESET_CMPLT:
+ rval = urndis_ctrl_handle_reset(sc, hdr);
+ break;
+
+ case REMOTE_NDIS_KEEPALIVE_CMPLT:
+ case REMOTE_NDIS_SET_CMPLT:
+ rval = le32toh(hdr->rm_status);
+ break;
+
+ default:
+ device_printf(sc->sc_ue.ue_dev,
+ "ctrl message error: unknown event 0x%x\n",
+ le32toh(hdr->rm_type));
+ rval = RNDIS_STATUS_FAILURE;
+ break;
+ }
+ return (rval);
+}
+
+static uint32_t
+urndis_ctrl_handle_init(struct urndis_softc *sc,
+ const struct rndis_comp_hdr *hdr)
+{
+ const struct rndis_init_comp *msg;
+
+ msg = (const struct rndis_init_comp *)hdr;
+
+ DPRINTF("len %u rid %u status 0x%x "
+ "ver_major %u ver_minor %u devflags 0x%x medium 0x%x pktmaxcnt %u "
+ "pktmaxsz %u align %u aflistoffset %u aflistsz %u\n",
+ le32toh(msg->rm_len),
+ le32toh(msg->rm_rid),
+ le32toh(msg->rm_status),
+ le32toh(msg->rm_ver_major),
+ le32toh(msg->rm_ver_minor),
+ le32toh(msg->rm_devflags),
+ le32toh(msg->rm_medium),
+ le32toh(msg->rm_pktmaxcnt),
+ le32toh(msg->rm_pktmaxsz),
+ le32toh(msg->rm_align),
+ le32toh(msg->rm_aflistoffset),
+ le32toh(msg->rm_aflistsz));
+
+ if (le32toh(msg->rm_status) != RNDIS_STATUS_SUCCESS) {
+ DPRINTF("init failed 0x%x\n", le32toh(msg->rm_status));
+ return (le32toh(msg->rm_status));
+ }
+ if (le32toh(msg->rm_devflags) != RNDIS_DF_CONNECTIONLESS) {
+ DPRINTF("wrong device type (current type: 0x%x)\n",
+ le32toh(msg->rm_devflags));
+ return (RNDIS_STATUS_FAILURE);
+ }
+ if (le32toh(msg->rm_medium) != RNDIS_MEDIUM_802_3) {
+ DPRINTF("medium not 802.3 (current medium: 0x%x)\n",
+ le32toh(msg->rm_medium));
+ return (RNDIS_STATUS_FAILURE);
+ }
+ sc->sc_lim_pktsz = le32toh(msg->rm_pktmaxsz);
+
+ return (le32toh(msg->rm_status));
+}
+
+static uint32_t
+urndis_ctrl_handle_query(struct urndis_softc *sc,
+ const struct rndis_comp_hdr *hdr, const void **buf, uint16_t *bufsz)
+{
+ const struct rndis_query_comp *msg;
+ uint64_t limit;
+
+ msg = (const struct rndis_query_comp *)hdr;
+
+ DPRINTF("len %u rid %u status 0x%x "
+ "buflen %u bufoff %u\n",
+ le32toh(msg->rm_len),
+ le32toh(msg->rm_rid),
+ le32toh(msg->rm_status),
+ le32toh(msg->rm_infobuflen),
+ le32toh(msg->rm_infobufoffset));
+
+ *buf = NULL;
+ *bufsz = 0;
+ if (le32toh(msg->rm_status) != RNDIS_STATUS_SUCCESS) {
+ DPRINTF("query failed 0x%x\n", le32toh(msg->rm_status));
+ return (le32toh(msg->rm_status));
+ }
+ limit = le32toh(msg->rm_infobuflen);
+ limit += le32toh(msg->rm_infobufoffset);
+ limit += RNDIS_HEADER_OFFSET;
+
+ if (limit > (uint64_t)le32toh(msg->rm_len)) {
+ DPRINTF("ctrl message error: invalid query info "
+ "len/offset/end_position(%u/%u/%u) -> "
+ "go out of buffer limit %u\n",
+ le32toh(msg->rm_infobuflen),
+ le32toh(msg->rm_infobufoffset),
+ le32toh(msg->rm_infobuflen) +
+ le32toh(msg->rm_infobufoffset) + RNDIS_HEADER_OFFSET,
+ le32toh(msg->rm_len));
+ return (RNDIS_STATUS_FAILURE);
+ }
+ *buf = ((const uint8_t *)msg) + RNDIS_HEADER_OFFSET +
+ le32toh(msg->rm_infobufoffset);
+ *bufsz = le32toh(msg->rm_infobuflen);
+
+ return (le32toh(msg->rm_status));
+}
+
+static uint32_t
+urndis_ctrl_handle_reset(struct urndis_softc *sc,
+ const struct rndis_comp_hdr *hdr)
+{
+ const struct rndis_reset_comp *msg;
+ uint32_t rval;
+
+ msg = (const struct rndis_reset_comp *)hdr;
+
+ rval = le32toh(msg->rm_status);
+
+ DPRINTF("len %u status 0x%x "
+ "adrreset %u\n",
+ le32toh(msg->rm_len),
+ rval,
+ le32toh(msg->rm_adrreset));
+
+ if (rval != RNDIS_STATUS_SUCCESS) {
+ DPRINTF("reset failed 0x%x\n", rval);
+ return (rval);
+ }
+ if (msg->rm_adrreset != 0) {
+ struct {
+ struct rndis_set_req hdr;
+ uint32_t filter;
+ } msg_filter;
+
+ msg_filter.filter = htole32(sc->sc_filter);
+
+ rval = urndis_ctrl_set(sc, OID_GEN_CURRENT_PACKET_FILTER,
+ (struct rndis_set_req *)&msg_filter, sizeof(msg_filter));
+
+ if (rval != RNDIS_STATUS_SUCCESS) {
+ DPRINTF("unable to reset data filters\n");
+ return (rval);
+ }
+ }
+ return (rval);
+}
+
+static uint32_t
+urndis_ctrl_init(struct urndis_softc *sc)
+{
+ struct rndis_init_req msg;
+ struct rndis_comp_hdr *hdr;
+ uint32_t rval;
+
+ msg.rm_type = htole32(REMOTE_NDIS_INITIALIZE_MSG);
+ msg.rm_len = htole32(sizeof(msg));
+ msg.rm_rid = 0;
+ msg.rm_ver_major = htole32(RNDIS_VERSION_MAJOR);
+ msg.rm_ver_minor = htole32(1);
+ msg.rm_max_xfersz = htole32(RNDIS_RX_MAXLEN);
+
+ DPRINTF("type %u len %u rid %u ver_major %u "
+ "ver_minor %u max_xfersz %u\n",
+ le32toh(msg.rm_type),
+ le32toh(msg.rm_len),
+ le32toh(msg.rm_rid),
+ le32toh(msg.rm_ver_major),
+ le32toh(msg.rm_ver_minor),
+ le32toh(msg.rm_max_xfersz));
+
+ rval = urndis_ctrl_send(sc, &msg, sizeof(msg));
+
+ if (rval != RNDIS_STATUS_SUCCESS) {
+ DPRINTF("init failed\n");
+ return (rval);
+ }
+ if ((hdr = urndis_ctrl_recv(sc)) == NULL) {
+ DPRINTF("unable to get init response\n");
+ return (RNDIS_STATUS_FAILURE);
+ }
+ rval = urndis_ctrl_handle(sc, hdr, NULL, NULL);
+
+ return (rval);
+}
+
+static uint32_t
+urndis_ctrl_halt(struct urndis_softc *sc)
+{
+ struct rndis_halt_req msg;
+ uint32_t rval;
+
+ msg.rm_type = htole32(REMOTE_NDIS_HALT_MSG);
+ msg.rm_len = htole32(sizeof(msg));
+ msg.rm_rid = 0;
+
+ DPRINTF("type %u len %u rid %u\n",
+ le32toh(msg.rm_type),
+ le32toh(msg.rm_len),
+ le32toh(msg.rm_rid));
+
+ rval = urndis_ctrl_send(sc, &msg, sizeof(msg));
+
+ if (rval != RNDIS_STATUS_SUCCESS)
+ DPRINTF("halt failed\n");
+
+ return (rval);
+}
+
+/*
+ * NB: Querying a device has the requirement of using an input buffer the size
+ * of the expected reply or larger, except for variably sized replies.
+ */
+static uint32_t
+urndis_ctrl_query(struct urndis_softc *sc, uint32_t oid,
+ struct rndis_query_req *msg, uint16_t len, const void **rbuf,
+ uint16_t *rbufsz)
+{
+ struct rndis_comp_hdr *hdr;
+ uint32_t datalen, rval;
+
+ msg->rm_type = htole32(REMOTE_NDIS_QUERY_MSG);
+ msg->rm_len = htole32(len);
+ msg->rm_rid = 0; /* XXX */
+ msg->rm_oid = htole32(oid);
+ datalen = len - sizeof(*msg);
+ msg->rm_infobuflen = htole32(datalen);
+ if (datalen != 0) {
+ msg->rm_infobufoffset = htole32(sizeof(*msg) -
+ RNDIS_HEADER_OFFSET);
+ } else {
+ msg->rm_infobufoffset = 0;
+ }
+ msg->rm_devicevchdl = 0;
+
+ DPRINTF("type %u len %u rid %u oid 0x%x "
+ "infobuflen %u infobufoffset %u devicevchdl %u\n",
+ le32toh(msg->rm_type),
+ le32toh(msg->rm_len),
+ le32toh(msg->rm_rid),
+ le32toh(msg->rm_oid),
+ le32toh(msg->rm_infobuflen),
+ le32toh(msg->rm_infobufoffset),
+ le32toh(msg->rm_devicevchdl));
+
+ rval = urndis_ctrl_send(sc, msg, len);
+
+ if (rval != RNDIS_STATUS_SUCCESS) {
+ DPRINTF("query failed\n");
+ return (rval);
+ }
+ if ((hdr = urndis_ctrl_recv(sc)) == NULL) {
+ DPRINTF("unable to get query response\n");
+ return (RNDIS_STATUS_FAILURE);
+ }
+ rval = urndis_ctrl_handle(sc, hdr, rbuf, rbufsz);
+
+ return (rval);
+}
+
+static uint32_t
+urndis_ctrl_set(struct urndis_softc *sc, uint32_t oid,
+ struct rndis_set_req *msg, uint16_t len)
+{
+ struct rndis_comp_hdr *hdr;
+ uint32_t datalen, rval;
+
+ msg->rm_type = htole32(REMOTE_NDIS_SET_MSG);
+ msg->rm_len = htole32(len);
+ msg->rm_rid = 0; /* XXX */
+ msg->rm_oid = htole32(oid);
+ datalen = len - sizeof(*msg);
+ msg->rm_infobuflen = htole32(datalen);
+ if (datalen != 0) {
+ msg->rm_infobufoffset = htole32(sizeof(*msg) -
+ RNDIS_HEADER_OFFSET);
+ } else {
+ msg->rm_infobufoffset = 0;
+ }
+ msg->rm_devicevchdl = 0;
+
+ DPRINTF("type %u len %u rid %u oid 0x%x "
+ "infobuflen %u infobufoffset %u devicevchdl %u\n",
+ le32toh(msg->rm_type),
+ le32toh(msg->rm_len),
+ le32toh(msg->rm_rid),
+ le32toh(msg->rm_oid),
+ le32toh(msg->rm_infobuflen),
+ le32toh(msg->rm_infobufoffset),
+ le32toh(msg->rm_devicevchdl));
+
+ rval = urndis_ctrl_send(sc, msg, len);
+
+ if (rval != RNDIS_STATUS_SUCCESS) {
+ DPRINTF("set failed\n");
+ return (rval);
+ }
+ if ((hdr = urndis_ctrl_recv(sc)) == NULL) {
+ DPRINTF("unable to get set response\n");
+ return (RNDIS_STATUS_FAILURE);
+ }
+ rval = urndis_ctrl_handle(sc, hdr, NULL, NULL);
+ if (rval != RNDIS_STATUS_SUCCESS)
+ DPRINTF("set failed 0x%x\n", rval);
+
+ return (rval);
+}
+
+static void
+urndis_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct urndis_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, 0);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct rndis_packet_msg msg;
+ struct mbuf *m;
+ int actlen;
+ int aframes;
+ int offset;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ DPRINTFN(1, "received %u bytes in %u frames\n", actlen, aframes);
+
+ for (offset = 0; actlen >= (uint32_t)sizeof(msg);) {
+ /* copy out header */
+ usbd_copy_out(pc, offset, &msg, sizeof(msg));
+
+ if (le32toh(0x1234567U) != 0x1234567U) {
+ /* swap endianness */
+ msg.rm_type = le32toh(msg.rm_type);
+ msg.rm_len = le32toh(msg.rm_len);
+ msg.rm_dataoffset = le32toh(msg.rm_dataoffset);
+ msg.rm_datalen = le32toh(msg.rm_datalen);
+ msg.rm_oobdataoffset = le32toh(msg.rm_oobdataoffset);
+ msg.rm_oobdatalen = le32toh(msg.rm_oobdatalen);
+ msg.rm_oobdataelements = le32toh(msg.rm_oobdataelements);
+ msg.rm_pktinfooffset = le32toh(msg.rm_pktinfooffset);
+ msg.rm_pktinfolen = le32toh(msg.rm_pktinfolen);
+ msg.rm_vchandle = le32toh(msg.rm_vchandle);
+ msg.rm_reserved = le32toh(msg.rm_reserved);
+ }
+
+ DPRINTF("len %u data(off:%u len:%u) "
+ "oobdata(off:%u len:%u nb:%u) perpacket(off:%u len:%u)\n",
+ msg.rm_len, msg.rm_dataoffset, msg.rm_datalen,
+ msg.rm_oobdataoffset, msg.rm_oobdatalen,
+ msg.rm_oobdataelements, msg.rm_pktinfooffset,
+ msg.rm_pktinfooffset);
+
+ /* sanity check the RNDIS header */
+ if (msg.rm_type != REMOTE_NDIS_PACKET_MSG) {
+ DPRINTF("invalid type 0x%x != 0x%x\n",
+ msg.rm_type, REMOTE_NDIS_PACKET_MSG);
+ goto tr_setup;
+ } else if (msg.rm_len < (uint32_t)sizeof(msg)) {
+ DPRINTF("invalid msg len %u < %u\n",
+ msg.rm_len, (unsigned)sizeof(msg));
+ goto tr_setup;
+ } else if (msg.rm_len > (uint32_t)actlen) {
+ DPRINTF("invalid msg len %u > buffer "
+ "len %u\n", msg.rm_len, actlen);
+ goto tr_setup;
+ } else if (msg.rm_dataoffset >= (uint32_t)actlen) {
+ DPRINTF("invalid msg dataoffset %u > buffer "
+ "dataoffset %u\n", msg.rm_dataoffset, actlen);
+ goto tr_setup;
+ } else if (msg.rm_datalen > (uint32_t)actlen) {
+ DPRINTF("invalid msg datalen %u > buffer "
+ "datalen %u\n", msg.rm_datalen, actlen);
+ goto tr_setup;
+ } else if ((msg.rm_dataoffset + msg.rm_datalen +
+ (uint32_t)__offsetof(struct rndis_packet_msg,
+ rm_dataoffset)) > (uint32_t)actlen) {
+ DPRINTF("invalid dataoffset %u larger than %u\n",
+ msg.rm_dataoffset + msg.rm_datalen +
+ (uint32_t)__offsetof(struct rndis_packet_msg,
+ rm_dataoffset), actlen);
+ goto tr_setup;
+ } else if (msg.rm_datalen < (uint32_t)sizeof(struct ether_header)) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ DPRINTF("invalid ethernet size "
+ "%u < %u\n", msg.rm_datalen, (unsigned)sizeof(struct ether_header));
+ goto tr_setup;
+ } else if (msg.rm_datalen > (uint32_t)(MCLBYTES - ETHER_ALIGN)) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ DPRINTF("invalid ethernet size "
+ "%u > %u\n",
+ msg.rm_datalen, (unsigned)MCLBYTES);
+ goto tr_setup;
+ } else if (msg.rm_datalen > (uint32_t)(MHLEN - ETHER_ALIGN)) {
+ m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ } else {
+ m = m_gethdr(M_NOWAIT, MT_DATA);
+ }
+
+ /* check if we have a buffer */
+ if (m != NULL) {
+ m->m_len = m->m_pkthdr.len = msg.rm_datalen + ETHER_ALIGN;
+ m_adj(m, ETHER_ALIGN);
+
+ usbd_copy_out(pc, offset + msg.rm_dataoffset +
+ __offsetof(struct rndis_packet_msg,
+ rm_dataoffset), m->m_data, msg.rm_datalen);
+
+ /* enqueue */
+ uether_rxmbuf(&sc->sc_ue, m, msg.rm_datalen);
+ } else {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ }
+ offset += msg.rm_len;
+ actlen -= msg.rm_len;
+ }
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, RNDIS_RX_MAXLEN);
+ usbd_xfer_set_frames(xfer, 1);
+ usbd_transfer_submit(xfer);
+ uether_rxflush(&sc->sc_ue); /* must be last */
+ break;
+
+ default: /* Error */
+ DPRINTFN(1, "error = %s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ usbd_xfer_set_frames(xfer, 0);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+ }
+}
+
+static void
+urndis_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct rndis_packet_msg msg;
+ struct urndis_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = uether_getifp(&sc->sc_ue);
+ struct mbuf *m;
+ unsigned x;
+ int actlen;
+ int aframes;
+
+ usbd_xfer_status(xfer, &actlen, NULL, &aframes, NULL);
+
+ DPRINTFN(1, "\n");
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "%u bytes in %u frames\n", actlen, aframes);
+
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ memset(&msg, 0, sizeof(msg));
+
+ for (x = 0; x != RNDIS_TX_FRAMES_MAX; x++) {
+ struct usb_page_cache *pc = usbd_xfer_get_frame(xfer, x);
+
+ usbd_xfer_set_frame_offset(xfer, x * RNDIS_TX_MAXLEN, x);
+
+next_pkt:
+ m = if_dequeue(ifp);
+
+ if (m == NULL)
+ break;
+
+ if ((m->m_pkthdr.len + sizeof(msg)) > RNDIS_TX_MAXLEN) {
+ DPRINTF("Too big packet\n");
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+
+ /* Free buffer */
+ m_freem(m);
+ goto next_pkt;
+ }
+ msg.rm_type = htole32(REMOTE_NDIS_PACKET_MSG);
+ msg.rm_len = htole32(sizeof(msg) + m->m_pkthdr.len);
+
+ msg.rm_dataoffset = htole32(RNDIS_DATA_OFFSET);
+ msg.rm_datalen = htole32(m->m_pkthdr.len);
+
+ /* copy in all data */
+ usbd_copy_in(pc, 0, &msg, sizeof(msg));
+ usbd_m_copy_in(pc, sizeof(msg), m, 0, m->m_pkthdr.len);
+ usbd_xfer_set_frame_len(xfer, x, sizeof(msg) + m->m_pkthdr.len);
+
+ /*
+ * If there's a BPF listener, bounce a copy of
+ * this frame to him:
+ */
+ BPF_MTAP(ifp, m);
+
+ /* Free buffer */
+ m_freem(m);
+ }
+ if (x != 0) {
+ usbd_xfer_set_frames(xfer, x);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ DPRINTFN(11, "transfer error, %s\n", usbd_errstr(error));
+
+ /* count output errors */
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+urndis_intr_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ DPRINTF("Received %d bytes\n", actlen);
+
+ /* TODO: decode some indications */
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* start clear stall */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
diff --git a/sys/dev/usb/net/if_urndisreg.h b/sys/dev/usb/net/if_urndisreg.h
new file mode 100644
index 000000000000..ba147928eb5d
--- /dev/null
+++ b/sys/dev/usb/net/if_urndisreg.h
@@ -0,0 +1,56 @@
+/* $OpenBSD: if_urndisreg.h,v 1.19 2013/11/21 14:08:05 mpi Exp $ */
+
+/*
+ * Copyright (c) 2010 Jonathan Armani <armani@openbsd.org>
+ * Copyright (c) 2010 Fabien Romano <fabien@openbsd.org>
+ * Copyright (c) 2010 Michael Knudsen <mk@openbsd.org>
+ * All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef _IF_URNDISREG_H_
+#define _IF_URNDISREG_H_
+
+#define RNDIS_RESPONSE_LEN 1024 /* bytes */
+#define RNDIS_RX_MAXLEN (16 * 1024)
+#define RNDIS_TX_FRAMES_MAX 8
+#define RNDIS_TX_MAXLEN MCLBYTES
+
+enum {
+ URNDIS_BULK_RX,
+ URNDIS_BULK_TX,
+ URNDIS_INTR_RX,
+ URNDIS_N_TRANSFER,
+};
+
+struct urndis_softc {
+ struct usb_ether sc_ue;
+ struct mtx sc_mtx;
+
+ /* RNDIS device info */
+ uint32_t sc_lim_pktsz;
+ uint32_t sc_filter;
+
+ struct usb_device *sc_udev;
+ struct usb_xfer *sc_xfer[URNDIS_N_TRANSFER];
+
+ uint8_t sc_ifaceno_ctl;
+ uint8_t sc_response_buf[RNDIS_RESPONSE_LEN] __aligned(4);
+};
+
+#define URNDIS_LOCK(sc) mtx_lock(&(sc)->sc_mtx)
+#define URNDIS_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx)
+#define URNDIS_LOCK_ASSERT(sc, what) mtx_assert(&(sc)->sc_mtx, (what))
+
+#endif /* _IF_URNDISREG_H_ */
diff --git a/sys/dev/usb/net/if_usie.c b/sys/dev/usb/net/if_usie.c
new file mode 100644
index 000000000000..bcbf056f3844
--- /dev/null
+++ b/sys/dev/usb/net/if_usie.c
@@ -0,0 +1,1608 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2011 Anybots Inc
+ * written by Akinori Furukoshi <moonlightakkiy@yahoo.ca>
+ * - ucom part is based on u3g.c
+ *
+ * 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 "opt_inet6.h"
+
+#include <sys/param.h>
+#include <sys/eventhandler.h>
+#include <sys/systm.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/sockio.h>
+#include <sys/socket.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/malloc.h>
+#include <sys/taskqueue.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+
+#include <machine/bus.h>
+
+#include <net/if.h>
+#include <net/if_types.h>
+#include <net/netisr.h>
+#include <net/bpf.h>
+#include <net/ethernet.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+
+#include <net80211/ieee80211_ioctl.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_cdc.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR usie_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_msctest.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#include <dev/usb/net/if_usievar.h>
+
+#ifdef USB_DEBUG
+static int usie_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, usie, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "sierra USB modem");
+SYSCTL_INT(_hw_usb_usie, OID_AUTO, debug, CTLFLAG_RWTUN, &usie_debug, 0,
+ "usie debug level");
+#endif
+
+/* Sierra Wireless Direct IP modems */
+static const STRUCT_USB_HOST_ID usie_devs[] = {
+#define USIE_DEV(v, d) { \
+ USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##d) }
+ USIE_DEV(SIERRA, MC8700),
+ USIE_DEV(SIERRA, TRUINSTALL),
+ USIE_DEV(AIRPRIME, USB308),
+#undef USIE_DEV
+};
+
+static device_probe_t usie_probe;
+static device_attach_t usie_attach;
+static device_detach_t usie_detach;
+static void usie_free_softc(struct usie_softc *);
+
+static void usie_free(struct ucom_softc *);
+static void usie_uc_update_line_state(struct ucom_softc *, uint8_t);
+static void usie_uc_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *);
+static void usie_uc_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void usie_uc_cfg_set_rts(struct ucom_softc *, uint8_t);
+static void usie_uc_cfg_open(struct ucom_softc *);
+static void usie_uc_cfg_close(struct ucom_softc *);
+static void usie_uc_start_read(struct ucom_softc *);
+static void usie_uc_stop_read(struct ucom_softc *);
+static void usie_uc_start_write(struct ucom_softc *);
+static void usie_uc_stop_write(struct ucom_softc *);
+
+static usb_callback_t usie_uc_tx_callback;
+static usb_callback_t usie_uc_rx_callback;
+static usb_callback_t usie_uc_status_callback;
+static usb_callback_t usie_if_tx_callback;
+static usb_callback_t usie_if_rx_callback;
+static usb_callback_t usie_if_status_callback;
+
+static void usie_if_sync_to(void *);
+static void usie_if_sync_cb(void *, int);
+static void usie_if_status_cb(void *, int);
+
+static void usie_if_start(if_t);
+static int usie_if_output(if_t, struct mbuf *,
+ const struct sockaddr *, struct route *);
+static void usie_if_init(void *);
+static void usie_if_stop(struct usie_softc *);
+static int usie_if_ioctl(if_t, u_long, caddr_t);
+
+static int usie_do_request(struct usie_softc *, struct usb_device_request *, void *);
+static int usie_if_cmd(struct usie_softc *, uint8_t);
+static void usie_cns_req(struct usie_softc *, uint32_t, uint16_t);
+static void usie_cns_rsp(struct usie_softc *, struct usie_cns *);
+static void usie_hip_rsp(struct usie_softc *, uint8_t *, uint32_t);
+static int usie_driver_loaded(struct module *, int, void *);
+
+static const struct usb_config usie_uc_config[USIE_UC_N_XFER] = {
+ [USIE_UC_STATUS] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = 0, /* use wMaxPacketSize */
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &usie_uc_status_callback,
+ },
+ [USIE_UC_RX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = USIE_BUFSIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1,},
+ .callback = &usie_uc_rx_callback,
+ },
+ [USIE_UC_TX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = USIE_BUFSIZE,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &usie_uc_tx_callback,
+ }
+};
+
+static const struct usb_config usie_if_config[USIE_IF_N_XFER] = {
+ [USIE_IF_STATUS] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = 0, /* use wMaxPacketSize */
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &usie_if_status_callback,
+ },
+ [USIE_IF_RX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = USIE_BUFSIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &usie_if_rx_callback,
+ },
+ [USIE_IF_TX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = MAX(USIE_BUFSIZE, MCLBYTES),
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &usie_if_tx_callback,
+ }
+};
+
+static device_method_t usie_methods[] = {
+ DEVMETHOD(device_probe, usie_probe),
+ DEVMETHOD(device_attach, usie_attach),
+ DEVMETHOD(device_detach, usie_detach),
+ DEVMETHOD_END
+};
+
+static driver_t usie_driver = {
+ .name = "usie",
+ .methods = usie_methods,
+ .size = sizeof(struct usie_softc),
+};
+
+static eventhandler_tag usie_etag;
+
+DRIVER_MODULE(usie, uhub, usie_driver, usie_driver_loaded, NULL);
+MODULE_DEPEND(usie, ucom, 1, 1, 1);
+MODULE_DEPEND(usie, usb, 1, 1, 1);
+MODULE_VERSION(usie, 1);
+USB_PNP_HOST_INFO(usie_devs);
+
+static const struct ucom_callback usie_uc_callback = {
+ .ucom_cfg_get_status = &usie_uc_cfg_get_status,
+ .ucom_cfg_set_dtr = &usie_uc_cfg_set_dtr,
+ .ucom_cfg_set_rts = &usie_uc_cfg_set_rts,
+ .ucom_cfg_open = &usie_uc_cfg_open,
+ .ucom_cfg_close = &usie_uc_cfg_close,
+ .ucom_start_read = &usie_uc_start_read,
+ .ucom_stop_read = &usie_uc_stop_read,
+ .ucom_start_write = &usie_uc_start_write,
+ .ucom_stop_write = &usie_uc_stop_write,
+ .ucom_free = &usie_free,
+};
+
+static void
+usie_autoinst(void *arg, struct usb_device *udev,
+ struct usb_attach_arg *uaa)
+{
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *id;
+ struct usb_device_request req;
+ int err;
+
+ if (uaa->dev_state != UAA_DEV_READY)
+ return;
+
+ iface = usbd_get_iface(udev, 0);
+ if (iface == NULL)
+ return;
+
+ id = iface->idesc;
+ if (id == NULL || id->bInterfaceClass != UICLASS_MASS)
+ return;
+
+ if (usbd_lookup_id_by_uaa(usie_devs, sizeof(usie_devs), uaa) != 0)
+ return; /* no device match */
+
+ if (bootverbose) {
+ DPRINTF("Ejecting %s %s\n",
+ usb_get_manufacturer(udev),
+ usb_get_product(udev));
+ }
+ 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);
+
+ /* at this moment there is no mutex */
+ err = usbd_do_request_flags(udev, NULL, &req,
+ NULL, 0, NULL, 250 /* ms */ );
+
+ /* success, mark the udev as disappearing */
+ if (err == 0)
+ uaa->dev_state = UAA_DEV_EJECTING;
+}
+
+static int
+usie_probe(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != USIE_CNFG_INDEX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != USIE_IFACE_INDEX)
+ return (ENXIO);
+ if (uaa->info.bInterfaceClass != UICLASS_VENDOR)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(usie_devs, sizeof(usie_devs), uaa));
+}
+
+static int
+usie_attach(device_t self)
+{
+ struct usie_softc *sc = device_get_softc(self);
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+ if_t ifp;
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *id;
+ struct usb_device_request req;
+ int err;
+ uint16_t fwattr;
+ uint8_t iface_index;
+ uint8_t ifidx;
+ uint8_t start;
+
+ device_set_usb_desc(self);
+ sc->sc_udev = uaa->device;
+ sc->sc_dev = self;
+
+ mtx_init(&sc->sc_mtx, "usie", MTX_NETWORK_LOCK, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ TASK_INIT(&sc->sc_if_status_task, 0, usie_if_status_cb, sc);
+ TASK_INIT(&sc->sc_if_sync_task, 0, usie_if_sync_cb, sc);
+
+ usb_callout_init_mtx(&sc->sc_if_sync_ch, &sc->sc_mtx, 0);
+
+ mtx_lock(&sc->sc_mtx);
+
+ /* set power mode to D0 */
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = USIE_POWER;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+ if (usie_do_request(sc, &req, NULL)) {
+ mtx_unlock(&sc->sc_mtx);
+ goto detach;
+ }
+ /* read fw attr */
+ fwattr = 0;
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = USIE_FW_ATTR;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, sizeof(fwattr));
+ if (usie_do_request(sc, &req, &fwattr)) {
+ mtx_unlock(&sc->sc_mtx);
+ goto detach;
+ }
+ mtx_unlock(&sc->sc_mtx);
+
+ /* check DHCP supports */
+ DPRINTF("fwattr=%x\n", fwattr);
+ if (!(fwattr & USIE_FW_DHCP)) {
+ device_printf(self, "DHCP is not supported. A firmware upgrade might be needed.\n");
+ }
+
+ /* find available interfaces */
+ sc->sc_nucom = 0;
+ for (ifidx = 0; ifidx < USIE_IFACE_MAX; ifidx++) {
+ iface = usbd_get_iface(uaa->device, ifidx);
+ if (iface == NULL)
+ break;
+
+ id = usbd_get_interface_descriptor(iface);
+ if ((id == NULL) || (id->bInterfaceClass != UICLASS_VENDOR))
+ continue;
+
+ /* setup Direct IP transfer */
+ if (id->bInterfaceNumber >= 7 && id->bNumEndpoints == 3) {
+ sc->sc_if_ifnum = id->bInterfaceNumber;
+ iface_index = ifidx;
+
+ DPRINTF("ifnum=%d, ifidx=%d\n",
+ sc->sc_if_ifnum, ifidx);
+
+ err = usbd_transfer_setup(uaa->device,
+ &iface_index, sc->sc_if_xfer, usie_if_config,
+ USIE_IF_N_XFER, sc, &sc->sc_mtx);
+
+ if (err == 0)
+ continue;
+
+ device_printf(self,
+ "could not allocate USB transfers on "
+ "iface_index=%d, err=%s\n",
+ iface_index, usbd_errstr(err));
+ goto detach;
+ }
+
+ /* setup ucom */
+ if (sc->sc_nucom >= USIE_UCOM_MAX)
+ continue;
+
+ usbd_set_parent_iface(uaa->device, ifidx,
+ uaa->info.bIfaceIndex);
+
+ DPRINTF("NumEndpoints=%d bInterfaceNumber=%d\n",
+ id->bNumEndpoints, id->bInterfaceNumber);
+
+ if (id->bNumEndpoints == 2) {
+ sc->sc_uc_xfer[sc->sc_nucom][0] = NULL;
+ start = 1;
+ } else
+ start = 0;
+
+ err = usbd_transfer_setup(uaa->device, &ifidx,
+ sc->sc_uc_xfer[sc->sc_nucom] + start,
+ usie_uc_config + start, USIE_UC_N_XFER - start,
+ &sc->sc_ucom[sc->sc_nucom], &sc->sc_mtx);
+
+ if (err != 0) {
+ DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(err));
+ continue;
+ }
+
+ mtx_lock(&sc->sc_mtx);
+ for (; start < USIE_UC_N_XFER; start++)
+ usbd_xfer_set_stall(sc->sc_uc_xfer[sc->sc_nucom][start]);
+ mtx_unlock(&sc->sc_mtx);
+
+ sc->sc_uc_ifnum[sc->sc_nucom] = id->bInterfaceNumber;
+
+ sc->sc_nucom++; /* found a port */
+ }
+
+ if (sc->sc_nucom == 0) {
+ device_printf(self, "no comports found\n");
+ goto detach;
+ }
+
+ err = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom,
+ sc->sc_nucom, sc, &usie_uc_callback, &sc->sc_mtx);
+
+ if (err != 0) {
+ DPRINTF("ucom_attach failed\n");
+ goto detach;
+ }
+ DPRINTF("Found %d interfaces.\n", sc->sc_nucom);
+
+ /* setup ifnet (Direct IP) */
+ sc->sc_ifp = ifp = if_alloc(IFT_OTHER);
+ if_initname(ifp, "usie", device_get_unit(self));
+
+ if_setsoftc(ifp, sc);
+ if_setmtu(ifp, USIE_MTU_MAX);
+ if_setflagbits(ifp, IFF_NOARP, 0);
+ if_setinitfn(ifp, usie_if_init);
+ if_setioctlfn(ifp, usie_if_ioctl);
+ if_setstartfn(ifp, usie_if_start);
+ if_setoutputfn(ifp, usie_if_output);
+ if_setsendqlen(ifp, ifqmaxlen);
+ if_setsendqready(ifp);
+
+ if_attach(ifp);
+ bpfattach(ifp, DLT_RAW, 0);
+
+ if (fwattr & USIE_PM_AUTO) {
+ usbd_set_power_mode(uaa->device, USB_POWER_MODE_SAVE);
+ DPRINTF("enabling automatic suspend and resume\n");
+ } else {
+ usbd_set_power_mode(uaa->device, USB_POWER_MODE_ON);
+ DPRINTF("USB power is always ON\n");
+ }
+
+ DPRINTF("device attached\n");
+ return (0);
+
+detach:
+ usie_detach(self);
+ return (ENOMEM);
+}
+
+static int
+usie_detach(device_t self)
+{
+ struct usie_softc *sc = device_get_softc(self);
+ uint8_t x;
+
+ /* detach ifnet */
+ if (sc->sc_ifp != NULL) {
+ usie_if_stop(sc);
+ usbd_transfer_unsetup(sc->sc_if_xfer, USIE_IF_N_XFER);
+ bpfdetach(sc->sc_ifp);
+ if_detach(sc->sc_ifp);
+ if_free(sc->sc_ifp);
+ sc->sc_ifp = NULL;
+ }
+ /* detach ucom */
+ if (sc->sc_nucom > 0)
+ ucom_detach(&sc->sc_super_ucom, sc->sc_ucom);
+
+ /* stop all USB transfers */
+ usbd_transfer_unsetup(sc->sc_if_xfer, USIE_IF_N_XFER);
+
+ for (x = 0; x != USIE_UCOM_MAX; x++)
+ usbd_transfer_unsetup(sc->sc_uc_xfer[x], USIE_UC_N_XFER);
+
+ device_claim_softc(self);
+
+ usie_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(usie);
+
+static void
+usie_free_softc(struct usie_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+usie_free(struct ucom_softc *ucom)
+{
+ usie_free_softc(ucom->sc_parent);
+}
+
+static void
+usie_uc_update_line_state(struct ucom_softc *ucom, uint8_t ls)
+{
+ struct usie_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+
+ if (sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS] == NULL)
+ return;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = USIE_LINK_STATE;
+ USETW(req.wValue, ls);
+ USETW(req.wIndex, sc->sc_uc_ifnum[ucom->sc_subunit]);
+ USETW(req.wLength, 0);
+
+ DPRINTF("sc_uc_ifnum=%d\n", sc->sc_uc_ifnum[ucom->sc_subunit]);
+
+ usie_do_request(sc, &req, NULL);
+}
+
+static void
+usie_uc_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct usie_softc *sc = ucom->sc_parent;
+
+ *msr = sc->sc_msr;
+ *lsr = sc->sc_lsr;
+}
+
+static void
+usie_uc_cfg_set_dtr(struct ucom_softc *ucom, uint8_t flag)
+{
+ uint8_t dtr;
+
+ dtr = flag ? USIE_LS_DTR : 0;
+ usie_uc_update_line_state(ucom, dtr);
+}
+
+static void
+usie_uc_cfg_set_rts(struct ucom_softc *ucom, uint8_t flag)
+{
+ uint8_t rts;
+
+ rts = flag ? USIE_LS_RTS : 0;
+ usie_uc_update_line_state(ucom, rts);
+}
+
+static void
+usie_uc_cfg_open(struct ucom_softc *ucom)
+{
+ struct usie_softc *sc = ucom->sc_parent;
+
+ /* usbd_transfer_start() is NULL safe */
+
+ usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS]);
+}
+
+static void
+usie_uc_cfg_close(struct ucom_softc *ucom)
+{
+ struct usie_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_STATUS]);
+}
+
+static void
+usie_uc_start_read(struct ucom_softc *ucom)
+{
+ struct usie_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_RX]);
+}
+
+static void
+usie_uc_stop_read(struct ucom_softc *ucom)
+{
+ struct usie_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_RX]);
+}
+
+static void
+usie_uc_start_write(struct ucom_softc *ucom)
+{
+ struct usie_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_TX]);
+}
+
+static void
+usie_uc_stop_write(struct ucom_softc *ucom)
+{
+ struct usie_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_uc_xfer[ucom->sc_subunit][USIE_UC_TX]);
+}
+
+static void
+usie_uc_rx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ucom_softc *ucom = usbd_xfer_softc(xfer);
+ struct usie_softc *sc = ucom->sc_parent;
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+
+ /* handle CnS response */
+ if (ucom == sc->sc_ucom && actlen >= USIE_HIPCNS_MIN) {
+ DPRINTF("transferred=%u\n", actlen);
+
+ /* check if it is really CnS reply */
+ usbd_copy_out(pc, 0, sc->sc_resp_temp, 1);
+
+ if (sc->sc_resp_temp[0] == USIE_HIP_FRM_CHR) {
+ /* verify actlen */
+ if (actlen > USIE_BUFSIZE)
+ actlen = USIE_BUFSIZE;
+
+ /* get complete message */
+ usbd_copy_out(pc, 0, sc->sc_resp_temp, actlen);
+ usie_hip_rsp(sc, sc->sc_resp_temp, actlen);
+
+ /* need to fall though */
+ goto tr_setup;
+ }
+ /* else call ucom_put_data() */
+ }
+ /* standard ucom transfer */
+ ucom_put_data(ucom, pc, 0, actlen);
+
+ /* fall though */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+usie_uc_tx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ucom_softc *ucom = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+
+ /* handle CnS request */
+ struct mbuf *m = usbd_xfer_get_priv(xfer);
+
+ if (m != NULL) {
+ usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len);
+ usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len);
+ usbd_xfer_set_priv(xfer, NULL);
+ usbd_transfer_submit(xfer);
+ m_freem(m);
+ break;
+ }
+ /* standard ucom transfer */
+ if (ucom_get_data(ucom, pc, 0, USIE_BUFSIZE, &actlen)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+usie_uc_status_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usb_page_cache *pc;
+ struct {
+ struct usb_device_request req;
+ uint16_t param;
+ } st;
+ uint32_t actlen;
+ uint16_t param;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(4, "info received, actlen=%u\n", actlen);
+
+ if (actlen < sizeof(st)) {
+ DPRINTF("data too short actlen=%u\n", actlen);
+ goto tr_setup;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &st, sizeof(st));
+
+ if (st.req.bmRequestType == 0xa1 && st.req.bRequest == 0x20) {
+ struct ucom_softc *ucom = usbd_xfer_softc(xfer);
+ struct usie_softc *sc = ucom->sc_parent;
+
+ param = le16toh(st.param);
+ DPRINTF("param=%x\n", param);
+ sc->sc_msr = sc->sc_lsr = 0;
+ sc->sc_msr |= (param & USIE_DCD) ? SER_DCD : 0;
+ sc->sc_msr |= (param & USIE_DSR) ? SER_DSR : 0;
+ sc->sc_msr |= (param & USIE_RI) ? SER_RI : 0;
+ sc->sc_msr |= (param & USIE_CTS) ? 0 : SER_CTS;
+ sc->sc_msr |= (param & USIE_RTS) ? SER_RTS : 0;
+ sc->sc_msr |= (param & USIE_DTR) ? SER_DTR : 0;
+ }
+ /* fall though */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ DPRINTF("USB transfer error, %s\n",
+ usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+usie_if_rx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct epoch_tracker et;
+ struct usie_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = sc->sc_ifp;
+ struct mbuf *m0;
+ struct mbuf *m = NULL;
+ struct usie_desc *rxd;
+ uint32_t actlen;
+ uint16_t err;
+ uint16_t pkt;
+ uint16_t ipl;
+ uint16_t len;
+ uint16_t diff;
+ uint8_t pad;
+ uint8_t ipv;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(15, "rx done, actlen=%u\n", actlen);
+
+ if (actlen < sizeof(struct usie_hip)) {
+ DPRINTF("data too short %u\n", actlen);
+ goto tr_setup;
+ }
+ m = sc->sc_rxm;
+ sc->sc_rxm = NULL;
+
+ /* fall though */
+ case USB_ST_SETUP:
+tr_setup:
+
+ if (sc->sc_rxm == NULL) {
+ sc->sc_rxm = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR,
+ MJUMPAGESIZE /* could be bigger than MCLBYTES */ );
+ }
+ if (sc->sc_rxm == NULL) {
+ DPRINTF("could not allocate Rx mbuf\n");
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ usbd_xfer_set_stall(xfer);
+ usbd_xfer_set_frames(xfer, 0);
+ } else {
+ /*
+ * Directly loading a mbuf cluster into DMA to
+ * save some data copying. This works because
+ * there is only one cluster.
+ */
+ usbd_xfer_set_frame_data(xfer, 0,
+ mtod(sc->sc_rxm, caddr_t), MIN(MJUMPAGESIZE, USIE_RXSZ_MAX));
+ usbd_xfer_set_frames(xfer, 1);
+ }
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ DPRINTF("USB transfer error, %s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ if (sc->sc_rxm != NULL) {
+ m_freem(sc->sc_rxm);
+ sc->sc_rxm = NULL;
+ }
+ break;
+ }
+
+ if (m == NULL)
+ return;
+
+ mtx_unlock(&sc->sc_mtx);
+
+ m->m_pkthdr.len = m->m_len = actlen;
+
+ err = pkt = 0;
+
+ /* HW can aggregate multiple frames in a single USB xfer */
+ NET_EPOCH_ENTER(et);
+ for (;;) {
+ rxd = mtod(m, struct usie_desc *);
+
+ len = be16toh(rxd->hip.len) & USIE_HIP_IP_LEN_MASK;
+ pad = (rxd->hip.id & USIE_HIP_PAD) ? 1 : 0;
+ ipl = (len - pad - ETHER_HDR_LEN);
+ if (ipl >= len) {
+ DPRINTF("Corrupt frame\n");
+ m_freem(m);
+ break;
+ }
+ diff = sizeof(struct usie_desc) + ipl + pad;
+
+ if (((rxd->hip.id & USIE_HIP_MASK) != USIE_HIP_IP) ||
+ (be16toh(rxd->desc_type) & USIE_TYPE_MASK) != USIE_IP_RX) {
+ DPRINTF("received wrong type of packet\n");
+ m->m_data += diff;
+ m->m_pkthdr.len = (m->m_len -= diff);
+ err++;
+ if (m->m_pkthdr.len > 0)
+ continue;
+ m_freem(m);
+ break;
+ }
+ switch (be16toh(rxd->ethhdr.ether_type)) {
+ case ETHERTYPE_IP:
+ ipv = NETISR_IP;
+ break;
+#ifdef INET6
+ case ETHERTYPE_IPV6:
+ ipv = NETISR_IPV6;
+ break;
+#endif
+ default:
+ DPRINTF("unsupported ether type\n");
+ err++;
+ break;
+ }
+
+ /* the last packet */
+ if (m->m_pkthdr.len <= diff) {
+ m->m_data += (sizeof(struct usie_desc) + pad);
+ m->m_pkthdr.len = m->m_len = ipl;
+ m->m_pkthdr.rcvif = ifp;
+ BPF_MTAP(sc->sc_ifp, m);
+ netisr_dispatch(ipv, m);
+ break;
+ }
+ /* copy aggregated frames to another mbuf */
+ m0 = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (__predict_false(m0 == NULL)) {
+ DPRINTF("could not allocate mbuf\n");
+ err++;
+ m_freem(m);
+ break;
+ }
+ m_copydata(m, sizeof(struct usie_desc) + pad, ipl, mtod(m0, caddr_t));
+ m0->m_pkthdr.rcvif = ifp;
+ m0->m_pkthdr.len = m0->m_len = ipl;
+
+ BPF_MTAP(sc->sc_ifp, m0);
+ netisr_dispatch(ipv, m0);
+
+ m->m_data += diff;
+ m->m_pkthdr.len = (m->m_len -= diff);
+ }
+ NET_EPOCH_EXIT(et);
+
+ mtx_lock(&sc->sc_mtx);
+
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, err);
+ if_inc_counter(ifp, IFCOUNTER_IPACKETS, pkt);
+}
+
+static void
+usie_if_tx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usie_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ if_t ifp = sc->sc_ifp;
+ struct mbuf *m;
+ uint16_t size;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "transfer complete\n");
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ /* fall though */
+ case USB_ST_SETUP:
+tr_setup:
+
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0)
+ break;
+
+ m = if_dequeue(ifp);
+ if (m == NULL)
+ break;
+
+ if (m->m_pkthdr.len > (int)(MCLBYTES - ETHER_HDR_LEN +
+ ETHER_CRC_LEN - sizeof(sc->sc_txd))) {
+ DPRINTF("packet len is too big: %d\n",
+ m->m_pkthdr.len);
+ break;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+
+ sc->sc_txd.hip.len = htobe16(m->m_pkthdr.len +
+ ETHER_HDR_LEN + ETHER_CRC_LEN);
+ size = sizeof(sc->sc_txd);
+
+ usbd_copy_in(pc, 0, &sc->sc_txd, size);
+ usbd_m_copy_in(pc, size, m, 0, m->m_pkthdr.len);
+ usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len +
+ size + ETHER_CRC_LEN);
+
+ BPF_MTAP(ifp, m);
+
+ m_freem(m);
+
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ DPRINTF("USB transfer error, %s\n",
+ usbd_errstr(error));
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+usie_if_status_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usie_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ struct usb_cdc_notification cdc;
+ uint32_t actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(4, "info received, actlen=%d\n", actlen);
+
+ /* usb_cdc_notification - .data[16] */
+ if (actlen < (sizeof(cdc) - 16)) {
+ DPRINTF("data too short %d\n", actlen);
+ goto tr_setup;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &cdc, (sizeof(cdc) - 16));
+
+ DPRINTFN(4, "bNotification=%x\n", cdc.bNotification);
+
+ if (cdc.bNotification & UCDC_N_RESPONSE_AVAILABLE) {
+ taskqueue_enqueue(taskqueue_thread,
+ &sc->sc_if_status_task);
+ }
+ /* fall though */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ DPRINTF("USB transfer error, %s\n",
+ usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+usie_if_sync_to(void *arg)
+{
+ struct usie_softc *sc = arg;
+
+ taskqueue_enqueue(taskqueue_thread, &sc->sc_if_sync_task);
+}
+
+static void
+usie_if_sync_cb(void *arg, int pending)
+{
+ struct usie_softc *sc = arg;
+
+ mtx_lock(&sc->sc_mtx);
+
+ /* call twice */
+ usie_if_cmd(sc, USIE_HIP_SYNC2M);
+ usie_if_cmd(sc, USIE_HIP_SYNC2M);
+
+ usb_callout_reset(&sc->sc_if_sync_ch, 2 * hz, usie_if_sync_to, sc);
+
+ mtx_unlock(&sc->sc_mtx);
+}
+
+static void
+usie_if_status_cb(void *arg, int pending)
+{
+ struct usie_softc *sc = arg;
+ if_t ifp = sc->sc_ifp;
+ struct usb_device_request req;
+ struct usie_hip *hip;
+ struct usie_lsi *lsi;
+ uint16_t actlen;
+ uint8_t ntries;
+ uint8_t pad;
+
+ mtx_lock(&sc->sc_mtx);
+
+ req.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, sc->sc_if_ifnum);
+ USETW(req.wLength, sizeof(sc->sc_status_temp));
+
+ for (ntries = 0; ntries != 10; ntries++) {
+ int err;
+
+ err = usbd_do_request_flags(sc->sc_udev,
+ &sc->sc_mtx, &req, sc->sc_status_temp, USB_SHORT_XFER_OK,
+ &actlen, USB_DEFAULT_TIMEOUT);
+
+ if (err == 0)
+ break;
+
+ DPRINTF("Control request failed: %s %d/10\n",
+ usbd_errstr(err), ntries);
+
+ usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10));
+ }
+
+ if (ntries == 10) {
+ mtx_unlock(&sc->sc_mtx);
+ DPRINTF("Timeout\n");
+ return;
+ }
+
+ hip = (struct usie_hip *)sc->sc_status_temp;
+
+ pad = (hip->id & USIE_HIP_PAD) ? 1 : 0;
+
+ DPRINTF("hip.id=%x hip.len=%d actlen=%u pad=%d\n",
+ hip->id, be16toh(hip->len), actlen, pad);
+
+ switch (hip->id & USIE_HIP_MASK) {
+ case USIE_HIP_SYNC2H:
+ usie_if_cmd(sc, USIE_HIP_SYNC2M);
+ break;
+ case USIE_HIP_RESTR:
+ usb_callout_stop(&sc->sc_if_sync_ch);
+ break;
+ case USIE_HIP_UMTS:
+ lsi = (struct usie_lsi *)(
+ sc->sc_status_temp + sizeof(struct usie_hip) + pad);
+
+ DPRINTF("lsi.proto=%x lsi.len=%d\n", lsi->proto,
+ be16toh(lsi->len));
+
+ if (lsi->proto != USIE_LSI_UMTS)
+ break;
+
+ if (lsi->area == USIE_LSI_AREA_NO ||
+ lsi->area == USIE_LSI_AREA_NODATA) {
+ device_printf(sc->sc_dev, "no service available\n");
+ break;
+ }
+ if (lsi->state == USIE_LSI_STATE_IDLE) {
+ DPRINTF("lsi.state=%x\n", lsi->state);
+ break;
+ }
+ DPRINTF("ctx=%x\n", hip->param);
+ sc->sc_txd.hip.param = hip->param;
+
+ sc->sc_net.addr_len = lsi->pdp_addr_len;
+ memcpy(&sc->sc_net.dns1_addr, &lsi->dns1_addr, 16);
+ memcpy(&sc->sc_net.dns2_addr, &lsi->dns2_addr, 16);
+ memcpy(sc->sc_net.pdp_addr, lsi->pdp_addr, 16);
+ memcpy(sc->sc_net.gw_addr, lsi->gw_addr, 16);
+ if_setflagbits(ifp, IFF_UP, 0);
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+
+ device_printf(sc->sc_dev, "IP Addr=%d.%d.%d.%d\n",
+ *lsi->pdp_addr, *(lsi->pdp_addr + 1),
+ *(lsi->pdp_addr + 2), *(lsi->pdp_addr + 3));
+ device_printf(sc->sc_dev, "Gateway Addr=%d.%d.%d.%d\n",
+ *lsi->gw_addr, *(lsi->gw_addr + 1),
+ *(lsi->gw_addr + 2), *(lsi->gw_addr + 3));
+ device_printf(sc->sc_dev, "Prim NS Addr=%d.%d.%d.%d\n",
+ *lsi->dns1_addr, *(lsi->dns1_addr + 1),
+ *(lsi->dns1_addr + 2), *(lsi->dns1_addr + 3));
+ device_printf(sc->sc_dev, "Scnd NS Addr=%d.%d.%d.%d\n",
+ *lsi->dns2_addr, *(lsi->dns2_addr + 1),
+ *(lsi->dns2_addr + 2), *(lsi->dns2_addr + 3));
+
+ usie_cns_req(sc, USIE_CNS_ID_RSSI, USIE_CNS_OB_RSSI);
+ break;
+
+ case USIE_HIP_RCGI:
+ /* ignore, workaround for sloppy windows */
+ break;
+ default:
+ DPRINTF("undefined msgid: %x\n", hip->id);
+ break;
+ }
+
+ mtx_unlock(&sc->sc_mtx);
+}
+
+static void
+usie_if_start(if_t ifp)
+{
+ struct usie_softc *sc = if_getsoftc(ifp);
+
+ if (!(if_getdrvflags(ifp) & IFF_DRV_RUNNING)) {
+ DPRINTF("Not running\n");
+ return;
+ }
+ mtx_lock(&sc->sc_mtx);
+ usbd_transfer_start(sc->sc_if_xfer[USIE_IF_TX]);
+ mtx_unlock(&sc->sc_mtx);
+
+ DPRINTFN(3, "interface started\n");
+}
+
+static int
+usie_if_output(if_t ifp, struct mbuf *m, const struct sockaddr *dst,
+ struct route *ro)
+{
+ int err;
+
+ DPRINTF("proto=%x\n", dst->sa_family);
+
+ switch (dst->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ /* fall though */
+#endif
+ case AF_INET:
+ break;
+
+ /* silently drop dhclient packets */
+ case AF_UNSPEC:
+ m_freem(m);
+ return (0);
+
+ /* drop other packet types */
+ default:
+ m_freem(m);
+ return (EAFNOSUPPORT);
+ }
+
+ err = if_transmit(ifp, m);
+ if (err) {
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ return (ENOBUFS);
+ }
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+
+ return (0);
+}
+
+static void
+usie_if_init(void *arg)
+{
+ struct usie_softc *sc = arg;
+ if_t ifp = sc->sc_ifp;
+ uint8_t i;
+
+ mtx_lock(&sc->sc_mtx);
+
+ /* write tx descriptor */
+ sc->sc_txd.hip.id = USIE_HIP_CTX;
+ sc->sc_txd.hip.param = 0; /* init value */
+ sc->sc_txd.desc_type = htobe16(USIE_IP_TX);
+
+ for (i = 0; i != USIE_IF_N_XFER; i++)
+ usbd_xfer_set_stall(sc->sc_if_xfer[i]);
+
+ usbd_transfer_start(sc->sc_uc_xfer[USIE_HIP_IF][USIE_UC_RX]);
+ usbd_transfer_start(sc->sc_if_xfer[USIE_IF_STATUS]);
+ usbd_transfer_start(sc->sc_if_xfer[USIE_IF_RX]);
+
+ /* if not running, initiate the modem */
+ if (!(if_getdrvflags(ifp) & IFF_DRV_RUNNING))
+ usie_cns_req(sc, USIE_CNS_ID_INIT, USIE_CNS_OB_LINK_UPDATE);
+
+ mtx_unlock(&sc->sc_mtx);
+
+ DPRINTF("ifnet initialized\n");
+}
+
+static void
+usie_if_stop(struct usie_softc *sc)
+{
+ usb_callout_drain(&sc->sc_if_sync_ch);
+
+ mtx_lock(&sc->sc_mtx);
+
+ /* usie_cns_req() clears IFF_* flags */
+ usie_cns_req(sc, USIE_CNS_ID_STOP, USIE_CNS_OB_LINK_UPDATE);
+
+ usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_TX]);
+ usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_RX]);
+ usbd_transfer_stop(sc->sc_if_xfer[USIE_IF_STATUS]);
+
+ /* shutdown device */
+ usie_if_cmd(sc, USIE_HIP_DOWN);
+
+ mtx_unlock(&sc->sc_mtx);
+}
+
+static int
+usie_if_ioctl(if_t ifp, u_long cmd, caddr_t data)
+{
+ struct usie_softc *sc = if_getsoftc(ifp);
+ struct ieee80211req *ireq;
+ struct ieee80211req_sta_info si;
+ struct ifmediareq *ifmr;
+
+ switch (cmd) {
+ case SIOCSIFFLAGS:
+ if (if_getflags(ifp) & IFF_UP) {
+ if (!(if_getdrvflags(ifp) & IFF_DRV_RUNNING))
+ usie_if_init(sc);
+ } else {
+ if (if_getdrvflags(ifp) & IFF_DRV_RUNNING)
+ usie_if_stop(sc);
+ }
+ break;
+
+ case SIOCSIFCAP:
+ if (!(if_getdrvflags(ifp) & IFF_DRV_RUNNING)) {
+ device_printf(sc->sc_dev,
+ "Connect to the network first.\n");
+ break;
+ }
+ mtx_lock(&sc->sc_mtx);
+ usie_cns_req(sc, USIE_CNS_ID_RSSI, USIE_CNS_OB_RSSI);
+ mtx_unlock(&sc->sc_mtx);
+ break;
+
+ case SIOCG80211:
+ ireq = (struct ieee80211req *)data;
+
+ if (ireq->i_type != IEEE80211_IOC_STA_INFO)
+ break;
+
+ memset(&si, 0, sizeof(si));
+ si.isi_len = sizeof(si);
+ /*
+ * ifconfig expects RSSI in 0.5dBm units
+ * relative to the noise floor.
+ */
+ si.isi_rssi = 2 * sc->sc_rssi;
+ if (copyout(&si, (uint8_t *)ireq->i_data + 8,
+ sizeof(struct ieee80211req_sta_info)))
+ DPRINTF("copyout failed\n");
+ DPRINTF("80211\n");
+ break;
+
+ case SIOCGIFMEDIA: /* to fool ifconfig */
+ ifmr = (struct ifmediareq *)data;
+ ifmr->ifm_count = 1;
+ DPRINTF("media\n");
+ break;
+
+ case SIOCSIFADDR:
+ break;
+
+ default:
+ return (EINVAL);
+ }
+ return (0);
+}
+
+static int
+usie_do_request(struct usie_softc *sc, struct usb_device_request *req,
+ void *data)
+{
+ int err = 0;
+ int ntries;
+
+ mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+ for (ntries = 0; ntries != 10; ntries++) {
+ err = usbd_do_request(sc->sc_udev,
+ &sc->sc_mtx, req, data);
+ if (err == 0)
+ break;
+
+ DPRINTF("Control request failed: %s %d/10\n",
+ usbd_errstr(err), ntries);
+
+ usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10));
+ }
+ return (err);
+}
+
+static int
+usie_if_cmd(struct usie_softc *sc, uint8_t cmd)
+{
+ struct usb_device_request req;
+ struct usie_hip msg;
+
+ msg.len = 0;
+ msg.id = cmd;
+ msg.param = 0;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, sc->sc_if_ifnum);
+ USETW(req.wLength, sizeof(msg));
+
+ DPRINTF("cmd=%x\n", cmd);
+
+ return (usie_do_request(sc, &req, &msg));
+}
+
+static void
+usie_cns_req(struct usie_softc *sc, uint32_t id, uint16_t obj)
+{
+ if_t ifp = sc->sc_ifp;
+ struct mbuf *m;
+ struct usb_xfer *xfer;
+ struct usie_hip *hip;
+ struct usie_cns *cns;
+ uint8_t *param;
+ uint8_t *tmp;
+ uint8_t cns_len;
+
+ m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (__predict_false(m == NULL)) {
+ DPRINTF("could not allocate mbuf\n");
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ return;
+ }
+ /* to align usie_hip{} on 32 bit */
+ m->m_data += 3;
+ param = mtod(m, uint8_t *);
+ *param++ = USIE_HIP_FRM_CHR;
+ hip = (struct usie_hip *)param;
+ cns = (struct usie_cns *)(hip + 1);
+
+ tmp = param + USIE_HIPCNS_MIN - 2;
+
+ switch (obj) {
+ case USIE_CNS_OB_LINK_UPDATE:
+ cns_len = 2;
+ cns->op = USIE_CNS_OP_SET;
+ *tmp++ = 1; /* profile ID, always use 1 for now */
+ *tmp++ = id == USIE_CNS_ID_INIT ? 1 : 0;
+ break;
+
+ case USIE_CNS_OB_PROF_WRITE:
+ cns_len = 245;
+ cns->op = USIE_CNS_OP_SET;
+ *tmp++ = 1; /* profile ID, always use 1 for now */
+ *tmp++ = 2;
+ memcpy(tmp, &sc->sc_net, 34);
+ memset(tmp + 35, 0, 245 - 36);
+ tmp += 243;
+ break;
+
+ case USIE_CNS_OB_RSSI:
+ cns_len = 0;
+ cns->op = USIE_CNS_OP_REQ;
+ break;
+
+ default:
+ DPRINTF("unsupported CnS object type\n");
+ return;
+ }
+ *tmp = USIE_HIP_FRM_CHR;
+
+ hip->len = htobe16(sizeof(struct usie_cns) + cns_len);
+ hip->id = USIE_HIP_CNS2M;
+ hip->param = 0; /* none for CnS */
+
+ cns->obj = htobe16(obj);
+ cns->id = htobe32(id);
+ cns->len = cns_len;
+ cns->rsv0 = cns->rsv1 = 0; /* always '0' */
+
+ param = (uint8_t *)(cns + 1);
+
+ DPRINTF("param: %16D\n", param, ":");
+
+ m->m_pkthdr.len = m->m_len = USIE_HIPCNS_MIN + cns_len + 2;
+
+ xfer = sc->sc_uc_xfer[USIE_HIP_IF][USIE_UC_TX];
+
+ if (usbd_xfer_get_priv(xfer) == NULL) {
+ usbd_xfer_set_priv(xfer, m);
+ usbd_transfer_start(xfer);
+ } else {
+ DPRINTF("Dropped CNS event\n");
+ m_freem(m);
+ }
+}
+
+static void
+usie_cns_rsp(struct usie_softc *sc, struct usie_cns *cns)
+{
+ if_t ifp = sc->sc_ifp;
+
+ DPRINTF("received CnS\n");
+
+ switch (be16toh(cns->obj)) {
+ case USIE_CNS_OB_LINK_UPDATE:
+ if (be32toh(cns->id) & USIE_CNS_ID_INIT)
+ usie_if_sync_to(sc);
+ else if (be32toh(cns->id) & USIE_CNS_ID_STOP) {
+ if_setflagbits(ifp, 0, IFF_UP);
+ if_setdrvflagbits(ifp, 0,
+ IFF_DRV_RUNNING | IFF_DRV_OACTIVE);
+ } else
+ DPRINTF("undefined link update\n");
+ break;
+
+ case USIE_CNS_OB_RSSI:
+ sc->sc_rssi = be16toh(*(int16_t *)(cns + 1));
+ if (sc->sc_rssi <= 0)
+ device_printf(sc->sc_dev, "No signal\n");
+ else {
+ device_printf(sc->sc_dev, "RSSI=%ddBm\n",
+ sc->sc_rssi - 110);
+ }
+ break;
+
+ case USIE_CNS_OB_PROF_WRITE:
+ break;
+
+ case USIE_CNS_OB_PDP_READ:
+ break;
+
+ default:
+ DPRINTF("undefined CnS\n");
+ break;
+ }
+}
+
+static void
+usie_hip_rsp(struct usie_softc *sc, uint8_t *rsp, uint32_t len)
+{
+ struct usie_hip *hip;
+ struct usie_cns *cns;
+ uint32_t i;
+ uint32_t j;
+ uint32_t off;
+ uint8_t tmp[USIE_HIPCNS_MAX] __aligned(4);
+
+ for (off = 0; (off + USIE_HIPCNS_MIN) <= len; off++) {
+ uint8_t pad;
+
+ while ((off < len) && (rsp[off] == USIE_HIP_FRM_CHR))
+ off++;
+
+ /* Unstuff the bytes */
+ for (i = j = 0; ((i + off) < len) &&
+ (j < USIE_HIPCNS_MAX); i++) {
+ if (rsp[i + off] == USIE_HIP_FRM_CHR)
+ break;
+
+ if (rsp[i + off] == USIE_HIP_ESC_CHR) {
+ if ((i + off + 1) >= len)
+ break;
+ tmp[j++] = rsp[i++ + off + 1] ^ 0x20;
+ } else {
+ tmp[j++] = rsp[i + off];
+ }
+ }
+
+ off += i;
+
+ DPRINTF("frame len=%d\n", j);
+
+ if (j < sizeof(struct usie_hip)) {
+ DPRINTF("too little data\n");
+ break;
+ }
+ /*
+ * Make sure we are not reading the stack if something
+ * is wrong.
+ */
+ memset(tmp + j, 0, sizeof(tmp) - j);
+
+ hip = (struct usie_hip *)tmp;
+
+ DPRINTF("hip: len=%d msgID=%02x, param=%02x\n",
+ be16toh(hip->len), hip->id, hip->param);
+
+ pad = (hip->id & USIE_HIP_PAD) ? 1 : 0;
+
+ if ((hip->id & USIE_HIP_MASK) == USIE_HIP_CNS2H) {
+ cns = (struct usie_cns *)(((uint8_t *)(hip + 1)) + pad);
+
+ if (j < (sizeof(struct usie_cns) +
+ sizeof(struct usie_hip) + pad)) {
+ DPRINTF("too little data\n");
+ break;
+ }
+ DPRINTF("cns: obj=%04x, op=%02x, rsv0=%02x, "
+ "app=%08x, rsv1=%02x, len=%d\n",
+ be16toh(cns->obj), cns->op, cns->rsv0,
+ be32toh(cns->id), cns->rsv1, cns->len);
+
+ if (cns->op & USIE_CNS_OP_ERR)
+ DPRINTF("CnS error response\n");
+ else
+ usie_cns_rsp(sc, cns);
+
+ i = sizeof(struct usie_hip) + pad + sizeof(struct usie_cns);
+ j = cns->len;
+ } else {
+ i = sizeof(struct usie_hip) + pad;
+ j = be16toh(hip->len);
+ }
+#ifdef USB_DEBUG
+ if (usie_debug == 0)
+ continue;
+
+ while (i < USIE_HIPCNS_MAX && j > 0) {
+ DPRINTF("param[0x%02x] = 0x%02x\n", i, tmp[i]);
+ i++;
+ j--;
+ }
+#endif
+ }
+}
+
+static int
+usie_driver_loaded(struct module *mod, int what, void *arg)
+{
+ switch (what) {
+ case MOD_LOAD:
+ /* register autoinstall handler */
+ usie_etag = EVENTHANDLER_REGISTER(usb_dev_configured,
+ usie_autoinst, NULL, EVENTHANDLER_PRI_ANY);
+ break;
+ case MOD_UNLOAD:
+ EVENTHANDLER_DEREGISTER(usb_dev_configured, usie_etag);
+ break;
+ default:
+ return (EOPNOTSUPP);
+ }
+ return (0);
+}
diff --git a/sys/dev/usb/net/if_usievar.h b/sys/dev/usb/net/if_usievar.h
new file mode 100644
index 000000000000..a843bc21e6ac
--- /dev/null
+++ b/sys/dev/usb/net/if_usievar.h
@@ -0,0 +1,257 @@
+
+/*-
+ * Copyright (c) 2011 Anybots Inc
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * written by Akinori Furukoshi <moonlightakkiy@yahoo.ca>
+ * - ucom part is based on u3g.c
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _IF_USEVAR_H_
+#define _IF_USEVAR_H_
+
+#define USIE_DCD 0x0001
+#define USIE_DSR 0x0002
+#define USIE_DTR 0x0004
+#define USIE_RI 0x0008
+#define USIE_CTS 0x0100
+#define USIE_RTS 0x0200
+
+#define USIE_HIP_FRM_CHR 0x7e
+#define USIE_HIP_ESC_CHR 0x7d
+#define USIE_HIP_IF 0
+
+#define USIE_HIPCNS_MIN 16 /* HIP + CnS + 2 framing char */
+#define USIE_HIPCNS_MAX 261 /* HIP + max CnS 255 + 2 framing char */
+
+#define USIE_CNFG_INDEX 0
+#define USIE_IFACE_INDEX 0
+#define USIE_IFACE_MAX 12
+#define USIE_BUFSIZE 2048
+#define USIE_MTU_MAX 1500
+#define USIE_RXSZ_MAX 4096
+
+/* USB control pipe request */
+#define USIE_POWER 0x00
+#define USIE_FW_ATTR 0x06
+#define USIE_NMEA 0x07
+#define USIE_LINK_STATE 0x22
+
+/* firmware attr flags */
+#define USIE_PM_AUTO (1 << 1)
+#define USIE_FW_DHCP (1 << 3) /* DHCP capable */
+
+/* line state flags */
+#define USIE_LS_DTR (1 << 0)
+#define USIE_LS_RTS (1 << 1)
+
+/* Host Interface Porotocol Header */
+struct usie_hip {
+ uint16_t len;
+#define USIE_HIP_LEN_MASK 0x3fff
+#define USIE_HIP_IP_LEN_MASK 0x07ff
+
+ uint8_t id;
+#define USIE_HIP_PAD (1 << 7)
+#define USIE_HIP_MASK 0x7f
+#define USIE_HIP_SYNC2M 0x20 /* host -> modem */
+#define USIE_HIP_DOWN 0x26
+#define USIE_HIP_CNS2M 0x2b /* h -> m */
+#define USIE_HIP_CTX 0x3f
+#define USIE_HIP_SYNC2H 0x60 /* h <- m */
+#define USIE_HIP_RESTR 0x62
+#define USIE_HIP_RCGI 0x64
+#define USIE_HIP_CNS2H 0x6b /* h <- m */
+#define USIE_HIP_UMTS 0x78
+#define USIE_HIP_IP 0x7f
+
+ uint8_t param;
+} __packed __aligned(4);
+
+/* Control and Status Header */
+struct usie_cns {
+ uint16_t obj; /* object type */
+#define USIE_CNS_OB_RSSI 0x1001 /* read RSSI */
+#define USIE_CNS_OB_HW_DISABLE 0x1011 /* disable h/w */
+#define USIE_CNS_OB_PW_SW 0x1071 /* power on/off */
+#define USIE_CNS_OB_PROF_WRITE 0x7003 /* write profile */
+#define USIE_CNS_OB_LINK_UPDATE 0x7004 /* dis/connect */
+#define USIE_CNS_OB_PDP_READ 0x7006 /* read out IP addr */
+
+ uint8_t op; /* operation type */
+#define USIE_CNS_OP_ERR (1 << 7)/* | == error */
+#define USIE_CNS_OP_REQ 0x01 /* host -> modem */
+#define USIE_CNS_OP_RSP 0x02 /* h <- m */
+#define USIE_CNS_OP_SET 0x03 /* h -> m */
+#define USIE_CNS_OP_ACK 0x04 /* h <- m */
+#define USIE_CNS_OP_NOTIF_ON 0x05 /* h -> m */
+#define USIE_CNS_OP_RSP_ON 0x06 /* h <- m */
+#define USIE_CNS_OP_NOTIF 0x07 /* h <- m */
+#define USIE_CNS_OP_NOTIF_OFF 0x08 /* h -> m */
+#define USIE_CNS_OP_RSP_OFF 0x09 /* h <- m */
+#define USIE_CNS_OP_REQ_CHG 0x0a /* h -> m */
+#define USIE_CNS_OP_RSP_CHG 0x0b /* h <- m */
+
+ uint8_t rsv0; /* reserved, always '0' */
+ uint32_t id; /* caller ID */
+/*
+ * .id is to identify calling functions
+ * h/w responses with the same .id used in request. Only '0' is reserved
+ * for notification (asynchronous message generated by h/w without any
+ * request). All other values are user defineable.
+ */
+#define USIE_CNS_ID_NOTIF 0x00000000 /* reserved */
+#define USIE_CNS_ID_INIT 0x00000001
+#define USIE_CNS_ID_STOP 0x00000002
+#define USIE_CNS_ID_DNS 0x00000003
+#define USIE_CNS_ID_RSSI 0x00000004
+
+ uint8_t rsv1; /* reserved, always '0' */
+ uint8_t len; /* length of param */
+} __packed;
+
+/*
+ * CnS param attached to struct usie_cns
+ * usie_cns.len is total size of this param
+ * max 255
+ */
+#define USIE_CNS_PM_UP 0x01
+#define USIE_CNS_PM_DOWN 0x00
+
+/* Link Sense Indication data structure */
+struct usie_lsi {
+ uint8_t proto;
+#define USIE_LSI_UMTS 0x01
+
+ uint8_t pad0;
+ uint16_t len;
+ uint8_t area;
+#define USIE_LSI_AREA_NO 0x00
+#define USIE_LSI_AREA_NODATA 0x01
+
+ uint8_t pad1[41];
+ uint8_t state;
+#define USIE_LSI_STATE_IDLE 0x00
+
+ uint8_t pad2[33];
+ uint8_t type;
+#define USIE_LSI_IP4 0x00
+
+ uint8_t pdp_addr_len; /* PDP addr */
+ uint8_t pdp_addr[16];
+ uint8_t pad3[23];
+ uint8_t dns1_addr_len; /* DNS addr */
+ uint8_t dns1_addr[16];
+ uint8_t dns2_addr_len;
+ uint8_t dns2_addr[16];
+ uint8_t wins1_addr_len; /* Wins addr */
+ uint8_t wins1_addr[16];
+ uint8_t wins2_addr_len;
+ uint8_t wins2_addr[16];
+ uint8_t pad4[4];
+ uint8_t gw_addr_len; /* GW addr */
+ uint8_t gw_addr[16];
+ uint8_t rsv[8];
+} __packed;
+
+struct usie_net_info {
+ uint8_t addr_len;
+ uint8_t pdp_addr[16];
+ uint8_t dns1_addr[16];
+ uint8_t dns2_addr[16];
+ uint8_t gw_addr[16];
+} __packed;
+
+/* Tx/Rx IP packet descriptor */
+struct usie_desc {
+ struct usie_hip hip;
+ uint16_t desc_type;
+#define USIE_TYPE_MASK 0x03ff
+#define USIE_IP_TX 0x0002
+#define USIE_IP_RX 0x0202
+
+ struct ether_header ethhdr;
+} __packed;
+
+enum {
+ USIE_UC_STATUS,
+ USIE_UC_RX,
+ USIE_UC_TX,
+ USIE_UC_N_XFER
+};
+
+enum {
+ USIE_IF_STATUS,
+ USIE_IF_RX,
+ USIE_IF_TX,
+ USIE_IF_N_XFER
+};
+
+struct usie_softc {
+ struct ucom_super_softc sc_super_ucom;
+
+#define USIE_UCOM_MAX 6
+ struct ucom_softc sc_ucom[USIE_UCOM_MAX];
+ uint8_t sc_uc_ifnum[USIE_UCOM_MAX];
+
+ struct mtx sc_mtx;
+
+ struct task sc_if_status_task;
+ struct task sc_if_sync_task;
+ struct usb_callout sc_if_sync_ch;
+
+ struct usie_net_info sc_net;
+
+ struct usie_desc sc_txd;
+
+ struct usb_xfer *sc_uc_xfer[USIE_UCOM_MAX][USIE_UC_N_XFER];
+ struct usb_xfer *sc_if_xfer[USIE_IF_N_XFER];
+
+ if_t sc_ifp;
+ struct usb_device *sc_udev;
+ device_t sc_dev;
+
+ struct mbuf *sc_rxm;
+
+ uint16_t sc_if_ifnum;
+
+ int16_t sc_rssi;
+
+ uint8_t sc_msr;
+ uint8_t sc_lsr;
+ uint8_t sc_nucom;
+
+ uint8_t sc_resp_temp[USIE_BUFSIZE] __aligned(4);
+ uint8_t sc_status_temp[USIE_BUFSIZE] __aligned(4);
+};
+
+/* Some code assumptions */
+
+extern uint8_t usie_assert[((sizeof(struct usie_hip) +
+ sizeof(struct usie_lsi) + 1) <= USIE_BUFSIZE) ? 1 : -1];
+
+extern uint8_t ucdc_assert[(sizeof(struct usb_cdc_notification)
+ >= 16) ? 1 : -1];
+
+#endif /* _IF_USEVAR_H_ */
diff --git a/sys/dev/usb/net/mbim.h b/sys/dev/usb/net/mbim.h
new file mode 100644
index 000000000000..b8b54f72e282
--- /dev/null
+++ b/sys/dev/usb/net/mbim.h
@@ -0,0 +1,727 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Original copyright (c) 2016 genua mbH (OpenBSD version)
+ *
+ * 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.
+ *
+ * Copyright (c) 2022 ADISTA SAS (re-write for FreeBSD)
+ *
+ * Re-write for FreeBSD by Pierre Pronchery <pierre@defora.net>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the copyright holder 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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.
+ *
+ * $OpenBSD: mbim.h,v 1.4 2017/04/18 13:27:55 gerhard Exp $
+ */
+
+/*
+ * Mobile Broadband Interface Model
+ * http://www.usb.org/developers/docs/devclass_docs/MBIM-Compliance-1.0.pdf
+ */
+
+#ifndef _MBIM_H_
+#define _MBIM_H_
+
+#define UDESCSUB_MBIM 27
+#define MBIM_INTERFACE_ALTSETTING 1
+
+#define MBIM_RESET_FUNCTION 0x05
+
+/*
+ * Registration state (MBIM_REGISTER_STATE)
+ */
+#define MBIM_REGSTATE_UNKNOWN 0
+#define MBIM_REGSTATE_DEREGISTERED 1
+#define MBIM_REGSTATE_SEARCHING 2
+#define MBIM_REGSTATE_HOME 3
+#define MBIM_REGSTATE_ROAMING 4
+#define MBIM_REGSTATE_PARTNER 5
+#define MBIM_REGSTATE_DENIED 6
+
+/*
+ * Data classes mask (MBIM_DATA_CLASS)
+ */
+#define MBIM_DATACLASS_NONE 0x00000000
+#define MBIM_DATACLASS_GPRS 0x00000001
+#define MBIM_DATACLASS_EDGE 0x00000002
+#define MBIM_DATACLASS_UMTS 0x00000004
+#define MBIM_DATACLASS_HSDPA 0x00000008
+#define MBIM_DATACLASS_HSUPA 0x00000010
+#define MBIM_DATACLASS_LTE 0x00000020
+#define MBIM_DATACLASS_1XRTT 0x00010000
+#define MBIM_DATACLASS_1XEVDO 0x00020000
+#define MBIM_DATACLASS_1XEVDO_REV_A 0x00040000
+#define MBIM_DATACLASS_1XEVDV 0x00080000
+#define MBIM_DATACLASS_3XRTT 0x00100000
+#define MBIM_DATACLASS_1XEVDO_REV_B 0x00200000
+#define MBIM_DATACLASS_UMB 0x00400000
+#define MBIM_DATACLASS_CUSTOM 0x80000000
+
+/*
+ * Cell classes mask (MBIM_CELLULAR_CLASS)
+ */
+#define MBIM_CELLCLASS_GSM 0x00000001
+#define MBIM_CELLCLASS_CDMA 0x00000002
+
+/*
+ * UUIDs
+ */
+#define MBIM_UUID_LEN 16
+
+#define MBIM_UUID_BASIC_CONNECT { \
+ 0xa2, 0x89, 0xcc, 0x33, 0xbc, 0xbb, 0x8b, 0x4f, \
+ 0xb6, 0xb0, 0x13, 0x3e, 0xc2, 0xaa, 0xe6, 0xdf \
+ }
+
+#define MBIM_UUID_CONTEXT_INTERNET { \
+ 0x7e, 0x5e, 0x2a, 0x7e, 0x4e, 0x6f, 0x72, 0x72, \
+ 0x73, 0x6b, 0x65, 0x6e, 0x7e, 0x5e, 0x2a, 0x7e \
+ }
+
+#define MBIM_UUID_CONTEXT_VPN { \
+ 0x9b, 0x9f, 0x7b, 0xbe, 0x89, 0x52, 0x44, 0xb7, \
+ 0x83, 0xac, 0xca, 0x41, 0x31, 0x8d, 0xf7, 0xa0 \
+ }
+
+#define MBIM_UUID_QMI_MBIM { \
+ 0xd1, 0xa3, 0x0b, 0xc2, 0xf9, 0x7a, 0x6e, 0x43, \
+ 0xbf, 0x65, 0xc7, 0xe2, 0x4f, 0xb0, 0xf0, 0xd3 \
+ }
+
+#define MBIM_CTRLMSG_MINLEN 64
+#define MBIM_CTRLMSG_MAXLEN (4 * 1204)
+
+#define MBIM_MAXSEGSZ_MINVAL (2 * 1024)
+
+/*
+ * Control messages (host to function)
+ */
+#define MBIM_OPEN_MSG 1U
+#define MBIM_CLOSE_MSG 2U
+#define MBIM_COMMAND_MSG 3U
+#define MBIM_HOST_ERROR_MSG 4U
+
+/*
+ * Control messages (function to host)
+ */
+#define MBIM_OPEN_DONE 0x80000001U
+#define MBIM_CLOSE_DONE 0x80000002U
+#define MBIM_COMMAND_DONE 0x80000003U
+#define MBIM_FUNCTION_ERROR_MSG 0x80000004U
+#define MBIM_INDICATE_STATUS_MSG 0x80000007U
+
+/*
+ * Generic status codes
+ */
+#define MBIM_STATUS_SUCCESS 0
+#define MBIM_STATUS_BUSY 1
+#define MBIM_STATUS_FAILURE 2
+#define MBIM_STATUS_SIM_NOT_INSERTED 3
+#define MBIM_STATUS_BAD_SIM 4
+#define MBIM_STATUS_PIN_REQUIRED 5
+#define MBIM_STATUS_PIN_DISABLED 6
+#define MBIM_STATUS_NOT_REGISTERED 7
+#define MBIM_STATUS_PROVIDERS_NOT_FOUND 8
+#define MBIM_STATUS_NO_DEVICE_SUPPORT 9
+#define MBIM_STATUS_PROVIDER_NOT_VISIBLE 10
+#define MBIM_STATUS_DATA_CLASS_NOT_AVAILABLE 11
+#define MBIM_STATUS_PACKET_SERVICE_DETACHED 12
+#define MBIM_STATUS_MAX_ACTIVATED_CONTEXTS 13
+#define MBIM_STATUS_NOT_INITIALIZED 14
+#define MBIM_STATUS_VOICE_CALL_IN_PROGRESS 15
+#define MBIM_STATUS_CONTEXT_NOT_ACTIVATED 16
+#define MBIM_STATUS_SERVICE_NOT_ACTIVATED 17
+#define MBIM_STATUS_INVALID_ACCESS_STRING 18
+#define MBIM_STATUS_INVALID_USER_NAME_PWD 19
+#define MBIM_STATUS_RADIO_POWER_OFF 20
+#define MBIM_STATUS_INVALID_PARAMETERS 21
+#define MBIM_STATUS_READ_FAILURE 22
+#define MBIM_STATUS_WRITE_FAILURE 23
+#define MBIM_STATUS_NO_PHONEBOOK 25
+#define MBIM_STATUS_PARAMETER_TOO_LONG 26
+#define MBIM_STATUS_STK_BUSY 27
+#define MBIM_STATUS_OPERATION_NOT_ALLOWED 28
+#define MBIM_STATUS_MEMORY_FAILURE 29
+#define MBIM_STATUS_INVALID_MEMORY_INDEX 30
+#define MBIM_STATUS_MEMORY_FULL 31
+#define MBIM_STATUS_FILTER_NOT_SUPPORTED 32
+#define MBIM_STATUS_DSS_INSTANCE_LIMIT 33
+#define MBIM_STATUS_INVALID_DEVICE_SERVICE_OPERATION 34
+#define MBIM_STATUS_AUTH_INCORRECT_AUTN 35
+#define MBIM_STATUS_AUTH_SYNC_FAILURE 36
+#define MBIM_STATUS_AUTH_AMF_NOT_SET 37
+#define MBIM_STATUS_CONTEXT_NOT_SUPPORTED 38
+#define MBIM_STATUS_SMS_UNKNOWN_SMSC_ADDRESS 100
+#define MBIM_STATUS_SMS_NETWORK_TIMEOUT 101
+#define MBIM_STATUS_SMS_LANG_NOT_SUPPORTED 102
+#define MBIM_STATUS_SMS_ENCODING_NOT_SUPPORTED 103
+#define MBIM_STATUS_SMS_FORMAT_NOT_SUPPORTED 104
+
+/*
+ * Message formats
+ */
+struct mbim_msghdr {
+ /* Msg header */
+ uint32_t type; /* message type */
+ uint32_t len; /* message length */
+ uint32_t tid; /* transaction id */
+} __packed;
+
+struct mbim_fraghdr {
+ uint32_t nfrag; /* total # of fragments */
+ uint32_t currfrag; /* current fragment */
+} __packed;
+
+struct mbim_fragmented_msg_hdr {
+ struct mbim_msghdr hdr;
+ struct mbim_fraghdr frag;
+} __packed;
+
+struct mbim_h2f_openmsg {
+ struct mbim_msghdr hdr;
+ uint32_t maxlen;
+} __packed;
+
+struct mbim_h2f_closemsg {
+ struct mbim_msghdr hdr;
+} __packed;
+
+struct mbim_h2f_cmd {
+ struct mbim_msghdr hdr;
+ struct mbim_fraghdr frag;
+ uint8_t devid[MBIM_UUID_LEN];
+ uint32_t cid; /* command id */
+#define MBIM_CMDOP_QRY 0
+#define MBIM_CMDOP_SET 1
+ uint32_t op;
+ uint32_t infolen;
+ uint8_t info[];
+} __packed;
+
+struct mbim_f2h_indicate_status {
+ struct mbim_msghdr hdr;
+ struct mbim_fraghdr frag;
+ uint8_t devid[MBIM_UUID_LEN];
+ uint32_t cid; /* command id */
+ uint32_t infolen;
+ uint8_t info[];
+} __packed;
+
+struct mbim_f2h_hosterr {
+ struct mbim_msghdr hdr;
+
+#define MBIM_ERROR_TIMEOUT_FRAGMENT 1
+#define MBIM_ERROR_FRAGMENT_OUT_OF_SEQUENCE 2
+#define MBIM_ERROR_LENGTH_MISMATCH 3
+#define MBIM_ERROR_DUPLICATED_TID 4
+#define MBIM_ERROR_NOT_OPENED 5
+#define MBIM_ERROR_UNKNOWN 6
+#define MBIM_ERROR_CANCEL 7
+#define MBIM_ERROR_MAX_TRANSFER 8
+ uint32_t err;
+} __packed;
+
+struct mbim_f2h_openclosedone {
+ struct mbim_msghdr hdr;
+ int32_t status;
+} __packed;
+
+struct mbim_f2h_cmddone {
+ struct mbim_msghdr hdr;
+ struct mbim_fraghdr frag;
+ uint8_t devid[MBIM_UUID_LEN];
+ uint32_t cid; /* command id */
+ int32_t status;
+ uint32_t infolen;
+ uint8_t info[];
+} __packed;
+
+/*
+ * Messages and commands for MBIM_UUID_BASIC_CONNECT
+ */
+#define MBIM_CID_DEVICE_CAPS 1
+#define MBIM_CID_SUBSCRIBER_READY_STATUS 2
+#define MBIM_CID_RADIO_STATE 3
+#define MBIM_CID_PIN 4
+#define MBIM_CID_PIN_LIST 5
+#define MBIM_CID_HOME_PROVIDER 6
+#define MBIM_CID_PREFERRED_PROVIDERS 7
+#define MBIM_CID_VISIBLE_PROVIDERS 8
+#define MBIM_CID_REGISTER_STATE 9
+#define MBIM_CID_PACKET_SERVICE 10
+#define MBIM_CID_SIGNAL_STATE 11
+#define MBIM_CID_CONNECT 12
+#define MBIM_CID_PROVISIONED_CONTEXTS 13
+#define MBIM_CID_SERVICE_ACTIVATION 14
+#define MBIM_CID_IP_CONFIGURATION 15
+#define MBIM_CID_DEVICE_SERVICES 16
+#define MBIM_CID_DEVICE_SERVICE_SUBSCRIBE_LIST 19
+#define MBIM_CID_PACKET_STATISTICS 20
+#define MBIM_CID_NETWORK_IDLE_HINT 21
+#define MBIM_CID_EMERGENCY_MODE 22
+#define MBIM_CID_IP_PACKET_FILTERS 23
+#define MBIM_CID_MULTICARRIER_PROVIDERS 24
+
+struct mbim_cid_subscriber_ready_info {
+#define MBIM_SIMSTATE_NOTINITIALIZED 0
+#define MBIM_SIMSTATE_INITIALIZED 1
+#define MBIM_SIMSTATE_NOTINSERTED 2
+#define MBIM_SIMSTATE_BADSIM 3
+#define MBIM_SIMSTATE_FAILURE 4
+#define MBIM_SIMSTATE_NOTACTIVATED 5
+#define MBIM_SIMSTATE_LOCKED 6
+ uint32_t ready;
+
+ uint32_t sid_offs;
+ uint32_t sid_size;
+
+ uint32_t icc_offs;
+ uint32_t icc_size;
+
+#define MBIM_SIMUNIQEID_NONE 0
+#define MBIM_SIMUNIQEID_PROTECT 1
+ uint32_t info;
+
+ uint32_t no_pn;
+ struct {
+ uint32_t offs;
+ uint32_t size;
+ }
+ pn[];
+} __packed;
+
+struct mbim_cid_radio_state {
+#define MBIM_RADIO_STATE_OFF 0
+#define MBIM_RADIO_STATE_ON 1
+ uint32_t state;
+} __packed;
+
+struct mbim_cid_radio_state_info {
+ uint32_t hw_state;
+ uint32_t sw_state;
+} __packed;
+
+struct mbim_cid_pin {
+#define MBIM_PIN_TYPE_NONE 0
+#define MBIM_PIN_TYPE_CUSTOM 1
+#define MBIM_PIN_TYPE_PIN1 2
+#define MBIM_PIN_TYPE_PIN2 3
+#define MBIM_PIN_TYPE_DEV_SIM_PIN 4
+#define MBIM_PIN_TYPE_DEV_FIRST_SIM_PIN 5
+#define MBIM_PIN_TYPE_NETWORK_PIN 6
+#define MBIM_PIN_TYPE_NETWORK_SUBSET_PIN 7
+#define MBIM_PIN_TYPE_SERVICE_PROVIDER_PIN 8
+#define MBIM_PIN_TYPE_CORPORATE_PIN 9
+#define MBIM_PIN_TYPE_SUBSIDY_LOCK 10
+#define MBIM_PIN_TYPE_PUK1 11
+#define MBIM_PIN_TYPE_PUK2 12
+#define MBIM_PIN_TYPE_DEV_FIRST_SIM_PUK 13
+#define MBIM_PIN_TYPE_NETWORK_PUK 14
+#define MBIM_PIN_TYPE_NETWORK_SUBSET_PUK 15
+#define MBIM_PIN_TYPE_SERVICE_PROVIDER_PUK 16
+#define MBIM_PIN_TYPE_CORPORATE_PUK 17
+ uint32_t type;
+
+#define MBIM_PIN_OP_ENTER 0
+#define MBIM_PIN_OP_ENABLE 1
+#define MBIM_PIN_OP_DISABLE 2
+#define MBIM_PIN_OP_CHANGE 3
+ uint32_t op;
+ uint32_t pin_offs;
+ uint32_t pin_size;
+ uint32_t newpin_offs;
+ uint32_t newpin_size;
+#define MBIM_PIN_MAXLEN 32
+ uint8_t data[2 * MBIM_PIN_MAXLEN];
+} __packed;
+
+struct mbim_cid_pin_info {
+ uint32_t type;
+
+#define MBIM_PIN_STATE_UNLOCKED 0
+#define MBIM_PIN_STATE_LOCKED 1
+ uint32_t state;
+ uint32_t remaining_attempts;
+} __packed;
+
+struct mbim_cid_pin_list_info {
+ struct mbim_pin_desc {
+
+#define MBIM_PINMODE_NOTSUPPORTED 0
+#define MBIM_PINMODE_ENABLED 1
+#define MBIM_PINMODE_DISABLED 2
+ uint32_t mode;
+
+#define MBIM_PINFORMAT_UNKNOWN 0
+#define MBIM_PINFORMAT_NUMERIC 1
+#define MBIM_PINFORMAT_ALPHANUMERIC 2
+ uint32_t format;
+
+ uint32_t minlen;
+ uint32_t maxlen;
+ }
+ pin1,
+ pin2,
+ dev_sim_pin,
+ first_dev_sim_pin,
+ net_pin,
+ net_sub_pin,
+ svp_pin,
+ corp_pin,
+ subsidy_lock,
+ custom;
+} __packed;
+
+struct mbim_cid_device_caps {
+#define MBIM_DEVTYPE_UNKNOWN 0
+#define MBIM_DEVTYPE_EMBEDDED 1
+#define MBIM_DEVTYPE_REMOVABLE 2
+#define MBIM_DEVTYPE_REMOTE 3
+ uint32_t devtype;
+
+ uint32_t cellclass; /* values: MBIM_CELLULAR_CLASS */
+ uint32_t voiceclass;
+ uint32_t simclass;
+ uint32_t dataclass; /* values: MBIM_DATA_CLASS */
+ uint32_t smscaps;
+ uint32_t cntrlcaps;
+ uint32_t max_sessions;
+
+ uint32_t custdataclass_offs;
+ uint32_t custdataclass_size;
+
+ uint32_t devid_offs;
+ uint32_t devid_size;
+
+ uint32_t fwinfo_offs;
+ uint32_t fwinfo_size;
+
+ uint32_t hwinfo_offs;
+ uint32_t hwinfo_size;
+
+ uint32_t data[];
+} __packed;
+
+struct mbim_cid_registration_state {
+ uint32_t provid_offs;
+ uint32_t provid_size;
+
+#define MBIM_REGACTION_AUTOMATIC 0
+#define MBIM_REGACTION_MANUAL 1
+ uint32_t regaction;
+ uint32_t data_class;
+
+ uint32_t data[];
+} __packed;
+
+struct mbim_cid_registration_state_info {
+ uint32_t nwerror;
+
+ uint32_t regstate; /* values: MBIM_REGISTER_STATE */
+
+#define MBIM_REGMODE_UNKNOWN 0
+#define MBIM_REGMODE_AUTOMATIC 1
+#define MBIM_REGMODE_MANUAL 2
+ uint32_t regmode;
+
+ uint32_t availclasses; /* values: MBIM_DATA_CLASS */
+ uint32_t curcellclass; /* values: MBIM_CELLULAR_CLASS */
+
+ uint32_t provid_offs;
+ uint32_t provid_size;
+
+ uint32_t provname_offs;
+ uint32_t provname_size;
+
+ uint32_t roamingtxt_offs;
+ uint32_t roamingtxt_size;
+
+#define MBIM_REGFLAGS_NONE 0
+#define MBIM_REGFLAGS_MANUAL_NOT_AVAILABLE 1
+#define MBIM_REGFLAGS_PACKETSERVICE_AUTOATTACH 2
+ uint32_t regflag;
+
+ uint32_t data[];
+} __packed;
+
+struct mbim_cid_packet_service {
+#define MBIM_PKTSERVICE_ACTION_ATTACH 0
+#define MBIM_PKTSERVICE_ACTION_DETACH 1
+ uint32_t action;
+} __packed;
+
+struct mbim_cid_packet_service_info {
+ uint32_t nwerror;
+
+#define MBIM_PKTSERVICE_STATE_UNKNOWN 0
+#define MBIM_PKTSERVICE_STATE_ATTACHING 1
+#define MBIM_PKTSERVICE_STATE_ATTACHED 2
+#define MBIM_PKTSERVICE_STATE_DETACHING 3
+#define MBIM_PKTSERVICE_STATE_DETACHED 4
+ uint32_t state;
+
+ uint32_t highest_dataclass;
+ uint64_t uplink_speed;
+ uint64_t downlink_speed;
+} __packed;
+
+struct mbim_cid_signal_state {
+ uint32_t rssi;
+ uint32_t err_rate;
+ uint32_t ss_intvl;
+ uint32_t rssi_thr;
+ uint32_t err_thr;
+} __packed;
+
+struct mbim_cid_connect {
+ uint32_t sessionid;
+
+#define MBIM_CONNECT_DEACTIVATE 0
+#define MBIM_CONNECT_ACTIVATE 1
+ uint32_t command;
+
+#define MBIM_ACCESS_MAXLEN 200
+ uint32_t access_offs;
+ uint32_t access_size;
+
+#define MBIM_USER_MAXLEN 510
+ uint32_t user_offs;
+ uint32_t user_size;
+
+#define MBIM_PASSWD_MAXLEN 510
+ uint32_t passwd_offs;
+ uint32_t passwd_size;
+
+#define MBIM_COMPRESSION_NONE 0
+#define MBIM_COMPRESSION_ENABLE 1
+ uint32_t compression;
+
+#define MBIM_AUTHPROT_NONE 0
+#define MBIM_AUTHPROT_PAP 1
+#define MBIM_AUTHPROT_CHAP 2
+#define MBIM_AUTHPROT_MSCHAP 3
+ uint32_t authprot;
+
+#define MBIM_CONTEXT_IPTYPE_DEFAULT 0
+#define MBIM_CONTEXT_IPTYPE_IPV4 1
+#define MBIM_CONTEXT_IPTYPE_IPV6 2
+#define MBIM_CONTEXT_IPTYPE_IPV4V6 3
+#define MBIM_CONTEXT_IPTYPE_IPV4ANDV6 4
+ uint32_t iptype;
+
+ uint8_t context[MBIM_UUID_LEN];
+
+ uint8_t data[MBIM_ACCESS_MAXLEN + MBIM_USER_MAXLEN +
+ MBIM_PASSWD_MAXLEN];
+
+} __packed;
+
+struct mbim_cid_connect_info {
+ uint32_t sessionid;
+
+#define MBIM_ACTIVATION_STATE_UNKNOWN 0
+#define MBIM_ACTIVATION_STATE_ACTIVATED 1
+#define MBIM_ACTIVATION_STATE_ACTIVATING 2
+#define MBIM_ACTIVATION_STATE_DEACTIVATED 3
+#define MBIM_ACTIVATION_STATE_DEACTIVATING 4
+ uint32_t activation;
+
+ uint32_t voice;
+ uint32_t iptype;
+ uint8_t context[MBIM_UUID_LEN];
+ uint32_t nwerror;
+} __packed;
+
+struct mbim_cid_ipv4_element {
+ uint32_t prefixlen;
+ uint32_t addr;
+} __packed;
+
+struct mbim_cid_ipv6_element {
+ uint32_t prefixlen;
+ uint8_t addr[16];
+} __packed;
+
+struct mbim_cid_ip_configuration_info {
+ uint32_t sessionid;
+
+#define MBIM_IPCONF_HAS_ADDRINFO 0x0001
+#define MBIM_IPCONF_HAS_GWINFO 0x0002
+#define MBIM_IPCONF_HAS_DNSINFO 0x0004
+#define MBIM_IPCONF_HAS_MTUINFO 0x0008
+ uint32_t ipv4_available;
+ uint32_t ipv6_available;
+
+ uint32_t ipv4_naddr;
+ uint32_t ipv4_addroffs;
+ uint32_t ipv6_naddr;
+ uint32_t ipv6_addroffs;
+
+ uint32_t ipv4_gwoffs;
+ uint32_t ipv6_gwoffs;
+
+ uint32_t ipv4_ndnssrv;
+ uint32_t ipv4_dnssrvoffs;
+ uint32_t ipv6_ndnssrv;
+ uint32_t ipv6_dnssrvoffs;
+
+ uint32_t ipv4_mtu;
+ uint32_t ipv6_mtu;
+
+ uint32_t data[];
+} __packed;
+
+struct mbim_cid_packet_statistics_info {
+ uint32_t in_discards;
+ uint32_t in_errors;
+ uint64_t in_octets;
+ uint64_t in_packets;
+ uint64_t out_octets;
+ uint64_t out_packets;
+ uint32_t out_errors;
+ uint32_t out_discards;
+} __packed;
+
+
+#ifdef _KERNEL
+
+struct mbim_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+#define MBIM_VER_MAJOR(v) (((v) >> 8) & 0x0f)
+#define MBIM_VER_MINOR(v) ((v) & 0x0f)
+ uWord bcdMBIMVersion;
+ uWord wMaxControlMessage;
+ uByte bNumberFilters;
+ uByte bMaxFilterSize;
+ uWord wMaxSegmentSize;
+ uByte bmNetworkCapabilities;
+} __packed;
+
+/*
+ * NCM Parameters
+ */
+#define NCM_GET_NTB_PARAMETERS 0x80
+
+struct ncm_ntb_parameters {
+ uWord wLength;
+ uWord bmNtbFormatsSupported;
+#define NCM_FORMAT_NTB16 0x0001
+#define NCM_FORMAT_NTB32 0x0002
+ uDWord dwNtbInMaxSize;
+ uWord wNdpInDivisor;
+ uWord wNdpInPayloadRemainder;
+ uWord wNdpInAlignment;
+ uWord wReserved1;
+ uDWord dwNtbOutMaxSize;
+ uWord wNdpOutDivisor;
+ uWord wNdpOutPayloadRemainder;
+ uWord wNdpOutAlignment;
+ uWord wNtbOutMaxDatagrams;
+} __packed;
+
+/*
+ * NCM Encoding
+ */
+#define MBIM_HDR16_LEN \
+ (sizeof(struct ncm_header16) + sizeof(struct ncm_pointer16))
+#define MBIM_HDR32_LEN \
+ (sizeof(struct ncm_header32) + sizeof(struct ncm_pointer32))
+
+struct ncm_header16 {
+#define NCM_HDR16_SIG 0x484d434e
+ uDWord dwSignature;
+ uWord wHeaderLength;
+ uWord wSequence;
+ uWord wBlockLength;
+ uWord wNdpIndex;
+} __packed;
+
+struct ncm_header32 {
+#define NCM_HDR32_SIG 0x686d636e
+ uDWord dwSignature;
+ uWord wHeaderLength;
+ uWord wSequence;
+ uDWord dwBlockLength;
+ uDWord dwNdpIndex;
+} __packed;
+
+
+#define MBIM_NCM_NTH_SIDSHIFT 24
+#define MBIM_NCM_NTH_GETSID(s) (((s) > MBIM_NCM_NTH_SIDSHIFT) & 0xff)
+
+struct ncm_pointer16_dgram {
+ uWord wDatagramIndex;
+ uWord wDatagramLen;
+} __packed;
+
+struct ncm_pointer16 {
+#define MBIM_NCM_NTH16_IPS 0x00535049
+#define MBIM_NCM_NTH16_ISISG(s) (((s) & 0x00ffffff) == MBIM_NCM_NTH16_IPS)
+#define MBIM_NCM_NTH16_SIG(s) \
+ ((((s) & 0xff) << MBIM_NCM_NTH_SIDSHIFT) | MBIM_NCM_NTH16_IPS)
+ uDWord dwSignature;
+ uWord wLength;
+ uWord wNextNdpIndex;
+
+ /* Minimum is two datagrams, but can be more */
+ struct ncm_pointer16_dgram dgram[2];
+} __packed;
+
+struct ncm_pointer32_dgram {
+ uDWord dwDatagramIndex;
+ uDWord dwDatagramLen;
+} __packed;
+
+struct ncm_pointer32 {
+#define MBIM_NCM_NTH32_IPS 0x00737069
+#define MBIM_NCM_NTH32_ISISG(s) \
+ (((s) & 0x00ffffff) == MBIM_NCM_NTH32_IPS)
+#define MBIM_NCM_NTH32_SIG(s) \
+ ((((s) & 0xff) << MBIM_NCM_NTH_SIDSHIFT) | MBIM_NCM_NTH32_IPS)
+ uDWord dwSignature;
+ uWord wLength;
+ uWord wReserved6;
+ uDWord dwNextNdpIndex;
+ uDWord dwReserved12;
+
+ /* Minimum is two datagrams, but can be more */
+ struct ncm_pointer32_dgram dgram[2];
+} __packed;
+
+#endif /* _KERNEL */
+
+#endif /* _MBIM_H_ */
diff --git a/sys/dev/usb/net/ruephy.c b/sys/dev/usb/net/ruephy.c
new file mode 100644
index 000000000000..2b5358ab8d0c
--- /dev/null
+++ b/sys/dev/usb/net/ruephy.c
@@ -0,0 +1,217 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ */
+
+/*
+ * driver for RealTek RTL8150 internal PHY
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/socket.h>
+#include <sys/bus.h>
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/if_media.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+#include "miidevs.h"
+
+#include <dev/usb/net/ruephyreg.h>
+
+#include "miibus_if.h"
+
+static int ruephy_probe(device_t);
+static int ruephy_attach(device_t);
+
+static device_method_t ruephy_methods[] = {
+ /* device interface */
+ DEVMETHOD(device_probe, ruephy_probe),
+ DEVMETHOD(device_attach, ruephy_attach),
+ DEVMETHOD(device_detach, mii_phy_detach),
+ DEVMETHOD(device_shutdown, bus_generic_shutdown),
+ DEVMETHOD_END
+};
+
+static driver_t ruephy_driver = {
+ .name = "ruephy",
+ .methods = ruephy_methods,
+ .size = sizeof(struct mii_softc)
+};
+
+DRIVER_MODULE(ruephy, miibus, ruephy_driver, 0, 0);
+
+static int ruephy_service(struct mii_softc *, struct mii_data *, int);
+static void ruephy_reset(struct mii_softc *);
+static void ruephy_status(struct mii_softc *);
+
+/*
+ * The RealTek RTL8150 internal PHY doesn't have vendor/device ID
+ * registers; rue(4) fakes up a return value of all zeros.
+ */
+static const struct mii_phydesc ruephys[] = {
+ { 0, 0, "RealTek RTL8150 internal media interface" },
+ MII_PHY_END
+};
+
+static const struct mii_phy_funcs ruephy_funcs = {
+ ruephy_service,
+ ruephy_status,
+ ruephy_reset
+};
+
+static int
+ruephy_probe(device_t dev)
+{
+
+ if (strcmp(device_get_name(device_get_parent(device_get_parent(dev))),
+ "rue") == 0)
+ return (mii_phy_dev_probe(dev, ruephys, BUS_PROBE_DEFAULT));
+ return (ENXIO);
+}
+
+static int
+ruephy_attach(device_t dev)
+{
+
+ mii_phy_dev_attach(dev, MIIF_NOISOLATE | MIIF_NOMANPAUSE,
+ &ruephy_funcs, 1);
+ return (0);
+}
+
+static int
+ruephy_service(struct mii_softc *sc, struct mii_data *mii, int cmd)
+{
+ struct ifmedia_entry *ife = mii->mii_media.ifm_cur;
+ int reg;
+
+ switch (cmd) {
+ case MII_POLLSTAT:
+ break;
+
+ case MII_MEDIACHG:
+ mii_phy_setmedia(sc);
+ break;
+
+ case MII_TICK:
+ /*
+ * Only used for autonegotiation.
+ */
+ if (IFM_SUBTYPE(ife->ifm_media) != IFM_AUTO)
+ break;
+
+ /*
+ * Check to see if we have link. If we do, we don't
+ * need to restart the autonegotiation process. Read
+ * the MSR twice in case it's latched.
+ */
+ reg = PHY_READ(sc, RUEPHY_MII_MSR) |
+ PHY_READ(sc, RUEPHY_MII_MSR);
+ if (reg & RUEPHY_MSR_LINK)
+ break;
+
+ /* Only retry autonegotiation every mii_anegticks seconds. */
+ if (sc->mii_ticks <= sc->mii_anegticks)
+ break;
+
+ sc->mii_ticks = 0;
+ PHY_RESET(sc);
+ if (mii_phy_auto(sc) == EJUSTRETURN)
+ return (0);
+ break;
+ }
+
+ /* Update the media status. */
+ PHY_STATUS(sc);
+
+ /* Callback if something changed. */
+ mii_phy_update(sc, cmd);
+
+ return (0);
+}
+
+static void
+ruephy_reset(struct mii_softc *sc)
+{
+
+ mii_phy_reset(sc);
+
+ /*
+ * XXX RealTek RTL8150 PHY doesn't set the BMCR properly after
+ * XXX reset, which breaks autonegotiation.
+ */
+ PHY_WRITE(sc, MII_BMCR, (BMCR_S100 | BMCR_AUTOEN | BMCR_FDX));
+}
+
+static void
+ruephy_status(struct mii_softc *phy)
+{
+ struct mii_data *mii = phy->mii_pdata;
+ struct ifmedia_entry *ife = mii->mii_media.ifm_cur;
+ int bmsr, bmcr, msr;
+
+ mii->mii_media_status = IFM_AVALID;
+ mii->mii_media_active = IFM_ETHER;
+
+ msr = PHY_READ(phy, RUEPHY_MII_MSR) | PHY_READ(phy, RUEPHY_MII_MSR);
+ if (msr & RUEPHY_MSR_LINK)
+ mii->mii_media_status |= IFM_ACTIVE;
+
+ bmcr = PHY_READ(phy, MII_BMCR);
+ if (bmcr & BMCR_ISO) {
+ mii->mii_media_active |= IFM_NONE;
+ mii->mii_media_status = 0;
+ return;
+ }
+
+ bmsr = PHY_READ(phy, MII_BMSR) | PHY_READ(phy, MII_BMSR);
+ if (bmcr & BMCR_AUTOEN) {
+ if ((bmsr & BMSR_ACOMP) == 0) {
+ /* Erg, still trying, I guess... */
+ mii->mii_media_active |= IFM_NONE;
+ return;
+ }
+
+ if (msr & RUEPHY_MSR_SPEED100)
+ mii->mii_media_active |= IFM_100_TX;
+ else
+ mii->mii_media_active |= IFM_10_T;
+
+ if (msr & RUEPHY_MSR_DUPLEX)
+ mii->mii_media_active |=
+ IFM_FDX | mii_phy_flowstatus(phy);
+ else
+ mii->mii_media_active |= IFM_HDX;
+ } else
+ mii->mii_media_active = ife->ifm_media;
+}
diff --git a/sys/dev/usb/net/ruephyreg.h b/sys/dev/usb/net/ruephyreg.h
new file mode 100644
index 000000000000..c4d2abeb4049
--- /dev/null
+++ b/sys/dev/usb/net/ruephyreg.h
@@ -0,0 +1,38 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ */
+
+#ifndef _RUEPHYREG_H_
+#define _RUEPHYREG_H_
+
+#define RUEPHY_MII_MSR 0x0137 /* B, R/W */
+#define RUEPHY_MSR_RXFCE 0x40
+#define RUEPHY_MSR_DUPLEX 0x10
+#define RUEPHY_MSR_SPEED100 0x08
+#define RUEPHY_MSR_LINK 0x04
+
+#endif /* _RUEPHYREG_H_ */
diff --git a/sys/dev/usb/net/uhso.c b/sys/dev/usb/net/uhso.c
new file mode 100644
index 000000000000..24135f6ccd5a
--- /dev/null
+++ b/sys/dev/usb/net/uhso.c
@@ -0,0 +1,1928 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Fredrik Lindberg <fli@shapeshifter.se>
+ * 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.
+ *
+ */
+
+#include <sys/param.h>
+#include <sys/eventhandler.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/tty.h>
+#include <sys/sysctl.h>
+#include <sys/condvar.h>
+#include <sys/sx.h>
+#include <sys/proc.h>
+#include <sys/conf.h>
+#include <sys/bus.h>
+#include <sys/systm.h>
+#include <sys/limits.h>
+
+#include <machine/bus.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_types.h>
+#include <net/netisr.h>
+#include <net/bpf.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_cdc.h>
+#include "usbdevs.h"
+#define USB_DEBUG_VAR uhso_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_msctest.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+struct uhso_tty {
+ struct uhso_softc *ht_sc;
+ struct usb_xfer *ht_xfer[3];
+ int ht_muxport; /* Mux. port no */
+ int ht_open;
+ char ht_name[32];
+};
+
+struct uhso_softc {
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+ uint32_t sc_type; /* Interface definition */
+ int sc_radio;
+
+ struct usb_xfer *sc_xfer[3];
+ uint8_t sc_iface_no;
+ uint8_t sc_iface_index;
+
+ /* Control pipe */
+ struct usb_xfer * sc_ctrl_xfer[2];
+ uint8_t sc_ctrl_iface_no;
+
+ /* Network */
+ struct usb_xfer *sc_if_xfer[2];
+ if_t sc_ifp;
+ struct mbuf *sc_mwait; /* Partial packet */
+ size_t sc_waitlen; /* No. of outstanding bytes */
+ struct mbufq sc_rxq;
+ struct callout sc_c;
+
+ /* TTY related structures */
+ struct ucom_super_softc sc_super_ucom;
+ int sc_ttys;
+ struct uhso_tty *sc_tty;
+ struct ucom_softc *sc_ucom;
+ int sc_msr;
+ int sc_lsr;
+ int sc_line;
+};
+
+#define UHSO_MAX_MTU 2048
+
+/*
+ * There are mainly two type of cards floating around.
+ * The first one has 2,3 or 4 interfaces with a multiplexed serial port
+ * and packet interface on the first interface and bulk serial ports
+ * on the others.
+ * The second type of card has several other interfaces, their purpose
+ * can be detected during run-time.
+ */
+#define UHSO_IFACE_SPEC(usb_type, port, port_type) \
+ (((usb_type) << 24) | ((port) << 16) | (port_type))
+
+#define UHSO_IFACE_USB_TYPE(x) ((x >> 24) & 0xff)
+#define UHSO_IFACE_PORT(x) ((x >> 16) & 0xff)
+#define UHSO_IFACE_PORT_TYPE(x) (x & 0xff)
+
+/*
+ * USB interface types
+ */
+#define UHSO_IF_NET 0x01 /* Network packet interface */
+#define UHSO_IF_MUX 0x02 /* Multiplexed serial port */
+#define UHSO_IF_BULK 0x04 /* Bulk interface */
+
+/*
+ * Port types
+ */
+#define UHSO_PORT_UNKNOWN 0x00
+#define UHSO_PORT_SERIAL 0x01 /* Serial port */
+#define UHSO_PORT_NETWORK 0x02 /* Network packet interface */
+
+/*
+ * Multiplexed serial port destination sub-port names
+ */
+#define UHSO_MPORT_TYPE_CTL 0x00 /* Control port */
+#define UHSO_MPORT_TYPE_APP 0x01 /* Application */
+#define UHSO_MPORT_TYPE_PCSC 0x02
+#define UHSO_MPORT_TYPE_GPS 0x03
+#define UHSO_MPORT_TYPE_APP2 0x04 /* Secondary application */
+#define UHSO_MPORT_TYPE_MAX UHSO_MPORT_TYPE_APP2
+#define UHSO_MPORT_TYPE_NOMAX 8 /* Max number of mux ports */
+
+/*
+ * Port definitions
+ * Note that these definitions are arbitrary and do not match the values
+ * returned by the auto config descriptor.
+ */
+#define UHSO_PORT_TYPE_UNKNOWN 0x00
+#define UHSO_PORT_TYPE_CTL 0x01
+#define UHSO_PORT_TYPE_APP 0x02
+#define UHSO_PORT_TYPE_APP2 0x03
+#define UHSO_PORT_TYPE_MODEM 0x04
+#define UHSO_PORT_TYPE_NETWORK 0x05
+#define UHSO_PORT_TYPE_DIAG 0x06
+#define UHSO_PORT_TYPE_DIAG2 0x07
+#define UHSO_PORT_TYPE_GPS 0x08
+#define UHSO_PORT_TYPE_GPSCTL 0x09
+#define UHSO_PORT_TYPE_PCSC 0x0a
+#define UHSO_PORT_TYPE_MSD 0x0b
+#define UHSO_PORT_TYPE_VOICE 0x0c
+#define UHSO_PORT_TYPE_MAX 0x0c
+
+static eventhandler_tag uhso_etag;
+
+/* Overall port type */
+static char *uhso_port[] = {
+ "Unknown",
+ "Serial",
+ "Network",
+ "Network/Serial"
+};
+
+/*
+ * Map between interface port type read from device and description type.
+ * The position in this array is a direct map to the auto config
+ * descriptor values.
+ */
+static unsigned char uhso_port_map[] = {
+ UHSO_PORT_TYPE_UNKNOWN,
+ UHSO_PORT_TYPE_DIAG,
+ UHSO_PORT_TYPE_GPS,
+ UHSO_PORT_TYPE_GPSCTL,
+ UHSO_PORT_TYPE_APP,
+ UHSO_PORT_TYPE_APP2,
+ UHSO_PORT_TYPE_CTL,
+ UHSO_PORT_TYPE_NETWORK,
+ UHSO_PORT_TYPE_MODEM,
+ UHSO_PORT_TYPE_MSD,
+ UHSO_PORT_TYPE_PCSC,
+ UHSO_PORT_TYPE_VOICE
+};
+static char uhso_port_map_max = sizeof(uhso_port_map) / sizeof(char);
+
+static unsigned char uhso_mux_port_map[] = {
+ UHSO_PORT_TYPE_CTL,
+ UHSO_PORT_TYPE_APP,
+ UHSO_PORT_TYPE_PCSC,
+ UHSO_PORT_TYPE_GPS,
+ UHSO_PORT_TYPE_APP2
+};
+
+static char *uhso_port_type[] = {
+ "Unknown", /* Not a valid port */
+ "Control",
+ "Application",
+ "Application (Secondary)",
+ "Modem",
+ "Network",
+ "Diagnostic",
+ "Diagnostic (Secondary)",
+ "GPS",
+ "GPS Control",
+ "PC Smartcard",
+ "MSD",
+ "Voice",
+};
+
+static char *uhso_port_type_sysctl[] = {
+ "unknown",
+ "control",
+ "application",
+ "application",
+ "modem",
+ "network",
+ "diagnostic",
+ "diagnostic",
+ "gps",
+ "gps_control",
+ "pcsc",
+ "msd",
+ "voice",
+};
+
+#define UHSO_STATIC_IFACE 0x01
+#define UHSO_AUTO_IFACE 0x02
+
+/* ifnet device unit allocations */
+static struct unrhdr *uhso_ifnet_unit = NULL;
+
+static const STRUCT_USB_HOST_ID uhso_devs[] = {
+#define UHSO_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
+ /* Option GlobeTrotter MAX 7.2 with upgraded firmware */
+ UHSO_DEV(OPTION, GTMAX72, UHSO_STATIC_IFACE),
+ /* Option GlobeSurfer iCON 7.2 */
+ UHSO_DEV(OPTION, GSICON72, UHSO_STATIC_IFACE),
+ /* Option iCON 225 */
+ UHSO_DEV(OPTION, GTHSDPA, UHSO_STATIC_IFACE),
+ /* Option GlobeSurfer iCON HSUPA */
+ UHSO_DEV(OPTION, GSICONHSUPA, UHSO_STATIC_IFACE),
+ /* Option GlobeTrotter HSUPA */
+ UHSO_DEV(OPTION, GTHSUPA, UHSO_STATIC_IFACE),
+ /* GE40x */
+ UHSO_DEV(OPTION, GE40X, UHSO_AUTO_IFACE),
+ UHSO_DEV(OPTION, GE40X_1, UHSO_AUTO_IFACE),
+ UHSO_DEV(OPTION, GE40X_2, UHSO_AUTO_IFACE),
+ UHSO_DEV(OPTION, GE40X_3, UHSO_AUTO_IFACE),
+ /* Option GlobeSurfer iCON 401 */
+ UHSO_DEV(OPTION, ICON401, UHSO_AUTO_IFACE),
+ /* Option GlobeTrotter Module 382 */
+ UHSO_DEV(OPTION, GMT382, UHSO_AUTO_IFACE),
+ /* Option GTM661W */
+ UHSO_DEV(OPTION, GTM661W, UHSO_AUTO_IFACE),
+ /* Option iCON EDGE */
+ UHSO_DEV(OPTION, ICONEDGE, UHSO_STATIC_IFACE),
+ /* Option Module HSxPA */
+ UHSO_DEV(OPTION, MODHSXPA, UHSO_STATIC_IFACE),
+ /* Option iCON 321 */
+ UHSO_DEV(OPTION, ICON321, UHSO_STATIC_IFACE),
+ /* Option iCON 322 */
+ UHSO_DEV(OPTION, GTICON322, UHSO_STATIC_IFACE),
+ /* Option iCON 505 */
+ UHSO_DEV(OPTION, ICON505, UHSO_AUTO_IFACE),
+ /* Option iCON 452 */
+ UHSO_DEV(OPTION, ICON505, UHSO_AUTO_IFACE),
+#undef UHSO_DEV
+};
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uhso, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB uhso");
+static int uhso_autoswitch = 1;
+SYSCTL_INT(_hw_usb_uhso, OID_AUTO, auto_switch, CTLFLAG_RWTUN,
+ &uhso_autoswitch, 0, "Automatically switch to modem mode");
+
+#ifdef USB_DEBUG
+#ifdef UHSO_DEBUG
+static int uhso_debug = UHSO_DEBUG;
+#else
+static int uhso_debug = -1;
+#endif
+
+SYSCTL_INT(_hw_usb_uhso, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &uhso_debug, 0, "Debug level");
+
+#define UHSO_DPRINTF(n, x, ...) {\
+ if (uhso_debug >= n) {\
+ printf("%s: " x, __func__, ##__VA_ARGS__);\
+ }\
+}
+#else
+#define UHSO_DPRINTF(n, x, ...)
+#endif
+
+#ifdef UHSO_DEBUG_HEXDUMP
+# define UHSO_HEXDUMP(_buf, _len) do { \
+ { \
+ size_t __tmp; \
+ const char *__buf = (const char *)_buf; \
+ for (__tmp = 0; __tmp < _len; __tmp++) \
+ printf("%02hhx ", *__buf++); \
+ printf("\n"); \
+ } \
+} while(0)
+#else
+# define UHSO_HEXDUMP(_buf, _len)
+#endif
+
+enum {
+ UHSO_MUX_ENDPT_INTR = 0,
+ UHSO_MUX_ENDPT_MAX
+};
+
+enum {
+ UHSO_CTRL_READ = 0,
+ UHSO_CTRL_WRITE,
+ UHSO_CTRL_MAX
+};
+
+enum {
+ UHSO_IFNET_READ = 0,
+ UHSO_IFNET_WRITE,
+ UHSO_IFNET_MAX
+};
+
+enum {
+ UHSO_BULK_ENDPT_READ = 0,
+ UHSO_BULK_ENDPT_WRITE,
+ UHSO_BULK_ENDPT_INTR,
+ UHSO_BULK_ENDPT_MAX
+};
+
+static usb_callback_t uhso_mux_intr_callback;
+static usb_callback_t uhso_mux_read_callback;
+static usb_callback_t uhso_mux_write_callback;
+static usb_callback_t uhso_bs_read_callback;
+static usb_callback_t uhso_bs_write_callback;
+static usb_callback_t uhso_bs_intr_callback;
+static usb_callback_t uhso_ifnet_read_callback;
+static usb_callback_t uhso_ifnet_write_callback;
+
+/* Config used for the default control pipes */
+static const struct usb_config uhso_ctrl_config[UHSO_CTRL_MAX] = {
+ [UHSO_CTRL_READ] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00,
+ .direction = UE_DIR_ANY,
+ .flags = { .pipe_bof = 1, .short_xfer_ok = 1 },
+ .bufsize = sizeof(struct usb_device_request) + 1024,
+ .callback = &uhso_mux_read_callback
+ },
+
+ [UHSO_CTRL_WRITE] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00,
+ .direction = UE_DIR_ANY,
+ .flags = { .pipe_bof = 1, .force_short_xfer = 1 },
+ .bufsize = sizeof(struct usb_device_request) + 1024,
+ .timeout = 1000,
+ .callback = &uhso_mux_write_callback
+ }
+};
+
+/* Config for the multiplexed serial ports */
+static const struct usb_config uhso_mux_config[UHSO_MUX_ENDPT_MAX] = {
+ [UHSO_MUX_ENDPT_INTR] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = { .short_xfer_ok = 1 },
+ .bufsize = 0,
+ .callback = &uhso_mux_intr_callback,
+ }
+};
+
+/* Config for the raw IP-packet interface */
+static const struct usb_config uhso_ifnet_config[UHSO_IFNET_MAX] = {
+ [UHSO_IFNET_READ] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = { .pipe_bof = 1, .short_xfer_ok = 1 },
+ .bufsize = MCLBYTES,
+ .callback = &uhso_ifnet_read_callback
+ },
+ [UHSO_IFNET_WRITE] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .flags = { .pipe_bof = 1, .force_short_xfer = 1 },
+ .bufsize = MCLBYTES,
+ .timeout = 5 * USB_MS_HZ,
+ .callback = &uhso_ifnet_write_callback
+ }
+};
+
+/* Config for interfaces with normal bulk serial ports */
+static const struct usb_config uhso_bs_config[UHSO_BULK_ENDPT_MAX] = {
+ [UHSO_BULK_ENDPT_READ] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = { .pipe_bof = 1, .short_xfer_ok = 1 },
+ .bufsize = 4096,
+ .callback = &uhso_bs_read_callback
+ },
+
+ [UHSO_BULK_ENDPT_WRITE] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .flags = { .pipe_bof = 1, .force_short_xfer = 1 },
+ .bufsize = 8192,
+ .callback = &uhso_bs_write_callback
+ },
+
+ [UHSO_BULK_ENDPT_INTR] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = { .short_xfer_ok = 1 },
+ .bufsize = 0,
+ .callback = &uhso_bs_intr_callback,
+ }
+};
+
+static int uhso_probe_iface(struct uhso_softc *, int,
+ int (*probe)(struct usb_device *, int));
+static int uhso_probe_iface_auto(struct usb_device *, int);
+static int uhso_probe_iface_static(struct usb_device *, int);
+static int uhso_attach_muxserial(struct uhso_softc *, struct usb_interface *,
+ int type);
+static int uhso_attach_bulkserial(struct uhso_softc *, struct usb_interface *,
+ int type);
+static int uhso_attach_ifnet(struct uhso_softc *, struct usb_interface *,
+ int type);
+static void uhso_test_autoinst(void *, struct usb_device *,
+ struct usb_attach_arg *);
+static int uhso_driver_loaded(struct module *, int, void *);
+static int uhso_radio_sysctl(SYSCTL_HANDLER_ARGS);
+static int uhso_radio_ctrl(struct uhso_softc *, int);
+
+static void uhso_free(struct ucom_softc *);
+static void uhso_ucom_start_read(struct ucom_softc *);
+static void uhso_ucom_stop_read(struct ucom_softc *);
+static void uhso_ucom_start_write(struct ucom_softc *);
+static void uhso_ucom_stop_write(struct ucom_softc *);
+static void uhso_ucom_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *);
+static void uhso_ucom_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void uhso_ucom_cfg_set_rts(struct ucom_softc *, uint8_t);
+static void uhso_if_init(void *);
+static void uhso_if_start(if_t);
+static void uhso_if_stop(struct uhso_softc *);
+static int uhso_if_ioctl(if_t, u_long, caddr_t);
+static int uhso_if_output(if_t, struct mbuf *,
+ const struct sockaddr *, struct route *);
+static void uhso_if_rxflush(void *);
+
+static device_probe_t uhso_probe;
+static device_attach_t uhso_attach;
+static device_detach_t uhso_detach;
+static void uhso_free_softc(struct uhso_softc *);
+
+static device_method_t uhso_methods[] = {
+ DEVMETHOD(device_probe, uhso_probe),
+ DEVMETHOD(device_attach, uhso_attach),
+ DEVMETHOD(device_detach, uhso_detach),
+ { 0, 0 }
+};
+
+static driver_t uhso_driver = {
+ .name = "uhso",
+ .methods = uhso_methods,
+ .size = sizeof(struct uhso_softc)
+};
+
+DRIVER_MODULE(uhso, uhub, uhso_driver, uhso_driver_loaded, NULL);
+MODULE_DEPEND(uhso, ucom, 1, 1, 1);
+MODULE_DEPEND(uhso, usb, 1, 1, 1);
+MODULE_VERSION(uhso, 1);
+USB_PNP_HOST_INFO(uhso_devs);
+
+static struct ucom_callback uhso_ucom_callback = {
+ .ucom_cfg_get_status = &uhso_ucom_cfg_get_status,
+ .ucom_cfg_set_dtr = &uhso_ucom_cfg_set_dtr,
+ .ucom_cfg_set_rts = &uhso_ucom_cfg_set_rts,
+ .ucom_start_read = uhso_ucom_start_read,
+ .ucom_stop_read = uhso_ucom_stop_read,
+ .ucom_start_write = uhso_ucom_start_write,
+ .ucom_stop_write = uhso_ucom_stop_write,
+ .ucom_free = &uhso_free,
+};
+
+static int
+uhso_probe(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+ int error;
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != 0)
+ return (ENXIO);
+ if (uaa->info.bDeviceClass != 0xff)
+ return (ENXIO);
+
+ error = usbd_lookup_id_by_uaa(uhso_devs, sizeof(uhso_devs), uaa);
+ if (error != 0)
+ return (error);
+
+ /*
+ * Probe device to see if we are able to attach
+ * to this interface or not.
+ */
+ if (USB_GET_DRIVER_INFO(uaa) == UHSO_AUTO_IFACE) {
+ if (uhso_probe_iface_auto(uaa->device,
+ uaa->info.bIfaceNum) == 0)
+ return (ENXIO);
+ }
+ return (error);
+}
+
+static int
+uhso_attach(device_t self)
+{
+ struct uhso_softc *sc = device_get_softc(self);
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+ struct usb_interface_descriptor *id;
+ struct sysctl_ctx_list *sctx;
+ struct sysctl_oid *soid;
+ struct sysctl_oid *tree = NULL, *tty_node;
+ struct ucom_softc *ucom;
+ struct uhso_tty *ht;
+ int i, error, port;
+ void *probe_f;
+ usb_error_t uerr;
+ char *desc;
+
+ sc->sc_dev = self;
+ sc->sc_udev = uaa->device;
+ mtx_init(&sc->sc_mtx, "uhso", NULL, MTX_DEF);
+ mbufq_init(&sc->sc_rxq, INT_MAX); /* XXXGL: sane maximum */
+ ucom_ref(&sc->sc_super_ucom);
+
+ sc->sc_radio = 1;
+
+ id = usbd_get_interface_descriptor(uaa->iface);
+ sc->sc_ctrl_iface_no = id->bInterfaceNumber;
+
+ sc->sc_iface_no = uaa->info.bIfaceNum;
+ sc->sc_iface_index = uaa->info.bIfaceIndex;
+
+ /* Setup control pipe */
+ uerr = usbd_transfer_setup(uaa->device,
+ &sc->sc_iface_index, sc->sc_ctrl_xfer,
+ uhso_ctrl_config, UHSO_CTRL_MAX, sc, &sc->sc_mtx);
+ if (uerr) {
+ device_printf(self, "Failed to setup control pipe: %s\n",
+ usbd_errstr(uerr));
+ goto out;
+ }
+
+ if (USB_GET_DRIVER_INFO(uaa) == UHSO_STATIC_IFACE)
+ probe_f = uhso_probe_iface_static;
+ else if (USB_GET_DRIVER_INFO(uaa) == UHSO_AUTO_IFACE)
+ probe_f = uhso_probe_iface_auto;
+ else
+ goto out;
+
+ error = uhso_probe_iface(sc, uaa->info.bIfaceNum, probe_f);
+ if (error != 0)
+ goto out;
+
+ sctx = device_get_sysctl_ctx(sc->sc_dev);
+ soid = device_get_sysctl_tree(sc->sc_dev);
+
+ SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "type",
+ CTLFLAG_RD, uhso_port[UHSO_IFACE_PORT(sc->sc_type)], 0,
+ "Port available at this interface");
+ SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "radio",
+ CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, sc, 0,
+ uhso_radio_sysctl, "I", "Enable radio");
+
+ /*
+ * The default interface description on most Option devices isn't
+ * very helpful. So we skip device_set_usb_desc and set the
+ * device description manually.
+ */
+ device_set_desc_copy(self, uhso_port_type[UHSO_IFACE_PORT_TYPE(sc->sc_type)]);
+ /* Announce device */
+ device_printf(self, "<%s port> at <%s %s> on %s\n",
+ uhso_port_type[UHSO_IFACE_PORT_TYPE(sc->sc_type)],
+ usb_get_manufacturer(uaa->device),
+ usb_get_product(uaa->device),
+ device_get_nameunit(device_get_parent(self)));
+
+ if (sc->sc_ttys > 0) {
+ SYSCTL_ADD_INT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "ports",
+ CTLFLAG_RD, &sc->sc_ttys, 0, "Number of attached serial ports");
+
+ tree = SYSCTL_ADD_NODE(sctx, SYSCTL_CHILDREN(soid), OID_AUTO,
+ "port", CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "Serial ports");
+ }
+
+ /*
+ * Loop through the number of found TTYs and create sysctl
+ * nodes for them.
+ */
+ for (i = 0; i < sc->sc_ttys; i++) {
+ ht = &sc->sc_tty[i];
+ ucom = &sc->sc_ucom[i];
+
+ if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX)
+ port = uhso_mux_port_map[ht->ht_muxport];
+ else
+ port = UHSO_IFACE_PORT_TYPE(sc->sc_type);
+
+ desc = uhso_port_type_sysctl[port];
+
+ tty_node = SYSCTL_ADD_NODE(sctx, SYSCTL_CHILDREN(tree), OID_AUTO,
+ desc, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "");
+
+ ht->ht_name[0] = 0;
+ if (sc->sc_ttys == 1)
+ snprintf(ht->ht_name, 32, "cuaU%d", ucom->sc_super->sc_unit);
+ else {
+ snprintf(ht->ht_name, 32, "cuaU%d.%d",
+ ucom->sc_super->sc_unit, ucom->sc_subunit);
+ }
+
+ desc = uhso_port_type[port];
+ SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(tty_node), OID_AUTO,
+ "tty", CTLFLAG_RD, ht->ht_name, 0, "");
+ SYSCTL_ADD_STRING(sctx, SYSCTL_CHILDREN(tty_node), OID_AUTO,
+ "desc", CTLFLAG_RD, desc, 0, "");
+
+ if (bootverbose)
+ device_printf(sc->sc_dev,
+ "\"%s\" port at %s\n", desc, ht->ht_name);
+ }
+
+ return (0);
+out:
+ uhso_detach(sc->sc_dev);
+ return (ENXIO);
+}
+
+static int
+uhso_detach(device_t self)
+{
+ struct uhso_softc *sc = device_get_softc(self);
+ int i;
+
+ usbd_transfer_unsetup(sc->sc_xfer, 3);
+ usbd_transfer_unsetup(sc->sc_ctrl_xfer, UHSO_CTRL_MAX);
+ if (sc->sc_ttys > 0) {
+ ucom_detach(&sc->sc_super_ucom, sc->sc_ucom);
+
+ for (i = 0; i < sc->sc_ttys; i++) {
+ if (sc->sc_tty[i].ht_muxport != -1) {
+ usbd_transfer_unsetup(sc->sc_tty[i].ht_xfer,
+ UHSO_CTRL_MAX);
+ }
+ }
+ }
+
+ if (sc->sc_ifp != NULL) {
+ callout_drain(&sc->sc_c);
+ free_unr(uhso_ifnet_unit, if_getdunit(sc->sc_ifp));
+ mtx_lock(&sc->sc_mtx);
+ uhso_if_stop(sc);
+ mtx_unlock(&sc->sc_mtx);
+ bpfdetach(sc->sc_ifp);
+ if_detach(sc->sc_ifp);
+ if_free(sc->sc_ifp);
+ usbd_transfer_unsetup(sc->sc_if_xfer, UHSO_IFNET_MAX);
+ }
+
+ device_claim_softc(self);
+
+ uhso_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(uhso);
+
+static void
+uhso_free_softc(struct uhso_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ free(sc->sc_tty, M_USBDEV);
+ free(sc->sc_ucom, M_USBDEV);
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+uhso_free(struct ucom_softc *ucom)
+{
+ uhso_free_softc(ucom->sc_parent);
+}
+
+static void
+uhso_test_autoinst(void *arg, struct usb_device *udev,
+ struct usb_attach_arg *uaa)
+{
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *id;
+
+ if (uaa->dev_state != UAA_DEV_READY || !uhso_autoswitch)
+ return;
+
+ iface = usbd_get_iface(udev, 0);
+ if (iface == NULL)
+ return;
+ id = iface->idesc;
+ if (id == NULL || id->bInterfaceClass != UICLASS_MASS)
+ return;
+ if (usbd_lookup_id_by_uaa(uhso_devs, sizeof(uhso_devs), uaa))
+ return; /* no device match */
+
+ if (usb_msc_eject(udev, 0, MSC_EJECT_REZERO) == 0) {
+ /* success, mark the udev as disappearing */
+ uaa->dev_state = UAA_DEV_EJECTING;
+ }
+}
+
+static int
+uhso_driver_loaded(struct module *mod, int what, void *arg)
+{
+ switch (what) {
+ case MOD_LOAD:
+ /* register our autoinstall handler */
+ uhso_etag = EVENTHANDLER_REGISTER(usb_dev_configured,
+ uhso_test_autoinst, NULL, EVENTHANDLER_PRI_ANY);
+ /* create our unit allocator for inet devs */
+ uhso_ifnet_unit = new_unrhdr(0, INT_MAX, NULL);
+ break;
+ case MOD_UNLOAD:
+ EVENTHANDLER_DEREGISTER(usb_dev_configured, uhso_etag);
+ delete_unrhdr(uhso_ifnet_unit);
+ break;
+ default:
+ return (EOPNOTSUPP);
+ }
+ return (0);
+}
+
+/*
+ * Probe the interface type by querying the device. The elements
+ * of an array indicates the capabilities of a particular interface.
+ * Returns a bit mask with the interface capabilities.
+ */
+static int
+uhso_probe_iface_auto(struct usb_device *udev, int index)
+{
+ struct usb_device_request req;
+ usb_error_t uerr;
+ uint16_t actlen = 0;
+ char port;
+ char buf[17] = {0};
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = 0x86;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 17);
+
+ uerr = usbd_do_request_flags(udev, NULL, &req, buf,
+ 0, &actlen, USB_MS_HZ);
+ if (uerr != 0) {
+ printf("%s: usbd_do_request_flags failed, %s\n",
+ __func__, usbd_errstr(uerr));
+ return (0);
+ }
+
+ UHSO_DPRINTF(1, "actlen=%d\n", actlen);
+ UHSO_HEXDUMP(buf, 17);
+
+ if (index < 0 || index > 16) {
+ UHSO_DPRINTF(0, "Index %d out of range\n", index);
+ return (0);
+ }
+
+ UHSO_DPRINTF(1, "index=%d, type=%x[%s]\n", index, buf[index],
+ uhso_port_type[(int)uhso_port_map[(int)buf[index]]]);
+
+ if (buf[index] >= uhso_port_map_max)
+ port = 0;
+ else
+ port = uhso_port_map[(int)buf[index]];
+
+ switch (port) {
+ case UHSO_PORT_TYPE_NETWORK:
+ return (UHSO_IFACE_SPEC(UHSO_IF_NET | UHSO_IF_MUX,
+ UHSO_PORT_SERIAL | UHSO_PORT_NETWORK, port));
+ case UHSO_PORT_TYPE_DIAG:
+ case UHSO_PORT_TYPE_DIAG2:
+ case UHSO_PORT_TYPE_GPS:
+ case UHSO_PORT_TYPE_GPSCTL:
+ case UHSO_PORT_TYPE_CTL:
+ case UHSO_PORT_TYPE_APP:
+ case UHSO_PORT_TYPE_APP2:
+ case UHSO_PORT_TYPE_MODEM:
+ return (UHSO_IFACE_SPEC(UHSO_IF_BULK,
+ UHSO_PORT_SERIAL, port));
+ case UHSO_PORT_TYPE_MSD:
+ return (0);
+ case UHSO_PORT_TYPE_UNKNOWN:
+ default:
+ return (0);
+ }
+
+ return (0);
+}
+
+/*
+ * Returns the capabilities of interfaces for devices that don't
+ * support the automatic query.
+ * Returns a bit mask with the interface capabilities.
+ */
+static int
+uhso_probe_iface_static(struct usb_device *udev, int index)
+{
+ struct usb_config_descriptor *cd;
+
+ cd = usbd_get_config_descriptor(udev);
+ if (cd->bNumInterface <= 3) {
+ /* Cards with 3 or less interfaces */
+ switch (index) {
+ case 0:
+ return UHSO_IFACE_SPEC(UHSO_IF_NET | UHSO_IF_MUX,
+ UHSO_PORT_SERIAL | UHSO_PORT_NETWORK,
+ UHSO_PORT_TYPE_NETWORK);
+ case 1:
+ return UHSO_IFACE_SPEC(UHSO_IF_BULK,
+ UHSO_PORT_SERIAL, UHSO_PORT_TYPE_DIAG);
+ case 2:
+ return UHSO_IFACE_SPEC(UHSO_IF_BULK,
+ UHSO_PORT_SERIAL, UHSO_PORT_TYPE_MODEM);
+ }
+ } else {
+ /* Cards with 4 interfaces */
+ switch (index) {
+ case 0:
+ return UHSO_IFACE_SPEC(UHSO_IF_NET | UHSO_IF_MUX,
+ UHSO_PORT_SERIAL | UHSO_PORT_NETWORK,
+ UHSO_PORT_TYPE_NETWORK);
+ case 1:
+ return UHSO_IFACE_SPEC(UHSO_IF_BULK,
+ UHSO_PORT_SERIAL, UHSO_PORT_TYPE_DIAG2);
+ case 2:
+ return UHSO_IFACE_SPEC(UHSO_IF_BULK,
+ UHSO_PORT_SERIAL, UHSO_PORT_TYPE_MODEM);
+ case 3:
+ return UHSO_IFACE_SPEC(UHSO_IF_BULK,
+ UHSO_PORT_SERIAL, UHSO_PORT_TYPE_DIAG);
+ }
+ }
+ return (0);
+}
+
+/*
+ * Probes an interface for its particular capabilities and attaches if
+ * it's a supported interface.
+ */
+static int
+uhso_probe_iface(struct uhso_softc *sc, int index,
+ int (*probe)(struct usb_device *, int))
+{
+ struct usb_interface *iface;
+ int type, error;
+
+ UHSO_DPRINTF(1, "Probing for interface %d, probe_func=%p\n", index, probe);
+
+ type = probe(sc->sc_udev, index);
+ UHSO_DPRINTF(1, "Probe result %x\n", type);
+ if (type <= 0)
+ return (ENXIO);
+
+ sc->sc_type = type;
+ iface = usbd_get_iface(sc->sc_udev, index);
+
+ if (UHSO_IFACE_PORT_TYPE(type) == UHSO_PORT_TYPE_NETWORK) {
+ error = uhso_attach_ifnet(sc, iface, type);
+ if (error) {
+ UHSO_DPRINTF(1, "uhso_attach_ifnet failed");
+ return (ENXIO);
+ }
+
+ /*
+ * If there is an additional interrupt endpoint on this
+ * interface then we most likely have a multiplexed serial port
+ * available.
+ */
+ if (iface->idesc->bNumEndpoints < 3) {
+ sc->sc_type = UHSO_IFACE_SPEC(
+ UHSO_IFACE_USB_TYPE(type) & ~UHSO_IF_MUX,
+ UHSO_IFACE_PORT(type) & ~UHSO_PORT_SERIAL,
+ UHSO_IFACE_PORT_TYPE(type));
+ return (0);
+ }
+
+ UHSO_DPRINTF(1, "Trying to attach mux. serial\n");
+ error = uhso_attach_muxserial(sc, iface, type);
+ if (error == 0 && sc->sc_ttys > 0) {
+ error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom,
+ sc->sc_ttys, sc, &uhso_ucom_callback, &sc->sc_mtx);
+ if (error) {
+ device_printf(sc->sc_dev, "ucom_attach failed\n");
+ return (ENXIO);
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, sc->sc_dev);
+
+ mtx_lock(&sc->sc_mtx);
+ usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]);
+ mtx_unlock(&sc->sc_mtx);
+ }
+ } else if ((UHSO_IFACE_USB_TYPE(type) & UHSO_IF_BULK) &&
+ UHSO_IFACE_PORT(type) & UHSO_PORT_SERIAL) {
+ error = uhso_attach_bulkserial(sc, iface, type);
+ if (error)
+ return (ENXIO);
+
+ error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom,
+ sc->sc_ttys, sc, &uhso_ucom_callback, &sc->sc_mtx);
+ if (error) {
+ device_printf(sc->sc_dev, "ucom_attach failed\n");
+ return (ENXIO);
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, sc->sc_dev);
+ }
+ else {
+ UHSO_DPRINTF(0, "Unknown type %x\n", type);
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+static int
+uhso_radio_ctrl(struct uhso_softc *sc, int onoff)
+{
+ struct usb_device_request req;
+ usb_error_t uerr;
+
+ req.bmRequestType = UT_VENDOR;
+ req.bRequest = onoff ? 0x82 : 0x81;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+
+ uerr = usbd_do_request(sc->sc_udev, NULL, &req, NULL);
+ if (uerr != 0) {
+ device_printf(sc->sc_dev, "usbd_do_request_flags failed: %s\n",
+ usbd_errstr(uerr));
+ return (-1);
+ }
+ return (onoff);
+}
+
+static int
+uhso_radio_sysctl(SYSCTL_HANDLER_ARGS)
+{
+ struct uhso_softc *sc = arg1;
+ int error, radio;
+
+ radio = sc->sc_radio;
+ error = sysctl_handle_int(oidp, &radio, 0, req);
+ if (error)
+ return (error);
+ if (radio != sc->sc_radio) {
+ radio = radio != 0 ? 1 : 0;
+ error = uhso_radio_ctrl(sc, radio);
+ if (error != -1)
+ sc->sc_radio = radio;
+
+ }
+ return (0);
+}
+
+/*
+ * Expands allocated memory to fit an additional TTY.
+ * Two arrays are kept with matching indexes, one for ucom and one
+ * for our private data.
+ */
+static int
+uhso_alloc_tty(struct uhso_softc *sc)
+{
+
+ sc->sc_ttys++;
+ sc->sc_tty = reallocf(sc->sc_tty, sizeof(struct uhso_tty) * sc->sc_ttys,
+ M_USBDEV, M_WAITOK | M_ZERO);
+ if (sc->sc_tty == NULL)
+ return (-1);
+
+ sc->sc_ucom = reallocf(sc->sc_ucom,
+ sizeof(struct ucom_softc) * sc->sc_ttys, M_USBDEV, M_WAITOK | M_ZERO);
+ if (sc->sc_ucom == NULL)
+ return (-1);
+
+ sc->sc_tty[sc->sc_ttys - 1].ht_sc = sc;
+
+ UHSO_DPRINTF(1, "Allocated TTY %d\n", sc->sc_ttys - 1);
+ return (sc->sc_ttys - 1);
+}
+
+/*
+ * Attach a multiplexed serial port
+ * Data is read/written with requests on the default control pipe. An interrupt
+ * endpoint returns when there is new data to be read.
+ */
+static int
+uhso_attach_muxserial(struct uhso_softc *sc, struct usb_interface *iface,
+ int type)
+{
+ struct usb_descriptor *desc;
+ int i, port, tty;
+ usb_error_t uerr;
+
+ /*
+ * The class specific interface (type 0x24) descriptor subtype field
+ * contains a bitmask that specifies which (and how many) ports that
+ * are available through this multiplexed serial port.
+ */
+ desc = usbd_find_descriptor(sc->sc_udev, NULL,
+ iface->idesc->bInterfaceNumber, UDESC_CS_INTERFACE, 0xff, 0, 0);
+ if (desc == NULL) {
+ UHSO_DPRINTF(0, "Failed to find UDESC_CS_INTERFACE\n");
+ return (ENXIO);
+ }
+
+ UHSO_DPRINTF(1, "Mux port mask %x\n", desc->bDescriptorSubtype);
+ if (desc->bDescriptorSubtype == 0)
+ return (ENXIO);
+
+ /*
+ * The bitmask is one octet, loop through the number of
+ * bits that are set and create a TTY for each.
+ */
+ for (i = 0; i < 8; i++) {
+ port = (1 << i);
+ if ((port & desc->bDescriptorSubtype) == port) {
+ UHSO_DPRINTF(2, "Found mux port %x (%d)\n", port, i);
+ tty = uhso_alloc_tty(sc);
+ if (tty < 0)
+ return (ENOMEM);
+ sc->sc_tty[tty].ht_muxport = i;
+ uerr = usbd_transfer_setup(sc->sc_udev,
+ &sc->sc_iface_index, sc->sc_tty[tty].ht_xfer,
+ uhso_ctrl_config, UHSO_CTRL_MAX, sc, &sc->sc_mtx);
+ if (uerr) {
+ device_printf(sc->sc_dev,
+ "Failed to setup control pipe: %s\n",
+ usbd_errstr(uerr));
+ return (ENXIO);
+ }
+ }
+ }
+
+ /* Setup the intr. endpoint */
+ uerr = usbd_transfer_setup(sc->sc_udev,
+ &iface->idesc->bInterfaceNumber, sc->sc_xfer,
+ uhso_mux_config, 1, sc, &sc->sc_mtx);
+ if (uerr)
+ return (ENXIO);
+
+ return (0);
+}
+
+/*
+ * Interrupt callback for the multiplexed serial port. Indicates
+ * which serial port has data waiting.
+ */
+static void
+uhso_mux_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usb_page_cache *pc;
+ struct usb_page_search res;
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ unsigned i, mux;
+
+ UHSO_DPRINTF(3, "status %d\n", USB_GET_STATE(xfer));
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ /*
+ * The multiplexed port number can be found at the first byte.
+ * It contains a bit mask, we transform this in to an integer.
+ */
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_get_page(pc, 0, &res);
+
+ i = *((unsigned char *)res.buffer);
+ mux = 0;
+ while (i >>= 1) {
+ mux++;
+ }
+
+ UHSO_DPRINTF(3, "mux port %d (%d)\n", mux, i);
+ if (mux > UHSO_MPORT_TYPE_NOMAX)
+ break;
+
+ /* Issue a read for this serial port */
+ usbd_xfer_set_priv(
+ sc->sc_tty[mux].ht_xfer[UHSO_CTRL_READ],
+ &sc->sc_tty[mux]);
+ usbd_transfer_start(sc->sc_tty[mux].ht_xfer[UHSO_CTRL_READ]);
+
+ break;
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+}
+
+static void
+uhso_mux_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ struct usb_device_request req;
+ struct uhso_tty *ht;
+ int actlen, len;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ UHSO_DPRINTF(3, "status %d\n", USB_GET_STATE(xfer));
+
+ ht = usbd_xfer_get_priv(xfer);
+ UHSO_DPRINTF(3, "ht=%p open=%d\n", ht, ht->ht_open);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ /* Got data, send to ucom */
+ pc = usbd_xfer_get_frame(xfer, 1);
+ len = usbd_xfer_frame_len(xfer, 1);
+
+ UHSO_DPRINTF(3, "got %d bytes on mux port %d\n", len,
+ ht->ht_muxport);
+ if (len <= 0) {
+ usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]);
+ break;
+ }
+
+ /* Deliver data if the TTY is open, discard otherwise */
+ if (ht->ht_open)
+ ucom_put_data(&sc->sc_ucom[ht->ht_muxport], pc, 0, len);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ memset(&req, 0, sizeof(struct usb_device_request));
+ req.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, ht->ht_muxport);
+ USETW(req.wLength, 1024);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, 1024);
+ usbd_xfer_set_frames(xfer, 2);
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+}
+
+static void
+uhso_mux_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ struct uhso_tty *ht;
+ struct usb_page_cache *pc;
+ struct usb_device_request req;
+ int actlen;
+ struct usb_page_search res;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ ht = usbd_xfer_get_priv(xfer);
+ UHSO_DPRINTF(3, "status=%d, using mux port %d\n",
+ USB_GET_STATE(xfer), ht->ht_muxport);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ UHSO_DPRINTF(3, "wrote %zd data bytes to muxport %d\n",
+ actlen - sizeof(struct usb_device_request) ,
+ ht->ht_muxport);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 1);
+ if (ucom_get_data(&sc->sc_ucom[ht->ht_muxport], pc,
+ 0, 32, &actlen)) {
+ usbd_get_page(pc, 0, &res);
+
+ memset(&req, 0, sizeof(struct usb_device_request));
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, ht->ht_muxport);
+ USETW(req.wLength, actlen);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, actlen);
+ usbd_xfer_set_frames(xfer, 2);
+
+ UHSO_DPRINTF(3, "Prepared %d bytes for transmit "
+ "on muxport %d\n", actlen, ht->ht_muxport);
+
+ usbd_transfer_submit(xfer);
+ }
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+}
+
+static int
+uhso_attach_bulkserial(struct uhso_softc *sc, struct usb_interface *iface,
+ int type)
+{
+ usb_error_t uerr;
+ int tty;
+
+ /* Try attaching RD/WR/INTR first */
+ uerr = usbd_transfer_setup(sc->sc_udev,
+ &iface->idesc->bInterfaceNumber, sc->sc_xfer,
+ uhso_bs_config, UHSO_BULK_ENDPT_MAX, sc, &sc->sc_mtx);
+ if (uerr) {
+ /* Try only RD/WR */
+ uerr = usbd_transfer_setup(sc->sc_udev,
+ &iface->idesc->bInterfaceNumber, sc->sc_xfer,
+ uhso_bs_config, UHSO_BULK_ENDPT_MAX - 1, sc, &sc->sc_mtx);
+ }
+ if (uerr) {
+ UHSO_DPRINTF(0, "usbd_transfer_setup failed");
+ return (-1);
+ }
+
+ tty = uhso_alloc_tty(sc);
+ if (tty < 0) {
+ usbd_transfer_unsetup(sc->sc_xfer, UHSO_BULK_ENDPT_MAX);
+ return (ENOMEM);
+ }
+
+ sc->sc_tty[tty].ht_muxport = -1;
+ return (0);
+}
+
+static void
+uhso_bs_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom[0], pc, 0, actlen);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+}
+
+static void
+uhso_bs_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(&sc->sc_ucom[0], pc, 0, 8192, &actlen)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+}
+
+static void
+uhso_bs_cfg(struct uhso_softc *sc)
+{
+ struct usb_device_request req;
+ usb_error_t uerr;
+
+ if (!(UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK))
+ return;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
+ USETW(req.wValue, sc->sc_line);
+ USETW(req.wIndex, sc->sc_iface_no);
+ USETW(req.wLength, 0);
+
+ uerr = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom[0], &req, NULL, 0, 1000);
+ if (uerr != 0) {
+ device_printf(sc->sc_dev, "failed to set ctrl line state to "
+ "0x%02x: %s\n", sc->sc_line, usbd_errstr(uerr));
+ }
+}
+
+static void
+uhso_bs_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+ struct usb_cdc_notification cdc;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+ UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (actlen < UCDC_NOTIFICATION_LENGTH) {
+ UHSO_DPRINTF(0, "UCDC notification too short: %d\n", actlen);
+ goto tr_setup;
+ }
+ else if (actlen > (int)sizeof(struct usb_cdc_notification)) {
+ UHSO_DPRINTF(0, "UCDC notification too large: %d\n", actlen);
+ actlen = sizeof(struct usb_cdc_notification);
+ }
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &cdc, actlen);
+
+ if (UGETW(cdc.wIndex) != sc->sc_iface_no) {
+ UHSO_DPRINTF(0, "Interface mismatch, got %d expected %d\n",
+ UGETW(cdc.wIndex), sc->sc_iface_no);
+ goto tr_setup;
+ }
+
+ if (cdc.bmRequestType == UCDC_NOTIFICATION &&
+ cdc.bNotification == UCDC_N_SERIAL_STATE) {
+ UHSO_DPRINTF(2, "notify = 0x%02x\n", cdc.data[0]);
+
+ sc->sc_msr = 0;
+ sc->sc_lsr = 0;
+ if (cdc.data[0] & UCDC_N_SERIAL_RI)
+ sc->sc_msr |= SER_RI;
+ if (cdc.data[0] & UCDC_N_SERIAL_DSR)
+ sc->sc_msr |= SER_DSR;
+ if (cdc.data[0] & UCDC_N_SERIAL_DCD)
+ sc->sc_msr |= SER_DCD;
+
+ ucom_status_change(&sc->sc_ucom[0]);
+ }
+ case USB_ST_SETUP:
+tr_setup:
+ default:
+ if (error == USB_ERR_CANCELLED)
+ break;
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+}
+
+static void
+uhso_ucom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ *lsr = sc->sc_lsr;
+ *msr = sc->sc_msr;
+}
+
+static void
+uhso_ucom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ if (!(UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK))
+ return;
+
+ if (onoff)
+ sc->sc_line |= UCDC_LINE_DTR;
+ else
+ sc->sc_line &= ~UCDC_LINE_DTR;
+
+ uhso_bs_cfg(sc);
+}
+
+static void
+uhso_ucom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ if (!(UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK))
+ return;
+
+ if (onoff)
+ sc->sc_line |= UCDC_LINE_RTS;
+ else
+ sc->sc_line &= ~UCDC_LINE_RTS;
+
+ uhso_bs_cfg(sc);
+}
+
+static void
+uhso_ucom_start_read(struct ucom_softc *ucom)
+{
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ UHSO_DPRINTF(3, "unit=%d, subunit=%d\n",
+ ucom->sc_super->sc_unit, ucom->sc_subunit);
+
+ if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) {
+ sc->sc_tty[ucom->sc_subunit].ht_open = 1;
+ usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]);
+ }
+ else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) {
+ sc->sc_tty[0].ht_open = 1;
+ usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_READ]);
+ if (sc->sc_xfer[UHSO_BULK_ENDPT_INTR] != NULL)
+ usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_INTR]);
+ }
+}
+
+static void
+uhso_ucom_stop_read(struct ucom_softc *ucom)
+{
+
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) {
+ sc->sc_tty[ucom->sc_subunit].ht_open = 0;
+ usbd_transfer_stop(
+ sc->sc_tty[ucom->sc_subunit].ht_xfer[UHSO_CTRL_READ]);
+ }
+ else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) {
+ sc->sc_tty[0].ht_open = 0;
+ usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_READ]);
+ if (sc->sc_xfer[UHSO_BULK_ENDPT_INTR] != NULL)
+ usbd_transfer_stop(sc->sc_xfer[UHSO_BULK_ENDPT_INTR]);
+ }
+}
+
+static void
+uhso_ucom_start_write(struct ucom_softc *ucom)
+{
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) {
+ UHSO_DPRINTF(3, "local unit %d\n", ucom->sc_subunit);
+
+ usbd_transfer_start(sc->sc_xfer[UHSO_MUX_ENDPT_INTR]);
+
+ usbd_xfer_set_priv(
+ sc->sc_tty[ucom->sc_subunit].ht_xfer[UHSO_CTRL_WRITE],
+ &sc->sc_tty[ucom->sc_subunit]);
+ usbd_transfer_start(
+ sc->sc_tty[ucom->sc_subunit].ht_xfer[UHSO_CTRL_WRITE]);
+ }
+ else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) {
+ usbd_transfer_start(sc->sc_xfer[UHSO_BULK_ENDPT_WRITE]);
+ }
+}
+
+static void
+uhso_ucom_stop_write(struct ucom_softc *ucom)
+{
+ struct uhso_softc *sc = ucom->sc_parent;
+
+ if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_MUX) {
+ usbd_transfer_stop(
+ sc->sc_tty[ucom->sc_subunit].ht_xfer[UHSO_CTRL_WRITE]);
+ }
+ else if (UHSO_IFACE_USB_TYPE(sc->sc_type) & UHSO_IF_BULK) {
+ usbd_transfer_stop(sc->sc_xfer[UHSO_BULK_ENDPT_WRITE]);
+ }
+}
+
+static int
+uhso_attach_ifnet(struct uhso_softc *sc, struct usb_interface *iface, int type)
+{
+ if_t ifp;
+ usb_error_t uerr;
+ struct sysctl_ctx_list *sctx;
+ struct sysctl_oid *soid;
+ unsigned devunit;
+
+ uerr = usbd_transfer_setup(sc->sc_udev,
+ &iface->idesc->bInterfaceNumber, sc->sc_if_xfer,
+ uhso_ifnet_config, UHSO_IFNET_MAX, sc, &sc->sc_mtx);
+ if (uerr) {
+ UHSO_DPRINTF(0, "usbd_transfer_setup failed: %s\n",
+ usbd_errstr(uerr));
+ return (-1);
+ }
+
+ sc->sc_ifp = ifp = if_alloc(IFT_OTHER);
+
+ callout_init_mtx(&sc->sc_c, &sc->sc_mtx, 0);
+ mtx_lock(&sc->sc_mtx);
+ callout_reset(&sc->sc_c, 1, uhso_if_rxflush, sc);
+ mtx_unlock(&sc->sc_mtx);
+
+ /*
+ * We create our own unit numbers for ifnet devices because the
+ * USB interface unit numbers can be at arbitrary positions yielding
+ * odd looking device names.
+ */
+ devunit = alloc_unr(uhso_ifnet_unit);
+
+ if_initname(ifp, device_get_name(sc->sc_dev), devunit);
+ if_setmtu(ifp, UHSO_MAX_MTU);
+ if_setioctlfn(ifp, uhso_if_ioctl);
+ if_setinitfn(ifp, uhso_if_init);
+ if_setstartfn(ifp, uhso_if_start);
+ if_setoutputfn(ifp, uhso_if_output);
+ if_setflags(ifp, IFF_BROADCAST | IFF_MULTICAST | IFF_NOARP);
+ if_setsoftc(ifp, sc);
+ if_setsendqlen(ifp, ifqmaxlen);
+ if_setsendqready(ifp);
+
+ if_attach(ifp);
+ bpfattach(ifp, DLT_RAW, 0);
+
+ sctx = device_get_sysctl_ctx(sc->sc_dev);
+ soid = device_get_sysctl_tree(sc->sc_dev);
+ /* Unlocked read... */
+ SYSCTL_ADD_CONST_STRING(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "netif",
+ CTLFLAG_RD, if_name(ifp), "Attached network interface");
+
+ return (0);
+}
+
+static void
+uhso_ifnet_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ struct mbuf *m;
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ UHSO_DPRINTF(3, "status=%d, actlen=%d\n", USB_GET_STATE(xfer), actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (actlen > 0 && (if_getdrvflags(sc->sc_ifp) & IFF_DRV_RUNNING)) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (mbufq_full(&sc->sc_rxq))
+ break;
+ m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ usbd_copy_out(pc, 0, mtod(m, uint8_t *), actlen);
+ m->m_pkthdr.len = m->m_len = actlen;
+ /* Enqueue frame for further processing */
+ mbufq_enqueue(&sc->sc_rxq, m);
+ if (!callout_pending(&sc->sc_c) ||
+ !callout_active(&sc->sc_c)) {
+ callout_schedule(&sc->sc_c, 1);
+ }
+ }
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+}
+
+/*
+ * Deferred RX processing, called with mutex locked.
+ *
+ * Each frame we receive might contain several small ip-packets as well
+ * as partial ip-packets. We need to separate/assemble them into individual
+ * packets before sending them to the ip-layer.
+ */
+static void
+uhso_if_rxflush(void *arg)
+{
+ struct epoch_tracker et;
+ struct uhso_softc *sc = arg;
+ if_t ifp = sc->sc_ifp;
+ uint8_t *cp;
+ struct mbuf *m, *m0, *mwait;
+ struct ip *ip;
+#ifdef INET6
+ struct ip6_hdr *ip6;
+#endif
+ uint16_t iplen;
+ int isr;
+
+ m = NULL;
+ mwait = sc->sc_mwait;
+ NET_EPOCH_ENTER(et);
+ for (;;) {
+ if (m == NULL) {
+ if ((m = mbufq_dequeue(&sc->sc_rxq)) == NULL)
+ break;
+ UHSO_DPRINTF(3, "dequeue m=%p, len=%d\n", m, m->m_len);
+ }
+ mtx_unlock(&sc->sc_mtx);
+
+ /* Do we have a partial packet waiting? */
+ if (mwait != NULL) {
+ m0 = mwait;
+ mwait = NULL;
+
+ UHSO_DPRINTF(3, "partial m0=%p(%d), concat w/ m=%p(%d)\n",
+ m0, m0->m_len, m, m->m_len);
+
+ m_catpkt(m0, m);
+ m = m_pullup(m0, sizeof(struct ip));
+ if (m == NULL) {
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ UHSO_DPRINTF(0, "m_pullup failed\n");
+ mtx_lock(&sc->sc_mtx);
+ continue;
+ }
+ UHSO_DPRINTF(3, "Constructed mbuf=%p, len=%d\n",
+ m, m->m_pkthdr.len);
+ }
+
+ cp = mtod(m, uint8_t *);
+ ip = (struct ip *)cp;
+#ifdef INET6
+ ip6 = (struct ip6_hdr *)cp;
+#endif
+
+ /* Check for IPv4 */
+ if (ip->ip_v == IPVERSION) {
+ iplen = htons(ip->ip_len);
+ isr = NETISR_IP;
+ }
+#ifdef INET6
+ /* Check for IPv6 */
+ else if ((ip6->ip6_vfc & IPV6_VERSION_MASK) == IPV6_VERSION) {
+ iplen = htons(ip6->ip6_plen);
+ isr = NETISR_IPV6;
+ }
+#endif
+ else {
+ UHSO_DPRINTF(0, "got unexpected ip version %d, "
+ "m=%p, len=%d\n", (*cp & 0xf0) >> 4, m, m->m_len);
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ UHSO_HEXDUMP(cp, 4);
+ m_freem(m);
+ m = NULL;
+ mtx_lock(&sc->sc_mtx);
+ continue;
+ }
+
+ if (iplen == 0) {
+ UHSO_DPRINTF(0, "Zero IP length\n");
+ if_inc_counter(ifp, IFCOUNTER_IERRORS, 1);
+ m_freem(m);
+ m = NULL;
+ mtx_lock(&sc->sc_mtx);
+ continue;
+ }
+
+ UHSO_DPRINTF(3, "m=%p, len=%d, cp=%p, iplen=%d\n",
+ m, m->m_pkthdr.len, cp, iplen);
+
+ m0 = NULL;
+
+ /* More IP packets in this mbuf */
+ if (iplen < m->m_pkthdr.len) {
+ m0 = m;
+
+ /*
+ * Allocate a new mbuf for this IP packet and
+ * copy the IP-packet into it.
+ */
+ m = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR);
+ memcpy(mtod(m, uint8_t *), mtod(m0, uint8_t *), iplen);
+ m->m_pkthdr.len = m->m_len = iplen;
+
+ /* Adjust the size of the original mbuf */
+ m_adj(m0, iplen);
+ m0 = m_defrag(m0, M_WAITOK);
+
+ UHSO_DPRINTF(3, "New mbuf=%p, len=%d/%d, m0=%p, "
+ "m0_len=%d/%d\n", m, m->m_pkthdr.len, m->m_len,
+ m0, m0->m_pkthdr.len, m0->m_len);
+ }
+ else if (iplen > m->m_pkthdr.len) {
+ UHSO_DPRINTF(3, "Deferred mbuf=%p, len=%d\n",
+ m, m->m_pkthdr.len);
+ mwait = m;
+ m = NULL;
+ mtx_lock(&sc->sc_mtx);
+ continue;
+ }
+
+ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
+ m->m_pkthdr.rcvif = ifp;
+
+ /* Dispatch to IP layer */
+ BPF_MTAP(sc->sc_ifp, m);
+ M_SETFIB(m, if_getfib(ifp));
+ netisr_dispatch(isr, m);
+ m = m0 != NULL ? m0 : NULL;
+ mtx_lock(&sc->sc_mtx);
+ }
+ NET_EPOCH_EXIT(et);
+ sc->sc_mwait = mwait;
+}
+
+static void
+uhso_ifnet_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhso_softc *sc = usbd_xfer_softc(xfer);
+ if_t ifp = sc->sc_ifp;
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ UHSO_DPRINTF(3, "status %d, actlen=%d\n", USB_GET_STATE(xfer), actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+ if_setdrvflagbits(ifp, 0, IFF_DRV_OACTIVE);
+ case USB_ST_SETUP:
+tr_setup:
+ m = if_dequeue(ifp);
+ if (m == NULL)
+ break;
+
+ if_setdrvflagbits(ifp, IFF_DRV_OACTIVE, 0);
+
+ if (m->m_pkthdr.len > MCLBYTES)
+ m->m_pkthdr.len = MCLBYTES;
+
+ usbd_xfer_set_frame_len(xfer, 0, m->m_pkthdr.len);
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_m_copy_in(pc, 0, m, 0, m->m_pkthdr.len);
+ usbd_transfer_submit(xfer);
+
+ BPF_MTAP(ifp, m);
+ m_freem(m);
+ break;
+ default:
+ UHSO_DPRINTF(0, "error: %s\n", usbd_errstr(error));
+ if (error == USB_ERR_CANCELLED)
+ break;
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+}
+
+static int
+uhso_if_ioctl(if_t ifp, u_long cmd, caddr_t data)
+{
+ struct uhso_softc *sc;
+
+ sc = if_getsoftc(ifp);
+
+ switch (cmd) {
+ case SIOCSIFFLAGS:
+ if (if_getflags(ifp) & IFF_UP) {
+ if (!(if_getdrvflags(ifp) & IFF_DRV_RUNNING)) {
+ uhso_if_init(sc);
+ }
+ }
+ else {
+ if (if_getdrvflags(ifp) & IFF_DRV_RUNNING) {
+ mtx_lock(&sc->sc_mtx);
+ uhso_if_stop(sc);
+ mtx_unlock(&sc->sc_mtx);
+ }
+ }
+ break;
+ case SIOCSIFADDR:
+ case SIOCADDMULTI:
+ case SIOCDELMULTI:
+ break;
+ default:
+ return (EINVAL);
+ }
+ return (0);
+}
+
+static void
+uhso_if_init(void *priv)
+{
+ struct uhso_softc *sc = priv;
+ if_t ifp = sc->sc_ifp;
+
+ mtx_lock(&sc->sc_mtx);
+ uhso_if_stop(sc);
+ ifp = sc->sc_ifp;
+ if_setflagbits(ifp, IFF_UP, 0);
+ if_setdrvflagbits(ifp, IFF_DRV_RUNNING, 0);
+ mtx_unlock(&sc->sc_mtx);
+
+ UHSO_DPRINTF(2, "ifnet initialized\n");
+}
+
+static int
+uhso_if_output(if_t ifp, struct mbuf *m0, const struct sockaddr *dst,
+ struct route *ro)
+{
+ int error;
+
+ /* Only IPv4/6 support */
+ if (dst->sa_family != AF_INET
+#ifdef INET6
+ && dst->sa_family != AF_INET6
+#endif
+ ) {
+ return (EAFNOSUPPORT);
+ }
+
+ error = if_transmit(ifp, m0);
+ if (error) {
+ if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
+ return (ENOBUFS);
+ }
+ if_inc_counter(ifp, IFCOUNTER_OPACKETS, 1);
+ return (0);
+}
+
+static void
+uhso_if_start(if_t ifp)
+{
+ struct uhso_softc *sc = if_getsoftc(ifp);
+
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0) {
+ UHSO_DPRINTF(1, "Not running\n");
+ return;
+ }
+
+ mtx_lock(&sc->sc_mtx);
+ usbd_transfer_start(sc->sc_if_xfer[UHSO_IFNET_READ]);
+ usbd_transfer_start(sc->sc_if_xfer[UHSO_IFNET_WRITE]);
+ mtx_unlock(&sc->sc_mtx);
+ UHSO_DPRINTF(3, "interface started\n");
+}
+
+static void
+uhso_if_stop(struct uhso_softc *sc)
+{
+
+ usbd_transfer_stop(sc->sc_if_xfer[UHSO_IFNET_READ]);
+ usbd_transfer_stop(sc->sc_if_xfer[UHSO_IFNET_WRITE]);
+ if_setdrvflagbits(sc->sc_ifp, 0, (IFF_DRV_RUNNING | IFF_DRV_OACTIVE));
+}
diff --git a/sys/dev/usb/net/usb_ethernet.c b/sys/dev/usb/net/usb_ethernet.c
new file mode 100644
index 000000000000..692ea64128b9
--- /dev/null
+++ b/sys/dev/usb/net/usb_ethernet.c
@@ -0,0 +1,666 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2009 Andrew Thompson (thompsa@FreeBSD.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.
+ *
+ * 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/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/condvar.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/ethernet.h>
+#include <net/if_types.h>
+#include <net/if_media.h>
+#include <net/if_vlan_var.h>
+
+#include <dev/mii/mii.h>
+#include <dev/mii/miivar.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_process.h>
+#include <dev/usb/net/usb_ethernet.h>
+
+static SYSCTL_NODE(_net, OID_AUTO, ue, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
+ "USB Ethernet parameters");
+
+#define UE_LOCK(_ue) mtx_lock((_ue)->ue_mtx)
+#define UE_UNLOCK(_ue) mtx_unlock((_ue)->ue_mtx)
+#define UE_LOCK_ASSERT(_ue, t) mtx_assert((_ue)->ue_mtx, t)
+
+MODULE_DEPEND(uether, usb, 1, 1, 1);
+MODULE_DEPEND(uether, miibus, 1, 1, 1);
+
+static struct unrhdr *ueunit;
+
+static usb_proc_callback_t ue_attach_post_task;
+static usb_proc_callback_t ue_promisc_task;
+static usb_proc_callback_t ue_setmulti_task;
+static usb_proc_callback_t ue_ifmedia_task;
+static usb_proc_callback_t ue_tick_task;
+static usb_proc_callback_t ue_start_task;
+static usb_proc_callback_t ue_stop_task;
+
+static void ue_init(void *);
+static void ue_start(if_t);
+static int ue_ifmedia_upd(if_t);
+static void ue_watchdog(void *);
+
+/*
+ * Return values:
+ * 0: success
+ * Else: device has been detached
+ */
+uint8_t
+uether_pause(struct usb_ether *ue, unsigned _ticks)
+{
+ if (usb_proc_is_gone(&ue->ue_tq)) {
+ /* nothing to do */
+ return (1);
+ }
+ usb_pause_mtx(ue->ue_mtx, _ticks);
+ return (0);
+}
+
+static void
+ue_queue_command(struct usb_ether *ue,
+ usb_proc_callback_t *fn,
+ struct usb_proc_msg *t0, struct usb_proc_msg *t1)
+{
+ struct usb_ether_cfg_task *task;
+
+ UE_LOCK_ASSERT(ue, MA_OWNED);
+
+ if (usb_proc_is_gone(&ue->ue_tq)) {
+ return; /* nothing to do */
+ }
+ /*
+ * NOTE: The task cannot get executed before we drop the
+ * "sc_mtx" mutex. It is safe to update fields in the message
+ * structure after that the message got queued.
+ */
+ task = (struct usb_ether_cfg_task *)
+ usb_proc_msignal(&ue->ue_tq, t0, t1);
+
+ /* Setup callback and self pointers */
+ task->hdr.pm_callback = fn;
+ task->ue = ue;
+
+ /*
+ * Start and stop must be synchronous!
+ */
+ if ((fn == ue_start_task) || (fn == ue_stop_task))
+ usb_proc_mwait(&ue->ue_tq, t0, t1);
+}
+
+if_t
+uether_getifp(struct usb_ether *ue)
+{
+ return (ue->ue_ifp);
+}
+
+struct mii_data *
+uether_getmii(struct usb_ether *ue)
+{
+ return (device_get_softc(ue->ue_miibus));
+}
+
+void *
+uether_getsc(struct usb_ether *ue)
+{
+ return (ue->ue_sc);
+}
+
+static int
+ue_sysctl_parent(SYSCTL_HANDLER_ARGS)
+{
+ struct usb_ether *ue = arg1;
+ const char *name;
+
+ name = device_get_nameunit(ue->ue_dev);
+ return SYSCTL_OUT_STR(req, name);
+}
+
+int
+uether_ifattach(struct usb_ether *ue)
+{
+ int error;
+
+ /* check some critical parameters */
+ if ((ue->ue_dev == NULL) ||
+ (ue->ue_udev == NULL) ||
+ (ue->ue_mtx == NULL) ||
+ (ue->ue_methods == NULL))
+ return (EINVAL);
+
+ error = usb_proc_create(&ue->ue_tq, ue->ue_mtx,
+ device_get_nameunit(ue->ue_dev), USB_PRI_MED);
+ if (error) {
+ device_printf(ue->ue_dev, "could not setup taskqueue\n");
+ goto error;
+ }
+
+ /* fork rest of the attach code */
+ UE_LOCK(ue);
+ ue_queue_command(ue, ue_attach_post_task,
+ &ue->ue_sync_task[0].hdr,
+ &ue->ue_sync_task[1].hdr);
+ UE_UNLOCK(ue);
+
+error:
+ return (error);
+}
+
+void
+uether_ifattach_wait(struct usb_ether *ue)
+{
+
+ UE_LOCK(ue);
+ usb_proc_mwait(&ue->ue_tq,
+ &ue->ue_sync_task[0].hdr,
+ &ue->ue_sync_task[1].hdr);
+ UE_UNLOCK(ue);
+}
+
+static void
+ue_attach_post_task(struct usb_proc_msg *_task)
+{
+ struct usb_ether_cfg_task *task =
+ (struct usb_ether_cfg_task *)_task;
+ struct usb_ether *ue = task->ue;
+ if_t ifp;
+ int error;
+ char num[14]; /* sufficient for 32 bits */
+
+ /* first call driver's post attach routine */
+ ue->ue_methods->ue_attach_post(ue);
+
+ UE_UNLOCK(ue);
+
+ ue->ue_unit = alloc_unr(ueunit);
+ usb_callout_init_mtx(&ue->ue_watchdog, ue->ue_mtx, 0);
+ sysctl_ctx_init(&ue->ue_sysctl_ctx);
+ mbufq_init(&ue->ue_rxq, 0 /* unlimited length */);
+
+ error = 0;
+ CURVNET_SET_QUIET(vnet0);
+ ifp = if_alloc(IFT_ETHER);
+ if_setsoftc(ifp, ue);
+ if_initname(ifp, "ue", ue->ue_unit);
+ if (ue->ue_methods->ue_attach_post_sub != NULL) {
+ ue->ue_ifp = ifp;
+ error = ue->ue_methods->ue_attach_post_sub(ue);
+ } else {
+ if_setflags(ifp, IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST);
+ if (ue->ue_methods->ue_ioctl != NULL)
+ if_setioctlfn(ifp, ue->ue_methods->ue_ioctl);
+ else
+ if_setioctlfn(ifp, uether_ioctl);
+ if_setstartfn(ifp, ue_start);
+ if_setinitfn(ifp, ue_init);
+ if_setsendqlen(ifp, ifqmaxlen);
+ if_setsendqready(ifp);
+ ue->ue_ifp = ifp;
+
+ if (ue->ue_methods->ue_mii_upd != NULL &&
+ ue->ue_methods->ue_mii_sts != NULL) {
+ bus_topo_lock();
+ error = mii_attach(ue->ue_dev, &ue->ue_miibus, ifp,
+ ue_ifmedia_upd, ue->ue_methods->ue_mii_sts,
+ BMSR_DEFCAPMASK, MII_PHY_ANY, MII_OFFSET_ANY, 0);
+ bus_topo_unlock();
+ }
+ }
+
+ if (error) {
+ device_printf(ue->ue_dev, "attaching PHYs failed\n");
+ goto fail;
+ }
+
+ if_printf(ifp, "<USB Ethernet> on %s\n", device_get_nameunit(ue->ue_dev));
+ ether_ifattach(ifp, ue->ue_eaddr);
+ /* Tell upper layer we support VLAN oversized frames. */
+ if (if_getcapabilities(ifp) & IFCAP_VLAN_MTU)
+ if_setifheaderlen(ifp, sizeof(struct ether_vlan_header));
+
+ CURVNET_RESTORE();
+
+ snprintf(num, sizeof(num), "%u", ue->ue_unit);
+ ue->ue_sysctl_oid = SYSCTL_ADD_NODE(&ue->ue_sysctl_ctx,
+ &SYSCTL_NODE_CHILDREN(_net, ue),
+ OID_AUTO, num, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "");
+ SYSCTL_ADD_PROC(&ue->ue_sysctl_ctx,
+ SYSCTL_CHILDREN(ue->ue_sysctl_oid), OID_AUTO, "%parent",
+ CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, ue, 0,
+ ue_sysctl_parent, "A", "parent device");
+
+ UE_LOCK(ue);
+ return;
+
+fail:
+ CURVNET_RESTORE();
+
+ /* drain mbuf queue */
+ mbufq_drain(&ue->ue_rxq);
+
+ /* free unit */
+ free_unr(ueunit, ue->ue_unit);
+ if (ue->ue_ifp != NULL) {
+ if_free(ue->ue_ifp);
+ ue->ue_ifp = NULL;
+ }
+ UE_LOCK(ue);
+ return;
+}
+
+void
+uether_ifdetach(struct usb_ether *ue)
+{
+ if_t ifp;
+
+ /* wait for any post attach or other command to complete */
+ usb_proc_drain(&ue->ue_tq);
+
+ /* read "ifnet" pointer after taskqueue drain */
+ ifp = ue->ue_ifp;
+
+ if (ifp != NULL) {
+ /* we are not running any more */
+ UE_LOCK(ue);
+ if_setdrvflagbits(ifp, 0, IFF_DRV_RUNNING);
+ UE_UNLOCK(ue);
+
+ /* drain any callouts */
+ usb_callout_drain(&ue->ue_watchdog);
+
+ /*
+ * Detach ethernet first to stop miibus calls from
+ * user-space:
+ */
+ ether_ifdetach(ifp);
+
+ /* detach miibus */
+ bus_topo_lock();
+ bus_generic_detach(ue->ue_dev);
+ bus_topo_unlock();
+
+ /* free interface instance */
+ if_free(ifp);
+
+ /* free sysctl */
+ sysctl_ctx_free(&ue->ue_sysctl_ctx);
+
+ /* drain mbuf queue */
+ mbufq_drain(&ue->ue_rxq);
+
+ /* free unit */
+ free_unr(ueunit, ue->ue_unit);
+ }
+
+ /* free taskqueue, if any */
+ usb_proc_free(&ue->ue_tq);
+}
+
+uint8_t
+uether_is_gone(struct usb_ether *ue)
+{
+ return (usb_proc_is_gone(&ue->ue_tq));
+}
+
+void
+uether_init(void *arg)
+{
+
+ ue_init(arg);
+}
+
+static void
+ue_init(void *arg)
+{
+ struct usb_ether *ue = arg;
+
+ UE_LOCK(ue);
+ ue_queue_command(ue, ue_start_task,
+ &ue->ue_sync_task[0].hdr,
+ &ue->ue_sync_task[1].hdr);
+ UE_UNLOCK(ue);
+}
+
+static void
+ue_start_task(struct usb_proc_msg *_task)
+{
+ struct usb_ether_cfg_task *task =
+ (struct usb_ether_cfg_task *)_task;
+ struct usb_ether *ue = task->ue;
+ if_t ifp = ue->ue_ifp;
+
+ UE_LOCK_ASSERT(ue, MA_OWNED);
+
+ ue->ue_methods->ue_init(ue);
+
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0)
+ return;
+
+ if (ue->ue_methods->ue_tick != NULL)
+ usb_callout_reset(&ue->ue_watchdog, hz, ue_watchdog, ue);
+}
+
+static void
+ue_stop_task(struct usb_proc_msg *_task)
+{
+ struct usb_ether_cfg_task *task =
+ (struct usb_ether_cfg_task *)_task;
+ struct usb_ether *ue = task->ue;
+
+ UE_LOCK_ASSERT(ue, MA_OWNED);
+
+ usb_callout_stop(&ue->ue_watchdog);
+
+ ue->ue_methods->ue_stop(ue);
+}
+
+void
+uether_start(if_t ifp)
+{
+
+ ue_start(ifp);
+}
+
+static void
+ue_start(if_t ifp)
+{
+ struct usb_ether *ue = if_getsoftc(ifp);
+
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0)
+ return;
+
+ UE_LOCK(ue);
+ ue->ue_methods->ue_start(ue);
+ UE_UNLOCK(ue);
+}
+
+static void
+ue_promisc_task(struct usb_proc_msg *_task)
+{
+ struct usb_ether_cfg_task *task =
+ (struct usb_ether_cfg_task *)_task;
+ struct usb_ether *ue = task->ue;
+
+ ue->ue_methods->ue_setpromisc(ue);
+}
+
+static void
+ue_setmulti_task(struct usb_proc_msg *_task)
+{
+ struct usb_ether_cfg_task *task =
+ (struct usb_ether_cfg_task *)_task;
+ struct usb_ether *ue = task->ue;
+
+ ue->ue_methods->ue_setmulti(ue);
+}
+
+int
+uether_ifmedia_upd(if_t ifp)
+{
+
+ return (ue_ifmedia_upd(ifp));
+}
+
+static int
+ue_ifmedia_upd(if_t ifp)
+{
+ struct usb_ether *ue = if_getsoftc(ifp);
+
+ /* Defer to process context */
+ UE_LOCK(ue);
+ ue_queue_command(ue, ue_ifmedia_task,
+ &ue->ue_media_task[0].hdr,
+ &ue->ue_media_task[1].hdr);
+ UE_UNLOCK(ue);
+
+ return (0);
+}
+
+static void
+ue_ifmedia_task(struct usb_proc_msg *_task)
+{
+ struct usb_ether_cfg_task *task =
+ (struct usb_ether_cfg_task *)_task;
+ struct usb_ether *ue = task->ue;
+ if_t ifp = ue->ue_ifp;
+
+ ue->ue_methods->ue_mii_upd(ifp);
+}
+
+static void
+ue_watchdog(void *arg)
+{
+ struct usb_ether *ue = arg;
+ if_t ifp = ue->ue_ifp;
+
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0)
+ return;
+
+ ue_queue_command(ue, ue_tick_task,
+ &ue->ue_tick_task[0].hdr,
+ &ue->ue_tick_task[1].hdr);
+
+ usb_callout_reset(&ue->ue_watchdog, hz, ue_watchdog, ue);
+}
+
+static void
+ue_tick_task(struct usb_proc_msg *_task)
+{
+ struct usb_ether_cfg_task *task =
+ (struct usb_ether_cfg_task *)_task;
+ struct usb_ether *ue = task->ue;
+ if_t ifp = ue->ue_ifp;
+
+ if ((if_getdrvflags(ifp) & IFF_DRV_RUNNING) == 0)
+ return;
+
+ ue->ue_methods->ue_tick(ue);
+}
+
+int
+uether_ioctl(if_t ifp, u_long command, caddr_t data)
+{
+ struct usb_ether *ue = if_getsoftc(ifp);
+ struct ifreq *ifr = (struct ifreq *)data;
+ struct mii_data *mii;
+ int error = 0;
+
+ switch (command) {
+ case SIOCSIFFLAGS:
+ UE_LOCK(ue);
+ if (if_getflags(ifp) & IFF_UP) {
+ if (if_getdrvflags(ifp) & IFF_DRV_RUNNING)
+ ue_queue_command(ue, ue_promisc_task,
+ &ue->ue_promisc_task[0].hdr,
+ &ue->ue_promisc_task[1].hdr);
+ else
+ ue_queue_command(ue, ue_start_task,
+ &ue->ue_sync_task[0].hdr,
+ &ue->ue_sync_task[1].hdr);
+ } else {
+ ue_queue_command(ue, ue_stop_task,
+ &ue->ue_sync_task[0].hdr,
+ &ue->ue_sync_task[1].hdr);
+ }
+ UE_UNLOCK(ue);
+ break;
+ case SIOCADDMULTI:
+ case SIOCDELMULTI:
+ UE_LOCK(ue);
+ ue_queue_command(ue, ue_setmulti_task,
+ &ue->ue_multi_task[0].hdr,
+ &ue->ue_multi_task[1].hdr);
+ UE_UNLOCK(ue);
+ break;
+ case SIOCGIFMEDIA:
+ case SIOCSIFMEDIA:
+ if (ue->ue_miibus != NULL) {
+ mii = device_get_softc(ue->ue_miibus);
+ error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, command);
+ } else
+ error = ether_ioctl(ifp, command, data);
+ break;
+ default:
+ error = ether_ioctl(ifp, command, data);
+ break;
+ }
+ return (error);
+}
+
+static int
+uether_modevent(module_t mod, int type, void *data)
+{
+
+ switch (type) {
+ case MOD_LOAD:
+ ueunit = new_unrhdr(0, INT_MAX, NULL);
+ break;
+ case MOD_UNLOAD:
+ break;
+ default:
+ return (EOPNOTSUPP);
+ }
+ return (0);
+}
+static moduledata_t uether_mod = {
+ "uether",
+ uether_modevent,
+ 0
+};
+
+struct mbuf *
+uether_newbuf(void)
+{
+ struct mbuf *m_new;
+
+ m_new = m_getcl(M_NOWAIT, 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
+uether_rxmbuf(struct usb_ether *ue, struct mbuf *m,
+ unsigned len)
+{
+ if_t ifp = ue->ue_ifp;
+
+ UE_LOCK_ASSERT(ue, MA_OWNED);
+
+ /* finalize mbuf */
+ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
+ m->m_pkthdr.rcvif = ifp;
+ if (len != 0) {
+ /*
+ * This is going to get it wrong for an mbuf chain, so let's
+ * make sure we're not doing that.
+ */
+ MPASS(m->m_next == NULL);
+ m->m_pkthdr.len = m->m_len = len;
+ }
+
+ /* enqueue for later when the lock can be released */
+ (void)mbufq_enqueue(&ue->ue_rxq, m);
+ return (0);
+}
+
+int
+uether_rxbuf(struct usb_ether *ue, struct usb_page_cache *pc,
+ unsigned offset, unsigned len)
+{
+ if_t ifp = ue->ue_ifp;
+ struct mbuf *m;
+
+ UE_LOCK_ASSERT(ue, MA_OWNED);
+
+ if (len < ETHER_HDR_LEN || len > MCLBYTES - ETHER_ALIGN)
+ return (1);
+
+ m = uether_newbuf();
+ if (m == NULL) {
+ if_inc_counter(ifp, IFCOUNTER_IQDROPS, 1);
+ return (ENOMEM);
+ }
+
+ usbd_copy_out(pc, offset, mtod(m, uint8_t *), len);
+
+ /* finalize mbuf */
+ if_inc_counter(ifp, IFCOUNTER_IPACKETS, 1);
+ m->m_pkthdr.rcvif = ifp;
+ m->m_pkthdr.len = m->m_len = len;
+
+ /* enqueue for later when the lock can be released */
+ (void)mbufq_enqueue(&ue->ue_rxq, m);
+ return (0);
+}
+
+void
+uether_rxflush(struct usb_ether *ue)
+{
+ if_t ifp = ue->ue_ifp;
+ struct epoch_tracker et;
+ struct mbuf *m, *n;
+
+ UE_LOCK_ASSERT(ue, MA_OWNED);
+
+ n = mbufq_flush(&ue->ue_rxq);
+ UE_UNLOCK(ue);
+ NET_EPOCH_ENTER(et);
+ while ((m = n) != NULL) {
+ n = STAILQ_NEXT(m, m_stailqpkt);
+ m->m_nextpkt = NULL;
+ if_input(ifp, m);
+ }
+ NET_EPOCH_EXIT(et);
+ UE_LOCK(ue);
+}
+
+/*
+ * USB net drivers are run by DRIVER_MODULE() thus SI_SUB_DRIVERS,
+ * SI_ORDER_MIDDLE. Run uether after that.
+ */
+DECLARE_MODULE(uether, uether_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
+MODULE_VERSION(uether, 1);
diff --git a/sys/dev/usb/net/usb_ethernet.h b/sys/dev/usb/net/usb_ethernet.h
new file mode 100644
index 000000000000..1f0e121af7a3
--- /dev/null
+++ b/sys/dev/usb/net/usb_ethernet.h
@@ -0,0 +1,123 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_ETHERNET_H_
+#define _USB_ETHERNET_H_
+
+#include "opt_inet.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/limits.h>
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+#include <net/bpf.h>
+#include <net/ethernet.h>
+
+struct mii_data;
+struct usb_ether;
+struct usb_device_request;
+
+typedef void (uether_fn_t)(struct usb_ether *);
+
+struct usb_ether_methods {
+ uether_fn_t *ue_attach_post;
+ uether_fn_t *ue_start;
+ uether_fn_t *ue_init;
+ uether_fn_t *ue_stop;
+ uether_fn_t *ue_setmulti;
+ uether_fn_t *ue_setpromisc;
+ uether_fn_t *ue_tick;
+ int (*ue_mii_upd)(if_t);
+ void (*ue_mii_sts)(if_t,
+ struct ifmediareq *);
+ int (*ue_ioctl)(if_t, u_long, caddr_t);
+ int (*ue_attach_post_sub)(struct usb_ether *);
+};
+
+struct usb_ether_cfg_task {
+ struct usb_proc_msg hdr;
+ struct usb_ether *ue;
+};
+
+struct usb_ether {
+ /* NOTE: the "ue_ifp" pointer must be first --hps */
+ if_t ue_ifp;
+ struct mtx *ue_mtx;
+ const struct usb_ether_methods *ue_methods;
+ struct sysctl_oid *ue_sysctl_oid;
+ void *ue_sc;
+ struct usb_device *ue_udev; /* used by uether_do_request() */
+ device_t ue_dev;
+ device_t ue_miibus;
+
+ struct usb_process ue_tq;
+ struct sysctl_ctx_list ue_sysctl_ctx;
+ struct mbufq ue_rxq;
+ struct usb_callout ue_watchdog;
+ struct usb_ether_cfg_task ue_sync_task[2];
+ struct usb_ether_cfg_task ue_media_task[2];
+ struct usb_ether_cfg_task ue_multi_task[2];
+ struct usb_ether_cfg_task ue_promisc_task[2];
+ struct usb_ether_cfg_task ue_tick_task[2];
+
+ int ue_unit;
+
+ /* ethernet address from eeprom */
+ uint8_t ue_eaddr[ETHER_ADDR_LEN];
+};
+
+#define uether_do_request(ue,req,data,timo) \
+ usbd_do_request_proc((ue)->ue_udev,&(ue)->ue_tq,req,data,0,NULL,timo)
+
+uint8_t uether_pause(struct usb_ether *, unsigned int);
+if_t uether_getifp(struct usb_ether *);
+struct mii_data *uether_getmii(struct usb_ether *);
+void *uether_getsc(struct usb_ether *);
+int uether_ifattach(struct usb_ether *);
+void uether_ifattach_wait(struct usb_ether *);
+void uether_ifdetach(struct usb_ether *);
+int uether_ifmedia_upd(if_t);
+void uether_init(void *);
+int uether_ioctl(if_t, u_long, caddr_t);
+struct mbuf *uether_newbuf(void);
+int uether_rxmbuf(struct usb_ether *, struct mbuf *,
+ unsigned);
+int uether_rxbuf(struct usb_ether *,
+ struct usb_page_cache *,
+ unsigned, unsigned);
+void uether_rxflush(struct usb_ether *);
+uint8_t uether_is_gone(struct usb_ether *);
+void uether_start(if_t);
+#endif /* _USB_ETHERNET_H_ */
diff --git a/sys/dev/usb/quirk/usb_quirk.c b/sys/dev/usb/quirk/usb_quirk.c
new file mode 100644
index 000000000000..802ea2b2ae6a
--- /dev/null
+++ b/sys/dev/usb/quirk/usb_quirk.c
@@ -0,0 +1,1064 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usbdi.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR usb_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_dynamic.h>
+
+#include <dev/usb/quirk/usb_quirk.h>
+
+MODULE_DEPEND(usb_quirk, usb, 1, 1, 1);
+MODULE_VERSION(usb_quirk, 1);
+
+#define USB_DEV_QUIRKS_MAX 384
+#define USB_SUB_QUIRKS_MAX 8
+#define USB_QUIRK_ENVROOT "hw.usb.quirk."
+
+struct usb_quirk_entry {
+ uint16_t vid;
+ uint16_t pid;
+ uint16_t lo_rev;
+ uint16_t hi_rev;
+ uint16_t quirks[USB_SUB_QUIRKS_MAX];
+};
+
+static struct mtx usb_quirk_mtx;
+
+#define USB_QUIRK(v, p, ...) { \
+ .vid = USB_VENDOR_##v, .pid = USB_PRODUCT_##v##_##p, .lo_rev = 0x0000, \
+ .hi_rev = 0xffff, .quirks = { __VA_ARGS__ } \
+}
+
+/* Vendor only */
+#define USB_QUIRK_VO(v, ...) { \
+ .vid = USB_VENDOR_##v, .pid = 0x0000, .lo_rev = 0x0000, .hi_rev = 0xffff, \
+ .quirks = { UQ_MATCH_VENDOR_ONLY, __VA_ARGS__ } \
+}
+
+/* Specific revision(s) */
+#define USB_QUIRK_REV(v, p, l, h, ...) { \
+ .vid = USB_VENDOR_##v, .pid = USB_PRODUCT_##v##_##p, .lo_rev = (l), \
+ .hi_rev = (h), .quirks = { __VA_ARGS__ } \
+}
+
+static struct usb_quirk_entry usb_quirks[USB_DEV_QUIRKS_MAX] = {
+ USB_QUIRK(ASUS, LCM, UQ_HID_IGNORE),
+ USB_QUIRK_REV(INSIDEOUT, EDGEPORT4, 0x094, 0x094, UQ_SWAP_UNICODE),
+ USB_QUIRK_REV(DALLAS, J6502, 0x0a2, 0x0a2, UQ_BAD_ADC),
+ USB_QUIRK_REV(DALLAS, J6502, 0x0a2, 0x0a2, UQ_AU_NO_XU),
+ USB_QUIRK_REV(ALTEC, ADA70, 0x103, 0x103, UQ_BAD_ADC),
+ USB_QUIRK_REV(ALTEC, ASC495, 0x000, 0x000, UQ_BAD_AUDIO),
+ USB_QUIRK_REV(QTRONIX, 980N, 0x110, 0x110, UQ_SPUR_BUT_UP),
+ USB_QUIRK_REV(ALCOR2, KBD_HUB, 0x001, 0x001, UQ_SPUR_BUT_UP),
+ USB_QUIRK_REV(MCT, HUB0100, 0x102, 0x102, UQ_BUS_POWERED),
+ USB_QUIRK_REV(MCT, USB232, 0x102, 0x102, UQ_BUS_POWERED),
+ USB_QUIRK_REV(TI, UTUSB41, 0x110, 0x110, UQ_POWER_CLAIM),
+ USB_QUIRK_REV(TELEX, MIC1, 0x009, 0x009, UQ_AU_NO_FRAC),
+ USB_QUIRK_REV(SILICONPORTALS, YAPPHONE, 0x100, 0x100, UQ_AU_INP_ASYNC),
+ USB_QUIRK(LOGITECH, UN53B, UQ_NO_STRINGS),
+ USB_QUIRK(LOGITECH, G510S, UQ_KBD_BOOTPROTO),
+ USB_QUIRK(REALTEK, RTL8196EU, UQ_CFG_INDEX_1),
+ USB_QUIRK(ELSA, MODEM1, UQ_CFG_INDEX_1),
+ USB_QUIRK(PLANEX2, MZKUE150N, UQ_CFG_INDEX_1),
+ USB_QUIRK(CISCOLINKSYS, USB3GIGV1, UQ_CFG_INDEX_1),
+ /* Quirks for printer devices */
+ USB_QUIRK(HP, 895C, UQ_BROKEN_BIDIR),
+ USB_QUIRK(HP, 880C, UQ_BROKEN_BIDIR),
+ USB_QUIRK(HP, 815C, UQ_BROKEN_BIDIR),
+ USB_QUIRK(HP, 810C, UQ_BROKEN_BIDIR),
+ USB_QUIRK(HP, 830C, UQ_BROKEN_BIDIR),
+ USB_QUIRK(HP, 1220C, UQ_BROKEN_BIDIR),
+ USB_QUIRK(XEROX, WCM15, UQ_BROKEN_BIDIR),
+ /* Devices which should be ignored by uhid */
+ USB_QUIRK(APC, UPS, UQ_HID_IGNORE),
+ USB_QUIRK(APC, UPS1000, UQ_HID_IGNORE),
+ USB_QUIRK(BELKIN, F6H375USB, UQ_HID_IGNORE),
+ USB_QUIRK(BELKIN, F6C550AVR, UQ_HID_IGNORE),
+ USB_QUIRK(BELKIN, F6C1250TWRK, UQ_HID_IGNORE),
+ USB_QUIRK(BELKIN, F6C1500TWRK, UQ_HID_IGNORE),
+ USB_QUIRK(BELKIN, F6C900UNV, UQ_HID_IGNORE),
+ USB_QUIRK(BELKIN, F6C100UNV, UQ_HID_IGNORE),
+ USB_QUIRK(BELKIN, F6C120UNV, UQ_HID_IGNORE),
+ USB_QUIRK(BELKIN, F6C800UNV, UQ_HID_IGNORE),
+ USB_QUIRK(BELKIN, F6C1100UNV, UQ_HID_IGNORE),
+ USB_QUIRK(CYBERPOWER, BC900D, UQ_HID_IGNORE),
+ USB_QUIRK(CYBERPOWER, 1500CAVRLCD, UQ_HID_IGNORE),
+ USB_QUIRK(CYBERPOWER, OR2200LCDRM2U, UQ_HID_IGNORE),
+ USB_QUIRK(DELL2, VARIOUS_UPS, UQ_HID_IGNORE),
+ USB_QUIRK(CYPRESS, SILVERSHIELD, UQ_HID_IGNORE),
+ USB_QUIRK(DELORME, EARTHMATE, UQ_HID_IGNORE),
+ USB_QUIRK(DREAMLINK, DL100B, UQ_HID_IGNORE),
+ USB_QUIRK(MICROCHIP, PICOLCD20X2, UQ_HID_IGNORE),
+ USB_QUIRK(MICROCHIP, PICOLCD4X20, UQ_HID_IGNORE),
+ USB_QUIRK(LIEBERT, POWERSURE_PXT, UQ_HID_IGNORE),
+ USB_QUIRK(LIEBERT2, PSI1000, UQ_HID_IGNORE),
+ USB_QUIRK(LIEBERT2, POWERSURE_PSA, UQ_HID_IGNORE),
+ USB_QUIRK(MGE, UPS1, UQ_HID_IGNORE),
+ USB_QUIRK(MGE, UPS2, UQ_HID_IGNORE),
+ USB_QUIRK(POWERCOM, IMPERIAL_SERIES, UQ_HID_IGNORE),
+ USB_QUIRK(POWERCOM, SMART_KING_PRO, UQ_HID_IGNORE),
+ USB_QUIRK(POWERCOM, WOW, UQ_HID_IGNORE),
+ USB_QUIRK(POWERCOM, VANGUARD, UQ_HID_IGNORE),
+ USB_QUIRK(POWERCOM, BLACK_KNIGHT_PRO, UQ_HID_IGNORE),
+ USB_QUIRK(TRIPPLITE2, AVR550U, UQ_HID_IGNORE),
+ USB_QUIRK(TRIPPLITE2, AVR750U, UQ_HID_IGNORE),
+ USB_QUIRK(TRIPPLITE2, ECO550UPS, UQ_HID_IGNORE),
+ USB_QUIRK(TRIPPLITE2, T750_INTL, UQ_HID_IGNORE),
+ USB_QUIRK(TRIPPLITE2, RT_2200_INTL, UQ_HID_IGNORE),
+ USB_QUIRK(TRIPPLITE2, OMNI1000LCD, UQ_HID_IGNORE),
+ USB_QUIRK(TRIPPLITE2, OMNI900LCD, UQ_HID_IGNORE),
+ USB_QUIRK(TRIPPLITE2, SMART_2200RMXL2U, UQ_HID_IGNORE),
+ USB_QUIRK(TRIPPLITE2, UPS_3014, UQ_HID_IGNORE),
+ USB_QUIRK(TRIPPLITE2, SU1500RTXL2UA, UQ_HID_IGNORE),
+ USB_QUIRK(TRIPPLITE2, SU6000RT4U, UQ_HID_IGNORE),
+ USB_QUIRK(TRIPPLITE2, SU1500RTXL2UA_2, UQ_HID_IGNORE),
+ USB_QUIRK(APPLE, IPHONE, UQ_HID_IGNORE),
+ USB_QUIRK(APPLE, IPHONE_3G, UQ_HID_IGNORE),
+ USB_QUIRK(MEGATEC, UPS, UQ_HID_IGNORE),
+ /* Devices which should be ignored by both ukbd and uhid */
+ USB_QUIRK(CYPRESS, WISPY1A, UQ_KBD_IGNORE, UQ_HID_IGNORE),
+ USB_QUIRK(METAGEEK, WISPY1B, UQ_KBD_IGNORE, UQ_HID_IGNORE),
+ USB_QUIRK(METAGEEK, WISPY24X, UQ_KBD_IGNORE, UQ_HID_IGNORE),
+ USB_QUIRK(METAGEEK2, WISPYDBX, UQ_KBD_IGNORE, UQ_HID_IGNORE),
+ USB_QUIRK_REV(TENX, UAUDIO0, 0x0101, 0x0101, UQ_AUDIO_SWAP_LR),
+ /* MS keyboards do weird things */
+ USB_QUIRK(MICROSOFT, NATURAL4000, UQ_KBD_BOOTPROTO),
+ USB_QUIRK(MICROSOFT, WLINTELLIMOUSE, UQ_MS_LEADING_BYTE),
+ /* Quirk for Corsair Vengeance K60 keyboard */
+ USB_QUIRK(CORSAIR, K60, UQ_KBD_BOOTPROTO),
+ /* Quirk for Corsair Gaming K68 keyboard */
+ USB_QUIRK(CORSAIR, K68, UQ_KBD_BOOTPROTO),
+ /* Quirk for Corsair Vengeance K70 keyboard */
+ USB_QUIRK(CORSAIR, K70, UQ_KBD_BOOTPROTO),
+ /* Quirk for Corsair K70 RGB keyboard */
+ USB_QUIRK(CORSAIR, K70_RGB, UQ_KBD_BOOTPROTO),
+ /* Quirk for Corsair STRAFE Gaming keyboard */
+ USB_QUIRK(CORSAIR, STRAFE, UQ_KBD_BOOTPROTO),
+ USB_QUIRK(CORSAIR, STRAFE2, UQ_KBD_BOOTPROTO),
+ /* Quirk for Kensington Slimblade Trackball */
+ USB_QUIRK(KENSINGTON, SLIMBLADE, UQ_MS_VENDOR_BTN),
+ /* umodem(4) device quirks */
+ USB_QUIRK_REV(METRICOM, RICOCHET_GS, 0x100, 0x100, UQ_ASSUME_CM_OVER_DATA),
+ USB_QUIRK_REV(SANYO, SCP4900, 0x000, 0x000, UQ_ASSUME_CM_OVER_DATA),
+ USB_QUIRK_REV(MOTOROLA2, T720C, 0x001, 0x001, UQ_ASSUME_CM_OVER_DATA),
+ USB_QUIRK_REV(EICON, DIVA852, 0x100, 0x100, UQ_ASSUME_CM_OVER_DATA),
+ USB_QUIRK_REV(SIEMENS2, ES75, 0x000, 0x000, UQ_ASSUME_CM_OVER_DATA),
+ USB_QUIRK(QUALCOMM, CDMA_MSM, UQ_ASSUME_CM_OVER_DATA),
+ USB_QUIRK(QUALCOMM2, CDMA_MSM, UQ_ASSUME_CM_OVER_DATA),
+ USB_QUIRK(CURITEL, UM150, UQ_ASSUME_CM_OVER_DATA),
+ USB_QUIRK(CURITEL, UM175, UQ_ASSUME_CM_OVER_DATA),
+ USB_QUIRK(VERTEX, VW110L, UQ_ASSUME_CM_OVER_DATA),
+ USB_QUIRK(BALTECH, SMARTCARDREADER, UQ_IGNORE_CDC_CM),
+
+ /* USB Mass Storage Class Quirks */
+ USB_QUIRK_VO(ASAHIOPTICAL, UQ_MSC_NO_RS_CLEAR_UA),
+ USB_QUIRK(ADDON, ATTACHE, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(ADDON, A256MB, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(ADDON, DISKPRO512, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(ADDONICS2, CABLE_205, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(AIPTEK, POCKETCAM3M, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(ALCOR, UMCR_9361, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(APACER, HT202, UQ_MSC_NO_TEST_UNIT_READY, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(ASAHIOPTICAL, OPTIO230, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(ASAHIOPTICAL, OPTIO330, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(ATP, EUSB, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(BELKIN, USB2SCSI, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(CASIO, QV_DIGICAM, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(CCYU, ED1064, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(CENTURY, EX35QUAT, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_FORCE_SHORT_INQ, UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK_REV(CREATIVE, NOMAD, 0x0001, 0xffff, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_READ_CAP_OFFBY1),
+ USB_QUIRK(CREATIVE, STAGE_SE_MINI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(CYPRESS, XX6830XX, UQ_MSC_NO_GETMAXLUN, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(EMTEC, DANEELEC4GB, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(DESKNOTE, UCR_61S2B, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(DMI, CFSM_RW, UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(EMTEC, RUF2PS, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(EPSON, STYLUS_875DC, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(EPSON, STYLUS_895, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(FEIYA, 5IN1, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(FEIYA, ELANGO, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(FREECOM, DVD, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(FUJIPHOTO, MASS0100, UQ_MSC_FORCE_WIRE_CBI_I,
+ UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_RS_CLEAR_UA, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(GARMIN, DAKOTA20, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(GARMIN, FORERUNNER230, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(GARMIN, GPSMAP62S, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(GARMIN, EDGETOURINGPLUS, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(GARMIN, INSTINCTSOLAR, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(GENESYS, GL641USB2IDE, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ, UQ_MSC_NO_START_STOP,
+ UQ_MSC_IGNORE_RESIDUE, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(GENESYS, GL641USB2IDE_2, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_FORCE_SHORT_INQ,
+ UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(GENESYS, GL641USB, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_FORCE_SHORT_INQ, UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(GENESYS, GL641USB_2, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_WRONG_CSWSIG),
+ USB_QUIRK(GENESYS, GL3220, UQ_MSC_NO_INQUIRY, UQ_MSC_NO_RS_CLEAR_UA),
+ USB_QUIRK(HAGIWARA, FG, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(HAGIWARA, FGSM, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(HITACHI, DVDCAM_DZ_MV100A, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(HITACHI, DVDCAM_USB, UQ_MSC_FORCE_WIRE_CBI_I,
+ UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(HP, CDW4E, UQ_MSC_FORCE_PROTO_ATAPI),
+ USB_QUIRK(HP, CDW8200, UQ_MSC_FORCE_WIRE_CBI_I, UQ_MSC_FORCE_PROTO_ATAPI,
+ UQ_MSC_NO_TEST_UNIT_READY, UQ_MSC_NO_START_STOP),
+ USB_QUIRK(HUAWEI, E3372_INIT, UQ_MSC_NO_INQUIRY, UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(IMAGINATION, DBX1, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_WRONG_CSWSIG),
+ USB_QUIRK(INSYSTEM, USBCABLE, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_ATAPI,
+ UQ_MSC_NO_TEST_UNIT_READY, UQ_MSC_NO_START_STOP, UQ_MSC_ALT_IFACE_1),
+ USB_QUIRK(INSYSTEM, ATAPI, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_RBC),
+ USB_QUIRK(INSYSTEM, STORAGE_V2, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_RBC),
+ USB_QUIRK(VIALABS, VL701, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(IODATA, IU_CD2, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(IODATA, DVR_UEH8, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(IOMEGA, ZIP100, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_NO_TEST_UNIT_READY), /* XXX ZIP drives can also use ATAPI */
+ USB_QUIRK(JMICRON, JMS566, UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(JMICRON, JMS567, UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(JMICRON, JM20337, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(KINGSTON, HYPERX3_0, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(KINGSTON, DATATRAVELER3_0, UQ_MSC_NO_PREVENT_ALLOW,
+ UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(KYOCERA, FINECAM_L3, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(KYOCERA, FINECAM_S3X, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(KYOCERA, FINECAM_S4, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(KYOCERA, FINECAM_S5, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(LACIE, HD, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_RBC),
+ USB_QUIRK(LEXAR, CF_READER, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(LEXAR, JUMPSHOT, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(LEXAR, JUMPDRIVE, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(LOGITEC, LDR_H443SU2, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(LOGITEC, LDR_H443U2, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(MELCO, DUBPXXG, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_FORCE_SHORT_INQ, UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(MICROTECH, DPCM, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_NO_TEST_UNIT_READY, UQ_MSC_NO_START_STOP),
+ USB_QUIRK(MICRON, REALSSD, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(MICROTECH, SCSIDB25, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(MICROTECH, SCSIHD50, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(MINOLTA, E223, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(MINOLTA, F300, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(MITSUMI, CDRRW, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_ATAPI),
+ USB_QUIRK(MOTOROLA2, E398, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_FORCE_SHORT_INQ, UQ_MSC_NO_INQUIRY_EVPD, UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK_VO(MPMAN, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(MSYSTEMS, DISKONKEY, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE, UQ_MSC_NO_GETMAXLUN,
+ UQ_MSC_NO_RS_CLEAR_UA),
+ USB_QUIRK(MSYSTEMS, DISKONKEY2, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_ATAPI),
+ USB_QUIRK(MYSON, HEDEN, UQ_MSC_IGNORE_RESIDUE, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(NEODIO, ND3260, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_FORCE_SHORT_INQ),
+ USB_QUIRK(NETAC, CF_CARD, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(NETAC, ONLYDISK, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(NETCHIP, CLIK_40, UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(NETCHIP, POCKETBOOK, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(NIKON, D300, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(NORELSYS, NS1081, UQ_MSC_NO_RS_CLEAR_UA, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(OLYMPUS, C1, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_WRONG_CSWSIG),
+ USB_QUIRK(OLYMPUS, C700, UQ_MSC_NO_GETMAXLUN),
+ /* Selected Olympus DSLR and EVIL models. See ../usbdevs for more
+ * details.
+ *
+ * Not all quirks apply to all models. The commented-out entries are
+ * correct for that model.
+ */
+ USB_QUIRK(OLYMPUS, E_1, UQ_MSC_NO_GETMAXLUN, UQ_MSC_NO_TEST_UNIT_READY,
+ UQ_MSC_NO_PREVENT_ALLOW, UQ_MSC_NO_SYNC_CACHE),
+ /*
+ * Product code 0x118.
+ * USB_QUIRK(OLYMPUS, E_300, UQ_MSC_NO_GETMAXLUN,
+ * UQ_MSC_NO_TEST_UNIT_READY, UQ_MSC_NO_PREVENT_ALLOW,
+ * UQ_MSC_NO_SYNC_CACHE),
+ * USB_QUIRK(OLYMPUS, E_30, UQ_MSC_NO_GETMAXLUN,
+ * UQ_MSC_NO_TEST_UNIT_READY, UQ_MSC_NO_PREVENT_ALLOW,
+ * UQ_MSC_NO_SYNC_CACHE), */
+ USB_QUIRK(OLYMPUS, E_330, UQ_MSC_NO_GETMAXLUN, UQ_MSC_NO_TEST_UNIT_READY,
+ UQ_MSC_NO_PREVENT_ALLOW, UQ_MSC_NO_SYNC_CACHE, UQ_MSC_NO_START_STOP),
+ USB_QUIRK(OLYMPUS, E_PM1, UQ_MSC_NO_GETMAXLUN, UQ_MSC_NO_TEST_UNIT_READY,
+ UQ_MSC_NO_PREVENT_ALLOW, UQ_MSC_NO_SYNC_CACHE, UQ_MSC_NO_START_STOP),
+ /* Product code 0x12e.
+ * USB_QUIRK(OLYMPUS, E_PM2, 0),
+ * USB_QUIRK(OLYMPUS, E_M1MarkII, UQ_MSC_NO_GETMAXLUN,
+ * UQ_MSC_NO_TEST_UNIT_READY, UQ_MSC_NO_PREVENT_ALLOW,
+ * UQ_MSC_NO_SYNC_CACHE),
+ * USB_QUIRK(OLYMPUS, E_M5MarkIII, 0),
+ */
+ USB_QUIRK(OLYMPUS, E_M1, UQ_MSC_NO_GETMAXLUN, UQ_MSC_NO_TEST_UNIT_READY,
+ UQ_MSC_NO_PREVENT_ALLOW, UQ_MSC_NO_SYNC_CACHE, UQ_MSC_NO_START_STOP),
+ USB_QUIRK(ONSPEC, SDS_HOTFIND_D, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(ONSPEC, CFMS_RW, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(ONSPEC, CFSM_COMBO, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(ONSPEC, CFSM_READER, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(ONSPEC, CFSM_READER2, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(ONSPEC, MDCFE_B_CF_READER, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(ONSPEC, MDSM_B_READER, UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(ONSPEC, READER, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(ONSPEC, UCF100, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY, UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(ONSPEC2, IMAGEMATE_SDDR55, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(PANASONIC, KXL840AN, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(PANASONIC, KXLCB20AN, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(PANASONIC, KXLCB35AN, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(PANASONIC, LS120CAM, UQ_MSC_FORCE_PROTO_UFI),
+ USB_QUIRK(PLEXTOR, 40_12_40U, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_TEST_UNIT_READY),
+ USB_QUIRK(PNY, ATTACHE2, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_IGNORE_RESIDUE, UQ_MSC_NO_START_STOP),
+ USB_QUIRK(PROLIFIC, PL2506, UQ_MSC_NO_SYNC_CACHE, UQ_MSC_NO_PREVENT_ALLOW),
+ USB_QUIRK(SAMSUNG_TECHWIN, DIGIMAX_410, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(SANDISK, SDDR05A, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_READ_CAP_OFFBY1),
+ USB_QUIRK(SANDISK, SDDR09, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_READ_CAP_OFFBY1, UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(SANDISK, SDDR12, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_READ_CAP_OFFBY1),
+ USB_QUIRK(SANDISK, SDCZ2_128, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(SANDISK, SDCZ2_256, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(SANDISK, SDCZ4_128, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(SANDISK, SDCZ4_256, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(SANDISK, SDCZ48_32, UQ_MSC_NO_SYNC_CACHE,
+ UQ_MSC_NO_TEST_UNIT_READY),
+ USB_QUIRK(SANDISK, SDDR31, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_READ_CAP_OFFBY1),
+ USB_QUIRK(SANDISK, IMAGEMATE_SDDR289, UQ_MSC_NO_SYNC_CACHE,
+ UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(SCANLOGIC, SL11R, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(SHUTTLE, EUSB, UQ_MSC_FORCE_WIRE_CBI_I, UQ_MSC_FORCE_PROTO_ATAPI,
+ UQ_MSC_NO_TEST_UNIT_READY, UQ_MSC_NO_START_STOP, UQ_MSC_SHUTTLE_INIT),
+ USB_QUIRK(SHUTTLE, CDRW, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_ATAPI),
+ USB_QUIRK(SHUTTLE, CF, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_ATAPI),
+ USB_QUIRK(SHUTTLE, EUSBATAPI, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_ATAPI),
+ USB_QUIRK(SHUTTLE, EUSBCFSM, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(SHUTTLE, EUSCSI, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(SHUTTLE, HIFD, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(SHUTTLE, SDDR09, UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(SHUTTLE, ZIOMMC, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(SIGMATEL, I_BEAD100, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_SHUTTLE_INIT),
+ USB_QUIRK(SIIG, WINTERREADER, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(SKANHEX, MD_7425, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(SKANHEX, SX_520Z, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK_REV(SONY, HANDYCAM, 0x0500, 0x0500, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_RBC, UQ_MSC_RBC_PAD_TO_12),
+ USB_QUIRK(SONY, CLIE_40_MS, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK_REV(SONY, DSC, 0x0500, 0x0500, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_RBC, UQ_MSC_RBC_PAD_TO_12),
+ USB_QUIRK_REV(SONY, DSC, 0x0600, 0x0600, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_RBC, UQ_MSC_RBC_PAD_TO_12),
+ USB_QUIRK(SONY, DSC, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_RBC),
+ USB_QUIRK(SONY, HANDYCAM, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_RBC),
+ USB_QUIRK(SONY, MSC, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_RBC),
+ USB_QUIRK(SONY, MS_MSC_U03, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_UFI),
+ USB_QUIRK(SONY, MS_NW_MS7, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(SONY, MS_PEG_N760C, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(SONY, MSACUS1, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_NO_GETMAXLUN),
+ USB_QUIRK(SONY, PORTABLE_HDD_V2, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(STMICRO, ST72682, UQ_MSC_NO_PREVENT_ALLOW),
+ USB_QUIRK(SUPERTOP, IDE, UQ_MSC_IGNORE_RESIDUE, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(SUPERTOP, FLASHDRIVE, UQ_MSC_NO_TEST_UNIT_READY,
+ UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(TAUGA, CAMERAMATE, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(TEAC, FD05PUB, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_UFI),
+ USB_QUIRK(TECLAST, TLC300, UQ_MSC_NO_TEST_UNIT_READY, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(TREK, MEMKEY, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(TREK, THUMBDRIVE_8MB, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(TRUMPION, C3310, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_UFI),
+ USB_QUIRK(TRUMPION, MP3, UQ_MSC_FORCE_PROTO_RBC),
+ USB_QUIRK(TRUMPION, T33520, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(TWINMOS, MDIV, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(VIA, USB2IDEBRIDGE, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_FORCE_PROTO_SCSI, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(VIVITAR, 35XX, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_NO_INQUIRY),
+ USB_QUIRK(WESTERN, COMBO, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_FORCE_SHORT_INQ, UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(WESTERN, EXTHDD, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_FORCE_SHORT_INQ, UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(WESTERN, MYBOOK, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_NO_INQUIRY_EVPD, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORT_00, UQ_MSC_FORCE_SHORT_INQ),
+ USB_QUIRK(WESTERN, MYPASSPORT_01, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORT_02, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORT_03, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORT_04, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORT_05, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORT_06, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORT_07, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORT_08, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORT_09, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORT_10, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORT_11, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORTES_00, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORTES_01, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORTES_02, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORTES_03, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORTES_04, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORTES_05, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORTES_06, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORTES_07, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORTES_08, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORTES_09, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(WESTERN, MYPASSPORTUL_00, UQ_MSC_NO_TEST_UNIT_READY),
+ USB_QUIRK(WINMAXGROUP, FLASH64MC, UQ_MSC_FORCE_WIRE_BBB,
+ UQ_MSC_NO_INQUIRY, UQ_MSC_FORCE_PROTO_SCSI),
+ USB_QUIRK(YANO, FW800HD, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_FORCE_SHORT_INQ, UQ_MSC_NO_START_STOP, UQ_MSC_IGNORE_RESIDUE),
+ USB_QUIRK(YANO, U640MO, UQ_MSC_FORCE_WIRE_CBI_I,
+ UQ_MSC_FORCE_PROTO_ATAPI, UQ_MSC_FORCE_SHORT_INQ),
+ USB_QUIRK_REV(YEDATA, FLASHBUSTERU, 0x0000, 0x007F, UQ_MSC_FORCE_WIRE_CBI,
+ UQ_MSC_FORCE_PROTO_UFI, UQ_MSC_NO_RS_CLEAR_UA, UQ_MSC_FLOPPY_SPEED,
+ UQ_MSC_NO_TEST_UNIT_READY),
+ USB_QUIRK_REV(YEDATA, FLASHBUSTERU, 0x0080, 0x0080,
+ UQ_MSC_FORCE_WIRE_CBI_I, UQ_MSC_FORCE_PROTO_UFI,
+ UQ_MSC_NO_RS_CLEAR_UA, UQ_MSC_FLOPPY_SPEED,
+ UQ_MSC_NO_TEST_UNIT_READY),
+ USB_QUIRK_REV(YEDATA, FLASHBUSTERU, 0x0081, 0xFFFF,
+ UQ_MSC_FORCE_WIRE_CBI_I, UQ_MSC_FORCE_PROTO_UFI,
+ UQ_MSC_NO_RS_CLEAR_UA, UQ_MSC_FLOPPY_SPEED),
+ USB_QUIRK(ZORAN, EX20DSC, UQ_MSC_FORCE_WIRE_CBI, UQ_MSC_FORCE_PROTO_ATAPI),
+ USB_QUIRK(MEIZU, M6_SL, UQ_MSC_FORCE_WIRE_BBB, UQ_MSC_FORCE_PROTO_SCSI,
+ UQ_MSC_NO_INQUIRY, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(TOSHIBA, TRANSMEMORY, UQ_MSC_NO_SYNC_CACHE,
+ UQ_MSC_NO_PREVENT_ALLOW),
+ USB_QUIRK(VIALABS, USB30SATABRIDGE, UQ_MSC_NO_SYNC_CACHE),
+ USB_QUIRK(QUALCOMMINC, ZTE_MF730M, UQ_MSC_NO_GETMAXLUN,
+ UQ_MSC_NO_INQUIRY, UQ_CFG_INDEX_0),
+ USB_QUIRK(SMART2, G2MEMKEY, UQ_MSC_NO_INQUIRY),
+ USB_QUIRK_REV(RALINK, RT_STOR, 0x0001, 0x0001, UQ_MSC_IGNORE),
+ USB_QUIRK(REALTEK, RTW8821CU_CD, UQ_MSC_IGNORE),
+ /* Non-standard USB MIDI devices */
+ USB_QUIRK(ROLAND, UM1, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, SC8850, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, SD90, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, UM880N, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, UA100, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, UM4, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, U8, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, UM2, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, SC8820, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, PC300, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, SK500, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, SCD70, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, UM550, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, SD20, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, SD80, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, UA700, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(ROLAND, PCR300, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(EGO, M4U, UQ_SINGLE_CMD_MIDI),
+ USB_QUIRK(LOGILINK, U2M, UQ_SINGLE_CMD_MIDI),
+ USB_QUIRK(MEDELI, DD305, UQ_SINGLE_CMD_MIDI, UQ_MATCH_VENDOR_ONLY),
+ USB_QUIRK(REDOCTANE, GHMIDI, UQ_SINGLE_CMD_MIDI),
+ USB_QUIRK(TEXTECH, U2M_1, UQ_SINGLE_CMD_MIDI),
+ USB_QUIRK(TEXTECH, U2M_2, UQ_SINGLE_CMD_MIDI),
+ USB_QUIRK(WCH2, U2M, UQ_SINGLE_CMD_MIDI),
+
+ /* Non-standard USB AUDIO devices */
+ USB_QUIRK(MAUDIO, FASTTRACKULTRA, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(MAUDIO, FASTTRACKULTRA8R, UQ_AU_VENDOR_CLASS),
+ USB_QUIRK(CMEDIA, CM6206, UQ_AU_SET_SPDIF_CM6206),
+ USB_QUIRK(PLOYTEC, SPL_CRIMSON_1, UQ_CFG_INDEX_1),
+ USB_QUIRK(ROLAND, UA25EX_AD, UQ_AU_VENDOR_CLASS),
+
+ /* DYMO LabelManager Pnp */
+ USB_QUIRK(DYMO, LABELMANAGERPNP, UQ_MSC_DYMO_EJECT),
+
+ /* Holtek USB gaming keyboard */
+ USB_QUIRK(HOLTEK, F85, UQ_KBD_BOOTPROTO),
+
+ /* This works much better with if_cdce than if_ure */
+ USB_QUIRK(LENOVO, TBT3LAN, UQ_CFG_INDEX_1),
+};
+#undef USB_QUIRK_VO
+#undef USB_QUIRK_REV
+#undef USB_QUIRK
+
+static const char *usb_quirk_str[USB_QUIRK_MAX] = {
+ [UQ_NONE] = "UQ_NONE",
+ [UQ_MATCH_VENDOR_ONLY] = "UQ_MATCH_VENDOR_ONLY",
+ [UQ_AUDIO_SWAP_LR] = "UQ_AUDIO_SWAP_LR",
+ [UQ_AU_INP_ASYNC] = "UQ_AU_INP_ASYNC",
+ [UQ_AU_NO_FRAC] = "UQ_AU_NO_FRAC",
+ [UQ_AU_NO_XU] = "UQ_AU_NO_XU",
+ [UQ_BAD_ADC] = "UQ_BAD_ADC",
+ [UQ_BAD_AUDIO] = "UQ_BAD_AUDIO",
+ [UQ_BROKEN_BIDIR] = "UQ_BROKEN_BIDIR",
+ [UQ_BUS_POWERED] = "UQ_BUS_POWERED",
+ [UQ_HID_IGNORE] = "UQ_HID_IGNORE",
+ [UQ_KBD_IGNORE] = "UQ_KBD_IGNORE",
+ [UQ_KBD_BOOTPROTO] = "UQ_KBD_BOOTPROTO",
+ [UQ_UMS_IGNORE] = "UQ_UMS_IGNORE",
+ [UQ_MS_BAD_CLASS] = "UQ_MS_BAD_CLASS",
+ [UQ_MS_LEADING_BYTE] = "UQ_MS_LEADING_BYTE",
+ [UQ_MS_REVZ] = "UQ_MS_REVZ",
+ [UQ_MS_VENDOR_BTN] = "UQ_MS_VENDOR_BTN",
+ [UQ_NO_STRINGS] = "UQ_NO_STRINGS",
+ [UQ_POWER_CLAIM] = "UQ_POWER_CLAIM",
+ [UQ_SPUR_BUT_UP] = "UQ_SPUR_BUT_UP",
+ [UQ_SWAP_UNICODE] = "UQ_SWAP_UNICODE",
+ [UQ_CFG_INDEX_1] = "UQ_CFG_INDEX_1",
+ [UQ_CFG_INDEX_2] = "UQ_CFG_INDEX_2",
+ [UQ_CFG_INDEX_3] = "UQ_CFG_INDEX_3",
+ [UQ_CFG_INDEX_4] = "UQ_CFG_INDEX_4",
+ [UQ_CFG_INDEX_0] = "UQ_CFG_INDEX_0",
+ [UQ_ASSUME_CM_OVER_DATA] = "UQ_ASSUME_CM_OVER_DATA",
+ [UQ_IGNORE_CDC_CM] = "UQ_IGNORE_CDC_CM",
+ [UQ_MSC_NO_TEST_UNIT_READY] = "UQ_MSC_NO_TEST_UNIT_READY",
+ [UQ_MSC_NO_RS_CLEAR_UA] = "UQ_MSC_NO_RS_CLEAR_UA",
+ [UQ_MSC_NO_START_STOP] = "UQ_MSC_NO_START_STOP",
+ [UQ_MSC_NO_GETMAXLUN] = "UQ_MSC_NO_GETMAXLUN",
+ [UQ_MSC_NO_INQUIRY] = "UQ_MSC_NO_INQUIRY",
+ [UQ_MSC_NO_INQUIRY_EVPD] = "UQ_MSC_NO_INQUIRY_EVPD",
+ [UQ_MSC_NO_PREVENT_ALLOW] = "UQ_MSC_NO_PREVENT_ALLOW",
+ [UQ_MSC_NO_SYNC_CACHE] = "UQ_MSC_NO_SYNC_CACHE",
+ [UQ_MSC_SHUTTLE_INIT] = "UQ_MSC_SHUTTLE_INIT",
+ [UQ_MSC_ALT_IFACE_1] = "UQ_MSC_ALT_IFACE_1",
+ [UQ_MSC_FLOPPY_SPEED] = "UQ_MSC_FLOPPY_SPEED",
+ [UQ_MSC_IGNORE_RESIDUE] = "UQ_MSC_IGNORE_RESIDUE",
+ [UQ_MSC_WRONG_CSWSIG] = "UQ_MSC_WRONG_CSWSIG",
+ [UQ_MSC_RBC_PAD_TO_12] = "UQ_MSC_RBC_PAD_TO_12",
+ [UQ_MSC_READ_CAP_OFFBY1] = "UQ_MSC_READ_CAP_OFFBY1",
+ [UQ_MSC_FORCE_SHORT_INQ] = "UQ_MSC_FORCE_SHORT_INQ",
+ [UQ_MSC_FORCE_WIRE_BBB] = "UQ_MSC_FORCE_WIRE_BBB",
+ [UQ_MSC_FORCE_WIRE_CBI] = "UQ_MSC_FORCE_WIRE_CBI",
+ [UQ_MSC_FORCE_WIRE_CBI_I] = "UQ_MSC_FORCE_WIRE_CBI_I",
+ [UQ_MSC_FORCE_PROTO_SCSI] = "UQ_MSC_FORCE_PROTO_SCSI",
+ [UQ_MSC_FORCE_PROTO_ATAPI] = "UQ_MSC_FORCE_PROTO_ATAPI",
+ [UQ_MSC_FORCE_PROTO_UFI] = "UQ_MSC_FORCE_PROTO_UFI",
+ [UQ_MSC_FORCE_PROTO_RBC] = "UQ_MSC_FORCE_PROTO_RBC",
+ [UQ_MSC_IGNORE] = "UQ_MSC_IGNORE",
+ [UQ_MSC_EJECT_HUAWEI] = "UQ_MSC_EJECT_HUAWEI",
+ [UQ_MSC_EJECT_SIERRA] = "UQ_MSC_EJECT_SIERRA",
+ [UQ_MSC_EJECT_SCSIEJECT] = "UQ_MSC_EJECT_SCSIEJECT",
+ [UQ_MSC_EJECT_REZERO] = "UQ_MSC_EJECT_REZERO",
+ [UQ_MSC_EJECT_ZTESTOR] = "UQ_MSC_EJECT_ZTESTOR",
+ [UQ_MSC_EJECT_CMOTECH] = "UQ_MSC_EJECT_CMOTECH",
+ [UQ_MSC_EJECT_WAIT] = "UQ_MSC_EJECT_WAIT",
+ [UQ_MSC_EJECT_SAEL_M460] = "UQ_MSC_EJECT_SAEL_M460",
+ [UQ_MSC_EJECT_HUAWEISCSI] = "UQ_MSC_EJECT_HUAWEISCSI",
+ [UQ_MSC_EJECT_HUAWEISCSI2] = "UQ_MSC_EJECT_HUAWEISCSI2",
+ [UQ_MSC_EJECT_HUAWEISCSI3] = "UQ_MSC_EJECT_HUAWEISCSI3",
+ [UQ_MSC_EJECT_HUAWEISCSI4] = "UQ_MSC_EJECT_HUAWEISCSI4",
+ [UQ_MSC_EJECT_TCT] = "UQ_MSC_EJECT_TCT",
+ [UQ_BAD_MIDI] = "UQ_BAD_MIDI",
+ [UQ_AU_VENDOR_CLASS] = "UQ_AU_VENDOR_CLASS",
+ [UQ_SINGLE_CMD_MIDI] = "UQ_SINGLE_CMD_MIDI",
+ [UQ_MSC_DYMO_EJECT] = "UQ_MSC_DYMO_EJECT",
+ [UQ_AU_SET_SPDIF_CM6206] = "UQ_AU_SET_SPDIF_CM6206",
+ [UQ_WMT_IGNORE] = "UQ_WMT_IGNORE",
+};
+
+/*------------------------------------------------------------------------*
+ * usb_quirkstr
+ *
+ * This function converts an USB quirk code into a string.
+ *------------------------------------------------------------------------*/
+static const char *
+usb_quirkstr(uint16_t quirk)
+{
+ return ((quirk < USB_QUIRK_MAX && usb_quirk_str[quirk] != NULL) ?
+ usb_quirk_str[quirk] : "UQ_UNKNOWN");
+}
+
+/*------------------------------------------------------------------------*
+ * usb_strquirk
+ *
+ * This function converts a string into a USB quirk code.
+ *
+ * Returns:
+ * Less than USB_QUIRK_MAX: Quirk code
+ * Else: Quirk code not found
+ *------------------------------------------------------------------------*/
+static uint16_t
+usb_strquirk(const char *str, size_t len)
+{
+ const char *quirk;
+ uint16_t x;
+
+ for (x = 0; x != USB_QUIRK_MAX; x++) {
+ quirk = usb_quirkstr(x);
+ if (strncmp(str, quirk, len) == 0 &&
+ quirk[len] == 0)
+ break;
+ }
+ return (x);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_test_quirk_by_info
+ *
+ * Returns:
+ * 0: Quirk not found
+ * Else: Quirk found
+ *------------------------------------------------------------------------*/
+static uint8_t
+usb_test_quirk_by_info(const struct usbd_lookup_info *info, uint16_t quirk)
+{
+ uint16_t x;
+ uint16_t y;
+
+ if (quirk == UQ_NONE)
+ goto done;
+
+ USB_MTX_LOCK(&usb_quirk_mtx);
+
+ for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) {
+ /* see if quirk information does not match */
+ if ((usb_quirks[x].vid != info->idVendor) ||
+ (usb_quirks[x].lo_rev > info->bcdDevice) ||
+ (usb_quirks[x].hi_rev < info->bcdDevice)) {
+ continue;
+ }
+ /* see if quirk only should match vendor ID */
+ if (usb_quirks[x].pid != info->idProduct) {
+ if (usb_quirks[x].pid != 0)
+ continue;
+
+ for (y = 0; y != USB_SUB_QUIRKS_MAX; y++) {
+ if (usb_quirks[x].quirks[y] == UQ_MATCH_VENDOR_ONLY)
+ break;
+ }
+ if (y == USB_SUB_QUIRKS_MAX)
+ continue;
+ }
+ /* lookup quirk */
+ for (y = 0; y != USB_SUB_QUIRKS_MAX; y++) {
+ if (usb_quirks[x].quirks[y] == quirk) {
+ USB_MTX_UNLOCK(&usb_quirk_mtx);
+ DPRINTF("Found quirk '%s'.\n", usb_quirkstr(quirk));
+ return (1);
+ }
+ }
+ }
+ USB_MTX_UNLOCK(&usb_quirk_mtx);
+done:
+ return (0); /* no quirk match */
+}
+
+static struct usb_quirk_entry *
+usb_quirk_get_entry(uint16_t vid, uint16_t pid,
+ uint16_t lo_rev, uint16_t hi_rev, uint8_t do_alloc)
+{
+ uint16_t x;
+
+ USB_MTX_ASSERT(&usb_quirk_mtx, MA_OWNED);
+
+ if ((vid | pid | lo_rev | hi_rev) == 0) {
+ /* all zero - special case */
+ return (usb_quirks + USB_DEV_QUIRKS_MAX - 1);
+ }
+ /* search for an existing entry */
+ for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) {
+ /* see if quirk information does not match */
+ if ((usb_quirks[x].vid != vid) ||
+ (usb_quirks[x].pid != pid) ||
+ (usb_quirks[x].lo_rev != lo_rev) ||
+ (usb_quirks[x].hi_rev != hi_rev)) {
+ continue;
+ }
+ return (usb_quirks + x);
+ }
+
+ if (do_alloc == 0) {
+ /* no match */
+ return (NULL);
+ }
+ /* search for a free entry */
+ for (x = 0; x != USB_DEV_QUIRKS_MAX; x++) {
+ /* see if quirk information does not match */
+ if ((usb_quirks[x].vid |
+ usb_quirks[x].pid |
+ usb_quirks[x].lo_rev |
+ usb_quirks[x].hi_rev) != 0) {
+ continue;
+ }
+ usb_quirks[x].vid = vid;
+ usb_quirks[x].pid = pid;
+ usb_quirks[x].lo_rev = lo_rev;
+ usb_quirks[x].hi_rev = hi_rev;
+
+ return (usb_quirks + x);
+ }
+
+ /* no entry found */
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_quirk_ioctl - handle quirk IOCTLs
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static int
+usb_quirk_ioctl(unsigned long cmd, caddr_t data,
+ int fflag, struct thread *td)
+{
+ struct usb_gen_quirk *pgq;
+ struct usb_quirk_entry *pqe;
+ uint32_t x;
+ uint32_t y;
+ int err;
+
+ switch (cmd) {
+ case USB_DEV_QUIRK_GET:
+ pgq = (void *)data;
+ x = pgq->index % USB_SUB_QUIRKS_MAX;
+ y = pgq->index / USB_SUB_QUIRKS_MAX;
+ if (y >= USB_DEV_QUIRKS_MAX) {
+ return (EINVAL);
+ }
+ USB_MTX_LOCK(&usb_quirk_mtx);
+ /* copy out data */
+ pgq->vid = usb_quirks[y].vid;
+ pgq->pid = usb_quirks[y].pid;
+ pgq->bcdDeviceLow = usb_quirks[y].lo_rev;
+ pgq->bcdDeviceHigh = usb_quirks[y].hi_rev;
+ strlcpy(pgq->quirkname,
+ usb_quirkstr(usb_quirks[y].quirks[x]),
+ sizeof(pgq->quirkname));
+ USB_MTX_UNLOCK(&usb_quirk_mtx);
+ return (0); /* success */
+
+ case USB_QUIRK_NAME_GET:
+ pgq = (void *)data;
+ x = pgq->index;
+ if (x >= USB_QUIRK_MAX) {
+ return (EINVAL);
+ }
+ strlcpy(pgq->quirkname,
+ usb_quirkstr(x), sizeof(pgq->quirkname));
+ return (0); /* success */
+
+ case USB_DEV_QUIRK_ADD:
+ pgq = (void *)data;
+
+ /* check privileges */
+ err = priv_check(curthread, PRIV_DRIVER);
+ if (err) {
+ return (err);
+ }
+ /* convert quirk string into numerical */
+ for (y = 0; y != USB_DEV_QUIRKS_MAX; y++) {
+ if (strcmp(pgq->quirkname, usb_quirkstr(y)) == 0) {
+ break;
+ }
+ }
+ if (y == USB_DEV_QUIRKS_MAX) {
+ return (EINVAL);
+ }
+ if (y == UQ_NONE) {
+ return (EINVAL);
+ }
+ USB_MTX_LOCK(&usb_quirk_mtx);
+ pqe = usb_quirk_get_entry(pgq->vid, pgq->pid,
+ pgq->bcdDeviceLow, pgq->bcdDeviceHigh, 1);
+ if (pqe == NULL) {
+ USB_MTX_UNLOCK(&usb_quirk_mtx);
+ return (EINVAL);
+ }
+ for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) {
+ if (pqe->quirks[x] == UQ_NONE) {
+ pqe->quirks[x] = y;
+ break;
+ }
+ }
+ USB_MTX_UNLOCK(&usb_quirk_mtx);
+ if (x == USB_SUB_QUIRKS_MAX) {
+ return (ENOMEM);
+ }
+ return (0); /* success */
+
+ case USB_DEV_QUIRK_REMOVE:
+ pgq = (void *)data;
+ /* check privileges */
+ err = priv_check(curthread, PRIV_DRIVER);
+ if (err) {
+ return (err);
+ }
+ /* convert quirk string into numerical */
+ for (y = 0; y != USB_DEV_QUIRKS_MAX; y++) {
+ if (strcmp(pgq->quirkname, usb_quirkstr(y)) == 0) {
+ break;
+ }
+ }
+ if (y == USB_DEV_QUIRKS_MAX) {
+ return (EINVAL);
+ }
+ if (y == UQ_NONE) {
+ return (EINVAL);
+ }
+ USB_MTX_LOCK(&usb_quirk_mtx);
+ pqe = usb_quirk_get_entry(pgq->vid, pgq->pid,
+ pgq->bcdDeviceLow, pgq->bcdDeviceHigh, 0);
+ if (pqe == NULL) {
+ USB_MTX_UNLOCK(&usb_quirk_mtx);
+ return (EINVAL);
+ }
+ for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) {
+ if (pqe->quirks[x] == y) {
+ pqe->quirks[x] = UQ_NONE;
+ break;
+ }
+ }
+ if (x == USB_SUB_QUIRKS_MAX) {
+ USB_MTX_UNLOCK(&usb_quirk_mtx);
+ return (ENOMEM);
+ }
+ for (x = 0; x != USB_SUB_QUIRKS_MAX; x++) {
+ if (pqe->quirks[x] != UQ_NONE) {
+ break;
+ }
+ }
+ if (x == USB_SUB_QUIRKS_MAX) {
+ /* all quirk entries are unused - release */
+ memset(pqe, 0, sizeof(*pqe));
+ }
+ USB_MTX_UNLOCK(&usb_quirk_mtx);
+ return (0); /* success */
+
+ default:
+ break;
+ }
+ return (ENOIOCTL);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_quirk_strtou16
+ *
+ * Helper function to scan a 16-bit integer.
+ *------------------------------------------------------------------------*/
+static uint16_t
+usb_quirk_strtou16(const char **pptr, const char *name, const char *what)
+{
+ unsigned long value;
+ char *end;
+
+ value = strtoul(*pptr, &end, 0);
+ if (value > 65535 || *pptr == end || (*end != ' ' && *end != '\t')) {
+ printf("%s: %s 16-bit %s value set to zero\n",
+ name, what, *end == 0 ? "incomplete" : "invalid");
+ return (0);
+ }
+ *pptr = end + 1;
+ return ((uint16_t)value);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_quirk_add_entry_from_str
+ *
+ * Add a USB quirk entry from string.
+ * "VENDOR PRODUCT LO_REV HI_REV QUIRK[,QUIRK[,...]]"
+ *------------------------------------------------------------------------*/
+static void
+usb_quirk_add_entry_from_str(const char *name, const char *env)
+{
+ struct usb_quirk_entry entry = { };
+ struct usb_quirk_entry *new;
+ uint16_t quirk_idx;
+ uint16_t quirk;
+ const char *end;
+
+ /* check for invalid environment variable */
+ if (name == NULL || env == NULL)
+ return;
+
+ if (bootverbose)
+ printf("Adding USB QUIRK '%s' = '%s'\n", name, env);
+
+ /* parse device information */
+ entry.vid = usb_quirk_strtou16(&env, name, "Vendor ID");
+ entry.pid = usb_quirk_strtou16(&env, name, "Product ID");
+ entry.lo_rev = usb_quirk_strtou16(&env, name, "Low revision");
+ entry.hi_rev = usb_quirk_strtou16(&env, name, "High revision");
+
+ /* parse quirk information */
+ quirk_idx = 0;
+ while (*env != 0 && quirk_idx != USB_SUB_QUIRKS_MAX) {
+ /* skip whitespace before quirks */
+ while (*env == ' ' || *env == '\t')
+ env++;
+
+ /* look for quirk separation character */
+ end = strchr(env, ',');
+ if (end == NULL)
+ end = env + strlen(env);
+
+ /* lookup quirk in string table */
+ quirk = usb_strquirk(env, end - env);
+ if (quirk < USB_QUIRK_MAX) {
+ entry.quirks[quirk_idx++] = quirk;
+ } else {
+ printf("%s: unknown USB quirk '%.*s' (skipped)\n",
+ name, (int)(end - env), env);
+ }
+ env = end;
+
+ /* skip quirk delimiter, if any */
+ if (*env != 0)
+ env++;
+ }
+
+ /* register quirk */
+ if (quirk_idx != 0) {
+ if (*env != 0) {
+ printf("%s: Too many USB quirks, only %d allowed!\n",
+ name, USB_SUB_QUIRKS_MAX);
+ }
+ USB_MTX_LOCK(&usb_quirk_mtx);
+ new = usb_quirk_get_entry(entry.vid, entry.pid,
+ entry.lo_rev, entry.hi_rev, 1);
+ if (new == NULL)
+ printf("%s: USB quirks table is full!\n", name);
+ else
+ memcpy(new->quirks, entry.quirks, sizeof(entry.quirks));
+ USB_MTX_UNLOCK(&usb_quirk_mtx);
+ } else {
+ printf("%s: No USB quirks found!\n", name);
+ }
+}
+
+static void
+usb_quirk_init(void *arg)
+{
+ char envkey[sizeof(USB_QUIRK_ENVROOT) + 2]; /* 2 digits max, 0 to 99 */
+ int i;
+
+ /* initialize mutex */
+ mtx_init(&usb_quirk_mtx, "USB quirk", NULL, MTX_DEF);
+
+ /* look for quirks defined by the environment variable */
+ for (i = 0; i != 100; i++) {
+ snprintf(envkey, sizeof(envkey), USB_QUIRK_ENVROOT "%d", i);
+
+ /* Stop at first undefined var */
+ if (!testenv(envkey))
+ break;
+
+ /* parse environment variable */
+ usb_quirk_add_entry_from_str(envkey, kern_getenv(envkey));
+ }
+
+ /* register our function */
+ usb_test_quirk_p = &usb_test_quirk_by_info;
+ usb_quirk_ioctl_p = &usb_quirk_ioctl;
+}
+
+static void
+usb_quirk_uninit(void *arg)
+{
+ usb_quirk_unload(arg);
+
+ /* destroy mutex */
+ mtx_destroy(&usb_quirk_mtx);
+}
+
+SYSINIT(usb_quirk_init, SI_SUB_LOCK, SI_ORDER_FIRST, usb_quirk_init, NULL);
+SYSUNINIT(usb_quirk_uninit, SI_SUB_LOCK, SI_ORDER_ANY, usb_quirk_uninit, NULL);
diff --git a/sys/dev/usb/quirk/usb_quirk.h b/sys/dev/usb/quirk/usb_quirk.h
new file mode 100644
index 000000000000..8e8bfd152eb8
--- /dev/null
+++ b/sys/dev/usb/quirk/usb_quirk.h
@@ -0,0 +1,126 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_QUIRK_H_
+#define _USB_QUIRK_H_
+
+enum {
+ /*
+ * Keep in sync with usb_quirk_str in usb_quirk.c, and with
+ * share/man/man4/usb_quirk.4
+ */
+ UQ_NONE, /* not a valid quirk */
+
+ UQ_MATCH_VENDOR_ONLY, /* match quirk on vendor only */
+
+ /* Various quirks */
+
+ UQ_AUDIO_SWAP_LR, /* left and right sound channels are swapped */
+ UQ_AU_INP_ASYNC, /* input is async despite claim of adaptive */
+ UQ_AU_NO_FRAC, /* don't adjust for fractional samples */
+ UQ_AU_NO_XU, /* audio device has broken extension unit */
+ UQ_BAD_ADC, /* bad audio spec version number */
+ UQ_BAD_AUDIO, /* device claims audio class, but isn't */
+ UQ_BROKEN_BIDIR, /* printer has broken bidir mode */
+ UQ_BUS_POWERED, /* device is bus powered, despite claim */
+ UQ_HID_IGNORE, /* device should be ignored by hid class */
+ UQ_KBD_IGNORE, /* device should be ignored by kbd class */
+ UQ_KBD_BOOTPROTO, /* device should set the boot protocol */
+ UQ_UMS_IGNORE, /* device should be ignored by ums class */
+ UQ_MS_BAD_CLASS, /* doesn't identify properly */
+ UQ_MS_LEADING_BYTE, /* mouse sends an unknown leading byte */
+ UQ_MS_REVZ, /* mouse has Z-axis reversed */
+ UQ_MS_VENDOR_BTN, /* mouse has buttons in vendor usage page */
+ UQ_NO_STRINGS, /* string descriptors are broken */
+ UQ_POWER_CLAIM, /* hub lies about power status */
+ UQ_SPUR_BUT_UP, /* spurious mouse button up events */
+ UQ_SWAP_UNICODE, /* has some Unicode strings swapped */
+ UQ_CFG_INDEX_1, /* select configuration index 1 by default */
+ UQ_CFG_INDEX_2, /* select configuration index 2 by default */
+ UQ_CFG_INDEX_3, /* select configuration index 3 by default */
+ UQ_CFG_INDEX_4, /* select configuration index 4 by default */
+ UQ_CFG_INDEX_0, /* select configuration index 0 by default */
+ UQ_ASSUME_CM_OVER_DATA, /* assume cm over data feature */
+ UQ_IGNORE_CDC_CM, /* ignore cm descriptor */
+
+ /*
+ * USB Mass Storage Quirks. See "storage/umass.c" for a
+ * detailed description.
+ */
+ UQ_MSC_NO_TEST_UNIT_READY, /* send start/stop instead of TUR */
+ UQ_MSC_NO_RS_CLEAR_UA, /* does not reset Unit Att. */
+ UQ_MSC_NO_START_STOP, /* does not support start/stop */
+ UQ_MSC_NO_GETMAXLUN, /* does not support get max LUN */
+ UQ_MSC_NO_INQUIRY, /* fake generic inq response */
+ UQ_MSC_NO_INQUIRY_EVPD, /* does not support inq EVPD */
+ UQ_MSC_NO_PREVENT_ALLOW, /* does not support medium removal */
+ UQ_MSC_NO_SYNC_CACHE, /* does not support sync cache */
+ UQ_MSC_SHUTTLE_INIT, /* requires Shuttle init sequence */
+ UQ_MSC_ALT_IFACE_1, /* switch to alternate interface 1 */
+ UQ_MSC_FLOPPY_SPEED, /* does floppy speeds (20kb/s) */
+ UQ_MSC_IGNORE_RESIDUE, /* gets residue wrong */
+ UQ_MSC_WRONG_CSWSIG, /* uses wrong CSW signature */
+ UQ_MSC_RBC_PAD_TO_12, /* pad RBC requests to 12 bytes */
+ UQ_MSC_READ_CAP_OFFBY1, /* reports sector count, not max sec. */
+ UQ_MSC_FORCE_SHORT_INQ, /* does not support full inq. */
+ UQ_MSC_FORCE_WIRE_BBB, /* force BBB wire protocol */
+ UQ_MSC_FORCE_WIRE_CBI, /* force CBI wire protocol */
+ UQ_MSC_FORCE_WIRE_CBI_I, /* force CBI with int. wire protocol */
+ UQ_MSC_FORCE_PROTO_SCSI, /* force SCSI command protocol */
+ UQ_MSC_FORCE_PROTO_ATAPI, /* force ATAPI command protocol */
+ UQ_MSC_FORCE_PROTO_UFI, /* force UFI command protocol */
+ UQ_MSC_FORCE_PROTO_RBC, /* force RBC command protocol */
+ UQ_MSC_IGNORE, /* device should be ignored by umass */
+
+ /* Ejection of mass storage (driver disk) */
+ UQ_MSC_EJECT_HUAWEI, /* ejects after Huawei USB command */
+ UQ_MSC_EJECT_SIERRA, /* ejects after Sierra USB command */
+ UQ_MSC_EJECT_SCSIEJECT, /* ejects after SCSI eject command */
+ UQ_MSC_EJECT_REZERO, /* ejects after SCSI rezero command */
+ UQ_MSC_EJECT_ZTESTOR, /* ejects after ZTE SCSI command */
+ UQ_MSC_EJECT_CMOTECH, /* ejects after C-motech SCSI cmd */
+ UQ_MSC_EJECT_WAIT, /* wait for the device to eject */
+ UQ_MSC_EJECT_SAEL_M460, /* ejects after Sael USB commands */
+ UQ_MSC_EJECT_HUAWEISCSI, /* ejects after Huawei SCSI command */
+ UQ_MSC_EJECT_HUAWEISCSI2, /* ejects after Huawei SCSI 2 command */
+ UQ_MSC_EJECT_HUAWEISCSI3, /* ejects after Huawei SCSI 3 command */
+ UQ_MSC_EJECT_HUAWEISCSI4, /* ejects after Huawei SCSI 4 command */
+ UQ_MSC_EJECT_TCT, /* ejects after TCT SCSI command */
+
+ UQ_BAD_MIDI, /* device claims MIDI class, but isn't */
+ UQ_AU_VENDOR_CLASS, /* audio device uses vendor and not audio class */
+ UQ_SINGLE_CMD_MIDI, /* at most one command per USB packet */
+ UQ_MSC_DYMO_EJECT, /* ejects Dymo MSC device */
+ UQ_AU_SET_SPDIF_CM6206, /* enable S/PDIF audio output */
+ UQ_WMT_IGNORE, /* device should be ignored by wmt driver */
+
+ USB_QUIRK_MAX
+};
+
+uint8_t usb_test_quirk(const struct usb_attach_arg *uaa, uint16_t quirk);
+
+#endif /* _USB_QUIRK_H_ */
diff --git a/sys/dev/usb/serial/u3g.c b/sys/dev/usb/serial/u3g.c
new file mode 100644
index 000000000000..a549f93b2af1
--- /dev/null
+++ b/sys/dev/usb/serial/u3g.c
@@ -0,0 +1,1338 @@
+/*
+ * 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.
+ */
+
+/*
+ * NOTE:
+ *
+ * - 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/eventhandler.h>
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_cdc.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR u3g_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_msctest.h>
+
+#include <dev/usb/serial/usb_serial.h>
+#include <dev/usb/quirk/usb_quirk.h>
+
+#ifdef USB_DEBUG
+static int u3g_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, u3g, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB 3g");
+SYSCTL_INT(_hw_usb_u3g, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &u3g_debug, 0, "Debug level");
+#endif
+
+#define U3G_MAXPORTS 12
+#define U3G_CONFIG_INDEX 0
+#define U3G_BSIZE 2048
+#define U3G_TXSIZE (U3G_BSIZE / U3G_TXFRAMES)
+#define U3G_TXFRAMES 4
+
+/* Eject methods; See also usb_quirks.h:UQ_MSC_EJECT_* */
+#define U3GINIT_HUAWEI 1 /* Requires Huawei init command */
+#define U3GINIT_SIERRA 2 /* Requires Sierra init command */
+#define U3GINIT_SCSIEJECT 3 /* Requires SCSI eject command */
+#define U3GINIT_REZERO 4 /* Requires SCSI rezero command */
+#define U3GINIT_ZTESTOR 5 /* Requires ZTE SCSI command */
+#define U3GINIT_CMOTECH 6 /* Requires CMOTECH SCSI command */
+#define U3GINIT_WAIT 7 /* Device reappears after a delay */
+#define U3GINIT_SAEL_M460 8 /* Requires vendor init */
+#define U3GINIT_HUAWEISCSI 9 /* Requires Huawei SCSI init command */
+#define U3GINIT_HUAWEISCSI2 10 /* Requires Huawei SCSI init command (2) */
+#define U3GINIT_HUAWEISCSI3 11 /* Requires Huawei SCSI init command (3) */
+#define U3GINIT_HUAWEISCSI4 12 /* Requires Huawei SCSI init command (4) */
+#define U3GINIT_TCT 13 /* Requires TCT Mobile init command */
+
+enum {
+ U3G_BULK_WR,
+ U3G_BULK_RD,
+ U3G_INTR,
+ U3G_N_TRANSFER,
+};
+
+struct u3g_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom[U3G_MAXPORTS];
+
+ struct usb_xfer *sc_xfer[U3G_MAXPORTS][U3G_N_TRANSFER];
+ uint8_t sc_iface[U3G_MAXPORTS]; /* local status register */
+ uint8_t sc_lsr[U3G_MAXPORTS]; /* local status register */
+ uint8_t sc_msr[U3G_MAXPORTS]; /* u3g status register */
+ uint16_t sc_line[U3G_MAXPORTS]; /* line status */
+
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+
+ uint8_t sc_numports;
+};
+
+static device_probe_t u3g_probe;
+static device_attach_t u3g_attach;
+static device_detach_t u3g_detach;
+static void u3g_free_softc(struct u3g_softc *);
+
+static usb_callback_t u3g_write_callback;
+static usb_callback_t u3g_read_callback;
+static usb_callback_t u3g_intr_callback;
+
+static void u3g_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *);
+static void u3g_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void u3g_cfg_set_rts(struct ucom_softc *, uint8_t);
+static void u3g_start_read(struct ucom_softc *ucom);
+static void u3g_stop_read(struct ucom_softc *ucom);
+static void u3g_start_write(struct ucom_softc *ucom);
+static void u3g_stop_write(struct ucom_softc *ucom);
+static void u3g_poll(struct ucom_softc *ucom);
+static void u3g_free(struct ucom_softc *ucom);
+
+static void u3g_test_autoinst(void *, struct usb_device *,
+ struct usb_attach_arg *);
+static int u3g_driver_loaded(struct module *mod, int what, void *arg);
+
+static eventhandler_tag u3g_etag;
+
+static const struct usb_config u3g_config[U3G_N_TRANSFER] = {
+ [U3G_BULK_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = U3G_BSIZE,/* bytes */
+ .frames = U3G_TXFRAMES,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &u3g_write_callback,
+ },
+
+ [U3G_BULK_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = U3G_BSIZE,/* bytes */
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &u3g_read_callback,
+ },
+
+ [U3G_INTR] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &u3g_intr_callback,
+ },
+};
+
+static const struct ucom_callback u3g_callback = {
+ .ucom_cfg_get_status = &u3g_cfg_get_status,
+ .ucom_cfg_set_dtr = &u3g_cfg_set_dtr,
+ .ucom_cfg_set_rts = &u3g_cfg_set_rts,
+ .ucom_start_read = &u3g_start_read,
+ .ucom_stop_read = &u3g_stop_read,
+ .ucom_start_write = &u3g_start_write,
+ .ucom_stop_write = &u3g_stop_write,
+ .ucom_poll = &u3g_poll,
+ .ucom_free = &u3g_free,
+};
+
+static device_method_t u3g_methods[] = {
+ DEVMETHOD(device_probe, u3g_probe),
+ DEVMETHOD(device_attach, u3g_attach),
+ DEVMETHOD(device_detach, u3g_detach),
+ DEVMETHOD_END
+};
+
+static driver_t u3g_driver = {
+ .name = "u3g",
+ .methods = u3g_methods,
+ .size = sizeof(struct u3g_softc),
+};
+
+static const STRUCT_USB_HOST_ID u3g_devs[] = {
+#define U3G_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
+ U3G_DEV(ABIT, AK_020, 0),
+ U3G_DEV(ACERP, H10, 0),
+ U3G_DEV(AIRPLUS, MCD650, 0),
+ U3G_DEV(AIRPRIME, PC5220, 0),
+ U3G_DEV(AIRPRIME, AC313U, 0),
+ U3G_DEV(ALINK, 3G, 0),
+ U3G_DEV(ALINK, 3GU, 0),
+ U3G_DEV(ALINK, DWM652U5, 0),
+ U3G_DEV(ALINK, SIM7600E, 0),
+ U3G_DEV(ALINK, SIM7600G, 0),
+ U3G_DEV(AMOI, H01, 0),
+ U3G_DEV(AMOI, H01A, 0),
+ U3G_DEV(AMOI, H02, 0),
+ U3G_DEV(ANYDATA, ADU_500A, 0),
+ U3G_DEV(ANYDATA, ADU_620UW, 0),
+ U3G_DEV(ANYDATA, ADU_E100X, 0),
+ U3G_DEV(AXESSTEL, DATAMODEM, 0),
+ U3G_DEV(CMOTECH, CDMA_MODEM1, 0),
+ U3G_DEV(CMOTECH, CGU628, U3GINIT_CMOTECH),
+ U3G_DEV(DELL, U5500, 0),
+ U3G_DEV(DELL, U5505, 0),
+ U3G_DEV(DELL, U5510, 0),
+ U3G_DEV(DELL, U5520, 0),
+ U3G_DEV(DELL, U5520_2, 0),
+ U3G_DEV(DELL, U5520_3, 0),
+ U3G_DEV(DELL, U5700, 0),
+ U3G_DEV(DELL, U5700_2, 0),
+ U3G_DEV(DELL, U5700_3, 0),
+ U3G_DEV(DELL, U5700_4, 0),
+ U3G_DEV(DELL, U5720, 0),
+ U3G_DEV(DELL, U5720_2, 0),
+ U3G_DEV(DELL, U5730, 0),
+ U3G_DEV(DELL, U5730_2, 0),
+ U3G_DEV(DELL, U5730_3, 0),
+ U3G_DEV(DELL, U740, 0),
+ U3G_DEV(DELL, DW5809, 0),
+ U3G_DEV(DELL, DW5809_2, 0),
+ U3G_DEV(DELL, DW5811, 0),
+ U3G_DEV(DELL, DW5811_2, 0),
+ U3G_DEV(DELL, DW5816, 0),
+ U3G_DEV(DELL, DW5816_2, 0),
+ U3G_DEV(DELL, DW5818, 0),
+ U3G_DEV(DELL, DW5818_2, 0),
+ U3G_DEV(DLINK, DWR510_CD, U3GINIT_SCSIEJECT),
+ U3G_DEV(DLINK, DWR510, 0),
+ U3G_DEV(DLINK, DWM157_CD, U3GINIT_SCSIEJECT),
+ U3G_DEV(DLINK, DWM157, 0),
+ U3G_DEV(DLINK, DWM157_CD_2, U3GINIT_SCSIEJECT),
+ U3G_DEV(DLINK, DWM157_2, 0),
+ U3G_DEV(DLINK, DWM222_CD, U3GINIT_SCSIEJECT),
+ U3G_DEV(DLINK, DWM222, 0),
+ U3G_DEV(DLINK, DWM222_CD_2, U3GINIT_SCSIEJECT),
+ U3G_DEV(DLINK, DWM222_2, 0),
+ U3G_DEV(DLINK3, DWM652, 0),
+ U3G_DEV(HP, EV2200, 0),
+ U3G_DEV(HP, HS2300, 0),
+ U3G_DEV(HP, UN2420_QDL, 0),
+ U3G_DEV(HP, UN2420, 0),
+ U3G_DEV(HP, LT4132, U3GINIT_HUAWEISCSI2),
+ U3G_DEV(HUAWEI, E1401, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1402, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1403, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1404, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1405, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1406, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1407, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1408, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1409, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E140A, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E140B, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E140D, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E140E, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E140F, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1410, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1411, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1412, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1413, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1414, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1415, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1416, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1417, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1418, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1419, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E141A, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E141B, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E141C, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E141D, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E141E, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E141F, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1420, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1421, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1422, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1423, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1424, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1425, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1426, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1427, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1428, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1429, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E142A, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E142B, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E142C, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E142D, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E142E, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E142F, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1430, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1431, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1432, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1433, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1434, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1435, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1436, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1437, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1438, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1439, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E143A, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E143B, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E143C, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E143D, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E143E, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E143F, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E173, 0),
+ U3G_DEV(HUAWEI, E173_INIT, U3GINIT_HUAWEISCSI),
+ U3G_DEV(HUAWEI, E180V, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E220, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E220BIS, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E392, U3GINIT_HUAWEISCSI),
+ U3G_DEV(HUAWEI, ME909U, U3GINIT_HUAWEISCSI2),
+ U3G_DEV(HUAWEI, ME909S, U3GINIT_HUAWEISCSI2),
+ U3G_DEV(HUAWEI, MOBILE, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E1752, U3GINIT_HUAWEISCSI),
+ U3G_DEV(HUAWEI, E1820, U3GINIT_HUAWEISCSI),
+ U3G_DEV(HUAWEI, K3771, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, K3771_INIT, U3GINIT_HUAWEISCSI2),
+ U3G_DEV(HUAWEI, K3772, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, K3772_INIT, U3GINIT_HUAWEISCSI2),
+ U3G_DEV(HUAWEI, K3765, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, K3765_INIT, U3GINIT_HUAWEISCSI),
+ U3G_DEV(HUAWEI, K3770, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, K3770_INIT, U3GINIT_HUAWEISCSI),
+ U3G_DEV(HUAWEI, K4505, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, K4505_INIT, U3GINIT_HUAWEISCSI),
+ U3G_DEV(HUAWEI, ETS2055, U3GINIT_HUAWEI),
+ U3G_DEV(HUAWEI, E3272_INIT, U3GINIT_HUAWEISCSI2),
+ U3G_DEV(HUAWEI, E3272, 0),
+ U3G_DEV(HUAWEI, E3372_NCM, 0),
+ U3G_DEV(HUAWEI, E3372_INIT, U3GINIT_HUAWEISCSI3),
+ U3G_DEV(HUAWEI, E3372v153_INIT, U3GINIT_HUAWEISCSI2),
+ U3G_DEV(HUAWEI, E3372v153_NCM, 0),
+ U3G_DEV(HUAWEI, E5573Cs322_NCM, 0),
+ U3G_DEV(HUAWEI, E5573Cs322_ECM, 0),
+ U3G_DEV(HUAWEI, E5573Cs322_ACM, 0),
+ U3G_DEV(KYOCERA2, CDMA_MSM_K, 0),
+ U3G_DEV(KYOCERA2, KPC680, 0),
+ U3G_DEV(LONGCHEER, WM66, U3GINIT_HUAWEI),
+ U3G_DEV(LONGCHEER, DISK, U3GINIT_TCT),
+ U3G_DEV(LONGCHEER, W14, 0),
+ U3G_DEV(LONGCHEER, XSSTICK, 0),
+ U3G_DEV(MERLIN, V620, 0),
+ U3G_DEV(NEOTEL, PRIME, 0),
+ U3G_DEV(NOVATEL, E725, 0),
+ U3G_DEV(NOVATEL, ES620, 0),
+ U3G_DEV(NOVATEL, ES620_2, 0),
+ U3G_DEV(NOVATEL, EU730, 0),
+ U3G_DEV(NOVATEL, EU740, 0),
+ U3G_DEV(NOVATEL, EU870D, 0),
+ U3G_DEV(NOVATEL, MC760, 0),
+ U3G_DEV(NOVATEL, MC547, 0),
+ U3G_DEV(NOVATEL, MC679, 0),
+ U3G_DEV(NOVATEL, MC950D, 0),
+ U3G_DEV(NOVATEL, MC990D, 0),
+ U3G_DEV(NOVATEL, MIFI2200, U3GINIT_SCSIEJECT),
+ U3G_DEV(NOVATEL, MIFI2200V, U3GINIT_SCSIEJECT),
+ U3G_DEV(NOVATEL, U720, 0),
+ U3G_DEV(NOVATEL, U727, 0),
+ U3G_DEV(NOVATEL, U727_2, 0),
+ U3G_DEV(NOVATEL, U740, 0),
+ U3G_DEV(NOVATEL, U740_2, 0),
+ U3G_DEV(NOVATEL, U760, U3GINIT_SCSIEJECT),
+ U3G_DEV(NOVATEL, U870, 0),
+ U3G_DEV(NOVATEL, V620, 0),
+ U3G_DEV(NOVATEL, V640, 0),
+ U3G_DEV(NOVATEL, V720, 0),
+ U3G_DEV(NOVATEL, V740, 0),
+ U3G_DEV(NOVATEL, X950D, 0),
+ U3G_DEV(NOVATEL, XU870, 0),
+ U3G_DEV(MOTOROLA2, MB886, U3GINIT_SCSIEJECT),
+ U3G_DEV(OPTION, E6500, 0),
+ U3G_DEV(OPTION, E6501, 0),
+ U3G_DEV(OPTION, E6601, 0),
+ U3G_DEV(OPTION, E6721, 0),
+ U3G_DEV(OPTION, E6741, 0),
+ U3G_DEV(OPTION, E6761, 0),
+ U3G_DEV(OPTION, E6800, 0),
+ U3G_DEV(OPTION, E7021, 0),
+ U3G_DEV(OPTION, E7041, 0),
+ U3G_DEV(OPTION, E7061, 0),
+ U3G_DEV(OPTION, E7100, 0),
+ U3G_DEV(OPTION, GE40X, 0),
+ U3G_DEV(OPTION, GT3G, 0),
+ U3G_DEV(OPTION, GT3GPLUS, 0),
+ U3G_DEV(OPTION, GT3GQUAD, 0),
+ U3G_DEV(OPTION, GT3G_1, 0),
+ U3G_DEV(OPTION, GT3G_2, 0),
+ U3G_DEV(OPTION, GT3G_3, 0),
+ U3G_DEV(OPTION, GT3G_4, 0),
+ U3G_DEV(OPTION, GT3G_5, 0),
+ U3G_DEV(OPTION, GT3G_6, 0),
+ U3G_DEV(OPTION, GTHSDPA, 0),
+ U3G_DEV(OPTION, GTM380, 0),
+ U3G_DEV(OPTION, GTMAX36, 0),
+ U3G_DEV(OPTION, GTMAX380HSUPAE, 0),
+ U3G_DEV(OPTION, GTMAXHSUPA, 0),
+ U3G_DEV(OPTION, GTMAXHSUPAE, 0),
+ U3G_DEV(OPTION, VODAFONEMC3G, 0),
+ U3G_DEV(PANASONIC, CFF9_3G_QDL, 0),
+ U3G_DEV(PANASONIC, CFF9_3G, 0),
+ U3G_DEV(QISDA, H20_1, 0),
+ U3G_DEV(QISDA, H20_2, 0),
+ U3G_DEV(QISDA, H21_1, 0),
+ U3G_DEV(QISDA, H21_2, 0),
+ U3G_DEV(QUALCOMM, NTT_L02C_MODEM, U3GINIT_SCSIEJECT),
+ U3G_DEV(QUALCOMM2, AC8700, 0),
+ U3G_DEV(QUALCOMM2, MF330, 0),
+ U3G_DEV(QUALCOMM2, SIM5218, 0),
+ U3G_DEV(QUALCOMM2, WM620, 0),
+ U3G_DEV(QUALCOMM2, VW110L, U3GINIT_SCSIEJECT),
+ U3G_DEV(QUALCOMM2, GOBI2000_QDL, 0),
+ U3G_DEV(QUALCOMM2, GOBI2000, 0),
+ U3G_DEV(QUALCOMM2, VT80N, 0),
+ U3G_DEV(QUALCOMM3, VFAST2, 0),
+ U3G_DEV(QUALCOMMINC, AC2726, 0),
+ U3G_DEV(QUALCOMMINC, AC682_INIT, U3GINIT_SCSIEJECT),
+ U3G_DEV(QUALCOMMINC, AC682, 0),
+ U3G_DEV(QUALCOMMINC, AC8700, 0),
+ U3G_DEV(QUALCOMMINC, AC8710, 0),
+ U3G_DEV(QUALCOMMINC, CDMA_MSM, U3GINIT_SCSIEJECT),
+ U3G_DEV(QUALCOMMINC, E0002, 0),
+ U3G_DEV(QUALCOMMINC, E0003, 0),
+ U3G_DEV(QUALCOMMINC, E0004, 0),
+ U3G_DEV(QUALCOMMINC, E0005, 0),
+ U3G_DEV(QUALCOMMINC, E0006, 0),
+ U3G_DEV(QUALCOMMINC, E0007, 0),
+ U3G_DEV(QUALCOMMINC, E0008, 0),
+ U3G_DEV(QUALCOMMINC, E0009, 0),
+ U3G_DEV(QUALCOMMINC, E000A, 0),
+ U3G_DEV(QUALCOMMINC, E000B, 0),
+ U3G_DEV(QUALCOMMINC, E000C, 0),
+ U3G_DEV(QUALCOMMINC, E000D, 0),
+ U3G_DEV(QUALCOMMINC, E000E, 0),
+ U3G_DEV(QUALCOMMINC, E000F, 0),
+ U3G_DEV(QUALCOMMINC, E0010, 0),
+ U3G_DEV(QUALCOMMINC, E0011, 0),
+ U3G_DEV(QUALCOMMINC, E0012, 0),
+ U3G_DEV(QUALCOMMINC, E0013, 0),
+ U3G_DEV(QUALCOMMINC, E0014, 0),
+ U3G_DEV(QUALCOMMINC, E0017, 0),
+ U3G_DEV(QUALCOMMINC, E0018, 0),
+ U3G_DEV(QUALCOMMINC, E0019, 0),
+ U3G_DEV(QUALCOMMINC, E0020, 0),
+ U3G_DEV(QUALCOMMINC, E0021, 0),
+ U3G_DEV(QUALCOMMINC, E0022, 0),
+ U3G_DEV(QUALCOMMINC, E0023, 0),
+ U3G_DEV(QUALCOMMINC, E0024, 0),
+ U3G_DEV(QUALCOMMINC, E0025, 0),
+ U3G_DEV(QUALCOMMINC, E0026, 0),
+ U3G_DEV(QUALCOMMINC, E0027, 0),
+ U3G_DEV(QUALCOMMINC, E0028, 0),
+ U3G_DEV(QUALCOMMINC, E0029, 0),
+ U3G_DEV(QUALCOMMINC, E0030, 0),
+ U3G_DEV(QUALCOMMINC, E0032, 0),
+ U3G_DEV(QUALCOMMINC, E0033, 0),
+ U3G_DEV(QUALCOMMINC, E0037, 0),
+ U3G_DEV(QUALCOMMINC, E0039, 0),
+ U3G_DEV(QUALCOMMINC, E0042, 0),
+ U3G_DEV(QUALCOMMINC, E0043, 0),
+ U3G_DEV(QUALCOMMINC, E0048, 0),
+ U3G_DEV(QUALCOMMINC, E0049, 0),
+ U3G_DEV(QUALCOMMINC, E0051, 0),
+ U3G_DEV(QUALCOMMINC, E0052, 0),
+ U3G_DEV(QUALCOMMINC, E0054, 0),
+ U3G_DEV(QUALCOMMINC, E0055, 0),
+ U3G_DEV(QUALCOMMINC, E0057, 0),
+ U3G_DEV(QUALCOMMINC, E0058, 0),
+ U3G_DEV(QUALCOMMINC, E0059, 0),
+ U3G_DEV(QUALCOMMINC, E0060, 0),
+ U3G_DEV(QUALCOMMINC, E0061, 0),
+ U3G_DEV(QUALCOMMINC, E0062, 0),
+ U3G_DEV(QUALCOMMINC, E0063, 0),
+ U3G_DEV(QUALCOMMINC, E0064, 0),
+ U3G_DEV(QUALCOMMINC, E0066, 0),
+ U3G_DEV(QUALCOMMINC, E0069, 0),
+ U3G_DEV(QUALCOMMINC, E0070, 0),
+ U3G_DEV(QUALCOMMINC, E0073, 0),
+ U3G_DEV(QUALCOMMINC, E0076, 0),
+ U3G_DEV(QUALCOMMINC, E0078, 0),
+ U3G_DEV(QUALCOMMINC, E0082, 0),
+ U3G_DEV(QUALCOMMINC, E0086, 0),
+ U3G_DEV(QUALCOMMINC, SURFSTICK, 0),
+ U3G_DEV(QUALCOMMINC, E2002, 0),
+ U3G_DEV(QUALCOMMINC, E2003, 0),
+ U3G_DEV(QUALCOMMINC, K3772_Z, 0),
+ U3G_DEV(QUALCOMMINC, K3772_Z_INIT, U3GINIT_SCSIEJECT),
+ U3G_DEV(QUALCOMMINC, MF112, U3GINIT_ZTESTOR),
+ U3G_DEV(QUALCOMMINC, MF195E, 0),
+ U3G_DEV(QUALCOMMINC, MF195E_INIT, U3GINIT_SCSIEJECT),
+ U3G_DEV(QUALCOMMINC, MF626, 0),
+ U3G_DEV(QUALCOMMINC, MF628, 0),
+ U3G_DEV(QUALCOMMINC, MF633R, 0),
+ /* the following is a RNDIS device, no modem features */
+ U3G_DEV(QUALCOMMINC, ZTE_MF730M, U3GINIT_SCSIEJECT),
+ U3G_DEV(QUANTA, GKE, 0),
+ U3G_DEV(QUANTA, GLE, 0),
+ U3G_DEV(QUANTA, GLX, 0),
+ U3G_DEV(QUANTA, Q101, 0),
+ U3G_DEV(QUANTA, Q111, 0),
+ U3G_DEV(QUECTEL, EC21, 0),
+ U3G_DEV(QUECTEL, EC25, 0),
+ U3G_DEV(QUECTEL, EM05, 0),
+ U3G_DEV(QUECTEL, EG91, 0),
+ U3G_DEV(QUECTEL, EG95, 0),
+ U3G_DEV(QUECTEL, BG96, 0),
+ U3G_DEV(QUECTEL, EP06, 0),
+ U3G_DEV(QUECTEL, EG065K, 0),
+ U3G_DEV(QUECTEL, AG15, 0),
+ U3G_DEV(QUECTEL, AG35, 0),
+ U3G_DEV(QUECTEL, AG520, 0),
+ U3G_DEV(QUECTEL, AG550, 0),
+ U3G_DEV(QUECTEL, EM12, 0),
+ U3G_DEV(QUECTEL, EM160R, 0),
+ U3G_DEV(QUECTEL, BG95, 0),
+ U3G_DEV(QUECTEL, RG500, 0),
+ U3G_DEV(QUECTEL, RG520, 0),
+ U3G_DEV(QUECTEL, EC200, 0),
+ U3G_DEV(QUECTEL, EC200S, 0),
+ U3G_DEV(QUECTEL, EC200T, 0),
+ U3G_DEV(QUECTEL, UC200, 0),
+ U3G_DEV(SIERRA, AC402, 0),
+ U3G_DEV(SIERRA, AC595U, 0),
+ U3G_DEV(SIERRA, AC313U, 0),
+ U3G_DEV(SIERRA, AC597E, 0),
+ U3G_DEV(SIERRA, AC875, 0),
+ U3G_DEV(SIERRA, AC875E, 0),
+ U3G_DEV(SIERRA, AC875U, 0),
+ U3G_DEV(SIERRA, AC875U_2, 0),
+ U3G_DEV(SIERRA, AC880, 0),
+ U3G_DEV(SIERRA, AC880E, 0),
+ U3G_DEV(SIERRA, AC880U, 0),
+ U3G_DEV(SIERRA, AC881, 0),
+ U3G_DEV(SIERRA, AC881E, 0),
+ U3G_DEV(SIERRA, AC881U, 0),
+ U3G_DEV(SIERRA, AC885E, 0),
+ U3G_DEV(SIERRA, AC885E_2, 0),
+ U3G_DEV(SIERRA, AC885U, 0),
+ U3G_DEV(SIERRA, AIRCARD580, 0),
+ U3G_DEV(SIERRA, AIRCARD595, 0),
+ U3G_DEV(SIERRA, C22, 0),
+ U3G_DEV(SIERRA, C597, 0),
+ U3G_DEV(SIERRA, C888, 0),
+ U3G_DEV(SIERRA, E0029, 0),
+ U3G_DEV(SIERRA, E6892, 0),
+ U3G_DEV(SIERRA, E6893, 0),
+ U3G_DEV(SIERRA, EM5625, 0),
+ U3G_DEV(SIERRA, EM5725, 0),
+ U3G_DEV(SIERRA, MC5720, 0),
+ U3G_DEV(SIERRA, MC5720_2, 0),
+ U3G_DEV(SIERRA, MC5725, 0),
+ U3G_DEV(SIERRA, MC5727, 0),
+ U3G_DEV(SIERRA, MC5727_2, 0),
+ U3G_DEV(SIERRA, MC5728, 0),
+ U3G_DEV(SIERRA, MC7354, 0),
+ U3G_DEV(SIERRA, MC7355, 0),
+ U3G_DEV(SIERRA, AC340U, 0),
+ U3G_DEV(SIERRA, MC7430, 0),
+ U3G_DEV(SIERRA, MC8700, 0),
+ U3G_DEV(SIERRA, MC8755, 0),
+ U3G_DEV(SIERRA, MC8755_2, 0),
+ U3G_DEV(SIERRA, MC8755_3, 0),
+ U3G_DEV(SIERRA, MC8755_4, 0),
+ U3G_DEV(SIERRA, MC8765, 0),
+ U3G_DEV(SIERRA, MC8765_2, 0),
+ U3G_DEV(SIERRA, MC8765_3, 0),
+ U3G_DEV(SIERRA, MC8775, 0),
+ U3G_DEV(SIERRA, MC8775_2, 0),
+ U3G_DEV(SIERRA, MC8780, 0),
+ U3G_DEV(SIERRA, MC8780_2, 0),
+ U3G_DEV(SIERRA, MC8780_3, 0),
+ U3G_DEV(SIERRA, MC8781, 0),
+ U3G_DEV(SIERRA, MC8781_2, 0),
+ U3G_DEV(SIERRA, MC8781_3, 0),
+ U3G_DEV(SIERRA, MC8785, 0),
+ U3G_DEV(SIERRA, MC8785_2, 0),
+ U3G_DEV(SIERRA, MC8790, 0),
+ U3G_DEV(SIERRA, MC8791, 0),
+ U3G_DEV(SIERRA, MC8792, 0),
+ U3G_DEV(SIERRA, MINI5725, 0),
+ U3G_DEV(SIERRA, T11, 0),
+ U3G_DEV(SIERRA, T598, 0),
+ U3G_DEV(SIERRA, EM7430, 0),
+ U3G_DEV(SIERRA, EM7430_2, 0),
+ U3G_DEV(SIERRA, EM7455, 0),
+ U3G_DEV(SIERRA, EM7455_2, 0),
+ U3G_DEV(SIERRA, EM7565, 0),
+ U3G_DEV(SIERRA, EM7565_2, 0),
+ U3G_DEV(SILABS, SAEL, U3GINIT_SAEL_M460),
+ U3G_DEV(STELERA, C105, 0),
+ U3G_DEV(STELERA, E1003, 0),
+ U3G_DEV(STELERA, E1004, 0),
+ U3G_DEV(STELERA, E1005, 0),
+ U3G_DEV(STELERA, E1006, 0),
+ U3G_DEV(STELERA, E1007, 0),
+ U3G_DEV(STELERA, E1008, 0),
+ U3G_DEV(STELERA, E1009, 0),
+ U3G_DEV(STELERA, E100A, 0),
+ U3G_DEV(STELERA, E100B, 0),
+ U3G_DEV(STELERA, E100C, 0),
+ U3G_DEV(STELERA, E100D, 0),
+ U3G_DEV(STELERA, E100E, 0),
+ U3G_DEV(STELERA, E100F, 0),
+ U3G_DEV(STELERA, E1010, 0),
+ U3G_DEV(STELERA, E1011, 0),
+ U3G_DEV(STELERA, E1012, 0),
+ U3G_DEV(TCTMOBILE, X060S, 0),
+ U3G_DEV(TCTMOBILE, X080S, U3GINIT_TCT),
+ U3G_DEV(TELIT, UC864E, 0),
+ U3G_DEV(TELIT, UC864G, 0),
+ U3G_DEV(TLAYTECH, TEU800, 0),
+ U3G_DEV(TOSHIBA, G450, 0),
+ U3G_DEV(TOSHIBA, HSDPA, 0),
+ U3G_DEV(YISO, C893, 0),
+ U3G_DEV(WETELECOM, WM_D200, 0),
+ /* Autoinstallers */
+ U3G_DEV(NOVATEL, ZEROCD, U3GINIT_SCSIEJECT),
+ U3G_DEV(OPTION, GTICON322, U3GINIT_REZERO),
+ U3G_DEV(QUALCOMMINC, ZTE_STOR, U3GINIT_ZTESTOR),
+ U3G_DEV(QUALCOMMINC, ZTE_STOR2, U3GINIT_SCSIEJECT),
+ U3G_DEV(QUANTA, Q101_STOR, U3GINIT_SCSIEJECT),
+ U3G_DEV(SIERRA, TRUINSTALL, U3GINIT_SIERRA),
+#undef U3G_DEV
+};
+
+DRIVER_MODULE(u3g, uhub, u3g_driver, u3g_driver_loaded, NULL);
+MODULE_DEPEND(u3g, ucom, 1, 1, 1);
+MODULE_DEPEND(u3g, usb, 1, 1, 1);
+MODULE_VERSION(u3g, 1);
+USB_PNP_HOST_INFO(u3g_devs);
+
+static int
+u3g_sierra_init(struct usb_device *udev)
+{
+ struct usb_device_request 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);
+
+ if (usbd_do_request_flags(udev, NULL, &req,
+ NULL, 0, NULL, USB_MS_HZ)) {
+ /* ignore any errors */
+ }
+ return (0);
+}
+
+static int
+u3g_huawei_init(struct usb_device *udev)
+{
+ struct usb_device_request 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);
+
+ if (usbd_do_request_flags(udev, NULL, &req,
+ NULL, 0, NULL, USB_MS_HZ)) {
+ /* ignore any errors */
+ }
+ return (0);
+}
+
+static int
+u3g_huawei_is_cdce(uint16_t idVendor, uint8_t bInterfaceSubClass,
+ uint8_t bInterfaceProtocol)
+{
+ /*
+ * This function returns non-zero if the interface being
+ * probed is of type CDC ethernet, which the U3G driver should
+ * not attach to. See sys/dev/usb/net/if_cdce.c for matching
+ * entries.
+ */
+ if (idVendor != USB_VENDOR_HUAWEI)
+ goto done;
+
+ switch (bInterfaceSubClass) {
+ case 0x02:
+ switch (bInterfaceProtocol) {
+ case 0x16:
+ case 0x46:
+ case 0x76:
+ return (1);
+ default:
+ break;
+ }
+ break;
+ case 0x03:
+ switch (bInterfaceProtocol) {
+ case 0x16:
+ return (1);
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+done:
+ return (0);
+}
+
+static void
+u3g_sael_m460_init(struct usb_device *udev)
+{
+ static const uint8_t setup[][24] = {
+ { 0x41, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
+ { 0x41, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 },
+ { 0x41, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
+ { 0xc1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02 },
+ { 0xc1, 0x08, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 },
+ { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 },
+ { 0xc1, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 },
+ { 0x41, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 },
+ { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 },
+ { 0x41, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00 },
+ { 0x41, 0x19, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x11, 0x13 },
+ { 0x41, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00 },
+ { 0x41, 0x12, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 },
+ { 0x41, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 },
+ { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 },
+ { 0x41, 0x03, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00 },
+ { 0x41, 0x19, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x11, 0x13 },
+ { 0x41, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00 },
+ { 0x41, 0x07, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00 },
+ };
+
+ struct usb_device_request req;
+ usb_error_t err;
+ uint16_t len;
+ uint8_t buf[0x300];
+ uint8_t n;
+
+ DPRINTFN(1, "\n");
+
+ if (usbd_req_set_alt_interface_no(udev, NULL, 0, 0)) {
+ DPRINTFN(0, "Alt setting 0 failed\n");
+ return;
+ }
+
+ for (n = 0; n != nitems(setup); n++) {
+ memcpy(&req, setup[n], sizeof(req));
+
+ len = UGETW(req.wLength);
+ if (req.bmRequestType & UE_DIR_IN) {
+ if (len > sizeof(buf)) {
+ DPRINTFN(0, "too small buffer\n");
+ continue;
+ }
+ err = usbd_do_request(udev, NULL, &req, buf);
+ } else {
+ if (len > (sizeof(setup[0]) - 8)) {
+ DPRINTFN(0, "too small buffer\n");
+ continue;
+ }
+ err = usbd_do_request(udev, NULL, &req,
+ __DECONST(uint8_t *, &setup[n][8]));
+ }
+ if (err) {
+ DPRINTFN(1, "request %u failed\n",
+ (unsigned)n);
+ /*
+ * Some of the requests will fail. Stop doing
+ * requests when we are getting timeouts so
+ * that we don't block the explore/attach
+ * thread forever.
+ */
+ if (err == USB_ERR_TIMEOUT)
+ break;
+ }
+ }
+}
+
+/*
+ * The following function 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.
+ */
+static void
+u3g_test_autoinst(void *arg, struct usb_device *udev,
+ struct usb_attach_arg *uaa)
+{
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *id;
+ int error;
+ unsigned long method;
+
+ if (uaa->dev_state != UAA_DEV_READY)
+ return;
+
+ iface = usbd_get_iface(udev, 0);
+ if (iface == NULL)
+ return;
+ id = iface->idesc;
+ if (id == NULL || id->bInterfaceClass != UICLASS_MASS)
+ return;
+
+ if (usb_test_quirk(uaa, UQ_MSC_EJECT_HUAWEI))
+ method = U3GINIT_HUAWEI;
+ else if (usb_test_quirk(uaa, UQ_MSC_EJECT_SIERRA))
+ method = U3GINIT_SIERRA;
+ else if (usb_test_quirk(uaa, UQ_MSC_EJECT_SCSIEJECT))
+ method = U3GINIT_SCSIEJECT;
+ else if (usb_test_quirk(uaa, UQ_MSC_EJECT_REZERO))
+ method = U3GINIT_REZERO;
+ else if (usb_test_quirk(uaa, UQ_MSC_EJECT_ZTESTOR))
+ method = U3GINIT_ZTESTOR;
+ else if (usb_test_quirk(uaa, UQ_MSC_EJECT_CMOTECH))
+ method = U3GINIT_CMOTECH;
+ else if (usb_test_quirk(uaa, UQ_MSC_EJECT_WAIT))
+ method = U3GINIT_WAIT;
+ else if (usb_test_quirk(uaa, UQ_MSC_EJECT_HUAWEISCSI))
+ method = U3GINIT_HUAWEISCSI;
+ else if (usb_test_quirk(uaa, UQ_MSC_EJECT_HUAWEISCSI2))
+ method = U3GINIT_HUAWEISCSI2;
+ else if (usb_test_quirk(uaa, UQ_MSC_EJECT_HUAWEISCSI3))
+ method = U3GINIT_HUAWEISCSI3;
+ else if (usb_test_quirk(uaa, UQ_MSC_EJECT_HUAWEISCSI4))
+ method = U3GINIT_HUAWEISCSI4;
+ else if (usb_test_quirk(uaa, UQ_MSC_EJECT_TCT))
+ method = U3GINIT_TCT;
+ else if (usbd_lookup_id_by_uaa(u3g_devs, sizeof(u3g_devs), uaa) == 0)
+ method = USB_GET_DRIVER_INFO(uaa);
+ else
+ return; /* no device match */
+
+ if (bootverbose) {
+ printf("Ejecting %s %s using method %ld\n",
+ usb_get_manufacturer(udev),
+ usb_get_product(udev), method);
+ }
+
+ switch (method) {
+ case U3GINIT_HUAWEI:
+ error = u3g_huawei_init(udev);
+ break;
+ case U3GINIT_HUAWEISCSI:
+ error = usb_msc_eject(udev, 0, MSC_EJECT_HUAWEI);
+ break;
+ case U3GINIT_HUAWEISCSI2:
+ error = usb_msc_eject(udev, 0, MSC_EJECT_HUAWEI2);
+ break;
+ case U3GINIT_HUAWEISCSI3:
+ error = usb_msc_eject(udev, 0, MSC_EJECT_HUAWEI3);
+ break;
+ case U3GINIT_HUAWEISCSI4:
+ error = usb_msc_eject(udev, 0, MSC_EJECT_HUAWEI4);
+ break;
+ case U3GINIT_SCSIEJECT:
+ error = usb_msc_eject(udev, 0, MSC_EJECT_STOPUNIT);
+ break;
+ case U3GINIT_REZERO:
+ error = usb_msc_eject(udev, 0, MSC_EJECT_REZERO);
+ break;
+ case U3GINIT_ZTESTOR:
+ error = usb_msc_eject(udev, 0, MSC_EJECT_STOPUNIT);
+ if (error == 0)
+ error = usb_msc_eject(udev, 0, MSC_EJECT_ZTESTOR);
+ break;
+ case U3GINIT_CMOTECH:
+ error = usb_msc_eject(udev, 0, MSC_EJECT_CMOTECH);
+ break;
+ case U3GINIT_TCT:
+ error = usb_msc_eject(udev, 0, MSC_EJECT_TCT);
+ break;
+ case U3GINIT_SIERRA:
+ error = u3g_sierra_init(udev);
+ break;
+ case U3GINIT_WAIT:
+ /* Just pretend we ejected, the card will timeout */
+ error = 0;
+ break;
+ default:
+ /* no 3G eject quirks */
+ error = EOPNOTSUPP;
+ break;
+ }
+ if (error == 0) {
+ /* success, mark the udev as disappearing */
+ uaa->dev_state = UAA_DEV_EJECTING;
+ }
+}
+
+static int
+u3g_driver_loaded(struct module *mod, int what, void *arg)
+{
+ switch (what) {
+ case MOD_LOAD:
+ /* register our autoinstall handler */
+ u3g_etag = EVENTHANDLER_REGISTER(usb_dev_configured,
+ u3g_test_autoinst, NULL, EVENTHANDLER_PRI_ANY);
+ break;
+ case MOD_UNLOAD:
+ EVENTHANDLER_DEREGISTER(usb_dev_configured, u3g_etag);
+ break;
+ default:
+ return (EOPNOTSUPP);
+ }
+ return (0);
+}
+
+static int
+u3g_probe(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != U3G_CONFIG_INDEX) {
+ return (ENXIO);
+ }
+ if (uaa->info.bInterfaceClass != UICLASS_VENDOR) {
+ return (ENXIO);
+ }
+ if (u3g_huawei_is_cdce(uaa->info.idVendor, uaa->info.bInterfaceSubClass,
+ uaa->info.bInterfaceProtocol)) {
+ return (ENXIO);
+ }
+ return (usbd_lookup_id_by_uaa(u3g_devs, sizeof(u3g_devs), uaa));
+}
+
+static int
+u3g_attach(device_t dev)
+{
+ struct usb_config u3g_config_tmp[U3G_N_TRANSFER];
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct u3g_softc *sc = device_get_softc(dev);
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *id;
+ uint32_t iface_valid;
+ int error, type, nports;
+ int ep, n;
+ uint8_t i;
+
+ DPRINTF("sc=%p\n", sc);
+
+ type = USB_GET_DRIVER_INFO(uaa);
+ if (type == U3GINIT_SAEL_M460
+ || usb_test_quirk(uaa, UQ_MSC_EJECT_SAEL_M460)) {
+ u3g_sael_m460_init(uaa->device);
+ }
+
+ /* copy in USB config */
+ for (n = 0; n != U3G_N_TRANSFER; n++)
+ u3g_config_tmp[n] = u3g_config[n];
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "u3g", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ sc->sc_udev = uaa->device;
+
+ /* Claim all interfaces on the device */
+ iface_valid = 0;
+ for (i = uaa->info.bIfaceIndex; i < USB_IFACE_MAX; i++) {
+ iface = usbd_get_iface(uaa->device, i);
+ if (iface == NULL)
+ break;
+ id = usbd_get_interface_descriptor(iface);
+ if (id == NULL || id->bInterfaceClass != UICLASS_VENDOR)
+ continue;
+ if (u3g_huawei_is_cdce(uaa->info.idVendor,
+ id->bInterfaceSubClass, id->bInterfaceProtocol))
+ continue;
+ usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex);
+ iface_valid |= (1<<i);
+ }
+
+ i = 0; /* interface index */
+ ep = 0; /* endpoint index */
+ nports = 0; /* number of ports */
+ while (i < USB_IFACE_MAX) {
+ if ((iface_valid & (1<<i)) == 0) {
+ i++;
+ continue;
+ }
+
+ /* update BULK endpoint index */
+ for (n = 0; n < U3G_N_TRANSFER; n++)
+ u3g_config_tmp[n].ep_index = ep;
+
+ /* try to allocate a set of BULK endpoints */
+ error = usbd_transfer_setup(uaa->device, &i,
+ sc->sc_xfer[nports], u3g_config_tmp, U3G_N_TRANSFER,
+ &sc->sc_ucom[nports], &sc->sc_mtx);
+ if (error) {
+ /* next interface */
+ i++;
+ ep = 0;
+ continue;
+ }
+
+ iface = usbd_get_iface(uaa->device, i);
+ id = usbd_get_interface_descriptor(iface);
+ sc->sc_iface[nports] = id->bInterfaceNumber;
+
+ if (bootverbose && sc->sc_xfer[nports][U3G_INTR]) {
+ device_printf(dev, "port %d supports modem control\n",
+ nports);
+ }
+
+ /* set stall by default */
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(sc->sc_xfer[nports][U3G_BULK_WR]);
+ usbd_xfer_set_stall(sc->sc_xfer[nports][U3G_BULK_RD]);
+ mtx_unlock(&sc->sc_mtx);
+
+ nports++; /* found one port */
+ ep++;
+ if (nports == U3G_MAXPORTS)
+ break;
+ }
+ if (nports == 0) {
+ device_printf(dev, "no ports found\n");
+ goto detach;
+ }
+ sc->sc_numports = nports;
+
+ error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom,
+ sc->sc_numports, sc, &u3g_callback, &sc->sc_mtx);
+ if (error) {
+ DPRINTF("ucom_attach failed\n");
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+ device_printf(dev, "Found %u port%s.\n", sc->sc_numports,
+ sc->sc_numports > 1 ? "s":"");
+
+ return (0);
+
+detach:
+ u3g_detach(dev);
+ return (ENXIO);
+}
+
+static int
+u3g_detach(device_t dev)
+{
+ struct u3g_softc *sc = device_get_softc(dev);
+ uint8_t subunit;
+
+ DPRINTF("sc=%p\n", sc);
+
+ /* NOTE: It is not dangerous to detach more ports than attached! */
+ ucom_detach(&sc->sc_super_ucom, sc->sc_ucom);
+
+ for (subunit = 0; subunit != U3G_MAXPORTS; subunit++)
+ usbd_transfer_unsetup(sc->sc_xfer[subunit], U3G_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ u3g_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(u3g);
+
+static void
+u3g_free_softc(struct u3g_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+u3g_free(struct ucom_softc *ucom)
+{
+ u3g_free_softc(ucom->sc_parent);
+}
+
+static void
+u3g_start_read(struct ucom_softc *ucom)
+{
+ struct u3g_softc *sc = ucom->sc_parent;
+
+ /* start interrupt endpoint (if configured) */
+ usbd_transfer_start(sc->sc_xfer[ucom->sc_subunit][U3G_INTR]);
+
+ /* start read endpoint */
+ usbd_transfer_start(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_RD]);
+}
+
+static void
+u3g_stop_read(struct ucom_softc *ucom)
+{
+ struct u3g_softc *sc = ucom->sc_parent;
+
+ /* stop interrupt endpoint (if configured) */
+ usbd_transfer_stop(sc->sc_xfer[ucom->sc_subunit][U3G_INTR]);
+
+ /* stop read endpoint */
+ usbd_transfer_stop(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_RD]);
+}
+
+static void
+u3g_start_write(struct ucom_softc *ucom)
+{
+ struct u3g_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_WR]);
+}
+
+static void
+u3g_stop_write(struct ucom_softc *ucom)
+{
+ struct u3g_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[ucom->sc_subunit][U3G_BULK_WR]);
+}
+
+static void
+u3g_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ucom_softc *ucom = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+ uint32_t frame;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+tr_setup:
+ for (frame = 0; frame != U3G_TXFRAMES; frame++) {
+ usbd_xfer_set_frame_offset(xfer, frame * U3G_TXSIZE, frame);
+
+ pc = usbd_xfer_get_frame(xfer, frame);
+ if (ucom_get_data(ucom, pc, 0, U3G_TXSIZE, &actlen) == 0)
+ break;
+ usbd_xfer_set_frame_len(xfer, frame, actlen);
+ }
+ if (frame != 0) {
+ usbd_xfer_set_frames(xfer, frame);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* do a builtin clear-stall */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+u3g_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ucom_softc *ucom = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(ucom, pc, 0, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* do a builtin clear-stall */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+u3g_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct u3g_softc *sc = ucom->sc_parent;
+
+ /* XXX Note: sc_lsr is always zero */
+ *lsr = sc->sc_lsr[ucom->sc_subunit];
+ *msr = sc->sc_msr[ucom->sc_subunit];
+}
+
+static void
+u3g_cfg_set_line(struct ucom_softc *ucom)
+{
+ struct u3g_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
+ USETW(req.wValue, sc->sc_line[ucom->sc_subunit]);
+ req.wIndex[0] = sc->sc_iface[ucom->sc_subunit];
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, ucom,
+ &req, NULL, 0, 1000);
+}
+
+static void
+u3g_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct u3g_softc *sc = ucom->sc_parent;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ if (onoff)
+ sc->sc_line[ucom->sc_subunit] |= UCDC_LINE_DTR;
+ else
+ sc->sc_line[ucom->sc_subunit] &= ~UCDC_LINE_DTR;
+
+ u3g_cfg_set_line(ucom);
+}
+
+static void
+u3g_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct u3g_softc *sc = ucom->sc_parent;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ if (onoff)
+ sc->sc_line[ucom->sc_subunit] |= UCDC_LINE_RTS;
+ else
+ sc->sc_line[ucom->sc_subunit] &= ~UCDC_LINE_RTS;
+
+ u3g_cfg_set_line(ucom);
+}
+
+static void
+u3g_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ucom_softc *ucom = usbd_xfer_softc(xfer);
+ struct u3g_softc *sc = ucom->sc_parent;
+ struct usb_page_cache *pc;
+ struct usb_cdc_notification pkt;
+ int actlen;
+ uint16_t wLen;
+ uint8_t mstatus;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (actlen < 8) { /* usb_cdc_notification with 2 data bytes */
+ DPRINTF("message too short (expected 8, received %d)\n", actlen);
+ goto tr_setup;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &pkt, actlen);
+
+ wLen = UGETW(pkt.wLength);
+ if (wLen < 2) {
+ DPRINTF("message too short (expected 2 data bytes, received %d)\n", wLen);
+ goto tr_setup;
+ }
+
+ if (pkt.bmRequestType == UCDC_NOTIFICATION
+ && pkt.bNotification == UCDC_N_SERIAL_STATE) {
+ /*
+ * Set the serial state in ucom driver based on
+ * the bits from the notify message
+ */
+ DPRINTF("notify bytes = 0x%02x, 0x%02x\n",
+ pkt.data[0], pkt.data[1]);
+
+ /* currently, lsr is always zero. */
+ sc->sc_lsr[ucom->sc_subunit] = 0;
+ sc->sc_msr[ucom->sc_subunit] = 0;
+
+ mstatus = pkt.data[0];
+
+ if (mstatus & UCDC_N_SERIAL_RI)
+ sc->sc_msr[ucom->sc_subunit] |= SER_RI;
+ if (mstatus & UCDC_N_SERIAL_DSR)
+ sc->sc_msr[ucom->sc_subunit] |= SER_DSR;
+ if (mstatus & UCDC_N_SERIAL_DCD)
+ sc->sc_msr[ucom->sc_subunit] |= SER_DCD;
+ ucom_status_change(ucom);
+ }
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+u3g_poll(struct ucom_softc *ucom)
+{
+ struct u3g_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_xfer[ucom->sc_subunit], U3G_N_TRANSFER);
+}
diff --git a/sys/dev/usb/serial/uark.c b/sys/dev/usb/serial/uark.c
new file mode 100644
index 000000000000..ac71787e767d
--- /dev/null
+++ b/sys/dev/usb/serial/uark.c
@@ -0,0 +1,463 @@
+/* $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.
+ */
+
+/*
+ * NOTE: all function names beginning like "uark_cfg_" can only
+ * be called from within the config thread function !
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR usb_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#define UARK_BUF_SIZE 1024 /* bytes */
+
+#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
+
+enum {
+ UARK_BULK_DT_WR,
+ UARK_BULK_DT_RD,
+ UARK_N_TRANSFER,
+};
+
+struct uark_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+
+ struct usb_xfer *sc_xfer[UARK_N_TRANSFER];
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+
+ uint8_t sc_msr;
+ uint8_t sc_lsr;
+};
+
+/* prototypes */
+
+static device_probe_t uark_probe;
+static device_attach_t uark_attach;
+static device_detach_t uark_detach;
+static void uark_free_softc(struct uark_softc *);
+
+static usb_callback_t uark_bulk_write_callback;
+static usb_callback_t uark_bulk_read_callback;
+
+static void uark_free(struct ucom_softc *);
+static void uark_start_read(struct ucom_softc *);
+static void uark_stop_read(struct ucom_softc *);
+static void uark_start_write(struct ucom_softc *);
+static void uark_stop_write(struct ucom_softc *);
+static int uark_pre_param(struct ucom_softc *, struct termios *);
+static void uark_cfg_param(struct ucom_softc *, struct termios *);
+static void uark_cfg_get_status(struct ucom_softc *, uint8_t *,
+ uint8_t *);
+static void uark_cfg_set_break(struct ucom_softc *, uint8_t);
+static void uark_cfg_write(struct uark_softc *, uint16_t, uint16_t);
+static void uark_poll(struct ucom_softc *ucom);
+
+static const struct usb_config
+ uark_xfer_config[UARK_N_TRANSFER] = {
+ [UARK_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UARK_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &uark_bulk_write_callback,
+ },
+
+ [UARK_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UARK_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &uark_bulk_read_callback,
+ },
+};
+
+static const struct ucom_callback uark_callback = {
+ .ucom_cfg_get_status = &uark_cfg_get_status,
+ .ucom_cfg_set_break = &uark_cfg_set_break,
+ .ucom_cfg_param = &uark_cfg_param,
+ .ucom_pre_param = &uark_pre_param,
+ .ucom_start_read = &uark_start_read,
+ .ucom_stop_read = &uark_stop_read,
+ .ucom_start_write = &uark_start_write,
+ .ucom_stop_write = &uark_stop_write,
+ .ucom_poll = &uark_poll,
+ .ucom_free = &uark_free,
+};
+
+static device_method_t uark_methods[] = {
+ /* Device methods */
+ DEVMETHOD(device_probe, uark_probe),
+ DEVMETHOD(device_attach, uark_attach),
+ DEVMETHOD(device_detach, uark_detach),
+ DEVMETHOD_END
+};
+
+static driver_t uark_driver = {
+ .name = "uark",
+ .methods = uark_methods,
+ .size = sizeof(struct uark_softc),
+};
+
+static const STRUCT_USB_HOST_ID uark_devs[] = {
+ {USB_VPI(USB_VENDOR_ARKMICRO, USB_PRODUCT_ARKMICRO_ARK3116, 0)},
+};
+
+DRIVER_MODULE(uark, uhub, uark_driver, NULL, NULL);
+MODULE_DEPEND(uark, ucom, 1, 1, 1);
+MODULE_DEPEND(uark, usb, 1, 1, 1);
+MODULE_VERSION(uark, 1);
+USB_PNP_HOST_INFO(uark_devs);
+
+static int
+uark_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != 0) {
+ return (ENXIO);
+ }
+ if (uaa->info.bIfaceIndex != UARK_IFACE_INDEX) {
+ return (ENXIO);
+ }
+ return (usbd_lookup_id_by_uaa(uark_devs, sizeof(uark_devs), uaa));
+}
+
+static int
+uark_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct uark_softc *sc = device_get_softc(dev);
+ int32_t error;
+ uint8_t iface_index;
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "uark", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ sc->sc_udev = uaa->device;
+
+ iface_index = UARK_IFACE_INDEX;
+ error = usbd_transfer_setup
+ (uaa->device, &iface_index, sc->sc_xfer,
+ uark_xfer_config, UARK_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error) {
+ device_printf(dev, "allocating control USB "
+ "transfers failed\n");
+ goto detach;
+ }
+ /* clear stall at first run */
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(sc->sc_xfer[UARK_BULK_DT_WR]);
+ usbd_xfer_set_stall(sc->sc_xfer[UARK_BULK_DT_RD]);
+ mtx_unlock(&sc->sc_mtx);
+
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &uark_callback, &sc->sc_mtx);
+ if (error) {
+ DPRINTF("ucom_attach failed\n");
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ return (0); /* success */
+
+detach:
+ uark_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+uark_detach(device_t dev)
+{
+ struct uark_softc *sc = device_get_softc(dev);
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, UARK_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ uark_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(uark);
+
+static void
+uark_free_softc(struct uark_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+uark_free(struct ucom_softc *ucom)
+{
+ uark_free_softc(ucom->sc_parent);
+}
+
+static void
+uark_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uark_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(&sc->sc_ucom, pc, 0,
+ UARK_BUF_SIZE, &actlen)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uark_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uark_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uark_start_read(struct ucom_softc *ucom)
+{
+ struct uark_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UARK_BULK_DT_RD]);
+}
+
+static void
+uark_stop_read(struct ucom_softc *ucom)
+{
+ struct uark_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UARK_BULK_DT_RD]);
+}
+
+static void
+uark_start_write(struct ucom_softc *ucom)
+{
+ struct uark_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UARK_BULK_DT_WR]);
+}
+
+static void
+uark_stop_write(struct ucom_softc *ucom)
+{
+ struct uark_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UARK_BULK_DT_WR]);
+}
+
+static int
+uark_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+ if ((t->c_ospeed < 300) || (t->c_ospeed > 115200))
+ return (EINVAL);
+ return (0);
+}
+
+static void
+uark_cfg_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct uark_softc *sc = ucom->sc_parent;
+ uint32_t speed = t->c_ospeed;
+ uint16_t data;
+
+ /*
+ * NOTE: When reverse computing the baud rate from the "data" all
+ * allowed baud rates are within 3% of the initial baud rate.
+ */
+ data = (UARK_BAUD_REF + (speed / 2)) / speed;
+
+ uark_cfg_write(sc, 3, 0x83);
+ uark_cfg_write(sc, 0, data & 0xFF);
+ uark_cfg_write(sc, 1, data >> 8);
+ uark_cfg_write(sc, 3, 0x03);
+
+ if (t->c_cflag & CSTOPB)
+ data = UARK_STOP_BITS_2;
+ else
+ data = UARK_STOP_BITS_1;
+
+ if (t->c_cflag & PARENB) {
+ if (t->c_cflag & PARODD)
+ data |= UARK_PARITY_ODD;
+ else
+ data |= UARK_PARITY_EVEN;
+ } else
+ data |= UARK_PARITY_NONE;
+
+ switch (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;
+ default:
+ case CS8:
+ data |= UARK_SET_DATA_BITS(8);
+ break;
+ }
+ uark_cfg_write(sc, 3, 0x00);
+ uark_cfg_write(sc, 3, data);
+}
+
+static void
+uark_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct uark_softc *sc = ucom->sc_parent;
+
+ /* XXX Note: sc_lsr is always zero */
+ *lsr = sc->sc_lsr;
+ *msr = sc->sc_msr;
+}
+
+static void
+uark_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uark_softc *sc = ucom->sc_parent;
+
+ DPRINTF("onoff=%d\n", onoff);
+
+ uark_cfg_write(sc, 4, onoff ? 0x01 : 0x00);
+}
+
+static void
+uark_cfg_write(struct uark_softc *sc, uint16_t index, uint16_t value)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+
+ req.bmRequestType = UARK_WRITE;
+ req.bRequest = UARK_REQUEST;
+ USETW(req.wValue, value);
+ USETW(req.wIndex, index);
+ USETW(req.wLength, 0);
+
+ err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+ if (err) {
+ DPRINTFN(0, "device request failed, err=%s "
+ "(ignored)\n", usbd_errstr(err));
+ }
+}
+
+static void
+uark_poll(struct ucom_softc *ucom)
+{
+ struct uark_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_xfer, UARK_N_TRANSFER);
+}
diff --git a/sys/dev/usb/serial/ubsa.c b/sys/dev/usb/serial/ubsa.c
new file mode 100644
index 000000000000..38782d5aef11
--- /dev/null
+++ b/sys/dev/usb/serial/ubsa.c
@@ -0,0 +1,692 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ */
+
+/*-
+ * 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.
+ *
+ * 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR ubsa_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#ifdef USB_DEBUG
+static int ubsa_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ubsa, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB ubsa");
+SYSCTL_INT(_hw_usb_ubsa, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &ubsa_debug, 0, "ubsa debug level");
+#endif
+
+#define UBSA_BSIZE 1024 /* bytes */
+
+#define UBSA_CONFIG_INDEX 0
+#define UBSA_IFACE_INDEX 0
+
+#define UBSA_REG_BAUDRATE 0x00
+#define UBSA_REG_STOP_BITS 0x01
+#define UBSA_REG_DATA_BITS 0x02
+#define UBSA_REG_PARITY 0x03
+#define UBSA_REG_DTR 0x0A
+#define UBSA_REG_RTS 0x0B
+#define UBSA_REG_BREAK 0x0C
+#define UBSA_REG_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 */
+
+enum {
+ UBSA_BULK_DT_WR,
+ UBSA_BULK_DT_RD,
+ UBSA_INTR_DT_RD,
+ UBSA_N_TRANSFER,
+};
+
+struct ubsa_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+
+ struct usb_xfer *sc_xfer[UBSA_N_TRANSFER];
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+
+ uint8_t sc_iface_no; /* interface number */
+ uint8_t sc_iface_index; /* interface index */
+ uint8_t sc_lsr; /* local status register */
+ uint8_t sc_msr; /* UBSA status register */
+};
+
+static device_probe_t ubsa_probe;
+static device_attach_t ubsa_attach;
+static device_detach_t ubsa_detach;
+static void ubsa_free_softc(struct ubsa_softc *);
+
+static usb_callback_t ubsa_write_callback;
+static usb_callback_t ubsa_read_callback;
+static usb_callback_t ubsa_intr_callback;
+
+static void ubsa_cfg_request(struct ubsa_softc *, uint8_t, uint16_t);
+static void ubsa_free(struct ucom_softc *);
+static void ubsa_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void ubsa_cfg_set_rts(struct ucom_softc *, uint8_t);
+static void ubsa_cfg_set_break(struct ucom_softc *, uint8_t);
+static int ubsa_pre_param(struct ucom_softc *, struct termios *);
+static void ubsa_cfg_param(struct ucom_softc *, struct termios *);
+static void ubsa_start_read(struct ucom_softc *);
+static void ubsa_stop_read(struct ucom_softc *);
+static void ubsa_start_write(struct ucom_softc *);
+static void ubsa_stop_write(struct ucom_softc *);
+static void ubsa_cfg_get_status(struct ucom_softc *, uint8_t *,
+ uint8_t *);
+static void ubsa_poll(struct ucom_softc *ucom);
+
+static const struct usb_config ubsa_config[UBSA_N_TRANSFER] = {
+ [UBSA_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UBSA_BSIZE, /* bytes */
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &ubsa_write_callback,
+ },
+
+ [UBSA_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UBSA_BSIZE, /* bytes */
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &ubsa_read_callback,
+ },
+
+ [UBSA_INTR_DT_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &ubsa_intr_callback,
+ },
+};
+
+static const struct ucom_callback ubsa_callback = {
+ .ucom_cfg_get_status = &ubsa_cfg_get_status,
+ .ucom_cfg_set_dtr = &ubsa_cfg_set_dtr,
+ .ucom_cfg_set_rts = &ubsa_cfg_set_rts,
+ .ucom_cfg_set_break = &ubsa_cfg_set_break,
+ .ucom_cfg_param = &ubsa_cfg_param,
+ .ucom_pre_param = &ubsa_pre_param,
+ .ucom_start_read = &ubsa_start_read,
+ .ucom_stop_read = &ubsa_stop_read,
+ .ucom_start_write = &ubsa_start_write,
+ .ucom_stop_write = &ubsa_stop_write,
+ .ucom_poll = &ubsa_poll,
+ .ucom_free = &ubsa_free,
+};
+
+static const STRUCT_USB_HOST_ID ubsa_devs[] = {
+ /* AnyData ADU-500A */
+ {USB_VPI(USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_500A, 0)},
+ /* AnyData ADU-E100A/H */
+ {USB_VPI(USB_VENDOR_ANYDATA, USB_PRODUCT_ANYDATA_ADU_E100X, 0)},
+ /* Axesstel MV100H */
+ {USB_VPI(USB_VENDOR_AXESSTEL, USB_PRODUCT_AXESSTEL_DATAMODEM, 0)},
+ /* BELKIN F5U103 */
+ {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U103, 0)},
+ /* BELKIN F5U120 */
+ {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U120, 0)},
+ /* GoHubs GO-COM232 */
+ {USB_VPI(USB_VENDOR_ETEK, USB_PRODUCT_ETEK_1COM, 0)},
+ /* GoHubs GO-COM232 */
+ {USB_VPI(USB_VENDOR_GOHUBS, USB_PRODUCT_GOHUBS_GOCOM232, 0)},
+ /* Peracom */
+ {USB_VPI(USB_VENDOR_PERACOM, USB_PRODUCT_PERACOM_SERIAL1, 0)},
+};
+
+static device_method_t ubsa_methods[] = {
+ DEVMETHOD(device_probe, ubsa_probe),
+ DEVMETHOD(device_attach, ubsa_attach),
+ DEVMETHOD(device_detach, ubsa_detach),
+ DEVMETHOD_END
+};
+
+static driver_t ubsa_driver = {
+ .name = "ubsa",
+ .methods = ubsa_methods,
+ .size = sizeof(struct ubsa_softc),
+};
+
+DRIVER_MODULE(ubsa, uhub, ubsa_driver, NULL, NULL);
+MODULE_DEPEND(ubsa, ucom, 1, 1, 1);
+MODULE_DEPEND(ubsa, usb, 1, 1, 1);
+MODULE_VERSION(ubsa, 1);
+USB_PNP_HOST_INFO(ubsa_devs);
+
+static int
+ubsa_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != UBSA_CONFIG_INDEX) {
+ return (ENXIO);
+ }
+ if (uaa->info.bIfaceIndex != UBSA_IFACE_INDEX) {
+ return (ENXIO);
+ }
+ return (usbd_lookup_id_by_uaa(ubsa_devs, sizeof(ubsa_devs), uaa));
+}
+
+static int
+ubsa_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct ubsa_softc *sc = device_get_softc(dev);
+ int error;
+
+ DPRINTF("sc=%p\n", sc);
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "ubsa", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ sc->sc_udev = uaa->device;
+ sc->sc_iface_no = uaa->info.bIfaceNum;
+ sc->sc_iface_index = UBSA_IFACE_INDEX;
+
+ error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index,
+ sc->sc_xfer, ubsa_config, UBSA_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error) {
+ DPRINTF("could not allocate all pipes\n");
+ goto detach;
+ }
+ /* clear stall at first run */
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(sc->sc_xfer[UBSA_BULK_DT_WR]);
+ usbd_xfer_set_stall(sc->sc_xfer[UBSA_BULK_DT_RD]);
+ mtx_unlock(&sc->sc_mtx);
+
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &ubsa_callback, &sc->sc_mtx);
+ if (error) {
+ DPRINTF("ucom_attach failed\n");
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ return (0);
+
+detach:
+ ubsa_detach(dev);
+ return (ENXIO);
+}
+
+static int
+ubsa_detach(device_t dev)
+{
+ struct ubsa_softc *sc = device_get_softc(dev);
+
+ DPRINTF("sc=%p\n", sc);
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, UBSA_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ ubsa_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(ubsa);
+
+static void
+ubsa_free_softc(struct ubsa_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+ubsa_free(struct ucom_softc *ucom)
+{
+ ubsa_free_softc(ucom->sc_parent);
+}
+
+static void
+ubsa_cfg_request(struct ubsa_softc *sc, uint8_t index, uint16_t value)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = index;
+ USETW(req.wValue, value);
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+ if (err) {
+ DPRINTFN(0, "device request failed, err=%s "
+ "(ignored)\n", usbd_errstr(err));
+ }
+}
+
+static void
+ubsa_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct ubsa_softc *sc = ucom->sc_parent;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ ubsa_cfg_request(sc, UBSA_REG_DTR, onoff ? 1 : 0);
+}
+
+static void
+ubsa_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct ubsa_softc *sc = ucom->sc_parent;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ ubsa_cfg_request(sc, UBSA_REG_RTS, onoff ? 1 : 0);
+}
+
+static void
+ubsa_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct ubsa_softc *sc = ucom->sc_parent;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ ubsa_cfg_request(sc, UBSA_REG_BREAK, onoff ? 1 : 0);
+}
+
+static int
+ubsa_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+
+ DPRINTF("sc = %p\n", ucom->sc_parent);
+
+ switch (t->c_ospeed) {
+ case B0:
+ case B300:
+ case B600:
+ case B1200:
+ case B2400:
+ case B4800:
+ case B9600:
+ case B19200:
+ case B38400:
+ case B57600:
+ case B115200:
+ case B230400:
+ break;
+ default:
+ return (EINVAL);
+ }
+ return (0);
+}
+
+static void
+ubsa_cfg_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct ubsa_softc *sc = ucom->sc_parent;
+ uint16_t value = 0;
+
+ DPRINTF("sc = %p\n", sc);
+
+ switch (t->c_ospeed) {
+ case B0:
+ ubsa_cfg_request(sc, UBSA_REG_FLOW_CTRL, 0);
+ ubsa_cfg_set_dtr(&sc->sc_ucom, 0);
+ ubsa_cfg_set_rts(&sc->sc_ucom, 0);
+ 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 / t->c_ospeed;
+ ubsa_cfg_request(sc, UBSA_REG_BAUDRATE, value);
+ break;
+ default:
+ return;
+ }
+
+ if (t->c_cflag & PARENB)
+ value = (t->c_cflag & PARODD) ? UBSA_PARITY_ODD : UBSA_PARITY_EVEN;
+ else
+ value = UBSA_PARITY_NONE;
+
+ ubsa_cfg_request(sc, UBSA_REG_PARITY, value);
+
+ switch (t->c_cflag & CSIZE) {
+ case CS5:
+ value = 0;
+ break;
+ case CS6:
+ value = 1;
+ break;
+ case CS7:
+ value = 2;
+ break;
+ default:
+ case CS8:
+ value = 3;
+ break;
+ }
+
+ ubsa_cfg_request(sc, UBSA_REG_DATA_BITS, value);
+
+ value = (t->c_cflag & CSTOPB) ? 1 : 0;
+
+ ubsa_cfg_request(sc, UBSA_REG_STOP_BITS, value);
+
+ value = 0;
+ if (t->c_cflag & CRTSCTS)
+ value |= UBSA_FLOW_OCTS | UBSA_FLOW_IRTS;
+
+ if (t->c_iflag & (IXON | IXOFF))
+ value |= UBSA_FLOW_OXON | UBSA_FLOW_IXON;
+
+ ubsa_cfg_request(sc, UBSA_REG_FLOW_CTRL, value);
+}
+
+static void
+ubsa_start_read(struct ucom_softc *ucom)
+{
+ struct ubsa_softc *sc = ucom->sc_parent;
+
+ /* start interrupt endpoint */
+ usbd_transfer_start(sc->sc_xfer[UBSA_INTR_DT_RD]);
+
+ /* start read endpoint */
+ usbd_transfer_start(sc->sc_xfer[UBSA_BULK_DT_RD]);
+}
+
+static void
+ubsa_stop_read(struct ucom_softc *ucom)
+{
+ struct ubsa_softc *sc = ucom->sc_parent;
+
+ /* stop interrupt endpoint */
+ usbd_transfer_stop(sc->sc_xfer[UBSA_INTR_DT_RD]);
+
+ /* stop read endpoint */
+ usbd_transfer_stop(sc->sc_xfer[UBSA_BULK_DT_RD]);
+}
+
+static void
+ubsa_start_write(struct ucom_softc *ucom)
+{
+ struct ubsa_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UBSA_BULK_DT_WR]);
+}
+
+static void
+ubsa_stop_write(struct ucom_softc *ucom)
+{
+ struct ubsa_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UBSA_BULK_DT_WR]);
+}
+
+static void
+ubsa_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct ubsa_softc *sc = ucom->sc_parent;
+
+ DPRINTF("\n");
+
+ *lsr = sc->sc_lsr;
+ *msr = sc->sc_msr;
+}
+
+static void
+ubsa_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ubsa_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(&sc->sc_ucom, pc, 0,
+ UBSA_BSIZE, &actlen)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+ubsa_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ubsa_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+ubsa_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ubsa_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint8_t buf[4];
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ if (actlen >= (int)sizeof(buf)) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, buf, sizeof(buf));
+
+ /*
+ * MSR bits need translation from ns16550 to SER_* values.
+ * LSR bits are ns16550 in hardware and ucom.
+ */
+ sc->sc_msr = 0;
+ if (buf[3] & UBSA_MSR_CTS)
+ sc->sc_msr |= SER_CTS;
+ if (buf[3] & UBSA_MSR_DCD)
+ sc->sc_msr |= SER_DCD;
+ if (buf[3] & UBSA_MSR_RI)
+ sc->sc_msr |= SER_RI;
+ if (buf[3] & UBSA_MSR_DSR)
+ sc->sc_msr |= SER_DSR;
+ sc->sc_lsr = buf[2];
+
+ DPRINTF("lsr = 0x%02x, msr = 0x%02x\n",
+ sc->sc_lsr, sc->sc_msr);
+
+ ucom_status_change(&sc->sc_ucom);
+ } else {
+ DPRINTF("ignoring short packet, %d bytes\n", actlen);
+ }
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+ubsa_poll(struct ucom_softc *ucom)
+{
+ struct ubsa_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_xfer, UBSA_N_TRANSFER);
+
+}
diff --git a/sys/dev/usb/serial/ubser.c b/sys/dev/usb/serial/ubser.c
new file mode 100644
index 000000000000..978639a809be
--- /dev/null
+++ b/sys/dev/usb/serial/ubser.c
@@ -0,0 +1,552 @@
+/*-
+ * 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 $
+ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * BWCT serial adapter driver
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR ubser_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#define UBSER_UNIT_MAX 32
+
+/* Vendor Interface Requests */
+#define VENDOR_GET_NUMSER 0x01
+#define VENDOR_SET_BREAK 0x02
+#define VENDOR_CLEAR_BREAK 0x03
+
+#ifdef USB_DEBUG
+static int ubser_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ubser, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB ubser");
+SYSCTL_INT(_hw_usb_ubser, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &ubser_debug, 0, "ubser debug level");
+#endif
+
+enum {
+ UBSER_BULK_DT_WR,
+ UBSER_BULK_DT_RD,
+ UBSER_N_TRANSFER,
+};
+
+struct ubser_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom[UBSER_UNIT_MAX];
+
+ struct usb_xfer *sc_xfer[UBSER_N_TRANSFER];
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+
+ uint16_t sc_tx_size;
+
+ uint8_t sc_numser;
+ uint8_t sc_iface_no;
+ uint8_t sc_iface_index;
+ uint8_t sc_curr_tx_unit;
+};
+
+/* prototypes */
+
+static device_probe_t ubser_probe;
+static device_attach_t ubser_attach;
+static device_detach_t ubser_detach;
+static void ubser_free_softc(struct ubser_softc *);
+
+static usb_callback_t ubser_write_callback;
+static usb_callback_t ubser_read_callback;
+
+static void ubser_free(struct ucom_softc *);
+static int ubser_pre_param(struct ucom_softc *, struct termios *);
+static void ubser_cfg_set_break(struct ucom_softc *, uint8_t);
+static void ubser_cfg_get_status(struct ucom_softc *, uint8_t *,
+ uint8_t *);
+static void ubser_start_read(struct ucom_softc *);
+static void ubser_stop_read(struct ucom_softc *);
+static void ubser_start_write(struct ucom_softc *);
+static void ubser_stop_write(struct ucom_softc *);
+static void ubser_poll(struct ucom_softc *ucom);
+
+static const struct usb_config ubser_config[UBSER_N_TRANSFER] = {
+ [UBSER_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = 0, /* use wMaxPacketSize */
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &ubser_write_callback,
+ },
+
+ [UBSER_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = 0, /* use wMaxPacketSize */
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &ubser_read_callback,
+ },
+};
+
+static const struct ucom_callback ubser_callback = {
+ .ucom_cfg_set_break = &ubser_cfg_set_break,
+ .ucom_cfg_get_status = &ubser_cfg_get_status,
+ .ucom_pre_param = &ubser_pre_param,
+ .ucom_start_read = &ubser_start_read,
+ .ucom_stop_read = &ubser_stop_read,
+ .ucom_start_write = &ubser_start_write,
+ .ucom_stop_write = &ubser_stop_write,
+ .ucom_poll = &ubser_poll,
+ .ucom_free = &ubser_free,
+};
+
+static device_method_t ubser_methods[] = {
+ DEVMETHOD(device_probe, ubser_probe),
+ DEVMETHOD(device_attach, ubser_attach),
+ DEVMETHOD(device_detach, ubser_detach),
+ DEVMETHOD_END
+};
+
+static driver_t ubser_driver = {
+ .name = "ubser",
+ .methods = ubser_methods,
+ .size = sizeof(struct ubser_softc),
+};
+
+DRIVER_MODULE(ubser, uhub, ubser_driver, NULL, NULL);
+MODULE_DEPEND(ubser, ucom, 1, 1, 1);
+MODULE_DEPEND(ubser, usb, 1, 1, 1);
+MODULE_VERSION(ubser, 1);
+
+static int
+ubser_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ /* check if this is a BWCT vendor specific ubser interface */
+ if ((strcmp(usb_get_manufacturer(uaa->device), "BWCT") == 0) &&
+ (uaa->info.bInterfaceClass == 0xff) &&
+ (uaa->info.bInterfaceSubClass == 0x00))
+ return (0);
+
+ return (ENXIO);
+}
+
+static int
+ubser_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct ubser_softc *sc = device_get_softc(dev);
+ struct usb_device_request req;
+ uint8_t n;
+ int error;
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "ubser", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ sc->sc_iface_no = uaa->info.bIfaceNum;
+ sc->sc_iface_index = uaa->info.bIfaceIndex;
+ sc->sc_udev = uaa->device;
+
+ /* get number of serials */
+ req.bmRequestType = UT_READ_VENDOR_INTERFACE;
+ req.bRequest = VENDOR_GET_NUMSER;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 1);
+ error = usbd_do_request_flags(uaa->device, NULL,
+ &req, &sc->sc_numser,
+ 0, NULL, USB_DEFAULT_TIMEOUT);
+
+ if (error || (sc->sc_numser == 0)) {
+ device_printf(dev, "failed to get number "
+ "of serial ports: %s\n",
+ usbd_errstr(error));
+ goto detach;
+ }
+ if (sc->sc_numser > UBSER_UNIT_MAX)
+ sc->sc_numser = UBSER_UNIT_MAX;
+
+ device_printf(dev, "found %i serials\n", sc->sc_numser);
+
+ error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index,
+ sc->sc_xfer, ubser_config, UBSER_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error) {
+ goto detach;
+ }
+ sc->sc_tx_size = usbd_xfer_max_len(sc->sc_xfer[UBSER_BULK_DT_WR]);
+
+ if (sc->sc_tx_size == 0) {
+ DPRINTFN(0, "invalid tx_size\n");
+ goto detach;
+ }
+ /* initialize port numbers */
+
+ for (n = 0; n < sc->sc_numser; n++) {
+ sc->sc_ucom[n].sc_portno = n;
+ }
+
+ error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom,
+ sc->sc_numser, sc, &ubser_callback, &sc->sc_mtx);
+ if (error) {
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(sc->sc_xfer[UBSER_BULK_DT_WR]);
+ usbd_xfer_set_stall(sc->sc_xfer[UBSER_BULK_DT_RD]);
+ usbd_transfer_start(sc->sc_xfer[UBSER_BULK_DT_RD]);
+ mtx_unlock(&sc->sc_mtx);
+
+ return (0); /* success */
+
+detach:
+ ubser_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+ubser_detach(device_t dev)
+{
+ struct ubser_softc *sc = device_get_softc(dev);
+
+ DPRINTF("\n");
+
+ ucom_detach(&sc->sc_super_ucom, sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, UBSER_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ ubser_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(ubser);
+
+static void
+ubser_free_softc(struct ubser_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+ubser_free(struct ucom_softc *ucom)
+{
+ ubser_free_softc(ucom->sc_parent);
+}
+
+static int
+ubser_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+ DPRINTF("\n");
+
+ /*
+ * The firmware on our devices can only do 8n1@9600bps
+ * without handshake.
+ * We refuse to accept other configurations.
+ */
+
+ /* ensure 9600bps */
+ switch (t->c_ospeed) {
+ case 9600:
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ /* 2 stop bits not possible */
+ if (t->c_cflag & CSTOPB)
+ return (EINVAL);
+
+ /* XXX parity handling not possible with current firmware */
+ if (t->c_cflag & PARENB)
+ return (EINVAL);
+
+ /* we can only do 8 data bits */
+ switch (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.
+ */
+
+ return (0);
+}
+
+static __inline void
+ubser_inc_tx_unit(struct ubser_softc *sc)
+{
+ sc->sc_curr_tx_unit++;
+ if (sc->sc_curr_tx_unit >= sc->sc_numser) {
+ sc->sc_curr_tx_unit = 0;
+ }
+}
+
+static void
+ubser_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ubser_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint8_t buf[1];
+ uint8_t first_unit = sc->sc_curr_tx_unit;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ do {
+ if (ucom_get_data(sc->sc_ucom + sc->sc_curr_tx_unit,
+ pc, 1, sc->sc_tx_size - 1,
+ &actlen)) {
+ buf[0] = sc->sc_curr_tx_unit;
+
+ usbd_copy_in(pc, 0, buf, 1);
+
+ usbd_xfer_set_frame_len(xfer, 0, actlen + 1);
+ usbd_transfer_submit(xfer);
+
+ ubser_inc_tx_unit(sc); /* round robin */
+
+ break;
+ }
+ ubser_inc_tx_unit(sc);
+
+ } while (sc->sc_curr_tx_unit != first_unit);
+
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+ubser_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ubser_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint8_t buf[1];
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (actlen < 1) {
+ DPRINTF("invalid actlen=0!\n");
+ goto tr_setup;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, buf, 1);
+
+ if (buf[0] >= sc->sc_numser) {
+ DPRINTF("invalid serial number!\n");
+ goto tr_setup;
+ }
+ ucom_put_data(sc->sc_ucom + buf[0], pc, 1, actlen - 1);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+ubser_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct ubser_softc *sc = ucom->sc_parent;
+ uint8_t x = ucom->sc_portno;
+ struct usb_device_request req;
+ usb_error_t err;
+
+ if (onoff) {
+ req.bmRequestType = UT_READ_VENDOR_INTERFACE;
+ req.bRequest = VENDOR_SET_BREAK;
+ req.wValue[0] = x;
+ req.wValue[1] = 0;
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ err = ucom_cfg_do_request(sc->sc_udev, ucom,
+ &req, NULL, 0, 1000);
+ if (err) {
+ DPRINTFN(0, "send break failed, error=%s\n",
+ usbd_errstr(err));
+ }
+ }
+}
+
+static void
+ubser_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ /* fake status bits */
+ *lsr = 0;
+ *msr = SER_DCD;
+}
+
+static void
+ubser_start_read(struct ucom_softc *ucom)
+{
+ struct ubser_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UBSER_BULK_DT_RD]);
+}
+
+static void
+ubser_stop_read(struct ucom_softc *ucom)
+{
+ struct ubser_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UBSER_BULK_DT_RD]);
+}
+
+static void
+ubser_start_write(struct ucom_softc *ucom)
+{
+ struct ubser_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UBSER_BULK_DT_WR]);
+}
+
+static void
+ubser_stop_write(struct ucom_softc *ucom)
+{
+ struct ubser_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UBSER_BULK_DT_WR]);
+}
+
+static void
+ubser_poll(struct ucom_softc *ucom)
+{
+ struct ubser_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_xfer, UBSER_N_TRANSFER);
+}
diff --git a/sys/dev/usb/serial/uchcom.c b/sys/dev/usb/serial/uchcom.c
new file mode 100644
index 000000000000..fdc5515fa722
--- /dev/null
+++ b/sys/dev/usb/serial/uchcom.c
@@ -0,0 +1,951 @@
+/* $NetBSD: uchcom.c,v 1.1 2007/09/03 17:57:37 tshiozak Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * Driver for WinChipHead CH9102/343/341/340.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR uchcom_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#ifdef USB_DEBUG
+static int uchcom_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uchcom, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB uchcom");
+SYSCTL_INT(_hw_usb_uchcom, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &uchcom_debug, 0, "uchcom debug level");
+#endif
+
+#define UCHCOM_IFACE_INDEX 0
+#define UCHCOM_CONFIG_INDEX 0
+#define UCHCOM_SECOND_IFACE_INDEX 1
+
+#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_REQ_CH343_WRITE_REG 0xA8
+
+#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_LCR1 0x18
+#define UCHCOM_REG_LCR2 0x25
+
+#define UCHCOM_VER_20 0x20
+#define UCHCOM_VER_30 0x30
+
+#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_BRK_MASK 0x01
+#define UCHCOM_ABRK_MASK 0x10
+#define UCHCOM_CH343_BRK_MASK 0x80
+
+#define UCHCOM_LCR1_MASK 0xAF
+#define UCHCOM_LCR2_MASK 0x07
+#define UCHCOM_LCR1_RX 0x80
+#define UCHCOM_LCR1_TX 0x40
+#define UCHCOM_LCR1_PARENB 0x08
+#define UCHCOM_LCR1_CS5 0x00
+#define UCHCOM_LCR1_CS6 0x01
+#define UCHCOM_LCR1_CS7 0x02
+#define UCHCOM_LCR1_CS8 0x03
+#define UCHCOM_LCR1_STOPB 0x04
+#define UCHCOM_LCR1_PARODD 0x00
+#define UCHCOM_LCR1_PAREVEN 0x10
+#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 UCHCOM_T 0x08
+#define UCHCOM_CL 0x04
+#define UCHCOM_CH343_CT 0x80
+#define UCHCOM_CT 0x90
+
+#define UCHCOM_BULK_BUF_SIZE 1024 /* bytes */
+
+#define TYPE_CH343 1
+
+enum {
+ UCHCOM_BULK_DT_WR,
+ UCHCOM_BULK_DT_RD,
+ UCHCOM_N_TRANSFER,
+};
+
+struct uchcom_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+
+ struct usb_xfer *sc_xfer[UCHCOM_N_TRANSFER];
+ struct usb_xfer *sc_intr_xfer; /* Interrupt endpoint */
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+
+ uint8_t sc_dtr; /* local copy */
+ uint8_t sc_rts; /* local copy */
+ uint8_t sc_version;
+ uint8_t sc_msr;
+ uint8_t sc_lsr; /* local status register */
+ uint8_t sc_chiptype; /* type of chip */
+ uint8_t sc_ctrl_iface_no;
+ uint8_t sc_iface_index;
+};
+
+static const STRUCT_USB_HOST_ID uchcom_devs[] = {
+ {USB_VPI(USB_VENDOR_WCH, USB_PRODUCT_WCH_CH341SER, 0)},
+ {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH341SER, 0)},
+ {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH341SER_2, 0)},
+ {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH341SER_3, 0)},
+ {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH343SER, 0)},
+ {USB_VPI(USB_VENDOR_WCH2, USB_PRODUCT_WCH2_CH9102SER, 0)},
+};
+
+/* protypes */
+
+static void uchcom_free(struct ucom_softc *);
+static int uchcom_pre_param(struct ucom_softc *, struct termios *);
+static void uchcom_cfg_get_status(struct ucom_softc *, uint8_t *,
+ uint8_t *);
+static void uchcom_cfg_open(struct ucom_softc *ucom);
+static void uchcom_cfg_param(struct ucom_softc *, struct termios *);
+static void uchcom_cfg_set_break(struct ucom_softc *, uint8_t);
+static void uchcom_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void uchcom_cfg_set_rts(struct ucom_softc *, uint8_t);
+static void uchcom_start_read(struct ucom_softc *);
+static void uchcom_start_write(struct ucom_softc *);
+static void uchcom_stop_read(struct ucom_softc *);
+static void uchcom_stop_write(struct ucom_softc *);
+static void uchcom_update_version(struct uchcom_softc *);
+static void uchcom_convert_status(struct uchcom_softc *, uint8_t);
+static void uchcom_update_status(struct uchcom_softc *);
+static void uchcom_set_dtr_rts(struct uchcom_softc *);
+static void uchcom_calc_baudrate(struct uchcom_softc *, uint32_t, uint8_t *,
+ uint8_t *);
+static void uchcom_set_baudrate(struct uchcom_softc *, uint32_t, uint16_t);
+static void uchcom_poll(struct ucom_softc *ucom);
+
+static device_probe_t uchcom_probe;
+static device_attach_t uchcom_attach;
+static device_detach_t uchcom_detach;
+static void uchcom_free_softc(struct uchcom_softc *);
+
+static usb_callback_t uchcom_intr_callback;
+static usb_callback_t uchcom_write_callback;
+static usb_callback_t uchcom_read_callback;
+
+static const struct usb_config uchcom_config_data[UCHCOM_N_TRANSFER] = {
+ [UCHCOM_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UCHCOM_BULK_BUF_SIZE,
+ .flags = {.pipe_bof = 1,},
+ .callback = &uchcom_write_callback,
+ },
+
+ [UCHCOM_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UCHCOM_BULK_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &uchcom_read_callback,
+ },
+};
+
+static const struct usb_config uchcom_intr_config_data[1] = {
+ [0] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &uchcom_intr_callback,
+ },
+};
+
+static struct ucom_callback uchcom_callback = {
+ .ucom_cfg_get_status = &uchcom_cfg_get_status,
+ .ucom_cfg_set_dtr = &uchcom_cfg_set_dtr,
+ .ucom_cfg_set_rts = &uchcom_cfg_set_rts,
+ .ucom_cfg_set_break = &uchcom_cfg_set_break,
+ .ucom_cfg_open = &uchcom_cfg_open,
+ .ucom_cfg_param = &uchcom_cfg_param,
+ .ucom_pre_param = &uchcom_pre_param,
+ .ucom_start_read = &uchcom_start_read,
+ .ucom_stop_read = &uchcom_stop_read,
+ .ucom_start_write = &uchcom_start_write,
+ .ucom_stop_write = &uchcom_stop_write,
+ .ucom_poll = &uchcom_poll,
+ .ucom_free = &uchcom_free,
+};
+
+/* ----------------------------------------------------------------------
+ * driver entry points
+ */
+
+static int
+uchcom_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ DPRINTFN(11, "\n");
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != UCHCOM_CONFIG_INDEX) {
+ return (ENXIO);
+ }
+ if (uaa->info.bIfaceIndex != UCHCOM_IFACE_INDEX) {
+ return (ENXIO);
+ }
+ return (usbd_lookup_id_by_uaa(uchcom_devs, sizeof(uchcom_devs), uaa));
+}
+
+static int
+uchcom_attach(device_t dev)
+{
+ struct uchcom_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *id;
+ int error;
+
+ DPRINTFN(11, "\n");
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "uchcom", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ sc->sc_udev = uaa->device;
+
+ switch (uaa->info.idProduct) {
+ case USB_PRODUCT_WCH2_CH341SER:
+ device_printf(dev, "CH340 detected\n");
+ break;
+ case USB_PRODUCT_WCH2_CH341SER_2:
+ case USB_PRODUCT_WCH2_CH341SER_3:
+ device_printf(dev, "CH341 detected\n");
+ break;
+ case USB_PRODUCT_WCH2_CH343SER:
+ device_printf(dev, "CH343 detected\n");
+ break;
+ case USB_PRODUCT_WCH2_CH9102SER:
+ device_printf(dev, "CH9102 detected\n");
+ break;
+ default:
+ device_printf(dev, "New CH340/CH341/CH343/CH9102 product "
+ "0x%04x detected\n", uaa->info.idProduct);
+ break;
+ }
+
+ /* CH343/CH9102 has two interfaces. */
+ sc->sc_ctrl_iface_no = uaa->info.bIfaceNum;
+
+ iface = usbd_get_iface(uaa->device, UCHCOM_SECOND_IFACE_INDEX);
+ if (iface) {
+ id = usbd_get_interface_descriptor(iface);
+ if (id == NULL) {
+ device_printf(dev, "no interface descriptor\n");
+ goto detach;
+ }
+ sc->sc_iface_index = UCHCOM_SECOND_IFACE_INDEX;
+ usbd_set_parent_iface(uaa->device, UCHCOM_SECOND_IFACE_INDEX,
+ uaa->info.bIfaceIndex);
+ sc->sc_chiptype = TYPE_CH343;
+ } else {
+ sc->sc_iface_index = UCHCOM_IFACE_INDEX;
+ }
+
+ /* Setup all transfers. */
+ error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index,
+ sc->sc_xfer, uchcom_config_data, UCHCOM_N_TRANSFER, sc,
+ &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "could not allocate all pipes\n");
+ goto detach;
+ }
+ error = usbd_transfer_setup(uaa->device, &sc->sc_ctrl_iface_no,
+ &sc->sc_intr_xfer, uchcom_intr_config_data, 1, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "allocating USB transfers failed for "
+ "interrupt\n");
+ goto detach;
+ }
+
+ /* clear stall at first run */
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(sc->sc_xfer[UCHCOM_BULK_DT_WR]);
+ usbd_xfer_set_stall(sc->sc_xfer[UCHCOM_BULK_DT_RD]);
+ mtx_unlock(&sc->sc_mtx);
+
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &uchcom_callback, &sc->sc_mtx);
+ if (error) {
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ return (0);
+
+detach:
+ uchcom_detach(dev);
+ return (ENXIO);
+}
+
+static int
+uchcom_detach(device_t dev)
+{
+ struct uchcom_softc *sc = device_get_softc(dev);
+
+ DPRINTFN(11, "\n");
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, UCHCOM_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ uchcom_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(uchcom);
+
+static void
+uchcom_free_softc(struct uchcom_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+uchcom_free(struct ucom_softc *ucom)
+{
+ uchcom_free_softc(ucom->sc_parent);
+}
+
+/* ----------------------------------------------------------------------
+ * low level i/o
+ */
+
+static void
+uchcom_ctrl_write(struct uchcom_softc *sc, uint8_t reqno,
+ uint16_t value, uint16_t index)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = reqno;
+ USETW(req.wValue, value);
+ USETW(req.wIndex, index);
+ USETW(req.wLength, 0);
+
+ DPRINTF("WR REQ 0x%02X VAL 0x%04X IDX 0x%04X\n",
+ reqno, value, index);
+ ucom_cfg_do_request(sc->sc_udev,
+ &sc->sc_ucom, &req, NULL, 0, 1000);
+}
+
+static void
+uchcom_ctrl_read(struct uchcom_softc *sc, uint8_t reqno,
+ uint16_t value, uint16_t index, void *buf, uint16_t buflen)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = reqno;
+ USETW(req.wValue, value);
+ USETW(req.wIndex, index);
+ USETW(req.wLength, buflen);
+
+ DPRINTF("RD REQ 0x%02X VAL 0x%04X IDX 0x%04X LEN %d\n",
+ reqno, value, index, buflen);
+ ucom_cfg_do_request(sc->sc_udev,
+ &sc->sc_ucom, &req, buf, USB_SHORT_XFER_OK, 1000);
+}
+
+static void
+uchcom_write_reg(struct uchcom_softc *sc,
+ uint8_t reg1, uint8_t val1, uint8_t reg2, uint8_t val2)
+{
+ DPRINTF("0x%02X<-0x%02X, 0x%02X<-0x%02X\n",
+ (unsigned)reg1, (unsigned)val1,
+ (unsigned)reg2, (unsigned)val2);
+ uchcom_ctrl_write(
+ sc,
+ (sc->sc_chiptype != TYPE_CH343) ?
+ UCHCOM_REQ_WRITE_REG : UCHCOM_REQ_CH343_WRITE_REG,
+ reg1 | ((uint16_t)reg2 << 8), val1 | ((uint16_t)val2 << 8));
+}
+
+static void
+uchcom_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];
+
+ uchcom_ctrl_read(
+ sc, UCHCOM_REQ_READ_REG,
+ reg1 | ((uint16_t)reg2 << 8), 0, buf, sizeof(buf));
+
+ DPRINTF("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];
+}
+
+static void
+uchcom_get_version(struct uchcom_softc *sc, uint8_t *rver)
+{
+ uint8_t buf[UCHCOM_INPUT_BUF_SIZE];
+
+ uchcom_ctrl_read(sc, UCHCOM_REQ_GET_VERSION, 0, 0, buf, sizeof(buf));
+
+ if (rver)
+ *rver = buf[0];
+}
+
+static void
+uchcom_get_status(struct uchcom_softc *sc, uint8_t *rval)
+{
+ uchcom_read_reg(sc, UCHCOM_REG_STAT1, rval, UCHCOM_REG_STAT2, NULL);
+}
+
+static void
+uchcom_set_dtr_rts_10(struct uchcom_softc *sc, uint8_t val)
+{
+ uchcom_write_reg(sc, UCHCOM_REG_STAT1, val, UCHCOM_REG_STAT1, val);
+}
+
+static void
+uchcom_set_dtr_rts_20(struct uchcom_softc *sc, uint8_t val)
+{
+ uchcom_ctrl_write(sc, UCHCOM_REQ_SET_DTRRTS, val, 0);
+}
+
+/* ----------------------------------------------------------------------
+ * middle layer
+ */
+
+static void
+uchcom_update_version(struct uchcom_softc *sc)
+{
+ uchcom_get_version(sc, &sc->sc_version);
+ DPRINTF("Chip version: 0x%02x\n", sc->sc_version);
+}
+
+static void
+uchcom_convert_status(struct uchcom_softc *sc, uint8_t cur)
+{
+ cur = ~cur & 0x0F;
+ sc->sc_msr = (cur << 4) | ((sc->sc_msr >> 4) ^ cur);
+}
+
+static void
+uchcom_update_status(struct uchcom_softc *sc)
+{
+ uint8_t cur;
+
+ uchcom_get_status(sc, &cur);
+ uchcom_convert_status(sc, cur);
+}
+
+static void
+uchcom_set_dtr_rts(struct uchcom_softc *sc)
+{
+ uint8_t val = 0;
+
+ if (sc->sc_dtr)
+ val |= UCHCOM_DTR_MASK;
+ if (sc->sc_rts)
+ val |= UCHCOM_RTS_MASK;
+
+ if (sc->sc_version < UCHCOM_VER_20)
+ uchcom_set_dtr_rts_10(sc, ~val);
+ else
+ uchcom_set_dtr_rts_20(sc, ~val);
+}
+
+static void
+uchcom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uchcom_softc *sc = ucom->sc_parent;
+ uint8_t brk1;
+ uint8_t brk2;
+
+ if (sc->sc_chiptype == TYPE_CH343) {
+ brk1 = UCHCOM_CH343_BRK_MASK;
+ if (!onoff)
+ brk1 |= UCHCOM_ABRK_MASK;
+ uchcom_write_reg(sc, brk1, 0, 0, 0);
+ } else {
+ uchcom_read_reg(sc, UCHCOM_REG_BREAK1, &brk1, UCHCOM_REG_LCR1,
+ &brk2);
+ if (onoff) {
+ /* on - clear bits */
+ brk1 &= ~UCHCOM_BRK_MASK;
+ brk2 &= ~UCHCOM_LCR1_TX;
+ } else {
+ /* off - set bits */
+ brk1 |= UCHCOM_BRK_MASK;
+ brk2 |= UCHCOM_LCR1_TX;
+ }
+ uchcom_write_reg(sc, UCHCOM_REG_BREAK1, brk1, UCHCOM_REG_LCR1,
+ brk2);
+ }
+}
+
+static void
+uchcom_calc_baudrate(struct uchcom_softc *sc, uint32_t rate, uint8_t *divisor,
+ uint8_t *factor)
+{
+ uint32_t clk = 12000000;
+
+ if (rate >= 256000 && sc->sc_chiptype == TYPE_CH343)
+ *divisor = 7;
+ else if (rate > 23529) {
+ clk /= 2;
+ *divisor = 3;
+ } else if (rate > 2941) {
+ clk /= 16;
+ *divisor = 2;
+ } else if (rate > 367) {
+ clk /= 128;
+ *divisor = 1;
+ } else {
+ clk = 11719;
+ *divisor = 0;
+ }
+
+ *factor = 256 - clk / rate;
+
+ if (rate == 921600 && sc->sc_chiptype != TYPE_CH343) {
+ *divisor = 7;
+ *factor = 243;
+ }
+}
+
+static void
+uchcom_set_baudrate(struct uchcom_softc *sc, uint32_t rate, uint16_t lcr)
+{
+ uint16_t idx;
+ uint8_t factor, div;
+
+ uchcom_calc_baudrate(sc, rate, &div, &factor);
+ div |= (sc->sc_chiptype != TYPE_CH343) ? 0x80 : 0x00;
+ idx = (factor << 8) | div;
+
+ uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, lcr, idx);
+}
+
+/* ----------------------------------------------------------------------
+ * methods for ucom
+ */
+static void
+uchcom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct uchcom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("\n");
+
+ /* XXX Note: sc_lsr is always zero */
+ *lsr = sc->sc_lsr;
+ *msr = sc->sc_msr;
+}
+
+static void
+uchcom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uchcom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ sc->sc_dtr = onoff;
+ uchcom_set_dtr_rts(sc);
+}
+
+static void
+uchcom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uchcom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ sc->sc_rts = onoff;
+ uchcom_set_dtr_rts(sc);
+}
+
+static void
+uchcom_cfg_open(struct ucom_softc *ucom)
+{
+ struct uchcom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("\n");
+
+ if (sc->sc_chiptype != TYPE_CH343) {
+ /* Set default configuration. */
+ uchcom_get_version(sc, NULL);
+ uchcom_ctrl_write(sc, UCHCOM_REQ_RESET, 0, 0);
+ uchcom_write_reg(sc, UCHCOM_REG_BPS_PRE, 0x82,
+ UCHCOM_REG_BPS_DIV, 0xd9);
+ uchcom_write_reg(sc, 0x2c, 0x07, UCHCOM_REG_BPS_PAD, 0);
+ }
+ uchcom_update_version(sc);
+ uchcom_update_status(sc);
+}
+
+static int
+uchcom_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct uchcom_softc *sc = ucom->sc_parent;
+
+ /*
+ * Check requested baud rate.
+ * The CH340/CH341 can set any baud rate up to 2Mb.
+ * The CH9102/CH343 can set any baud rate up to 6Mb.
+ */
+ switch (sc->sc_chiptype) {
+ case TYPE_CH343:
+ if (t->c_ospeed <= 6000000)
+ return (0);
+ break;
+ default:
+ if (t->c_ospeed <= 2000000)
+ return (0);
+ break;
+ }
+
+ return (EIO);
+}
+
+static void
+uchcom_cfg_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct uchcom_softc *sc = ucom->sc_parent;
+ uint8_t lcr;
+
+ lcr = UCHCOM_LCR1_RX | UCHCOM_LCR1_TX;
+
+ if (t->c_cflag & CSTOPB)
+ lcr |= UCHCOM_LCR1_STOPB;
+
+ if (t->c_cflag & PARENB) {
+ lcr |= UCHCOM_LCR1_PARENB;
+ if (t->c_cflag & PARODD)
+ lcr |= UCHCOM_LCR1_PARODD;
+ else
+ lcr |= UCHCOM_LCR1_PAREVEN;
+ }
+
+ switch (t->c_cflag & CSIZE) {
+ case CS5:
+ lcr |= UCHCOM_LCR1_CS5;
+ break;
+ case CS6:
+ lcr |= UCHCOM_LCR1_CS6;
+ break;
+ case CS7:
+ lcr |= UCHCOM_LCR1_CS7;
+ break;
+ case CS8:
+ default:
+ lcr |= UCHCOM_LCR1_CS8;
+ break;
+ }
+
+ if (sc->sc_chiptype == TYPE_CH343)
+ uchcom_set_baudrate(sc, t->c_ospeed,
+ UCHCOM_T | UCHCOM_CL | UCHCOM_CH343_CT | lcr << 8);
+ else
+ uchcom_set_baudrate(sc, t->c_ospeed,
+ UCHCOM_T | UCHCOM_CL | UCHCOM_CT | lcr << 8);
+
+ uchcom_set_dtr_rts(sc);
+ uchcom_update_status(sc);
+}
+
+static void
+uchcom_start_read(struct ucom_softc *ucom)
+{
+ struct uchcom_softc *sc = ucom->sc_parent;
+
+ /* start interrupt endpoint */
+ usbd_transfer_start(sc->sc_intr_xfer);
+
+ /* start read endpoint */
+ usbd_transfer_start(sc->sc_xfer[UCHCOM_BULK_DT_RD]);
+}
+
+static void
+uchcom_stop_read(struct ucom_softc *ucom)
+{
+ struct uchcom_softc *sc = ucom->sc_parent;
+
+ /* stop interrupt endpoint */
+ usbd_transfer_stop(sc->sc_intr_xfer);
+
+ /* stop read endpoint */
+ usbd_transfer_stop(sc->sc_xfer[UCHCOM_BULK_DT_RD]);
+}
+
+static void
+uchcom_start_write(struct ucom_softc *ucom)
+{
+ struct uchcom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UCHCOM_BULK_DT_WR]);
+}
+
+static void
+uchcom_stop_write(struct ucom_softc *ucom)
+{
+ struct uchcom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UCHCOM_BULK_DT_WR]);
+}
+
+/* ----------------------------------------------------------------------
+ * callback when the modem status is changed.
+ */
+static void
+uchcom_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uchcom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t intrstat;
+ uint8_t buf[16];
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ DPRINTF("actlen = %u\n", actlen);
+
+ if (actlen >= UCHCOM_INTR_LEAST) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, buf, sizeof(buf));
+
+ intrstat = (sc->sc_chiptype == TYPE_CH343) ?
+ actlen - 1 : UCHCOM_INTR_STAT1;
+
+ uchcom_convert_status(sc, buf[intrstat]);
+ ucom_status_change(&sc->sc_ucom);
+ }
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+uchcom_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uchcom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(&sc->sc_ucom, pc, 0,
+ usbd_xfer_max_len(xfer), &actlen)) {
+ DPRINTF("actlen = %d\n", actlen);
+
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+uchcom_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uchcom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ if (actlen > 0) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
+ }
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+uchcom_poll(struct ucom_softc *ucom)
+{
+ struct uchcom_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_xfer, UCHCOM_N_TRANSFER);
+}
+
+static device_method_t uchcom_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, uchcom_probe),
+ DEVMETHOD(device_attach, uchcom_attach),
+ DEVMETHOD(device_detach, uchcom_detach),
+ DEVMETHOD_END
+};
+
+static driver_t uchcom_driver = {
+ .name = "uchcom",
+ .methods = uchcom_methods,
+ .size = sizeof(struct uchcom_softc)
+};
+
+DRIVER_MODULE(uchcom, uhub, uchcom_driver, NULL, NULL);
+MODULE_DEPEND(uchcom, ucom, 1, 1, 1);
+MODULE_DEPEND(uchcom, usb, 1, 1, 1);
+MODULE_VERSION(uchcom, 1);
+USB_PNP_HOST_INFO(uchcom_devs);
diff --git a/sys/dev/usb/serial/ucycom.c b/sys/dev/usb/serial/ucycom.c
new file mode 100644
index 000000000000..5ab1810a0d11
--- /dev/null
+++ b/sys/dev/usb/serial/ucycom.c
@@ -0,0 +1,609 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2004 Dag-Erling 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.
+ */
+
+/*
+ * Device driver for Cypress CY7C637xx and CY7C640/1xx series USB to
+ * RS232 bridges.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/hid/hid.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR usb_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#define UCYCOM_MAX_IOLEN (1024 + 2) /* bytes */
+
+#define UCYCOM_IFACE_INDEX 0
+
+enum {
+ UCYCOM_CTRL_RD,
+ UCYCOM_INTR_RD,
+ UCYCOM_N_TRANSFER,
+};
+
+struct ucycom_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+
+ struct usb_device *sc_udev;
+ struct usb_xfer *sc_xfer[UCYCOM_N_TRANSFER];
+ struct mtx sc_mtx;
+
+ uint32_t sc_model;
+#define MODEL_CY7C63743 0x63743
+#define MODEL_CY7C64013 0x64013
+
+ uint16_t sc_flen; /* feature report length */
+ uint16_t sc_ilen; /* input report length */
+ uint16_t sc_olen; /* output report length */
+
+ uint8_t sc_fid; /* feature report id */
+ uint8_t sc_iid; /* input report id */
+ uint8_t sc_oid; /* output report id */
+ 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_iface_no;
+ uint8_t sc_temp_cfg[32];
+};
+
+/* prototypes */
+
+static device_probe_t ucycom_probe;
+static device_attach_t ucycom_attach;
+static device_detach_t ucycom_detach;
+static void ucycom_free_softc(struct ucycom_softc *);
+
+static usb_callback_t ucycom_ctrl_write_callback;
+static usb_callback_t ucycom_intr_read_callback;
+
+static void ucycom_free(struct ucom_softc *);
+static void ucycom_cfg_open(struct ucom_softc *);
+static void ucycom_start_read(struct ucom_softc *);
+static void ucycom_stop_read(struct ucom_softc *);
+static void ucycom_start_write(struct ucom_softc *);
+static void ucycom_stop_write(struct ucom_softc *);
+static void ucycom_cfg_write(struct ucycom_softc *, uint32_t, uint8_t);
+static int ucycom_pre_param(struct ucom_softc *, struct termios *);
+static void ucycom_cfg_param(struct ucom_softc *, struct termios *);
+static void ucycom_poll(struct ucom_softc *ucom);
+
+static const struct usb_config ucycom_config[UCYCOM_N_TRANSFER] = {
+ [UCYCOM_CTRL_RD] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = (sizeof(struct usb_device_request) + UCYCOM_MAX_IOLEN),
+ .callback = &ucycom_ctrl_write_callback,
+ .timeout = 1000, /* 1 second */
+ },
+
+ [UCYCOM_INTR_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = UCYCOM_MAX_IOLEN,
+ .callback = &ucycom_intr_read_callback,
+ },
+};
+
+static const struct ucom_callback ucycom_callback = {
+ .ucom_cfg_param = &ucycom_cfg_param,
+ .ucom_cfg_open = &ucycom_cfg_open,
+ .ucom_pre_param = &ucycom_pre_param,
+ .ucom_start_read = &ucycom_start_read,
+ .ucom_stop_read = &ucycom_stop_read,
+ .ucom_start_write = &ucycom_start_write,
+ .ucom_stop_write = &ucycom_stop_write,
+ .ucom_poll = &ucycom_poll,
+ .ucom_free = &ucycom_free,
+};
+
+static device_method_t ucycom_methods[] = {
+ DEVMETHOD(device_probe, ucycom_probe),
+ DEVMETHOD(device_attach, ucycom_attach),
+ DEVMETHOD(device_detach, ucycom_detach),
+ DEVMETHOD_END
+};
+
+static driver_t ucycom_driver = {
+ .name = "ucycom",
+ .methods = ucycom_methods,
+ .size = sizeof(struct ucycom_softc),
+};
+
+/*
+ * Supported devices
+ */
+static const STRUCT_USB_HOST_ID ucycom_devs[] = {
+ {USB_VPI(USB_VENDOR_DELORME, USB_PRODUCT_DELORME_EARTHMATE, MODEL_CY7C64013)},
+};
+
+DRIVER_MODULE(ucycom, uhub, ucycom_driver, NULL, NULL);
+MODULE_DEPEND(ucycom, ucom, 1, 1, 1);
+MODULE_DEPEND(ucycom, usb, 1, 1, 1);
+MODULE_DEPEND(ucycom, hid, 1, 1, 1);
+MODULE_VERSION(ucycom, 1);
+USB_PNP_HOST_INFO(ucycom_devs);
+
+#define UCYCOM_DEFAULT_RATE 4800
+#define UCYCOM_DEFAULT_CFG 0x03 /* N-8-1 */
+
+static int
+ucycom_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != 0) {
+ return (ENXIO);
+ }
+ if (uaa->info.bIfaceIndex != UCYCOM_IFACE_INDEX) {
+ return (ENXIO);
+ }
+ return (usbd_lookup_id_by_uaa(ucycom_devs, sizeof(ucycom_devs), uaa));
+}
+
+static int
+ucycom_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct ucycom_softc *sc = device_get_softc(dev);
+ void *urd_ptr = NULL;
+ int32_t error;
+ uint16_t urd_len;
+ uint8_t iface_index;
+
+ sc->sc_udev = uaa->device;
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "ucycom", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ DPRINTF("\n");
+
+ /* get chip model */
+ sc->sc_model = USB_GET_DRIVER_INFO(uaa);
+ if (sc->sc_model == 0) {
+ device_printf(dev, "unsupported device\n");
+ goto detach;
+ }
+ device_printf(dev, "Cypress CY7C%X USB to RS232 bridge\n", sc->sc_model);
+
+ /* get report descriptor */
+
+ error = usbd_req_get_hid_desc(uaa->device, NULL,
+ &urd_ptr, &urd_len, M_USBDEV,
+ UCYCOM_IFACE_INDEX);
+
+ if (error) {
+ device_printf(dev, "failed to get report "
+ "descriptor: %s\n",
+ usbd_errstr(error));
+ goto detach;
+ }
+ /* get report sizes */
+
+ sc->sc_flen = hid_report_size_max(urd_ptr, urd_len, hid_feature, &sc->sc_fid);
+ sc->sc_ilen = hid_report_size_max(urd_ptr, urd_len, hid_input, &sc->sc_iid);
+ sc->sc_olen = hid_report_size_max(urd_ptr, urd_len, hid_output, &sc->sc_oid);
+
+ if ((sc->sc_ilen > UCYCOM_MAX_IOLEN) || (sc->sc_ilen < 1) ||
+ (sc->sc_olen > UCYCOM_MAX_IOLEN) || (sc->sc_olen < 2) ||
+ (sc->sc_flen > UCYCOM_MAX_IOLEN) || (sc->sc_flen < 5)) {
+ device_printf(dev, "invalid report size i=%d, o=%d, f=%d, max=%d\n",
+ sc->sc_ilen, sc->sc_olen, sc->sc_flen,
+ UCYCOM_MAX_IOLEN);
+ goto detach;
+ }
+ sc->sc_iface_no = uaa->info.bIfaceNum;
+
+ iface_index = UCYCOM_IFACE_INDEX;
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, ucycom_config, UCYCOM_N_TRANSFER,
+ sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "allocating USB "
+ "transfers failed\n");
+ goto detach;
+ }
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &ucycom_callback, &sc->sc_mtx);
+ if (error) {
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ if (urd_ptr) {
+ free(urd_ptr, M_USBDEV);
+ }
+
+ return (0); /* success */
+
+detach:
+ if (urd_ptr) {
+ free(urd_ptr, M_USBDEV);
+ }
+ ucycom_detach(dev);
+ return (ENXIO);
+}
+
+static int
+ucycom_detach(device_t dev)
+{
+ struct ucycom_softc *sc = device_get_softc(dev);
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, UCYCOM_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ ucycom_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(ucycom);
+
+static void
+ucycom_free_softc(struct ucycom_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+ucycom_free(struct ucom_softc *ucom)
+{
+ ucycom_free_softc(ucom->sc_parent);
+}
+
+static void
+ucycom_cfg_open(struct ucom_softc *ucom)
+{
+ struct ucycom_softc *sc = ucom->sc_parent;
+
+ /* set default configuration */
+ ucycom_cfg_write(sc, UCYCOM_DEFAULT_RATE, UCYCOM_DEFAULT_CFG);
+}
+
+static void
+ucycom_start_read(struct ucom_softc *ucom)
+{
+ struct ucycom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UCYCOM_INTR_RD]);
+}
+
+static void
+ucycom_stop_read(struct ucom_softc *ucom)
+{
+ struct ucycom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UCYCOM_INTR_RD]);
+}
+
+static void
+ucycom_start_write(struct ucom_softc *ucom)
+{
+ struct ucycom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UCYCOM_CTRL_RD]);
+}
+
+static void
+ucycom_stop_write(struct ucom_softc *ucom)
+{
+ struct ucycom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UCYCOM_CTRL_RD]);
+}
+
+static void
+ucycom_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ucycom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_device_request req;
+ struct usb_page_cache *pc0, *pc1;
+ uint8_t data[2];
+ uint8_t offset;
+ uint32_t actlen;
+
+ pc0 = usbd_xfer_get_frame(xfer, 0);
+ pc1 = usbd_xfer_get_frame(xfer, 1);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+tr_transferred:
+ case USB_ST_SETUP:
+
+ switch (sc->sc_model) {
+ case MODEL_CY7C63743:
+ offset = 1;
+ break;
+ case MODEL_CY7C64013:
+ offset = 2;
+ break;
+ default:
+ offset = 0;
+ break;
+ }
+
+ if (ucom_get_data(&sc->sc_ucom, pc1, offset,
+ sc->sc_olen - offset, &actlen)) {
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UR_SET_REPORT;
+ USETW2(req.wValue, UHID_OUTPUT_REPORT, sc->sc_oid);
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, sc->sc_olen);
+
+ switch (sc->sc_model) {
+ case MODEL_CY7C63743:
+ data[0] = actlen;
+ break;
+ case MODEL_CY7C64013:
+ data[0] = 0;
+ data[1] = actlen;
+ break;
+ default:
+ break;
+ }
+
+ usbd_copy_in(pc0, 0, &req, sizeof(req));
+ usbd_copy_in(pc1, 0, data, offset);
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, sc->sc_olen);
+ usbd_xfer_set_frames(xfer, sc->sc_olen ? 2 : 1);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error == USB_ERR_CANCELLED) {
+ return;
+ }
+ DPRINTF("error=%s\n",
+ usbd_errstr(error));
+ goto tr_transferred;
+ }
+}
+
+static void
+ucycom_cfg_write(struct ucycom_softc *sc, uint32_t baud, uint8_t cfg)
+{
+ struct usb_device_request req;
+ uint16_t len;
+ usb_error_t err;
+
+ len = sc->sc_flen;
+ if (len > sizeof(sc->sc_temp_cfg)) {
+ len = sizeof(sc->sc_temp_cfg);
+ }
+ sc->sc_cfg = cfg;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UR_SET_REPORT;
+ USETW2(req.wValue, UHID_FEATURE_REPORT, sc->sc_fid);
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, len);
+
+ sc->sc_temp_cfg[0] = (baud & 0xff);
+ sc->sc_temp_cfg[1] = (baud >> 8) & 0xff;
+ sc->sc_temp_cfg[2] = (baud >> 16) & 0xff;
+ sc->sc_temp_cfg[3] = (baud >> 24) & 0xff;
+ sc->sc_temp_cfg[4] = cfg;
+
+ err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, sc->sc_temp_cfg, 0, 1000);
+ if (err) {
+ DPRINTFN(0, "device request failed, err=%s "
+ "(ignored)\n", usbd_errstr(err));
+ }
+}
+
+static int
+ucycom_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+ switch (t->c_ospeed) {
+ 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);
+ }
+ return (0);
+}
+
+static void
+ucycom_cfg_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct ucycom_softc *sc = ucom->sc_parent;
+ uint8_t cfg;
+
+ DPRINTF("\n");
+
+ if (t->c_cflag & CIGNORE) {
+ cfg = sc->sc_cfg;
+ } else {
+ cfg = 0;
+ switch (t->c_cflag & CSIZE) {
+ default:
+ case CS8:
+ ++cfg;
+ case CS7:
+ ++cfg;
+ case CS6:
+ ++cfg;
+ case CS5:
+ break;
+ }
+
+ 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;
+ }
+
+ ucycom_cfg_write(sc, t->c_ospeed, cfg);
+}
+
+static void
+ucycom_intr_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ucycom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint8_t buf[2];
+ uint32_t offset;
+ int len;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+ pc = usbd_xfer_get_frame(xfer, 0);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ switch (sc->sc_model) {
+ case MODEL_CY7C63743:
+ if (actlen < 1) {
+ goto tr_setup;
+ }
+ usbd_copy_out(pc, 0, buf, 1);
+
+ sc->sc_ist = buf[0] & ~0x07;
+ len = buf[0] & 0x07;
+
+ actlen--;
+ offset = 1;
+
+ break;
+
+ case MODEL_CY7C64013:
+ if (actlen < 2) {
+ goto tr_setup;
+ }
+ usbd_copy_out(pc, 0, buf, 2);
+
+ sc->sc_ist = buf[0] & ~0x07;
+ len = buf[1];
+
+ actlen -= 2;
+ offset = 2;
+
+ break;
+
+ default:
+ DPRINTFN(0, "unsupported model number\n");
+ goto tr_setup;
+ }
+
+ if (len > actlen)
+ len = actlen;
+ if (len)
+ ucom_put_data(&sc->sc_ucom, pc, offset, len);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, sc->sc_ilen);
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+ucycom_poll(struct ucom_softc *ucom)
+{
+ struct ucycom_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_xfer, UCYCOM_N_TRANSFER);
+}
diff --git a/sys/dev/usb/serial/ufoma.c b/sys/dev/usb/serial/ufoma.c
new file mode 100644
index 000000000000..3fc6a7a609ba
--- /dev/null
+++ b/sys/dev/usb/serial/ufoma.c
@@ -0,0 +1,1259 @@
+/* $NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $ */
+
+#define UFOMA_HANDSFREE
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2005, Takanori Watanabe All rights reserved.
+ * Copyright (c) 2003 M. Warner Losh <imp@FreeBSD.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.
+ *
+ * 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.
+ *
+ * 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:
+ * - Implement a Call Device for modems without multiplexed commands.
+ */
+
+/*
+ * NOTE: all function names beginning like "ufoma_cfg_" can only
+ * be called from within the config thread function !
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/sbuf.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_cdc.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR usb_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+typedef struct ufoma_mobile_acm_descriptor {
+ uint8_t bFunctionLength;
+ uint8_t bDescriptorType;
+ uint8_t bDescriptorSubtype;
+ uint8_t bType;
+ uint8_t bMode[1];
+} __packed 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_ACKNOWLEDGE 0x31
+
+#define UFOMA_MAX_TIMEOUT 15 /* standard says 10 seconds */
+#define UFOMA_CMD_BUF_SIZE 64 /* bytes */
+
+#define UFOMA_BULK_BUF_SIZE 1024 /* bytes */
+
+enum {
+ UFOMA_CTRL_ENDPT_INTR,
+ UFOMA_CTRL_ENDPT_READ,
+ UFOMA_CTRL_ENDPT_WRITE,
+ UFOMA_CTRL_ENDPT_MAX,
+};
+
+enum {
+ UFOMA_BULK_ENDPT_WRITE,
+ UFOMA_BULK_ENDPT_READ,
+ UFOMA_BULK_ENDPT_MAX,
+};
+
+struct ufoma_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+ struct cv sc_cv;
+ struct mtx sc_mtx;
+
+ struct usb_xfer *sc_ctrl_xfer[UFOMA_CTRL_ENDPT_MAX];
+ struct usb_xfer *sc_bulk_xfer[UFOMA_BULK_ENDPT_MAX];
+ uint8_t *sc_modetable;
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+
+ uint32_t sc_unit;
+
+ uint16_t sc_line;
+
+ uint8_t sc_num_msg;
+ uint8_t sc_nobulk;
+ uint8_t sc_ctrl_iface_no;
+ uint8_t sc_ctrl_iface_index;
+ uint8_t sc_data_iface_no;
+ uint8_t sc_data_iface_index;
+ uint8_t sc_cm_cap;
+ uint8_t sc_acm_cap;
+ uint8_t sc_lsr;
+ uint8_t sc_msr;
+ uint8_t sc_modetoactivate;
+ uint8_t sc_currentmode;
+};
+
+/* prototypes */
+
+static device_probe_t ufoma_probe;
+static device_attach_t ufoma_attach;
+static device_detach_t ufoma_detach;
+static void ufoma_free_softc(struct ufoma_softc *);
+
+static usb_callback_t ufoma_ctrl_read_callback;
+static usb_callback_t ufoma_ctrl_write_callback;
+static usb_callback_t ufoma_intr_callback;
+static usb_callback_t ufoma_bulk_write_callback;
+static usb_callback_t ufoma_bulk_read_callback;
+
+static void *ufoma_get_intconf(struct usb_config_descriptor *,
+ struct usb_interface_descriptor *, uint8_t, uint8_t);
+static void ufoma_cfg_link_state(struct ufoma_softc *);
+static void ufoma_cfg_activate_state(struct ufoma_softc *, uint16_t);
+static void ufoma_free(struct ucom_softc *);
+static void ufoma_cfg_open(struct ucom_softc *);
+static void ufoma_cfg_close(struct ucom_softc *);
+static void ufoma_cfg_set_break(struct ucom_softc *, uint8_t);
+static void ufoma_cfg_get_status(struct ucom_softc *, uint8_t *,
+ uint8_t *);
+static void ufoma_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void ufoma_cfg_set_rts(struct ucom_softc *, uint8_t);
+static int ufoma_pre_param(struct ucom_softc *, struct termios *);
+static void ufoma_cfg_param(struct ucom_softc *, struct termios *);
+static int ufoma_modem_setup(device_t, struct ufoma_softc *,
+ struct usb_attach_arg *);
+static void ufoma_start_read(struct ucom_softc *);
+static void ufoma_stop_read(struct ucom_softc *);
+static void ufoma_start_write(struct ucom_softc *);
+static void ufoma_stop_write(struct ucom_softc *);
+static void ufoma_poll(struct ucom_softc *ucom);
+
+/*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 const struct usb_config
+ ufoma_ctrl_config[UFOMA_CTRL_ENDPT_MAX] = {
+ [UFOMA_CTRL_ENDPT_INTR] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = sizeof(struct usb_cdc_notification),
+ .callback = &ufoma_intr_callback,
+ },
+
+ [UFOMA_CTRL_ENDPT_READ] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = (sizeof(struct usb_device_request) + UFOMA_CMD_BUF_SIZE),
+ .flags = {.short_xfer_ok = 1,},
+ .callback = &ufoma_ctrl_read_callback,
+ .timeout = 1000, /* 1 second */
+ },
+
+ [UFOMA_CTRL_ENDPT_WRITE] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = (sizeof(struct usb_device_request) + 1),
+ .callback = &ufoma_ctrl_write_callback,
+ .timeout = 1000, /* 1 second */
+ },
+};
+
+static const struct usb_config
+ ufoma_bulk_config[UFOMA_BULK_ENDPT_MAX] = {
+ [UFOMA_BULK_ENDPT_WRITE] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UFOMA_BULK_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &ufoma_bulk_write_callback,
+ },
+
+ [UFOMA_BULK_ENDPT_READ] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UFOMA_BULK_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &ufoma_bulk_read_callback,
+ },
+};
+
+static const struct ucom_callback ufoma_callback = {
+ .ucom_cfg_get_status = &ufoma_cfg_get_status,
+ .ucom_cfg_set_dtr = &ufoma_cfg_set_dtr,
+ .ucom_cfg_set_rts = &ufoma_cfg_set_rts,
+ .ucom_cfg_set_break = &ufoma_cfg_set_break,
+ .ucom_cfg_param = &ufoma_cfg_param,
+ .ucom_cfg_open = &ufoma_cfg_open,
+ .ucom_cfg_close = &ufoma_cfg_close,
+ .ucom_pre_param = &ufoma_pre_param,
+ .ucom_start_read = &ufoma_start_read,
+ .ucom_stop_read = &ufoma_stop_read,
+ .ucom_start_write = &ufoma_start_write,
+ .ucom_stop_write = &ufoma_stop_write,
+ .ucom_poll = &ufoma_poll,
+ .ucom_free = &ufoma_free,
+};
+
+static device_method_t ufoma_methods[] = {
+ /* Device methods */
+ DEVMETHOD(device_probe, ufoma_probe),
+ DEVMETHOD(device_attach, ufoma_attach),
+ DEVMETHOD(device_detach, ufoma_detach),
+ DEVMETHOD_END
+};
+
+static driver_t ufoma_driver = {
+ .name = "ufoma",
+ .methods = ufoma_methods,
+ .size = sizeof(struct ufoma_softc),
+};
+
+static const STRUCT_USB_HOST_ID ufoma_devs[] = {
+ {USB_IFACE_CLASS(UICLASS_CDC),
+ USB_IFACE_SUBCLASS(UISUBCLASS_MCPC),},
+};
+
+DRIVER_MODULE(ufoma, uhub, ufoma_driver, NULL, NULL);
+MODULE_DEPEND(ufoma, ucom, 1, 1, 1);
+MODULE_DEPEND(ufoma, usb, 1, 1, 1);
+MODULE_VERSION(ufoma, 1);
+USB_PNP_HOST_INFO(ufoma_devs);
+
+static int
+ufoma_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct usb_interface_descriptor *id;
+ struct usb_config_descriptor *cd;
+ usb_mcpc_acm_descriptor *mad;
+ int error;
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ error = usbd_lookup_id_by_uaa(ufoma_devs, sizeof(ufoma_devs), uaa);
+ if (error)
+ return (error);
+
+ id = usbd_get_interface_descriptor(uaa->iface);
+ cd = usbd_get_config_descriptor(uaa->device);
+
+ if (id == NULL || cd == NULL)
+ return (ENXIO);
+
+ mad = ufoma_get_intconf(cd, id, UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM);
+ if (mad == NULL)
+ return (ENXIO);
+
+#ifndef UFOMA_HANDSFREE
+ if ((mad->bType == UMCPC_ACM_TYPE_AB5) ||
+ (mad->bType == UMCPC_ACM_TYPE_AB6))
+ return (ENXIO);
+#endif
+ return (BUS_PROBE_GENERIC);
+}
+
+static int
+ufoma_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct ufoma_softc *sc = device_get_softc(dev);
+ struct usb_config_descriptor *cd;
+ struct usb_interface_descriptor *id;
+ struct sysctl_ctx_list *sctx;
+ struct sysctl_oid *soid;
+
+ usb_mcpc_acm_descriptor *mad;
+ uint8_t elements;
+ int32_t error;
+
+ sc->sc_udev = uaa->device;
+ sc->sc_dev = dev;
+ sc->sc_unit = device_get_unit(dev);
+
+ mtx_init(&sc->sc_mtx, "ufoma", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+ cv_init(&sc->sc_cv, "CWAIT");
+
+ device_set_usb_desc(dev);
+
+ DPRINTF("\n");
+
+ /* setup control transfers */
+
+ cd = usbd_get_config_descriptor(uaa->device);
+ id = usbd_get_interface_descriptor(uaa->iface);
+ sc->sc_ctrl_iface_no = id->bInterfaceNumber;
+ sc->sc_ctrl_iface_index = uaa->info.bIfaceIndex;
+
+ error = usbd_transfer_setup(uaa->device,
+ &sc->sc_ctrl_iface_index, sc->sc_ctrl_xfer,
+ ufoma_ctrl_config, UFOMA_CTRL_ENDPT_MAX, sc, &sc->sc_mtx);
+
+ if (error) {
+ device_printf(dev, "allocating control USB "
+ "transfers failed\n");
+ goto detach;
+ }
+ mad = ufoma_get_intconf(cd, id, UDESC_VS_INTERFACE, UDESCSUB_MCPC_ACM);
+ if (mad == NULL) {
+ goto detach;
+ }
+ if (mad->bFunctionLength < sizeof(*mad)) {
+ device_printf(dev, "invalid MAD descriptor\n");
+ goto detach;
+ }
+ if ((mad->bType == UMCPC_ACM_TYPE_AB5) ||
+ (mad->bType == UMCPC_ACM_TYPE_AB6)) {
+ sc->sc_nobulk = 1;
+ } else {
+ sc->sc_nobulk = 0;
+ if (ufoma_modem_setup(dev, sc, uaa)) {
+ goto detach;
+ }
+ }
+
+ elements = (mad->bFunctionLength - sizeof(*mad) + 1);
+
+ /* initialize mode variables */
+
+ sc->sc_modetable = malloc(elements + 1, M_USBDEV, M_WAITOK);
+
+ if (sc->sc_modetable == NULL) {
+ goto detach;
+ }
+ sc->sc_modetable[0] = (elements + 1);
+ memcpy(&sc->sc_modetable[1], mad->bMode, elements);
+
+ sc->sc_currentmode = UMCPC_ACM_MODE_UNLINKED;
+ sc->sc_modetoactivate = mad->bMode[0];
+
+ /* clear stall at first run, if any */
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]);
+ usbd_xfer_set_stall(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]);
+ mtx_unlock(&sc->sc_mtx);
+
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &ufoma_callback, &sc->sc_mtx);
+ if (error) {
+ DPRINTF("ucom_attach failed\n");
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ /*Sysctls*/
+ sctx = device_get_sysctl_ctx(dev);
+ soid = device_get_sysctl_tree(dev);
+
+ SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "supportmode",
+ CTLFLAG_RD | CTLTYPE_STRING | CTLFLAG_MPSAFE, sc, 0,
+ ufoma_sysctl_support, "A", "Supporting port role");
+
+ SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "currentmode",
+ CTLFLAG_RD | CTLTYPE_STRING | CTLFLAG_MPSAFE, sc, 0,
+ ufoma_sysctl_current, "A", "Current port role");
+
+ SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "openmode",
+ CTLFLAG_RW | CTLTYPE_STRING | CTLFLAG_MPSAFE, sc, 0,
+ ufoma_sysctl_open, "A", "Mode to transit when port is opened");
+ SYSCTL_ADD_UINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "comunit",
+ CTLFLAG_RD, &(sc->sc_super_ucom.sc_unit), 0,
+ "Unit number as USB serial");
+
+ return (0); /* success */
+
+detach:
+ ufoma_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+ufoma_detach(device_t dev)
+{
+ struct ufoma_softc *sc = device_get_softc(dev);
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_ctrl_xfer, UFOMA_CTRL_ENDPT_MAX);
+ usbd_transfer_unsetup(sc->sc_bulk_xfer, UFOMA_BULK_ENDPT_MAX);
+
+ if (sc->sc_modetable) {
+ free(sc->sc_modetable, M_USBDEV);
+ }
+ cv_destroy(&sc->sc_cv);
+
+ device_claim_softc(dev);
+
+ ufoma_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(ufoma);
+
+static void
+ufoma_free_softc(struct ufoma_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+ufoma_free(struct ucom_softc *ucom)
+{
+ ufoma_free_softc(ucom->sc_parent);
+}
+
+static void *
+ufoma_get_intconf(struct usb_config_descriptor *cd, struct usb_interface_descriptor *id,
+ uint8_t type, uint8_t subtype)
+{
+ struct usb_descriptor *desc = (void *)id;
+
+ while ((desc = usb_desc_foreach(cd, desc))) {
+ if (desc->bDescriptorType == UDESC_INTERFACE) {
+ return (NULL);
+ }
+ if ((desc->bDescriptorType == type) &&
+ (desc->bDescriptorSubtype == subtype)) {
+ break;
+ }
+ }
+ return (desc);
+}
+
+static void
+ufoma_cfg_link_state(struct ufoma_softc *sc)
+{
+ struct usb_device_request req;
+ int32_t error;
+
+ req.bmRequestType = UT_WRITE_VENDOR_INTERFACE;
+ req.bRequest = UMCPC_SET_LINK;
+ USETW(req.wValue, UMCPC_CM_MOBILE_ACM);
+ USETW(req.wIndex, sc->sc_ctrl_iface_no);
+ USETW(req.wLength, sc->sc_modetable[0]);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, sc->sc_modetable, 0, 1000);
+
+ error = cv_timedwait(&sc->sc_cv, &sc->sc_mtx, hz);
+
+ if (error) {
+ DPRINTF("NO response\n");
+ }
+}
+
+static void
+ufoma_cfg_activate_state(struct ufoma_softc *sc, uint16_t state)
+{
+ struct usb_device_request req;
+ int32_t error;
+
+ req.bmRequestType = UT_WRITE_VENDOR_INTERFACE;
+ req.bRequest = UMCPC_ACTIVATE_MODE;
+ USETW(req.wValue, state);
+ USETW(req.wIndex, sc->sc_ctrl_iface_no);
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+
+ error = cv_timedwait(&sc->sc_cv, &sc->sc_mtx,
+ (UFOMA_MAX_TIMEOUT * hz));
+ if (error) {
+ DPRINTF("No response\n");
+ }
+}
+
+static void
+ufoma_ctrl_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ufoma_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_device_request req;
+ struct usb_page_cache *pc0, *pc1;
+ int len, aframes, nframes;
+
+ usbd_xfer_status(xfer, NULL, NULL, &aframes, &nframes);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+tr_transferred:
+ if (aframes != nframes)
+ goto tr_setup;
+ pc1 = usbd_xfer_get_frame(xfer, 1);
+ len = usbd_xfer_frame_len(xfer, 1);
+ if (len > 0)
+ ucom_put_data(&sc->sc_ucom, pc1, 0, len);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ if (sc->sc_num_msg) {
+ sc->sc_num_msg--;
+
+ req.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.bRequest = UCDC_GET_ENCAPSULATED_RESPONSE;
+ USETW(req.wIndex, sc->sc_ctrl_iface_no);
+ USETW(req.wValue, 0);
+ USETW(req.wLength, UFOMA_CMD_BUF_SIZE);
+
+ pc0 = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc0, 0, &req, sizeof(req));
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, UFOMA_CMD_BUF_SIZE);
+ usbd_xfer_set_frames(xfer, 2);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ DPRINTF("error = %s\n",
+ usbd_errstr(error));
+
+ if (error == USB_ERR_CANCELLED) {
+ return;
+ }
+ goto tr_transferred;
+ }
+}
+
+static void
+ufoma_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ufoma_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_device_request req;
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+tr_transferred:
+ case USB_ST_SETUP:
+ pc = usbd_xfer_get_frame(xfer, 1);
+ if (ucom_get_data(&sc->sc_ucom, pc, 0, 1, &actlen)) {
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SEND_ENCAPSULATED_COMMAND;
+ USETW(req.wIndex, sc->sc_ctrl_iface_no);
+ USETW(req.wValue, 0);
+ USETW(req.wLength, 1);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, 1);
+ usbd_xfer_set_frames(xfer, 2);
+
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ DPRINTF("error = %s\n", usbd_errstr(error));
+
+ if (error == USB_ERR_CANCELLED) {
+ return;
+ }
+ goto tr_transferred;
+ }
+}
+
+static void
+ufoma_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ufoma_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_cdc_notification pkt;
+ struct usb_page_cache *pc;
+ uint16_t wLen;
+ uint16_t temp;
+ uint8_t mstatus;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (actlen < 8) {
+ DPRINTF("too short message\n");
+ goto tr_setup;
+ }
+ if (actlen > (int)sizeof(pkt)) {
+ DPRINTF("truncating message\n");
+ actlen = sizeof(pkt);
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &pkt, actlen);
+
+ actlen -= 8;
+
+ wLen = UGETW(pkt.wLength);
+ if (actlen > wLen) {
+ actlen = wLen;
+ }
+ if ((pkt.bmRequestType == UT_READ_VENDOR_INTERFACE) &&
+ (pkt.bNotification == UMCPC_REQUEST_ACKNOWLEDGE)) {
+ temp = UGETW(pkt.wValue);
+ sc->sc_currentmode = (temp >> 8);
+ if (!(temp & 0xff)) {
+ DPRINTF("Mode change failed!\n");
+ }
+ cv_signal(&sc->sc_cv);
+ }
+ if (pkt.bmRequestType != UCDC_NOTIFICATION) {
+ goto tr_setup;
+ }
+ switch (pkt.bNotification) {
+ case UCDC_N_RESPONSE_AVAILABLE:
+ if (!(sc->sc_nobulk)) {
+ DPRINTF("Wrong serial state!\n");
+ break;
+ }
+ if (sc->sc_num_msg != 0xFF) {
+ sc->sc_num_msg++;
+ }
+ usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]);
+ break;
+
+ case UCDC_N_SERIAL_STATE:
+ if (sc->sc_nobulk) {
+ DPRINTF("Wrong serial state!\n");
+ break;
+ }
+ /*
+ * Set the serial state in ucom driver based on
+ * the bits from the notify message
+ */
+ if (actlen < 2) {
+ DPRINTF("invalid notification "
+ "length, %d bytes!\n", actlen);
+ break;
+ }
+ DPRINTF("notify bytes = 0x%02x, 0x%02x\n",
+ pkt.data[0], pkt.data[1]);
+
+ /* currently, lsr is always zero. */
+ sc->sc_lsr = 0;
+ sc->sc_msr = 0;
+
+ mstatus = pkt.data[0];
+
+ if (mstatus & UCDC_N_SERIAL_RI) {
+ sc->sc_msr |= SER_RI;
+ }
+ if (mstatus & UCDC_N_SERIAL_DSR) {
+ sc->sc_msr |= SER_DSR;
+ }
+ if (mstatus & UCDC_N_SERIAL_DCD) {
+ sc->sc_msr |= SER_DCD;
+ }
+ ucom_status_change(&sc->sc_ucom);
+ break;
+
+ default:
+ break;
+ }
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+ufoma_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ufoma_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(&sc->sc_ucom, pc, 0,
+ UFOMA_BULK_BUF_SIZE, &actlen)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+ufoma_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ufoma_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+ufoma_cfg_open(struct ucom_softc *ucom)
+{
+ struct ufoma_softc *sc = ucom->sc_parent;
+
+ /* empty input queue */
+
+ if (sc->sc_num_msg != 0xFF) {
+ sc->sc_num_msg++;
+ }
+ if (sc->sc_currentmode == UMCPC_ACM_MODE_UNLINKED) {
+ ufoma_cfg_link_state(sc);
+ }
+ if (sc->sc_currentmode == UMCPC_ACM_MODE_DEACTIVATED) {
+ ufoma_cfg_activate_state(sc, sc->sc_modetoactivate);
+ }
+}
+
+static void
+ufoma_cfg_close(struct ucom_softc *ucom)
+{
+ struct ufoma_softc *sc = ucom->sc_parent;
+
+ ufoma_cfg_activate_state(sc, UMCPC_ACM_MODE_DEACTIVATED);
+}
+
+static void
+ufoma_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct ufoma_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+ uint16_t wValue;
+
+ if (sc->sc_nobulk ||
+ (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX)) {
+ return;
+ }
+ if (!(sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK)) {
+ return;
+ }
+ wValue = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SEND_BREAK;
+ USETW(req.wValue, wValue);
+ req.wIndex[0] = sc->sc_ctrl_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+static void
+ufoma_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct ufoma_softc *sc = ucom->sc_parent;
+
+ /* XXX Note: sc_lsr is always zero */
+ *lsr = sc->sc_lsr;
+ *msr = sc->sc_msr;
+}
+
+static void
+ufoma_cfg_set_line_state(struct ufoma_softc *sc)
+{
+ struct usb_device_request req;
+
+ /* Don't send line state emulation request for OBEX port */
+ if (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX) {
+ return;
+ }
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
+ USETW(req.wValue, sc->sc_line);
+ req.wIndex[0] = sc->sc_ctrl_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+static void
+ufoma_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct ufoma_softc *sc = ucom->sc_parent;
+
+ if (sc->sc_nobulk) {
+ return;
+ }
+ if (onoff)
+ sc->sc_line |= UCDC_LINE_DTR;
+ else
+ sc->sc_line &= ~UCDC_LINE_DTR;
+
+ ufoma_cfg_set_line_state(sc);
+}
+
+static void
+ufoma_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct ufoma_softc *sc = ucom->sc_parent;
+
+ if (sc->sc_nobulk) {
+ return;
+ }
+ if (onoff)
+ sc->sc_line |= UCDC_LINE_RTS;
+ else
+ sc->sc_line &= ~UCDC_LINE_RTS;
+
+ ufoma_cfg_set_line_state(sc);
+}
+
+static int
+ufoma_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+ return (0); /* we accept anything */
+}
+
+static void
+ufoma_cfg_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct ufoma_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+ struct usb_cdc_line_state ls;
+
+ if (sc->sc_nobulk ||
+ (sc->sc_currentmode == UMCPC_ACM_MODE_OBEX)) {
+ return;
+ }
+ DPRINTF("\n");
+
+ memset(&ls, 0, sizeof(ls));
+
+ USETDW(ls.dwDTERate, t->c_ospeed);
+
+ if (t->c_cflag & CSTOPB) {
+ ls.bCharFormat = UCDC_STOP_BIT_2;
+ } else {
+ ls.bCharFormat = UCDC_STOP_BIT_1;
+ }
+
+ if (t->c_cflag & PARENB) {
+ if (t->c_cflag & PARODD) {
+ ls.bParityType = UCDC_PARITY_ODD;
+ } else {
+ ls.bParityType = UCDC_PARITY_EVEN;
+ }
+ } else {
+ ls.bParityType = UCDC_PARITY_NONE;
+ }
+
+ switch (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;
+ }
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_LINE_CODING;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_ctrl_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, UCDC_LINE_STATE_LENGTH);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, &ls, 0, 1000);
+}
+
+static int
+ufoma_modem_setup(device_t dev, struct ufoma_softc *sc,
+ struct usb_attach_arg *uaa)
+{
+ struct usb_config_descriptor *cd;
+ struct usb_cdc_acm_descriptor *acm;
+ struct usb_cdc_cm_descriptor *cmd;
+ struct usb_interface_descriptor *id;
+ struct usb_interface *iface;
+ uint8_t i;
+ int32_t error;
+
+ cd = usbd_get_config_descriptor(uaa->device);
+ id = usbd_get_interface_descriptor(uaa->iface);
+
+ cmd = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM);
+
+ if ((cmd == NULL) ||
+ (cmd->bLength < sizeof(*cmd))) {
+ return (EINVAL);
+ }
+ sc->sc_cm_cap = cmd->bmCapabilities;
+ sc->sc_data_iface_no = cmd->bDataInterface;
+
+ acm = ufoma_get_intconf(cd, id, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM);
+
+ if ((acm == NULL) ||
+ (acm->bLength < sizeof(*acm))) {
+ return (EINVAL);
+ }
+ sc->sc_acm_cap = acm->bmCapabilities;
+
+ device_printf(dev, "data interface %d, has %sCM over data, "
+ "has %sbreak\n",
+ 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 ");
+
+ /* get the data interface too */
+
+ for (i = 0;; i++) {
+ iface = usbd_get_iface(uaa->device, i);
+
+ if (iface) {
+ id = usbd_get_interface_descriptor(iface);
+
+ if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) {
+ sc->sc_data_iface_index = i;
+ usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex);
+ break;
+ }
+ } else {
+ device_printf(dev, "no data interface\n");
+ return (EINVAL);
+ }
+ }
+
+ error = usbd_transfer_setup(uaa->device,
+ &sc->sc_data_iface_index, sc->sc_bulk_xfer,
+ ufoma_bulk_config, UFOMA_BULK_ENDPT_MAX, sc, &sc->sc_mtx);
+
+ if (error) {
+ device_printf(dev, "allocating BULK USB "
+ "transfers failed\n");
+ return (EINVAL);
+ }
+ return (0);
+}
+
+static void
+ufoma_start_read(struct ucom_softc *ucom)
+{
+ struct ufoma_softc *sc = ucom->sc_parent;
+
+ /* start interrupt transfer */
+ usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_INTR]);
+
+ /* start data transfer */
+ if (sc->sc_nobulk) {
+ usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]);
+ } else {
+ usbd_transfer_start(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]);
+ }
+}
+
+static void
+ufoma_stop_read(struct ucom_softc *ucom)
+{
+ struct ufoma_softc *sc = ucom->sc_parent;
+
+ /* stop interrupt transfer */
+ usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_INTR]);
+
+ /* stop data transfer */
+ if (sc->sc_nobulk) {
+ usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_READ]);
+ } else {
+ usbd_transfer_stop(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_READ]);
+ }
+}
+
+static void
+ufoma_start_write(struct ucom_softc *ucom)
+{
+ struct ufoma_softc *sc = ucom->sc_parent;
+
+ if (sc->sc_nobulk) {
+ usbd_transfer_start(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_WRITE]);
+ } else {
+ usbd_transfer_start(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]);
+ }
+}
+
+static void
+ufoma_stop_write(struct ucom_softc *ucom)
+{
+ struct ufoma_softc *sc = ucom->sc_parent;
+
+ if (sc->sc_nobulk) {
+ usbd_transfer_stop(sc->sc_ctrl_xfer[UFOMA_CTRL_ENDPT_WRITE]);
+ } else {
+ usbd_transfer_stop(sc->sc_bulk_xfer[UFOMA_BULK_ENDPT_WRITE]);
+ }
+}
+
+static 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 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 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;
+}
+
+static void
+ufoma_poll(struct ucom_softc *ucom)
+{
+ struct ufoma_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_ctrl_xfer, UFOMA_CTRL_ENDPT_MAX);
+ usbd_transfer_poll(sc->sc_bulk_xfer, UFOMA_BULK_ENDPT_MAX);
+}
diff --git a/sys/dev/usb/serial/uftdi.c b/sys/dev/usb/serial/uftdi.c
new file mode 100644
index 000000000000..b06dc38432be
--- /dev/null
+++ b/sys/dev/usb/serial/uftdi.c
@@ -0,0 +1,2030 @@
+/* $NetBSD: uftdi.c,v 1.13 2002/09/23 05:51:23 simonb Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * NOTE: all function names beginning like "uftdi_cfg_" can only
+ * be called from within the config thread function !
+ */
+
+/*
+ * FTDI FT232x, FT2232x, FT4232x, FT8U100AX and FT8U232xM serial adapters.
+ *
+ * Note that we specifically do not do a reset or otherwise alter the state of
+ * the chip during attach, detach, open, and close, because it could be
+ * pre-initialized (via an attached serial eeprom) to power-on into a mode such
+ * as bitbang in which the pins are being driven to a specific state which we
+ * must not perturb. The device gets reset at power-on, and doesn't need to be
+ * reset again after that to function, except as directed by ioctl() calls.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_ioctl.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR uftdi_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+#include <dev/usb/serial/uftdi_reg.h>
+#include <dev/usb/uftdiio.h>
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uftdi, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB uftdi");
+
+#ifdef USB_DEBUG
+static int uftdi_debug = 0;
+SYSCTL_INT(_hw_usb_uftdi, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &uftdi_debug, 0, "Debug level");
+#endif
+
+#define UFTDI_CONFIG_INDEX 0
+
+/*
+ * IO buffer sizes and FTDI device procotol sizes.
+ *
+ * Note that the output packet size in the following defines is not the usb
+ * protocol packet size based on bus speed, it is the size dictated by the FTDI
+ * device itself, and is used only on older chips.
+ *
+ * We allocate buffers bigger than the hardware's packet size, and process
+ * multiple packets within each buffer. This allows the controller to make
+ * optimal use of the usb bus by conducting multiple transfers with the device
+ * during a single bus timeslice to fill or drain the chip's fifos.
+ *
+ * The output data on newer chips has no packet header, and we are able to pack
+ * any number of output bytes into a buffer. On some older chips, each output
+ * packet contains a 1-byte header and up to 63 bytes of payload. The size is
+ * encoded in 6 bits of the header, hence the 64-byte limit on packet size. We
+ * loop to fill the buffer with many of these header+payload packets.
+ *
+ * The input data on all chips consists of packets which contain a 2-byte header
+ * followed by data payload. The total size of the packet is wMaxPacketSize
+ * which can change based on the bus speed (e.g., 64 for full speed, 512 for
+ * high speed). We loop to extract the headers and payloads from the packets
+ * packed into an input buffer.
+ */
+#define UFTDI_IBUFSIZE 2048
+#define UFTDI_IHDRSIZE 2
+#define UFTDI_OBUFSIZE 2048
+#define UFTDI_OPKTSIZE 64
+
+enum {
+ UFTDI_BULK_DT_WR,
+ UFTDI_BULK_DT_RD,
+ UFTDI_N_TRANSFER,
+};
+
+enum {
+ DEVT_SIO,
+ DEVT_232A,
+ DEVT_232B,
+ DEVT_2232D, /* Includes 2232C */
+ DEVT_232R,
+ DEVT_2232H,
+ DEVT_4232H,
+ DEVT_232H,
+ DEVT_230X,
+};
+
+#define DEVF_BAUDBITS_HINDEX 0x01 /* Baud bits in high byte of index. */
+#define DEVF_BAUDCLK_12M 0X02 /* Base baud clock is 12MHz. */
+
+struct uftdi_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+
+ struct usb_device *sc_udev;
+ struct usb_xfer *sc_xfer[UFTDI_N_TRANSFER];
+ device_t sc_dev;
+ struct mtx sc_mtx;
+
+ uint32_t sc_unit;
+
+ uint16_t sc_last_lcr;
+ uint16_t sc_bcdDevice;
+
+ uint8_t sc_devtype;
+ uint8_t sc_devflags;
+ uint8_t sc_hdrlen;
+ uint8_t sc_msr;
+ uint8_t sc_lsr;
+ uint8_t sc_bitmode;
+};
+
+struct uftdi_param_config {
+ uint16_t baud_lobits;
+ uint16_t baud_hibits;
+ uint16_t lcr;
+ uint8_t v_start;
+ uint8_t v_stop;
+ uint8_t v_flow;
+};
+
+/* prototypes */
+
+static device_probe_t uftdi_probe;
+static device_attach_t uftdi_attach;
+static device_detach_t uftdi_detach;
+static void uftdi_free_softc(struct uftdi_softc *);
+
+static usb_callback_t uftdi_write_callback;
+static usb_callback_t uftdi_read_callback;
+
+static void uftdi_free(struct ucom_softc *);
+static void uftdi_cfg_open(struct ucom_softc *);
+static void uftdi_cfg_close(struct ucom_softc *);
+static void uftdi_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void uftdi_cfg_set_rts(struct ucom_softc *, uint8_t);
+static void uftdi_cfg_set_break(struct ucom_softc *, uint8_t);
+static int uftdi_set_parm_soft(struct ucom_softc *, struct termios *,
+ struct uftdi_param_config *);
+static int uftdi_pre_param(struct ucom_softc *, struct termios *);
+static void uftdi_cfg_param(struct ucom_softc *, struct termios *);
+static void uftdi_cfg_get_status(struct ucom_softc *, uint8_t *,
+ uint8_t *);
+static int uftdi_reset(struct ucom_softc *, int);
+static int uftdi_set_bitmode(struct ucom_softc *, uint8_t, uint8_t);
+static int uftdi_get_bitmode(struct ucom_softc *, uint8_t *, uint8_t *);
+static int uftdi_set_latency(struct ucom_softc *, int);
+static int uftdi_get_latency(struct ucom_softc *, int *);
+static int uftdi_set_event_char(struct ucom_softc *, int);
+static int uftdi_set_error_char(struct ucom_softc *, int);
+static int uftdi_ioctl(struct ucom_softc *, uint32_t, caddr_t, int,
+ struct thread *);
+static void uftdi_start_read(struct ucom_softc *);
+static void uftdi_stop_read(struct ucom_softc *);
+static void uftdi_start_write(struct ucom_softc *);
+static void uftdi_stop_write(struct ucom_softc *);
+static void uftdi_poll(struct ucom_softc *ucom);
+
+static const struct usb_config uftdi_config[UFTDI_N_TRANSFER] = {
+ [UFTDI_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UFTDI_OBUFSIZE,
+ .flags = {.pipe_bof = 1,},
+ .callback = &uftdi_write_callback,
+ },
+
+ [UFTDI_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UFTDI_IBUFSIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &uftdi_read_callback,
+ },
+};
+
+static const struct ucom_callback uftdi_callback = {
+ .ucom_cfg_get_status = &uftdi_cfg_get_status,
+ .ucom_cfg_set_dtr = &uftdi_cfg_set_dtr,
+ .ucom_cfg_set_rts = &uftdi_cfg_set_rts,
+ .ucom_cfg_set_break = &uftdi_cfg_set_break,
+ .ucom_cfg_param = &uftdi_cfg_param,
+ .ucom_cfg_open = &uftdi_cfg_open,
+ .ucom_cfg_close = &uftdi_cfg_close,
+ .ucom_pre_param = &uftdi_pre_param,
+ .ucom_ioctl = &uftdi_ioctl,
+ .ucom_start_read = &uftdi_start_read,
+ .ucom_stop_read = &uftdi_stop_read,
+ .ucom_start_write = &uftdi_start_write,
+ .ucom_stop_write = &uftdi_stop_write,
+ .ucom_poll = &uftdi_poll,
+ .ucom_free = &uftdi_free,
+};
+
+static device_method_t uftdi_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, uftdi_probe),
+ DEVMETHOD(device_attach, uftdi_attach),
+ DEVMETHOD(device_detach, uftdi_detach),
+ DEVMETHOD_END
+};
+
+static driver_t uftdi_driver = {
+ .name = "uftdi",
+ .methods = uftdi_methods,
+ .size = sizeof(struct uftdi_softc),
+};
+
+static const STRUCT_USB_HOST_ID uftdi_devs[] = {
+#define UFTDI_DEV(v, p, i) \
+ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
+ UFTDI_DEV(ACTON, SPECTRAPRO, 0),
+ UFTDI_DEV(ALTI2, N3, 0),
+ UFTDI_DEV(ANALOGDEVICES, GNICE, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(ANALOGDEVICES, GNICEPLUS, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(ATMEL, STK541, 0),
+ UFTDI_DEV(BAYER, CONTOUR_CABLE, 0),
+ UFTDI_DEV(BBELECTRONICS, 232USB9M, 0),
+ UFTDI_DEV(BBELECTRONICS, 485USB9F_2W, 0),
+ UFTDI_DEV(BBELECTRONICS, 485USB9F_4W, 0),
+ UFTDI_DEV(BBELECTRONICS, 485USBTB_2W, 0),
+ UFTDI_DEV(BBELECTRONICS, 485USBTB_4W, 0),
+ UFTDI_DEV(BBELECTRONICS, TTL3USB9M, 0),
+ UFTDI_DEV(BBELECTRONICS, TTL5USB9M, 0),
+ UFTDI_DEV(BBELECTRONICS, USO9ML2, 0),
+ UFTDI_DEV(BBELECTRONICS, USO9ML2DR, 0),
+ UFTDI_DEV(BBELECTRONICS, USO9ML2DR_2, 0),
+ UFTDI_DEV(BBELECTRONICS, USOPTL4, 0),
+ UFTDI_DEV(BBELECTRONICS, USOPTL4DR, 0),
+ UFTDI_DEV(BBELECTRONICS, USOPTL4DR2, 0),
+ UFTDI_DEV(BBELECTRONICS, USOTL4, 0),
+ UFTDI_DEV(BBELECTRONICS, USPTL4, 0),
+ UFTDI_DEV(BBELECTRONICS, USTL4, 0),
+ UFTDI_DEV(BBELECTRONICS, ZZ_PROG1_USB, 0),
+ UFTDI_DEV(BRAINBOXES, US101, 0),
+ UFTDI_DEV(BRAINBOXES, US159, 0),
+ UFTDI_DEV(BRAINBOXES, US235, 0),
+ UFTDI_DEV(BRAINBOXES, US257, 0),
+ UFTDI_DEV(BRAINBOXES, US25701, 0),
+ UFTDI_DEV(BRAINBOXES, US279_12, 0),
+ UFTDI_DEV(BRAINBOXES, US279_34, 0),
+ UFTDI_DEV(BRAINBOXES, US279_56, 0),
+ UFTDI_DEV(BRAINBOXES, US279_78, 0),
+ UFTDI_DEV(BRAINBOXES, US313, 0),
+ UFTDI_DEV(BRAINBOXES, US320, 0),
+ UFTDI_DEV(BRAINBOXES, US324, 0),
+ UFTDI_DEV(BRAINBOXES, US346_12, 0),
+ UFTDI_DEV(BRAINBOXES, US346_34, 0),
+ UFTDI_DEV(BRAINBOXES, US701_12, 0),
+ UFTDI_DEV(BRAINBOXES, US701_34, 0),
+ UFTDI_DEV(BRAINBOXES, US842_12, 0),
+ UFTDI_DEV(BRAINBOXES, US842_34, 0),
+ UFTDI_DEV(BRAINBOXES, US842_56, 0),
+ UFTDI_DEV(BRAINBOXES, US842_78, 0),
+ UFTDI_DEV(CONTEC, COM1USBH, 0),
+ UFTDI_DEV(DRESDENELEKTRONIK, SENSORTERMINALBOARD, 0),
+ UFTDI_DEV(DRESDENELEKTRONIK, WIRELESSHANDHELDTERMINAL, 0),
+ UFTDI_DEV(DRESDENELEKTRONIK, DE_RFNODE, 0),
+ UFTDI_DEV(DRESDENELEKTRONIK, LEVELSHIFTERSTICKLOWCOST, 0),
+ UFTDI_DEV(ELEKTOR, FT323R, 0),
+ UFTDI_DEV(EVOLUTION, ER1, 0),
+ UFTDI_DEV(EVOLUTION, HYBRID, 0),
+ UFTDI_DEV(EVOLUTION, RCM4, 0),
+ UFTDI_DEV(FALCOM, SAMBA, 0),
+ UFTDI_DEV(FALCOM, TWIST, 0),
+ UFTDI_DEV(FIC, NEO1973_DEBUG, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FIC, NEO1973_DEBUG, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FTDI, 232EX, 0),
+ UFTDI_DEV(FTDI, 232H, 0),
+ UFTDI_DEV(FTDI, 232RL, 0),
+ UFTDI_DEV(FTDI, 4N_GALAXY_DE_1, 0),
+ UFTDI_DEV(FTDI, 4N_GALAXY_DE_2, 0),
+ UFTDI_DEV(FTDI, 4N_GALAXY_DE_3, 0),
+ UFTDI_DEV(FTDI, 8U232AM_ALT, 0),
+ UFTDI_DEV(FTDI, ACCESSO, 0),
+ UFTDI_DEV(FTDI, ACG_HFDUAL, 0),
+ UFTDI_DEV(FTDI, ACTIVE_ROBOTS, 0),
+ UFTDI_DEV(FTDI, ACTZWAVE, 0),
+ UFTDI_DEV(FTDI, AMC232, 0),
+ UFTDI_DEV(FTDI, ARTEMIS, 0),
+ UFTDI_DEV(FTDI, ASK_RDR400, 0),
+ UFTDI_DEV(FTDI, ATIK_ATK16, 0),
+ UFTDI_DEV(FTDI, ATIK_ATK16C, 0),
+ UFTDI_DEV(FTDI, ATIK_ATK16HR, 0),
+ UFTDI_DEV(FTDI, ATIK_ATK16HRC, 0),
+ UFTDI_DEV(FTDI, ATIK_ATK16IC, 0),
+ UFTDI_DEV(FTDI, BCS_SE923, 0),
+ UFTDI_DEV(FTDI, CANDAPTER, 0),
+ UFTDI_DEV(FTDI, CANUSB, 0),
+ UFTDI_DEV(FTDI, CCSICDU20_0, 0),
+ UFTDI_DEV(FTDI, CCSICDU40_1, 0),
+ UFTDI_DEV(FTDI, CCSICDU64_4, 0),
+ UFTDI_DEV(FTDI, CCSLOAD_N_GO_3, 0),
+ UFTDI_DEV(FTDI, CCSMACHX_2, 0),
+ UFTDI_DEV(FTDI, CCSPRIME8_5, 0),
+ UFTDI_DEV(FTDI, CFA_631, 0),
+ UFTDI_DEV(FTDI, CFA_632, 0),
+ UFTDI_DEV(FTDI, CFA_633, 0),
+ UFTDI_DEV(FTDI, CFA_634, 0),
+ UFTDI_DEV(FTDI, CFA_635, 0),
+ UFTDI_DEV(FTDI, CHAMSYS_24_MASTER_WING, 0),
+ UFTDI_DEV(FTDI, CHAMSYS_MAXI_WING, 0),
+ UFTDI_DEV(FTDI, CHAMSYS_MEDIA_WING, 0),
+ UFTDI_DEV(FTDI, CHAMSYS_MIDI_TIMECODE, 0),
+ UFTDI_DEV(FTDI, CHAMSYS_MINI_WING, 0),
+ UFTDI_DEV(FTDI, CHAMSYS_PC_WING, 0),
+ UFTDI_DEV(FTDI, CHAMSYS_USB_DMX, 0),
+ UFTDI_DEV(FTDI, CHAMSYS_WING, 0),
+ UFTDI_DEV(FTDI, COM4SM, 0),
+ UFTDI_DEV(FTDI, CONVERTER_0, 0),
+ UFTDI_DEV(FTDI, CONVERTER_1, 0),
+ UFTDI_DEV(FTDI, CONVERTER_2, 0),
+ UFTDI_DEV(FTDI, CONVERTER_3, 0),
+ UFTDI_DEV(FTDI, CONVERTER_4, 0),
+ UFTDI_DEV(FTDI, CONVERTER_5, 0),
+ UFTDI_DEV(FTDI, CONVERTER_6, 0),
+ UFTDI_DEV(FTDI, CONVERTER_7, 0),
+ UFTDI_DEV(FTDI, CTI_USB_MINI_485, 0),
+ UFTDI_DEV(FTDI, CTI_USB_NANO_485, 0),
+ UFTDI_DEV(FTDI, DMX4ALL, 0),
+ UFTDI_DEV(FTDI, DOMINTELL_DGQG, 0),
+ UFTDI_DEV(FTDI, DOMINTELL_DUSB, 0),
+ UFTDI_DEV(FTDI, DOTEC, 0),
+ UFTDI_DEV(FTDI, ECLO_COM_1WIRE, 0),
+ UFTDI_DEV(FTDI, ECO_PRO_CDS, 0),
+ UFTDI_DEV(FTDI, EISCOU, 0),
+ UFTDI_DEV(FTDI, ELSTER_UNICOM, 0),
+ UFTDI_DEV(FTDI, ELV_ALC8500, 0),
+ UFTDI_DEV(FTDI, ELV_CLI7000, 0),
+ UFTDI_DEV(FTDI, ELV_CSI8, 0),
+ UFTDI_DEV(FTDI, ELV_EC3000, 0),
+ UFTDI_DEV(FTDI, ELV_EM1000DL, 0),
+ UFTDI_DEV(FTDI, ELV_EM1010PC, 0),
+ UFTDI_DEV(FTDI, ELV_FEM, 0),
+ UFTDI_DEV(FTDI, ELV_FHZ1000PC, 0),
+ UFTDI_DEV(FTDI, ELV_FHZ1300PC, 0),
+ UFTDI_DEV(FTDI, ELV_FM3RX, 0),
+ UFTDI_DEV(FTDI, ELV_FS20SIG, 0),
+ UFTDI_DEV(FTDI, ELV_HS485, 0),
+ UFTDI_DEV(FTDI, ELV_KL100, 0),
+ UFTDI_DEV(FTDI, ELV_MSM1, 0),
+ UFTDI_DEV(FTDI, ELV_PCD200, 0),
+ UFTDI_DEV(FTDI, ELV_PCK100, 0),
+ UFTDI_DEV(FTDI, ELV_PPS7330, 0),
+ UFTDI_DEV(FTDI, ELV_RFP500, 0),
+ UFTDI_DEV(FTDI, ELV_T1100, 0),
+ UFTDI_DEV(FTDI, ELV_TFD128, 0),
+ UFTDI_DEV(FTDI, ELV_TFM100, 0),
+ UFTDI_DEV(FTDI, ELV_TWS550, 0),
+ UFTDI_DEV(FTDI, ELV_UAD8, 0),
+ UFTDI_DEV(FTDI, ELV_UDA7, 0),
+ UFTDI_DEV(FTDI, ELV_UDF77, 0),
+ UFTDI_DEV(FTDI, ELV_UIO88, 0),
+ UFTDI_DEV(FTDI, ELV_ULA200, 0),
+ UFTDI_DEV(FTDI, ELV_UM100, 0),
+ UFTDI_DEV(FTDI, ELV_UMS100, 0),
+ UFTDI_DEV(FTDI, ELV_UO100, 0),
+ UFTDI_DEV(FTDI, ELV_UR100, 0),
+ UFTDI_DEV(FTDI, ELV_USI2, 0),
+ UFTDI_DEV(FTDI, ELV_USR, 0),
+ UFTDI_DEV(FTDI, ELV_UTP8, 0),
+ UFTDI_DEV(FTDI, ELV_WS300PC, 0),
+ UFTDI_DEV(FTDI, ELV_WS444PC, 0),
+ UFTDI_DEV(FTDI, ELV_WS500, 0),
+ UFTDI_DEV(FTDI, ELV_WS550, 0),
+ UFTDI_DEV(FTDI, ELV_WS777, 0),
+ UFTDI_DEV(FTDI, ELV_WS888, 0),
+ UFTDI_DEV(FTDI, EMCU2D, 0),
+ UFTDI_DEV(FTDI, EMCU2H, 0),
+ UFTDI_DEV(FTDI, FUTURE_0, 0),
+ UFTDI_DEV(FTDI, FUTURE_1, 0),
+ UFTDI_DEV(FTDI, FUTURE_2, 0),
+ UFTDI_DEV(FTDI, GAMMASCOUT, 0),
+ UFTDI_DEV(FTDI, GENERIC, 0),
+ UFTDI_DEV(FTDI, GUDEADS_E808, 0),
+ UFTDI_DEV(FTDI, GUDEADS_E809, 0),
+ UFTDI_DEV(FTDI, GUDEADS_E80A, 0),
+ UFTDI_DEV(FTDI, GUDEADS_E80B, 0),
+ UFTDI_DEV(FTDI, GUDEADS_E80C, 0),
+ UFTDI_DEV(FTDI, GUDEADS_E80D, 0),
+ UFTDI_DEV(FTDI, GUDEADS_E80E, 0),
+ UFTDI_DEV(FTDI, GUDEADS_E80F, 0),
+ UFTDI_DEV(FTDI, GUDEADS_E88D, 0),
+ UFTDI_DEV(FTDI, GUDEADS_E88E, 0),
+ UFTDI_DEV(FTDI, GUDEADS_E88F, 0),
+ UFTDI_DEV(FTDI, HD_RADIO, 0),
+ UFTDI_DEV(FTDI, HO720, 0),
+ UFTDI_DEV(FTDI, HO730, 0),
+ UFTDI_DEV(FTDI, HO820, 0),
+ UFTDI_DEV(FTDI, HO870, 0),
+ UFTDI_DEV(FTDI, IBS_APP70, 0),
+ UFTDI_DEV(FTDI, IBS_PCMCIA, 0),
+ UFTDI_DEV(FTDI, IBS_PEDO, 0),
+ UFTDI_DEV(FTDI, IBS_PICPRO, 0),
+ UFTDI_DEV(FTDI, IBS_PK1, 0),
+ UFTDI_DEV(FTDI, IBS_PROD, 0),
+ UFTDI_DEV(FTDI, IBS_RS232MON, 0),
+ UFTDI_DEV(FTDI, IBS_US485, 0),
+ UFTDI_DEV(FTDI, IPLUS, 0),
+ UFTDI_DEV(FTDI, IPLUS2, 0),
+ UFTDI_DEV(FTDI, IRTRANS, 0),
+ UFTDI_DEV(FTDI, KBS, 0),
+ UFTDI_DEV(FTDI, KTLINK, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FTDI, LENZ_LIUSB, 0),
+ UFTDI_DEV(FTDI, LK202, 0),
+ UFTDI_DEV(FTDI, LK204, 0),
+ UFTDI_DEV(FTDI, LM3S_DEVEL_BOARD, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FTDI, LM3S_EVAL_BOARD, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FTDI, LM3S_ICDI_B_BOARD, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FTDI, MASTERDEVEL2, 0),
+ UFTDI_DEV(FTDI, MAXSTREAM, 0),
+ UFTDI_DEV(FTDI, MHAM_DB9, 0),
+ UFTDI_DEV(FTDI, MHAM_IC, 0),
+ UFTDI_DEV(FTDI, MHAM_KW, 0),
+ UFTDI_DEV(FTDI, MHAM_RS232, 0),
+ UFTDI_DEV(FTDI, MHAM_Y6, 0),
+ UFTDI_DEV(FTDI, MHAM_Y8, 0),
+ UFTDI_DEV(FTDI, MHAM_Y9, 0),
+ UFTDI_DEV(FTDI, MHAM_YS, 0),
+ UFTDI_DEV(FTDI, MICRO_CHAMELEON, 0),
+ UFTDI_DEV(FTDI, MTXORB_5, 0),
+ UFTDI_DEV(FTDI, MTXORB_6, 0),
+ UFTDI_DEV(FTDI, MX2_3, 0),
+ UFTDI_DEV(FTDI, MX4_5, 0),
+ UFTDI_DEV(FTDI, NXTCAM, 0),
+ UFTDI_DEV(FTDI, OCEANIC, 0),
+ UFTDI_DEV(FTDI, OOCDLINK, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FTDI, OPENDCC, 0),
+ UFTDI_DEV(FTDI, OPENDCC_GATEWAY, 0),
+ UFTDI_DEV(FTDI, OPENDCC_GBM, 0),
+ UFTDI_DEV(FTDI, OPENDCC_SNIFFER, 0),
+ UFTDI_DEV(FTDI, OPENDCC_THROTTLE, 0),
+ UFTDI_DEV(FTDI, PCDJ_DAC2, 0),
+ UFTDI_DEV(FTDI, PCMSFU, 0),
+ UFTDI_DEV(FTDI, PERLE_ULTRAPORT, 0),
+ UFTDI_DEV(FTDI, PHI_FISCO, 0),
+ UFTDI_DEV(FTDI, PIEGROUP, 0),
+ UFTDI_DEV(FTDI, PROPOX_JTAGCABLEII, 0),
+ UFTDI_DEV(FTDI, R2000KU_TRUE_RNG, 0),
+ UFTDI_DEV(FTDI, R2X0, 0),
+ UFTDI_DEV(FTDI, RELAIS, 0),
+ UFTDI_DEV(FTDI, REU_TINY, 0),
+ UFTDI_DEV(FTDI, RMP200, 0),
+ UFTDI_DEV(FTDI, RM_CANVIEW, 0),
+ UFTDI_DEV(FTDI, RRCIRKITS_LOCOBUFFER, 0),
+ UFTDI_DEV(FTDI, SCIENCESCOPE_HS_LOGBOOK, 0),
+ UFTDI_DEV(FTDI, SCIENCESCOPE_LOGBOOKML, 0),
+ UFTDI_DEV(FTDI, SCIENCESCOPE_LS_LOGBOOK, 0),
+ UFTDI_DEV(FTDI, SCS_DEVICE_0, 0),
+ UFTDI_DEV(FTDI, SCS_DEVICE_1, 0),
+ UFTDI_DEV(FTDI, SCS_DEVICE_2, 0),
+ UFTDI_DEV(FTDI, SCS_DEVICE_3, 0),
+ UFTDI_DEV(FTDI, SCS_DEVICE_4, 0),
+ UFTDI_DEV(FTDI, SCS_DEVICE_5, 0),
+ UFTDI_DEV(FTDI, SCS_DEVICE_6, 0),
+ UFTDI_DEV(FTDI, SCS_DEVICE_7, 0),
+ UFTDI_DEV(FTDI, SCX8_USB_PHOENIX, 0),
+ UFTDI_DEV(FTDI, SDMUSBQSS, 0),
+ UFTDI_DEV(FTDI, SEMC_DSS20, 0),
+ UFTDI_DEV(FTDI, SERIAL_2232C, UFTDI_JTAG_CHECK_STRING),
+ UFTDI_DEV(FTDI, SERIAL_2232D, 0),
+ UFTDI_DEV(FTDI, SERIAL_232RL, 0),
+ UFTDI_DEV(FTDI, SERIAL_4232H, 0),
+ UFTDI_DEV(FTDI, SERIAL_8U100AX, 0),
+ UFTDI_DEV(FTDI, SERIAL_8U232AM, 0),
+ UFTDI_DEV(FTDI, SERIAL_8U232AM4, 0),
+ UFTDI_DEV(FTDI, SIGNALYZER_SH2, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FTDI, SIGNALYZER_SH4, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FTDI, SIGNALYZER_SLITE, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FTDI, SIGNALYZER_ST, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FTDI, SITOP_UPS500S, 0),
+ UFTDI_DEV(FTDI, SPECIAL_1, 0),
+ UFTDI_DEV(FTDI, SPECIAL_3, 0),
+ UFTDI_DEV(FTDI, SPECIAL_4, 0),
+ UFTDI_DEV(FTDI, SPROG_II, 0),
+ UFTDI_DEV(FTDI, SR_RADIO, 0),
+ UFTDI_DEV(FTDI, SUUNTO_SPORTS, 0),
+ UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13M, 0),
+ UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13S, 0),
+ UFTDI_DEV(FTDI, TACTRIX_OPENPORT_13U, 0),
+ UFTDI_DEV(FTDI, TAVIR_STK500, 0),
+ UFTDI_DEV(FTDI, TERATRONIK_D2XX, 0),
+ UFTDI_DEV(FTDI, TERATRONIK_VCP, 0),
+ UFTDI_DEV(FTDI, THORLABS, 0),
+ UFTDI_DEV(FTDI, TIAO, 0),
+ UFTDI_DEV(FTDI, TNC_X, 0),
+ UFTDI_DEV(FTDI, TTUSB, 0),
+ UFTDI_DEV(FTDI, TURTELIZER2, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FTDI, UOPTBR, 0),
+ UFTDI_DEV(FTDI, USBSERIAL, 0),
+ UFTDI_DEV(FTDI, USBX_707, 0),
+ UFTDI_DEV(FTDI, USB_UIRT, 0),
+ UFTDI_DEV(FTDI, USINT_CAT, 0),
+ UFTDI_DEV(FTDI, USINT_RS232, 0),
+ UFTDI_DEV(FTDI, USINT_WKEY, 0),
+ UFTDI_DEV(FTDI, VARDAAN, 0),
+ UFTDI_DEV(FTDI, VNHCPCUSB_D, 0),
+ UFTDI_DEV(FTDI, WESTREX_MODEL_777, 0),
+ UFTDI_DEV(FTDI, WESTREX_MODEL_8900F, 0),
+ UFTDI_DEV(FTDI, XDS100V2, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FTDI, XDS100V3, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(FTDI, XF_547, 0),
+ UFTDI_DEV(FTDI, XF_640, 0),
+ UFTDI_DEV(FTDI, XF_642, 0),
+ UFTDI_DEV(FTDI, XM_RADIO, 0),
+ UFTDI_DEV(FTDI, YEI_SERVOCENTER31, 0),
+ UFTDI_DEV(GNOTOMETRICS, USB, 0),
+ UFTDI_DEV(ICOM, SP1, 0),
+ UFTDI_DEV(ICOM, OPC_U_UC, 0),
+ UFTDI_DEV(ICOM, RP2C1, 0),
+ UFTDI_DEV(ICOM, RP2C2, 0),
+ UFTDI_DEV(ICOM, RP2D, 0),
+ UFTDI_DEV(ICOM, RP2KVR, 0),
+ UFTDI_DEV(ICOM, RP2KVT, 0),
+ UFTDI_DEV(ICOM, RP2VR, 0),
+ UFTDI_DEV(ICOM, RP2VT, 0),
+ UFTDI_DEV(ICOM, RP4KVR, 0),
+ UFTDI_DEV(ICOM, RP4KVT, 0),
+ UFTDI_DEV(IDTECH, IDT1221U, 0),
+ UFTDI_DEV(INTERBIOMETRICS, IOBOARD, 0),
+ UFTDI_DEV(INTERBIOMETRICS, MINI_IOBOARD, 0),
+ UFTDI_DEV(INTREPIDCS, NEOVI, 0),
+ UFTDI_DEV(INTREPIDCS, VALUECAN, 0),
+ UFTDI_DEV(IONICS, PLUGCOMPUTER, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(JETI, SPC1201, 0),
+ UFTDI_DEV(KOBIL, CONV_B1, 0),
+ UFTDI_DEV(KOBIL, CONV_KAAN, 0),
+ UFTDI_DEV(LARSENBRUSGAARD, ALTITRACK, 0),
+ UFTDI_DEV(MARVELL, SHEEVAPLUG, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0100, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0101, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0102, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0103, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0104, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0105, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0106, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0107, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0108, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0109, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010A, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010B, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010C, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010D, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010E, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_010F, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0110, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0111, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0112, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0113, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0114, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0115, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0116, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0117, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0118, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0119, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011A, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011B, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011C, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011D, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011E, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_011F, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0120, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0121, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0122, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0123, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0124, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0125, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0126, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0128, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0129, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012A, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012B, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012D, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012E, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_012F, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0130, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0131, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0132, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0133, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0134, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0135, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0136, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0137, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0138, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0139, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013A, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013B, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013C, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013D, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013E, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_013F, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0140, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0141, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0142, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0143, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0144, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0145, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0146, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0147, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0148, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0149, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014A, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014B, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014C, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014D, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014E, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_014F, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0150, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0151, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0152, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0159, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015A, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015B, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015C, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015D, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015E, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_015F, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0160, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0161, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0162, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0163, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0164, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0165, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0166, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0167, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0168, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0169, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016A, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016B, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016C, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016D, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016E, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_016F, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0170, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0171, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0172, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0173, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0174, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0175, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0176, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0177, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0178, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0179, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017A, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017B, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017C, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017D, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017E, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_017F, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0180, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0181, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0182, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0183, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0184, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0185, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0186, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0187, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0188, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0189, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018A, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018B, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018C, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018D, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018E, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_018F, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0190, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0191, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0192, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0193, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0194, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0195, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0196, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0197, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0198, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_0199, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019A, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019B, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019C, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019D, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019E, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_019F, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A0, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A1, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A2, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A3, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A4, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A5, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A6, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A7, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A8, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01A9, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AA, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AB, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AC, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AD, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AE, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01AF, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B0, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B1, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B2, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B3, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B4, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B5, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B6, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B7, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B8, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01B9, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BA, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BB, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BC, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BD, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BE, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01BF, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C0, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C1, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C2, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C3, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C4, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C5, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C6, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C7, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C8, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01C9, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CA, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CB, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CC, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CD, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CE, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01CF, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D0, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D1, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D2, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D3, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D4, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D5, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D6, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D7, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D8, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01D9, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DA, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DB, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DC, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DD, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DE, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01DF, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E0, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E1, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E2, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E3, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E4, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E5, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E6, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E7, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E8, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01E9, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EA, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EB, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EC, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01ED, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EE, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01EF, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F0, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F1, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F2, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F3, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F4, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F5, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F6, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F7, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F8, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01F9, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FA, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FB, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FC, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FD, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FE, 0),
+ UFTDI_DEV(MATRIXORBITAL, FTDI_RANGE_01FF, 0),
+ UFTDI_DEV(MATRIXORBITAL, MOUA, 0),
+ UFTDI_DEV(MELCO, PCOPRS1, 0),
+ UFTDI_DEV(METAGEEK, TELLSTICK, 0),
+ UFTDI_DEV(MOBILITY, USB_SERIAL, 0),
+ UFTDI_DEV(OLIMEX, ARM_USB_OCD, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(OLIMEX, ARM_USB_OCD_H, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(OPTO, CRD7734, 0),
+ UFTDI_DEV(OPTO, CRD7734_1, 0),
+ UFTDI_DEV(PAPOUCH, AD4USB, 0),
+ UFTDI_DEV(PAPOUCH, AP485, 0),
+ UFTDI_DEV(PAPOUCH, AP485_2, 0),
+ UFTDI_DEV(PAPOUCH, DRAK5, 0),
+ UFTDI_DEV(PAPOUCH, DRAK6, 0),
+ UFTDI_DEV(PAPOUCH, GMSR, 0),
+ UFTDI_DEV(PAPOUCH, GMUX, 0),
+ UFTDI_DEV(PAPOUCH, IRAMP, 0),
+ UFTDI_DEV(PAPOUCH, LEC, 0),
+ UFTDI_DEV(PAPOUCH, MU, 0),
+ UFTDI_DEV(PAPOUCH, QUIDO10X1, 0),
+ UFTDI_DEV(PAPOUCH, QUIDO2X16, 0),
+ UFTDI_DEV(PAPOUCH, QUIDO2X2, 0),
+ UFTDI_DEV(PAPOUCH, QUIDO30X3, 0),
+ UFTDI_DEV(PAPOUCH, QUIDO3X32, 0),
+ UFTDI_DEV(PAPOUCH, QUIDO4X4, 0),
+ UFTDI_DEV(PAPOUCH, QUIDO60X3, 0),
+ UFTDI_DEV(PAPOUCH, QUIDO8X8, 0),
+ UFTDI_DEV(PAPOUCH, SB232, 0),
+ UFTDI_DEV(PAPOUCH, SB422, 0),
+ UFTDI_DEV(PAPOUCH, SB422_2, 0),
+ UFTDI_DEV(PAPOUCH, SB485, 0),
+ UFTDI_DEV(PAPOUCH, SB485C, 0),
+ UFTDI_DEV(PAPOUCH, SB485S, 0),
+ UFTDI_DEV(PAPOUCH, SB485_2, 0),
+ UFTDI_DEV(PAPOUCH, SIMUKEY, 0),
+ UFTDI_DEV(PAPOUCH, TMU, 0),
+ UFTDI_DEV(PAPOUCH, UPSUSB, 0),
+ UFTDI_DEV(POSIFLEX, PP7000, 0),
+ UFTDI_DEV(QIHARDWARE, JTAGSERIAL, UFTDI_JTAG_IFACE(0)),
+ UFTDI_DEV(RATOC, REXUSB60F, 0),
+ UFTDI_DEV(RTSYSTEMS, CT29B, 0),
+ UFTDI_DEV(RTSYSTEMS, SERIAL_VX7, 0),
+ UFTDI_DEV(SEALEVEL, 2101, 0),
+ UFTDI_DEV(SEALEVEL, 2102, 0),
+ UFTDI_DEV(SEALEVEL, 2103, 0),
+ UFTDI_DEV(SEALEVEL, 2104, 0),
+ UFTDI_DEV(SEALEVEL, 2106, 0),
+ UFTDI_DEV(SEALEVEL, 2201_1, 0),
+ UFTDI_DEV(SEALEVEL, 2201_2, 0),
+ UFTDI_DEV(SEALEVEL, 2202_1, 0),
+ UFTDI_DEV(SEALEVEL, 2202_2, 0),
+ UFTDI_DEV(SEALEVEL, 2203_1, 0),
+ UFTDI_DEV(SEALEVEL, 2203_2, 0),
+ UFTDI_DEV(SEALEVEL, 2401_1, 0),
+ UFTDI_DEV(SEALEVEL, 2401_2, 0),
+ UFTDI_DEV(SEALEVEL, 2401_3, 0),
+ UFTDI_DEV(SEALEVEL, 2401_4, 0),
+ UFTDI_DEV(SEALEVEL, 2402_1, 0),
+ UFTDI_DEV(SEALEVEL, 2402_2, 0),
+ UFTDI_DEV(SEALEVEL, 2402_3, 0),
+ UFTDI_DEV(SEALEVEL, 2402_4, 0),
+ UFTDI_DEV(SEALEVEL, 2403_1, 0),
+ UFTDI_DEV(SEALEVEL, 2403_2, 0),
+ UFTDI_DEV(SEALEVEL, 2403_3, 0),
+ UFTDI_DEV(SEALEVEL, 2403_4, 0),
+ UFTDI_DEV(SEALEVEL, 2801_1, 0),
+ UFTDI_DEV(SEALEVEL, 2801_2, 0),
+ UFTDI_DEV(SEALEVEL, 2801_3, 0),
+ UFTDI_DEV(SEALEVEL, 2801_4, 0),
+ UFTDI_DEV(SEALEVEL, 2801_5, 0),
+ UFTDI_DEV(SEALEVEL, 2801_6, 0),
+ UFTDI_DEV(SEALEVEL, 2801_7, 0),
+ UFTDI_DEV(SEALEVEL, 2801_8, 0),
+ UFTDI_DEV(SEALEVEL, 2802_1, 0),
+ UFTDI_DEV(SEALEVEL, 2802_2, 0),
+ UFTDI_DEV(SEALEVEL, 2802_3, 0),
+ UFTDI_DEV(SEALEVEL, 2802_4, 0),
+ UFTDI_DEV(SEALEVEL, 2802_5, 0),
+ UFTDI_DEV(SEALEVEL, 2802_6, 0),
+ UFTDI_DEV(SEALEVEL, 2802_7, 0),
+ UFTDI_DEV(SEALEVEL, 2802_8, 0),
+ UFTDI_DEV(SEALEVEL, 2803_1, 0),
+ UFTDI_DEV(SEALEVEL, 2803_2, 0),
+ UFTDI_DEV(SEALEVEL, 2803_3, 0),
+ UFTDI_DEV(SEALEVEL, 2803_4, 0),
+ UFTDI_DEV(SEALEVEL, 2803_5, 0),
+ UFTDI_DEV(SEALEVEL, 2803_6, 0),
+ UFTDI_DEV(SEALEVEL, 2803_7, 0),
+ UFTDI_DEV(SEALEVEL, 2803_8, 0),
+ UFTDI_DEV(SIIG2, DK201, 0),
+ UFTDI_DEV(SIIG2, US2308, 0),
+ UFTDI_DEV(TESTO, USB_INTERFACE, 0),
+ UFTDI_DEV(TML, USB_SERIAL, 0),
+ UFTDI_DEV(TTI, QL355P, 0),
+ UFTDI_DEV(UBLOX, XPLR_M9, 0),
+ UFTDI_DEV(UNKNOWN4, NF_RIC, 0),
+#undef UFTDI_DEV
+};
+
+DRIVER_MODULE(uftdi, uhub, uftdi_driver, NULL, NULL);
+MODULE_DEPEND(uftdi, ucom, 1, 1, 1);
+MODULE_DEPEND(uftdi, usb, 1, 1, 1);
+MODULE_VERSION(uftdi, 1);
+USB_PNP_HOST_INFO(uftdi_devs);
+
+/*
+ * Jtag product name strings table. Some products have one or more interfaces
+ * dedicated to jtag or gpio, but use a product ID that's the same as other
+ * products which don't. They are marked with a flag in the table above, and
+ * the following string table is checked for flagged products. The string check
+ * is done with strstr(); in effect there is an implicit wildcard at the
+ * beginning and end of each product name string in table.
+ */
+static const struct jtag_by_name {
+ const char * product_name;
+ uint32_t jtag_interfaces;
+} jtag_products_by_name[] = {
+ /* TI Beaglebone and TI XDS100Vn jtag product line. */
+ {"XDS100V", UFTDI_JTAG_IFACE(0)},
+};
+
+/*
+ * Set up a sysctl and tunable to en/disable the feature of skipping the
+ * creation of tty devices for jtag interfaces. Enabled by default.
+ */
+static int skip_jtag_interfaces = 1;
+SYSCTL_INT(_hw_usb_uftdi, OID_AUTO, skip_jtag_interfaces, CTLFLAG_RWTUN,
+ &skip_jtag_interfaces, 1, "Skip creating tty devices for jtag interfaces");
+
+static boolean_t
+is_jtag_interface(struct usb_attach_arg *uaa, const struct usb_device_id *id)
+{
+ int i, iface_bit;
+ const char * product_name;
+ const struct jtag_by_name *jbn;
+
+ /* We only allocate 8 flag bits for jtag interface flags. */
+ if (uaa->info.bIfaceIndex >= UFTDI_JTAG_IFACES_MAX)
+ return (0);
+ iface_bit = UFTDI_JTAG_IFACE(uaa->info.bIfaceIndex);
+
+ /*
+ * If requested, search the name strings table and use the interface
+ * bits from that table when the product name string matches, else use
+ * the jtag interface bits from the main ID table.
+ */
+ if ((id->driver_info & UFTDI_JTAG_MASK) == UFTDI_JTAG_CHECK_STRING) {
+ product_name = usb_get_product(uaa->device);
+ for (i = 0; i < nitems(jtag_products_by_name); i++) {
+ jbn = &jtag_products_by_name[i];
+ if (strstr(product_name, jbn->product_name) != NULL &&
+ (jbn->jtag_interfaces & iface_bit) != 0)
+ return (1);
+ }
+ } else if ((id->driver_info & iface_bit) != 0)
+ return (1);
+
+ return (0);
+}
+
+/*
+ * Set up softc fields whose value depends on the device type.
+ *
+ * Note that the 2232C and 2232D devices are the same for our purposes. In the
+ * silicon the difference is that the D series has CPU FIFO mode and C doesn't.
+ * I haven't found any way of determining the C/D difference from info provided
+ * by the chip other than trying to set CPU FIFO mode and having it work or not.
+ *
+ * Due to a hardware bug, a 232B chip without an eeprom reports itself as a
+ * 232A, but if the serial number is also zero we know it's really a 232B.
+ */
+static void
+uftdi_devtype_setup(struct uftdi_softc *sc, struct usb_attach_arg *uaa)
+{
+ struct usb_device_descriptor *dd;
+
+ sc->sc_bcdDevice = uaa->info.bcdDevice;
+
+ switch (uaa->info.bcdDevice) {
+ case 0x200:
+ dd = usbd_get_device_descriptor(sc->sc_udev);
+ if (dd->iSerialNumber == 0) {
+ sc->sc_devtype = DEVT_232B;
+ } else {
+ sc->sc_devtype = DEVT_232A;
+ }
+ sc->sc_ucom.sc_portno = 0;
+ break;
+ case 0x400:
+ sc->sc_devtype = DEVT_232B;
+ sc->sc_ucom.sc_portno = 0;
+ break;
+ case 0x500:
+ sc->sc_devtype = DEVT_2232D;
+ sc->sc_devflags |= DEVF_BAUDBITS_HINDEX;
+ sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum;
+ break;
+ case 0x600:
+ sc->sc_devtype = DEVT_232R;
+ sc->sc_ucom.sc_portno = 0;
+ break;
+ case 0x700:
+ sc->sc_devtype = DEVT_2232H;
+ sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M;
+ sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum;
+ break;
+ case 0x800:
+ sc->sc_devtype = DEVT_4232H;
+ sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M;
+ sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum;
+ break;
+ case 0x900:
+ sc->sc_devtype = DEVT_232H;
+ sc->sc_devflags |= DEVF_BAUDBITS_HINDEX | DEVF_BAUDCLK_12M;
+ sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum;
+ break;
+ case 0x1000:
+ sc->sc_devtype = DEVT_230X;
+ sc->sc_devflags |= DEVF_BAUDBITS_HINDEX;
+ sc->sc_ucom.sc_portno = FTDI_PIT_SIOA + uaa->info.bIfaceNum;
+ break;
+ default:
+ if (uaa->info.bcdDevice < 0x200) {
+ sc->sc_devtype = DEVT_SIO;
+ sc->sc_hdrlen = 1;
+ } else {
+ sc->sc_devtype = DEVT_232R;
+ device_printf(sc->sc_dev, "Warning: unknown FTDI "
+ "device type, bcdDevice=0x%04x, assuming 232R\n",
+ uaa->info.bcdDevice);
+ }
+ sc->sc_ucom.sc_portno = 0;
+ break;
+ }
+}
+
+static int
+uftdi_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ const struct usb_device_id *id;
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != UFTDI_CONFIG_INDEX) {
+ return (ENXIO);
+ }
+
+ /*
+ * Attach to all present interfaces unless this is a JTAG one, which
+ * we leave for userland.
+ */
+ id = usbd_lookup_id_by_info(uftdi_devs, sizeof(uftdi_devs),
+ &uaa->info);
+ if (id == NULL)
+ return (ENXIO);
+ if (skip_jtag_interfaces && is_jtag_interface(uaa, id)) {
+ printf("%s: skipping JTAG interface #%d for '%s' at %u.%u\n",
+ device_get_name(dev),
+ uaa->info.bIfaceIndex,
+ usb_get_product(uaa->device),
+ usbd_get_bus_index(uaa->device),
+ usbd_get_device_index(uaa->device));
+ return (ENXIO);
+ }
+ uaa->driver_info = id->driver_info;
+ return (BUS_PROBE_SPECIFIC);
+}
+
+static int
+uftdi_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct uftdi_softc *sc = device_get_softc(dev);
+ int error;
+
+ DPRINTF("\n");
+
+ sc->sc_udev = uaa->device;
+ sc->sc_dev = dev;
+ sc->sc_unit = device_get_unit(dev);
+ sc->sc_bitmode = UFTDI_BITMODE_NONE;
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "uftdi", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ uftdi_devtype_setup(sc, uaa);
+
+ error = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer, uftdi_config,
+ UFTDI_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error) {
+ device_printf(dev, "allocating USB "
+ "transfers failed\n");
+ goto detach;
+ }
+ /* clear stall at first run */
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_WR]);
+ usbd_xfer_set_stall(sc->sc_xfer[UFTDI_BULK_DT_RD]);
+ mtx_unlock(&sc->sc_mtx);
+
+ /* set a valid "lcr" value */
+
+ sc->sc_last_lcr =
+ (FTDI_SIO_SET_DATA_STOP_BITS_2 |
+ FTDI_SIO_SET_DATA_PARITY_NONE |
+ FTDI_SIO_SET_DATA_BITS(8));
+
+ /* Indicate tx bits in sc_lsr can be used to determine busy vs idle. */
+ ucom_use_lsr_txbits(&sc->sc_ucom);
+
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &uftdi_callback, &sc->sc_mtx);
+ if (error) {
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ return (0); /* success */
+
+detach:
+ uftdi_detach(dev);
+ return (ENXIO);
+}
+
+static int
+uftdi_detach(device_t dev)
+{
+ struct uftdi_softc *sc = device_get_softc(dev);
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, UFTDI_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ uftdi_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(uftdi);
+
+static void
+uftdi_free_softc(struct uftdi_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+uftdi_free(struct ucom_softc *ucom)
+{
+ uftdi_free_softc(ucom->sc_parent);
+}
+
+static void
+uftdi_cfg_open(struct ucom_softc *ucom)
+{
+
+ /*
+ * This do-nothing open routine exists for the sole purpose of this
+ * DPRINTF() so that you can see the point at which open gets called
+ * when debugging is enabled.
+ */
+ DPRINTF("\n");
+}
+
+static void
+uftdi_cfg_close(struct ucom_softc *ucom)
+{
+
+ /*
+ * This do-nothing close routine exists for the sole purpose of this
+ * DPRINTF() so that you can see the point at which close gets called
+ * when debugging is enabled.
+ */
+ DPRINTF("\n");
+}
+
+static void
+uftdi_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uftdi_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t pktlen;
+ uint32_t buflen;
+ uint8_t buf[1];
+
+ DPRINTFN(3, "\n");
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ /*
+ * If output packets don't require headers (the common case) we
+ * can just load the buffer up with payload bytes all at once.
+ * Otherwise, loop to format packets into the buffer while there
+ * is data available, and room for a packet header and at least
+ * one byte of payload.
+ *
+ * NOTE: The FTDI chip doesn't accept zero length
+ * packets. This cannot happen because the "pktlen"
+ * will always be non-zero when "ucom_get_data()"
+ * returns non-zero which we check below.
+ */
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (sc->sc_hdrlen == 0) {
+ if (ucom_get_data(&sc->sc_ucom, pc, 0, UFTDI_OBUFSIZE,
+ &buflen) == 0)
+ break;
+ } else {
+ buflen = 0;
+ while (buflen < UFTDI_OBUFSIZE - sc->sc_hdrlen - 1 &&
+ ucom_get_data(&sc->sc_ucom, pc, buflen +
+ sc->sc_hdrlen, UFTDI_OPKTSIZE - sc->sc_hdrlen,
+ &pktlen) != 0) {
+ buf[0] = FTDI_OUT_TAG(pktlen,
+ sc->sc_ucom.sc_portno);
+ usbd_copy_in(pc, buflen, buf, 1);
+ buflen += pktlen + sc->sc_hdrlen;
+ }
+ }
+ if (buflen != 0) {
+ usbd_xfer_set_frame_len(xfer, 0, buflen);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+uftdi_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uftdi_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint8_t buf[2];
+ uint8_t ftdi_msr;
+ uint8_t msr;
+ uint8_t lsr;
+ int buflen;
+ int pktlen;
+ int pktmax;
+ int offset;
+
+ DPRINTFN(3, "\n");
+
+ usbd_xfer_status(xfer, &buflen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (buflen < UFTDI_IHDRSIZE)
+ goto tr_setup;
+ pc = usbd_xfer_get_frame(xfer, 0);
+ pktmax = xfer->max_packet_size - UFTDI_IHDRSIZE;
+ lsr = 0;
+ msr = 0;
+ offset = 0;
+ /*
+ * Extract packet headers and payload bytes from the buffer.
+ * Feed payload bytes to ucom/tty layer; OR-accumulate the
+ * receiver-related header status bits which are transient and
+ * could toggle with each packet, but for transmitter-related
+ * bits keep only the ones from the last packet.
+ *
+ * After processing all packets in the buffer, process the
+ * accumulated transient MSR and LSR values along with the
+ * non-transient bits from the last packet header.
+ */
+ while (buflen >= UFTDI_IHDRSIZE) {
+ usbd_copy_out(pc, offset, buf, UFTDI_IHDRSIZE);
+ offset += UFTDI_IHDRSIZE;
+ buflen -= UFTDI_IHDRSIZE;
+ lsr &= ~(ULSR_TXRDY | ULSR_TSRE);
+ lsr |= FTDI_GET_LSR(buf);
+ if (FTDI_GET_MSR(buf) & FTDI_SIO_RI_MASK)
+ msr |= SER_RI;
+ pktlen = min(buflen, pktmax);
+ if (pktlen != 0) {
+ ucom_put_data(&sc->sc_ucom, pc, offset,
+ pktlen);
+ offset += pktlen;
+ buflen -= pktlen;
+ }
+ }
+ ftdi_msr = FTDI_GET_MSR(buf);
+
+ if (ftdi_msr & FTDI_SIO_CTS_MASK)
+ msr |= SER_CTS;
+ if (ftdi_msr & FTDI_SIO_DSR_MASK)
+ msr |= SER_DSR;
+ if (ftdi_msr & FTDI_SIO_RI_MASK)
+ msr |= SER_RI;
+ if (ftdi_msr & FTDI_SIO_RLSD_MASK)
+ msr |= SER_DCD;
+
+ if (sc->sc_msr != msr || sc->sc_lsr != lsr) {
+ DPRINTF("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);
+ }
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uftdi_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ uint16_t wIndex = ucom->sc_portno;
+ uint16_t wValue;
+ struct usb_device_request req;
+
+ DPRINTFN(2, "DTR=%u\n", onoff);
+
+ wValue = onoff ? FTDI_SIO_SET_DTR_HIGH : FTDI_SIO_SET_DTR_LOW;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_MODEM_CTRL;
+ USETW(req.wValue, wValue);
+ USETW(req.wIndex, wIndex);
+ USETW(req.wLength, 0);
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+static void
+uftdi_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ uint16_t wIndex = ucom->sc_portno;
+ uint16_t wValue;
+ struct usb_device_request req;
+
+ DPRINTFN(2, "RTS=%u\n", onoff);
+
+ wValue = onoff ? FTDI_SIO_SET_RTS_HIGH : FTDI_SIO_SET_RTS_LOW;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_MODEM_CTRL;
+ USETW(req.wValue, wValue);
+ USETW(req.wIndex, wIndex);
+ USETW(req.wLength, 0);
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+static void
+uftdi_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ uint16_t wIndex = ucom->sc_portno;
+ uint16_t wValue;
+ struct usb_device_request req;
+
+ DPRINTFN(2, "BREAK=%u\n", onoff);
+
+ if (onoff) {
+ sc->sc_last_lcr |= FTDI_SIO_SET_BREAK;
+ } else {
+ sc->sc_last_lcr &= ~FTDI_SIO_SET_BREAK;
+ }
+
+ wValue = sc->sc_last_lcr;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_SET_DATA;
+ USETW(req.wValue, wValue);
+ USETW(req.wIndex, wIndex);
+ USETW(req.wLength, 0);
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+/*
+ * Return true if the given speed is within operational tolerance of the target
+ * speed. FTDI recommends that the hardware speed be within 3% of nominal.
+ */
+static inline boolean_t
+uftdi_baud_within_tolerance(uint64_t speed, uint64_t target)
+{
+ return ((speed >= (target * 100) / 103) &&
+ (speed <= (target * 100) / 97));
+}
+
+static int
+uftdi_sio_encode_baudrate(struct uftdi_softc *sc, speed_t speed,
+ struct uftdi_param_config *cfg)
+{
+ u_int i;
+ const speed_t sio_speeds[] = {
+ 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
+ };
+
+ /*
+ * The original SIO chips were limited to a small choice of speeds
+ * listed in an internal table of speeds chosen by an index value.
+ */
+ for (i = 0; i < nitems(sio_speeds); ++i) {
+ if (speed == sio_speeds[i]) {
+ cfg->baud_lobits = i;
+ cfg->baud_hibits = 0;
+ return (0);
+ }
+ }
+ return (ERANGE);
+}
+
+static int
+uftdi_encode_baudrate(struct uftdi_softc *sc, speed_t speed,
+ struct uftdi_param_config *cfg)
+{
+ static const uint8_t encoded_fraction[8] = {0, 3, 2, 4, 1, 5, 6, 7};
+ static const uint8_t roundoff_232a[16] = {
+ 0, 1, 0, 1, 0, -1, 2, 1,
+ 0, -1, -2, -3, 4, 3, 2, 1,
+ };
+ uint32_t clk, divisor, fastclk_flag, frac, hwspeed;
+
+ /*
+ * If this chip has the fast clock capability and the speed is within
+ * range, use the 12MHz clock, otherwise the standard clock is 3MHz.
+ */
+ if ((sc->sc_devflags & DEVF_BAUDCLK_12M) && speed >= 1200) {
+ clk = 12000000;
+ fastclk_flag = (1 << 17);
+ } else {
+ clk = 3000000;
+ fastclk_flag = 0;
+ }
+
+ /*
+ * Make sure the requested speed is reachable with the available clock
+ * and a 14-bit divisor.
+ */
+ if (speed < (clk >> 14) || speed > clk)
+ return (ERANGE);
+
+ /*
+ * Calculate the divisor, initially yielding a fixed point number with a
+ * 4-bit (1/16ths) fraction, then round it to the nearest fraction the
+ * hardware can handle. When the integral part of the divisor is
+ * greater than one, the fractional part is in 1/8ths of the base clock.
+ * The FT8U232AM chips can handle only 0.125, 0.250, and 0.5 fractions.
+ * Later chips can handle all 1/8th fractions.
+ *
+ * If the integral part of the divisor is 1, a special rule applies: the
+ * fractional part can only be .0 or .5 (this is a limitation of the
+ * hardware). We handle this by truncating the fraction rather than
+ * rounding, because this only applies to the two fastest speeds the
+ * chip can achieve and rounding doesn't matter, either you've asked for
+ * that exact speed or you've asked for something the chip can't do.
+ *
+ * For the FT8U232AM chips, use a roundoff table to adjust the result
+ * to the nearest 1/8th fraction that is supported by the hardware,
+ * leaving a fixed-point number with a 3-bit fraction which exactly
+ * represents the math the hardware divider will do. For later-series
+ * chips that support all 8 fractional divisors, just round 16ths to
+ * 8ths by adding 1 and dividing by 2.
+ */
+ divisor = (clk << 4) / speed;
+ if ((divisor & 0xf) == 1)
+ divisor &= 0xfffffff8;
+ else if (sc->sc_devtype == DEVT_232A)
+ divisor += roundoff_232a[divisor & 0x0f];
+ else
+ divisor += 1; /* Rounds odd 16ths up to next 8th. */
+ divisor >>= 1;
+
+ /*
+ * Ensure the resulting hardware speed will be within operational
+ * tolerance (within 3% of nominal).
+ */
+ hwspeed = (clk << 3) / divisor;
+ if (!uftdi_baud_within_tolerance(hwspeed, speed))
+ return (ERANGE);
+
+ /*
+ * Re-pack the divisor into hardware format. The lower 14-bits hold the
+ * integral part, while the upper bits specify the fraction by indexing
+ * a table of fractions within the hardware which is laid out as:
+ * {0.0, 0.5, 0.25, 0.125, 0.325, 0.625, 0.725, 0.875}
+ * The A-series chips only have the first four table entries; the
+ * roundoff table logic above ensures that the fractional part for those
+ * chips will be one of the first four values.
+ *
+ * When the divisor is 1 a special encoding applies: 1.0 is encoded as
+ * 0.0, and 1.5 is encoded as 1.0. The rounding logic above has already
+ * ensured that the fraction is either .0 or .5 if the integral is 1.
+ */
+ frac = divisor & 0x07;
+ divisor >>= 3;
+ if (divisor == 1) {
+ if (frac == 0)
+ divisor = 0; /* 1.0 becomes 0.0 */
+ else
+ frac = 0; /* 1.5 becomes 1.0 */
+ }
+ divisor |= (encoded_fraction[frac] << 14) | fastclk_flag;
+
+ cfg->baud_lobits = (uint16_t)divisor;
+ cfg->baud_hibits = (uint16_t)(divisor >> 16);
+
+ /*
+ * If this chip requires the baud bits to be in the high byte of the
+ * index word, move the bits up to that location.
+ */
+ if (sc->sc_devflags & DEVF_BAUDBITS_HINDEX) {
+ cfg->baud_hibits <<= 8;
+ }
+
+ return (0);
+}
+
+static int
+uftdi_set_parm_soft(struct ucom_softc *ucom, struct termios *t,
+ struct uftdi_param_config *cfg)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ int err;
+
+ memset(cfg, 0, sizeof(*cfg));
+
+ if (sc->sc_devtype == DEVT_SIO)
+ err = uftdi_sio_encode_baudrate(sc, t->c_ospeed, cfg);
+ else
+ err = uftdi_encode_baudrate(sc, t->c_ospeed, cfg);
+ if (err != 0)
+ return (err);
+
+ if (t->c_cflag & CSTOPB)
+ cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_2;
+ else
+ cfg->lcr = FTDI_SIO_SET_DATA_STOP_BITS_1;
+
+ if (t->c_cflag & PARENB) {
+ if (t->c_cflag & PARODD) {
+ cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_ODD;
+ } else {
+ cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_EVEN;
+ }
+ } else {
+ cfg->lcr |= FTDI_SIO_SET_DATA_PARITY_NONE;
+ }
+
+ switch (t->c_cflag & CSIZE) {
+ case CS5:
+ cfg->lcr |= FTDI_SIO_SET_DATA_BITS(5);
+ break;
+
+ case CS6:
+ cfg->lcr |= FTDI_SIO_SET_DATA_BITS(6);
+ break;
+
+ case CS7:
+ cfg->lcr |= FTDI_SIO_SET_DATA_BITS(7);
+ break;
+
+ case CS8:
+ cfg->lcr |= FTDI_SIO_SET_DATA_BITS(8);
+ break;
+ }
+
+ if (t->c_cflag & CRTSCTS) {
+ cfg->v_flow = FTDI_SIO_RTS_CTS_HS;
+ } else if (t->c_iflag & (IXON | IXOFF)) {
+ cfg->v_flow = FTDI_SIO_XON_XOFF_HS;
+ cfg->v_start = t->c_cc[VSTART];
+ cfg->v_stop = t->c_cc[VSTOP];
+ } else {
+ cfg->v_flow = FTDI_SIO_DISABLE_FLOW_CTRL;
+ }
+
+ return (0);
+}
+
+static int
+uftdi_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct uftdi_param_config cfg;
+
+ DPRINTF("\n");
+
+ return (uftdi_set_parm_soft(ucom, t, &cfg));
+}
+
+static void
+uftdi_cfg_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ uint16_t wIndex = ucom->sc_portno;
+ struct uftdi_param_config cfg;
+ struct usb_device_request req;
+
+ DPRINTF("\n");
+
+ if (uftdi_set_parm_soft(ucom, t, &cfg)) {
+ /* should not happen */
+ return;
+ }
+ sc->sc_last_lcr = cfg.lcr;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_SET_BAUD_RATE;
+ USETW(req.wValue, cfg.baud_lobits);
+ USETW(req.wIndex, cfg.baud_hibits | wIndex);
+ USETW(req.wLength, 0);
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_SET_DATA;
+ USETW(req.wValue, cfg.lcr);
+ USETW(req.wIndex, wIndex);
+ USETW(req.wLength, 0);
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_SET_FLOW_CTRL;
+ USETW2(req.wValue, cfg.v_stop, cfg.v_start);
+ USETW2(req.wIndex, cfg.v_flow, wIndex);
+ USETW(req.wLength, 0);
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+static void
+uftdi_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+
+ DPRINTFN(3, "msr=0x%02x lsr=0x%02x\n", sc->sc_msr, sc->sc_lsr);
+
+ *msr = sc->sc_msr;
+ *lsr = sc->sc_lsr;
+}
+
+static int
+uftdi_reset(struct ucom_softc *ucom, int reset_type)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ usb_device_request_t req;
+
+ DPRINTFN(2, "\n");
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_RESET;
+
+ USETW(req.wIndex, sc->sc_ucom.sc_portno);
+ USETW(req.wLength, 0);
+ USETW(req.wValue, reset_type);
+
+ return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL));
+}
+
+static int
+uftdi_set_bitmode(struct ucom_softc *ucom, uint8_t bitmode, uint8_t iomask)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ usb_device_request_t req;
+ int rv;
+
+ DPRINTFN(2, "\n");
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_SET_BITMODE;
+
+ USETW(req.wIndex, sc->sc_ucom.sc_portno);
+ USETW(req.wLength, 0);
+
+ if (bitmode == UFTDI_BITMODE_NONE)
+ USETW2(req.wValue, 0, 0);
+ else
+ USETW2(req.wValue, (1 << bitmode), iomask);
+
+ rv = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL);
+ if (rv == USB_ERR_NORMAL_COMPLETION)
+ sc->sc_bitmode = bitmode;
+
+ return (rv);
+}
+
+static int
+uftdi_get_bitmode(struct ucom_softc *ucom, uint8_t *bitmode, uint8_t *iomask)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ usb_device_request_t req;
+
+ DPRINTFN(2, "\n");
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_GET_BITMODE;
+
+ USETW(req.wIndex, sc->sc_ucom.sc_portno);
+ USETW(req.wLength, 1);
+ USETW(req.wValue, 0);
+
+ *bitmode = sc->sc_bitmode;
+ return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, iomask));
+}
+
+static int
+uftdi_set_latency(struct ucom_softc *ucom, int latency)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ usb_device_request_t req;
+
+ DPRINTFN(2, "\n");
+
+ if (latency < 0 || latency > 255)
+ return (USB_ERR_INVAL);
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_SET_LATENCY;
+
+ USETW(req.wIndex, sc->sc_ucom.sc_portno);
+ USETW(req.wLength, 0);
+ USETW2(req.wValue, 0, latency);
+
+ return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL));
+}
+
+static int
+uftdi_get_latency(struct ucom_softc *ucom, int *latency)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ usb_device_request_t req;
+ usb_error_t err;
+ uint8_t buf;
+
+ DPRINTFN(2, "\n");
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_GET_LATENCY;
+
+ USETW(req.wIndex, sc->sc_ucom.sc_portno);
+ USETW(req.wLength, 1);
+ USETW(req.wValue, 0);
+
+ err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, &buf);
+ *latency = buf;
+
+ return (err);
+}
+
+static int
+uftdi_set_event_char(struct ucom_softc *ucom, int echar)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ usb_device_request_t req;
+ uint8_t enable;
+
+ DPRINTFN(2, "\n");
+
+ enable = (echar == -1) ? 0 : 1;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_SET_EVENT_CHAR;
+
+ USETW(req.wIndex, sc->sc_ucom.sc_portno);
+ USETW(req.wLength, 0);
+ USETW2(req.wValue, enable, echar & 0xff);
+
+ return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL));
+}
+
+static int
+uftdi_set_error_char(struct ucom_softc *ucom, int echar)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ usb_device_request_t req;
+ uint8_t enable;
+
+ DPRINTFN(2, "\n");
+
+ enable = (echar == -1) ? 0 : 1;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_SET_ERROR_CHAR;
+
+ USETW(req.wIndex, sc->sc_ucom.sc_portno);
+ USETW(req.wLength, 0);
+ USETW2(req.wValue, enable, echar & 0xff);
+
+ return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL));
+}
+
+static int
+uftdi_read_eeprom(struct ucom_softc *ucom, struct uftdi_eeio *eeio)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ usb_device_request_t req;
+ usb_error_t err;
+ uint16_t widx, wlength, woffset;
+
+ DPRINTFN(3, "\n");
+
+ /* Offset and length must both be evenly divisible by two. */
+ if ((eeio->offset | eeio->length) & 0x01)
+ return (EINVAL);
+
+ woffset = eeio->offset / 2U;
+ wlength = eeio->length / 2U;
+ for (widx = 0; widx < wlength; widx++) {
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_READ_EEPROM;
+ USETW(req.wIndex, widx + woffset);
+ USETW(req.wLength, 2);
+ USETW(req.wValue, 0);
+ err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req,
+ &eeio->data[widx]);
+ if (err != USB_ERR_NORMAL_COMPLETION)
+ return (err);
+ }
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static int
+uftdi_write_eeprom(struct ucom_softc *ucom, struct uftdi_eeio *eeio)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ usb_device_request_t req;
+ usb_error_t err;
+ uint16_t widx, wlength, woffset;
+
+ DPRINTFN(3, "\n");
+
+ /* Offset and length must both be evenly divisible by two. */
+ if ((eeio->offset | eeio->length) & 0x01)
+ return (EINVAL);
+
+ woffset = eeio->offset / 2U;
+ wlength = eeio->length / 2U;
+ for (widx = 0; widx < wlength; widx++) {
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_WRITE_EEPROM;
+ USETW(req.wIndex, widx + woffset);
+ USETW(req.wLength, 0);
+ USETW(req.wValue, eeio->data[widx]);
+ err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL);
+ if (err != USB_ERR_NORMAL_COMPLETION)
+ return (err);
+ }
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static int
+uftdi_erase_eeprom(struct ucom_softc *ucom, int confirmation)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ usb_device_request_t req;
+ usb_error_t err;
+
+ DPRINTFN(2, "\n");
+
+ /* Small effort to prevent accidental erasure. */
+ if (confirmation != UFTDI_CONFIRM_ERASE)
+ return (EINVAL);
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = FTDI_SIO_ERASE_EEPROM;
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+ USETW(req.wValue, 0);
+ err = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL);
+
+ return (err);
+}
+
+static int
+uftdi_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data,
+ int flag, struct thread *td)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+ int err;
+ struct uftdi_bitmode * mode;
+
+ switch (cmd) {
+ case UFTDIIOC_RESET_IO:
+ case UFTDIIOC_RESET_RX:
+ case UFTDIIOC_RESET_TX:
+ err = uftdi_reset(ucom,
+ cmd == UFTDIIOC_RESET_IO ? FTDI_SIO_RESET_SIO :
+ (cmd == UFTDIIOC_RESET_RX ? FTDI_SIO_RESET_PURGE_RX :
+ FTDI_SIO_RESET_PURGE_TX));
+ break;
+ case UFTDIIOC_SET_BITMODE:
+ mode = (struct uftdi_bitmode *)data;
+ err = uftdi_set_bitmode(ucom, mode->mode, mode->iomask);
+ break;
+ case UFTDIIOC_GET_BITMODE:
+ mode = (struct uftdi_bitmode *)data;
+ err = uftdi_get_bitmode(ucom, &mode->mode, &mode->iomask);
+ break;
+ case UFTDIIOC_SET_LATENCY:
+ err = uftdi_set_latency(ucom, *((int *)data));
+ break;
+ case UFTDIIOC_GET_LATENCY:
+ err = uftdi_get_latency(ucom, (int *)data);
+ break;
+ case UFTDIIOC_SET_ERROR_CHAR:
+ err = uftdi_set_error_char(ucom, *(int *)data);
+ break;
+ case UFTDIIOC_SET_EVENT_CHAR:
+ err = uftdi_set_event_char(ucom, *(int *)data);
+ break;
+ case UFTDIIOC_GET_HWREV:
+ *(int *)data = sc->sc_bcdDevice;
+ err = 0;
+ break;
+ case UFTDIIOC_READ_EEPROM:
+ err = uftdi_read_eeprom(ucom, (struct uftdi_eeio *)data);
+ break;
+ case UFTDIIOC_WRITE_EEPROM:
+ err = uftdi_write_eeprom(ucom, (struct uftdi_eeio *)data);
+ break;
+ case UFTDIIOC_ERASE_EEPROM:
+ err = uftdi_erase_eeprom(ucom, *(int *)data);
+ break;
+ default:
+ return (ENOIOCTL);
+ }
+ if (err != USB_ERR_NORMAL_COMPLETION)
+ return (EIO);
+ return (0);
+}
+
+static void
+uftdi_start_read(struct ucom_softc *ucom)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_RD]);
+}
+
+static void
+uftdi_stop_read(struct ucom_softc *ucom)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_RD]);
+}
+
+static void
+uftdi_start_write(struct ucom_softc *ucom)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UFTDI_BULK_DT_WR]);
+}
+
+static void
+uftdi_stop_write(struct ucom_softc *ucom)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UFTDI_BULK_DT_WR]);
+}
+
+static void
+uftdi_poll(struct ucom_softc *ucom)
+{
+ struct uftdi_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_poll(sc->sc_xfer, UFTDI_N_TRANSFER);
+}
diff --git a/sys/dev/usb/serial/uftdi_reg.h b/sys/dev/usb/serial/uftdi_reg.h
new file mode 100644
index 000000000000..19794413cf2f
--- /dev/null
+++ b/sys/dev/usb/serial/uftdi_reg.h
@@ -0,0 +1,313 @@
+/* $NetBSD: uftdireg.h,v 1.6 2002/07/11 21:14:28 augustss Exp $ */
+
+/*
+ * 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 assistance
+ * 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 */
+#define FTDI_SIO_SET_LATENCY 9 /* Set the latency timer */
+#define FTDI_SIO_GET_LATENCY 10 /* Read the latency timer */
+#define FTDI_SIO_SET_BITMODE 11 /* Set the bit bang I/O mode */
+#define FTDI_SIO_GET_BITMODE 12 /* Read pin states from any mode */
+#define FTDI_SIO_READ_EEPROM 144 /* Read eeprom word */
+#define FTDI_SIO_WRITE_EEPROM 145 /* Write eeprom word */
+#define FTDI_SIO_ERASE_EEPROM 146 /* Erase entire eeprom */
+
+/* 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 */
+
+/* Values for driver_info */
+#define UFTDI_JTAG_IFACE(i) (1 << i) /* Flag interface as jtag */
+#define UFTDI_JTAG_IFACES_MAX 8 /* Allow up to 8 jtag intfs */
+#define UFTDI_JTAG_CHECK_STRING 0xff /* Check product names table */
+#define UFTDI_JTAG_MASK 0xff
+
+/*
+ * 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 low bits
+ * wIndex: Port and BaudRate high bits
+ * wLength: 0
+ * Data: None
+ */
+/* FTDI_SIO_SET_BAUDRATE */
+
+/*
+ * 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 protocol / 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/dev/usb/serial/ugensa.c b/sys/dev/usb/serial/ugensa.c
new file mode 100644
index 000000000000..4df8876abffa
--- /dev/null
+++ b/sys/dev/usb/serial/ugensa.c
@@ -0,0 +1,404 @@
+/* $NetBSD: ugensa.c,v 1.9.2.1 2007/03/24 14:55:50 yamt Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2004, 2005 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Roland C. Dowdeswell <elric@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.
+ *
+ * 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.
+ */
+
+/*
+ * NOTE: all function names beginning like "ugensa_cfg_" can only
+ * be called from within the config thread function !
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR usb_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#define UGENSA_BUF_SIZE 2048 /* bytes */
+#define UGENSA_CONFIG_INDEX 0
+#define UGENSA_IFACE_INDEX 0
+#define UGENSA_IFACE_MAX 8 /* exclusivly */
+#define UGENSA_PORT_MAX 8 /* exclusivly */
+
+enum {
+ UGENSA_BULK_DT_WR,
+ UGENSA_BULK_DT_RD,
+ UGENSA_N_TRANSFER,
+};
+
+struct ugensa_sub_softc {
+ struct ucom_softc *sc_ucom_ptr;
+ struct usb_xfer *sc_xfer[UGENSA_N_TRANSFER];
+};
+
+struct ugensa_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom[UGENSA_PORT_MAX];
+ struct ugensa_sub_softc sc_sub[UGENSA_PORT_MAX];
+
+ struct mtx sc_mtx;
+ uint8_t sc_nports;
+};
+
+/* prototypes */
+
+static device_probe_t ugensa_probe;
+static device_attach_t ugensa_attach;
+static device_detach_t ugensa_detach;
+static void ugensa_free_softc(struct ugensa_softc *);
+
+static usb_callback_t ugensa_bulk_write_callback;
+static usb_callback_t ugensa_bulk_read_callback;
+
+static void ugensa_free(struct ucom_softc *);
+static void ugensa_start_read(struct ucom_softc *);
+static void ugensa_stop_read(struct ucom_softc *);
+static void ugensa_start_write(struct ucom_softc *);
+static void ugensa_stop_write(struct ucom_softc *);
+static void ugensa_poll(struct ucom_softc *ucom);
+
+static const struct usb_config ugensa_xfer_config[UGENSA_N_TRANSFER] = {
+ [UGENSA_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UGENSA_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &ugensa_bulk_write_callback,
+ },
+
+ [UGENSA_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UGENSA_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &ugensa_bulk_read_callback,
+ },
+};
+
+static const struct ucom_callback ugensa_callback = {
+ .ucom_start_read = &ugensa_start_read,
+ .ucom_stop_read = &ugensa_stop_read,
+ .ucom_start_write = &ugensa_start_write,
+ .ucom_stop_write = &ugensa_stop_write,
+ .ucom_poll = &ugensa_poll,
+ .ucom_free = &ugensa_free,
+};
+
+static device_method_t ugensa_methods[] = {
+ /* Device methods */
+ DEVMETHOD(device_probe, ugensa_probe),
+ DEVMETHOD(device_attach, ugensa_attach),
+ DEVMETHOD(device_detach, ugensa_detach),
+ DEVMETHOD_END
+};
+
+static driver_t ugensa_driver = {
+ .name = "ugensa",
+ .methods = ugensa_methods,
+ .size = sizeof(struct ugensa_softc),
+};
+
+/* Driver-info is max number of serial ports per interface */
+static const STRUCT_USB_HOST_ID ugensa_devs[] = {
+ {USB_VPI(USB_VENDOR_AIRPRIME, USB_PRODUCT_AIRPRIME_PC5220, 1)},
+ {USB_VPI(USB_VENDOR_CMOTECH, USB_PRODUCT_CMOTECH_CDMA_MODEM1, 1)},
+ {USB_VPI(USB_VENDOR_KYOCERA2, USB_PRODUCT_KYOCERA2_CDMA_MSM_K, 1)},
+ {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_49GPLUS, 1)},
+ {USB_VPI(USB_VENDOR_NOVATEL2, USB_PRODUCT_NOVATEL2_FLEXPACKGPS, 3)},
+ {USB_VENDOR(USB_VENDOR_GOOGLE), USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(0x50), USB_IFACE_PROTOCOL(0x01), USB_DRIVER_INFO(10)},
+};
+
+DRIVER_MODULE(ugensa, uhub, ugensa_driver, NULL, NULL);
+MODULE_DEPEND(ugensa, ucom, 1, 1, 1);
+MODULE_DEPEND(ugensa, usb, 1, 1, 1);
+MODULE_VERSION(ugensa, 1);
+USB_PNP_HOST_INFO(ugensa_devs);
+
+static int
+ugensa_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != UGENSA_CONFIG_INDEX) {
+ return (ENXIO);
+ }
+ if (uaa->info.bIfaceIndex != 0) {
+ return (ENXIO);
+ }
+ return (usbd_lookup_id_by_uaa(ugensa_devs, sizeof(ugensa_devs), uaa));
+}
+
+static int
+ugensa_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct ugensa_softc *sc = device_get_softc(dev);
+ struct ugensa_sub_softc *ssc;
+ struct usb_interface *iface;
+ struct usb_config xfer_config[UGENSA_N_TRANSFER];
+ int32_t error;
+ uint8_t iface_index;
+ int x, maxports;
+
+ maxports = USB_GET_DRIVER_INFO(uaa);
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "ugensa", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ for (iface_index = UGENSA_IFACE_INDEX; iface_index < UGENSA_IFACE_MAX; iface_index++) {
+ iface = usbd_get_iface(uaa->device, iface_index);
+ if (iface == NULL || iface->idesc->bInterfaceClass != UICLASS_VENDOR)
+ /* Not a serial port, most likely a SD reader */
+ continue;
+
+ /* Loop over all endpoints pairwise */
+ for (x = 0; x < maxports && sc->sc_nports < UGENSA_PORT_MAX; x++) {
+ ssc = sc->sc_sub + sc->sc_nports;
+ ssc->sc_ucom_ptr = sc->sc_ucom + sc->sc_nports;
+
+ memcpy(xfer_config, ugensa_xfer_config, sizeof ugensa_xfer_config);
+ xfer_config[UGENSA_BULK_DT_RD].ep_index = x;
+ xfer_config[UGENSA_BULK_DT_WR].ep_index = x;
+
+ error = usbd_transfer_setup(uaa->device,
+ &iface_index, ssc->sc_xfer, xfer_config,
+ UGENSA_N_TRANSFER, ssc, &sc->sc_mtx);
+
+ if (error) {
+ if (x == 0) {
+ device_printf(dev, "allocating USB "
+ "transfers failed (%d)\n", error);
+ goto detach;
+ }
+ break;
+ }
+
+ /* clear stall at first run */
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(ssc->sc_xfer[UGENSA_BULK_DT_WR]);
+ usbd_xfer_set_stall(ssc->sc_xfer[UGENSA_BULK_DT_RD]);
+ mtx_unlock(&sc->sc_mtx);
+
+ /* initialize port number */
+ ssc->sc_ucom_ptr->sc_portno = sc->sc_nports;
+ if (iface_index != uaa->info.bIfaceIndex) {
+ usbd_set_parent_iface(uaa->device, iface_index,
+ uaa->info.bIfaceIndex);
+ }
+ sc->sc_nports++;
+ }
+ }
+ device_printf(dev, "Found %d serial ports.\n", sc->sc_nports);
+
+ error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_nports, sc,
+ &ugensa_callback, &sc->sc_mtx);
+
+ if (error) {
+ DPRINTF("ucom attach failed\n");
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ return (0); /* success */
+
+detach:
+ ugensa_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+ugensa_detach(device_t dev)
+{
+ struct ugensa_softc *sc = device_get_softc(dev);
+ uint8_t x;
+
+ ucom_detach(&sc->sc_super_ucom, sc->sc_ucom);
+
+ for (x = 0; x < sc->sc_nports; x++) {
+ usbd_transfer_unsetup(sc->sc_sub[x].sc_xfer, UGENSA_N_TRANSFER);
+ }
+
+ device_claim_softc(dev);
+
+ ugensa_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(ugensa);
+
+static void
+ugensa_free_softc(struct ugensa_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+ugensa_free(struct ucom_softc *ucom)
+{
+ ugensa_free_softc(ucom->sc_parent);
+}
+
+static void
+ugensa_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ugensa_sub_softc *ssc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(ssc->sc_ucom_ptr, pc, 0,
+ UGENSA_BUF_SIZE, &actlen)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+ugensa_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ugensa_sub_softc *ssc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(ssc->sc_ucom_ptr, pc, 0, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+ugensa_start_read(struct ucom_softc *ucom)
+{
+ struct ugensa_softc *sc = ucom->sc_parent;
+ struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno;
+
+ usbd_transfer_start(ssc->sc_xfer[UGENSA_BULK_DT_RD]);
+}
+
+static void
+ugensa_stop_read(struct ucom_softc *ucom)
+{
+ struct ugensa_softc *sc = ucom->sc_parent;
+ struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno;
+
+ usbd_transfer_stop(ssc->sc_xfer[UGENSA_BULK_DT_RD]);
+}
+
+static void
+ugensa_start_write(struct ucom_softc *ucom)
+{
+ struct ugensa_softc *sc = ucom->sc_parent;
+ struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno;
+
+ usbd_transfer_start(ssc->sc_xfer[UGENSA_BULK_DT_WR]);
+}
+
+static void
+ugensa_stop_write(struct ucom_softc *ucom)
+{
+ struct ugensa_softc *sc = ucom->sc_parent;
+ struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno;
+
+ usbd_transfer_stop(ssc->sc_xfer[UGENSA_BULK_DT_WR]);
+}
+
+static void
+ugensa_poll(struct ucom_softc *ucom)
+{
+ struct ugensa_softc *sc = ucom->sc_parent;
+ struct ugensa_sub_softc *ssc = sc->sc_sub + ucom->sc_portno;
+
+ usbd_transfer_poll(ssc->sc_xfer, UGENSA_N_TRANSFER);
+}
diff --git a/sys/dev/usb/serial/uipaq.c b/sys/dev/usb/serial/uipaq.c
new file mode 100644
index 000000000000..f24f1e215767
--- /dev/null
+++ b/sys/dev/usb/serial/uipaq.c
@@ -0,0 +1,1371 @@
+/* $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 $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_cdc.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR usb_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#define UIPAQ_CONFIG_INDEX 0 /* config number 1 */
+#define UIPAQ_IFACE_INDEX 0
+
+#define UIPAQ_BUF_SIZE 1024
+
+enum {
+ UIPAQ_BULK_DT_WR,
+ UIPAQ_BULK_DT_RD,
+ UIPAQ_N_TRANSFER,
+};
+
+struct uipaq_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+
+ struct usb_xfer *sc_xfer[UIPAQ_N_TRANSFER];
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+
+ uint16_t sc_line;
+
+ uint8_t sc_lsr; /* local status register */
+ uint8_t sc_msr; /* modem status register */
+};
+
+static device_probe_t uipaq_probe;
+static device_attach_t uipaq_attach;
+static device_detach_t uipaq_detach;
+static void uipaq_free_softc(struct uipaq_softc *);
+
+static usb_callback_t uipaq_write_callback;
+static usb_callback_t uipaq_read_callback;
+
+static void uipaq_free(struct ucom_softc *);
+static void uipaq_start_read(struct ucom_softc *);
+static void uipaq_stop_read(struct ucom_softc *);
+static void uipaq_start_write(struct ucom_softc *);
+static void uipaq_stop_write(struct ucom_softc *);
+static void uipaq_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void uipaq_cfg_set_rts(struct ucom_softc *, uint8_t);
+static void uipaq_cfg_set_break(struct ucom_softc *, uint8_t);
+static void uipaq_poll(struct ucom_softc *ucom);
+
+static const struct usb_config uipaq_config_data[UIPAQ_N_TRANSFER] = {
+ [UIPAQ_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UIPAQ_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &uipaq_write_callback,
+ },
+
+ [UIPAQ_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UIPAQ_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &uipaq_read_callback,
+ },
+};
+
+static const struct ucom_callback uipaq_callback = {
+ .ucom_cfg_set_dtr = &uipaq_cfg_set_dtr,
+ .ucom_cfg_set_rts = &uipaq_cfg_set_rts,
+ .ucom_cfg_set_break = &uipaq_cfg_set_break,
+ .ucom_start_read = &uipaq_start_read,
+ .ucom_stop_read = &uipaq_stop_read,
+ .ucom_start_write = &uipaq_start_write,
+ .ucom_stop_write = &uipaq_stop_write,
+ .ucom_poll = &uipaq_poll,
+ .ucom_free = &uipaq_free,
+};
+
+/*
+ * 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_USB_HOST_ID uipaq_devs[] = {
+ /* Socket USB Sync */
+ {USB_VPI(0x0104, 0x00be, 0)},
+ /* USB Sync 0301 */
+ {USB_VPI(0x04ad, 0x0301, 0)},
+ /* USB Sync 0302 */
+ {USB_VPI(0x04ad, 0x0302, 0)},
+ /* USB Sync 0303 */
+ {USB_VPI(0x04ad, 0x0303, 0)},
+ /* GPS Pocket PC USB Sync */
+ {USB_VPI(0x04ad, 0x0306, 0)},
+ /* HHP PDT */
+ {USB_VPI(0x0536, 0x01a0, 0)},
+ /* Intermec Mobile Computer */
+ {USB_VPI(0x067e, 0x1001, 0)},
+ /* Linkup Systems USB Sync */
+ {USB_VPI(0x094b, 0x0001, 0)},
+ /* BCOM USB Sync 0065 */
+ {USB_VPI(0x0960, 0x0065, 0)},
+ /* BCOM USB Sync 0066 */
+ {USB_VPI(0x0960, 0x0066, 0)},
+ /* BCOM USB Sync 0067 */
+ {USB_VPI(0x0960, 0x0067, 0)},
+ /* Portatec USB Sync */
+ {USB_VPI(0x0961, 0x0010, 0)},
+ /* Trimble GeoExplorer */
+ {USB_VPI(0x099e, 0x0052, 0)},
+ /* TDS Data Collector */
+ {USB_VPI(0x099e, 0x4000, 0)},
+ /* Motorola iDEN Smartphone */
+ {USB_VPI(0x0c44, 0x03a2, 0)},
+ /* Cesscom Luxian Series */
+ {USB_VPI(0x0c8e, 0x6000, 0)},
+ /* Motorola PowerPad Pocket PCDevice */
+ {USB_VPI(0x0cad, 0x9001, 0)},
+ /* Freedom Scientific USB Sync */
+ {USB_VPI(0x0f4e, 0x0200, 0)},
+ /* Cyberbank USB Sync */
+ {USB_VPI(0x0f98, 0x0201, 0)},
+ /* Wistron USB Sync */
+ {USB_VPI(0x0fb8, 0x3001, 0)},
+ /* Wistron USB Sync */
+ {USB_VPI(0x0fb8, 0x3002, 0)},
+ /* Wistron USB Sync */
+ {USB_VPI(0x0fb8, 0x3003, 0)},
+ /* Wistron USB Sync */
+ {USB_VPI(0x0fb8, 0x4001, 0)},
+ /* E-TEN USB Sync */
+ {USB_VPI(0x1066, 0x00ce, 0)},
+ /* E-TEN P3XX Pocket PC */
+ {USB_VPI(0x1066, 0x0300, 0)},
+ /* E-TEN P5XX Pocket PC */
+ {USB_VPI(0x1066, 0x0500, 0)},
+ /* E-TEN P6XX Pocket PC */
+ {USB_VPI(0x1066, 0x0600, 0)},
+ /* E-TEN P7XX Pocket PC */
+ {USB_VPI(0x1066, 0x0700, 0)},
+ /* Psion Teklogix Sync 753x */
+ {USB_VPI(0x1114, 0x0001, 0)},
+ /* Psion Teklogix Sync netBookPro */
+ {USB_VPI(0x1114, 0x0004, 0)},
+ /* Psion Teklogix Sync 7525 */
+ {USB_VPI(0x1114, 0x0006, 0)},
+ /* VES USB Sync */
+ {USB_VPI(0x1182, 0x1388, 0)},
+ /* Rugged Pocket PC 2003 */
+ {USB_VPI(0x11d9, 0x1002, 0)},
+ /* Rugged Pocket PC 2003 */
+ {USB_VPI(0x11d9, 0x1003, 0)},
+ /* USB Sync 03 */
+ {USB_VPI(0x1231, 0xce01, 0)},
+ /* USB Sync 03 */
+ {USB_VPI(0x1231, 0xce02, 0)},
+ /* Mio DigiWalker PPC StrongARM */
+ {USB_VPI(0x3340, 0x011c, 0)},
+ /* Mio DigiWalker 338 */
+ {USB_VPI(0x3340, 0x0326, 0)},
+ /* Mio DigiWalker 338 */
+ {USB_VPI(0x3340, 0x0426, 0)},
+ /* Mio DigiWalker USB Sync */
+ {USB_VPI(0x3340, 0x043a, 0)},
+ /* MiTAC USB Sync 528 */
+ {USB_VPI(0x3340, 0x051c, 0)},
+ /* Mio DigiWalker SmartPhone USB Sync */
+ {USB_VPI(0x3340, 0x053a, 0)},
+ /* MiTAC USB Sync */
+ {USB_VPI(0x3340, 0x071c, 0)},
+ /* Generic PPC StrongARM */
+ {USB_VPI(0x3340, 0x0b1c, 0)},
+ /* Generic PPC USB Sync */
+ {USB_VPI(0x3340, 0x0e3a, 0)},
+ /* Itautec USB Sync */
+ {USB_VPI(0x3340, 0x0f1c, 0)},
+ /* Generic SmartPhone USB Sync */
+ {USB_VPI(0x3340, 0x0f3a, 0)},
+ /* Itautec USB Sync */
+ {USB_VPI(0x3340, 0x1326, 0)},
+ /* YAKUMO USB Sync */
+ {USB_VPI(0x3340, 0x191c, 0)},
+ /* Vobis USB Sync */
+ {USB_VPI(0x3340, 0x2326, 0)},
+ /* MEDION Winodws Moble USB Sync */
+ {USB_VPI(0x3340, 0x3326, 0)},
+ /* Legend USB Sync */
+ {USB_VPI(0x3708, 0x20ce, 0)},
+ /* Lenovo USB Sync */
+ {USB_VPI(0x3708, 0x21ce, 0)},
+ /* Mobile Media Technology USB Sync */
+ {USB_VPI(0x4113, 0x0210, 0)},
+ /* Mobile Media Technology USB Sync */
+ {USB_VPI(0x4113, 0x0211, 0)},
+ /* Mobile Media Technology USB Sync */
+ {USB_VPI(0x4113, 0x0400, 0)},
+ /* Mobile Media Technology USB Sync */
+ {USB_VPI(0x4113, 0x0410, 0)},
+ /* Smartphone */
+ {USB_VPI(0x4505, 0x0010, 0)},
+ /* SAGEM Wireless Assistant */
+ {USB_VPI(0x5e04, 0xce00, 0)},
+ /* c10 Series */
+ {USB_VPI(USB_VENDOR_ACER, 0x1631, 0)},
+ /* c20 Series */
+ {USB_VPI(USB_VENDOR_ACER, 0x1632, 0)},
+ /* Acer n10 Handheld USB Sync */
+ {USB_VPI(USB_VENDOR_ACER, 0x16e1, 0)},
+ /* Acer n20 Handheld USB Sync */
+ {USB_VPI(USB_VENDOR_ACER, 0x16e2, 0)},
+ /* Acer n30 Handheld USB Sync */
+ {USB_VPI(USB_VENDOR_ACER, 0x16e3, 0)},
+ /* ASUS USB Sync */
+ {USB_VPI(USB_VENDOR_ASUS, 0x4200, 0)},
+ /* ASUS USB Sync */
+ {USB_VPI(USB_VENDOR_ASUS, 0x4201, 0)},
+ /* ASUS USB Sync */
+ {USB_VPI(USB_VENDOR_ASUS, 0x4202, 0)},
+ /* ASUS USB Sync */
+ {USB_VPI(USB_VENDOR_ASUS, 0x9200, 0)},
+ /* ASUS USB Sync */
+ {USB_VPI(USB_VENDOR_ASUS, 0x9202, 0)},
+ /**/
+ {USB_VPI(USB_VENDOR_ASUS, USB_PRODUCT_ASUS_P535, 0)},
+ /* CASIO USB Sync 2001 */
+ {USB_VPI(USB_VENDOR_CASIO, 0x2001, 0)},
+ /* CASIO USB Sync 2003 */
+ {USB_VPI(USB_VENDOR_CASIO, 0x2003, 0)},
+ /**/
+ {USB_VPI(USB_VENDOR_CASIO, USB_PRODUCT_CASIO_BE300, 0)},
+ /* MyGuide 7000 XL USB Sync */
+ {USB_VPI(USB_VENDOR_COMPAL, 0x0531, 0)},
+ /* Compaq iPAQ USB Sync */
+ {USB_VPI(USB_VENDOR_COMPAQ, 0x0032, 0)},
+ /**/
+ {USB_VPI(USB_VENDOR_COMPAQ, USB_PRODUCT_COMPAQ_IPAQPOCKETPC, 0)},
+ /* Dell Axim USB Sync */
+ {USB_VPI(USB_VENDOR_DELL, 0x4001, 0)},
+ /* Dell Axim USB Sync */
+ {USB_VPI(USB_VENDOR_DELL, 0x4002, 0)},
+ /* Dell Axim USB Sync */
+ {USB_VPI(USB_VENDOR_DELL, 0x4003, 0)},
+ /* Dell Axim USB Sync */
+ {USB_VPI(USB_VENDOR_DELL, 0x4004, 0)},
+ /* Dell Axim USB Sync */
+ {USB_VPI(USB_VENDOR_DELL, 0x4005, 0)},
+ /* Dell Axim USB Sync */
+ {USB_VPI(USB_VENDOR_DELL, 0x4006, 0)},
+ /* Dell Axim USB Sync */
+ {USB_VPI(USB_VENDOR_DELL, 0x4007, 0)},
+ /* Dell Axim USB Sync */
+ {USB_VPI(USB_VENDOR_DELL, 0x4008, 0)},
+ /* Dell Axim USB Sync */
+ {USB_VPI(USB_VENDOR_DELL, 0x4009, 0)},
+ /* Fujitsu Siemens Computers USB Sync */
+ {USB_VPI(USB_VENDOR_FSC, 0x1001, 0)},
+ /* FUJITSU USB Sync */
+ {USB_VPI(USB_VENDOR_FUJITSU, 0x1058, 0)},
+ /* FUJITSU USB Sync */
+ {USB_VPI(USB_VENDOR_FUJITSU, 0x1079, 0)},
+ /* Askey USB Sync */
+ {USB_VPI(USB_VENDOR_GIGASET, 0x0601, 0)},
+ /* Hitachi USB Sync */
+ {USB_VPI(USB_VENDOR_HITACHI, 0x0014, 0)},
+ /* HP USB Sync 1612 */
+ {USB_VPI(USB_VENDOR_HP, 0x1216, 0)},
+ /* HP USB Sync 1620 */
+ {USB_VPI(USB_VENDOR_HP, 0x2016, 0)},
+ /* HP USB Sync 1621 */
+ {USB_VPI(USB_VENDOR_HP, 0x2116, 0)},
+ /* HP USB Sync 1622 */
+ {USB_VPI(USB_VENDOR_HP, 0x2216, 0)},
+ /* HP USB Sync 1630 */
+ {USB_VPI(USB_VENDOR_HP, 0x3016, 0)},
+ /* HP USB Sync 1631 */
+ {USB_VPI(USB_VENDOR_HP, 0x3116, 0)},
+ /* HP USB Sync 1632 */
+ {USB_VPI(USB_VENDOR_HP, 0x3216, 0)},
+ /* HP USB Sync 1640 */
+ {USB_VPI(USB_VENDOR_HP, 0x4016, 0)},
+ /* HP USB Sync 1641 */
+ {USB_VPI(USB_VENDOR_HP, 0x4116, 0)},
+ /* HP USB Sync 1642 */
+ {USB_VPI(USB_VENDOR_HP, 0x4216, 0)},
+ /* HP USB Sync 1650 */
+ {USB_VPI(USB_VENDOR_HP, 0x5016, 0)},
+ /* HP USB Sync 1651 */
+ {USB_VPI(USB_VENDOR_HP, 0x5116, 0)},
+ /* HP USB Sync 1652 */
+ {USB_VPI(USB_VENDOR_HP, 0x5216, 0)},
+ /**/
+ {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_2215, 0)},
+ /**/
+ {USB_VPI(USB_VENDOR_HP, USB_PRODUCT_HP_568J, 0)},
+ /* HTC USB Modem */
+ {USB_VPI(USB_VENDOR_HTC, 0x00cf, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a01, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a02, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a03, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a04, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a05, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a06, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a07, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a08, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a09, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a0a, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a0b, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a0c, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a0d, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a0e, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a0f, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a10, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a11, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a12, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a13, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a14, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a15, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a16, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a17, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a18, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a19, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a1a, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a1b, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a1c, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a1d, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a1e, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a1f, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a20, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a21, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a22, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a23, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a24, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a25, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a26, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a27, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a28, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a29, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a2a, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a2b, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a2c, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a2d, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a2e, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a2f, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a30, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a31, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a32, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a33, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a34, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a35, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a36, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a37, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a38, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a39, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a3a, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a3b, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a3c, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a3d, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a3e, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a3f, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a40, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a41, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a42, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a43, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a44, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a45, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a46, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a47, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a48, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a49, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a4a, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a4b, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a4c, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a4d, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a4e, 0)},
+ /* PocketPC USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a4f, 0)},
+ /* HTC SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a50, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a52, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a53, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a54, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a55, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a56, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a57, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a58, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a59, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a5a, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a5b, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a5c, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a5d, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a5e, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a5f, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a60, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a61, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a62, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a63, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a64, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a65, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a66, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a67, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a68, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a69, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a6a, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a6b, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a6c, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a6d, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a6e, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a6f, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a70, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a71, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a72, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a73, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a74, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a75, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a76, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a77, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a78, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a79, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a7a, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a7b, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a7c, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a7d, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a7e, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a7f, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a80, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a81, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a82, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a83, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a84, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a85, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a86, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a87, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a88, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a89, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a8a, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a8b, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a8c, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a8d, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a8e, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a8f, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a90, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a91, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a92, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a93, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a94, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a95, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a96, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a97, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a98, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a99, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a9a, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a9b, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a9c, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a9d, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a9e, 0)},
+ /* SmartPhone USB Sync */
+ {USB_VPI(USB_VENDOR_HTC, 0x0a9f, 0)},
+ /**/
+ {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_PPC6700MODEM, 0)},
+ /**/
+ {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_SMARTPHONE, 0)},
+ /**/
+ {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_WINMOBILE, 0)},
+ /* High Tech Computer Wizard Smartphone */
+ {USB_VPI(USB_VENDOR_HTC, USB_PRODUCT_HTC_WIZARD, 0)},
+ /* JVC USB Sync */
+ {USB_VPI(USB_VENDOR_JVC, 0x3011, 0)},
+ /* JVC USB Sync */
+ {USB_VPI(USB_VENDOR_JVC, 0x3012, 0)},
+ /* LGE USB Sync */
+ {USB_VPI(USB_VENDOR_LG, 0x9c01, 0)},
+ /* Microsoft USB Sync */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x00ce, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0400, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0401, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0402, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0403, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0404, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0405, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0406, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0407, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0408, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0409, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x040a, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x040b, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x040c, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x040d, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x040e, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x040f, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0410, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0411, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0412, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0413, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0414, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0415, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0416, 0)},
+ /* Windows Pocket PC 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0417, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0432, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0433, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0434, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0435, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0436, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0437, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0438, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0439, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x043a, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x043b, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x043c, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x043d, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x043e, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x043f, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0440, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0441, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0442, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0443, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0444, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0445, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0446, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0447, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0448, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0449, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x044a, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x044b, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x044c, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x044d, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x044e, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x044f, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0450, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0451, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0452, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0453, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0454, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0455, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0456, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0457, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0458, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0459, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x045a, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x045b, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x045c, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x045d, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x045e, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x045f, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0460, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0461, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0462, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0463, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0464, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0465, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0466, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0467, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0468, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0469, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x046a, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x046b, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x046c, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x046d, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x046e, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x046f, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0470, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0471, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0472, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0473, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0474, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0475, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0476, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0477, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0478, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x0479, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x047a, 0)},
+ /* Windows Pocket PC 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x047b, 0)},
+ /* Windows Smartphone 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04c8, 0)},
+ /* Windows Smartphone 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04c9, 0)},
+ /* Windows Smartphone 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04ca, 0)},
+ /* Windows Smartphone 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04cb, 0)},
+ /* Windows Smartphone 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04cc, 0)},
+ /* Windows Smartphone 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04cd, 0)},
+ /* Windows Smartphone 2002 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04ce, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04d7, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04d8, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04d9, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04da, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04db, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04dc, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04dd, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04de, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04df, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e0, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e1, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e2, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e3, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e4, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e5, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e6, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e7, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e8, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04e9, 0)},
+ /* Windows Smartphone 2003 */
+ {USB_VPI(USB_VENDOR_MICROSOFT, 0x04ea, 0)},
+ /* Motorola MPx200 Smartphone */
+ {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4204, 0)},
+ /* Motorola MPc GSM */
+ {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4214, 0)},
+ /* Motorola MPx220 Smartphone */
+ {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4224, 0)},
+ /* Motorola MPc CDMA */
+ {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4234, 0)},
+ /* Motorola MPx100 Smartphone */
+ {USB_VPI(USB_VENDOR_MOTOROLA2, 0x4244, 0)},
+ /* NEC USB Sync */
+ {USB_VPI(USB_VENDOR_NEC, 0x00d5, 0)},
+ /* NEC USB Sync */
+ {USB_VPI(USB_VENDOR_NEC, 0x00d6, 0)},
+ /* NEC USB Sync */
+ {USB_VPI(USB_VENDOR_NEC, 0x00d7, 0)},
+ /* NEC USB Sync */
+ {USB_VPI(USB_VENDOR_NEC, 0x8024, 0)},
+ /* NEC USB Sync */
+ {USB_VPI(USB_VENDOR_NEC, 0x8025, 0)},
+ /* Panasonic USB Sync */
+ {USB_VPI(USB_VENDOR_PANASONIC, 0x2500, 0)},
+ /* Samsung NEXiO USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f00, 0)},
+ /* Samsung NEXiO USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f01, 0)},
+ /* Samsung NEXiO USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f02, 0)},
+ /* Samsung NEXiO USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f03, 0)},
+ /* Samsung NEXiO USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x5f04, 0)},
+ /* Samsung MITs USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x6611, 0)},
+ /* Samsung MITs USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x6613, 0)},
+ /* Samsung MITs USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x6615, 0)},
+ /* Samsung MITs USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x6617, 0)},
+ /* Samsung MITs USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x6619, 0)},
+ /* Samsung MITs USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x661b, 0)},
+ /* Samsung MITs USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x662e, 0)},
+ /* Samsung MITs USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x6630, 0)},
+ /* Samsung MITs USB Sync */
+ {USB_VPI(USB_VENDOR_SAMSUNG, 0x6632, 0)},
+ /* SHARP WS003SH USB Modem */
+ {USB_VPI(USB_VENDOR_SHARP, 0x9102, 0)},
+ /* SHARP WS004SH USB Modem */
+ {USB_VPI(USB_VENDOR_SHARP, 0x9121, 0)},
+ /* SHARP S01SH USB Modem */
+ {USB_VPI(USB_VENDOR_SHARP, 0x9151, 0)},
+ /**/
+ {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_WZERO3ES, 0)},
+ /**/
+ {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_WZERO3ADES, 0)},
+ /**/
+ {USB_VPI(USB_VENDOR_SHARP, USB_PRODUCT_SHARP_WILLCOM03, 0)},
+ /* Symbol USB Sync */
+ {USB_VPI(USB_VENDOR_SYMBOL, 0x2000, 0)},
+ /* Symbol USB Sync 0x2001 */
+ {USB_VPI(USB_VENDOR_SYMBOL, 0x2001, 0)},
+ /* Symbol USB Sync 0x2002 */
+ {USB_VPI(USB_VENDOR_SYMBOL, 0x2002, 0)},
+ /* Symbol USB Sync 0x2003 */
+ {USB_VPI(USB_VENDOR_SYMBOL, 0x2003, 0)},
+ /* Symbol USB Sync 0x2004 */
+ {USB_VPI(USB_VENDOR_SYMBOL, 0x2004, 0)},
+ /* Symbol USB Sync 0x2005 */
+ {USB_VPI(USB_VENDOR_SYMBOL, 0x2005, 0)},
+ /* Symbol USB Sync 0x2006 */
+ {USB_VPI(USB_VENDOR_SYMBOL, 0x2006, 0)},
+ /* Symbol USB Sync 0x2007 */
+ {USB_VPI(USB_VENDOR_SYMBOL, 0x2007, 0)},
+ /* Symbol USB Sync 0x2008 */
+ {USB_VPI(USB_VENDOR_SYMBOL, 0x2008, 0)},
+ /* Symbol USB Sync 0x2009 */
+ {USB_VPI(USB_VENDOR_SYMBOL, 0x2009, 0)},
+ /* Symbol USB Sync 0x200a */
+ {USB_VPI(USB_VENDOR_SYMBOL, 0x200a, 0)},
+ /* TOSHIBA USB Sync 0700 */
+ {USB_VPI(USB_VENDOR_TOSHIBA, 0x0700, 0)},
+ /* TOSHIBA Pocket PC e310 */
+ {USB_VPI(USB_VENDOR_TOSHIBA, 0x0705, 0)},
+ /* TOSHIBA Pocket PC e330 Series */
+ {USB_VPI(USB_VENDOR_TOSHIBA, 0x0707, 0)},
+ /* TOSHIBA Pocket PC e350Series */
+ {USB_VPI(USB_VENDOR_TOSHIBA, 0x0708, 0)},
+ /* TOSHIBA Pocket PC e750 Series */
+ {USB_VPI(USB_VENDOR_TOSHIBA, 0x0709, 0)},
+ /* TOSHIBA Pocket PC e400 Series */
+ {USB_VPI(USB_VENDOR_TOSHIBA, 0x070a, 0)},
+ /* TOSHIBA Pocket PC e800 Series */
+ {USB_VPI(USB_VENDOR_TOSHIBA, 0x070b, 0)},
+ /* TOSHIBA Pocket PC e740 */
+ {USB_VPI(USB_VENDOR_TOSHIBA, USB_PRODUCT_TOSHIBA_POCKETPC_E740, 0)},
+ /* ViewSonic Color Pocket PC V35 */
+ {USB_VPI(USB_VENDOR_VIEWSONIC, 0x0ed9, 0)},
+ /* ViewSonic Color Pocket PC V36 */
+ {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1527, 0)},
+ /* ViewSonic Color Pocket PC V37 */
+ {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1529, 0)},
+ /* ViewSonic Color Pocket PC V38 */
+ {USB_VPI(USB_VENDOR_VIEWSONIC, 0x152b, 0)},
+ /* ViewSonic Pocket PC */
+ {USB_VPI(USB_VENDOR_VIEWSONIC, 0x152e, 0)},
+ /* ViewSonic Communicator Pocket PC */
+ {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1921, 0)},
+ /* ViewSonic Smartphone */
+ {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1922, 0)},
+ /* ViewSonic Pocket PC V30 */
+ {USB_VPI(USB_VENDOR_VIEWSONIC, 0x1923, 0)},
+};
+
+static device_method_t uipaq_methods[] = {
+ DEVMETHOD(device_probe, uipaq_probe),
+ DEVMETHOD(device_attach, uipaq_attach),
+ DEVMETHOD(device_detach, uipaq_detach),
+ DEVMETHOD_END
+};
+
+static driver_t uipaq_driver = {
+ .name = "uipaq",
+ .methods = uipaq_methods,
+ .size = sizeof(struct uipaq_softc),
+};
+
+DRIVER_MODULE(uipaq, uhub, uipaq_driver, NULL, NULL);
+MODULE_DEPEND(uipaq, ucom, 1, 1, 1);
+MODULE_DEPEND(uipaq, usb, 1, 1, 1);
+MODULE_VERSION(uipaq, 1);
+USB_PNP_HOST_INFO(uipaq_devs);
+
+static int
+uipaq_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != UIPAQ_CONFIG_INDEX) {
+ return (ENXIO);
+ }
+ if (uaa->info.bIfaceIndex != UIPAQ_IFACE_INDEX) {
+ return (ENXIO);
+ }
+ if (uaa->info.bInterfaceClass == UICLASS_IAD) {
+ DPRINTF("IAD detected - not UIPAQ serial device\n");
+ return (ENXIO);
+ }
+ return (usbd_lookup_id_by_uaa(uipaq_devs, sizeof(uipaq_devs), uaa));
+}
+
+static int
+uipaq_attach(device_t dev)
+{
+ struct usb_device_request req;
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct uipaq_softc *sc = device_get_softc(dev);
+ int error;
+ uint8_t iface_index;
+ uint8_t i;
+
+ sc->sc_udev = uaa->device;
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "uipaq", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ /*
+ * Send magic bytes, cribbed from Linux ipaq driver that
+ * claims to have sniffed them from Win98. Wait for driver to
+ * become ready on device side?
+ */
+ 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 != 64; i++) {
+ error =
+ usbd_do_request_flags(uaa->device, NULL, &req,
+ NULL, 0, NULL, 100);
+ if (error == 0)
+ break;
+ usb_pause_mtx(NULL, hz / 10);
+ }
+
+ iface_index = UIPAQ_IFACE_INDEX;
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, uipaq_config_data,
+ UIPAQ_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error) {
+ goto detach;
+ }
+ /* clear stall at first run */
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(sc->sc_xfer[UIPAQ_BULK_DT_WR]);
+ usbd_xfer_set_stall(sc->sc_xfer[UIPAQ_BULK_DT_RD]);
+ mtx_unlock(&sc->sc_mtx);
+
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &uipaq_callback, &sc->sc_mtx);
+ if (error) {
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ return (0);
+
+detach:
+ uipaq_detach(dev);
+ return (ENXIO);
+}
+
+int
+uipaq_detach(device_t dev)
+{
+ struct uipaq_softc *sc = device_get_softc(dev);
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, UIPAQ_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ uipaq_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(uipaq);
+
+static void
+uipaq_free_softc(struct uipaq_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+uipaq_free(struct ucom_softc *ucom)
+{
+ uipaq_free_softc(ucom->sc_parent);
+}
+
+static void
+uipaq_start_read(struct ucom_softc *ucom)
+{
+ struct uipaq_softc *sc = ucom->sc_parent;
+
+ /* start read endpoint */
+ usbd_transfer_start(sc->sc_xfer[UIPAQ_BULK_DT_RD]);
+}
+
+static void
+uipaq_stop_read(struct ucom_softc *ucom)
+{
+ struct uipaq_softc *sc = ucom->sc_parent;
+
+ /* stop read endpoint */
+ usbd_transfer_stop(sc->sc_xfer[UIPAQ_BULK_DT_RD]);
+}
+
+static void
+uipaq_start_write(struct ucom_softc *ucom)
+{
+ struct uipaq_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UIPAQ_BULK_DT_WR]);
+}
+
+static void
+uipaq_stop_write(struct ucom_softc *ucom)
+{
+ struct uipaq_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UIPAQ_BULK_DT_WR]);
+}
+
+static void
+uipaq_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uipaq_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+
+ DPRINTF("onoff=%d\n", onoff);
+
+ if (onoff)
+ sc->sc_line |= UCDC_LINE_DTR;
+ else
+ sc->sc_line &= ~UCDC_LINE_DTR;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
+ USETW(req.wValue, sc->sc_line);
+ req.wIndex[0] = UIPAQ_IFACE_INDEX;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+static void
+uipaq_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uipaq_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+
+ DPRINTF("onoff=%d\n", onoff);
+
+ if (onoff)
+ sc->sc_line |= UCDC_LINE_RTS;
+ else
+ sc->sc_line &= ~UCDC_LINE_RTS;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
+ USETW(req.wValue, sc->sc_line);
+ req.wIndex[0] = UIPAQ_IFACE_INDEX;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+static void
+uipaq_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uipaq_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+ uint16_t temp;
+
+ temp = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SEND_BREAK;
+ USETW(req.wValue, temp);
+ req.wIndex[0] = UIPAQ_IFACE_INDEX;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+static void
+uipaq_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uipaq_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(&sc->sc_ucom, pc, 0,
+ UIPAQ_BUF_SIZE, &actlen)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uipaq_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uipaq_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uipaq_poll(struct ucom_softc *ucom)
+{
+ struct uipaq_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_xfer, UIPAQ_N_TRANSFER);
+}
diff --git a/sys/dev/usb/serial/ulpt.c b/sys/dev/usb/serial/ulpt.c
new file mode 100644
index 000000000000..ec25ad737596
--- /dev/null
+++ b/sys/dev/usb/serial/ulpt.c
@@ -0,0 +1,765 @@
+/* $NetBSD: ulpt.c,v 1.60 2003/10/04 21:19:50 augustss Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * Printer Class spec: http://www.usb.org/developers/data/devclass/usbprint109.PDF
+ * Printer Class spec: http://www.usb.org/developers/devclass_docs/usbprint11.pdf
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/syslog.h>
+#include <sys/selinfo.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR ulpt_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#ifdef USB_DEBUG
+static int ulpt_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ulpt, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB ulpt");
+SYSCTL_INT(_hw_usb_ulpt, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &ulpt_debug, 0, "Debug level");
+#endif
+
+#define ULPT_BSIZE (1<<15) /* bytes */
+#define ULPT_IFQ_MAXLEN 2 /* units */
+
+#define UR_GET_DEVICE_ID 0x00
+#define UR_GET_PORT_STATUS 0x01
+#define UR_SOFT_RESET 0x02
+
+#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)
+
+enum {
+ ULPT_BULK_DT_WR,
+ ULPT_BULK_DT_RD,
+ ULPT_INTR_DT_RD,
+ ULPT_N_TRANSFER,
+};
+
+struct ulpt_softc {
+ struct usb_fifo_sc sc_fifo;
+ struct usb_fifo_sc sc_fifo_noreset;
+ struct mtx sc_mtx;
+ struct usb_callout sc_watchdog;
+
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+ struct usb_fifo *sc_fifo_open[2];
+ struct usb_xfer *sc_xfer[ULPT_N_TRANSFER];
+
+ int sc_fflags; /* current open flags, FREAD and
+ * FWRITE */
+ uint8_t sc_iface_no;
+ uint8_t sc_last_status;
+ uint8_t sc_zlps; /* number of consequtive zero length
+ * packets received */
+};
+
+/* prototypes */
+
+static device_probe_t ulpt_probe;
+static device_attach_t ulpt_attach;
+static device_detach_t ulpt_detach;
+
+static usb_callback_t ulpt_write_callback;
+static usb_callback_t ulpt_read_callback;
+static usb_callback_t ulpt_status_callback;
+
+static void ulpt_reset(struct ulpt_softc *);
+static void ulpt_watchdog(void *);
+
+static usb_fifo_close_t ulpt_close;
+static usb_fifo_cmd_t ulpt_start_read;
+static usb_fifo_cmd_t ulpt_start_write;
+static usb_fifo_cmd_t ulpt_stop_read;
+static usb_fifo_cmd_t ulpt_stop_write;
+static usb_fifo_ioctl_t ulpt_ioctl;
+static usb_fifo_open_t ulpt_open;
+static usb_fifo_open_t unlpt_open;
+
+static struct usb_fifo_methods ulpt_fifo_methods = {
+ .f_close = &ulpt_close,
+ .f_ioctl = &ulpt_ioctl,
+ .f_open = &ulpt_open,
+ .f_start_read = &ulpt_start_read,
+ .f_start_write = &ulpt_start_write,
+ .f_stop_read = &ulpt_stop_read,
+ .f_stop_write = &ulpt_stop_write,
+ .basename[0] = "ulpt",
+};
+
+static struct usb_fifo_methods unlpt_fifo_methods = {
+ .f_close = &ulpt_close,
+ .f_ioctl = &ulpt_ioctl,
+ .f_open = &unlpt_open,
+ .f_start_read = &ulpt_start_read,
+ .f_start_write = &ulpt_start_write,
+ .f_stop_read = &ulpt_stop_read,
+ .f_stop_write = &ulpt_stop_write,
+ .basename[0] = "unlpt",
+};
+
+static void
+ulpt_reset(struct ulpt_softc *sc)
+{
+ struct usb_device_request req;
+
+ DPRINTFN(2, "\n");
+
+ req.bRequest = UR_SOFT_RESET;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, sc->sc_iface_no);
+ 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.
+ */
+
+ mtx_lock(&sc->sc_mtx);
+ req.bmRequestType = UT_WRITE_CLASS_OTHER;
+ if (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx,
+ &req, NULL, 0, NULL, 2 * USB_MS_HZ)) { /* 1.0 */
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ if (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx,
+ &req, NULL, 0, NULL, 2 * USB_MS_HZ)) { /* 1.1 */
+ /* ignore error */
+ }
+ }
+ mtx_unlock(&sc->sc_mtx);
+}
+
+static void
+ulpt_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ulpt_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_fifo *f = sc->sc_fifo_open[USB_FIFO_TX];
+ struct usb_page_cache *pc;
+ int actlen, max;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ if (f == NULL) {
+ /* should not happen */
+ DPRINTF("no FIFO\n");
+ return;
+ }
+ DPRINTF("state=0x%x actlen=%d\n", USB_GET_STATE(xfer), actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ max = usbd_xfer_max_len(xfer);
+ if (usb_fifo_get_data(f, pc, 0, max, &actlen, 0)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+ulpt_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ulpt_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_fifo *f = sc->sc_fifo_open[USB_FIFO_RX];
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ if (f == NULL) {
+ /* should not happen */
+ DPRINTF("no FIFO\n");
+ return;
+ }
+ DPRINTF("state=0x%x\n", USB_GET_STATE(xfer));
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ if (actlen == 0) {
+ if (sc->sc_zlps == 4) {
+ /* enable BULK throttle */
+ usbd_xfer_set_interval(xfer, 500); /* ms */
+ } else {
+ sc->sc_zlps++;
+ }
+ } else {
+ /* disable BULK throttle */
+
+ usbd_xfer_set_interval(xfer, 0);
+ sc->sc_zlps = 0;
+ }
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usb_fifo_put_data(f, pc, 0, actlen, 1);
+
+ case USB_ST_SETUP:
+tr_setup:
+ if (usb_fifo_put_bytes_max(f) != 0) {
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ /* disable BULK throttle */
+ usbd_xfer_set_interval(xfer, 0);
+ sc->sc_zlps = 0;
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+ulpt_status_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ulpt_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_device_request req;
+ struct usb_page_cache *pc;
+ uint8_t cur_status;
+ uint8_t new_status;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ pc = usbd_xfer_get_frame(xfer, 1);
+ usbd_copy_out(pc, 0, &cur_status, 1);
+
+ cur_status = (cur_status ^ LPS_INVERT) & LPS_MASK;
+ new_status = cur_status & ~sc->sc_last_status;
+ sc->sc_last_status = cur_status;
+
+ if (new_status & LPS_SELECT)
+ log(LOG_NOTICE, "%s: offline\n",
+ device_get_nameunit(sc->sc_dev));
+ else if (new_status & LPS_NOPAPER)
+ log(LOG_NOTICE, "%s: out of paper\n",
+ device_get_nameunit(sc->sc_dev));
+ else if (new_status & LPS_NERR)
+ log(LOG_NOTICE, "%s: output error\n",
+ device_get_nameunit(sc->sc_dev));
+ break;
+
+ case USB_ST_SETUP:
+ req.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.bRequest = UR_GET_PORT_STATUS;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 1);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, 1);
+ usbd_xfer_set_frames(xfer, 2);
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ DPRINTF("error=%s\n", usbd_errstr(error));
+ if (error != USB_ERR_CANCELLED) {
+ /* wait for next watchdog timeout */
+ }
+ break;
+ }
+}
+
+static const struct usb_config ulpt_config[ULPT_N_TRANSFER] = {
+ [ULPT_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = ULPT_BSIZE,
+ .flags = {.pipe_bof = 1,.proxy_buffer = 1},
+ .callback = &ulpt_write_callback,
+ },
+
+ [ULPT_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = ULPT_BSIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1},
+ .callback = &ulpt_read_callback,
+ },
+
+ [ULPT_INTR_DT_RD] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request) + 1,
+ .callback = &ulpt_status_callback,
+ .timeout = 1000, /* 1 second */
+ },
+};
+
+static void
+ulpt_start_read(struct usb_fifo *fifo)
+{
+ struct ulpt_softc *sc = usb_fifo_softc(fifo);
+
+ usbd_transfer_start(sc->sc_xfer[ULPT_BULK_DT_RD]);
+}
+
+static void
+ulpt_stop_read(struct usb_fifo *fifo)
+{
+ struct ulpt_softc *sc = usb_fifo_softc(fifo);
+
+ usbd_transfer_stop(sc->sc_xfer[ULPT_BULK_DT_RD]);
+}
+
+static void
+ulpt_start_write(struct usb_fifo *fifo)
+{
+ struct ulpt_softc *sc = usb_fifo_softc(fifo);
+
+ usbd_transfer_start(sc->sc_xfer[ULPT_BULK_DT_WR]);
+}
+
+static void
+ulpt_stop_write(struct usb_fifo *fifo)
+{
+ struct ulpt_softc *sc = usb_fifo_softc(fifo);
+
+ usbd_transfer_stop(sc->sc_xfer[ULPT_BULK_DT_WR]);
+}
+
+static int
+ulpt_open(struct usb_fifo *fifo, int fflags)
+{
+ struct ulpt_softc *sc = usb_fifo_softc(fifo);
+
+ /* we assume that open is a serial process */
+
+ if (sc->sc_fflags == 0) {
+ /* reset USB parallel port */
+
+ ulpt_reset(sc);
+ }
+ return (unlpt_open(fifo, fflags));
+}
+
+static int
+unlpt_open(struct usb_fifo *fifo, int fflags)
+{
+ struct ulpt_softc *sc = usb_fifo_softc(fifo);
+
+ if (sc->sc_fflags & fflags) {
+ return (EBUSY);
+ }
+ if (fflags & FREAD) {
+ /* clear stall first */
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(sc->sc_xfer[ULPT_BULK_DT_RD]);
+ mtx_unlock(&sc->sc_mtx);
+ if (usb_fifo_alloc_buffer(fifo,
+ usbd_xfer_max_len(sc->sc_xfer[ULPT_BULK_DT_RD]),
+ ULPT_IFQ_MAXLEN)) {
+ return (ENOMEM);
+ }
+ /* set which FIFO is opened */
+ sc->sc_fifo_open[USB_FIFO_RX] = fifo;
+ }
+ if (fflags & FWRITE) {
+ /* clear stall first */
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(sc->sc_xfer[ULPT_BULK_DT_WR]);
+ mtx_unlock(&sc->sc_mtx);
+ if (usb_fifo_alloc_buffer(fifo,
+ usbd_xfer_max_len(sc->sc_xfer[ULPT_BULK_DT_WR]),
+ ULPT_IFQ_MAXLEN)) {
+ return (ENOMEM);
+ }
+ /* set which FIFO is opened */
+ sc->sc_fifo_open[USB_FIFO_TX] = fifo;
+ }
+ sc->sc_fflags |= fflags & (FREAD | FWRITE);
+ return (0);
+}
+
+static void
+ulpt_close(struct usb_fifo *fifo, int fflags)
+{
+ struct ulpt_softc *sc = usb_fifo_softc(fifo);
+
+ sc->sc_fflags &= ~(fflags & (FREAD | FWRITE));
+
+ if (fflags & (FREAD | FWRITE)) {
+ usb_fifo_free_buffer(fifo);
+ }
+}
+
+static int
+ulpt_ioctl(struct usb_fifo *fifo, u_long cmd, void *data,
+ int fflags)
+{
+ return (ENODEV);
+}
+
+static const STRUCT_USB_HOST_ID ulpt_devs[] = {
+ /* Uni-directional USB printer */
+ {USB_IFACE_CLASS(UICLASS_PRINTER),
+ USB_IFACE_SUBCLASS(UISUBCLASS_PRINTER),
+ USB_IFACE_PROTOCOL(UIPROTO_PRINTER_UNI)},
+
+ /* Bi-directional USB printer */
+ {USB_IFACE_CLASS(UICLASS_PRINTER),
+ USB_IFACE_SUBCLASS(UISUBCLASS_PRINTER),
+ USB_IFACE_PROTOCOL(UIPROTO_PRINTER_BI)},
+
+ /* 1284 USB printer */
+ {USB_IFACE_CLASS(UICLASS_PRINTER),
+ USB_IFACE_SUBCLASS(UISUBCLASS_PRINTER),
+ USB_IFACE_PROTOCOL(UIPROTO_PRINTER_1284)},
+
+ /* Epson printer */
+ {USB_VENDOR(USB_VENDOR_EPSON),
+ USB_PRODUCT(USB_PRODUCT_EPSON_TMU220B),
+ USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(UISUBCLASS_VENDOR),
+ USB_IFACE_PROTOCOL(UIPROTO_PRINTER_BI)},
+};
+
+static int
+ulpt_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ int error;
+
+ DPRINTFN(11, "\n");
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ error = usbd_lookup_id_by_uaa(ulpt_devs, sizeof(ulpt_devs), uaa);
+ if (error)
+ return (error);
+
+ return (BUS_PROBE_GENERIC);
+}
+
+static int
+ulpt_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct ulpt_softc *sc = device_get_softc(dev);
+ struct usb_interface_descriptor *id;
+ int unit = device_get_unit(dev);
+ int error;
+ uint8_t iface_index = uaa->info.bIfaceIndex;
+ uint8_t alt_index;
+
+ DPRINTFN(11, "sc=%p\n", sc);
+
+ sc->sc_dev = dev;
+ sc->sc_udev = uaa->device;
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, "ulpt lock", NULL, MTX_DEF | MTX_RECURSE);
+
+ usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0);
+
+ /* search through all the descriptors looking for bidir mode */
+
+ id = usbd_get_interface_descriptor(uaa->iface);
+ alt_index = 0xFF;
+ while (1) {
+ if (id == NULL) {
+ break;
+ }
+ if ((id->bDescriptorType == UDESC_INTERFACE) &&
+ (id->bLength >= sizeof(*id))) {
+ if (id->bInterfaceNumber != uaa->info.bIfaceNum) {
+ break;
+ } else {
+ alt_index++;
+ if ((id->bInterfaceClass == UICLASS_PRINTER ||
+ id->bInterfaceClass == UICLASS_VENDOR) &&
+ (id->bInterfaceSubClass == UISUBCLASS_PRINTER ||
+ id->bInterfaceSubClass == UISUBCLASS_VENDOR) &&
+ (id->bInterfaceProtocol == UIPROTO_PRINTER_BI)) {
+ goto found;
+ }
+ }
+ }
+ id = (void *)usb_desc_foreach(
+ usbd_get_config_descriptor(uaa->device), (void *)id);
+ }
+ goto detach;
+
+found:
+
+ DPRINTF("setting alternate "
+ "config number: %d\n", alt_index);
+
+ if (alt_index) {
+ error = usbd_set_alt_interface_index
+ (uaa->device, iface_index, alt_index);
+
+ if (error) {
+ DPRINTF("could not set alternate "
+ "config, error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+ }
+ sc->sc_iface_no = id->bInterfaceNumber;
+
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, ulpt_config, ULPT_N_TRANSFER,
+ sc, &sc->sc_mtx);
+ if (error) {
+ DPRINTF("error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+ device_printf(sc->sc_dev, "using bi-directional mode\n");
+
+#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*
+ */
+ {
+ struct usb_config_descriptor *cd = usbd_get_config_descriptor(dev);
+ struct usb_device_request 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);
+ error = usbd_do_request_flags(dev, &req, devinfo, USB_SHORT_XFER_OK,
+ &alen, USB_DEFAULT_TIMEOUT);
+ if (error) {
+ device_printf(sc->sc_dev, "cannot get device id\n");
+ } else if (alen <= 2) {
+ device_printf(sc->sc_dev, "empty device id, no "
+ "printer connected?\n");
+ } 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
+
+ error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx,
+ &ulpt_fifo_methods, &sc->sc_fifo,
+ unit, -1, uaa->info.bIfaceIndex,
+ UID_ROOT, GID_OPERATOR, 0644);
+ if (error) {
+ goto detach;
+ }
+ error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx,
+ &unlpt_fifo_methods, &sc->sc_fifo_noreset,
+ unit, -1, uaa->info.bIfaceIndex,
+ UID_ROOT, GID_OPERATOR, 0644);
+ if (error) {
+ goto detach;
+ }
+ /* start reading of status */
+
+ mtx_lock(&sc->sc_mtx);
+ ulpt_watchdog(sc);
+ mtx_unlock(&sc->sc_mtx);
+ return (0);
+
+detach:
+ ulpt_detach(dev);
+ return (ENOMEM);
+}
+
+static int
+ulpt_detach(device_t dev)
+{
+ struct ulpt_softc *sc = device_get_softc(dev);
+
+ DPRINTF("sc=%p\n", sc);
+
+ usb_fifo_detach(&sc->sc_fifo);
+ usb_fifo_detach(&sc->sc_fifo_noreset);
+
+ mtx_lock(&sc->sc_mtx);
+ usb_callout_stop(&sc->sc_watchdog);
+ mtx_unlock(&sc->sc_mtx);
+
+ usbd_transfer_unsetup(sc->sc_xfer, ULPT_N_TRANSFER);
+ usb_callout_drain(&sc->sc_watchdog);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+#if 0
+/* XXX This does not belong here. */
+
+/*
+ * Compare two strings until the second ends.
+ */
+
+static uint8_t
+ieee1284_compare(const char *a, const char *b)
+{
+ while (1) {
+ if (*b == 0) {
+ break;
+ }
+ if (*a != *b) {
+ return 1;
+ }
+ b++;
+ a++;
+ }
+ return 0;
+}
+
+/*
+ * 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 (ieee1284_compare(p, "MFG:") == 0 ||
+ ieee1284_compare(p, "MANUFACTURER:") == 0 ||
+ ieee1284_compare(p, "MDL:") == 0 ||
+ ieee1284_compare(p, "MODEL:") == 0) {
+ q = strchr(p, ';');
+ if (q)
+ printf("%.*s", (int)(q - p + 1), p);
+ }
+ }
+}
+
+#endif
+
+static void
+ulpt_watchdog(void *arg)
+{
+ struct ulpt_softc *sc = arg;
+
+ mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+ /*
+ * Only read status while the device is not opened, due to
+ * possible hardware or firmware bug in some printers.
+ */
+ if (sc->sc_fflags == 0)
+ usbd_transfer_start(sc->sc_xfer[ULPT_INTR_DT_RD]);
+
+ usb_callout_reset(&sc->sc_watchdog,
+ hz, &ulpt_watchdog, sc);
+}
+
+static device_method_t ulpt_methods[] = {
+ DEVMETHOD(device_probe, ulpt_probe),
+ DEVMETHOD(device_attach, ulpt_attach),
+ DEVMETHOD(device_detach, ulpt_detach),
+ DEVMETHOD_END
+};
+
+static driver_t ulpt_driver = {
+ .name = "ulpt",
+ .methods = ulpt_methods,
+ .size = sizeof(struct ulpt_softc),
+};
+
+DRIVER_MODULE(ulpt, uhub, ulpt_driver, NULL, NULL);
+MODULE_DEPEND(ulpt, usb, 1, 1, 1);
+MODULE_VERSION(ulpt, 1);
+USB_PNP_HOST_INFO(ulpt_devs);
diff --git a/sys/dev/usb/serial/umcs.c b/sys/dev/usb/serial/umcs.c
new file mode 100644
index 000000000000..8b9b7807ac61
--- /dev/null
+++ b/sys/dev/usb/serial/umcs.c
@@ -0,0 +1,1101 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Lev Serebryakov <lev@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.
+ */
+
+/*
+ * This driver supports several multiport USB-to-RS232 serial adapters driven
+ * by MosChip mos7820 and mos7840, bridge chips.
+ * The adapters are sold under many different brand names.
+ *
+ * Datasheets are available at MosChip www site at
+ * http://www.moschip.com. The datasheets don't contain full
+ * programming information for the chip.
+ *
+ * It is normal to have only two enabled ports in devices, based on
+ * quad-port mos7840.
+ *
+ */
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/linker_set.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_cdc.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR umcs_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#include <dev/usb/serial/umcs.h>
+
+#define UMCS7840_MODVER 1
+
+#ifdef USB_DEBUG
+static int umcs_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, umcs, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB umcs quadport serial adapter");
+SYSCTL_INT(_hw_usb_umcs, OID_AUTO, debug, CTLFLAG_RWTUN, &umcs_debug, 0, "Debug level");
+#endif /* USB_DEBUG */
+
+/*
+ * Two-port devices (both with 7820 chip and 7840 chip configured as two-port)
+ * have ports 0 and 2, with ports 1 and 3 omitted.
+ * So,PHYSICAL port numbers (indexes) on two-port device will be 0 and 2.
+ * This driver trys to use physical numbers as much as possible.
+ */
+
+/*
+ * Indexed by PHYSICAL port number.
+ * Pack non-regular registers to array to easier if-less access.
+ */
+struct umcs7840_port_registers {
+ uint8_t reg_sp; /* SP register. */
+ uint8_t reg_control; /* CONTROL register. */
+ uint8_t reg_dcr; /* DCR0 register. DCR1 & DCR2 can be
+ * calculated */
+};
+
+static const struct umcs7840_port_registers umcs7840_port_registers[UMCS7840_MAX_PORTS] = {
+ {.reg_sp = MCS7840_DEV_REG_SP1,.reg_control = MCS7840_DEV_REG_CONTROL1,.reg_dcr = MCS7840_DEV_REG_DCR0_1},
+ {.reg_sp = MCS7840_DEV_REG_SP2,.reg_control = MCS7840_DEV_REG_CONTROL2,.reg_dcr = MCS7840_DEV_REG_DCR0_2},
+ {.reg_sp = MCS7840_DEV_REG_SP3,.reg_control = MCS7840_DEV_REG_CONTROL3,.reg_dcr = MCS7840_DEV_REG_DCR0_3},
+ {.reg_sp = MCS7840_DEV_REG_SP4,.reg_control = MCS7840_DEV_REG_CONTROL4,.reg_dcr = MCS7840_DEV_REG_DCR0_4},
+};
+
+enum {
+ UMCS7840_BULK_RD_EP,
+ UMCS7840_BULK_WR_EP,
+ UMCS7840_N_TRANSFERS
+};
+
+struct umcs7840_softc_oneport {
+ struct usb_xfer *sc_xfer[UMCS7840_N_TRANSFERS]; /* Control structures
+ * for two transfers */
+
+ uint8_t sc_lcr; /* local line control register */
+ uint8_t sc_mcr; /* local modem control register */
+};
+
+struct umcs7840_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom[UMCS7840_MAX_PORTS]; /* Need to be continuous
+ * array, so indexed by
+ * LOGICAL port
+ * (subunit) number */
+
+ struct usb_xfer *sc_intr_xfer; /* Interrupt endpoint */
+
+ device_t sc_dev; /* Device for error prints */
+ struct usb_device *sc_udev; /* USB Device for all operations */
+ struct mtx sc_mtx; /* ucom requires this */
+
+ uint8_t sc_driver_done; /* Flag when enumeration is finished */
+
+ uint8_t sc_numports; /* Number of ports (subunits) */
+ struct umcs7840_softc_oneport sc_ports[UMCS7840_MAX_PORTS]; /* Indexed by PHYSICAL
+ * port number. */
+};
+
+/* prototypes */
+static usb_error_t umcs7840_get_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t *);
+static usb_error_t umcs7840_set_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t);
+static usb_error_t umcs7840_get_UART_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t, uint8_t *);
+static usb_error_t umcs7840_set_UART_reg_sync(struct umcs7840_softc *, uint8_t, uint8_t, uint8_t);
+
+static usb_error_t umcs7840_set_baudrate(struct umcs7840_softc *, uint8_t, uint32_t);
+static usb_error_t umcs7840_calc_baudrate(uint32_t rate, uint16_t *, uint8_t *);
+
+static void umcs7840_free(struct ucom_softc *);
+static void umcs7840_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *);
+static void umcs7840_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void umcs7840_cfg_set_rts(struct ucom_softc *, uint8_t);
+static void umcs7840_cfg_set_break(struct ucom_softc *, uint8_t);
+static void umcs7840_cfg_param(struct ucom_softc *, struct termios *);
+static void umcs7840_cfg_open(struct ucom_softc *);
+static void umcs7840_cfg_close(struct ucom_softc *);
+
+static int umcs7840_pre_param(struct ucom_softc *, struct termios *);
+
+static void umcs7840_start_read(struct ucom_softc *);
+static void umcs7840_stop_read(struct ucom_softc *);
+
+static void umcs7840_start_write(struct ucom_softc *);
+static void umcs7840_stop_write(struct ucom_softc *);
+
+static void umcs7840_poll(struct ucom_softc *ucom);
+
+static device_probe_t umcs7840_probe;
+static device_attach_t umcs7840_attach;
+static device_detach_t umcs7840_detach;
+static void umcs7840_free_softc(struct umcs7840_softc *);
+
+static usb_callback_t umcs7840_intr_callback;
+static usb_callback_t umcs7840_read_callback1;
+static usb_callback_t umcs7840_read_callback2;
+static usb_callback_t umcs7840_read_callback3;
+static usb_callback_t umcs7840_read_callback4;
+static usb_callback_t umcs7840_write_callback1;
+static usb_callback_t umcs7840_write_callback2;
+static usb_callback_t umcs7840_write_callback3;
+static usb_callback_t umcs7840_write_callback4;
+
+static void umcs7840_read_callbackN(struct usb_xfer *, usb_error_t, uint8_t);
+static void umcs7840_write_callbackN(struct usb_xfer *, usb_error_t, uint8_t);
+
+/* Indexed by LOGICAL port number (subunit), so two-port device uses 0 & 1 */
+static usb_callback_t *umcs7840_rw_callbacks[UMCS7840_MAX_PORTS][UMCS7840_N_TRANSFERS] = {
+ {&umcs7840_read_callback1, &umcs7840_write_callback1},
+ {&umcs7840_read_callback2, &umcs7840_write_callback2},
+ {&umcs7840_read_callback3, &umcs7840_write_callback3},
+ {&umcs7840_read_callback4, &umcs7840_write_callback4},
+};
+
+static const struct usb_config umcs7840_bulk_config_data[UMCS7840_N_TRANSFERS] = {
+ [UMCS7840_BULK_RD_EP] = {
+ .type = UE_BULK,
+ .endpoint = 0x01,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &umcs7840_read_callback1,
+ .if_index = 0,
+ },
+
+ [UMCS7840_BULK_WR_EP] = {
+ .type = UE_BULK,
+ .endpoint = 0x02,
+ .direction = UE_DIR_OUT,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &umcs7840_write_callback1,
+ .if_index = 0,
+ },
+};
+
+static const struct usb_config umcs7840_intr_config_data[1] = {
+ [0] = {
+ .type = UE_INTERRUPT,
+ .endpoint = 0x09,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &umcs7840_intr_callback,
+ .if_index = 0,
+ },
+};
+
+static struct ucom_callback umcs7840_callback = {
+ .ucom_cfg_get_status = &umcs7840_cfg_get_status,
+
+ .ucom_cfg_set_dtr = &umcs7840_cfg_set_dtr,
+ .ucom_cfg_set_rts = &umcs7840_cfg_set_rts,
+ .ucom_cfg_set_break = &umcs7840_cfg_set_break,
+
+ .ucom_cfg_param = &umcs7840_cfg_param,
+ .ucom_cfg_open = &umcs7840_cfg_open,
+ .ucom_cfg_close = &umcs7840_cfg_close,
+
+ .ucom_pre_param = &umcs7840_pre_param,
+
+ .ucom_start_read = &umcs7840_start_read,
+ .ucom_stop_read = &umcs7840_stop_read,
+
+ .ucom_start_write = &umcs7840_start_write,
+ .ucom_stop_write = &umcs7840_stop_write,
+
+ .ucom_poll = &umcs7840_poll,
+ .ucom_free = &umcs7840_free,
+};
+
+static const STRUCT_USB_HOST_ID umcs7840_devs[] = {
+ {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7820, 0)},
+ {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7840, 0)},
+};
+
+static device_method_t umcs7840_methods[] = {
+ DEVMETHOD(device_probe, umcs7840_probe),
+ DEVMETHOD(device_attach, umcs7840_attach),
+ DEVMETHOD(device_detach, umcs7840_detach),
+ DEVMETHOD_END
+};
+
+static driver_t umcs7840_driver = {
+ .name = "umcs7840",
+ .methods = umcs7840_methods,
+ .size = sizeof(struct umcs7840_softc),
+};
+
+DRIVER_MODULE(umcs7840, uhub, umcs7840_driver, 0, 0);
+MODULE_DEPEND(umcs7840, ucom, 1, 1, 1);
+MODULE_DEPEND(umcs7840, usb, 1, 1, 1);
+MODULE_VERSION(umcs7840, UMCS7840_MODVER);
+USB_PNP_HOST_INFO(umcs7840_devs);
+
+static int
+umcs7840_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != MCS7840_CONFIG_INDEX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != MCS7840_IFACE_INDEX)
+ return (ENXIO);
+ return (usbd_lookup_id_by_uaa(umcs7840_devs, sizeof(umcs7840_devs), uaa));
+}
+
+static int
+umcs7840_attach(device_t dev)
+{
+ struct usb_config umcs7840_config_tmp[UMCS7840_N_TRANSFERS];
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct umcs7840_softc *sc = device_get_softc(dev);
+
+ uint8_t iface_index = MCS7840_IFACE_INDEX;
+ int error;
+ int subunit;
+ int n;
+ uint8_t data;
+
+ for (n = 0; n < UMCS7840_N_TRANSFERS; ++n)
+ umcs7840_config_tmp[n] = umcs7840_bulk_config_data[n];
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "umcs7840", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ sc->sc_dev = dev;
+ sc->sc_udev = uaa->device;
+
+ /*
+ * Get number of ports
+ * Documentation (full datasheet) says, that number of ports is
+ * set as MCS7840_DEV_MODE_SELECT24S bit in MODE R/Only
+ * register. But vendor driver uses these undocumented
+ * register & bit.
+ *
+ * Experiments show, that MODE register can have `0'
+ * (4 ports) bit on 2-port device, so use vendor driver's way.
+ *
+ * Also, see notes in header file for these constants.
+ */
+ umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_GPIO, &data);
+ if (data & MCS7840_DEV_GPIO_4PORTS) {
+ sc->sc_numports = 4;
+ /* Store physical port numbers in sc_portno */
+ sc->sc_ucom[0].sc_portno = 0;
+ sc->sc_ucom[1].sc_portno = 1;
+ sc->sc_ucom[2].sc_portno = 2;
+ sc->sc_ucom[3].sc_portno = 3;
+ } else {
+ sc->sc_numports = 2;
+ /* Store physical port numbers in sc_portno */
+ sc->sc_ucom[0].sc_portno = 0;
+ sc->sc_ucom[1].sc_portno = 2; /* '1' is skipped */
+ }
+ device_printf(dev, "Chip mcs%04x, found %d active ports\n", uaa->info.idProduct, sc->sc_numports);
+ if (!umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_MODE, &data)) {
+ device_printf(dev, "On-die configuration: RST: active %s, HRD: %s, PLL: %s, POR: %s, Ports: %s, EEPROM write %s, IrDA is %savailable\n",
+ (data & MCS7840_DEV_MODE_RESET) ? "low" : "high",
+ (data & MCS7840_DEV_MODE_SER_PRSNT) ? "yes" : "no",
+ (data & MCS7840_DEV_MODE_PLLBYPASS) ? "bypassed" : "avail",
+ (data & MCS7840_DEV_MODE_PORBYPASS) ? "bypassed" : "avail",
+ (data & MCS7840_DEV_MODE_SELECT24S) ? "2" : "4",
+ (data & MCS7840_DEV_MODE_EEPROMWR) ? "enabled" : "disabled",
+ (data & MCS7840_DEV_MODE_IRDA) ? "" : "not ");
+ }
+ /* Setup all transfers */
+ for (subunit = 0; subunit < sc->sc_numports; ++subunit) {
+ for (n = 0; n < UMCS7840_N_TRANSFERS; ++n) {
+ /* Set endpoint address */
+ umcs7840_config_tmp[n].endpoint = umcs7840_bulk_config_data[n].endpoint + 2 * sc->sc_ucom[subunit].sc_portno;
+ umcs7840_config_tmp[n].callback = umcs7840_rw_callbacks[subunit][n];
+ }
+ error = usbd_transfer_setup(uaa->device,
+ &iface_index, sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer, umcs7840_config_tmp,
+ UMCS7840_N_TRANSFERS, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "allocating USB transfers failed for subunit %d of %d\n",
+ subunit + 1, sc->sc_numports);
+ goto detach;
+ }
+ }
+ error = usbd_transfer_setup(uaa->device,
+ &iface_index, &sc->sc_intr_xfer, umcs7840_intr_config_data,
+ 1, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "allocating USB transfers failed for interrupt\n");
+ goto detach;
+ }
+ /* clear stall at first run */
+ mtx_lock(&sc->sc_mtx);
+ for (subunit = 0; subunit < sc->sc_numports; ++subunit) {
+ usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_RD_EP]);
+ usbd_xfer_set_stall(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer[UMCS7840_BULK_WR_EP]);
+ }
+ mtx_unlock(&sc->sc_mtx);
+
+ error = ucom_attach(&sc->sc_super_ucom, sc->sc_ucom, sc->sc_numports, sc,
+ &umcs7840_callback, &sc->sc_mtx);
+ if (error)
+ goto detach;
+
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ return (0);
+
+detach:
+ umcs7840_detach(dev);
+ return (ENXIO);
+}
+
+static int
+umcs7840_detach(device_t dev)
+{
+ struct umcs7840_softc *sc = device_get_softc(dev);
+ int subunit;
+
+ ucom_detach(&sc->sc_super_ucom, sc->sc_ucom);
+
+ for (subunit = 0; subunit < sc->sc_numports; ++subunit)
+ usbd_transfer_unsetup(sc->sc_ports[sc->sc_ucom[subunit].sc_portno].sc_xfer, UMCS7840_N_TRANSFERS);
+ usbd_transfer_unsetup(&sc->sc_intr_xfer, 1);
+
+ device_claim_softc(dev);
+
+ umcs7840_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(umcs7840);
+
+static void
+umcs7840_free_softc(struct umcs7840_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+umcs7840_free(struct ucom_softc *ucom)
+{
+ umcs7840_free_softc(ucom->sc_parent);
+}
+
+static void
+umcs7840_cfg_open(struct ucom_softc *ucom)
+{
+ struct umcs7840_softc *sc = ucom->sc_parent;
+ uint16_t pn = ucom->sc_portno;
+ uint8_t data;
+
+ /* If it very first open, finish global configuration */
+ if (!sc->sc_driver_done) {
+ /*
+ * USB enumeration is finished, pass internal memory to FIFOs
+ * If it is done in the end of "attach", kernel panics.
+ */
+ if (umcs7840_get_reg_sync(sc, MCS7840_DEV_REG_CONTROL1, &data))
+ return;
+ data |= MCS7840_DEV_CONTROL1_DRIVER_DONE;
+ if (umcs7840_set_reg_sync(sc, MCS7840_DEV_REG_CONTROL1, data))
+ return;
+ sc->sc_driver_done = 1;
+ }
+ /* Toggle reset bit on-off */
+ if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, &data))
+ return;
+ data |= MCS7840_DEV_SPx_UART_RESET;
+ if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data))
+ return;
+ data &= ~MCS7840_DEV_SPx_UART_RESET;
+ if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data))
+ return;
+
+ /* Set RS-232 mode */
+ if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_SCRATCHPAD, MCS7840_UART_SCRATCHPAD_RS232))
+ return;
+
+ /* Disable RX on time of initialization */
+ if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data))
+ return;
+ data |= MCS7840_DEV_CONTROLx_RX_DISABLE;
+ if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data))
+ return;
+
+ /* Disable all interrupts */
+ if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, 0))
+ return;
+
+ /* Reset FIFO -- documented */
+ if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_FCR, 0))
+ return;
+ if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_FCR,
+ MCS7840_UART_FCR_ENABLE | MCS7840_UART_FCR_FLUSHRHR |
+ MCS7840_UART_FCR_FLUSHTHR | MCS7840_UART_FCR_RTL_1_14))
+ return;
+
+ /* Set 8 bit, no parity, 1 stop bit -- documented */
+ sc->sc_ports[pn].sc_lcr = MCS7840_UART_LCR_DATALEN8 | MCS7840_UART_LCR_STOPB1;
+ if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr))
+ return;
+
+ /*
+ * Enable DTR/RTS on modem control, enable modem interrupts --
+ * documented
+ */
+ sc->sc_ports[pn].sc_mcr = MCS7840_UART_MCR_IE;
+ if (ucom->sc_tty == NULL || (ucom->sc_tty->t_termios.c_cflag & CNO_RTSDTR) == 0)
+ sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_DTR | MCS7840_UART_MCR_RTS;
+ if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr))
+ return;
+
+ /* Clearing Bulkin and Bulkout FIFO */
+ if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, &data))
+ return;
+ data |= MCS7840_DEV_SPx_RESET_OUT_FIFO | MCS7840_DEV_SPx_RESET_IN_FIFO;
+ if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data))
+ return;
+ data &= ~(MCS7840_DEV_SPx_RESET_OUT_FIFO | MCS7840_DEV_SPx_RESET_IN_FIFO);
+ if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_sp, data))
+ return;
+
+ /* Set speed 9600 */
+ if (umcs7840_set_baudrate(sc, pn, 9600))
+ return;
+
+ /* Finally enable all interrupts -- documented */
+ /*
+ * Copied from vendor driver, I don't know why we should read LCR
+ * here
+ */
+ if (umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, &sc->sc_ports[pn].sc_lcr))
+ return;
+ if (umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER,
+ MCS7840_UART_IER_RXSTAT | MCS7840_UART_IER_MODEM))
+ return;
+
+ /* Enable RX */
+ if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data))
+ return;
+ data &= ~MCS7840_DEV_CONTROLx_RX_DISABLE;
+ if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data))
+ return;
+
+ DPRINTF("Port %d has been opened\n", pn);
+}
+
+static void
+umcs7840_cfg_close(struct ucom_softc *ucom)
+{
+ struct umcs7840_softc *sc = ucom->sc_parent;
+ uint16_t pn = ucom->sc_portno;
+ uint8_t data;
+
+ umcs7840_stop_read(ucom);
+ umcs7840_stop_write(ucom);
+
+ umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, 0);
+ umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_IER, 0);
+
+ /* Disable RX */
+ if (umcs7840_get_reg_sync(sc, umcs7840_port_registers[pn].reg_control, &data))
+ return;
+ data |= MCS7840_DEV_CONTROLx_RX_DISABLE;
+ if (umcs7840_set_reg_sync(sc, umcs7840_port_registers[pn].reg_control, data))
+ return;
+ DPRINTF("Port %d has been closed\n", pn);
+}
+
+static void
+umcs7840_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct umcs7840_softc *sc = ucom->sc_parent;
+ uint8_t pn = ucom->sc_portno;
+
+ if (onoff)
+ sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_DTR;
+ else
+ sc->sc_ports[pn].sc_mcr &= ~MCS7840_UART_MCR_DTR;
+
+ umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr);
+ DPRINTF("Port %d DTR set to: %s\n", pn, onoff ? "on" : "off");
+}
+
+static void
+umcs7840_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct umcs7840_softc *sc = ucom->sc_parent;
+ uint8_t pn = ucom->sc_portno;
+
+ if (onoff)
+ sc->sc_ports[pn].sc_mcr |= MCS7840_UART_MCR_RTS;
+ else
+ sc->sc_ports[pn].sc_mcr &= ~MCS7840_UART_MCR_RTS;
+
+ umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr);
+ DPRINTF("Port %d RTS set to: %s\n", pn, onoff ? "on" : "off");
+}
+
+static void
+umcs7840_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct umcs7840_softc *sc = ucom->sc_parent;
+ uint8_t pn = ucom->sc_portno;
+
+ if (onoff)
+ sc->sc_ports[pn].sc_lcr |= MCS7840_UART_LCR_BREAK;
+ else
+ sc->sc_ports[pn].sc_lcr &= ~MCS7840_UART_LCR_BREAK;
+
+ umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr);
+ DPRINTF("Port %d BREAK set to: %s\n", pn, onoff ? "on" : "off");
+}
+
+static void
+umcs7840_cfg_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct umcs7840_softc *sc = ucom->sc_parent;
+ uint8_t pn = ucom->sc_portno;
+ uint8_t lcr = sc->sc_ports[pn].sc_lcr;
+ uint8_t mcr = sc->sc_ports[pn].sc_mcr;
+
+ DPRINTF("Port %d config:\n", pn);
+ if (t->c_cflag & CSTOPB) {
+ DPRINTF(" 2 stop bits\n");
+ lcr |= MCS7840_UART_LCR_STOPB2;
+ } else {
+ lcr |= MCS7840_UART_LCR_STOPB1;
+ DPRINTF(" 1 stop bit\n");
+ }
+
+ lcr &= ~MCS7840_UART_LCR_PARITYMASK;
+ if (t->c_cflag & PARENB) {
+ lcr |= MCS7840_UART_LCR_PARITYON;
+ if (t->c_cflag & PARODD) {
+ lcr = MCS7840_UART_LCR_PARITYODD;
+ DPRINTF(" parity on - odd\n");
+ } else {
+ lcr = MCS7840_UART_LCR_PARITYEVEN;
+ DPRINTF(" parity on - even\n");
+ }
+ } else {
+ lcr &= ~MCS7840_UART_LCR_PARITYON;
+ DPRINTF(" parity off\n");
+ }
+
+ lcr &= ~MCS7840_UART_LCR_DATALENMASK;
+ switch (t->c_cflag & CSIZE) {
+ case CS5:
+ lcr |= MCS7840_UART_LCR_DATALEN5;
+ DPRINTF(" 5 bit\n");
+ break;
+ case CS6:
+ lcr |= MCS7840_UART_LCR_DATALEN6;
+ DPRINTF(" 6 bit\n");
+ break;
+ case CS7:
+ lcr |= MCS7840_UART_LCR_DATALEN7;
+ DPRINTF(" 7 bit\n");
+ break;
+ case CS8:
+ lcr |= MCS7840_UART_LCR_DATALEN8;
+ DPRINTF(" 8 bit\n");
+ break;
+ }
+
+ if (t->c_cflag & CRTSCTS) {
+ mcr |= MCS7840_UART_MCR_CTSRTS;
+ DPRINTF(" CTS/RTS\n");
+ } else
+ mcr &= ~MCS7840_UART_MCR_CTSRTS;
+
+ if (t->c_cflag & (CDTR_IFLOW | CDSR_OFLOW)) {
+ mcr |= MCS7840_UART_MCR_DTRDSR;
+ DPRINTF(" DTR/DSR\n");
+ } else
+ mcr &= ~MCS7840_UART_MCR_DTRDSR;
+
+ sc->sc_ports[pn].sc_lcr = lcr;
+ umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_LCR, sc->sc_ports[pn].sc_lcr);
+ DPRINTF("Port %d LCR=%02x\n", pn, sc->sc_ports[pn].sc_lcr);
+
+ sc->sc_ports[pn].sc_mcr = mcr;
+ umcs7840_set_UART_reg_sync(sc, pn, MCS7840_UART_REG_MCR, sc->sc_ports[pn].sc_mcr);
+ DPRINTF("Port %d MCR=%02x\n", pn, sc->sc_ports[pn].sc_mcr);
+
+ umcs7840_set_baudrate(sc, pn, t->c_ospeed);
+}
+
+static int
+umcs7840_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+ uint8_t clk;
+ uint16_t divisor;
+
+ if (umcs7840_calc_baudrate(t->c_ospeed, &divisor, &clk) || !divisor)
+ return (EINVAL);
+ return (0);
+}
+
+static void
+umcs7840_start_read(struct ucom_softc *ucom)
+{
+ struct umcs7840_softc *sc = ucom->sc_parent;
+ uint8_t pn = ucom->sc_portno;
+
+ /* Start interrupt transfer */
+ usbd_transfer_start(sc->sc_intr_xfer);
+
+ /* Start read transfer */
+ usbd_transfer_start(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_RD_EP]);
+}
+
+static void
+umcs7840_stop_read(struct ucom_softc *ucom)
+{
+ struct umcs7840_softc *sc = ucom->sc_parent;
+ uint8_t pn = ucom->sc_portno;
+
+ /* Stop read transfer */
+ usbd_transfer_stop(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_RD_EP]);
+}
+
+static void
+umcs7840_start_write(struct ucom_softc *ucom)
+{
+ struct umcs7840_softc *sc = ucom->sc_parent;
+ uint8_t pn = ucom->sc_portno;
+
+ /* Start interrupt transfer */
+ usbd_transfer_start(sc->sc_intr_xfer);
+
+ /* Start write transfer */
+ usbd_transfer_start(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_WR_EP]);
+}
+
+static void
+umcs7840_stop_write(struct ucom_softc *ucom)
+{
+ struct umcs7840_softc *sc = ucom->sc_parent;
+ uint8_t pn = ucom->sc_portno;
+
+ /* Stop write transfer */
+ usbd_transfer_stop(sc->sc_ports[pn].sc_xfer[UMCS7840_BULK_WR_EP]);
+}
+
+static void
+umcs7840_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct umcs7840_softc *sc = ucom->sc_parent;
+ uint8_t pn = ucom->sc_portno;
+ uint8_t hw_msr = 0; /* local modem status register */
+
+ /*
+ * Read status registers. MSR bits need translation from ns16550 to
+ * SER_* values. LSR bits are ns16550 in hardware and ucom.
+ */
+ umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_LSR, lsr);
+ umcs7840_get_UART_reg_sync(sc, pn, MCS7840_UART_REG_MSR, &hw_msr);
+
+ if (hw_msr & MCS7840_UART_MSR_NEGCTS)
+ *msr |= SER_CTS;
+
+ if (hw_msr & MCS7840_UART_MSR_NEGDCD)
+ *msr |= SER_DCD;
+
+ if (hw_msr & MCS7840_UART_MSR_NEGRI)
+ *msr |= SER_RI;
+
+ if (hw_msr & MCS7840_UART_MSR_NEGDSR)
+ *msr |= SER_DSR;
+
+ DPRINTF("Port %d status: LSR=%02x MSR=%02x\n", ucom->sc_portno, *lsr, *msr);
+}
+
+static void
+umcs7840_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umcs7840_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint8_t buf[13];
+ int actlen;
+ int subunit;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (actlen == 5 || actlen == 13) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, buf, actlen);
+ /* Check status of all ports */
+ for (subunit = 0; subunit < sc->sc_numports; ++subunit) {
+ uint8_t pn = sc->sc_ucom[subunit].sc_portno;
+
+ if (buf[pn] & MCS7840_UART_ISR_NOPENDING)
+ continue;
+ DPRINTF("Port %d has pending interrupt: %02x (FIFO: %02x)\n", pn, buf[pn] & MCS7840_UART_ISR_INTMASK, buf[pn] & (~MCS7840_UART_ISR_INTMASK));
+ switch (buf[pn] & MCS7840_UART_ISR_INTMASK) {
+ case MCS7840_UART_ISR_RXERR:
+ case MCS7840_UART_ISR_RXHASDATA:
+ case MCS7840_UART_ISR_RXTIMEOUT:
+ case MCS7840_UART_ISR_MSCHANGE:
+ ucom_status_change(&sc->sc_ucom[subunit]);
+ break;
+ default:
+ /* Do nothing */
+ break;
+ }
+ }
+ } else
+ device_printf(sc->sc_dev, "Invalid interrupt data length %d", actlen);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+umcs7840_read_callback1(struct usb_xfer *xfer, usb_error_t error)
+{
+ umcs7840_read_callbackN(xfer, error, 0);
+}
+
+static void
+umcs7840_read_callback2(struct usb_xfer *xfer, usb_error_t error)
+{
+ umcs7840_read_callbackN(xfer, error, 1);
+}
+static void
+umcs7840_read_callback3(struct usb_xfer *xfer, usb_error_t error)
+{
+ umcs7840_read_callbackN(xfer, error, 2);
+}
+
+static void
+umcs7840_read_callback4(struct usb_xfer *xfer, usb_error_t error)
+{
+ umcs7840_read_callbackN(xfer, error, 3);
+}
+
+static void
+umcs7840_read_callbackN(struct usb_xfer *xfer, usb_error_t error, uint8_t subunit)
+{
+ struct umcs7840_softc *sc = usbd_xfer_softc(xfer);
+ struct ucom_softc *ucom = &sc->sc_ucom[subunit];
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ DPRINTF("Port %d read, state = %d, data length = %d\n", ucom->sc_portno, USB_GET_STATE(xfer), actlen);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(ucom, pc, 0, actlen);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+umcs7840_write_callback1(struct usb_xfer *xfer, usb_error_t error)
+{
+ umcs7840_write_callbackN(xfer, error, 0);
+}
+
+static void
+umcs7840_write_callback2(struct usb_xfer *xfer, usb_error_t error)
+{
+ umcs7840_write_callbackN(xfer, error, 1);
+}
+
+static void
+umcs7840_write_callback3(struct usb_xfer *xfer, usb_error_t error)
+{
+ umcs7840_write_callbackN(xfer, error, 2);
+}
+
+static void
+umcs7840_write_callback4(struct usb_xfer *xfer, usb_error_t error)
+{
+ umcs7840_write_callbackN(xfer, error, 3);
+}
+
+static void
+umcs7840_write_callbackN(struct usb_xfer *xfer, usb_error_t error, uint8_t subunit)
+{
+ struct umcs7840_softc *sc = usbd_xfer_softc(xfer);
+ struct ucom_softc *ucom = &sc->sc_ucom[subunit];
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ DPRINTF("Port %d write, state = %d\n", ucom->sc_portno, USB_GET_STATE(xfer));
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(ucom, pc, 0, usbd_xfer_max_len(xfer), &actlen)) {
+ DPRINTF("Port %d write, has %d bytes\n", ucom->sc_portno, actlen);
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+umcs7840_poll(struct ucom_softc *ucom)
+{
+ struct umcs7840_softc *sc = ucom->sc_parent;
+
+ DPRINTF("Port %d poll\n", ucom->sc_portno);
+ usbd_transfer_poll(sc->sc_ports[ucom->sc_portno].sc_xfer, UMCS7840_N_TRANSFERS);
+ usbd_transfer_poll(&sc->sc_intr_xfer, 1);
+}
+
+static usb_error_t
+umcs7840_get_reg_sync(struct umcs7840_softc *sc, uint8_t reg, uint8_t *data)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+ uint16_t len;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = MCS7840_RDREQ;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, UMCS7840_READ_LENGTH);
+
+ err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, (void *)data, 0, &len, UMCS7840_CTRL_TIMEOUT);
+ if (err == USB_ERR_NORMAL_COMPLETION && len != 1) {
+ device_printf(sc->sc_dev, "Reading register %d failed: invalid length %d\n", reg, len);
+ return (USB_ERR_INVAL);
+ } else if (err)
+ device_printf(sc->sc_dev, "Reading register %d failed: %s\n", reg, usbd_errstr(err));
+ return (err);
+}
+
+static usb_error_t
+umcs7840_set_reg_sync(struct umcs7840_softc *sc, uint8_t reg, uint8_t data)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = MCS7840_WRREQ;
+ USETW(req.wValue, data);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 0);
+
+ err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, NULL, 0, NULL, UMCS7840_CTRL_TIMEOUT);
+ if (err)
+ device_printf(sc->sc_dev, "Writing register %d failed: %s\n", reg, usbd_errstr(err));
+
+ return (err);
+}
+
+static usb_error_t
+umcs7840_get_UART_reg_sync(struct umcs7840_softc *sc, uint8_t portno, uint8_t reg, uint8_t *data)
+{
+ struct usb_device_request req;
+ uint16_t wVal;
+ usb_error_t err;
+ uint16_t len;
+
+ /* portno is port number */
+ wVal = ((uint16_t)(portno + 1)) << 8;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = MCS7840_RDREQ;
+ USETW(req.wValue, wVal);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, UMCS7840_READ_LENGTH);
+
+ err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, (void *)data, 0, &len, UMCS7840_CTRL_TIMEOUT);
+ if (err == USB_ERR_NORMAL_COMPLETION && len != 1) {
+ device_printf(sc->sc_dev, "Reading UART%d register %d failed: invalid length %d\n", portno, reg, len);
+ return (USB_ERR_INVAL);
+ } else if (err)
+ device_printf(sc->sc_dev, "Reading UART%d register %d failed: %s\n", portno, reg, usbd_errstr(err));
+ return (err);
+}
+
+static usb_error_t
+umcs7840_set_UART_reg_sync(struct umcs7840_softc *sc, uint8_t portno, uint8_t reg, uint8_t data)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+ uint16_t wVal;
+
+ /* portno is port number */
+ wVal = ((uint16_t)(portno + 1)) << 8 | data;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = MCS7840_WRREQ;
+ USETW(req.wValue, wVal);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 0);
+
+ err = usbd_do_request_proc(sc->sc_udev, &sc->sc_super_ucom.sc_tq, &req, NULL, 0, NULL, UMCS7840_CTRL_TIMEOUT);
+ if (err)
+ device_printf(sc->sc_dev, "Writing UART%d register %d failed: %s\n", portno, reg, usbd_errstr(err));
+ return (err);
+}
+
+static usb_error_t
+umcs7840_set_baudrate(struct umcs7840_softc *sc, uint8_t portno, uint32_t rate)
+{
+ usb_error_t err;
+ uint16_t divisor;
+ uint8_t clk;
+ uint8_t data;
+
+ if (umcs7840_calc_baudrate(rate, &divisor, &clk)) {
+ DPRINTF("Port %d bad speed: %d\n", portno, rate);
+ return (-1);
+ }
+ if (divisor == 0 || (clk & MCS7840_DEV_SPx_CLOCK_MASK) != clk) {
+ DPRINTF("Port %d bad speed calculation: %d\n", portno, rate);
+ return (-1);
+ }
+ DPRINTF("Port %d set speed: %d (%02x / %d)\n", portno, rate, clk, divisor);
+
+ /* Set clock source for standard BAUD frequencies */
+ err = umcs7840_get_reg_sync(sc, umcs7840_port_registers[portno].reg_sp, &data);
+ if (err)
+ return (err);
+ data &= MCS7840_DEV_SPx_CLOCK_MASK;
+ data |= clk;
+ err = umcs7840_set_reg_sync(sc, umcs7840_port_registers[portno].reg_sp, data);
+ if (err)
+ return (err);
+
+ /* Set divider */
+ sc->sc_ports[portno].sc_lcr |= MCS7840_UART_LCR_DIVISORS;
+ err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_LCR, sc->sc_ports[portno].sc_lcr);
+ if (err)
+ return (err);
+
+ err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_DLL, (uint8_t)(divisor & 0xff));
+ if (err)
+ return (err);
+ err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_DLM, (uint8_t)((divisor >> 8) & 0xff));
+ if (err)
+ return (err);
+
+ /* Turn off access to DLL/DLM registers of UART */
+ sc->sc_ports[portno].sc_lcr &= ~MCS7840_UART_LCR_DIVISORS;
+ err = umcs7840_set_UART_reg_sync(sc, portno, MCS7840_UART_REG_LCR, sc->sc_ports[portno].sc_lcr);
+ if (err)
+ return (err);
+ return (0);
+}
+
+/* Maximum speeds for standard frequencies, when PLL is not used */
+static const uint32_t umcs7840_baudrate_divisors[] = {0, 115200, 230400, 403200, 460800, 806400, 921600, 1572864, 3145728,};
+static const uint8_t umcs7840_baudrate_divisors_len = nitems(umcs7840_baudrate_divisors);
+
+static usb_error_t
+umcs7840_calc_baudrate(uint32_t rate, uint16_t *divisor, uint8_t *clk)
+{
+ uint8_t i = 0;
+
+ if (rate > umcs7840_baudrate_divisors[umcs7840_baudrate_divisors_len - 1])
+ return (-1);
+
+ for (i = 0; i < umcs7840_baudrate_divisors_len - 1 &&
+ !(rate > umcs7840_baudrate_divisors[i] && rate <= umcs7840_baudrate_divisors[i + 1]); ++i);
+ if (rate == 0)
+ *divisor = 1; /* XXX */
+ else
+ *divisor = umcs7840_baudrate_divisors[i + 1] / rate;
+ /* 0x00 .. 0x70 */
+ *clk = i << MCS7840_DEV_SPx_CLOCK_SHIFT;
+ return (0);
+}
diff --git a/sys/dev/usb/serial/umcs.h b/sys/dev/usb/serial/umcs.h
new file mode 100644
index 000000000000..0d34c54a3707
--- /dev/null
+++ b/sys/dev/usb/serial/umcs.h
@@ -0,0 +1,645 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Lev Serebryakov <lev@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.
+ */
+#ifndef _UMCS7840_H_
+#define _UMCS7840_H_
+
+#define UMCS7840_MAX_PORTS 4
+
+#define UMCS7840_READ_LENGTH 1 /* bytes */
+#define UMCS7840_CTRL_TIMEOUT 500 /* ms */
+
+/* Read/Wrtire registers vendor commands */
+#define MCS7840_RDREQ 0x0d
+#define MCS7840_WRREQ 0x0e
+
+/* Read/Wrtie EEPROM values */
+#define MCS7840_EEPROM_RW_WVALUE 0x0900
+
+/*
+ * All these registers are documented only in full datasheet,
+ * which can be requested from MosChip tech support.
+ */
+#define MCS7840_DEV_REG_SP1 0x00 /* Options for UART 1, R/W */
+#define MCS7840_DEV_REG_CONTROL1 0x01 /* Control bits for UART 1,
+ * R/W */
+#define MCS7840_DEV_REG_PINPONGHIGH 0x02 /* High bits of ping-pong
+ * register, R/W */
+#define MCS7840_DEV_REG_PINPONGLOW 0x03 /* Low bits of ping-pong
+ * register, R/W */
+/* DCRx_1 Registers goes here (see below, they are documented) */
+#define MCS7840_DEV_REG_GPIO 0x07 /* GPIO_0 and GPIO_1 bits,
+ * undocumented, see notes
+ * below R/W */
+#define MCS7840_DEV_REG_SP2 0x08 /* Options for UART 2, R/W */
+#define MCS7840_DEV_REG_CONTROL2 0x09 /* Control bits for UART 2,
+ * R/W */
+#define MCS7840_DEV_REG_SP3 0x0a /* Options for UART 3, R/W */
+#define MCS7840_DEV_REG_CONTROL3 0x0b /* Control bits for UART 3,
+ * R/W */
+#define MCS7840_DEV_REG_SP4 0x0c /* Options for UART 4, R/W */
+#define MCS7840_DEV_REG_CONTROL4 0x0d /* Control bits for UART 4,
+ * R/W */
+#define MCS7840_DEV_REG_PLL_DIV_M 0x0e /* Pre-diviedr for PLL, R/W */
+#define MCS7840_DEV_REG_UNKNOWN1 0x0f /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_PLL_DIV_N 0x10 /* Loop divider for PLL, R/W */
+#define MCS7840_DEV_REG_CLOCK_MUX 0x12 /* PLL input clock & Interrupt
+ * endpoint control, R/W */
+#define MCS7840_DEV_REG_UNKNOWN2 0x11 /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_CLOCK_SELECT12 0x13 /* Clock source for ports 1 &
+ * 2, R/W */
+#define MCS7840_DEV_REG_CLOCK_SELECT34 0x14 /* Clock source for ports 3 &
+ * 4, R/W */
+#define MCS7840_DEV_REG_UNKNOWN3 0x15 /* NOT MENTIONED AND NOT USED */
+/* DCRx_2-DCRx_4 Registers goes here (see below, they are documented) */
+#define MCS7840_DEV_REG_UNKNOWN4 0x1f /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_UNKNOWN5 0x20 /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_UNKNOWN6 0x21 /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_UNKNOWN7 0x22 /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_UNKNOWN8 0x23 /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_UNKNOWN9 0x24 /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_UNKNOWNA 0x25 /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_UNKNOWNB 0x26 /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_UNKNOWNC 0x27 /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_UNKNOWND 0x28 /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_UNKNOWNE 0x29 /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_UNKNOWNF 0x2a /* NOT MENTIONED AND NOT USED */
+#define MCS7840_DEV_REG_MODE 0x2b /* Hardware configuration,
+ * R/Only */
+#define MCS7840_DEV_REG_SP1_ICG 0x2c /* Inter character gap
+ * configuration for Port 1,
+ * R/W */
+#define MCS7840_DEV_REG_SP2_ICG 0x2d /* Inter character gap
+ * configuration for Port 2,
+ * R/W */
+#define MCS7840_DEV_REG_SP3_ICG 0x2e /* Inter character gap
+ * configuration for Port 3,
+ * R/W */
+#define MCS7840_DEV_REG_SP4_ICG 0x2f /* Inter character gap
+ * configuration for Port 4,
+ * R/W */
+#define MCS7840_DEV_REG_RX_SAMPLING12 0x30 /* RX sampling for ports 1 &
+ * 2, R/W */
+#define MCS7840_DEV_REG_RX_SAMPLING34 0x31 /* RX sampling for ports 3 &
+ * 4, R/W */
+#define MCS7840_DEV_REG_BI_FIFO_STAT1 0x32 /* Bulk-In FIFO Stat for Port
+ * 1, contains number of
+ * available bytes, R/Only */
+#define MCS7840_DEV_REG_BO_FIFO_STAT1 0x33 /* Bulk-out FIFO Stat for Port
+ * 1, contains number of
+ * available bytes, R/Only */
+#define MCS7840_DEV_REG_BI_FIFO_STAT2 0x34 /* Bulk-In FIFO Stat for Port
+ * 2, contains number of
+ * available bytes, R/Only */
+#define MCS7840_DEV_REG_BO_FIFO_STAT2 0x35 /* Bulk-out FIFO Stat for Port
+ * 2, contains number of
+ * available bytes, R/Only */
+#define MCS7840_DEV_REG_BI_FIFO_STAT3 0x36 /* Bulk-In FIFO Stat for Port
+ * 3, contains number of
+ * available bytes, R/Only */
+#define MCS7840_DEV_REG_BO_FIFO_STAT3 0x37 /* Bulk-out FIFO Stat for Port
+ * 3, contains number of
+ * available bytes, R/Only */
+#define MCS7840_DEV_REG_BI_FIFO_STAT4 0x38 /* Bulk-In FIFO Stat for Port
+ * 4, contains number of
+ * available bytes, R/Only */
+#define MCS7840_DEV_REG_BO_FIFO_STAT4 0x39 /* Bulk-out FIFO Stat for Port
+ * 4, contains number of
+ * available bytes, R/Only */
+#define MCS7840_DEV_REG_ZERO_PERIOD1 0x3a /* Period between zero out
+ * frames for Port 1, R/W */
+#define MCS7840_DEV_REG_ZERO_PERIOD2 0x3b /* Period between zero out
+ * frames for Port 1, R/W */
+#define MCS7840_DEV_REG_ZERO_PERIOD3 0x3c /* Period between zero out
+ * frames for Port 1, R/W */
+#define MCS7840_DEV_REG_ZERO_PERIOD4 0x3d /* Period between zero out
+ * frames for Port 1, R/W */
+#define MCS7840_DEV_REG_ZERO_ENABLE 0x3e /* Enable/disable of zero out
+ * frames, R/W */
+#define MCS7840_DEV_REG_THR_VAL_LOW1 0x3f /* Low 8 bits of threshold
+ * value for Bulk-Out for Port
+ * 1, R/W */
+#define MCS7840_DEV_REG_THR_VAL_HIGH1 0x40 /* High 1 bit of threshold
+ * value for Bulk-Out and
+ * enable flag for Port 1, R/W */
+#define MCS7840_DEV_REG_THR_VAL_LOW2 0x41 /* Low 8 bits of threshold
+ * value for Bulk-Out for Port
+ * 2, R/W */
+#define MCS7840_DEV_REG_THR_VAL_HIGH2 0x42 /* High 1 bit of threshold
+ * value for Bulk-Out and
+ * enable flag for Port 2, R/W */
+#define MCS7840_DEV_REG_THR_VAL_LOW3 0x43 /* Low 8 bits of threshold
+ * value for Bulk-Out for Port
+ * 3, R/W */
+#define MCS7840_DEV_REG_THR_VAL_HIGH3 0x44 /* High 1 bit of threshold
+ * value for Bulk-Out and
+ * enable flag for Port 3, R/W */
+#define MCS7840_DEV_REG_THR_VAL_LOW4 0x45 /* Low 8 bits of threshold
+ * value for Bulk-Out for Port
+ * 4, R/W */
+#define MCS7840_DEV_REG_THR_VAL_HIGH4 0x46 /* High 1 bit of threshold
+ * value for Bulk-Out and
+ * enable flag for Port 4, R/W */
+
+/* Bits for SPx registers */
+#define MCS7840_DEV_SPx_LOOP_PIPES 0x01 /* Loop Bulk-Out FIFO to the
+ * Bulk-In FIFO, default = 0 */
+#define MCS7840_DEV_SPx_SKIP_ERR_DATA 0x02 /* Drop data bytes from UART,
+ * which were received with
+ * errors, default = 0 */
+#define MCS7840_DEV_SPx_RESET_OUT_FIFO 0x04 /* Reset Bulk-Out FIFO */
+#define MCS7840_DEV_SPx_RESET_IN_FIFO 0x08 /* Reset Bulk-In FIFO */
+#define MCS7840_DEV_SPx_CLOCK_MASK 0x70 /* Mask to extract Baud CLK
+ * source */
+#define MCS7840_DEV_SPx_CLOCK_X1 0x00 /* CLK = 1.8432Mhz, max speed
+ * = 115200 bps, default */
+#define MCS7840_DEV_SPx_CLOCK_X2 0x10 /* CLK = 3.6864Mhz, max speed
+ * = 230400 bps */
+#define MCS7840_DEV_SPx_CLOCK_X35 0x20 /* CLK = 6.4512Mhz, max speed
+ * = 403200 bps */
+#define MCS7840_DEV_SPx_CLOCK_X4 0x30 /* CLK = 7.3728Mhz, max speed
+ * = 460800 bps */
+#define MCS7840_DEV_SPx_CLOCK_X7 0x40 /* CLK = 12.9024Mhz, max speed
+ * = 806400 bps */
+#define MCS7840_DEV_SPx_CLOCK_X8 0x50 /* CLK = 14.7456Mhz, max speed
+ * = 921600 bps */
+#define MCS7840_DEV_SPx_CLOCK_24MHZ 0x60 /* CLK = 24.0000Mhz, max speed
+ * = 1.5 Mbps */
+#define MCS7840_DEV_SPx_CLOCK_48MHZ 0x70 /* CLK = 48.0000Mhz, max speed
+ * = 3.0 Mbps */
+#define MCS7840_DEV_SPx_CLOCK_SHIFT 4 /* Value 0..7 can be shifted
+ * to get clock value */
+#define MCS7840_DEV_SPx_UART_RESET 0x80 /* Reset UART */
+
+/* Bits for CONTROLx registers */
+#define MCS7840_DEV_CONTROLx_HWFC 0x01 /* Enable hardware flow
+ * control (when power
+ * down? It is unclear
+ * in documents),
+ * default = 0 */
+#define MCS7840_DEV_CONTROLx_UNUNSED1 0x02 /* Reserved */
+#define MCS7840_DEV_CONTROLx_CTS_ENABLE 0x04 /* CTS changes are
+ * translated to MSR,
+ * default = 0 */
+#define MCS7840_DEV_CONTROLx_UNUSED2 0x08 /* Reserved for ports
+ * 2,3,4 */
+#define MCS7840_DEV_CONTROL1_DRIVER_DONE 0x08 /* USB enumerating is
+ * finished, USB
+ * enumeration memory
+ * can be used as FIFOs */
+#define MCS7840_DEV_CONTROLx_RX_NEGATE 0x10 /* Negate RX input,
+ * works for IrDA mode
+ * only, default = 0 */
+#define MCS7840_DEV_CONTROLx_RX_DISABLE 0x20 /* Disable RX logic,
+ * works only for
+ * RS-232/RS-485 mode,
+ * default = 0 */
+#define MCS7840_DEV_CONTROLx_FSM_CONTROL 0x40 /* Disable RX FSM when
+ * TX is in progress,
+ * works for IrDA mode
+ * only, default = 0 */
+#define MCS7840_DEV_CONTROLx_UNUSED3 0x80 /* Reserved */
+
+/*
+ * Bits for PINPONGx registers
+ * These registers control how often two input buffers
+ * for Bulk-In FIFOs are swapped. One of buffers is used
+ * for USB trnasfer, other for receiving data from UART.
+ * Exact meaning of 15 bit value in these registers is unknown
+ */
+#define MCS7840_DEV_PINPONGHIGH_MULT 128 /* Only 7 bits in PINPONGLOW
+ * register */
+#define MCS7840_DEV_PINPONGLOW_BITS 7 /* Only 7 bits in PINPONGLOW
+ * register */
+
+/*
+ * THIS ONE IS UNDOCUMENTED IN FULL DATASHEET, but e-mail from tech support
+ * confirms, that it is register for GPIO_0 and GPIO_1 data input/output.
+ * Chips has 2 GPIO, but first one (lower bit) MUST be used by device
+ * authors as "number of port" indicator, grounded (0) for two-port
+ * devices and pulled-up to 1 for 4-port devices.
+ */
+#define MCS7840_DEV_GPIO_4PORTS 0x01 /* Device has 4 ports
+ * configured */
+#define MCS7840_DEV_GPIO_GPIO_0 0x01 /* The same as above */
+#define MCS7840_DEV_GPIO_GPIO_1 0x02 /* GPIO_1 data */
+
+/*
+ * Constants for PLL dividers
+ * Ouptut frequency of PLL is:
+ * Fout = (N/M) * Fin.
+ * Default PLL input frequency Fin is 12Mhz (on-chip).
+ */
+#define MCS7840_DEV_PLL_DIV_M_BITS 6 /* Number of useful bits for M
+ * divider */
+#define MCS7840_DEV_PLL_DIV_M_MASK 0x3f /* Mask for M divider */
+#define MCS7840_DEV_PLL_DIV_M_MIN 1 /* Minimum value for M, 0 is
+ * forbidden */
+#define MCS7840_DEV_PLL_DIV_M_DEF 1 /* Default value for M */
+#define MCS7840_DEV_PLL_DIV_M_MAX 63 /* Maximum value for M */
+#define MCS7840_DEV_PLL_DIV_N_BITS 6 /* Number of useful bits for N
+ * divider */
+#define MCS7840_DEV_PLL_DIV_N_MASK 0x3f /* Mask for N divider */
+#define MCS7840_DEV_PLL_DIV_N_MIN 1 /* Minimum value for N, 0 is
+ * forbidden */
+#define MCS7840_DEV_PLL_DIV_N_DEF 8 /* Default value for N */
+#define MCS7840_DEV_PLL_DIV_N_MAX 63 /* Maximum value for N */
+
+/* Bits for CLOCK_MUX register */
+#define MCS7840_DEV_CLOCK_MUX_INPUTMASK 0x03 /* Mask to extract PLL clock
+ * input */
+#define MCS7840_DEV_CLOCK_MUX_IN12MHZ 0x00 /* 12Mhz PLL input, default */
+#define MCS7840_DEV_CLOCK_MUX_INEXTRN 0x01 /* External (device-depended)
+ * PLL input */
+#define MCS7840_DEV_CLOCK_MUX_INRSV1 0x02 /* Reserved */
+#define MCS7840_DEV_CLOCK_MUX_INRSV2 0x03 /* Reserved */
+#define MCS7840_DEV_CLOCK_MUX_PLLHIGH 0x04 /* 0 = PLL Output is
+ * 20MHz-100MHz (default), 1 =
+ * 100MHz-300MHz range */
+#define MCS7840_DEV_CLOCK_MUX_INTRFIFOS 0x08 /* Enable additional 8 bytes
+ * fro Interrupt USB pipe with
+ * USB FIFOs statuses, default
+ * = 0 */
+#define MCS7840_DEV_CLOCK_MUX_RESERVED1 0x10 /* Unused */
+#define MCS7840_DEV_CLOCK_MUX_RESERVED2 0x20 /* Unused */
+#define MCS7840_DEV_CLOCK_MUX_RESERVED3 0x40 /* Unused */
+#define MCS7840_DEV_CLOCK_MUX_RESERVED4 0x80 /* Unused */
+
+/* Bits for CLOCK_SELECTxx registers */
+#define MCS7840_DEV_CLOCK_SELECT1_MASK 0x07 /* Bits for port 1 in
+ * CLOCK_SELECT12 */
+#define MCS7840_DEV_CLOCK_SELECT1_SHIFT 0 /* Shift for port 1in
+ * CLOCK_SELECT12 */
+#define MCS7840_DEV_CLOCK_SELECT2_MASK 0x38 /* Bits for port 2 in
+ * CLOCK_SELECT12 */
+#define MCS7840_DEV_CLOCK_SELECT2_SHIFT 3 /* Shift for port 2 in
+ * CLOCK_SELECT12 */
+#define MCS7840_DEV_CLOCK_SELECT3_MASK 0x07 /* Bits for port 3 in
+ * CLOCK_SELECT23 */
+#define MCS7840_DEV_CLOCK_SELECT3_SHIFT 0 /* Shift for port 3 in
+ * CLOCK_SELECT23 */
+#define MCS7840_DEV_CLOCK_SELECT4_MASK 0x38 /* Bits for port 4 in
+ * CLOCK_SELECT23 */
+#define MCS7840_DEV_CLOCK_SELECT4_SHIFT 3 /* Shift for port 4 in
+ * CLOCK_SELECT23 */
+#define MCS7840_DEV_CLOCK_SELECT_STD 0x00 /* STANDARD baudrate derived
+ * from 96Mhz, default for all
+ * ports */
+#define MCS7840_DEV_CLOCK_SELECT_30MHZ 0x01 /* 30Mhz */
+#define MCS7840_DEV_CLOCK_SELECT_96MHZ 0x02 /* 96Mhz direct */
+#define MCS7840_DEV_CLOCK_SELECT_120MHZ 0x03 /* 120Mhz */
+#define MCS7840_DEV_CLOCK_SELECT_PLL 0x04 /* PLL output (see for M and N
+ * dividers) */
+#define MCS7840_DEV_CLOCK_SELECT_EXT 0x05 /* External clock input
+ * (device-dependend) */
+#define MCS7840_DEV_CLOCK_SELECT_RES1 0x06 /* Unused */
+#define MCS7840_DEV_CLOCK_SELECT_RES2 0x07 /* Unused */
+
+/* Bits for MODE register */
+#define MCS7840_DEV_MODE_RESERVED1 0x01 /* Unused */
+#define MCS7840_DEV_MODE_RESET 0x02 /* 0: RESET = Active High
+ * (default), 1: Reserved (?) */
+#define MCS7840_DEV_MODE_SER_PRSNT 0x04 /* 0: Reserved, 1: Do not use
+ * hardocded values (default)
+ * (?) */
+#define MCS7840_DEV_MODE_PLLBYPASS 0x08 /* 1: PLL output is bypassed,
+ * default = 0 */
+#define MCS7840_DEV_MODE_PORBYPASS 0x10 /* 1: Power-On Reset is
+ * bypassed, default = 0 */
+#define MCS7840_DEV_MODE_SELECT24S 0x20 /* 0: 4 Serial Ports / IrDA
+ * active, 1: 2 Serial Ports /
+ * IrDA active */
+#define MCS7840_DEV_MODE_EEPROMWR 0x40 /* EEPROM write is enabled,
+ * default */
+#define MCS7840_DEV_MODE_IRDA 0x80 /* IrDA mode is activated
+ * (could be turned on),
+ * default */
+
+/* Bits for SPx ICG */
+#define MCS7840_DEV_SPx_ICG_DEF 0x24 /* All 8 bits is used as
+ * number of BAUD clocks of
+ * pause */
+
+/*
+ * Bits for RX_SAMPLINGxx registers
+ * These registers control when bit value will be sampled within
+ * the baud period.
+ * 0 is very beginning of period, 15 is very end, 7 is the middle.
+ */
+#define MCS7840_DEV_RX_SAMPLING1_MASK 0x0f /* Bits for port 1 in
+ * RX_SAMPLING12 */
+#define MCS7840_DEV_RX_SAMPLING1_SHIFT 0 /* Shift for port 1in
+ * RX_SAMPLING12 */
+#define MCS7840_DEV_RX_SAMPLING2_MASK 0xf0 /* Bits for port 2 in
+ * RX_SAMPLING12 */
+#define MCS7840_DEV_RX_SAMPLING2_SHIFT 4 /* Shift for port 2 in
+ * RX_SAMPLING12 */
+#define MCS7840_DEV_RX_SAMPLING3_MASK 0x0f /* Bits for port 3 in
+ * RX_SAMPLING23 */
+#define MCS7840_DEV_RX_SAMPLING3_SHIFT 0 /* Shift for port 3 in
+ * RX_SAMPLING23 */
+#define MCS7840_DEV_RX_SAMPLING4_MASK 0xf0 /* Bits for port 4 in
+ * RX_SAMPLING23 */
+#define MCS7840_DEV_RX_SAMPLING4_SHIFT 4 /* Shift for port 4 in
+ * RX_SAMPLING23 */
+#define MCS7840_DEV_RX_SAMPLINGx_MIN 0 /* Max for any RX Sampling */
+#define MCS7840_DEV_RX_SAMPLINGx_DEF 7 /* Default for any RX
+ * Sampling, center of period */
+#define MCS7840_DEV_RX_SAMPLINGx_MAX 15 /* Min for any RX Sampling */
+
+/* Bits for ZERO_PERIODx */
+#define MCS7840_DEV_ZERO_PERIODx_DEF 20 /* Number of Bulk-in requests
+ * befor sending zero-sized
+ * reply */
+
+/* Bits for ZERO_ENABLE */
+#define MCS7840_DEV_ZERO_ENABLE_PORT1 0x01 /* Enable of sending
+ * zero-sized replies for port
+ * 1, default */
+#define MCS7840_DEV_ZERO_ENABLE_PORT2 0x02 /* Enable of sending
+ * zero-sized replies for port
+ * 2, default */
+#define MCS7840_DEV_ZERO_ENABLE_PORT3 0x04 /* Enable of sending
+ * zero-sized replies for port
+ * 3, default */
+#define MCS7840_DEV_ZERO_ENABLE_PORT4 0x08 /* Enable of sending
+ * zero-sized replies for port
+ * 4, default */
+
+/* Bits for THR_VAL_HIGHx */
+#define MCS7840_DEV_THR_VAL_HIGH_MASK 0x01 /* Only one bit is used */
+#define MCS7840_DEV_THR_VAL_HIGH_MUL 256 /* This one bit is means "256" */
+#define MCS7840_DEV_THR_VAL_HIGH_SHIFT 8 /* This one bit is means "256" */
+#define MCS7840_DEV_THR_VAL_HIGH_ENABLE 0x80 /* Enable threshold */
+
+/* These are documented in "public" datasheet */
+#define MCS7840_DEV_REG_DCR0_1 0x04 /* Device contol register 0 for Port
+ * 1, R/W */
+#define MCS7840_DEV_REG_DCR1_1 0x05 /* Device contol register 1 for Port
+ * 1, R/W */
+#define MCS7840_DEV_REG_DCR2_1 0x06 /* Device contol register 2 for Port
+ * 1, R/W */
+#define MCS7840_DEV_REG_DCR0_2 0x16 /* Device contol register 0 for Port
+ * 2, R/W */
+#define MCS7840_DEV_REG_DCR1_2 0x17 /* Device contol register 1 for Port
+ * 2, R/W */
+#define MCS7840_DEV_REG_DCR2_2 0x18 /* Device contol register 2 for Port
+ * 2, R/W */
+#define MCS7840_DEV_REG_DCR0_3 0x19 /* Device contol register 0 for Port
+ * 3, R/W */
+#define MCS7840_DEV_REG_DCR1_3 0x1a /* Device contol register 1 for Port
+ * 3, R/W */
+#define MCS7840_DEV_REG_DCR2_3 0x1b /* Device contol register 2 for Port
+ * 3, R/W */
+#define MCS7840_DEV_REG_DCR0_4 0x1c /* Device contol register 0 for Port
+ * 4, R/W */
+#define MCS7840_DEV_REG_DCR1_4 0x1d /* Device contol register 1 for Port
+ * 4, R/W */
+#define MCS7840_DEV_REG_DCR2_4 0x1e /* Device contol register 2 for Port
+ * 4, R/W */
+
+/* Bits of DCR0 registers, documented in datasheet */
+#define MCS7840_DEV_DCR0_PWRSAVE 0x01 /* Shutdown transiver
+ * when USB Suspend is
+ * engaged, default = 1 */
+#define MCS7840_DEV_DCR0_RESERVED1 0x02 /* Unused */
+#define MCS7840_DEV_DCR0_GPIO_MODE_MASK 0x0c /* GPIO Mode bits, WORKS
+ * ONLY FOR PORT 1 */
+#define MCS7840_DEV_DCR0_GPIO_MODE_IN 0x00 /* GPIO Mode - Input
+ * (0b00), WORKS ONLY
+ * FOR PORT 1 */
+#define MCS7840_DEV_DCR0_GPIO_MODE_OUT 0x08 /* GPIO Mode - Input
+ * (0b10), WORKS ONLY
+ * FOR PORT 1 */
+#define MCS7840_DEV_DCR0_RTS_ACTIVE_HIGH 0x10 /* RTS Active is HIGH,
+ * default = 0 (low) */
+#define MCS7840_DEV_DCR0_RTS_AUTO 0x20 /* RTS is controlled by
+ * state of TX buffer,
+ * default = 0
+ * (controlled by MCR) */
+#define MCS7840_DEV_DCR0_IRDA 0x40 /* IrDA mode */
+#define MCS7840_DEV_DCR0_RESERVED2 0x80 /* Unused */
+
+/* Bits of DCR1 registers, documented in datasheet */
+#define MCS7840_DEV_DCR1_GPIO_CURRENT_MASK 0x03 /* Mask to extract GPIO
+ * current value, WORKS
+ * ONLY FOR PORT 1 */
+#define MCS7840_DEV_DCR1_GPIO_CURRENT_6MA 0x00 /* GPIO output current
+ * 6mA, WORKS ONLY FOR
+ * PORT 1 */
+#define MCS7840_DEV_DCR1_GPIO_CURRENT_8MA 0x01 /* GPIO output current
+ * 8mA, defauilt, WORKS
+ * ONLY FOR PORT 1 */
+#define MCS7840_DEV_DCR1_GPIO_CURRENT_10MA 0x02 /* GPIO output current
+ * 10mA, WORKS ONLY FOR
+ * PORT 1 */
+#define MCS7840_DEV_DCR1_GPIO_CURRENT_12MA 0x03 /* GPIO output current
+ * 12mA, WORKS ONLY FOR
+ * PORT 1 */
+#define MCS7840_DEV_DCR1_UART_CURRENT_MASK 0x0c /* Mask to extract UART
+ * signals current value */
+#define MCS7840_DEV_DCR1_UART_CURRENT_6MA 0x00 /* UART output current
+ * 6mA */
+#define MCS7840_DEV_DCR1_UART_CURRENT_8MA 0x04 /* UART output current
+ * 8mA, defauilt */
+#define MCS7840_DEV_DCR1_UART_CURRENT_10MA 0x08 /* UART output current
+ * 10mA */
+#define MCS7840_DEV_DCR1_UART_CURRENT_12MA 0x0c /* UART output current
+ * 12mA */
+#define MCS7840_DEV_DCR1_WAKEUP_DISABLE 0x10 /* Disable Remote USB
+ * Wakeup */
+#define MCS7840_DEV_DCR1_PLLPWRDOWN_DISABLE 0x20 /* Disable PLL power
+ * down when not needed,
+ * WORKS ONLY FOR PORT 1 */
+#define MCS7840_DEV_DCR1_LONG_INTERRUPT 0x40 /* Enable 13 bytes of
+ * interrupt data, with
+ * FIFO statistics,
+ * WORKS ONLY FOR PORT 1 */
+#define MCS7840_DEV_DCR1_RESERVED1 0x80 /* Unused */
+
+/*
+ * Bits of DCR2 registers, documented in datasheet
+ * Wakeup will work only if DCR0_IRDA = 0 (RS-xxx mode) and
+ * DCR1_WAKEUP_DISABLE = 0 (wakeup enabled).
+ */
+#define MCS7840_DEV_DCR2_WAKEUP_CTS 0x01 /* Wakeup on CTS change,
+ * default = 0 */
+#define MCS7840_DEV_DCR2_WAKEUP_DCD 0x02 /* Wakeup on DCD change,
+ * default = 0 */
+#define MCS7840_DEV_DCR2_WAKEUP_RI 0x04 /* Wakeup on RI change,
+ * default = 1 */
+#define MCS7840_DEV_DCR2_WAKEUP_DSR 0x08 /* Wakeup on DSR change,
+ * default = 0 */
+#define MCS7840_DEV_DCR2_WAKEUP_RXD 0x10 /* Wakeup on RX Data change,
+ * default = 0 */
+#define MCS7840_DEV_DCR2_WAKEUP_RESUME 0x20 /* Wakeup issues RESUME
+ * signal, DISCONNECT
+ * otherwise, default = 1 */
+#define MCS7840_DEV_DCR2_RESERVED1 0x40 /* Unused */
+#define MCS7840_DEV_DCR2_SHDN_POLARITY 0x80 /* 0: Pin 12 Active Low, 1:
+ * Pin 12 Active High, default
+ * = 0 */
+
+/* Interrupt endpoint bytes & bits */
+#define MCS7840_IEP_FIFO_STATUS_INDEX 5
+/*
+ * Thesse can be calculated as "1 << portnumber" for Bulk-out and
+ * "1 << (portnumber+1)" for Bulk-in
+ */
+#define MCS7840_IEP_BO_PORT1_HASDATA 0x01
+#define MCS7840_IEP_BI_PORT1_HASDATA 0x02
+#define MCS7840_IEP_BO_PORT2_HASDATA 0x04
+#define MCS7840_IEP_BI_PORT2_HASDATA 0x08
+#define MCS7840_IEP_BO_PORT3_HASDATA 0x10
+#define MCS7840_IEP_BI_PORT3_HASDATA 0x20
+#define MCS7840_IEP_BO_PORT4_HASDATA 0x40
+#define MCS7840_IEP_BI_PORT4_HASDATA 0x80
+
+/* Documented UART registers (fully compatible with 16550 UART) */
+#define MCS7840_UART_REG_THR 0x00 /* Transmitter Holding
+ * Register W/Only */
+#define MCS7840_UART_REG_RHR 0x00 /* Receiver Holding Register
+ * R/Only */
+#define MCS7840_UART_REG_IER 0x01 /* Interrupt enable register -
+ * R/W */
+#define MCS7840_UART_REG_FCR 0x02 /* FIFO Control register -
+ * W/Only */
+#define MCS7840_UART_REG_ISR 0x02 /* Interrupt Status Registter
+ * R/Only */
+#define MCS7840_UART_REG_LCR 0x03 /* Line control register R/W */
+#define MCS7840_UART_REG_MCR 0x04 /* Modem control register R/W */
+#define MCS7840_UART_REG_LSR 0x05 /* Line status register R/Only */
+#define MCS7840_UART_REG_MSR 0x06 /* Modem status register
+ * R/Only */
+#define MCS7840_UART_REG_SCRATCHPAD 0x07 /* Scratch pad register */
+
+#define MCS7840_UART_REG_DLL 0x00 /* Low bits of BAUD divider */
+#define MCS7840_UART_REG_DLM 0x01 /* High bits of BAUD divider */
+
+/* IER bits */
+#define MCS7840_UART_IER_RXREADY 0x01 /* RX Ready interrumpt mask */
+#define MCS7840_UART_IER_TXREADY 0x02 /* TX Ready interrumpt mask */
+#define MCS7840_UART_IER_RXSTAT 0x04 /* RX Status interrumpt mask */
+#define MCS7840_UART_IER_MODEM 0x08 /* Modem status change
+ * interrumpt mask */
+#define MCS7840_UART_IER_SLEEP 0x10 /* SLEEP enable */
+
+/* FCR bits */
+#define MCS7840_UART_FCR_ENABLE 0x01 /* Enable FIFO */
+#define MCS7840_UART_FCR_FLUSHRHR 0x02 /* Flush RHR and FIFO */
+#define MCS7840_UART_FCR_FLUSHTHR 0x04 /* Flush THR and FIFO */
+#define MCS7840_UART_FCR_RTLMASK 0xa0 /* Mask to select RHR
+ * Interrupt Trigger level */
+#define MCS7840_UART_FCR_RTL_1_1 0x00 /* L1 = 1, L2 = 1 */
+#define MCS7840_UART_FCR_RTL_1_4 0x40 /* L1 = 1, L2 = 4 */
+#define MCS7840_UART_FCR_RTL_1_8 0x80 /* L1 = 1, L2 = 8 */
+#define MCS7840_UART_FCR_RTL_1_14 0xa0 /* L1 = 1, L2 = 14 */
+
+/* ISR bits */
+#define MCS7840_UART_ISR_NOPENDING 0x01 /* No interrupt pending */
+#define MCS7840_UART_ISR_INTMASK 0x3f /* Mask to select interrupt
+ * source */
+#define MCS7840_UART_ISR_RXERR 0x06 /* Recevir error */
+#define MCS7840_UART_ISR_RXHASDATA 0x04 /* Recevier has data */
+#define MCS7840_UART_ISR_RXTIMEOUT 0x0c /* Recevier timeout */
+#define MCS7840_UART_ISR_TXEMPTY 0x02 /* Transmitter empty */
+#define MCS7840_UART_ISR_MSCHANGE 0x00 /* Modem status change */
+
+/* LCR bits */
+#define MCS7840_UART_LCR_DATALENMASK 0x03 /* Mask for data length */
+#define MCS7840_UART_LCR_DATALEN5 0x00 /* 5 data bits */
+#define MCS7840_UART_LCR_DATALEN6 0x01 /* 6 data bits */
+#define MCS7840_UART_LCR_DATALEN7 0x02 /* 7 data bits */
+#define MCS7840_UART_LCR_DATALEN8 0x03 /* 8 data bits */
+
+#define MCS7840_UART_LCR_STOPBMASK 0x04 /* Mask for stop bits */
+#define MCS7840_UART_LCR_STOPB1 0x00 /* 1 stop bit in any case */
+#define MCS7840_UART_LCR_STOPB2 0x04 /* 1.5-2 stop bits depends on
+ * data length */
+
+#define MCS7840_UART_LCR_PARITYMASK 0x38 /* Mask for all parity data */
+#define MCS7840_UART_LCR_PARITYON 0x08 /* Parity ON/OFF - ON */
+#define MCS7840_UART_LCR_PARITYODD 0x00 /* Parity Odd */
+#define MCS7840_UART_LCR_PARITYEVEN 0x10 /* Parity Even */
+#define MCS7840_UART_LCR_PARITYODD 0x00 /* Parity Odd */
+#define MCS7840_UART_LCR_PARITYFORCE 0x20 /* Force parity odd/even */
+
+#define MCS7840_UART_LCR_BREAK 0x40 /* Send BREAK */
+#define MCS7840_UART_LCR_DIVISORS 0x80 /* Map DLL/DLM instead of
+ * xHR/IER */
+
+/* LSR bits */
+#define MCS7840_UART_LSR_RHRAVAIL 0x01 /* Data available for read */
+#define MCS7840_UART_LSR_RHROVERRUN 0x02 /* Data FIFO/register overflow */
+#define MCS7840_UART_LSR_PARITYERR 0x04 /* Parity error */
+#define MCS7840_UART_LSR_FRAMEERR 0x10 /* Framing error */
+#define MCS7840_UART_LSR_BREAKERR 0x20 /* BREAK signal received */
+#define MCS7840_UART_LSR_THREMPTY 0x40 /* THR register is empty,
+ * ready for transmit */
+#define MCS7840_UART_LSR_HASERR 0x80 /* Has error in receiver FIFO */
+
+/* MCR bits */
+#define MCS7840_UART_MCR_DTR 0x01 /* Force DTR to be active
+ * (low) */
+#define MCS7840_UART_MCR_RTS 0x02 /* Force RTS to be active
+ * (low) */
+#define MCS7840_UART_MCR_IE 0x04 /* Enable interrupts (from
+ * code, not documented) */
+#define MCS7840_UART_MCR_LOOPBACK 0x10 /* Enable local loopback test
+ * mode */
+#define MCS7840_UART_MCR_CTSRTS 0x20 /* Enable CTS/RTS flow control
+ * in 550 (FIFO) mode */
+#define MCS7840_UART_MCR_DTRDSR 0x40 /* Enable DTR/DSR flow control
+ * in 550 (FIFO) mode */
+#define MCS7840_UART_MCR_DCD 0x80 /* Enable DCD flow control in
+ * 550 (FIFO) mode */
+
+/* MSR bits */
+#define MCS7840_UART_MSR_DELTACTS 0x01 /* CTS was changed since last
+ * read */
+#define MCS7840_UART_MSR_DELTADSR 0x02 /* DSR was changed since last
+ * read */
+#define MCS7840_UART_MSR_DELTARI 0x04 /* RI was changed from low to
+ * high since last read */
+#define MCS7840_UART_MSR_DELTADCD 0x08 /* DCD was changed since last
+ * read */
+#define MCS7840_UART_MSR_NEGCTS 0x10 /* Negated CTS signal */
+#define MCS7840_UART_MSR_NEGDSR 0x20 /* Negated DSR signal */
+#define MCS7840_UART_MSR_NEGRI 0x40 /* Negated RI signal */
+#define MCS7840_UART_MSR_NEGDCD 0x80 /* Negated DCD signal */
+
+/* SCRATCHPAD bits */
+#define MCS7840_UART_SCRATCHPAD_RS232 0x00 /* RS-485 disabled */
+#define MCS7840_UART_SCRATCHPAD_RS485_DTRRX 0x80 /* RS-485 mode, DTR High
+ * = RX */
+#define MCS7840_UART_SCRATCHPAD_RS485_DTRTX 0xc0 /* RS-485 mode, DTR High
+ * = TX */
+
+#define MCS7840_CONFIG_INDEX 0
+#define MCS7840_IFACE_INDEX 0
+
+#endif
diff --git a/sys/dev/usb/serial/umct.c b/sys/dev/usb/serial/umct.c
new file mode 100644
index 000000000000..bf6c672907e0
--- /dev/null
+++ b/sys/dev/usb/serial/umct.c
@@ -0,0 +1,676 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ */
+
+/*
+ * 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.
+ */
+
+/*
+ * NOTE: all function names beginning like "umct_cfg_" can only
+ * be called from within the config thread function !
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR usb_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.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_MSR_CTS_CHG 0x01
+#define UMCT_MSR_DSR_CHG 0x02
+#define UMCT_MSR_RI_CHG 0x04
+#define UMCT_MSR_CD_CHG 0x08
+#define UMCT_MSR_CTS 0x10
+#define UMCT_MSR_RTS 0x20
+#define UMCT_MSR_RI 0x40
+#define UMCT_MSR_CD 0x80
+
+#define UMCT_INTR_INTERVAL 100
+#define UMCT_IFACE_INDEX 0
+#define UMCT_CONFIG_INDEX 0
+
+enum {
+ UMCT_BULK_DT_WR,
+ UMCT_BULK_DT_RD,
+ UMCT_INTR_DT_RD,
+ UMCT_N_TRANSFER,
+};
+
+struct umct_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+
+ struct usb_device *sc_udev;
+ struct usb_xfer *sc_xfer[UMCT_N_TRANSFER];
+ struct mtx sc_mtx;
+
+ uint32_t sc_unit;
+
+ uint16_t sc_obufsize;
+
+ uint8_t sc_lsr;
+ uint8_t sc_msr;
+ uint8_t sc_lcr;
+ uint8_t sc_mcr;
+ uint8_t sc_iface_no;
+ uint8_t sc_swap_cb;
+};
+
+/* prototypes */
+
+static device_probe_t umct_probe;
+static device_attach_t umct_attach;
+static device_detach_t umct_detach;
+static void umct_free_softc(struct umct_softc *);
+
+static usb_callback_t umct_intr_callback;
+static usb_callback_t umct_intr_callback_sub;
+static usb_callback_t umct_read_callback;
+static usb_callback_t umct_read_callback_sub;
+static usb_callback_t umct_write_callback;
+
+static void umct_cfg_do_request(struct umct_softc *sc, uint8_t request,
+ uint16_t len, uint32_t value);
+static void umct_free(struct ucom_softc *);
+static void umct_cfg_get_status(struct ucom_softc *, uint8_t *,
+ uint8_t *);
+static void umct_cfg_set_break(struct ucom_softc *, uint8_t);
+static void umct_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void umct_cfg_set_rts(struct ucom_softc *, uint8_t);
+static uint8_t umct_calc_baud(uint32_t);
+static int umct_pre_param(struct ucom_softc *, struct termios *);
+static void umct_cfg_param(struct ucom_softc *, struct termios *);
+static void umct_start_read(struct ucom_softc *);
+static void umct_stop_read(struct ucom_softc *);
+static void umct_start_write(struct ucom_softc *);
+static void umct_stop_write(struct ucom_softc *);
+static void umct_poll(struct ucom_softc *ucom);
+
+static const struct usb_config umct_config[UMCT_N_TRANSFER] = {
+ [UMCT_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = 0, /* use wMaxPacketSize */
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &umct_write_callback,
+ },
+
+ [UMCT_BULK_DT_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &umct_read_callback,
+ .ep_index = 0, /* first interrupt endpoint */
+ },
+
+ [UMCT_INTR_DT_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &umct_intr_callback,
+ .ep_index = 1, /* second interrupt endpoint */
+ },
+};
+
+static const struct ucom_callback umct_callback = {
+ .ucom_cfg_get_status = &umct_cfg_get_status,
+ .ucom_cfg_set_dtr = &umct_cfg_set_dtr,
+ .ucom_cfg_set_rts = &umct_cfg_set_rts,
+ .ucom_cfg_set_break = &umct_cfg_set_break,
+ .ucom_cfg_param = &umct_cfg_param,
+ .ucom_pre_param = &umct_pre_param,
+ .ucom_start_read = &umct_start_read,
+ .ucom_stop_read = &umct_stop_read,
+ .ucom_start_write = &umct_start_write,
+ .ucom_stop_write = &umct_stop_write,
+ .ucom_poll = &umct_poll,
+ .ucom_free = &umct_free,
+};
+
+static const STRUCT_USB_HOST_ID umct_devs[] = {
+ {USB_VPI(USB_VENDOR_MCT, USB_PRODUCT_MCT_USB232, 0)},
+ {USB_VPI(USB_VENDOR_MCT, USB_PRODUCT_MCT_SITECOM_USB232, 0)},
+ {USB_VPI(USB_VENDOR_MCT, USB_PRODUCT_MCT_DU_H3SP_USB232, 0)},
+ {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U109, 0)},
+ {USB_VPI(USB_VENDOR_BELKIN, USB_PRODUCT_BELKIN_F5U409, 0)},
+};
+
+static device_method_t umct_methods[] = {
+ DEVMETHOD(device_probe, umct_probe),
+ DEVMETHOD(device_attach, umct_attach),
+ DEVMETHOD(device_detach, umct_detach),
+ DEVMETHOD_END
+};
+
+static driver_t umct_driver = {
+ .name = "umct",
+ .methods = umct_methods,
+ .size = sizeof(struct umct_softc),
+};
+
+DRIVER_MODULE(umct, uhub, umct_driver, NULL, NULL);
+MODULE_DEPEND(umct, ucom, 1, 1, 1);
+MODULE_DEPEND(umct, usb, 1, 1, 1);
+MODULE_VERSION(umct, 1);
+USB_PNP_HOST_INFO(umct_devs);
+
+static int
+umct_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != UMCT_CONFIG_INDEX) {
+ return (ENXIO);
+ }
+ if (uaa->info.bIfaceIndex != UMCT_IFACE_INDEX) {
+ return (ENXIO);
+ }
+ return (usbd_lookup_id_by_uaa(umct_devs, sizeof(umct_devs), uaa));
+}
+
+static int
+umct_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct umct_softc *sc = device_get_softc(dev);
+ int32_t error;
+ uint16_t maxp;
+ uint8_t iface_index;
+
+ sc->sc_udev = uaa->device;
+ sc->sc_unit = device_get_unit(dev);
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "umct", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ sc->sc_iface_no = uaa->info.bIfaceNum;
+
+ iface_index = UMCT_IFACE_INDEX;
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, umct_config, UMCT_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error) {
+ device_printf(dev, "allocating USB "
+ "transfers failed\n");
+ goto detach;
+ }
+
+ /*
+ * 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.
+ */
+ maxp = usbd_xfer_max_framelen(sc->sc_xfer[UMCT_BULK_DT_RD]);
+ if (maxp == 0x2) {
+ /* guessed wrong - switch around endpoints */
+
+ struct usb_xfer *temp = sc->sc_xfer[UMCT_INTR_DT_RD];
+
+ sc->sc_xfer[UMCT_INTR_DT_RD] = sc->sc_xfer[UMCT_BULK_DT_RD];
+ sc->sc_xfer[UMCT_BULK_DT_RD] = temp;
+ sc->sc_swap_cb = 1;
+ }
+
+ sc->sc_obufsize = usbd_xfer_max_len(sc->sc_xfer[UMCT_BULK_DT_WR]);
+
+ if (uaa->info.idProduct == USB_PRODUCT_MCT_SITECOM_USB232) {
+ if (sc->sc_obufsize > 16) {
+ sc->sc_obufsize = 16;
+ }
+ }
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &umct_callback, &sc->sc_mtx);
+ if (error) {
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ return (0); /* success */
+
+detach:
+ umct_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+umct_detach(device_t dev)
+{
+ struct umct_softc *sc = device_get_softc(dev);
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, UMCT_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ umct_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(umct);
+
+static void
+umct_free_softc(struct umct_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+umct_free(struct ucom_softc *ucom)
+{
+ umct_free_softc(ucom->sc_parent);
+}
+
+static void
+umct_cfg_do_request(struct umct_softc *sc, uint8_t request,
+ uint16_t len, uint32_t value)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+ uint8_t temp[4];
+
+ if (len > 4)
+ len = 4;
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = request;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, len);
+ USETDW(temp, value);
+
+ err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, temp, 0, 1000);
+ if (err) {
+ DPRINTFN(0, "device request failed, err=%s "
+ "(ignored)\n", usbd_errstr(err));
+ }
+ return;
+}
+
+static void
+umct_intr_callback_sub(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umct_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint8_t buf[2];
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (actlen < 2) {
+ DPRINTF("too short message\n");
+ goto tr_setup;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, buf, sizeof(buf));
+
+ /*
+ * MSR bits need translation from ns16550 to SER_* values.
+ * LSR bits are ns16550 in hardware and ucom.
+ */
+ sc->sc_msr = 0;
+ if (buf[0] & UMCT_MSR_CTS)
+ sc->sc_msr |= SER_CTS;
+ if (buf[0] & UMCT_MSR_CD)
+ sc->sc_msr |= SER_DCD;
+ if (buf[0] & UMCT_MSR_RI)
+ sc->sc_msr |= SER_RI;
+ if (buf[0] & UMCT_MSR_RTS)
+ sc->sc_msr |= SER_DSR;
+ sc->sc_lsr = buf[1];
+
+ ucom_status_change(&sc->sc_ucom);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+umct_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct umct_softc *sc = ucom->sc_parent;
+
+ *lsr = sc->sc_lsr;
+ *msr = sc->sc_msr;
+}
+
+static void
+umct_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct umct_softc *sc = ucom->sc_parent;
+
+ if (onoff)
+ sc->sc_lcr |= 0x40;
+ else
+ sc->sc_lcr &= ~0x40;
+
+ umct_cfg_do_request(sc, UMCT_SET_LCR, UMCT_SET_LCR_SIZE, sc->sc_lcr);
+}
+
+static void
+umct_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct umct_softc *sc = ucom->sc_parent;
+
+ if (onoff)
+ sc->sc_mcr |= 0x01;
+ else
+ sc->sc_mcr &= ~0x01;
+
+ umct_cfg_do_request(sc, UMCT_SET_MCR, UMCT_SET_MCR_SIZE, sc->sc_mcr);
+}
+
+static void
+umct_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct umct_softc *sc = ucom->sc_parent;
+
+ if (onoff)
+ sc->sc_mcr |= 0x02;
+ else
+ sc->sc_mcr &= ~0x02;
+
+ umct_cfg_do_request(sc, UMCT_SET_MCR, UMCT_SET_MCR_SIZE, sc->sc_mcr);
+}
+
+static uint8_t
+umct_calc_baud(uint32_t 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_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+ return (0); /* we accept anything */
+}
+
+static void
+umct_cfg_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct umct_softc *sc = ucom->sc_parent;
+ uint32_t value;
+
+ value = umct_calc_baud(t->c_ospeed);
+ umct_cfg_do_request(sc, UMCT_SET_BAUD, UMCT_SET_BAUD_SIZE, value);
+
+ value = (sc->sc_lcr & 0x40);
+
+ switch (t->c_cflag & CSIZE) {
+ case CS5:
+ value |= 0x0;
+ break;
+ case CS6:
+ value |= 0x1;
+ break;
+ case CS7:
+ value |= 0x2;
+ break;
+ default:
+ case CS8:
+ value |= 0x3;
+ break;
+ }
+
+ value |= (t->c_cflag & CSTOPB) ? 0x4 : 0;
+ if (t->c_cflag & PARENB) {
+ value |= 0x8;
+ value |= (t->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_cfg_do_request(sc, UMCT_SET_LCR, UMCT_SET_LCR_SIZE, value);
+}
+
+static void
+umct_start_read(struct ucom_softc *ucom)
+{
+ struct umct_softc *sc = ucom->sc_parent;
+
+ /* start interrupt endpoint */
+ usbd_transfer_start(sc->sc_xfer[UMCT_INTR_DT_RD]);
+
+ /* start read endpoint */
+ usbd_transfer_start(sc->sc_xfer[UMCT_BULK_DT_RD]);
+}
+
+static void
+umct_stop_read(struct ucom_softc *ucom)
+{
+ struct umct_softc *sc = ucom->sc_parent;
+
+ /* stop interrupt endpoint */
+ usbd_transfer_stop(sc->sc_xfer[UMCT_INTR_DT_RD]);
+
+ /* stop read endpoint */
+ usbd_transfer_stop(sc->sc_xfer[UMCT_BULK_DT_RD]);
+}
+
+static void
+umct_start_write(struct ucom_softc *ucom)
+{
+ struct umct_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UMCT_BULK_DT_WR]);
+}
+
+static void
+umct_stop_write(struct ucom_softc *ucom)
+{
+ struct umct_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UMCT_BULK_DT_WR]);
+}
+
+static void
+umct_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umct_softc *sc = usbd_xfer_softc(xfer);
+
+ if (sc->sc_swap_cb)
+ umct_intr_callback_sub(xfer, error);
+ else
+ umct_read_callback_sub(xfer, error);
+}
+
+static void
+umct_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umct_softc *sc = usbd_xfer_softc(xfer);
+
+ if (sc->sc_swap_cb)
+ umct_read_callback_sub(xfer, error);
+ else
+ umct_intr_callback_sub(xfer, error);
+}
+
+static void
+umct_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umct_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(&sc->sc_ucom, pc, 0,
+ sc->sc_obufsize, &actlen)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+umct_read_callback_sub(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umct_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+umct_poll(struct ucom_softc *ucom)
+{
+ struct umct_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_xfer, UMCT_N_TRANSFER);
+}
diff --git a/sys/dev/usb/serial/umodem.c b/sys/dev/usb/serial/umodem.c
new file mode 100644
index 000000000000..59aa5b21e85f
--- /dev/null
+++ b/sys/dev/usb/serial/umodem.c
@@ -0,0 +1,1042 @@
+/* $NetBSD: umodem.c,v 1.45 2002/09/23 05:51:23 simonb Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2003 M. Warner Losh <imp@FreeBSD.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.
+ *
+ * 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.
+ *
+ * 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
+ * http://www.usb.org/developers/devclass_docs/cdc_wmc10.zip
+ */
+
+/*
+ * 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.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/usb_cdc.h>
+#include "usbdevs.h"
+#include "usb_if.h"
+
+#include <dev/usb/usb_ioctl.h>
+
+#define USB_DEBUG_VAR umodem_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/quirk/usb_quirk.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#ifdef USB_DEBUG
+static int umodem_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, umodem, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB umodem");
+SYSCTL_INT(_hw_usb_umodem, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &umodem_debug, 0, "Debug level");
+#endif
+
+static const STRUCT_USB_DUAL_ID umodem_dual_devs[] = {
+ /* Generic Modem class match */
+ {USB_IFACE_CLASS(UICLASS_CDC),
+ USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL),
+ USB_IFACE_PROTOCOL(UIPROTO_CDC_AT)},
+ {USB_IFACE_CLASS(UICLASS_CDC),
+ USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL),
+ USB_IFACE_PROTOCOL(UIPROTO_CDC_NONE)},
+};
+
+static const STRUCT_USB_HOST_ID umodem_host_devs[] = {
+ /* Huawei Modem class match */
+ {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x01)},
+ {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x02)},
+ {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x10)},
+ {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x12)},
+ {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x61)},
+ {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(UICLASS_VENDOR),
+ USB_IFACE_SUBCLASS(0x02), USB_IFACE_PROTOCOL(0x62)},
+ {USB_VENDOR(USB_VENDOR_HUAWEI),USB_IFACE_CLASS(UICLASS_CDC),
+ USB_IFACE_SUBCLASS(UISUBCLASS_ABSTRACT_CONTROL_MODEL),
+ USB_IFACE_PROTOCOL(0xFF)},
+ {USB_VENDOR(USB_VENDOR_HUAWEI), USB_IFACE_CLASS(0xFF),
+ USB_IFACE_SUBCLASS(0xF), USB_IFACE_PROTOCOL(0xFF)},
+ /* Kyocera AH-K3001V */
+ {USB_VPI(USB_VENDOR_KYOCERA, USB_PRODUCT_KYOCERA_AHK3001V, 1)},
+ {USB_VPI(USB_VENDOR_SIERRA, USB_PRODUCT_SIERRA_MC5720, 1)},
+ {USB_VPI(USB_VENDOR_CURITEL, USB_PRODUCT_CURITEL_PC5740, 1)},
+ /* Winbond */
+ {USB_VENDOR(USB_VENDOR_WINBOND), USB_PRODUCT(USB_PRODUCT_WINBOND_CDC)},
+};
+
+/*
+ * As speeds for umodem devices increase, these numbers will need to
+ * be increased. They should be good for G3 speeds and below.
+ *
+ * TODO: The TTY buffers should be increased!
+ */
+#define UMODEM_BUF_SIZE 1024
+
+enum {
+ UMODEM_BULK_WR,
+ UMODEM_BULK_RD,
+ UMODEM_INTR_WR,
+ UMODEM_INTR_RD,
+ UMODEM_N_TRANSFER,
+};
+
+#define UMODEM_MODVER 1 /* module version */
+
+struct umodem_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+
+ struct usb_xfer *sc_xfer[UMODEM_N_TRANSFER];
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+
+ uint16_t sc_line;
+
+ uint8_t sc_lsr; /* local status register */
+ uint8_t sc_msr; /* modem status register */
+ uint8_t sc_ctrl_iface_no;
+ uint8_t sc_data_iface_no;
+ uint8_t sc_iface_index[2];
+ uint8_t sc_cm_over_data;
+ uint8_t sc_cm_cap; /* CM capabilities */
+ uint8_t sc_acm_cap; /* ACM capabilities */
+ uint8_t sc_line_coding[32]; /* used in USB device mode */
+ uint8_t sc_abstract_state[32]; /* used in USB device mode */
+};
+
+static device_probe_t umodem_probe;
+static device_attach_t umodem_attach;
+static device_detach_t umodem_detach;
+static usb_handle_request_t umodem_handle_request;
+
+static void umodem_free_softc(struct umodem_softc *);
+
+static usb_callback_t umodem_intr_read_callback;
+static usb_callback_t umodem_intr_write_callback;
+static usb_callback_t umodem_write_callback;
+static usb_callback_t umodem_read_callback;
+
+static void umodem_free(struct ucom_softc *);
+static void umodem_start_read(struct ucom_softc *);
+static void umodem_stop_read(struct ucom_softc *);
+static void umodem_start_write(struct ucom_softc *);
+static void umodem_stop_write(struct ucom_softc *);
+static void umodem_get_caps(struct usb_attach_arg *, uint8_t *, uint8_t *);
+static void umodem_cfg_get_status(struct ucom_softc *, uint8_t *,
+ uint8_t *);
+static int umodem_pre_param(struct ucom_softc *, struct termios *);
+static void umodem_cfg_param(struct ucom_softc *, struct termios *);
+static void umodem_cfg_open(struct ucom_softc *);
+static int umodem_ioctl(struct ucom_softc *, uint32_t, caddr_t, int,
+ struct thread *);
+static void umodem_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void umodem_cfg_set_rts(struct ucom_softc *, uint8_t);
+static void umodem_cfg_set_break(struct ucom_softc *, uint8_t);
+static void *umodem_get_desc(struct usb_attach_arg *, uint8_t, uint8_t);
+static usb_error_t umodem_set_comm_feature(struct usb_device *, uint8_t,
+ uint16_t, uint16_t);
+static void umodem_poll(struct ucom_softc *ucom);
+static void umodem_find_data_iface(struct usb_attach_arg *uaa,
+ uint8_t, uint8_t *, uint8_t *);
+
+static const struct usb_config umodem_config[UMODEM_N_TRANSFER] = {
+ [UMODEM_BULK_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .if_index = 0,
+ .bufsize = UMODEM_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &umodem_write_callback,
+ .usb_mode = USB_MODE_DUAL,
+ },
+
+ [UMODEM_BULK_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_RX,
+ .if_index = 0,
+ .bufsize = UMODEM_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &umodem_read_callback,
+ .usb_mode = USB_MODE_DUAL,
+ },
+
+ [UMODEM_INTR_WR] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .if_index = 1,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &umodem_intr_write_callback,
+ .usb_mode = USB_MODE_DEVICE,
+ },
+
+ [UMODEM_INTR_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_RX,
+ .if_index = 1,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.no_pipe_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &umodem_intr_read_callback,
+ .usb_mode = USB_MODE_HOST,
+ },
+};
+
+static const struct ucom_callback umodem_callback = {
+ .ucom_cfg_get_status = &umodem_cfg_get_status,
+ .ucom_cfg_set_dtr = &umodem_cfg_set_dtr,
+ .ucom_cfg_set_rts = &umodem_cfg_set_rts,
+ .ucom_cfg_set_break = &umodem_cfg_set_break,
+ .ucom_cfg_param = &umodem_cfg_param,
+ .ucom_pre_param = &umodem_pre_param,
+ .ucom_cfg_open = &umodem_cfg_open,
+ .ucom_ioctl = &umodem_ioctl,
+ .ucom_start_read = &umodem_start_read,
+ .ucom_stop_read = &umodem_stop_read,
+ .ucom_start_write = &umodem_start_write,
+ .ucom_stop_write = &umodem_stop_write,
+ .ucom_poll = &umodem_poll,
+ .ucom_free = &umodem_free,
+};
+
+static device_method_t umodem_methods[] = {
+ /* USB interface */
+ DEVMETHOD(usb_handle_request, umodem_handle_request),
+
+ /* Device interface */
+ DEVMETHOD(device_probe, umodem_probe),
+ DEVMETHOD(device_attach, umodem_attach),
+ DEVMETHOD(device_detach, umodem_detach),
+ DEVMETHOD_END
+};
+
+static driver_t umodem_driver = {
+ .name = "umodem",
+ .methods = umodem_methods,
+ .size = sizeof(struct umodem_softc),
+};
+
+DRIVER_MODULE(umodem, uhub, umodem_driver, NULL, NULL);
+MODULE_DEPEND(umodem, ucom, 1, 1, 1);
+MODULE_DEPEND(umodem, usb, 1, 1, 1);
+MODULE_VERSION(umodem, UMODEM_MODVER);
+USB_PNP_DUAL_INFO(umodem_dual_devs);
+USB_PNP_HOST_INFO(umodem_host_devs);
+
+static int
+umodem_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ int error;
+
+ DPRINTFN(11, "\n");
+
+ error = usbd_lookup_id_by_uaa(umodem_host_devs,
+ sizeof(umodem_host_devs), uaa);
+ if (error) {
+ error = usbd_lookup_id_by_uaa(umodem_dual_devs,
+ sizeof(umodem_dual_devs), uaa);
+ if (error)
+ return (error);
+ }
+ return (BUS_PROBE_GENERIC);
+}
+
+static int
+umodem_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct umodem_softc *sc = device_get_softc(dev);
+ struct usb_cdc_cm_descriptor *cmd;
+ struct usb_cdc_union_descriptor *cud;
+ uint8_t i;
+ int error;
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "umodem", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ sc->sc_ctrl_iface_no = uaa->info.bIfaceNum;
+ sc->sc_iface_index[1] = uaa->info.bIfaceIndex;
+ sc->sc_udev = uaa->device;
+
+ umodem_get_caps(uaa, &sc->sc_cm_cap, &sc->sc_acm_cap);
+
+ /* get the data interface number */
+
+ cmd = NULL;
+ if (!usb_test_quirk(uaa, UQ_IGNORE_CDC_CM))
+ cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM);
+
+ if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) {
+ cud = usbd_find_descriptor(uaa->device, NULL,
+ uaa->info.bIfaceIndex, UDESC_CS_INTERFACE,
+ 0xFF, UDESCSUB_CDC_UNION, 0xFF);
+
+ if ((cud == NULL) || (cud->bLength < sizeof(*cud))) {
+ DPRINTF("Missing descriptor. "
+ "Assuming data interface is next.\n");
+ if (sc->sc_ctrl_iface_no == 0xFF) {
+ goto detach;
+ } else {
+ uint8_t class_match = 0;
+
+ /* set default interface number */
+ sc->sc_data_iface_no = 0xFF;
+
+ /* try to find the data interface backwards */
+ umodem_find_data_iface(uaa,
+ uaa->info.bIfaceIndex - 1,
+ &sc->sc_data_iface_no, &class_match);
+
+ /* try to find the data interface forwards */
+ umodem_find_data_iface(uaa,
+ uaa->info.bIfaceIndex + 1,
+ &sc->sc_data_iface_no, &class_match);
+
+ /* check if nothing was found */
+ if (sc->sc_data_iface_no == 0xFF)
+ goto detach;
+ }
+ } else {
+ sc->sc_data_iface_no = cud->bSlaveInterface[0];
+ }
+ } else {
+ sc->sc_data_iface_no = cmd->bDataInterface;
+ }
+
+ device_printf(dev, "data interface %d, has %sCM over "
+ "data, has %sbreak\n",
+ 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 ");
+
+ /* get the data interface too */
+
+ for (i = 0;; i++) {
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *id;
+
+ iface = usbd_get_iface(uaa->device, i);
+
+ if (iface) {
+ id = usbd_get_interface_descriptor(iface);
+
+ if (id && (id->bInterfaceNumber == sc->sc_data_iface_no)) {
+ sc->sc_iface_index[0] = i;
+ usbd_set_parent_iface(uaa->device, i, uaa->info.bIfaceIndex);
+ break;
+ }
+ } else {
+ device_printf(dev, "no data interface\n");
+ goto detach;
+ }
+ }
+
+ if (usb_test_quirk(uaa, UQ_ASSUME_CM_OVER_DATA)) {
+ sc->sc_cm_over_data = 1;
+ } else {
+ if (sc->sc_cm_cap & USB_CDC_CM_OVER_DATA) {
+ if (sc->sc_acm_cap & USB_CDC_ACM_HAS_FEATURE) {
+ error = umodem_set_comm_feature
+ (uaa->device, sc->sc_ctrl_iface_no,
+ UCDC_ABSTRACT_STATE, UCDC_DATA_MULTIPLEXED);
+
+ /* ignore any errors */
+ }
+ sc->sc_cm_over_data = 1;
+ }
+ }
+ error = usbd_transfer_setup(uaa->device,
+ sc->sc_iface_index, sc->sc_xfer,
+ umodem_config, UMODEM_N_TRANSFER,
+ sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "Can't setup transfer\n");
+ goto detach;
+ }
+
+ ucom_set_usb_mode(&sc->sc_super_ucom, uaa->usb_mode);
+
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &umodem_callback, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "Can't attach com\n");
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ return (0);
+
+detach:
+ umodem_detach(dev);
+ return (ENXIO);
+}
+
+static void
+umodem_find_data_iface(struct usb_attach_arg *uaa,
+ uint8_t iface_index, uint8_t *p_data_no, uint8_t *p_match_class)
+{
+ struct usb_interface_descriptor *id;
+ struct usb_interface *iface;
+
+ iface = usbd_get_iface(uaa->device, iface_index);
+
+ /* check for end of interfaces */
+ if (iface == NULL)
+ return;
+
+ id = usbd_get_interface_descriptor(iface);
+
+ /* check for non-matching interface class */
+ if (id->bInterfaceClass != UICLASS_CDC_DATA ||
+ id->bInterfaceSubClass != UISUBCLASS_DATA) {
+ /* if we got a class match then return */
+ if (*p_match_class)
+ return;
+ } else {
+ *p_match_class = 1;
+ }
+
+ DPRINTFN(11, "Match at index %u\n", iface_index);
+
+ *p_data_no = id->bInterfaceNumber;
+}
+
+static void
+umodem_start_read(struct ucom_softc *ucom)
+{
+ struct umodem_softc *sc = ucom->sc_parent;
+
+ /* start interrupt endpoint, if any */
+ usbd_transfer_start(sc->sc_xfer[UMODEM_INTR_RD]);
+
+ /* start read endpoint */
+ usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_RD]);
+}
+
+static void
+umodem_stop_read(struct ucom_softc *ucom)
+{
+ struct umodem_softc *sc = ucom->sc_parent;
+
+ /* stop interrupt endpoint, if any */
+ usbd_transfer_stop(sc->sc_xfer[UMODEM_INTR_RD]);
+
+ /* stop read endpoint */
+ usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_RD]);
+}
+
+static void
+umodem_start_write(struct ucom_softc *ucom)
+{
+ struct umodem_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UMODEM_INTR_WR]);
+ usbd_transfer_start(sc->sc_xfer[UMODEM_BULK_WR]);
+}
+
+static void
+umodem_stop_write(struct ucom_softc *ucom)
+{
+ struct umodem_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UMODEM_INTR_WR]);
+ usbd_transfer_stop(sc->sc_xfer[UMODEM_BULK_WR]);
+}
+
+static void
+umodem_get_caps(struct usb_attach_arg *uaa, uint8_t *cm, uint8_t *acm)
+{
+ struct usb_cdc_cm_descriptor *cmd;
+ struct usb_cdc_acm_descriptor *cad;
+
+ cmd = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_CM);
+ if ((cmd == NULL) || (cmd->bLength < sizeof(*cmd))) {
+ DPRINTF("no CM desc (faking one)\n");
+ *cm = USB_CDC_CM_DOES_CM | USB_CDC_CM_OVER_DATA;
+ } else
+ *cm = cmd->bmCapabilities;
+
+ cad = umodem_get_desc(uaa, UDESC_CS_INTERFACE, UDESCSUB_CDC_ACM);
+ if ((cad == NULL) || (cad->bLength < sizeof(*cad))) {
+ DPRINTF("no ACM desc\n");
+ *acm = 0;
+ } else
+ *acm = cad->bmCapabilities;
+}
+
+static void
+umodem_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct umodem_softc *sc = ucom->sc_parent;
+
+ DPRINTF("\n");
+
+ /* XXX Note: sc_lsr is always zero */
+ *lsr = sc->sc_lsr;
+ *msr = sc->sc_msr;
+}
+
+static int
+umodem_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+ return (0); /* we accept anything */
+}
+
+static void
+umodem_cfg_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct umodem_softc *sc = ucom->sc_parent;
+ struct usb_cdc_line_state ls;
+ struct usb_device_request req;
+
+ DPRINTF("sc=%p\n", sc);
+
+ memset(&ls, 0, sizeof(ls));
+
+ USETDW(ls.dwDTERate, t->c_ospeed);
+
+ ls.bCharFormat = (t->c_cflag & CSTOPB) ?
+ UCDC_STOP_BIT_2 : UCDC_STOP_BIT_1;
+
+ ls.bParityType = (t->c_cflag & PARENB) ?
+ ((t->c_cflag & PARODD) ?
+ UCDC_PARITY_ODD : UCDC_PARITY_EVEN) : UCDC_PARITY_NONE;
+
+ switch (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;
+ }
+
+ DPRINTF("rate=%d fmt=%d parity=%d bits=%d\n",
+ UGETDW(ls.dwDTERate), ls.bCharFormat,
+ ls.bParityType, ls.bDataBits);
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_LINE_CODING;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_ctrl_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, sizeof(ls));
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, &ls, 0, 1000);
+}
+
+static void
+umodem_cfg_open(struct ucom_softc *ucom)
+{
+ struct umodem_softc *sc = ucom->sc_parent;
+
+ /* clear stall, if in USB host mode */
+ if ((sc->sc_super_ucom.sc_flag & UCOM_FLAG_DEVICE_MODE) == 0) {
+ usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_WR]);
+ usbd_xfer_set_stall(sc->sc_xfer[UMODEM_BULK_RD]);
+ }
+}
+
+static int
+umodem_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data,
+ int flag, struct thread *td)
+{
+ struct umodem_softc *sc = ucom->sc_parent;
+ int error = 0;
+
+ DPRINTF("cmd=0x%08x\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("unknown\n");
+ error = ENOIOCTL;
+ break;
+ }
+
+ return (error);
+}
+
+static void
+umodem_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct umodem_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+
+ DPRINTF("onoff=%d\n", onoff);
+
+ if (onoff)
+ sc->sc_line |= UCDC_LINE_DTR;
+ else
+ sc->sc_line &= ~UCDC_LINE_DTR;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
+ USETW(req.wValue, sc->sc_line);
+ req.wIndex[0] = sc->sc_ctrl_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+static void
+umodem_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct umodem_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+
+ DPRINTF("onoff=%d\n", onoff);
+
+ if (onoff)
+ sc->sc_line |= UCDC_LINE_RTS;
+ else
+ sc->sc_line &= ~UCDC_LINE_RTS;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
+ USETW(req.wValue, sc->sc_line);
+ req.wIndex[0] = sc->sc_ctrl_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+static void
+umodem_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct umodem_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+ uint16_t temp;
+
+ DPRINTF("onoff=%d\n", onoff);
+
+ if (sc->sc_acm_cap & USB_CDC_ACM_HAS_BREAK) {
+ temp = onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SEND_BREAK;
+ USETW(req.wValue, temp);
+ req.wIndex[0] = sc->sc_ctrl_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+ }
+}
+
+static void
+umodem_intr_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ DPRINTF("Transferred %d bytes\n", actlen);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* start clear stall */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+umodem_intr_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usb_cdc_notification pkt;
+ struct umodem_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint16_t wLen;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ if (actlen < 8) {
+ DPRINTF("received short packet, "
+ "%d bytes\n", actlen);
+ goto tr_setup;
+ }
+ if (actlen > (int)sizeof(pkt)) {
+ DPRINTF("truncating message\n");
+ actlen = sizeof(pkt);
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &pkt, actlen);
+
+ actlen -= 8;
+
+ wLen = UGETW(pkt.wLength);
+ if (actlen > wLen) {
+ actlen = wLen;
+ }
+ if (pkt.bmRequestType != UCDC_NOTIFICATION) {
+ DPRINTF("unknown message type, "
+ "0x%02x, on notify pipe!\n",
+ pkt.bmRequestType);
+ goto tr_setup;
+ }
+ switch (pkt.bNotification) {
+ case UCDC_N_SERIAL_STATE:
+ /*
+ * Set the serial state in ucom driver based on
+ * the bits from the notify message
+ */
+ if (actlen < 2) {
+ DPRINTF("invalid notification "
+ "length, %d bytes!\n", actlen);
+ break;
+ }
+ DPRINTF("notify bytes = %02x%02x\n",
+ pkt.data[0],
+ pkt.data[1]);
+
+ /* Currently, lsr is always zero. */
+ sc->sc_lsr = 0;
+ sc->sc_msr = 0;
+
+ if (pkt.data[0] & UCDC_N_SERIAL_RI) {
+ sc->sc_msr |= SER_RI;
+ }
+ if (pkt.data[0] & UCDC_N_SERIAL_DSR) {
+ sc->sc_msr |= SER_DSR;
+ }
+ if (pkt.data[0] & UCDC_N_SERIAL_DCD) {
+ sc->sc_msr |= SER_DCD;
+ }
+ ucom_status_change(&sc->sc_ucom);
+ break;
+
+ default:
+ DPRINTF("unknown notify message: 0x%02x\n",
+ pkt.bNotification);
+ break;
+ }
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+umodem_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umodem_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(&sc->sc_ucom, pc, 0,
+ UMODEM_BUF_SIZE, &actlen)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+umodem_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umodem_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ DPRINTF("actlen=%d\n", actlen);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void *
+umodem_get_desc(struct usb_attach_arg *uaa, uint8_t type, uint8_t subtype)
+{
+ return (usbd_find_descriptor(uaa->device, NULL, uaa->info.bIfaceIndex,
+ type, 0xFF, subtype, 0xFF));
+}
+
+static usb_error_t
+umodem_set_comm_feature(struct usb_device *udev, uint8_t iface_no,
+ uint16_t feature, uint16_t state)
+{
+ struct usb_device_request req;
+ struct usb_cdc_abstract_state ast;
+
+ DPRINTF("feature=%d state=%d\n",
+ feature, state);
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_COMM_FEATURE;
+ USETW(req.wValue, feature);
+ req.wIndex[0] = iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, UCDC_ABSTRACT_STATE_LENGTH);
+ USETW(ast.wState, state);
+
+ return (usbd_do_request(udev, NULL, &req, &ast));
+}
+
+static int
+umodem_detach(device_t dev)
+{
+ struct umodem_softc *sc = device_get_softc(dev);
+
+ DPRINTF("sc=%p\n", sc);
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, UMODEM_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ umodem_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(umodem);
+
+static void
+umodem_free_softc(struct umodem_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+umodem_free(struct ucom_softc *ucom)
+{
+ umodem_free_softc(ucom->sc_parent);
+}
+
+static void
+umodem_poll(struct ucom_softc *ucom)
+{
+ struct umodem_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_xfer, UMODEM_N_TRANSFER);
+}
+
+static int
+umodem_handle_request(device_t dev,
+ const void *preq, void **pptr, uint16_t *plen,
+ uint16_t offset, uint8_t *pstate)
+{
+ struct umodem_softc *sc = device_get_softc(dev);
+ const struct usb_device_request *req = preq;
+ uint8_t is_complete = *pstate;
+
+ DPRINTF("sc=%p\n", sc);
+
+ if (!is_complete) {
+ if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->bRequest == UCDC_SET_LINE_CODING) &&
+ (req->wIndex[0] == sc->sc_ctrl_iface_no) &&
+ (req->wIndex[1] == 0x00) &&
+ (req->wValue[0] == 0x00) &&
+ (req->wValue[1] == 0x00)) {
+ if (offset == 0) {
+ *plen = sizeof(sc->sc_line_coding);
+ *pptr = &sc->sc_line_coding;
+ } else {
+ *plen = 0;
+ }
+ return (0);
+ } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->wIndex[0] == sc->sc_ctrl_iface_no) &&
+ (req->wIndex[1] == 0x00) &&
+ (req->bRequest == UCDC_SET_COMM_FEATURE)) {
+ if (offset == 0) {
+ *plen = sizeof(sc->sc_abstract_state);
+ *pptr = &sc->sc_abstract_state;
+ } else {
+ *plen = 0;
+ }
+ return (0);
+ } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->wIndex[0] == sc->sc_ctrl_iface_no) &&
+ (req->wIndex[1] == 0x00) &&
+ (req->bRequest == UCDC_SET_CONTROL_LINE_STATE)) {
+ *plen = 0;
+ return (0);
+ } else if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->wIndex[0] == sc->sc_ctrl_iface_no) &&
+ (req->wIndex[1] == 0x00) &&
+ (req->bRequest == UCDC_SEND_BREAK)) {
+ *plen = 0;
+ return (0);
+ }
+ }
+ return (ENXIO); /* use builtin handler */
+}
diff --git a/sys/dev/usb/serial/umoscom.c b/sys/dev/usb/serial/umoscom.c
new file mode 100644
index 000000000000..2ee98efd83cb
--- /dev/null
+++ b/sys/dev/usb/serial/umoscom.c
@@ -0,0 +1,730 @@
+/* $OpenBSD: umoscom.c,v 1.2 2006/10/26 06:02:43 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR umoscom_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#ifdef USB_DEBUG
+static int umoscom_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, umoscom, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB umoscom");
+SYSCTL_INT(_hw_usb_umoscom, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &umoscom_debug, 0, "Debug level");
+#endif
+
+#define UMOSCOM_BUFSIZE 1024 /* bytes */
+
+#define UMOSCOM_CONFIG_INDEX 0
+#define UMOSCOM_IFACE_INDEX 0
+
+/* interrupt packet */
+#define UMOSCOM_IIR_RLS 0x06
+#define UMOSCOM_IIR_RDA 0x04
+#define UMOSCOM_IIR_CTI 0x0c
+#define UMOSCOM_IIR_THR 0x02
+#define UMOSCOM_IIR_MS 0x00
+
+/* registers */
+#define UMOSCOM_READ 0x0d
+#define UMOSCOM_WRITE 0x0e
+#define UMOSCOM_UART_REG 0x0300
+#define UMOSCOM_VEND_REG 0x0000
+
+#define UMOSCOM_TXBUF 0x00 /* Write */
+#define UMOSCOM_RXBUF 0x00 /* Read */
+#define UMOSCOM_INT 0x01
+#define UMOSCOM_FIFO 0x02 /* Write */
+#define UMOSCOM_ISR 0x02 /* Read */
+#define UMOSCOM_LCR 0x03
+#define UMOSCOM_MCR 0x04
+#define UMOSCOM_LSR 0x05
+#define UMOSCOM_MSR 0x06
+#define UMOSCOM_SCRATCH 0x07
+#define UMOSCOM_DIV_LO 0x08
+#define UMOSCOM_DIV_HI 0x09
+#define UMOSCOM_EFR 0x0a
+#define UMOSCOM_XON1 0x0b
+#define UMOSCOM_XON2 0x0c
+#define UMOSCOM_XOFF1 0x0d
+#define UMOSCOM_XOFF2 0x0e
+
+#define UMOSCOM_BAUDLO 0x00
+#define UMOSCOM_BAUDHI 0x01
+
+#define UMOSCOM_INT_RXEN 0x01
+#define UMOSCOM_INT_TXEN 0x02
+#define UMOSCOM_INT_RSEN 0x04
+#define UMOSCOM_INT_MDMEM 0x08
+#define UMOSCOM_INT_SLEEP 0x10
+#define UMOSCOM_INT_XOFF 0x20
+#define UMOSCOM_INT_RTS 0x40
+
+#define UMOSCOM_FIFO_EN 0x01
+#define UMOSCOM_FIFO_RXCLR 0x02
+#define UMOSCOM_FIFO_TXCLR 0x04
+#define UMOSCOM_FIFO_DMA_BLK 0x08
+#define UMOSCOM_FIFO_TXLVL_MASK 0x30
+#define UMOSCOM_FIFO_TXLVL_8 0x00
+#define UMOSCOM_FIFO_TXLVL_16 0x10
+#define UMOSCOM_FIFO_TXLVL_32 0x20
+#define UMOSCOM_FIFO_TXLVL_56 0x30
+#define UMOSCOM_FIFO_RXLVL_MASK 0xc0
+#define UMOSCOM_FIFO_RXLVL_8 0x00
+#define UMOSCOM_FIFO_RXLVL_16 0x40
+#define UMOSCOM_FIFO_RXLVL_56 0x80
+#define UMOSCOM_FIFO_RXLVL_80 0xc0
+
+#define UMOSCOM_ISR_MDM 0x00
+#define UMOSCOM_ISR_NONE 0x01
+#define UMOSCOM_ISR_TX 0x02
+#define UMOSCOM_ISR_RX 0x04
+#define UMOSCOM_ISR_LINE 0x06
+#define UMOSCOM_ISR_RXTIMEOUT 0x0c
+#define UMOSCOM_ISR_RX_XOFF 0x10
+#define UMOSCOM_ISR_RTSCTS 0x20
+#define UMOSCOM_ISR_FIFOEN 0xc0
+
+#define UMOSCOM_LCR_DBITS(x) ((x) - 5)
+#define UMOSCOM_LCR_STOP_BITS_1 0x00
+#define UMOSCOM_LCR_STOP_BITS_2 0x04 /* 2 if 6-8 bits/char or 1.5 if 5 */
+#define UMOSCOM_LCR_PARITY_NONE 0x00
+#define UMOSCOM_LCR_PARITY_ODD 0x08
+#define UMOSCOM_LCR_PARITY_EVEN 0x18
+#define UMOSCOM_LCR_BREAK 0x40
+#define UMOSCOM_LCR_DIVLATCH_EN 0x80
+
+#define UMOSCOM_MCR_DTR 0x01
+#define UMOSCOM_MCR_RTS 0x02
+#define UMOSCOM_MCR_LOOP 0x04
+#define UMOSCOM_MCR_INTEN 0x08
+#define UMOSCOM_MCR_LOOPBACK 0x10
+#define UMOSCOM_MCR_XONANY 0x20
+#define UMOSCOM_MCR_IRDA_EN 0x40
+#define UMOSCOM_MCR_BAUD_DIV4 0x80
+
+#define UMOSCOM_LSR_RXDATA 0x01
+#define UMOSCOM_LSR_RXOVER 0x02
+#define UMOSCOM_LSR_RXPAR_ERR 0x04
+#define UMOSCOM_LSR_RXFRM_ERR 0x08
+#define UMOSCOM_LSR_RXBREAK 0x10
+#define UMOSCOM_LSR_TXEMPTY 0x20
+#define UMOSCOM_LSR_TXALLEMPTY 0x40
+#define UMOSCOM_LSR_TXFIFO_ERR 0x80
+
+#define UMOSCOM_MSR_CTS_CHG 0x01
+#define UMOSCOM_MSR_DSR_CHG 0x02
+#define UMOSCOM_MSR_RI_CHG 0x04
+#define UMOSCOM_MSR_CD_CHG 0x08
+#define UMOSCOM_MSR_CTS 0x10
+#define UMOSCOM_MSR_RTS 0x20
+#define UMOSCOM_MSR_RI 0x40
+#define UMOSCOM_MSR_CD 0x80
+
+#define UMOSCOM_BAUD_REF 115200
+
+enum {
+ UMOSCOM_BULK_DT_WR,
+ UMOSCOM_BULK_DT_RD,
+ UMOSCOM_INTR_DT_RD,
+ UMOSCOM_N_TRANSFER,
+};
+
+struct umoscom_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+
+ struct usb_xfer *sc_xfer[UMOSCOM_N_TRANSFER];
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+
+ uint8_t sc_mcr;
+ uint8_t sc_lcr;
+};
+
+/* prototypes */
+
+static device_probe_t umoscom_probe;
+static device_attach_t umoscom_attach;
+static device_detach_t umoscom_detach;
+static void umoscom_free_softc(struct umoscom_softc *);
+
+static usb_callback_t umoscom_write_callback;
+static usb_callback_t umoscom_read_callback;
+static usb_callback_t umoscom_intr_callback;
+
+static void umoscom_free(struct ucom_softc *);
+static void umoscom_cfg_open(struct ucom_softc *);
+static void umoscom_cfg_close(struct ucom_softc *);
+static void umoscom_cfg_set_break(struct ucom_softc *, uint8_t);
+static void umoscom_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void umoscom_cfg_set_rts(struct ucom_softc *, uint8_t);
+static int umoscom_pre_param(struct ucom_softc *, struct termios *);
+static void umoscom_cfg_param(struct ucom_softc *, struct termios *);
+static void umoscom_cfg_get_status(struct ucom_softc *, uint8_t *,
+ uint8_t *);
+static void umoscom_cfg_write(struct umoscom_softc *, uint16_t, uint16_t);
+static uint8_t umoscom_cfg_read(struct umoscom_softc *, uint16_t);
+static void umoscom_start_read(struct ucom_softc *);
+static void umoscom_stop_read(struct ucom_softc *);
+static void umoscom_start_write(struct ucom_softc *);
+static void umoscom_stop_write(struct ucom_softc *);
+static void umoscom_poll(struct ucom_softc *ucom);
+
+static const struct usb_config umoscom_config_data[UMOSCOM_N_TRANSFER] = {
+ [UMOSCOM_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UMOSCOM_BUFSIZE,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &umoscom_write_callback,
+ },
+
+ [UMOSCOM_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UMOSCOM_BUFSIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &umoscom_read_callback,
+ },
+
+ [UMOSCOM_INTR_DT_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &umoscom_intr_callback,
+ },
+};
+
+static const struct ucom_callback umoscom_callback = {
+ /* configuration callbacks */
+ .ucom_cfg_get_status = &umoscom_cfg_get_status,
+ .ucom_cfg_set_dtr = &umoscom_cfg_set_dtr,
+ .ucom_cfg_set_rts = &umoscom_cfg_set_rts,
+ .ucom_cfg_set_break = &umoscom_cfg_set_break,
+ .ucom_cfg_param = &umoscom_cfg_param,
+ .ucom_cfg_open = &umoscom_cfg_open,
+ .ucom_cfg_close = &umoscom_cfg_close,
+
+ /* other callbacks */
+ .ucom_pre_param = &umoscom_pre_param,
+ .ucom_start_read = &umoscom_start_read,
+ .ucom_stop_read = &umoscom_stop_read,
+ .ucom_start_write = &umoscom_start_write,
+ .ucom_stop_write = &umoscom_stop_write,
+ .ucom_poll = &umoscom_poll,
+ .ucom_free = &umoscom_free,
+};
+
+static device_method_t umoscom_methods[] = {
+ DEVMETHOD(device_probe, umoscom_probe),
+ DEVMETHOD(device_attach, umoscom_attach),
+ DEVMETHOD(device_detach, umoscom_detach),
+ DEVMETHOD_END
+};
+
+static driver_t umoscom_driver = {
+ .name = "umoscom",
+ .methods = umoscom_methods,
+ .size = sizeof(struct umoscom_softc),
+};
+
+static const STRUCT_USB_HOST_ID umoscom_devs[] = {
+ {USB_VPI(USB_VENDOR_MOSCHIP, USB_PRODUCT_MOSCHIP_MCS7703, 0)}
+};
+
+DRIVER_MODULE(umoscom, uhub, umoscom_driver, NULL, NULL);
+MODULE_DEPEND(umoscom, ucom, 1, 1, 1);
+MODULE_DEPEND(umoscom, usb, 1, 1, 1);
+MODULE_VERSION(umoscom, 1);
+USB_PNP_HOST_INFO(umoscom_devs);
+
+static int
+umoscom_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != UMOSCOM_CONFIG_INDEX) {
+ return (ENXIO);
+ }
+ if (uaa->info.bIfaceIndex != UMOSCOM_IFACE_INDEX) {
+ return (ENXIO);
+ }
+ return (usbd_lookup_id_by_uaa(umoscom_devs, sizeof(umoscom_devs), uaa));
+}
+
+static int
+umoscom_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct umoscom_softc *sc = device_get_softc(dev);
+ int error;
+ uint8_t iface_index;
+
+ sc->sc_udev = uaa->device;
+ sc->sc_mcr = 0x08; /* enable interrupts */
+
+ /* XXX the device doesn't provide any ID string, so set a static one */
+ device_set_desc(dev, "MOSCHIP USB Serial Port Adapter");
+ device_printf(dev, "<MOSCHIP USB Serial Port Adapter>\n");
+
+ mtx_init(&sc->sc_mtx, "umoscom", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ iface_index = UMOSCOM_IFACE_INDEX;
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, umoscom_config_data,
+ UMOSCOM_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error) {
+ goto detach;
+ }
+ /* clear stall at first run */
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(sc->sc_xfer[UMOSCOM_BULK_DT_WR]);
+ usbd_xfer_set_stall(sc->sc_xfer[UMOSCOM_BULK_DT_RD]);
+ mtx_unlock(&sc->sc_mtx);
+
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &umoscom_callback, &sc->sc_mtx);
+ if (error) {
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ return (0);
+
+detach:
+ device_printf(dev, "attach error: %s\n", usbd_errstr(error));
+ umoscom_detach(dev);
+ return (ENXIO);
+}
+
+static int
+umoscom_detach(device_t dev)
+{
+ struct umoscom_softc *sc = device_get_softc(dev);
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, UMOSCOM_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ umoscom_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(umoscom);
+
+static void
+umoscom_free_softc(struct umoscom_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+umoscom_free(struct ucom_softc *ucom)
+{
+ umoscom_free_softc(ucom->sc_parent);
+}
+
+static void
+umoscom_cfg_open(struct ucom_softc *ucom)
+{
+ struct umoscom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("\n");
+
+ /* Purge FIFOs or odd things happen */
+ umoscom_cfg_write(sc, UMOSCOM_FIFO, 0x00 | UMOSCOM_UART_REG);
+
+ /* Enable FIFO */
+ umoscom_cfg_write(sc, UMOSCOM_FIFO, UMOSCOM_FIFO_EN |
+ UMOSCOM_FIFO_RXCLR | UMOSCOM_FIFO_TXCLR |
+ UMOSCOM_FIFO_DMA_BLK | UMOSCOM_FIFO_RXLVL_MASK |
+ UMOSCOM_UART_REG);
+
+ /* Enable Interrupt Registers */
+ umoscom_cfg_write(sc, UMOSCOM_INT, 0x0C | UMOSCOM_UART_REG);
+
+ /* Magic */
+ umoscom_cfg_write(sc, 0x01, 0x08);
+
+ /* Magic */
+ umoscom_cfg_write(sc, 0x00, 0x02);
+}
+
+static void
+umoscom_cfg_close(struct ucom_softc *ucom)
+{
+ return;
+}
+
+static void
+umoscom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct umoscom_softc *sc = ucom->sc_parent;
+ uint16_t val;
+
+ val = sc->sc_lcr;
+ if (onoff)
+ val |= UMOSCOM_LCR_BREAK;
+
+ umoscom_cfg_write(sc, UMOSCOM_LCR, val | UMOSCOM_UART_REG);
+}
+
+static void
+umoscom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct umoscom_softc *sc = ucom->sc_parent;
+
+ if (onoff)
+ sc->sc_mcr |= UMOSCOM_MCR_DTR;
+ else
+ sc->sc_mcr &= ~UMOSCOM_MCR_DTR;
+
+ umoscom_cfg_write(sc, UMOSCOM_MCR, sc->sc_mcr | UMOSCOM_UART_REG);
+}
+
+static void
+umoscom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct umoscom_softc *sc = ucom->sc_parent;
+
+ if (onoff)
+ sc->sc_mcr |= UMOSCOM_MCR_RTS;
+ else
+ sc->sc_mcr &= ~UMOSCOM_MCR_RTS;
+
+ umoscom_cfg_write(sc, UMOSCOM_MCR, sc->sc_mcr | UMOSCOM_UART_REG);
+}
+
+static int
+umoscom_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+ if ((t->c_ospeed <= 1) || (t->c_ospeed > 115200))
+ return (EINVAL);
+
+ return (0);
+}
+
+static void
+umoscom_cfg_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct umoscom_softc *sc = ucom->sc_parent;
+ uint16_t data;
+
+ DPRINTF("speed=%d\n", t->c_ospeed);
+
+ data = ((uint32_t)UMOSCOM_BAUD_REF) / ((uint32_t)t->c_ospeed);
+
+ if (data == 0) {
+ DPRINTF("invalid baud rate!\n");
+ return;
+ }
+ umoscom_cfg_write(sc, UMOSCOM_LCR,
+ UMOSCOM_LCR_DIVLATCH_EN | UMOSCOM_UART_REG);
+
+ umoscom_cfg_write(sc, UMOSCOM_BAUDLO,
+ (data & 0xFF) | UMOSCOM_UART_REG);
+
+ umoscom_cfg_write(sc, UMOSCOM_BAUDHI,
+ ((data >> 8) & 0xFF) | UMOSCOM_UART_REG);
+
+ if (t->c_cflag & CSTOPB)
+ data = UMOSCOM_LCR_STOP_BITS_2;
+ else
+ data = UMOSCOM_LCR_STOP_BITS_1;
+
+ if (t->c_cflag & PARENB) {
+ if (t->c_cflag & PARODD)
+ data |= UMOSCOM_LCR_PARITY_ODD;
+ else
+ data |= UMOSCOM_LCR_PARITY_EVEN;
+ } else
+ data |= UMOSCOM_LCR_PARITY_NONE;
+
+ switch (t->c_cflag & CSIZE) {
+ case CS5:
+ data |= UMOSCOM_LCR_DBITS(5);
+ break;
+ case CS6:
+ data |= UMOSCOM_LCR_DBITS(6);
+ break;
+ case CS7:
+ data |= UMOSCOM_LCR_DBITS(7);
+ break;
+ case CS8:
+ data |= UMOSCOM_LCR_DBITS(8);
+ break;
+ }
+
+ sc->sc_lcr = data;
+ umoscom_cfg_write(sc, UMOSCOM_LCR, data | UMOSCOM_UART_REG);
+}
+
+static void
+umoscom_cfg_get_status(struct ucom_softc *ucom, uint8_t *p_lsr, uint8_t *p_msr)
+{
+ struct umoscom_softc *sc = ucom->sc_parent;
+ uint8_t msr;
+
+ DPRINTFN(5, "\n");
+
+ /*
+ * Read status registers. MSR bits need translation from ns16550 to
+ * SER_* values. LSR bits are ns16550 in hardware and ucom.
+ */
+
+ *p_lsr = umoscom_cfg_read(sc, UMOSCOM_LSR);
+ msr = umoscom_cfg_read(sc, UMOSCOM_MSR);
+
+ /* translate bits */
+
+ if (msr & UMOSCOM_MSR_CTS)
+ *p_msr |= SER_CTS;
+
+ if (msr & UMOSCOM_MSR_CD)
+ *p_msr |= SER_DCD;
+
+ if (msr & UMOSCOM_MSR_RI)
+ *p_msr |= SER_RI;
+
+ if (msr & UMOSCOM_MSR_RTS)
+ *p_msr |= SER_DSR;
+}
+
+static void
+umoscom_cfg_write(struct umoscom_softc *sc, uint16_t reg, uint16_t val)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = UMOSCOM_WRITE;
+ USETW(req.wValue, val);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+static uint8_t
+umoscom_cfg_read(struct umoscom_softc *sc, uint16_t reg)
+{
+ struct usb_device_request req;
+ uint8_t val;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = UMOSCOM_READ;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 1);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, &val, 0, 1000);
+
+ DPRINTF("reg=0x%04x, val=0x%02x\n", reg, val);
+
+ return (val);
+}
+
+static void
+umoscom_start_read(struct ucom_softc *ucom)
+{
+ struct umoscom_softc *sc = ucom->sc_parent;
+
+#if 0
+ /* start interrupt endpoint */
+ usbd_transfer_start(sc->sc_xfer[UMOSCOM_INTR_DT_RD]);
+#endif
+ /* start read endpoint */
+ usbd_transfer_start(sc->sc_xfer[UMOSCOM_BULK_DT_RD]);
+}
+
+static void
+umoscom_stop_read(struct ucom_softc *ucom)
+{
+ struct umoscom_softc *sc = ucom->sc_parent;
+
+ /* stop interrupt transfer */
+ usbd_transfer_stop(sc->sc_xfer[UMOSCOM_INTR_DT_RD]);
+
+ /* stop read endpoint */
+ usbd_transfer_stop(sc->sc_xfer[UMOSCOM_BULK_DT_RD]);
+}
+
+static void
+umoscom_start_write(struct ucom_softc *ucom)
+{
+ struct umoscom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UMOSCOM_BULK_DT_WR]);
+}
+
+static void
+umoscom_stop_write(struct ucom_softc *ucom)
+{
+ struct umoscom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UMOSCOM_BULK_DT_WR]);
+}
+
+static void
+umoscom_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umoscom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ DPRINTF("\n");
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(&sc->sc_ucom, pc, 0,
+ UMOSCOM_BUFSIZE, &actlen)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ DPRINTFN(0, "transfer failed\n");
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+umoscom_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umoscom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTF("got %d bytes\n", actlen);
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ DPRINTF("\n");
+
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ DPRINTFN(0, "transfer failed\n");
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+umoscom_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umoscom_softc *sc = usbd_xfer_softc(xfer);
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (actlen < 2) {
+ DPRINTF("too short message\n");
+ goto tr_setup;
+ }
+ ucom_status_change(&sc->sc_ucom);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ DPRINTFN(0, "transfer failed\n");
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+umoscom_poll(struct ucom_softc *ucom)
+{
+ struct umoscom_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_xfer, UMOSCOM_N_TRANSFER);
+}
diff --git a/sys/dev/usb/serial/uplcom.c b/sys/dev/usb/serial/uplcom.c
new file mode 100644
index 000000000000..1fd73f1f7665
--- /dev/null
+++ b/sys/dev/usb/serial/uplcom.c
@@ -0,0 +1,1147 @@
+/* $NetBSD: uplcom.c,v 1.21 2001/11/13 06:24:56 lukem Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ */
+
+/*-
+ * 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.
+ *
+ * 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 <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_cdc.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR uplcom_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#ifdef USB_DEBUG
+static int uplcom_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uplcom, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB uplcom");
+SYSCTL_INT(_hw_usb_uplcom, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &uplcom_debug, 0, "Debug level");
+#endif
+
+#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 0 /* default */
+#endif
+
+#define UPLCOM_BULK_BUF_SIZE 1024 /* bytes */
+
+#define UPLCOM_SET_REQUEST 0x01
+#define UPLCOM_SET_REQUEST_PL2303HXN 0x80
+#define UPLCOM_SET_CRTSCTS 0x41
+#define UPLCOM_SET_CRTSCTS_PL2303X 0x61
+#define UPLCOM_SET_CRTSCTS_PL2303HXN 0xFA
+#define UPLCOM_CLEAR_CRTSCTS_PL2303HXN 0xFF
+#define UPLCOM_CRTSCTS_REG_PL2303HXN 0x0A
+#define UPLCOM_STATUS_REG_PL2303HX 0x8080
+#define RSAQ_STATUS_CTS 0x80
+#define RSAQ_STATUS_OVERRUN_ERROR 0x40
+#define RSAQ_STATUS_PARITY_ERROR 0x20
+#define RSAQ_STATUS_FRAME_ERROR 0x10
+#define RSAQ_STATUS_RING 0x08
+#define RSAQ_STATUS_BREAK_ERROR 0x04
+#define RSAQ_STATUS_DSR 0x02
+#define RSAQ_STATUS_DCD 0x01
+
+#define TYPE_PL2303 0
+#define TYPE_PL2303HX 1
+#define TYPE_PL2303HXD 2
+#define TYPE_PL2303HXN 3
+
+#define UPLCOM_STATE_INDEX 8
+
+enum {
+ UPLCOM_BULK_DT_WR,
+ UPLCOM_BULK_DT_RD,
+ UPLCOM_INTR_DT_RD,
+ UPLCOM_N_TRANSFER,
+};
+
+struct uplcom_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+
+ struct usb_xfer *sc_xfer[UPLCOM_N_TRANSFER];
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+
+ uint16_t sc_line;
+
+ uint8_t sc_lsr; /* local status register */
+ uint8_t sc_msr; /* uplcom status register */
+ uint8_t sc_chiptype; /* type of chip */
+ uint8_t sc_ctrl_iface_no;
+ uint8_t sc_data_iface_no;
+ uint8_t sc_iface_index[2];
+};
+
+/* prototypes */
+
+static usb_error_t uplcom_reset(struct uplcom_softc *, struct usb_device *);
+static usb_error_t uplcom_pl2303_do(struct usb_device *, uint8_t, uint8_t,
+ uint16_t, uint16_t, uint16_t);
+static int uplcom_pl2303_init(struct usb_device *, uint8_t);
+static void uplcom_free(struct ucom_softc *);
+static void uplcom_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void uplcom_cfg_set_rts(struct ucom_softc *, uint8_t);
+static void uplcom_cfg_set_break(struct ucom_softc *, uint8_t);
+static int uplcom_pre_param(struct ucom_softc *, struct termios *);
+static void uplcom_cfg_param(struct ucom_softc *, struct termios *);
+static void uplcom_start_read(struct ucom_softc *);
+static void uplcom_stop_read(struct ucom_softc *);
+static void uplcom_start_write(struct ucom_softc *);
+static void uplcom_stop_write(struct ucom_softc *);
+static void uplcom_cfg_get_status(struct ucom_softc *, uint8_t *,
+ uint8_t *);
+static void uplcom_poll(struct ucom_softc *ucom);
+
+static device_probe_t uplcom_probe;
+static device_attach_t uplcom_attach;
+static device_detach_t uplcom_detach;
+static void uplcom_free_softc(struct uplcom_softc *);
+
+static usb_callback_t uplcom_intr_callback;
+static usb_callback_t uplcom_write_callback;
+static usb_callback_t uplcom_read_callback;
+
+static const struct usb_config uplcom_config_data[UPLCOM_N_TRANSFER] = {
+ [UPLCOM_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UPLCOM_BULK_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &uplcom_write_callback,
+ .if_index = 0,
+ },
+
+ [UPLCOM_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UPLCOM_BULK_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &uplcom_read_callback,
+ .if_index = 0,
+ },
+
+ [UPLCOM_INTR_DT_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &uplcom_intr_callback,
+ .if_index = 1,
+ },
+};
+
+static struct ucom_callback uplcom_callback = {
+ .ucom_cfg_get_status = &uplcom_cfg_get_status,
+ .ucom_cfg_set_dtr = &uplcom_cfg_set_dtr,
+ .ucom_cfg_set_rts = &uplcom_cfg_set_rts,
+ .ucom_cfg_set_break = &uplcom_cfg_set_break,
+ .ucom_cfg_param = &uplcom_cfg_param,
+ .ucom_pre_param = &uplcom_pre_param,
+ .ucom_start_read = &uplcom_start_read,
+ .ucom_stop_read = &uplcom_stop_read,
+ .ucom_start_write = &uplcom_start_write,
+ .ucom_stop_write = &uplcom_stop_write,
+ .ucom_poll = &uplcom_poll,
+ .ucom_free = &uplcom_free,
+};
+
+#define UPLCOM_DEV(v,p) \
+ { USB_VENDOR(USB_VENDOR_##v), USB_PRODUCT(USB_PRODUCT_##v##_##p) }
+
+static const STRUCT_USB_HOST_ID uplcom_devs[] = {
+ UPLCOM_DEV(ACERP, S81), /* BenQ S81 phone */
+ UPLCOM_DEV(ADLINK, ND6530), /* ADLINK ND-6530 USB-Serial */
+ UPLCOM_DEV(ALCATEL, OT535), /* Alcatel One Touch 535/735 */
+ UPLCOM_DEV(ALCOR, AU9720), /* Alcor AU9720 USB 2.0-RS232 */
+ UPLCOM_DEV(ANCHOR, SERIAL), /* Anchor Serial adapter */
+ UPLCOM_DEV(ATEN, UC232A), /* PLANEX USB-RS232 URS-03 */
+ UPLCOM_DEV(ATEN, UC232B), /* Prolific USB-RS232 Controller D */
+ UPLCOM_DEV(BELKIN, F5U257), /* Belkin F5U257 USB to Serial */
+ UPLCOM_DEV(COREGA, CGUSBRS232R), /* Corega CG-USBRS232R */
+ UPLCOM_DEV(EPSON, CRESSI_EDY), /* Cressi Edy diving computer */
+ UPLCOM_DEV(EPSON, N2ITION3), /* Zeagle N2iTion3 diving computer */
+ UPLCOM_DEV(ELECOM, UCSGT), /* ELECOM UC-SGT Serial Adapter */
+ UPLCOM_DEV(ELECOM, UCSGT0), /* ELECOM UC-SGT Serial Adapter */
+ UPLCOM_DEV(HAL, IMR001), /* HAL Corporation Crossam2+USB */
+ UPLCOM_DEV(HP, LD220), /* HP LD220 POS Display */
+ UPLCOM_DEV(IODATA, USBRSAQ), /* I/O DATA USB-RSAQ */
+ UPLCOM_DEV(IODATA, USBRSAQ5), /* I/O DATA USB-RSAQ5 */
+ UPLCOM_DEV(ITEGNO, WM1080A), /* iTegno WM1080A GSM/GFPRS modem */
+ UPLCOM_DEV(ITEGNO, WM2080A), /* iTegno WM2080A CDMA modem */
+ UPLCOM_DEV(LEADTEK, 9531), /* Leadtek 9531 GPS */
+ UPLCOM_DEV(MICROSOFT, 700WX), /* Microsoft Palm 700WX */
+ UPLCOM_DEV(MOBILEACTION, MA620), /* Mobile Action MA-620 Infrared Adapter */
+ UPLCOM_DEV(NETINDEX, WS002IN), /* Willcom W-S002IN */
+ UPLCOM_DEV(NOKIA2, CA42), /* Nokia CA-42 cable */
+ UPLCOM_DEV(OTI, DKU5), /* OTI DKU-5 cable */
+ UPLCOM_DEV(PANASONIC, TYTP50P6S), /* Panasonic TY-TP50P6-S flat screen */
+ UPLCOM_DEV(PLX, CA42), /* PLX CA-42 clone cable */
+ UPLCOM_DEV(PROLIFIC, ALLTRONIX_GPRS), /* Alltronix ACM003U00 modem */
+ UPLCOM_DEV(PROLIFIC, ALDIGA_AL11U), /* AlDiga AL-11U modem */
+ UPLCOM_DEV(PROLIFIC, DCU11), /* DCU-11 Phone Cable */
+ UPLCOM_DEV(PROLIFIC, HCR331), /* HCR331 Card Reader */
+ UPLCOM_DEV(PROLIFIC, MICROMAX_610U), /* Micromax 610U modem */
+ UPLCOM_DEV(PROLIFIC, MOTOROLA), /* Motorola cable */
+ UPLCOM_DEV(PROLIFIC, PHAROS), /* Prolific Pharos */
+ UPLCOM_DEV(PROLIFIC, PL2303), /* Generic adapter */
+ UPLCOM_DEV(PROLIFIC, PL2303GC), /* Generic adapter (PL2303HXN, type GC) */
+ UPLCOM_DEV(PROLIFIC, PL2303GB), /* Generic adapter (PL2303HXN, type GB) */
+ UPLCOM_DEV(PROLIFIC, PL2303GT), /* Generic adapter (PL2303HXN, type GT) */
+ UPLCOM_DEV(PROLIFIC, PL2303GL), /* Generic adapter (PL2303HXN, type GL) */
+ UPLCOM_DEV(PROLIFIC, PL2303GE), /* Generic adapter (PL2303HXN, type GE) */
+ UPLCOM_DEV(PROLIFIC, PL2303GS), /* Generic adapter (PL2303HXN, type GS) */
+ UPLCOM_DEV(PROLIFIC, RSAQ2), /* I/O DATA USB-RSAQ2 */
+ UPLCOM_DEV(PROLIFIC, RSAQ3), /* I/O DATA USB-RSAQ3 */
+ UPLCOM_DEV(PROLIFIC, UIC_MSR206), /* UIC MSR206 Card Reader */
+ UPLCOM_DEV(PROLIFIC2, PL2303), /* Prolific adapter */
+ UPLCOM_DEV(RADIOSHACK, USBCABLE), /* Radio Shack USB Adapter */
+ UPLCOM_DEV(RATOC, REXUSB60), /* RATOC REX-USB60 */
+ UPLCOM_DEV(SAGEM, USBSERIAL), /* Sagem USB-Serial Controller */
+ UPLCOM_DEV(SAMSUNG, I330), /* Samsung I330 phone cradle */
+ UPLCOM_DEV(SANWA, KB_USB2), /* Sanwa KB-USB2 Multimeter cable */
+ UPLCOM_DEV(SIEMENS3, EF81), /* Siemens EF81 */
+ UPLCOM_DEV(SIEMENS3, SX1), /* Siemens SX1 */
+ UPLCOM_DEV(SIEMENS3, X65), /* Siemens X65 */
+ UPLCOM_DEV(SIEMENS3, X75), /* Siemens X75 */
+ UPLCOM_DEV(SITECOM, SERIAL), /* Sitecom USB to Serial */
+ UPLCOM_DEV(SMART, PL2303), /* SMART Technologies USB to Serial */
+ UPLCOM_DEV(SONY, QN3), /* Sony QN3 phone cable */
+ UPLCOM_DEV(SONYERICSSON, DATAPILOT), /* Sony Ericsson Datapilot */
+ UPLCOM_DEV(SONYERICSSON, DCU10), /* Sony Ericsson DCU-10 Cable */
+ UPLCOM_DEV(SOURCENEXT, KEIKAI8), /* SOURCENEXT KeikaiDenwa 8 */
+ UPLCOM_DEV(SOURCENEXT, KEIKAI8_CHG), /* SOURCENEXT KeikaiDenwa 8 with charger */
+ UPLCOM_DEV(SPEEDDRAGON, MS3303H), /* Speed Dragon USB-Serial */
+ UPLCOM_DEV(SYNTECH, CPT8001C), /* Syntech CPT-8001C Barcode scanner */
+ UPLCOM_DEV(TDK, UHA6400), /* TDK USB-PHS Adapter UHA6400 */
+ UPLCOM_DEV(TDK, UPA9664), /* TDK USB-PHS Adapter UPA9664 */
+ UPLCOM_DEV(TRIPPLITE, U209), /* Tripp-Lite U209-000-R USB to Serial */
+ UPLCOM_DEV(YCCABLE, PL2303), /* YC Cable USB-Serial */
+};
+#undef UPLCOM_DEV
+
+static device_method_t uplcom_methods[] = {
+ DEVMETHOD(device_probe, uplcom_probe),
+ DEVMETHOD(device_attach, uplcom_attach),
+ DEVMETHOD(device_detach, uplcom_detach),
+ DEVMETHOD_END
+};
+
+static driver_t uplcom_driver = {
+ .name = "uplcom",
+ .methods = uplcom_methods,
+ .size = sizeof(struct uplcom_softc),
+};
+
+DRIVER_MODULE(uplcom, uhub, uplcom_driver, NULL, NULL);
+MODULE_DEPEND(uplcom, ucom, 1, 1, 1);
+MODULE_DEPEND(uplcom, usb, 1, 1, 1);
+MODULE_VERSION(uplcom, UPLCOM_MODVER);
+USB_PNP_HOST_INFO(uplcom_devs);
+
+static int
+uplcom_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ DPRINTFN(11, "\n");
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != UPLCOM_CONFIG_INDEX) {
+ return (ENXIO);
+ }
+ if (uaa->info.bIfaceIndex != UPLCOM_IFACE_INDEX) {
+ return (ENXIO);
+ }
+ return (usbd_lookup_id_by_uaa(uplcom_devs, sizeof(uplcom_devs), uaa));
+}
+
+static int
+uplcom_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct uplcom_softc *sc = device_get_softc(dev);
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *id;
+ struct usb_device_descriptor *dd;
+ int error;
+
+ struct usb_device_request req;
+ usb_error_t err;
+ uint8_t buf[4];
+
+ DPRINTFN(11, "\n");
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "uplcom", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ DPRINTF("sc = %p\n", sc);
+
+ sc->sc_udev = uaa->device;
+
+ dd = usbd_get_device_descriptor(sc->sc_udev);
+
+ switch (UGETW(dd->bcdDevice)) {
+ case 0x0300:
+ sc->sc_chiptype = TYPE_PL2303HX;
+ /* or TA, that is HX with external crystal */
+ break;
+ case 0x0400:
+ sc->sc_chiptype = TYPE_PL2303HXD;
+ /* or EA, that is HXD with ESD protection */
+ /* or RA, that has internal voltage level converter that works only up to 1Mbaud (!) */
+ break;
+ case 0x0500:
+ sc->sc_chiptype = TYPE_PL2303HXD;
+ /* in fact it's TB, that is HXD with external crystal */
+ break;
+ default:
+ /* NOTE: I have no info about the bcdDevice for the base PL2303 (up to 1.2Mbaud,
+ only fixed rates) and for PL2303SA (8-pin chip, up to 115200 baud */
+ /* Determine the chip type. This algorithm is taken from Linux. */
+ if (dd->bDeviceClass == 0x02)
+ sc->sc_chiptype = TYPE_PL2303;
+ else if (dd->bMaxPacketSize == 0x40)
+ sc->sc_chiptype = TYPE_PL2303HX;
+ else
+ sc->sc_chiptype = TYPE_PL2303;
+ break;
+ }
+
+ /*
+ * The new chip revision PL2303HXN is only compatible with the new
+ * UPLCOM_SET_REQUEST_PL2303HXN command. Issuing the old command
+ * UPLCOM_SET_REQUEST to the new chip raises an error. Thus, PL2303HX
+ * and PL2303HXN can be distinguished by issuing an old-style request
+ * (on a status register) to the new chip and checking the error.
+ */
+ if (sc->sc_chiptype == TYPE_PL2303HX) {
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = UPLCOM_SET_REQUEST;
+ USETW(req.wValue, UPLCOM_STATUS_REG_PL2303HX);
+ req.wIndex[0] = sc->sc_data_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 1);
+ err = usbd_do_request(sc->sc_udev, NULL, &req, buf);
+ if (err)
+ sc->sc_chiptype = TYPE_PL2303HXN;
+ }
+
+ switch (sc->sc_chiptype) {
+ case TYPE_PL2303:
+ DPRINTF("chiptype: 2303\n");
+ break;
+ case TYPE_PL2303HX:
+ DPRINTF("chiptype: 2303HX/TA\n");
+ break;
+ case TYPE_PL2303HXN:
+ DPRINTF("chiptype: 2303HXN\n");
+ break;
+ case TYPE_PL2303HXD:
+ DPRINTF("chiptype: 2303HXD/TB/RA/EA\n");
+ break;
+ default:
+ DPRINTF("chiptype: unknown %d\n", sc->sc_chiptype);
+ break;
+ }
+
+ /*
+ * 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) |
+ */
+
+ sc->sc_ctrl_iface_no = uaa->info.bIfaceNum;
+ sc->sc_iface_index[1] = UPLCOM_IFACE_INDEX;
+
+ iface = usbd_get_iface(uaa->device, UPLCOM_SECOND_IFACE_INDEX);
+ if (iface) {
+ id = usbd_get_interface_descriptor(iface);
+ if (id == NULL) {
+ device_printf(dev, "no interface descriptor (2)\n");
+ goto detach;
+ }
+ sc->sc_data_iface_no = id->bInterfaceNumber;
+ sc->sc_iface_index[0] = UPLCOM_SECOND_IFACE_INDEX;
+ usbd_set_parent_iface(uaa->device,
+ UPLCOM_SECOND_IFACE_INDEX, uaa->info.bIfaceIndex);
+ } else {
+ sc->sc_data_iface_no = sc->sc_ctrl_iface_no;
+ sc->sc_iface_index[0] = UPLCOM_IFACE_INDEX;
+ }
+
+ error = usbd_transfer_setup(uaa->device,
+ sc->sc_iface_index, sc->sc_xfer, uplcom_config_data,
+ UPLCOM_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error) {
+ DPRINTF("one or more missing USB endpoints, "
+ "error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+ error = uplcom_reset(sc, uaa->device);
+ if (error) {
+ device_printf(dev, "reset failed, error=%s\n",
+ usbd_errstr(error));
+ goto detach;
+ }
+
+ if (sc->sc_chiptype == TYPE_PL2303) {
+ /* HX variants seem to lock up after a clear stall request. */
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_WR]);
+ usbd_xfer_set_stall(sc->sc_xfer[UPLCOM_BULK_DT_RD]);
+ mtx_unlock(&sc->sc_mtx);
+ } else if (sc->sc_chiptype == TYPE_PL2303HX ||
+ sc->sc_chiptype == TYPE_PL2303HXD) {
+ /* reset upstream data pipes */
+ if (uplcom_pl2303_do(sc->sc_udev, UT_WRITE_VENDOR_DEVICE,
+ UPLCOM_SET_REQUEST, 8, 0, 0) ||
+ uplcom_pl2303_do(sc->sc_udev, UT_WRITE_VENDOR_DEVICE,
+ UPLCOM_SET_REQUEST, 9, 0, 0)) {
+ goto detach;
+ }
+ } else if (sc->sc_chiptype == TYPE_PL2303HXN) {
+ /* reset upstream data pipes */
+ if (uplcom_pl2303_do(sc->sc_udev, UT_WRITE_VENDOR_DEVICE,
+ UPLCOM_SET_REQUEST_PL2303HXN, 0x07, 0x03, 0)) {
+ goto detach;
+ }
+ }
+
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &uplcom_callback, &sc->sc_mtx);
+ if (error) {
+ goto detach;
+ }
+ /*
+ * do the initialization during attach so that the system does not
+ * sleep during open:
+ */
+ if (uplcom_pl2303_init(uaa->device, sc->sc_chiptype)) {
+ device_printf(dev, "init failed\n");
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ return (0);
+
+detach:
+ uplcom_detach(dev);
+ return (ENXIO);
+}
+
+static int
+uplcom_detach(device_t dev)
+{
+ struct uplcom_softc *sc = device_get_softc(dev);
+
+ DPRINTF("sc=%p\n", sc);
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, UPLCOM_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ uplcom_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(uplcom);
+
+static void
+uplcom_free_softc(struct uplcom_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+uplcom_free(struct ucom_softc *ucom)
+{
+ uplcom_free_softc(ucom->sc_parent);
+}
+
+static usb_error_t
+uplcom_reset(struct uplcom_softc *sc, struct usb_device *udev)
+{
+ struct usb_device_request req;
+
+ if (sc->sc_chiptype == TYPE_PL2303HXN) {
+ /* PL2303HXN doesn't need this reset sequence */
+ return (0);
+ }
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = UPLCOM_SET_REQUEST;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_data_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ return (usbd_do_request(udev, NULL, &req, NULL));
+}
+
+static usb_error_t
+uplcom_pl2303_do(struct usb_device *udev, uint8_t req_type, uint8_t request,
+ uint16_t value, uint16_t index, uint16_t length)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+ uint8_t buf[4];
+
+ req.bmRequestType = req_type;
+ req.bRequest = request;
+ USETW(req.wValue, value);
+ USETW(req.wIndex, index);
+ USETW(req.wLength, length);
+
+ err = usbd_do_request(udev, NULL, &req, buf);
+ if (err) {
+ DPRINTF("error=%s\n", usbd_errstr(err));
+ return (1);
+ }
+ return (0);
+}
+
+static int
+uplcom_pl2303_init(struct usb_device *udev, uint8_t chiptype)
+{
+ int err;
+
+ if (chiptype == TYPE_PL2303HXN) {
+ /* PL2303HXN doesn't need this initialization sequence */
+ return (0);
+ }
+
+ if (uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1)
+ || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 0, 0)
+ || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1)
+ || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 1)
+ || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1)
+ || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x0404, 1, 0)
+ || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8484, 0, 1)
+ || uplcom_pl2303_do(udev, UT_READ_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0x8383, 0, 1)
+ || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 0, 1, 0)
+ || uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 1, 0, 0))
+ return (EIO);
+
+ if (chiptype != TYPE_PL2303)
+ err = uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x44, 0);
+ else
+ err = uplcom_pl2303_do(udev, UT_WRITE_VENDOR_DEVICE, UPLCOM_SET_REQUEST, 2, 0x24, 0);
+ if (err)
+ return (EIO);
+
+ return (0);
+}
+
+static void
+uplcom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uplcom_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ if (onoff)
+ sc->sc_line |= UCDC_LINE_DTR;
+ else
+ sc->sc_line &= ~UCDC_LINE_DTR;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
+ USETW(req.wValue, sc->sc_line);
+ req.wIndex[0] = sc->sc_data_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+static void
+uplcom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uplcom_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ if (onoff)
+ sc->sc_line |= UCDC_LINE_RTS;
+ else
+ sc->sc_line &= ~UCDC_LINE_RTS;
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_CONTROL_LINE_STATE;
+ USETW(req.wValue, sc->sc_line);
+ req.wIndex[0] = sc->sc_data_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+static void
+uplcom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uplcom_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+ uint16_t temp;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ temp = (onoff ? UCDC_BREAK_ON : UCDC_BREAK_OFF);
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SEND_BREAK;
+ USETW(req.wValue, temp);
+ req.wIndex[0] = sc->sc_data_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+}
+
+/*
+ * NOTE: These baud rates are officially supported, they can be written
+ * directly into dwDTERate register.
+ *
+ * Free baudrate setting is not supported by the base PL2303, and on
+ * other models it requires writing a divisor value to dwDTERate instead
+ * of the raw baudrate. The formula for divisor calculation is not published
+ * by the vendor, so it is speculative, though the official product homepage
+ * refers to the Linux module source as a reference implementation.
+ */
+static const uint32_t uplcom_rates[] = {
+ /*
+ * Basic 'standard' speed rates, supported by all models
+ * NOTE: 900 and 56000 actually works as well
+ */
+ 75, 150, 300, 600, 900, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400,
+ 19200, 28800, 38400, 56000, 57600, 115200,
+ /*
+ * Advanced speed rates up to 6Mbs, supported by HX/TA and HXD/TB/EA/RA
+ * NOTE: regardless of the spec, 256000 does not work
+ */
+ 128000, 134400, 161280, 201600, 230400, 268800, 403200, 460800, 614400,
+ 806400, 921600, 1228800, 2457600, 3000000, 6000000,
+ /*
+ * Advanced speed rates up to 12, supported by HXD/TB/EA/RA
+ */
+ 12000000
+};
+
+#define N_UPLCOM_RATES nitems(uplcom_rates)
+
+static int
+uplcom_baud_supported(unsigned speed)
+{
+ int i;
+ for (i = 0; i < N_UPLCOM_RATES; i++) {
+ if (uplcom_rates[i] == speed)
+ return 1;
+ }
+ return 0;
+}
+
+static int
+uplcom_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct uplcom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("\n");
+
+ /**
+ * Check requested baud rate.
+ *
+ * The PL2303 can only set specific baud rates, up to 1228800 baud.
+ * The PL2303HX can set any baud rate up to 6Mb.
+ * The PL2303HX rev. D and PL2303HXN can set any baud rate up to 12Mb.
+ *
+ */
+
+ /* accept raw divisor data, if someone wants to do the math in user domain */
+ if (t->c_ospeed & 0x80000000)
+ return 0;
+ switch (sc->sc_chiptype) {
+ case TYPE_PL2303HXN:
+ if (t->c_ospeed <= 12000000)
+ return (0);
+ break;
+ case TYPE_PL2303HXD:
+ if (t->c_ospeed <= 12000000)
+ return (0);
+ break;
+ case TYPE_PL2303HX:
+ if (t->c_ospeed <= 6000000)
+ return (0);
+ break;
+ default:
+ if (uplcom_baud_supported(t->c_ospeed))
+ return (0);
+ break;
+ }
+
+ DPRINTF("uplcom_param: bad baud rate (%d)\n", t->c_ospeed);
+ return (EIO);
+}
+
+static unsigned
+uplcom_encode_baud_rate_divisor(uint8_t *buf, unsigned baud)
+{
+ unsigned baseline, mantissa, exponent;
+
+ /* Determine the baud rate divisor. This algorithm is taken from Linux. */
+ /*
+ * Apparently the formula is:
+ * baudrate = baseline / (mantissa * 4^exponent)
+ * where
+ * mantissa = buf[8:0]
+ * exponent = buf[11:9]
+ */
+ if (baud == 0)
+ baud = 1;
+ baseline = 383385600;
+ mantissa = baseline / baud;
+ if (mantissa == 0)
+ mantissa = 1;
+ exponent = 0;
+ while (mantissa >= 512) {
+ if (exponent < 7) {
+ mantissa >>= 2; /* divide by 4 */
+ exponent++;
+ } else {
+ /* Exponent is maxed. Trim mantissa and leave. This gives approx. 45.8 baud */
+ mantissa = 511;
+ break;
+ }
+ }
+
+ buf[3] = 0x80;
+ buf[2] = 0;
+ buf[1] = exponent << 1 | mantissa >> 8;
+ buf[0] = mantissa & 0xff;
+
+ /* Calculate and return the exact baud rate. */
+ baud = (baseline / mantissa) >> (exponent << 1);
+ DPRINTF("real baud rate will be %u\n", baud);
+
+ return baud;
+}
+static void
+uplcom_cfg_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct uplcom_softc *sc = ucom->sc_parent;
+ struct usb_cdc_line_state ls;
+ struct usb_device_request req;
+
+ DPRINTF("sc = %p\n", sc);
+
+ memset(&ls, 0, sizeof(ls));
+
+ /*
+ * NOTE: If unsupported baud rates are set directly, the PL2303* uses 9600 baud.
+ */
+ if ((t->c_ospeed & 0x80000000) || uplcom_baud_supported(t->c_ospeed))
+ USETDW(ls.dwDTERate, t->c_ospeed);
+ else
+ t->c_ospeed = uplcom_encode_baud_rate_divisor((uint8_t*)&ls.dwDTERate, t->c_ospeed);
+
+ if (t->c_cflag & CSTOPB) {
+ if ((t->c_cflag & CSIZE) == CS5) {
+ /*
+ * NOTE: Comply with "real" UARTs / RS232:
+ * use 1.5 instead of 2 stop bits with 5 data bits
+ */
+ ls.bCharFormat = UCDC_STOP_BIT_1_5;
+ } else {
+ ls.bCharFormat = UCDC_STOP_BIT_2;
+ }
+ } else {
+ ls.bCharFormat = UCDC_STOP_BIT_1;
+ }
+
+ if (t->c_cflag & PARENB) {
+ if (t->c_cflag & PARODD) {
+ ls.bParityType = UCDC_PARITY_ODD;
+ } else {
+ ls.bParityType = UCDC_PARITY_EVEN;
+ }
+ } else {
+ ls.bParityType = UCDC_PARITY_NONE;
+ }
+
+ switch (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;
+ }
+
+ DPRINTF("rate=0x%08x fmt=%d parity=%d bits=%d\n",
+ UGETDW(ls.dwDTERate), ls.bCharFormat,
+ ls.bParityType, ls.bDataBits);
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UCDC_SET_LINE_CODING;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_data_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, UCDC_LINE_STATE_LENGTH);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, &ls, 0, 1000);
+
+ if (t->c_cflag & CRTSCTS) {
+ DPRINTF("crtscts = on\n");
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ if (sc->sc_chiptype == TYPE_PL2303HXN) {
+ req.bRequest = UPLCOM_SET_REQUEST_PL2303HXN;
+ USETW(req.wValue, UPLCOM_CRTSCTS_REG_PL2303HXN);
+ USETW(req.wIndex, UPLCOM_SET_CRTSCTS_PL2303HXN);
+ } else {
+ req.bRequest = UPLCOM_SET_REQUEST;
+ USETW(req.wValue, 0);
+ if (sc->sc_chiptype != TYPE_PL2303)
+ USETW(req.wIndex, UPLCOM_SET_CRTSCTS_PL2303X);
+ else
+ USETW(req.wIndex, UPLCOM_SET_CRTSCTS);
+ }
+ USETW(req.wLength, 0);
+
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+ } else {
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ if (sc->sc_chiptype == TYPE_PL2303HXN) {
+ req.bRequest = UPLCOM_SET_REQUEST_PL2303HXN;
+ USETW(req.wValue, UPLCOM_CRTSCTS_REG_PL2303HXN);
+ USETW(req.wIndex, UPLCOM_CLEAR_CRTSCTS_PL2303HXN);
+ }
+ else {
+ req.bRequest = UPLCOM_SET_REQUEST;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, 0);
+ }
+ USETW(req.wLength, 0);
+ ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+ }
+}
+
+static void
+uplcom_start_read(struct ucom_softc *ucom)
+{
+ struct uplcom_softc *sc = ucom->sc_parent;
+
+ /* start interrupt endpoint */
+ usbd_transfer_start(sc->sc_xfer[UPLCOM_INTR_DT_RD]);
+
+ /* start read endpoint */
+ usbd_transfer_start(sc->sc_xfer[UPLCOM_BULK_DT_RD]);
+}
+
+static void
+uplcom_stop_read(struct ucom_softc *ucom)
+{
+ struct uplcom_softc *sc = ucom->sc_parent;
+
+ /* stop interrupt endpoint */
+ usbd_transfer_stop(sc->sc_xfer[UPLCOM_INTR_DT_RD]);
+
+ /* stop read endpoint */
+ usbd_transfer_stop(sc->sc_xfer[UPLCOM_BULK_DT_RD]);
+}
+
+static void
+uplcom_start_write(struct ucom_softc *ucom)
+{
+ struct uplcom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UPLCOM_BULK_DT_WR]);
+}
+
+static void
+uplcom_stop_write(struct ucom_softc *ucom)
+{
+ struct uplcom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UPLCOM_BULK_DT_WR]);
+}
+
+static void
+uplcom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct uplcom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("\n");
+
+ *lsr = sc->sc_lsr;
+ *msr = sc->sc_msr;
+}
+
+static void
+uplcom_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uplcom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint8_t buf[9];
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ DPRINTF("actlen = %u\n", actlen);
+
+ if (actlen >= 9) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, buf, sizeof(buf));
+
+ DPRINTF("status = 0x%02x\n", buf[UPLCOM_STATE_INDEX]);
+
+ sc->sc_lsr = 0;
+ sc->sc_msr = 0;
+
+ if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_CTS) {
+ sc->sc_msr |= SER_CTS;
+ }
+ if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_OVERRUN_ERROR) {
+ sc->sc_lsr |= ULSR_OE;
+ }
+ if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_PARITY_ERROR) {
+ sc->sc_lsr |= ULSR_PE;
+ }
+ if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_FRAME_ERROR) {
+ sc->sc_lsr |= ULSR_FE;
+ }
+ if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_RING) {
+ sc->sc_msr |= SER_RI;
+ }
+ if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_BREAK_ERROR) {
+ sc->sc_lsr |= ULSR_BI;
+ }
+ if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_DSR) {
+ sc->sc_msr |= SER_DSR;
+ }
+ if (buf[UPLCOM_STATE_INDEX] & RSAQ_STATUS_DCD) {
+ sc->sc_msr |= SER_DCD;
+ }
+ ucom_status_change(&sc->sc_ucom);
+ }
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uplcom_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uplcom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(&sc->sc_ucom, pc, 0,
+ UPLCOM_BULK_BUF_SIZE, &actlen)) {
+ DPRINTF("actlen = %d\n", actlen);
+
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uplcom_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uplcom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uplcom_poll(struct ucom_softc *ucom)
+{
+ struct uplcom_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_xfer, UPLCOM_N_TRANSFER);
+}
diff --git a/sys/dev/usb/serial/usb_serial.c b/sys/dev/usb/serial/usb_serial.c
new file mode 100644
index 000000000000..e62bfdb8ff1d
--- /dev/null
+++ b/sys/dev/usb/serial/usb_serial.c
@@ -0,0 +1,1846 @@
+/* $NetBSD: ucom.c,v 1.40 2001/11/13 06:24:54 lukem Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ */
+
+/*-
+ * 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.
+ *
+ * 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/cons.h>
+
+#include <dev/uart/uart_ppstypes.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+
+#define USB_DEBUG_VAR ucom_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#include "opt_gdb.h"
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ucom, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB ucom");
+
+static int ucom_pps_mode;
+
+SYSCTL_INT(_hw_usb_ucom, OID_AUTO, pps_mode, CTLFLAG_RWTUN,
+ &ucom_pps_mode, 0,
+ "pulse capture mode: 0/1/2=disabled/CTS/DCD; add 0x10 to invert");
+
+static int ucom_device_mode_console = 1;
+
+SYSCTL_INT(_hw_usb_ucom, OID_AUTO, device_mode_console, CTLFLAG_RW,
+ &ucom_device_mode_console, 0,
+ "set to 1 to mark terminals as consoles when in device mode");
+
+#ifdef USB_DEBUG
+static int ucom_debug = 0;
+
+SYSCTL_INT(_hw_usb_ucom, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &ucom_debug, 0, "ucom debug level");
+#endif
+
+#define UCOM_CONS_BUFSIZE 1024
+
+static uint8_t ucom_cons_rx_buf[UCOM_CONS_BUFSIZE];
+static uint8_t ucom_cons_tx_buf[UCOM_CONS_BUFSIZE];
+
+static unsigned ucom_cons_rx_low = 0;
+static unsigned ucom_cons_rx_high = 0;
+
+static unsigned ucom_cons_tx_low = 0;
+static unsigned ucom_cons_tx_high = 0;
+
+static int ucom_cons_unit = -1;
+static int ucom_cons_subunit = 0;
+static int ucom_cons_baud = 115200;
+static struct ucom_softc *ucom_cons_softc = NULL;
+
+SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_unit, CTLFLAG_RWTUN,
+ &ucom_cons_unit, 0, "console unit number");
+SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_subunit, CTLFLAG_RWTUN,
+ &ucom_cons_subunit, 0, "console subunit number");
+SYSCTL_INT(_hw_usb_ucom, OID_AUTO, cons_baud, CTLFLAG_RWTUN,
+ &ucom_cons_baud, 0, "console baud rate");
+
+static usb_proc_callback_t ucom_cfg_start_transfers;
+static usb_proc_callback_t ucom_cfg_open;
+static usb_proc_callback_t ucom_cfg_close;
+static usb_proc_callback_t ucom_cfg_line_state;
+static usb_proc_callback_t ucom_cfg_status_change;
+static usb_proc_callback_t ucom_cfg_param;
+
+static int ucom_unit_alloc(void);
+static void ucom_unit_free(int);
+static int ucom_attach_tty(struct ucom_super_softc *, struct ucom_softc *);
+static void ucom_detach_tty(struct ucom_super_softc *, struct ucom_softc *);
+static int ucom_queue_command(struct ucom_softc *,
+ usb_proc_callback_t *, struct termios *pt,
+ struct usb_proc_msg *t0, struct usb_proc_msg *t1, bool wait);
+static void ucom_shutdown(struct ucom_softc *);
+static void ucom_ring(struct ucom_softc *, uint8_t);
+static void ucom_break(struct ucom_softc *, uint8_t);
+static void ucom_dtr(struct ucom_softc *, uint8_t);
+static void ucom_rts(struct ucom_softc *, uint8_t);
+
+static tsw_open_t ucom_open;
+static tsw_close_t ucom_close;
+static tsw_ioctl_t ucom_ioctl;
+static tsw_modem_t ucom_modem;
+static tsw_param_t ucom_param;
+static tsw_outwakeup_t ucom_outwakeup;
+static tsw_inwakeup_t ucom_inwakeup;
+static tsw_free_t ucom_free;
+static tsw_busy_t ucom_busy;
+
+static struct ttydevsw ucom_class = {
+ .tsw_flags = TF_INITLOCK | TF_CALLOUT,
+ .tsw_open = ucom_open,
+ .tsw_close = ucom_close,
+ .tsw_outwakeup = ucom_outwakeup,
+ .tsw_inwakeup = ucom_inwakeup,
+ .tsw_ioctl = ucom_ioctl,
+ .tsw_param = ucom_param,
+ .tsw_modem = ucom_modem,
+ .tsw_free = ucom_free,
+ .tsw_busy = ucom_busy,
+};
+
+MODULE_DEPEND(ucom, usb, 1, 1, 1);
+MODULE_VERSION(ucom, 1);
+
+#define UCOM_UNIT_MAX 128 /* maximum number of units */
+#define UCOM_TTY_PREFIX "U"
+
+static struct unrhdr *ucom_unrhdr;
+static struct mtx ucom_mtx;
+static int ucom_close_refs;
+
+static void
+ucom_init(void *arg)
+{
+ DPRINTF("\n");
+ ucom_unrhdr = new_unrhdr(0, UCOM_UNIT_MAX - 1, NULL);
+ mtx_init(&ucom_mtx, "UCOM MTX", NULL, MTX_DEF);
+}
+SYSINIT(ucom_init, SI_SUB_KLD - 1, SI_ORDER_ANY, ucom_init, NULL);
+
+static void
+ucom_uninit(void *arg)
+{
+ struct unrhdr *hdr;
+ hdr = ucom_unrhdr;
+ ucom_unrhdr = NULL;
+
+ DPRINTF("\n");
+
+ if (hdr != NULL)
+ delete_unrhdr(hdr);
+
+ mtx_destroy(&ucom_mtx);
+}
+SYSUNINIT(ucom_uninit, SI_SUB_KLD - 3, SI_ORDER_ANY, ucom_uninit, NULL);
+
+/*
+ * Mark a unit number (the X in cuaUX) as in use.
+ *
+ * Note that devices using a different naming scheme (see ucom_tty_name()
+ * callback) still use this unit allocation.
+ */
+static int
+ucom_unit_alloc(void)
+{
+ int unit;
+
+ /* sanity checks */
+ if (ucom_unrhdr == NULL) {
+ DPRINTF("ucom_unrhdr is NULL\n");
+ return (-1);
+ }
+ unit = alloc_unr(ucom_unrhdr);
+ DPRINTF("unit %d is allocated\n", unit);
+ return (unit);
+}
+
+/*
+ * Mark the unit number as not in use.
+ */
+static void
+ucom_unit_free(int unit)
+{
+ /* sanity checks */
+ if (unit < 0 || unit >= UCOM_UNIT_MAX || ucom_unrhdr == NULL) {
+ DPRINTF("cannot free unit number\n");
+ return;
+ }
+ DPRINTF("unit %d is freed\n", unit);
+ free_unr(ucom_unrhdr, unit);
+}
+
+/*
+ * Setup a group of one or more serial ports.
+ *
+ * The mutex pointed to by "mtx" is applied before all
+ * callbacks are called back. Also "mtx" must be applied
+ * before calling into the ucom-layer!
+ */
+int
+ucom_attach(struct ucom_super_softc *ssc, struct ucom_softc *sc,
+ int subunits, void *parent,
+ const struct ucom_callback *callback, struct mtx *mtx)
+{
+ int subunit;
+ int error = 0;
+
+ if ((sc == NULL) ||
+ (subunits <= 0) ||
+ (callback == NULL) ||
+ (mtx == NULL)) {
+ return (EINVAL);
+ }
+
+ /* allocate a uniq unit number */
+ ssc->sc_unit = ucom_unit_alloc();
+ if (ssc->sc_unit == -1)
+ return (ENOMEM);
+
+ /* generate TTY name string */
+ snprintf(ssc->sc_ttyname, sizeof(ssc->sc_ttyname),
+ UCOM_TTY_PREFIX "%d", ssc->sc_unit);
+
+ /* create USB request handling process */
+ error = usb_proc_create(&ssc->sc_tq, mtx, "ucom", USB_PRI_MED);
+ if (error) {
+ ucom_unit_free(ssc->sc_unit);
+ return (error);
+ }
+ ssc->sc_subunits = subunits;
+ ssc->sc_flag = UCOM_FLAG_ATTACHED |
+ UCOM_FLAG_FREE_UNIT | (ssc->sc_flag & UCOM_FLAG_DEVICE_MODE);
+
+ if (callback->ucom_free == NULL)
+ ssc->sc_flag |= UCOM_FLAG_WAIT_REFS;
+
+ /* increment reference count */
+ ucom_ref(ssc);
+
+ for (subunit = 0; subunit < ssc->sc_subunits; subunit++) {
+ sc[subunit].sc_subunit = subunit;
+ sc[subunit].sc_super = ssc;
+ sc[subunit].sc_mtx = mtx;
+ sc[subunit].sc_parent = parent;
+ sc[subunit].sc_callback = callback;
+
+ error = ucom_attach_tty(ssc, &sc[subunit]);
+ if (error) {
+ ucom_detach(ssc, &sc[0]);
+ return (error);
+ }
+ /* increment reference count */
+ ucom_ref(ssc);
+
+ /* set subunit attached */
+ sc[subunit].sc_flag |= UCOM_FLAG_ATTACHED;
+ }
+
+ DPRINTF("tp = %p, unit = %d, subunits = %d\n",
+ sc->sc_tty, ssc->sc_unit, ssc->sc_subunits);
+
+ return (0);
+}
+
+/*
+ * The following function will do nothing if the structure pointed to
+ * by "ssc" and "sc" is zero or has already been detached.
+ */
+void
+ucom_detach(struct ucom_super_softc *ssc, struct ucom_softc *sc)
+{
+ int subunit;
+
+ if (!(ssc->sc_flag & UCOM_FLAG_ATTACHED))
+ return; /* not initialized */
+
+ if (ssc->sc_sysctl_ttyname != NULL) {
+ sysctl_remove_oid(ssc->sc_sysctl_ttyname, 1, 0);
+ ssc->sc_sysctl_ttyname = NULL;
+ }
+
+ if (ssc->sc_sysctl_ttyports != NULL) {
+ sysctl_remove_oid(ssc->sc_sysctl_ttyports, 1, 0);
+ ssc->sc_sysctl_ttyports = NULL;
+ }
+
+ usb_proc_drain(&ssc->sc_tq);
+
+ for (subunit = 0; subunit < ssc->sc_subunits; subunit++) {
+ if (sc[subunit].sc_flag & UCOM_FLAG_ATTACHED) {
+ ucom_detach_tty(ssc, &sc[subunit]);
+
+ /* avoid duplicate detach */
+ sc[subunit].sc_flag &= ~UCOM_FLAG_ATTACHED;
+ }
+ }
+ usb_proc_free(&ssc->sc_tq);
+
+ ucom_unref(ssc);
+
+ if (ssc->sc_flag & UCOM_FLAG_WAIT_REFS)
+ ucom_drain(ssc);
+
+ /* make sure we don't detach twice */
+ ssc->sc_flag &= ~UCOM_FLAG_ATTACHED;
+}
+
+void
+ucom_drain(struct ucom_super_softc *ssc)
+{
+ mtx_lock(&ucom_mtx);
+ while (ssc->sc_refs > 0) {
+ printf("ucom: Waiting for a TTY device to close.\n");
+ usb_pause_mtx(&ucom_mtx, hz);
+ }
+ mtx_unlock(&ucom_mtx);
+}
+
+void
+ucom_drain_all(void *arg)
+{
+ mtx_lock(&ucom_mtx);
+ while (ucom_close_refs > 0) {
+ printf("ucom: Waiting for all detached TTY "
+ "devices to have open fds closed.\n");
+ usb_pause_mtx(&ucom_mtx, hz);
+ }
+ mtx_unlock(&ucom_mtx);
+}
+
+static cn_probe_t ucom_cnprobe;
+static cn_init_t ucom_cninit;
+static cn_term_t ucom_cnterm;
+static cn_getc_t ucom_cngetc;
+static cn_putc_t ucom_cnputc;
+static cn_grab_t ucom_cngrab;
+static cn_ungrab_t ucom_cnungrab;
+
+const struct consdev_ops ucom_cnops = {
+ .cn_probe = ucom_cnprobe,
+ .cn_init = ucom_cninit,
+ .cn_term = ucom_cnterm,
+ .cn_getc = ucom_cngetc,
+ .cn_putc = ucom_cnputc,
+ .cn_grab = ucom_cngrab,
+ .cn_ungrab = ucom_cnungrab,
+};
+
+static int
+ucom_attach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc)
+{
+ struct tty *tp;
+ char buf[32]; /* temporary TTY device name buffer */
+
+ tp = tty_alloc_mutex(&ucom_class, sc, sc->sc_mtx);
+ if (tp == NULL)
+ return (ENOMEM);
+
+ /* Check if the client has a custom TTY name */
+ buf[0] = '\0';
+ if (sc->sc_callback->ucom_tty_name) {
+ sc->sc_callback->ucom_tty_name(sc, buf,
+ sizeof(buf), ssc->sc_unit, sc->sc_subunit);
+ }
+ if (buf[0] == 0) {
+ /* Use default TTY name */
+ if (ssc->sc_subunits > 1) {
+ /* multiple modems in one */
+ snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u.%u",
+ ssc->sc_unit, sc->sc_subunit);
+ } else {
+ /* single modem */
+ snprintf(buf, sizeof(buf), UCOM_TTY_PREFIX "%u",
+ ssc->sc_unit);
+ }
+ }
+ tty_makedev(tp, NULL, "%s", buf);
+
+ sc->sc_tty = tp;
+
+ sc->sc_pps.ppscap = PPS_CAPTUREBOTH;
+ sc->sc_pps.driver_abi = PPS_ABI_VERSION;
+ sc->sc_pps.driver_mtx = sc->sc_mtx;
+ pps_init_abi(&sc->sc_pps);
+
+ DPRINTF("ttycreate: %s\n", buf);
+
+ /* Check if this device should be a console */
+ if ((ucom_cons_softc == NULL) &&
+ (ssc->sc_unit == ucom_cons_unit) &&
+ (sc->sc_subunit == ucom_cons_subunit)) {
+ DPRINTF("unit %d subunit %d is console",
+ ssc->sc_unit, sc->sc_subunit);
+
+ ucom_cons_softc = sc;
+
+ tty_init_console(tp, ucom_cons_baud);
+
+ UCOM_MTX_LOCK(ucom_cons_softc);
+ ucom_cons_rx_low = 0;
+ ucom_cons_rx_high = 0;
+ ucom_cons_tx_low = 0;
+ ucom_cons_tx_high = 0;
+ sc->sc_flag |= UCOM_FLAG_CONSOLE;
+ ucom_open(ucom_cons_softc->sc_tty);
+ ucom_param(ucom_cons_softc->sc_tty, &tp->t_termios_init_in);
+ UCOM_MTX_UNLOCK(ucom_cons_softc);
+ }
+
+ if ((ssc->sc_flag & UCOM_FLAG_DEVICE_MODE) != 0 &&
+ ucom_device_mode_console > 0 &&
+ ucom_cons_softc == NULL) {
+ struct consdev *cp;
+
+ cp = malloc(sizeof(struct consdev), M_USBDEV,
+ M_WAITOK|M_ZERO);
+ cp->cn_ops = &ucom_cnops;
+ cp->cn_arg = NULL;
+ cp->cn_pri = CN_NORMAL;
+ strlcpy(cp->cn_name, "tty", sizeof(cp->cn_name));
+ strlcat(cp->cn_name, buf, sizeof(cp->cn_name));
+
+ sc->sc_consdev = cp;
+
+ cnadd(cp);
+ }
+
+ return (0);
+}
+
+static void
+ucom_detach_tty(struct ucom_super_softc *ssc, struct ucom_softc *sc)
+{
+ struct tty *tp = sc->sc_tty;
+
+ DPRINTF("sc = %p, tp = %p\n", sc, sc->sc_tty);
+
+ if (sc->sc_consdev != NULL) {
+ cnremove(sc->sc_consdev);
+ free(sc->sc_consdev, M_USBDEV);
+ sc->sc_consdev = NULL;
+ }
+
+ if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
+ UCOM_MTX_LOCK(ucom_cons_softc);
+ ucom_close(ucom_cons_softc->sc_tty);
+ sc->sc_flag &= ~UCOM_FLAG_CONSOLE;
+ UCOM_MTX_UNLOCK(ucom_cons_softc);
+ ucom_cons_softc = NULL;
+ }
+
+ /* the config thread has been stopped when we get here */
+
+ UCOM_MTX_LOCK(sc);
+ sc->sc_flag |= UCOM_FLAG_GONE;
+ sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_LL_READY);
+ UCOM_MTX_UNLOCK(sc);
+
+ if (tp) {
+ mtx_lock(&ucom_mtx);
+ ucom_close_refs++;
+ mtx_unlock(&ucom_mtx);
+
+ tty_lock(tp);
+
+ ucom_close(tp); /* close, if any */
+
+ tty_rel_gone(tp);
+
+ UCOM_MTX_LOCK(sc);
+ /*
+ * make sure that read and write transfers are stopped
+ */
+ if (sc->sc_callback->ucom_stop_read)
+ (sc->sc_callback->ucom_stop_read) (sc);
+ if (sc->sc_callback->ucom_stop_write)
+ (sc->sc_callback->ucom_stop_write) (sc);
+ UCOM_MTX_UNLOCK(sc);
+ }
+}
+
+void
+ucom_set_pnpinfo_usb(struct ucom_super_softc *ssc, device_t dev)
+{
+ char buf[64];
+ uint8_t iface_index;
+ struct usb_attach_arg *uaa;
+
+ snprintf(buf, sizeof(buf), "ttyname=" UCOM_TTY_PREFIX
+ "%d ttyports=%d", ssc->sc_unit, ssc->sc_subunits);
+
+ /* Store the PNP info in the first interface for the device */
+ uaa = device_get_ivars(dev);
+ iface_index = uaa->info.bIfaceIndex;
+
+ if (usbd_set_pnpinfo(uaa->device, iface_index, buf) != 0)
+ device_printf(dev, "Could not set PNP info\n");
+
+ /*
+ * The following information is also replicated in the PNP-info
+ * string which is registered above:
+ */
+ if (ssc->sc_sysctl_ttyname == NULL) {
+ ssc->sc_sysctl_ttyname = SYSCTL_ADD_STRING(NULL,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
+ OID_AUTO, "ttyname", CTLFLAG_RD, ssc->sc_ttyname, 0,
+ "TTY device basename");
+ }
+ if (ssc->sc_sysctl_ttyports == NULL) {
+ ssc->sc_sysctl_ttyports = SYSCTL_ADD_INT(NULL,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
+ OID_AUTO, "ttyports", CTLFLAG_RD,
+ NULL, ssc->sc_subunits, "Number of ports");
+ }
+}
+
+void
+ucom_set_usb_mode(struct ucom_super_softc *ssc, enum usb_hc_mode usb_mode)
+{
+
+ switch (usb_mode) {
+ case USB_MODE_DEVICE:
+ ssc->sc_flag |= UCOM_FLAG_DEVICE_MODE;
+ break;
+ default:
+ ssc->sc_flag &= ~UCOM_FLAG_DEVICE_MODE;
+ break;
+ }
+}
+
+static void
+ucom_command_barrier_cb(struct usb_proc_msg *msg __unused)
+{
+ /* NOP */
+}
+
+/*
+ * ucom_command_barrier inserts a dummy task and waits for it so that we can be
+ * certain that previously enqueued tasks are finished before returning back to
+ * the tty layer.
+ */
+static int
+ucom_command_barrier(struct ucom_softc *sc)
+{
+ struct ucom_super_softc *ssc = sc->sc_super;
+ struct usb_proc_msg dummy = { .pm_callback = ucom_command_barrier_cb };
+ struct usb_proc_msg *task;
+ int error;
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ if (usb_proc_is_gone(&ssc->sc_tq)) {
+ DPRINTF("proc is gone\n");
+ return (ENXIO); /* nothing to do */
+ }
+
+ task = usb_proc_msignal(&ssc->sc_tq, &dummy, &dummy);
+ error = usb_proc_mwait_sig(&ssc->sc_tq, task, task);
+ if (error == 0 && sc->sc_tty != NULL && tty_gone(sc->sc_tty))
+ error = ENXIO;
+ return (error);
+}
+
+static int
+ucom_queue_command(struct ucom_softc *sc,
+ usb_proc_callback_t *fn, struct termios *pt,
+ struct usb_proc_msg *t0, struct usb_proc_msg *t1, bool wait)
+{
+ struct ucom_super_softc *ssc = sc->sc_super;
+ struct ucom_param_task *task;
+ int error;
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ if (usb_proc_is_gone(&ssc->sc_tq)) {
+ DPRINTF("proc is gone\n");
+ return (ENXIO); /* nothing to do */
+ }
+ /*
+ * NOTE: The task cannot get executed before we drop the
+ * "sc_mtx" mutex. It is safe to update fields in the message
+ * structure after that the message got queued.
+ */
+ task = (struct ucom_param_task *)
+ usb_proc_msignal(&ssc->sc_tq, t0, t1);
+
+ /* Setup callback and softc pointers */
+ task->hdr.pm_callback = fn;
+ task->sc = sc;
+
+ /*
+ * Make a copy of the termios. This field is only present if
+ * the "pt" field is not NULL.
+ */
+ if (pt != NULL)
+ task->termios_copy = *pt;
+
+ /*
+ * Closing or opening the device should be synchronous.
+ */
+ if (wait) {
+ error = usb_proc_mwait_sig(&ssc->sc_tq, t0, t1);
+
+ /* usb_proc_mwait_sig may have dropped the tty lock. */
+ if (error == 0 && sc->sc_tty != NULL && tty_gone(sc->sc_tty))
+ error = ENXIO;
+ } else {
+ error = 0;
+ }
+
+ /*
+ * In case of multiple configure requests,
+ * keep track of the last one!
+ */
+ if (fn == ucom_cfg_start_transfers)
+ sc->sc_last_start_xfer = &task->hdr;
+
+ return (error);
+}
+
+static void
+ucom_shutdown(struct ucom_softc *sc)
+{
+ struct tty *tp = sc->sc_tty;
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ DPRINTF("\n");
+
+ /*
+ * Hang up if necessary:
+ */
+ if (tp->t_termios.c_cflag & HUPCL) {
+ ucom_modem(tp, 0, SER_DTR);
+ }
+}
+
+/*
+ * Return values:
+ * 0: normal
+ * else: taskqueue is draining or gone
+ */
+uint8_t
+ucom_cfg_is_gone(struct ucom_softc *sc)
+{
+ struct ucom_super_softc *ssc = sc->sc_super;
+
+ return (usb_proc_is_gone(&ssc->sc_tq));
+}
+
+static void
+ucom_cfg_start_transfers(struct usb_proc_msg *_task)
+{
+ struct ucom_cfg_task *task =
+ (struct ucom_cfg_task *)_task;
+ struct ucom_softc *sc = task->sc;
+
+ if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
+ return;
+ }
+ if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
+ /* TTY device closed */
+ return;
+ }
+
+ if (_task == sc->sc_last_start_xfer)
+ sc->sc_flag |= UCOM_FLAG_GP_DATA;
+
+ if (sc->sc_callback->ucom_start_read) {
+ (sc->sc_callback->ucom_start_read) (sc);
+ }
+ if (sc->sc_callback->ucom_start_write) {
+ (sc->sc_callback->ucom_start_write) (sc);
+ }
+}
+
+static void
+ucom_start_transfers(struct ucom_softc *sc)
+{
+ if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
+ return;
+ }
+ /*
+ * Make sure that data transfers are started in both
+ * directions:
+ */
+ if (sc->sc_callback->ucom_start_read) {
+ (sc->sc_callback->ucom_start_read) (sc);
+ }
+ if (sc->sc_callback->ucom_start_write) {
+ (sc->sc_callback->ucom_start_write) (sc);
+ }
+}
+
+static void
+ucom_cfg_open(struct usb_proc_msg *_task)
+{
+ struct ucom_cfg_task *task =
+ (struct ucom_cfg_task *)_task;
+ struct ucom_softc *sc = task->sc;
+
+ DPRINTF("\n");
+
+ if (sc->sc_flag & UCOM_FLAG_LL_READY) {
+ /* already opened */
+
+ } else {
+ sc->sc_flag |= UCOM_FLAG_LL_READY;
+
+ if (sc->sc_callback->ucom_cfg_open) {
+ (sc->sc_callback->ucom_cfg_open) (sc);
+
+ /* wait a little */
+ usb_pause_mtx(sc->sc_mtx, hz / 10);
+ }
+ }
+}
+
+static int
+ucom_open(struct tty *tp)
+{
+ struct ucom_softc *sc = tty_softc(tp);
+ int error;
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ if (sc->sc_flag & UCOM_FLAG_GONE) {
+ return (ENXIO);
+ }
+ if (sc->sc_flag & UCOM_FLAG_HL_READY) {
+ /* already opened */
+ return (0);
+ }
+ DPRINTF("tp = %p\n", tp);
+
+ if (sc->sc_callback->ucom_pre_open) {
+ /*
+ * give the lower layer a chance to disallow TTY open, for
+ * example if the device is not present:
+ */
+ error = (sc->sc_callback->ucom_pre_open) (sc);
+ if (error != 0)
+ goto out;
+ }
+ sc->sc_flag |= UCOM_FLAG_HL_READY;
+
+ /* Disable transfers */
+ sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
+
+ sc->sc_lsr = 0;
+ sc->sc_msr = 0;
+ sc->sc_mcr = 0;
+
+ /* reset programmed line state */
+ sc->sc_pls_curr = 0;
+ sc->sc_pls_set = 0;
+ sc->sc_pls_clr = 0;
+
+ /* reset jitter buffer */
+ sc->sc_jitterbuf_in = 0;
+ sc->sc_jitterbuf_out = 0;
+
+ error = ucom_queue_command(sc, ucom_cfg_open, NULL,
+ &sc->sc_open_task[0].hdr,
+ &sc->sc_open_task[1].hdr, true);
+ if (error != 0)
+ goto out;
+
+ /*
+ * Queue transfer enable command last, we'll have a barrier later so we
+ * don't need to wait on this to complete specifically.
+ */
+ error = ucom_queue_command(sc, ucom_cfg_start_transfers, NULL,
+ &sc->sc_start_task[0].hdr,
+ &sc->sc_start_task[1].hdr, true);
+ if (error != 0)
+ goto out;
+
+ if (sc->sc_tty == NULL || (sc->sc_tty->t_termios.c_cflag & CNO_RTSDTR) == 0)
+ ucom_modem(tp, SER_DTR | SER_RTS, 0);
+
+ ucom_ring(sc, 0);
+
+ ucom_break(sc, 0);
+
+ ucom_status_change(sc);
+
+ error = ucom_command_barrier(sc);
+out:
+ return (error);
+}
+
+static void
+ucom_cfg_close(struct usb_proc_msg *_task)
+{
+ struct ucom_cfg_task *task =
+ (struct ucom_cfg_task *)_task;
+ struct ucom_softc *sc = task->sc;
+
+ DPRINTF("\n");
+
+ if (sc->sc_flag & UCOM_FLAG_LL_READY) {
+ sc->sc_flag &= ~UCOM_FLAG_LL_READY;
+ if (sc->sc_callback->ucom_cfg_close)
+ (sc->sc_callback->ucom_cfg_close) (sc);
+ } else {
+ /* already closed */
+ }
+}
+
+static void
+ucom_close(struct tty *tp)
+{
+ struct ucom_softc *sc = tty_softc(tp);
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ DPRINTF("tp=%p\n", tp);
+
+ if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
+ DPRINTF("tp=%p already closed\n", tp);
+ return;
+ }
+ ucom_shutdown(sc);
+
+ (void)ucom_queue_command(sc, ucom_cfg_close, NULL,
+ &sc->sc_close_task[0].hdr,
+ &sc->sc_close_task[1].hdr, true);
+
+ sc->sc_flag &= ~(UCOM_FLAG_HL_READY | UCOM_FLAG_RTS_IFLOW);
+
+ if (sc->sc_callback->ucom_stop_read) {
+ (sc->sc_callback->ucom_stop_read) (sc);
+ }
+}
+
+static void
+ucom_inwakeup(struct tty *tp)
+{
+ struct ucom_softc *sc = tty_softc(tp);
+ uint16_t pos;
+
+ if (sc == NULL)
+ return;
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ DPRINTF("tp=%p\n", tp);
+
+ if (ttydisc_can_bypass(tp) != 0 ||
+ (sc->sc_flag & UCOM_FLAG_HL_READY) == 0 ||
+ (sc->sc_flag & UCOM_FLAG_INWAKEUP) != 0) {
+ return;
+ }
+
+ /* prevent recursion */
+ sc->sc_flag |= UCOM_FLAG_INWAKEUP;
+
+ pos = sc->sc_jitterbuf_out;
+
+ while (sc->sc_jitterbuf_in != pos) {
+ int c;
+
+ c = (char)sc->sc_jitterbuf[pos];
+
+ if (ttydisc_rint(tp, c, 0) == -1)
+ break;
+ pos++;
+ if (pos >= UCOM_JITTERBUF_SIZE)
+ pos -= UCOM_JITTERBUF_SIZE;
+ }
+
+ sc->sc_jitterbuf_out = pos;
+
+ /* clear RTS in async fashion */
+ if ((sc->sc_jitterbuf_in == pos) &&
+ (sc->sc_flag & UCOM_FLAG_RTS_IFLOW))
+ ucom_rts(sc, 0);
+
+ sc->sc_flag &= ~UCOM_FLAG_INWAKEUP;
+}
+
+static int
+ucom_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td)
+{
+ struct ucom_softc *sc = tty_softc(tp);
+ int error;
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
+ return (EIO);
+ }
+ DPRINTF("cmd = 0x%08lx\n", cmd);
+
+ switch (cmd) {
+#if 0
+ case TIOCSRING:
+ ucom_ring(sc, 1);
+ error = 0;
+ break;
+ case TIOCCRING:
+ ucom_ring(sc, 0);
+ error = 0;
+ break;
+#endif
+ case TIOCSBRK:
+ ucom_break(sc, 1);
+ error = ucom_command_barrier(sc);
+ if (error == ENXIO)
+ error = ENODEV;
+ break;
+ case TIOCCBRK:
+ ucom_break(sc, 0);
+ error = ucom_command_barrier(sc);
+ if (error == ENXIO)
+ error = ENODEV;
+ break;
+ default:
+ if (sc->sc_callback->ucom_ioctl) {
+ error = (sc->sc_callback->ucom_ioctl)
+ (sc, cmd, data, 0, td);
+ } else {
+ error = ENOIOCTL;
+ }
+ if (error == ENOIOCTL)
+ error = pps_ioctl(cmd, data, &sc->sc_pps);
+ break;
+ }
+ return (error);
+}
+
+static int
+ucom_modem(struct tty *tp, int sigon, int sigoff)
+{
+ struct ucom_softc *sc = tty_softc(tp);
+ uint8_t onoff;
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
+ return (0);
+ }
+ if ((sigon == 0) && (sigoff == 0)) {
+ if (sc->sc_mcr & SER_DTR) {
+ sigon |= SER_DTR;
+ }
+ if (sc->sc_mcr & SER_RTS) {
+ sigon |= SER_RTS;
+ }
+ if (sc->sc_msr & SER_CTS) {
+ sigon |= SER_CTS;
+ }
+ if (sc->sc_msr & SER_DCD) {
+ sigon |= SER_DCD;
+ }
+ if (sc->sc_msr & SER_DSR) {
+ sigon |= SER_DSR;
+ }
+ if (sc->sc_msr & SER_RI) {
+ sigon |= SER_RI;
+ }
+ return (sigon);
+ }
+ if (sigon & SER_DTR) {
+ sc->sc_mcr |= SER_DTR;
+ }
+ if (sigoff & SER_DTR) {
+ sc->sc_mcr &= ~SER_DTR;
+ }
+ if (sigon & SER_RTS) {
+ sc->sc_mcr |= SER_RTS;
+ }
+ if (sigoff & SER_RTS) {
+ sc->sc_mcr &= ~SER_RTS;
+ }
+ onoff = (sc->sc_mcr & SER_DTR) ? 1 : 0;
+ ucom_dtr(sc, onoff);
+
+ onoff = (sc->sc_mcr & SER_RTS) ? 1 : 0;
+ ucom_rts(sc, onoff);
+
+ return (0);
+}
+
+static void
+ucom_cfg_line_state(struct usb_proc_msg *_task)
+{
+ struct ucom_cfg_task *task =
+ (struct ucom_cfg_task *)_task;
+ struct ucom_softc *sc = task->sc;
+ uint8_t notch_bits;
+ uint8_t any_bits;
+ uint8_t prev_value;
+ uint8_t last_value;
+ uint8_t mask;
+
+ if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
+ return;
+ }
+
+ mask = 0;
+ /* compute callback mask */
+ if (sc->sc_callback->ucom_cfg_set_dtr)
+ mask |= UCOM_LS_DTR;
+ if (sc->sc_callback->ucom_cfg_set_rts)
+ mask |= UCOM_LS_RTS;
+ if (sc->sc_callback->ucom_cfg_set_break)
+ mask |= UCOM_LS_BREAK;
+ if (sc->sc_callback->ucom_cfg_set_ring)
+ mask |= UCOM_LS_RING;
+
+ /* compute the bits we are to program */
+ notch_bits = (sc->sc_pls_set & sc->sc_pls_clr) & mask;
+ any_bits = (sc->sc_pls_set | sc->sc_pls_clr) & mask;
+ prev_value = sc->sc_pls_curr ^ notch_bits;
+ last_value = sc->sc_pls_curr;
+
+ /* reset programmed line state */
+ sc->sc_pls_curr = 0;
+ sc->sc_pls_set = 0;
+ sc->sc_pls_clr = 0;
+
+ /* ensure that we don't lose any levels */
+ if (notch_bits & UCOM_LS_DTR)
+ sc->sc_callback->ucom_cfg_set_dtr(sc,
+ (prev_value & UCOM_LS_DTR) ? 1 : 0);
+ if (notch_bits & UCOM_LS_RTS)
+ sc->sc_callback->ucom_cfg_set_rts(sc,
+ (prev_value & UCOM_LS_RTS) ? 1 : 0);
+ if (notch_bits & UCOM_LS_BREAK)
+ sc->sc_callback->ucom_cfg_set_break(sc,
+ (prev_value & UCOM_LS_BREAK) ? 1 : 0);
+ if (notch_bits & UCOM_LS_RING)
+ sc->sc_callback->ucom_cfg_set_ring(sc,
+ (prev_value & UCOM_LS_RING) ? 1 : 0);
+
+ /* set last value */
+ if (any_bits & UCOM_LS_DTR)
+ sc->sc_callback->ucom_cfg_set_dtr(sc,
+ (last_value & UCOM_LS_DTR) ? 1 : 0);
+ if (any_bits & UCOM_LS_RTS)
+ sc->sc_callback->ucom_cfg_set_rts(sc,
+ (last_value & UCOM_LS_RTS) ? 1 : 0);
+ if (any_bits & UCOM_LS_BREAK)
+ sc->sc_callback->ucom_cfg_set_break(sc,
+ (last_value & UCOM_LS_BREAK) ? 1 : 0);
+ if (any_bits & UCOM_LS_RING)
+ sc->sc_callback->ucom_cfg_set_ring(sc,
+ (last_value & UCOM_LS_RING) ? 1 : 0);
+}
+
+static void
+ucom_line_state(struct ucom_softc *sc,
+ uint8_t set_bits, uint8_t clear_bits)
+{
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
+ return;
+ }
+
+ DPRINTF("on=0x%02x, off=0x%02x\n", set_bits, clear_bits);
+
+ /* update current programmed line state */
+ sc->sc_pls_curr |= set_bits;
+ sc->sc_pls_curr &= ~clear_bits;
+ sc->sc_pls_set |= set_bits;
+ sc->sc_pls_clr |= clear_bits;
+
+ /*
+ * defer driver programming - we don't propagate any error from
+ * this call because we'll catch such errors further up the call stack.
+ */
+ (void)ucom_queue_command(sc, ucom_cfg_line_state, NULL,
+ &sc->sc_line_state_task[0].hdr,
+ &sc->sc_line_state_task[1].hdr, false);
+}
+
+static void
+ucom_ring(struct ucom_softc *sc, uint8_t onoff)
+{
+ DPRINTF("onoff = %d\n", onoff);
+
+ if (onoff)
+ ucom_line_state(sc, UCOM_LS_RING, 0);
+ else
+ ucom_line_state(sc, 0, UCOM_LS_RING);
+}
+
+static void
+ucom_break(struct ucom_softc *sc, uint8_t onoff)
+{
+ DPRINTF("onoff = %d\n", onoff);
+
+ if (onoff)
+ ucom_line_state(sc, UCOM_LS_BREAK, 0);
+ else
+ ucom_line_state(sc, 0, UCOM_LS_BREAK);
+}
+
+static void
+ucom_dtr(struct ucom_softc *sc, uint8_t onoff)
+{
+ DPRINTF("onoff = %d\n", onoff);
+
+ if (onoff)
+ ucom_line_state(sc, UCOM_LS_DTR, 0);
+ else
+ ucom_line_state(sc, 0, UCOM_LS_DTR);
+}
+
+static void
+ucom_rts(struct ucom_softc *sc, uint8_t onoff)
+{
+ DPRINTF("onoff = %d\n", onoff);
+
+ if (onoff)
+ ucom_line_state(sc, UCOM_LS_RTS, 0);
+ else
+ ucom_line_state(sc, 0, UCOM_LS_RTS);
+}
+
+static void
+ucom_cfg_status_change(struct usb_proc_msg *_task)
+{
+ struct ucom_cfg_task *task =
+ (struct ucom_cfg_task *)_task;
+ struct ucom_softc *sc = task->sc;
+ struct tty *tp;
+ int onoff;
+ uint8_t new_msr;
+ uint8_t new_lsr;
+ uint8_t msr_delta;
+ uint8_t lsr_delta;
+ uint8_t pps_signal;
+
+ tp = sc->sc_tty;
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
+ return;
+ }
+ if (sc->sc_callback->ucom_cfg_get_status == NULL) {
+ return;
+ }
+ /* get status */
+
+ new_msr = 0;
+ new_lsr = 0;
+
+ (sc->sc_callback->ucom_cfg_get_status) (sc, &new_lsr, &new_msr);
+
+ if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
+ /* TTY device closed */
+ return;
+ }
+ msr_delta = (sc->sc_msr ^ new_msr);
+ lsr_delta = (sc->sc_lsr ^ new_lsr);
+
+ sc->sc_msr = new_msr;
+ sc->sc_lsr = new_lsr;
+
+ /*
+ * Time pulse counting support.
+ */
+ switch(ucom_pps_mode & UART_PPS_SIGNAL_MASK) {
+ case UART_PPS_CTS:
+ pps_signal = SER_CTS;
+ break;
+ case UART_PPS_DCD:
+ pps_signal = SER_DCD;
+ break;
+ default:
+ pps_signal = 0;
+ break;
+ }
+
+ if ((sc->sc_pps.ppsparam.mode & PPS_CAPTUREBOTH) &&
+ (msr_delta & pps_signal)) {
+ pps_capture(&sc->sc_pps);
+ onoff = (sc->sc_msr & pps_signal) ? 1 : 0;
+ if (ucom_pps_mode & UART_PPS_INVERT_PULSE)
+ onoff = !onoff;
+ pps_event(&sc->sc_pps, onoff ? PPS_CAPTUREASSERT :
+ PPS_CAPTURECLEAR);
+ }
+
+ if (msr_delta & SER_DCD) {
+ onoff = (sc->sc_msr & SER_DCD) ? 1 : 0;
+
+ DPRINTF("DCD changed to %d\n", onoff);
+
+ ttydisc_modem(tp, onoff);
+ }
+
+ if ((lsr_delta & ULSR_BI) && (sc->sc_lsr & ULSR_BI)) {
+ DPRINTF("BREAK detected\n");
+
+ ttydisc_rint(tp, 0, TRE_BREAK);
+ ttydisc_rint_done(tp);
+ }
+
+ if ((lsr_delta & ULSR_FE) && (sc->sc_lsr & ULSR_FE)) {
+ DPRINTF("Frame error detected\n");
+
+ ttydisc_rint(tp, 0, TRE_FRAMING);
+ ttydisc_rint_done(tp);
+ }
+
+ if ((lsr_delta & ULSR_PE) && (sc->sc_lsr & ULSR_PE)) {
+ DPRINTF("Parity error detected\n");
+
+ ttydisc_rint(tp, 0, TRE_PARITY);
+ ttydisc_rint_done(tp);
+ }
+}
+
+void
+ucom_status_change(struct ucom_softc *sc)
+{
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ if (sc->sc_flag & UCOM_FLAG_CONSOLE)
+ return; /* not supported */
+
+ if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
+ return;
+ }
+ DPRINTF("\n");
+
+ (void)ucom_queue_command(sc, ucom_cfg_status_change, NULL,
+ &sc->sc_status_task[0].hdr,
+ &sc->sc_status_task[1].hdr, true);
+}
+
+static void
+ucom_cfg_param(struct usb_proc_msg *_task)
+{
+ struct ucom_param_task *task =
+ (struct ucom_param_task *)_task;
+ struct ucom_softc *sc = task->sc;
+
+ if (!(sc->sc_flag & UCOM_FLAG_LL_READY)) {
+ return;
+ }
+ if (sc->sc_callback->ucom_cfg_param == NULL) {
+ return;
+ }
+
+ (sc->sc_callback->ucom_cfg_param) (sc, &task->termios_copy);
+
+ /* wait a little */
+ usb_pause_mtx(sc->sc_mtx, hz / 10);
+}
+
+static int
+ucom_param(struct tty *tp, struct termios *t)
+{
+ struct ucom_softc *sc = tty_softc(tp);
+ uint8_t opened;
+ int error;
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ opened = 0;
+ error = 0;
+
+ if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
+ /* XXX the TTY layer should call "open()" first! */
+ /*
+ * Not quite: Its ordering is partly backwards, but
+ * some parameters must be set early in ttydev_open(),
+ * possibly before calling ttydevsw_open().
+ */
+ error = ucom_open(tp);
+ if (error)
+ goto done;
+
+ opened = 1;
+ }
+ DPRINTF("sc = %p\n", sc);
+
+ /* Check requested parameters. */
+ if (t->c_ispeed && (t->c_ispeed != t->c_ospeed)) {
+ /* XXX c_ospeed == 0 is perfectly valid. */
+ DPRINTF("mismatch ispeed and ospeed\n");
+ error = EINVAL;
+ goto done;
+ }
+ t->c_ispeed = t->c_ospeed;
+
+ if (sc->sc_callback->ucom_pre_param) {
+ /* Let the lower layer verify the parameters */
+ error = (sc->sc_callback->ucom_pre_param) (sc, t);
+ if (error) {
+ DPRINTF("callback error = %d\n", error);
+ goto done;
+ }
+ }
+
+ /* Disable transfers */
+ sc->sc_flag &= ~UCOM_FLAG_GP_DATA;
+
+ /* Queue baud rate programming command first */
+ error = ucom_queue_command(sc, ucom_cfg_param, t,
+ &sc->sc_param_task[0].hdr,
+ &sc->sc_param_task[1].hdr, true);
+ if (error != 0)
+ goto done;
+
+ /* Queue transfer enable command last */
+ error = ucom_queue_command(sc, ucom_cfg_start_transfers, NULL,
+ &sc->sc_start_task[0].hdr,
+ &sc->sc_start_task[1].hdr, true);
+ if (error != 0)
+ goto done;
+
+ if (t->c_cflag & CRTS_IFLOW) {
+ sc->sc_flag |= UCOM_FLAG_RTS_IFLOW;
+ } else if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW) {
+ sc->sc_flag &= ~UCOM_FLAG_RTS_IFLOW;
+ ucom_modem(tp, SER_RTS, 0);
+ }
+
+ error = ucom_command_barrier(sc);
+done:
+ if (error) {
+ if (opened) {
+ ucom_close(tp);
+ }
+ }
+ return (error);
+}
+
+static void
+ucom_outwakeup(struct tty *tp)
+{
+ struct ucom_softc *sc = tty_softc(tp);
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ DPRINTF("sc = %p\n", sc);
+
+ if (!(sc->sc_flag & UCOM_FLAG_HL_READY)) {
+ /* The higher layer is not ready */
+ return;
+ }
+ ucom_start_transfers(sc);
+}
+
+static bool
+ucom_busy(struct tty *tp)
+{
+ struct ucom_softc *sc = tty_softc(tp);
+ const uint8_t txidle = ULSR_TXRDY | ULSR_TSRE;
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ DPRINTFN(3, "sc = %p lsr 0x%02x\n", sc, sc->sc_lsr);
+
+ /*
+ * If the driver maintains the txidle bits in LSR, we can use them to
+ * determine whether the transmitter is busy or idle. Otherwise we have
+ * to assume it is idle to avoid hanging forever on tcdrain(3).
+ */
+ if (sc->sc_flag & UCOM_FLAG_LSRTXIDLE)
+ return ((sc->sc_lsr & txidle) != txidle);
+ else
+ return (false);
+}
+
+/*------------------------------------------------------------------------*
+ * ucom_get_data
+ *
+ * Return values:
+ * 0: No data is available.
+ * Else: Data is available.
+ *------------------------------------------------------------------------*/
+uint8_t
+ucom_get_data(struct ucom_softc *sc, struct usb_page_cache *pc,
+ uint32_t offset, uint32_t len, uint32_t *actlen)
+{
+ struct usb_page_search res;
+ struct tty *tp = sc->sc_tty;
+ uint32_t cnt;
+ uint32_t offset_orig;
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
+ unsigned temp;
+
+ /* get total TX length */
+
+ temp = ucom_cons_tx_high - ucom_cons_tx_low;
+ temp %= UCOM_CONS_BUFSIZE;
+
+ /* limit TX length */
+
+ if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_tx_low))
+ temp = (UCOM_CONS_BUFSIZE - ucom_cons_tx_low);
+
+ if (temp > len)
+ temp = len;
+
+ /* copy in data */
+
+ usbd_copy_in(pc, offset, ucom_cons_tx_buf + ucom_cons_tx_low, temp);
+
+ /* update counters */
+
+ ucom_cons_tx_low += temp;
+ ucom_cons_tx_low %= UCOM_CONS_BUFSIZE;
+
+ /* store actual length */
+
+ *actlen = temp;
+
+ return (temp ? 1 : 0);
+ }
+
+ if (tty_gone(tp) ||
+ !(sc->sc_flag & UCOM_FLAG_GP_DATA)) {
+ actlen[0] = 0;
+ return (0); /* multiport device polling */
+ }
+ offset_orig = offset;
+
+ while (len != 0) {
+ usbd_get_page(pc, offset, &res);
+
+ if (res.length > len) {
+ res.length = len;
+ }
+ /* copy data directly into USB buffer */
+ cnt = ttydisc_getc(tp, res.buffer, res.length);
+
+ offset += cnt;
+ len -= cnt;
+
+ if (cnt < res.length) {
+ /* end of buffer */
+ break;
+ }
+ }
+
+ actlen[0] = offset - offset_orig;
+
+ DPRINTF("cnt=%d\n", actlen[0]);
+
+ if (actlen[0] == 0) {
+ return (0);
+ }
+ return (1);
+}
+
+void
+ucom_put_data(struct ucom_softc *sc, struct usb_page_cache *pc,
+ uint32_t offset, uint32_t len)
+{
+ struct usb_page_search res;
+ struct tty *tp = sc->sc_tty;
+ char *buf;
+ uint32_t cnt;
+
+ UCOM_MTX_ASSERT(sc, MA_OWNED);
+
+ if (sc->sc_flag & UCOM_FLAG_CONSOLE) {
+ unsigned temp;
+
+ /* get maximum RX length */
+
+ temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_rx_high + ucom_cons_rx_low;
+ temp %= UCOM_CONS_BUFSIZE;
+
+ /* limit RX length */
+
+ if (temp > (UCOM_CONS_BUFSIZE - ucom_cons_rx_high))
+ temp = (UCOM_CONS_BUFSIZE - ucom_cons_rx_high);
+
+ if (temp > len)
+ temp = len;
+
+ /* copy out data */
+
+ usbd_copy_out(pc, offset, ucom_cons_rx_buf + ucom_cons_rx_high, temp);
+
+ /* update counters */
+
+ ucom_cons_rx_high += temp;
+ ucom_cons_rx_high %= UCOM_CONS_BUFSIZE;
+
+ return;
+ }
+
+ if (tty_gone(tp))
+ return; /* multiport device polling */
+
+ if (len == 0)
+ return; /* no data */
+
+ /* set a flag to prevent recursation ? */
+
+ while (len > 0) {
+ usbd_get_page(pc, offset, &res);
+
+ if (res.length > len) {
+ res.length = len;
+ }
+ len -= res.length;
+ offset += res.length;
+
+ /* pass characters to tty layer */
+
+ buf = res.buffer;
+ cnt = res.length;
+
+ /* first check if we can pass the buffer directly */
+
+ if (ttydisc_can_bypass(tp)) {
+ /* clear any jitter buffer */
+ sc->sc_jitterbuf_in = 0;
+ sc->sc_jitterbuf_out = 0;
+
+ if (ttydisc_rint_bypass(tp, buf, cnt) != cnt) {
+ DPRINTF("tp=%p, data lost\n", tp);
+ }
+ continue;
+ }
+ /* need to loop */
+
+ for (cnt = 0; cnt != res.length; cnt++) {
+ if (sc->sc_jitterbuf_in != sc->sc_jitterbuf_out ||
+ ttydisc_rint(tp, buf[cnt], 0) == -1) {
+ uint16_t end;
+ uint16_t pos;
+
+ pos = sc->sc_jitterbuf_in;
+ end = sc->sc_jitterbuf_out +
+ UCOM_JITTERBUF_SIZE - 1;
+ if (end >= UCOM_JITTERBUF_SIZE)
+ end -= UCOM_JITTERBUF_SIZE;
+
+ for (; cnt != res.length; cnt++) {
+ if (pos == end)
+ break;
+ sc->sc_jitterbuf[pos] = buf[cnt];
+ pos++;
+ if (pos >= UCOM_JITTERBUF_SIZE)
+ pos -= UCOM_JITTERBUF_SIZE;
+ }
+
+ sc->sc_jitterbuf_in = pos;
+
+ /* set RTS in async fashion */
+ if (sc->sc_flag & UCOM_FLAG_RTS_IFLOW)
+ ucom_rts(sc, 1);
+
+ DPRINTF("tp=%p, lost %d "
+ "chars\n", tp, res.length - cnt);
+ break;
+ }
+ }
+ }
+ ttydisc_rint_done(tp);
+}
+
+static void
+ucom_free(void *xsc)
+{
+ struct ucom_softc *sc = xsc;
+
+ if (sc->sc_callback->ucom_free != NULL)
+ sc->sc_callback->ucom_free(sc);
+ else
+ ucom_unref(sc->sc_super);
+
+ mtx_lock(&ucom_mtx);
+ ucom_close_refs--;
+ mtx_unlock(&ucom_mtx);
+}
+
+CONSOLE_DRIVER(ucom);
+
+static void
+ucom_cnprobe(struct consdev *cp)
+{
+ if (ucom_cons_unit != -1)
+ cp->cn_pri = CN_NORMAL;
+ else
+ cp->cn_pri = CN_DEAD;
+
+ strlcpy(cp->cn_name, "ucom", sizeof(cp->cn_name));
+}
+
+static void
+ucom_cninit(struct consdev *cp)
+{
+}
+
+static void
+ucom_cnterm(struct consdev *cp)
+{
+}
+
+static void
+ucom_cngrab(struct consdev *cp)
+{
+}
+
+static void
+ucom_cnungrab(struct consdev *cp)
+{
+}
+
+static int
+ucom_cngetc(struct consdev *cd)
+{
+ struct ucom_softc *sc = ucom_cons_softc;
+ int c;
+
+ if (sc == NULL)
+ return (-1);
+
+ UCOM_MTX_LOCK(sc);
+
+ if (ucom_cons_rx_low != ucom_cons_rx_high) {
+ c = ucom_cons_rx_buf[ucom_cons_rx_low];
+ ucom_cons_rx_low ++;
+ ucom_cons_rx_low %= UCOM_CONS_BUFSIZE;
+ } else {
+ c = -1;
+ }
+
+ /* start USB transfers */
+ ucom_outwakeup(sc->sc_tty);
+
+ UCOM_MTX_UNLOCK(sc);
+
+ /* poll if necessary */
+ if (USB_IN_POLLING_MODE_FUNC() && sc->sc_callback->ucom_poll)
+ (sc->sc_callback->ucom_poll) (sc);
+
+ return (c);
+}
+
+static void
+ucom_cnputc(struct consdev *cd, int c)
+{
+ struct ucom_softc *sc = ucom_cons_softc;
+ unsigned temp;
+
+ if (sc == NULL)
+ return;
+
+ repeat:
+
+ UCOM_MTX_LOCK(sc);
+
+ /* compute maximum TX length */
+
+ temp = (UCOM_CONS_BUFSIZE - 1) - ucom_cons_tx_high + ucom_cons_tx_low;
+ temp %= UCOM_CONS_BUFSIZE;
+
+ if (temp) {
+ ucom_cons_tx_buf[ucom_cons_tx_high] = c;
+ ucom_cons_tx_high ++;
+ ucom_cons_tx_high %= UCOM_CONS_BUFSIZE;
+ }
+
+ /* start USB transfers */
+ ucom_outwakeup(sc->sc_tty);
+
+ UCOM_MTX_UNLOCK(sc);
+
+ /* poll if necessary */
+ if (USB_IN_POLLING_MODE_FUNC() && sc->sc_callback->ucom_poll) {
+ (sc->sc_callback->ucom_poll) (sc);
+ /* simple flow control */
+ if (temp == 0)
+ goto repeat;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * ucom_ref
+ *
+ * This function will increment the super UCOM reference count.
+ *------------------------------------------------------------------------*/
+void
+ucom_ref(struct ucom_super_softc *ssc)
+{
+ mtx_lock(&ucom_mtx);
+ ssc->sc_refs++;
+ mtx_unlock(&ucom_mtx);
+}
+
+/*------------------------------------------------------------------------*
+ * ucom_free_unit
+ *
+ * This function will free the super UCOM's allocated unit
+ * number. This function can be called on a zero-initialized
+ * structure. This function can be called multiple times.
+ *------------------------------------------------------------------------*/
+static void
+ucom_free_unit(struct ucom_super_softc *ssc)
+{
+ if (!(ssc->sc_flag & UCOM_FLAG_FREE_UNIT))
+ return;
+
+ ucom_unit_free(ssc->sc_unit);
+
+ ssc->sc_flag &= ~UCOM_FLAG_FREE_UNIT;
+}
+
+/*------------------------------------------------------------------------*
+ * ucom_unref
+ *
+ * This function will decrement the super UCOM reference count.
+ *
+ * Return values:
+ * 0: UCOM structures are still referenced.
+ * Else: UCOM structures are no longer referenced.
+ *------------------------------------------------------------------------*/
+int
+ucom_unref(struct ucom_super_softc *ssc)
+{
+ int retval;
+
+ mtx_lock(&ucom_mtx);
+ retval = (ssc->sc_refs < 2);
+ ssc->sc_refs--;
+ mtx_unlock(&ucom_mtx);
+
+ if (retval)
+ ucom_free_unit(ssc);
+
+ return (retval);
+}
+
+#if defined(GDB)
+
+#include <gdb/gdb.h>
+
+static gdb_probe_f ucom_gdbprobe;
+static gdb_init_f ucom_gdbinit;
+static gdb_term_f ucom_gdbterm;
+static gdb_getc_f ucom_gdbgetc;
+static gdb_putc_f ucom_gdbputc;
+
+GDB_DBGPORT(ucom, ucom_gdbprobe, ucom_gdbinit, ucom_gdbterm, ucom_gdbgetc, ucom_gdbputc);
+
+static int
+ucom_gdbprobe(void)
+{
+ return ((ucom_cons_softc != NULL) ? 0 : -1);
+}
+
+static void
+ucom_gdbinit(void)
+{
+}
+
+static void
+ucom_gdbterm(void)
+{
+}
+
+static void
+ucom_gdbputc(int c)
+{
+ ucom_cnputc(NULL, c);
+}
+
+static int
+ucom_gdbgetc(void)
+{
+ return (ucom_cngetc(NULL));
+}
+
+#endif
diff --git a/sys/dev/usb/serial/usb_serial.h b/sys/dev/usb/serial/usb_serial.h
new file mode 100644
index 000000000000..01b032ac394c
--- /dev/null
+++ b/sys/dev/usb/serial/usb_serial.h
@@ -0,0 +1,234 @@
+/* $NetBSD: ucomvar.h,v 1.9 2001/01/23 21:56:17 augustss Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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_SERIAL_H_
+#define _USB_SERIAL_H_
+
+#include <sys/tty.h>
+#include <sys/serial.h>
+#include <sys/fcntl.h>
+#include <sys/sysctl.h>
+#include <sys/timepps.h>
+
+/* Module interface related macros */
+#define UCOM_MODVER 1
+
+#define UCOM_MINVER 1
+#define UCOM_PREFVER UCOM_MODVER
+#define UCOM_MAXVER 1
+#define UCOM_JITTERBUF_SIZE 128 /* bytes */
+
+struct usb_device;
+struct ucom_softc;
+struct usb_device_request;
+struct thread;
+
+/*
+ * NOTE: There is no guarantee that "ucom_cfg_close()" will
+ * be called after "ucom_cfg_open()" if the device is detached
+ * while it is open!
+ */
+struct ucom_callback {
+ void (*ucom_cfg_get_status) (struct ucom_softc *, uint8_t *plsr, uint8_t *pmsr);
+ void (*ucom_cfg_set_dtr) (struct ucom_softc *, uint8_t);
+ void (*ucom_cfg_set_rts) (struct ucom_softc *, uint8_t);
+ void (*ucom_cfg_set_break) (struct ucom_softc *, uint8_t);
+ void (*ucom_cfg_set_ring) (struct ucom_softc *, uint8_t);
+ void (*ucom_cfg_param) (struct ucom_softc *, struct termios *);
+ void (*ucom_cfg_open) (struct ucom_softc *);
+ void (*ucom_cfg_close) (struct ucom_softc *);
+ int (*ucom_pre_open) (struct ucom_softc *);
+ int (*ucom_pre_param) (struct ucom_softc *, struct termios *);
+ int (*ucom_ioctl) (struct ucom_softc *, uint32_t, caddr_t, int, struct thread *);
+ void (*ucom_start_read) (struct ucom_softc *);
+ void (*ucom_stop_read) (struct ucom_softc *);
+ void (*ucom_start_write) (struct ucom_softc *);
+ void (*ucom_stop_write) (struct ucom_softc *);
+ void (*ucom_tty_name) (struct ucom_softc *, char *pbuf, uint16_t buflen, uint16_t unit, uint16_t subunit);
+ void (*ucom_poll) (struct ucom_softc *);
+ void (*ucom_free) (struct ucom_softc *);
+};
+
+/* 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 */
+
+struct ucom_cfg_task {
+ struct usb_proc_msg hdr;
+ struct ucom_softc *sc;
+};
+
+struct ucom_param_task {
+ struct usb_proc_msg hdr;
+ struct ucom_softc *sc;
+ struct termios termios_copy;
+};
+
+struct ucom_super_softc {
+ struct usb_process sc_tq;
+ int sc_unit;
+ int sc_subunits;
+ int sc_refs;
+ int sc_flag; /* see UCOM_FLAG_XXX */
+ struct sysctl_oid *sc_sysctl_ttyname;
+ struct sysctl_oid *sc_sysctl_ttyports;
+ char sc_ttyname[16];
+};
+
+struct ucom_softc {
+ /*
+ * NOTE: To avoid losing level change information we use two
+ * tasks instead of one for all commands.
+ *
+ * Level changes are transitions like:
+ *
+ * ON->OFF
+ * OFF->ON
+ * OPEN->CLOSE
+ * CLOSE->OPEN
+ */
+ struct ucom_cfg_task sc_start_task[2];
+ struct ucom_cfg_task sc_open_task[2];
+ struct ucom_cfg_task sc_close_task[2];
+ struct ucom_cfg_task sc_line_state_task[2];
+ struct ucom_cfg_task sc_status_task[2];
+ struct ucom_param_task sc_param_task[2];
+ /* pulse capturing support, PPS */
+ struct pps_state sc_pps;
+ /* Used to set "UCOM_FLAG_GP_DATA" flag: */
+ struct usb_proc_msg *sc_last_start_xfer;
+ const struct ucom_callback *sc_callback;
+ struct ucom_super_softc *sc_super;
+ struct tty *sc_tty;
+ struct consdev *sc_consdev;
+ struct mtx *sc_mtx;
+ void *sc_parent;
+ int sc_subunit;
+ uint16_t sc_jitterbuf_in;
+ uint16_t sc_jitterbuf_out;
+ uint16_t sc_portno;
+ uint16_t sc_flag;
+#define UCOM_FLAG_RTS_IFLOW 0x01 /* use RTS input flow control */
+#define UCOM_FLAG_GONE 0x02 /* the device is gone */
+#define UCOM_FLAG_ATTACHED 0x04 /* set if attached */
+#define UCOM_FLAG_GP_DATA 0x08 /* set if get and put data is possible */
+#define UCOM_FLAG_LL_READY 0x20 /* set if low layer is ready */
+#define UCOM_FLAG_HL_READY 0x40 /* set if high layer is ready */
+#define UCOM_FLAG_CONSOLE 0x80 /* set if device is a console */
+#define UCOM_FLAG_WAIT_REFS 0x0100 /* set if we must wait for refs */
+#define UCOM_FLAG_FREE_UNIT 0x0200 /* set if we must free the unit */
+#define UCOM_FLAG_INWAKEUP 0x0400 /* set if we are in the tsw_inwakeup callback */
+#define UCOM_FLAG_LSRTXIDLE 0x0800 /* set if sc_lsr bits ULSR_TSRE+TXRDY work */
+#define UCOM_FLAG_DEVICE_MODE 0x1000 /* set if we're an USB device, not a host */
+ uint8_t sc_lsr;
+ uint8_t sc_msr;
+ uint8_t sc_mcr;
+ /* programmed line state bits */
+ uint8_t sc_pls_set; /* set bits */
+ uint8_t sc_pls_clr; /* cleared bits */
+ uint8_t sc_pls_curr; /* last state */
+#define UCOM_LS_DTR 0x01
+#define UCOM_LS_RTS 0x02
+#define UCOM_LS_BREAK 0x04
+#define UCOM_LS_RING 0x08
+ uint8_t sc_jitterbuf[UCOM_JITTERBUF_SIZE];
+};
+
+#define UCOM_MTX_ASSERT(sc, what) USB_MTX_ASSERT((sc)->sc_mtx, what)
+#define UCOM_MTX_LOCK(sc) USB_MTX_LOCK((sc)->sc_mtx)
+#define UCOM_MTX_UNLOCK(sc) USB_MTX_UNLOCK((sc)->sc_mtx)
+#define UCOM_UNLOAD_DRAIN(x) \
+SYSUNINIT(var, SI_SUB_KLD - 2, SI_ORDER_ANY, ucom_drain_all, 0)
+
+#define ucom_cfg_do_request(udev,com,req,ptr,flags,timo) \
+ usbd_do_request_proc(udev,&(com)->sc_super->sc_tq,req,ptr,flags,NULL,timo)
+
+int ucom_attach(struct ucom_super_softc *,
+ struct ucom_softc *, int, void *,
+ const struct ucom_callback *callback, struct mtx *);
+void ucom_detach(struct ucom_super_softc *, struct ucom_softc *);
+void ucom_set_pnpinfo_usb(struct ucom_super_softc *, device_t);
+void ucom_set_usb_mode(struct ucom_super_softc *, enum usb_hc_mode);
+void ucom_status_change(struct ucom_softc *);
+uint8_t ucom_get_data(struct ucom_softc *, struct usb_page_cache *,
+ uint32_t, uint32_t, uint32_t *);
+void ucom_put_data(struct ucom_softc *, struct usb_page_cache *,
+ uint32_t, uint32_t);
+uint8_t ucom_cfg_is_gone(struct ucom_softc *);
+void ucom_drain(struct ucom_super_softc *);
+void ucom_drain_all(void *);
+void ucom_ref(struct ucom_super_softc *);
+int ucom_unref(struct ucom_super_softc *);
+
+static inline void
+ucom_use_lsr_txbits(struct ucom_softc *sc)
+{
+
+ sc->sc_flag |= UCOM_FLAG_LSRTXIDLE;
+}
+
+#endif /* _USB_SERIAL_H_ */
diff --git a/sys/dev/usb/serial/uslcom.c b/sys/dev/usb/serial/uslcom.c
new file mode 100644
index 000000000000..26b937d0b200
--- /dev/null
+++ b/sys/dev/usb/serial/uslcom.c
@@ -0,0 +1,957 @@
+/* $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.
+ */
+
+/*
+ * Driver for Silicon Laboratories CP2101/CP2102/CP2103/CP2104/CP2105
+ * USB-Serial adapters. Based on datasheet AN571, publicly available from
+ * http://www.silabs.com/Support%20Documents/TechnicalDocs/AN571.pdf
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_ioctl.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR uslcom_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#ifdef USB_DEBUG
+static int uslcom_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uslcom, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB uslcom");
+SYSCTL_INT(_hw_usb_uslcom, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &uslcom_debug, 0, "Debug level");
+#endif
+
+#define USLCOM_BULK_BUF_SIZE 1024
+#define USLCOM_CONFIG_INDEX 0
+
+/* Request types */
+#define USLCOM_WRITE 0x41
+#define USLCOM_READ 0xc1
+
+/* Request codes */
+#define USLCOM_IFC_ENABLE 0x00
+#define USLCOM_SET_BAUDDIV 0x01
+#define USLCOM_SET_LINE_CTL 0x03
+#define USLCOM_SET_BREAK 0x05
+#define USLCOM_SET_MHS 0x07
+#define USLCOM_GET_MDMSTS 0x08
+#define USLCOM_SET_FLOW 0x13
+#define USLCOM_SET_BAUDRATE 0x1e
+#define USLCOM_VENDOR_SPECIFIC 0xff
+
+/* USLCOM_IFC_ENABLE values */
+#define USLCOM_IFC_ENABLE_DIS 0x00
+#define USLCOM_IFC_ENABLE_EN 0x01
+
+/* USLCOM_SET_MHS/USLCOM_GET_MDMSTS values */
+#define USLCOM_MHS_DTR_ON 0x0001
+#define USLCOM_MHS_DTR_SET 0x0100
+#define USLCOM_MHS_RTS_ON 0x0002
+#define USLCOM_MHS_RTS_SET 0x0200
+#define USLCOM_MHS_CTS 0x0010
+#define USLCOM_MHS_DSR 0x0020
+#define USLCOM_MHS_RI 0x0040
+#define USLCOM_MHS_DCD 0x0080
+
+/* USLCOM_SET_BAUDDIV values */
+#define USLCOM_BAUDDIV_REF 3686400 /* 3.6864 MHz */
+
+/* USLCOM_SET_LINE_CTL values */
+#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_SET_DATA_BITS(x) ((x) << 8)
+
+/* USLCOM_SET_BREAK values */
+#define USLCOM_SET_BREAK_OFF 0x00
+#define USLCOM_SET_BREAK_ON 0x01
+
+/* USLCOM_SET_FLOW values - 1st word */
+#define USLCOM_FLOW_DTR_ON 0x00000001 /* DTR static active */
+#define USLCOM_FLOW_CTS_HS 0x00000008 /* CTS handshake */
+/* USLCOM_SET_FLOW values - 2nd word */
+#define USLCOM_FLOW_RTS_ON 0x00000040 /* RTS static active */
+#define USLCOM_FLOW_RTS_HS 0x00000080 /* RTS handshake */
+
+/* USLCOM_VENDOR_SPECIFIC values */
+#define USLCOM_GET_PARTNUM 0x370B
+#define USLCOM_WRITE_LATCH 0x37E1
+#define USLCOM_READ_LATCH 0x00C2
+
+/* USLCOM_GET_PARTNUM values from hardware */
+#define USLCOM_PARTNUM_CP2101 1
+#define USLCOM_PARTNUM_CP2102 2
+#define USLCOM_PARTNUM_CP2103 3
+#define USLCOM_PARTNUM_CP2104 4
+#define USLCOM_PARTNUM_CP2105 5
+
+enum {
+ USLCOM_BULK_DT_WR,
+ USLCOM_BULK_DT_RD,
+ USLCOM_CTRL_DT_RD,
+ USLCOM_N_TRANSFER,
+};
+
+struct uslcom_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+ struct usb_callout sc_watchdog;
+
+ struct usb_xfer *sc_xfer[USLCOM_N_TRANSFER];
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+
+ uint8_t sc_msr;
+ uint8_t sc_lsr;
+ uint8_t sc_iface_no;
+ uint8_t sc_partnum;
+};
+
+static device_probe_t uslcom_probe;
+static device_attach_t uslcom_attach;
+static device_detach_t uslcom_detach;
+static void uslcom_free_softc(struct uslcom_softc *);
+
+static usb_callback_t uslcom_write_callback;
+static usb_callback_t uslcom_read_callback;
+static usb_callback_t uslcom_control_callback;
+
+static void uslcom_free(struct ucom_softc *);
+static uint8_t uslcom_get_partnum(struct uslcom_softc *);
+static void uslcom_cfg_open(struct ucom_softc *);
+static void uslcom_cfg_close(struct ucom_softc *);
+static void uslcom_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void uslcom_cfg_set_rts(struct ucom_softc *, uint8_t);
+static void uslcom_cfg_set_break(struct ucom_softc *, uint8_t);
+static void uslcom_cfg_param(struct ucom_softc *, struct termios *);
+static void uslcom_cfg_get_status(struct ucom_softc *, uint8_t *, uint8_t *);
+static int uslcom_ioctl(struct ucom_softc *, uint32_t, caddr_t, int,
+ struct thread *);
+static int uslcom_pre_param(struct ucom_softc *, struct termios *);
+static void uslcom_start_read(struct ucom_softc *);
+static void uslcom_stop_read(struct ucom_softc *);
+static void uslcom_start_write(struct ucom_softc *);
+static void uslcom_stop_write(struct ucom_softc *);
+static void uslcom_poll(struct ucom_softc *ucom);
+
+static const struct usb_config uslcom_config[USLCOM_N_TRANSFER] = {
+ [USLCOM_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = USLCOM_BULK_BUF_SIZE,
+ .flags = {.pipe_bof = 1,},
+ .callback = &uslcom_write_callback,
+ },
+
+ [USLCOM_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = USLCOM_BULK_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &uslcom_read_callback,
+ },
+ [USLCOM_CTRL_DT_RD] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00,
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request) + 8,
+ .flags = {.pipe_bof = 1,},
+ .callback = &uslcom_control_callback,
+ .timeout = 1000, /* 1 second timeout */
+ },
+};
+
+static struct ucom_callback uslcom_callback = {
+ .ucom_cfg_get_status = &uslcom_cfg_get_status,
+ .ucom_cfg_set_dtr = &uslcom_cfg_set_dtr,
+ .ucom_cfg_set_rts = &uslcom_cfg_set_rts,
+ .ucom_cfg_set_break = &uslcom_cfg_set_break,
+ .ucom_cfg_open = &uslcom_cfg_open,
+ .ucom_cfg_close = &uslcom_cfg_close,
+ .ucom_cfg_param = &uslcom_cfg_param,
+ .ucom_pre_param = &uslcom_pre_param,
+ .ucom_ioctl = &uslcom_ioctl,
+ .ucom_start_read = &uslcom_start_read,
+ .ucom_stop_read = &uslcom_stop_read,
+ .ucom_start_write = &uslcom_start_write,
+ .ucom_stop_write = &uslcom_stop_write,
+ .ucom_poll = &uslcom_poll,
+ .ucom_free = &uslcom_free,
+};
+
+static const STRUCT_USB_HOST_ID uslcom_devs[] = {
+#define USLCOM_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) }
+ USLCOM_DEV(BALTECH, CARDREADER),
+ USLCOM_DEV(CLIPSAL, 5000CT2),
+ USLCOM_DEV(CLIPSAL, 5500PACA),
+ USLCOM_DEV(CLIPSAL, 5500PCU),
+ USLCOM_DEV(CLIPSAL, 560884),
+ USLCOM_DEV(CLIPSAL, 5800PC),
+ USLCOM_DEV(CLIPSAL, C5000CT2),
+ USLCOM_DEV(CLIPSAL, L51xx),
+ USLCOM_DEV(DATAAPEX, MULTICOM),
+ USLCOM_DEV(DELL, DW700),
+ USLCOM_DEV(DIGIANSWER, ZIGBEE802154),
+ USLCOM_DEV(DYNASTREAM, ANTDEVBOARD),
+ USLCOM_DEV(DYNASTREAM, ANTDEVBOARD2),
+ USLCOM_DEV(DYNASTREAM, ANT2USB),
+ USLCOM_DEV(ELV, USBI2C),
+ USLCOM_DEV(FESTO, CMSP),
+ USLCOM_DEV(FESTO, CPX_USB),
+ USLCOM_DEV(FOXCONN, PIRELLI_DP_L10),
+ USLCOM_DEV(FOXCONN, TCOM_TC_300),
+ USLCOM_DEV(GEMALTO, PROXPU),
+ USLCOM_DEV(JABLOTRON, PC60B),
+ USLCOM_DEV(KAMSTRUP, OPTICALEYE),
+ USLCOM_DEV(KAMSTRUP, MBUS_250D),
+ USLCOM_DEV(LAKESHORE, 121),
+ USLCOM_DEV(LAKESHORE, 218A),
+ USLCOM_DEV(LAKESHORE, 219),
+ USLCOM_DEV(LAKESHORE, 233),
+ USLCOM_DEV(LAKESHORE, 235),
+ USLCOM_DEV(LAKESHORE, 335),
+ USLCOM_DEV(LAKESHORE, 336),
+ USLCOM_DEV(LAKESHORE, 350),
+ USLCOM_DEV(LAKESHORE, 371),
+ USLCOM_DEV(LAKESHORE, 411),
+ USLCOM_DEV(LAKESHORE, 425),
+ USLCOM_DEV(LAKESHORE, 455A),
+ USLCOM_DEV(LAKESHORE, 465),
+ USLCOM_DEV(LAKESHORE, 475A),
+ USLCOM_DEV(LAKESHORE, 625A),
+ USLCOM_DEV(LAKESHORE, 642A),
+ USLCOM_DEV(LAKESHORE, 648),
+ USLCOM_DEV(LAKESHORE, 737),
+ USLCOM_DEV(LAKESHORE, 776),
+ USLCOM_DEV(LINKINSTRUMENTS, MSO19),
+ USLCOM_DEV(LINKINSTRUMENTS, MSO28),
+ USLCOM_DEV(LINKINSTRUMENTS, MSO28_2),
+ USLCOM_DEV(MEI, CASHFLOW_SC),
+ USLCOM_DEV(MEI, S2000),
+ USLCOM_DEV(NETGEAR, M4100),
+ USLCOM_DEV(OWEN, AC4),
+ USLCOM_DEV(OWL, CM_160),
+ USLCOM_DEV(PHILIPS, ACE1001),
+ USLCOM_DEV(PLX, CA42),
+ USLCOM_DEV(RENESAS, RX610),
+ USLCOM_DEV(SEL, C662),
+ USLCOM_DEV(SILABS, AC_SERV_CAN),
+ USLCOM_DEV(SILABS, AC_SERV_CIS),
+ USLCOM_DEV(SILABS, AC_SERV_IBUS),
+ USLCOM_DEV(SILABS, AC_SERV_OBD),
+ USLCOM_DEV(SILABS, AEROCOMM),
+ USLCOM_DEV(SILABS, AMBER_AMB2560),
+ USLCOM_DEV(SILABS, ARGUSISP),
+ USLCOM_DEV(SILABS, ARKHAM_DS101_A),
+ USLCOM_DEV(SILABS, ARKHAM_DS101_M),
+ USLCOM_DEV(SILABS, ARYGON_MIFARE),
+ USLCOM_DEV(SILABS, AVIT_USB_TTL),
+ USLCOM_DEV(SILABS, B_G_H3000),
+ USLCOM_DEV(SILABS, BALLUFF_RFID),
+ USLCOM_DEV(SILABS, BEI_VCP),
+ USLCOM_DEV(SILABS, BSM7DUSB),
+ USLCOM_DEV(SILABS, BURNSIDE),
+ USLCOM_DEV(SILABS, C2_EDGE_MODEM),
+ USLCOM_DEV(SILABS, CP2102),
+ USLCOM_DEV(SILABS, CP210X_2),
+ USLCOM_DEV(SILABS, CP210X_3),
+ USLCOM_DEV(SILABS, CP210X_4),
+ USLCOM_DEV(SILABS, CRUMB128),
+ USLCOM_DEV(SILABS, CYGNAL),
+ USLCOM_DEV(SILABS, CYGNAL_DEBUG),
+ USLCOM_DEV(SILABS, CYGNAL_GPS),
+ USLCOM_DEV(SILABS, DEGREE),
+ USLCOM_DEV(SILABS, DEKTEK_DTAPLUS),
+ USLCOM_DEV(SILABS, EMS_C1007),
+ USLCOM_DEV(SILABS, HAMLINKUSB),
+ USLCOM_DEV(SILABS, HELICOM),
+ USLCOM_DEV(SILABS, HUBZ),
+ USLCOM_DEV(SILABS, BV_AV2010_10),
+ USLCOM_DEV(SILABS, IMS_USB_RS422),
+ USLCOM_DEV(SILABS, INFINITY_MIC),
+ USLCOM_DEV(SILABS, INGENI_ZIGBEE),
+ USLCOM_DEV(SILABS, INSYS_MODEM),
+ USLCOM_DEV(SILABS, IRZ_SG10),
+ USLCOM_DEV(SILABS, KYOCERA_GPS),
+ USLCOM_DEV(SILABS, LIPOWSKY_HARP),
+ USLCOM_DEV(SILABS, LIPOWSKY_JTAG),
+ USLCOM_DEV(SILABS, LIPOWSKY_LIN),
+ USLCOM_DEV(SILABS, MC35PU),
+ USLCOM_DEV(SILABS, MMB_ZIGBEE),
+ USLCOM_DEV(SILABS, MJS_TOSLINK),
+ USLCOM_DEV(SILABS, MSD_DASHHAWK),
+ USLCOM_DEV(SILABS, MULTIPLEX_RC),
+ USLCOM_DEV(SILABS, OPTRIS_MSPRO),
+ USLCOM_DEV(SILABS, POLOLU),
+ USLCOM_DEV(SILABS, PROCYON_AVS),
+ USLCOM_DEV(SILABS, SB_PARAMOUNT_ME),
+ USLCOM_DEV(SILABS, SUUNTO),
+ USLCOM_DEV(SILABS, TAMSMASTER),
+ USLCOM_DEV(SILABS, TELEGESIS_ETRX2),
+ USLCOM_DEV(SILABS, TRACIENT),
+ USLCOM_DEV(SILABS, TRAQMATE),
+ USLCOM_DEV(SILABS, USBCOUNT50),
+ USLCOM_DEV(SILABS, USBPULSE100),
+ USLCOM_DEV(SILABS, USBSCOPE50),
+ USLCOM_DEV(SILABS, USBWAVE12),
+ USLCOM_DEV(SILABS, V_PREON32),
+ USLCOM_DEV(SILABS, VSTABI),
+ USLCOM_DEV(SILABS, WAVIT),
+ USLCOM_DEV(SILABS, WMRBATT),
+ USLCOM_DEV(SILABS, WMRRIGBLASTER),
+ USLCOM_DEV(SILABS, WMRRIGTALK),
+ USLCOM_DEV(SILABS, ZEPHYR_BIO),
+ USLCOM_DEV(SILABS2, DCU11CLONE),
+ USLCOM_DEV(SILABS3, GPRS_MODEM),
+ USLCOM_DEV(SILABS4, 100EU_MODEM),
+ USLCOM_DEV(SYNTECH, CYPHERLAB100),
+ USLCOM_DEV(USI, MC60),
+ USLCOM_DEV(VAISALA, CABLE),
+ USLCOM_DEV(WAGO, SERVICECABLE),
+ USLCOM_DEV(WAVESENSE, JAZZ),
+ USLCOM_DEV(WESTMOUNTAIN, RIGBLASTER_ADVANTAGE),
+ USLCOM_DEV(WIENERPLEINBAUS, PL512),
+ USLCOM_DEV(WIENERPLEINBAUS, RCM),
+ USLCOM_DEV(WIENERPLEINBAUS, MPOD),
+ USLCOM_DEV(WIENERPLEINBAUS, CML),
+#undef USLCOM_DEV
+};
+
+static device_method_t uslcom_methods[] = {
+ DEVMETHOD(device_probe, uslcom_probe),
+ DEVMETHOD(device_attach, uslcom_attach),
+ DEVMETHOD(device_detach, uslcom_detach),
+ DEVMETHOD_END
+};
+
+static driver_t uslcom_driver = {
+ .name = "uslcom",
+ .methods = uslcom_methods,
+ .size = sizeof(struct uslcom_softc),
+};
+
+DRIVER_MODULE(uslcom, uhub, uslcom_driver, NULL, NULL);
+MODULE_DEPEND(uslcom, ucom, 1, 1, 1);
+MODULE_DEPEND(uslcom, usb, 1, 1, 1);
+MODULE_VERSION(uslcom, 1);
+USB_PNP_HOST_INFO(uslcom_devs);
+
+static void
+uslcom_watchdog(void *arg)
+{
+ struct uslcom_softc *sc = arg;
+
+ mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+ usbd_transfer_start(sc->sc_xfer[USLCOM_CTRL_DT_RD]);
+
+ usb_callout_reset(&sc->sc_watchdog,
+ hz / 4, &uslcom_watchdog, sc);
+}
+
+static int
+uslcom_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ DPRINTFN(11, "\n");
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != USLCOM_CONFIG_INDEX) {
+ return (ENXIO);
+ }
+ return (usbd_lookup_id_by_uaa(uslcom_devs, sizeof(uslcom_devs), uaa));
+}
+
+static int
+uslcom_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct uslcom_softc *sc = device_get_softc(dev);
+ int error;
+
+ DPRINTFN(11, "\n");
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "uslcom", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+ usb_callout_init_mtx(&sc->sc_watchdog, &sc->sc_mtx, 0);
+
+ sc->sc_udev = uaa->device;
+ /* use the interface number from the USB interface descriptor */
+ sc->sc_iface_no = uaa->info.bIfaceNum;
+
+ error = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer, uslcom_config,
+ USLCOM_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error) {
+ DPRINTF("one or more missing USB endpoints, "
+ "error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+ sc->sc_partnum = uslcom_get_partnum(sc);
+
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &uslcom_callback, &sc->sc_mtx);
+ if (error) {
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ return (0);
+
+detach:
+ uslcom_detach(dev);
+ return (ENXIO);
+}
+
+static int
+uslcom_detach(device_t dev)
+{
+ struct uslcom_softc *sc = device_get_softc(dev);
+
+ DPRINTF("sc=%p\n", sc);
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, USLCOM_N_TRANSFER);
+
+ usb_callout_drain(&sc->sc_watchdog);
+
+ device_claim_softc(dev);
+
+ uslcom_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(uslcom);
+
+static void
+uslcom_free_softc(struct uslcom_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+uslcom_free(struct ucom_softc *ucom)
+{
+ uslcom_free_softc(ucom->sc_parent);
+}
+
+static void
+uslcom_cfg_open(struct ucom_softc *ucom)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+
+ req.bmRequestType = USLCOM_WRITE;
+ req.bRequest = USLCOM_IFC_ENABLE;
+ USETW(req.wValue, USLCOM_IFC_ENABLE_EN);
+ USETW(req.wIndex, sc->sc_iface_no);
+ USETW(req.wLength, 0);
+
+ if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000)) {
+ DPRINTF("UART enable failed (ignored)\n");
+ }
+
+ /* clear stall */
+ usbd_xfer_set_stall(sc->sc_xfer[USLCOM_BULK_DT_WR]);
+ usbd_xfer_set_stall(sc->sc_xfer[USLCOM_BULK_DT_RD]);
+
+ /* start polling status */
+ uslcom_watchdog(sc);
+}
+
+static void
+uslcom_cfg_close(struct ucom_softc *ucom)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+
+ /* stop polling status */
+ usb_callout_stop(&sc->sc_watchdog);
+
+ req.bmRequestType = USLCOM_WRITE;
+ req.bRequest = USLCOM_IFC_ENABLE;
+ USETW(req.wValue, USLCOM_IFC_ENABLE_DIS);
+ USETW(req.wIndex, sc->sc_iface_no);
+ USETW(req.wLength, 0);
+
+ if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000)) {
+ DPRINTF("UART disable failed (ignored)\n");
+ }
+}
+
+static uint8_t
+uslcom_get_partnum(struct uslcom_softc *sc)
+{
+ struct usb_device_request req;
+ uint8_t partnum;
+
+ /* Find specific chip type */
+ partnum = 0;
+ req.bmRequestType = USLCOM_READ;
+ req.bRequest = USLCOM_VENDOR_SPECIFIC;
+ USETW(req.wValue, USLCOM_GET_PARTNUM);
+ USETW(req.wIndex, sc->sc_iface_no);
+ USETW(req.wLength, sizeof(partnum));
+
+ if (usbd_do_request_flags(sc->sc_udev, NULL,
+ &req, &partnum, 0, NULL, 1000)) {
+ DPRINTF("GET_PARTNUM failed\n");
+ }
+
+ return(partnum);
+}
+
+static void
+uslcom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+ uint16_t ctl;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ ctl = onoff ? USLCOM_MHS_DTR_ON : 0;
+ ctl |= USLCOM_MHS_DTR_SET;
+
+ req.bmRequestType = USLCOM_WRITE;
+ req.bRequest = USLCOM_SET_MHS;
+ USETW(req.wValue, ctl);
+ USETW(req.wIndex, sc->sc_iface_no);
+ USETW(req.wLength, 0);
+
+ if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000)) {
+ DPRINTF("Setting DTR failed (ignored)\n");
+ }
+}
+
+static void
+uslcom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+ uint16_t ctl;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ ctl = onoff ? USLCOM_MHS_RTS_ON : 0;
+ ctl |= USLCOM_MHS_RTS_SET;
+
+ req.bmRequestType = USLCOM_WRITE;
+ req.bRequest = USLCOM_SET_MHS;
+ USETW(req.wValue, ctl);
+ USETW(req.wIndex, sc->sc_iface_no);
+ USETW(req.wLength, 0);
+
+ if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000)) {
+ DPRINTF("Setting DTR failed (ignored)\n");
+ }
+}
+
+static int
+uslcom_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+ uint32_t maxspeed;
+
+ switch (sc->sc_partnum) {
+ case USLCOM_PARTNUM_CP2104:
+ case USLCOM_PARTNUM_CP2105:
+ maxspeed = 2000000;
+ break;
+ case USLCOM_PARTNUM_CP2101:
+ case USLCOM_PARTNUM_CP2102:
+ case USLCOM_PARTNUM_CP2103:
+ default:
+ /*
+ * Datasheet for cp2102 says 921600 max. Testing shows that
+ * 1228800 and 1843200 work fine.
+ */
+ maxspeed = 1843200;
+ break;
+ }
+ if (t->c_ospeed <= 0 || t->c_ospeed > maxspeed)
+ return (EINVAL);
+ return (0);
+}
+
+static void
+uslcom_cfg_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+ uint32_t baudrate, flowctrl[4];
+ uint16_t data;
+
+ DPRINTF("\n");
+
+ baudrate = t->c_ospeed;
+ req.bmRequestType = USLCOM_WRITE;
+ req.bRequest = USLCOM_SET_BAUDRATE;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, sc->sc_iface_no);
+ USETW(req.wLength, sizeof(baudrate));
+
+ if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, &baudrate, 0, 1000)) {
+ printf("Set baudrate failed (ignored)\n");
+ }
+
+ if (t->c_cflag & CSTOPB)
+ data = USLCOM_STOP_BITS_2;
+ else
+ data = USLCOM_STOP_BITS_1;
+ if (t->c_cflag & PARENB) {
+ if (t->c_cflag & PARODD)
+ data |= USLCOM_PARITY_ODD;
+ else
+ data |= USLCOM_PARITY_EVEN;
+ } else
+ data |= USLCOM_PARITY_NONE;
+ switch (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_SET_LINE_CTL;
+ USETW(req.wValue, data);
+ USETW(req.wIndex, sc->sc_iface_no);
+ USETW(req.wLength, 0);
+
+ if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000)) {
+ DPRINTF("Set format failed (ignored)\n");
+ }
+
+ if (t->c_cflag & CRTSCTS) {
+ flowctrl[0] = htole32(USLCOM_FLOW_DTR_ON | USLCOM_FLOW_CTS_HS);
+ flowctrl[1] = htole32(USLCOM_FLOW_RTS_HS);
+ } else {
+ flowctrl[0] = htole32(USLCOM_FLOW_DTR_ON);
+ flowctrl[1] = htole32(USLCOM_FLOW_RTS_ON);
+ }
+ flowctrl[2] = 0;
+ flowctrl[3] = 0;
+ req.bmRequestType = USLCOM_WRITE;
+ req.bRequest = USLCOM_SET_FLOW;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, sc->sc_iface_no);
+ USETW(req.wLength, sizeof(flowctrl));
+
+ if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, flowctrl, 0, 1000)) {
+ DPRINTF("Set flowcontrol failed (ignored)\n");
+ }
+}
+
+static void
+uslcom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("\n");
+
+ /* XXX Note: sc_lsr is always zero */
+ *lsr = sc->sc_lsr;
+ *msr = sc->sc_msr;
+}
+
+static void
+uslcom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+ uint16_t brk = onoff ? USLCOM_SET_BREAK_ON : USLCOM_SET_BREAK_OFF;
+
+ req.bmRequestType = USLCOM_WRITE;
+ req.bRequest = USLCOM_SET_BREAK;
+ USETW(req.wValue, brk);
+ USETW(req.wIndex, sc->sc_iface_no);
+ USETW(req.wLength, 0);
+
+ if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000)) {
+ DPRINTF("Set BREAK failed (ignored)\n");
+ }
+}
+
+static int
+uslcom_ioctl(struct ucom_softc *ucom, uint32_t cmd, caddr_t data,
+ int flag, struct thread *td)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+ struct usb_device_request req;
+ int error = 0;
+ uint8_t latch;
+
+ DPRINTF("cmd=0x%08x\n", cmd);
+
+ switch (cmd) {
+ case USB_GET_GPIO:
+ if (sc->sc_partnum < USLCOM_PARTNUM_CP2103) {
+ error = ENODEV;
+ break;
+ }
+ req.bmRequestType = USLCOM_READ;
+ req.bRequest = USLCOM_VENDOR_SPECIFIC;
+ USETW(req.wValue, USLCOM_READ_LATCH);
+ USETW(req.wIndex, sc->sc_iface_no);
+ USETW(req.wLength, sizeof(latch));
+
+ if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, &latch, 0, 1000)) {
+ DPRINTF("Get LATCH failed\n");
+ error = EIO;
+ }
+ *(int *)data = latch;
+ break;
+
+ case USB_SET_GPIO:
+ if (sc->sc_partnum < USLCOM_PARTNUM_CP2103)
+ error = ENODEV;
+ else if ((sc->sc_partnum == USLCOM_PARTNUM_CP2103) ||
+ (sc->sc_partnum == USLCOM_PARTNUM_CP2104)) {
+ req.bmRequestType = USLCOM_WRITE;
+ req.bRequest = USLCOM_VENDOR_SPECIFIC;
+ USETW(req.wValue, USLCOM_WRITE_LATCH);
+ USETW(req.wIndex, (*(int *)data));
+ USETW(req.wLength, 0);
+
+ if (ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000)) {
+ DPRINTF("Set LATCH failed\n");
+ error = EIO;
+ }
+ } else
+ error = ENODEV; /* Not yet */
+ break;
+
+ default:
+ DPRINTF("Unknown IOCTL\n");
+ error = ENOIOCTL;
+ break;
+ }
+ return (error);
+}
+
+static void
+uslcom_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uslcom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(&sc->sc_ucom, pc, 0,
+ USLCOM_BULK_BUF_SIZE, &actlen)) {
+ DPRINTF("actlen = %d\n", actlen);
+
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uslcom_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uslcom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uslcom_control_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uslcom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ struct usb_device_request req;
+ uint8_t msr = 0;
+ uint8_t buf;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 1);
+ usbd_copy_out(pc, 0, &buf, sizeof(buf));
+ if (buf & USLCOM_MHS_CTS)
+ msr |= SER_CTS;
+ if (buf & USLCOM_MHS_DSR)
+ msr |= SER_DSR;
+ if (buf & USLCOM_MHS_RI)
+ msr |= SER_RI;
+ if (buf & USLCOM_MHS_DCD)
+ msr |= SER_DCD;
+
+ if (msr != sc->sc_msr) {
+ DPRINTF("status change msr=0x%02x "
+ "(was 0x%02x)\n", msr, sc->sc_msr);
+ sc->sc_msr = msr;
+ ucom_status_change(&sc->sc_ucom);
+ }
+ break;
+
+ case USB_ST_SETUP:
+ req.bmRequestType = USLCOM_READ;
+ req.bRequest = USLCOM_GET_MDMSTS;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, sc->sc_iface_no);
+ USETW(req.wLength, sizeof(buf));
+
+ usbd_xfer_set_frames(xfer, 2);
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, sizeof(buf));
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* error */
+ if (error != USB_ERR_CANCELLED)
+ DPRINTF("error=%s\n", usbd_errstr(error));
+ break;
+ }
+}
+
+static void
+uslcom_start_read(struct ucom_softc *ucom)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+
+ /* start read endpoint */
+ usbd_transfer_start(sc->sc_xfer[USLCOM_BULK_DT_RD]);
+}
+
+static void
+uslcom_stop_read(struct ucom_softc *ucom)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+
+ /* stop read endpoint */
+ usbd_transfer_stop(sc->sc_xfer[USLCOM_BULK_DT_RD]);
+}
+
+static void
+uslcom_start_write(struct ucom_softc *ucom)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[USLCOM_BULK_DT_WR]);
+}
+
+static void
+uslcom_stop_write(struct ucom_softc *ucom)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[USLCOM_BULK_DT_WR]);
+}
+
+static void
+uslcom_poll(struct ucom_softc *ucom)
+{
+ struct uslcom_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_xfer, USLCOM_N_TRANSFER);
+}
diff --git a/sys/dev/usb/serial/uvisor.c b/sys/dev/usb/serial/uvisor.c
new file mode 100644
index 000000000000..35640624093e
--- /dev/null
+++ b/sys/dev/usb/serial/uvisor.c
@@ -0,0 +1,675 @@
+/* $NetBSD: uvisor.c,v 1.9 2001/01/23 14:04:14 augustss Exp $ */
+
+/* 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 $
+ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR uvisor_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#ifdef USB_DEBUG
+static int uvisor_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uvisor, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB uvisor");
+SYSCTL_INT(_hw_usb_uvisor, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &uvisor_debug, 0, "Debug level");
+#endif
+
+#define UVISOR_CONFIG_INDEX 0
+#define UVISOR_IFACE_INDEX 0
+
+/*
+ * The following buffer sizes are hardcoded due to the way the Palm
+ * firmware works. It looks like the device is not short terminating
+ * the data transferred.
+ */
+#define UVISORIBUFSIZE 0 /* Use wMaxPacketSize */
+#define UVISOROBUFSIZE 32 /* bytes */
+#define UVISOROFRAMES 32 /* units */
+
+/* From the Linux driver */
+/*
+ * UVISOR_REQUEST_BYTES_AVAILABLE asks the visor for the number of bytes that
+ * are available to be transferred 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;
+ } __packed connections[UVISOR_MAX_CONN];
+} __packed;
+
+#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;
+ } __packed connections[UVISOR_MAX_CONN];
+} __packed;
+
+enum {
+ UVISOR_BULK_DT_WR,
+ UVISOR_BULK_DT_RD,
+ UVISOR_N_TRANSFER,
+};
+
+struct uvisor_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+
+ struct usb_xfer *sc_xfer[UVISOR_N_TRANSFER];
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+
+ uint16_t sc_flag;
+#define UVISOR_FLAG_PALM4 0x0001
+#define UVISOR_FLAG_VISOR 0x0002
+#define UVISOR_FLAG_PALM35 0x0004
+#define UVISOR_FLAG_SEND_NOTIFY 0x0008
+
+ uint8_t sc_iface_no;
+ uint8_t sc_iface_index;
+};
+
+/* prototypes */
+
+static device_probe_t uvisor_probe;
+static device_attach_t uvisor_attach;
+static device_detach_t uvisor_detach;
+static void uvisor_free_softc(struct uvisor_softc *);
+
+static usb_callback_t uvisor_write_callback;
+static usb_callback_t uvisor_read_callback;
+
+static usb_error_t uvisor_init(struct uvisor_softc *, struct usb_device *,
+ struct usb_config *);
+static void uvisor_free(struct ucom_softc *);
+static void uvisor_cfg_open(struct ucom_softc *);
+static void uvisor_cfg_close(struct ucom_softc *);
+static void uvisor_start_read(struct ucom_softc *);
+static void uvisor_stop_read(struct ucom_softc *);
+static void uvisor_start_write(struct ucom_softc *);
+static void uvisor_stop_write(struct ucom_softc *);
+
+static const struct usb_config uvisor_config[UVISOR_N_TRANSFER] = {
+ [UVISOR_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UVISOROBUFSIZE * UVISOROFRAMES,
+ .frames = UVISOROFRAMES,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &uvisor_write_callback,
+ },
+
+ [UVISOR_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UVISORIBUFSIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &uvisor_read_callback,
+ },
+};
+
+static const struct ucom_callback uvisor_callback = {
+ .ucom_cfg_open = &uvisor_cfg_open,
+ .ucom_cfg_close = &uvisor_cfg_close,
+ .ucom_start_read = &uvisor_start_read,
+ .ucom_stop_read = &uvisor_stop_read,
+ .ucom_start_write = &uvisor_start_write,
+ .ucom_stop_write = &uvisor_stop_write,
+ .ucom_free = &uvisor_free,
+};
+
+static device_method_t uvisor_methods[] = {
+ DEVMETHOD(device_probe, uvisor_probe),
+ DEVMETHOD(device_attach, uvisor_attach),
+ DEVMETHOD(device_detach, uvisor_detach),
+ DEVMETHOD_END
+};
+
+static driver_t uvisor_driver = {
+ .name = "uvisor",
+ .methods = uvisor_methods,
+ .size = sizeof(struct uvisor_softc),
+};
+
+static const STRUCT_USB_HOST_ID uvisor_devs[] = {
+#define UVISOR_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
+ UVISOR_DEV(ACEECA, MEZ1000, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(ALPHASMART, DANA_SYNC, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(GARMIN, IQUE_3600, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(FOSSIL, WRISTPDA, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(HANDSPRING, VISOR, UVISOR_FLAG_VISOR),
+ UVISOR_DEV(HANDSPRING, TREO, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(HANDSPRING, TREO600, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(PALM, M500, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(PALM, M505, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(PALM, M515, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(PALM, I705, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(PALM, M125, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(PALM, M130, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(PALM, TUNGSTEN_Z, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(PALM, TUNGSTEN_T, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(PALM, ZIRE, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(PALM, ZIRE31, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(SAMSUNG, I500, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(SONY, CLIE_40, 0),
+ UVISOR_DEV(SONY, CLIE_41, 0),
+ UVISOR_DEV(SONY, CLIE_S360, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(SONY, CLIE_NX60, UVISOR_FLAG_PALM4),
+ UVISOR_DEV(SONY, CLIE_35, UVISOR_FLAG_PALM35),
+/* UVISOR_DEV(SONY, CLIE_25, UVISOR_FLAG_PALM4 ), */
+ UVISOR_DEV(SONY, CLIE_TJ37, UVISOR_FLAG_PALM4),
+/* UVISOR_DEV(SONY, CLIE_TH55, UVISOR_FLAG_PALM4 ), See PR 80935 */
+ UVISOR_DEV(TAPWAVE, ZODIAC, UVISOR_FLAG_PALM4),
+#undef UVISOR_DEV
+};
+
+DRIVER_MODULE(uvisor, uhub, uvisor_driver, NULL, NULL);
+MODULE_DEPEND(uvisor, ucom, 1, 1, 1);
+MODULE_DEPEND(uvisor, usb, 1, 1, 1);
+MODULE_VERSION(uvisor, 1);
+USB_PNP_HOST_INFO(uvisor_devs);
+
+static int
+uvisor_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != UVISOR_CONFIG_INDEX) {
+ return (ENXIO);
+ }
+ if (uaa->info.bIfaceIndex != UVISOR_IFACE_INDEX) {
+ return (ENXIO);
+ }
+ return (usbd_lookup_id_by_uaa(uvisor_devs, sizeof(uvisor_devs), uaa));
+}
+
+static int
+uvisor_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct uvisor_softc *sc = device_get_softc(dev);
+ struct usb_config uvisor_config_copy[UVISOR_N_TRANSFER];
+ int error;
+
+ DPRINTF("sc=%p\n", sc);
+ memcpy(uvisor_config_copy, uvisor_config,
+ sizeof(uvisor_config_copy));
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, "uvisor", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ sc->sc_udev = uaa->device;
+
+ /* configure the device */
+
+ sc->sc_flag = USB_GET_DRIVER_INFO(uaa);
+ sc->sc_iface_no = uaa->info.bIfaceNum;
+ sc->sc_iface_index = UVISOR_IFACE_INDEX;
+
+ error = uvisor_init(sc, uaa->device, uvisor_config_copy);
+
+ if (error) {
+ DPRINTF("init failed, error=%s\n",
+ usbd_errstr(error));
+ goto detach;
+ }
+ error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index,
+ sc->sc_xfer, uvisor_config_copy, UVISOR_N_TRANSFER,
+ sc, &sc->sc_mtx);
+ if (error) {
+ DPRINTF("could not allocate all pipes\n");
+ goto detach;
+ }
+
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &uvisor_callback, &sc->sc_mtx);
+ if (error) {
+ DPRINTF("ucom_attach failed\n");
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ return (0);
+
+detach:
+ uvisor_detach(dev);
+ return (ENXIO);
+}
+
+static int
+uvisor_detach(device_t dev)
+{
+ struct uvisor_softc *sc = device_get_softc(dev);
+
+ DPRINTF("sc=%p\n", sc);
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, UVISOR_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ uvisor_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(uvisor);
+
+static void
+uvisor_free_softc(struct uvisor_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+uvisor_free(struct ucom_softc *ucom)
+{
+ uvisor_free_softc(ucom->sc_parent);
+}
+
+static usb_error_t
+uvisor_init(struct uvisor_softc *sc, struct usb_device *udev, struct usb_config *config)
+{
+ usb_error_t err = 0;
+ struct usb_device_request req;
+ struct uvisor_connection_info coninfo;
+ struct uvisor_palm_connection_info pconinfo;
+ uint16_t actlen;
+ uint8_t buffer[256];
+
+ if (sc->sc_flag & UVISOR_FLAG_VISOR) {
+ DPRINTF("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(udev, NULL,
+ &req, &coninfo, USB_SHORT_XFER_OK,
+ &actlen, USB_DEFAULT_TIMEOUT);
+
+ if (err) {
+ goto done;
+ }
+ }
+#ifdef USB_DEBUG
+ if (sc->sc_flag & UVISOR_FLAG_VISOR) {
+ uint16_t i, np;
+ const char *desc;
+
+ np = UGETW(coninfo.num_ports);
+ if (np > UVISOR_MAX_CONN) {
+ np = UVISOR_MAX_CONN;
+ }
+ DPRINTF("Number of ports: %d\n", np);
+
+ for (i = 0; i < np; ++i) {
+ switch (coninfo.connections[i].port_function_id) {
+ case UVISOR_FUNCTION_GENERIC:
+ desc = "Generic";
+ break;
+ case UVISOR_FUNCTION_DEBUGGER:
+ desc = "Debugger";
+ break;
+ case UVISOR_FUNCTION_HOTSYNC:
+ desc = "HotSync";
+ break;
+ case UVISOR_FUNCTION_REMOTE_FILE_SYS:
+ desc = "Remote File System";
+ break;
+ default:
+ desc = "unknown";
+ break;
+ }
+ DPRINTF("Port %d is for %s\n",
+ coninfo.connections[i].port, desc);
+ }
+ }
+#endif
+
+ if (sc->sc_flag & UVISOR_FLAG_PALM4) {
+ uint8_t 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
+ (udev, NULL, &req, &pconinfo, USB_SHORT_XFER_OK,
+ &actlen, USB_DEFAULT_TIMEOUT);
+
+ if (err) {
+ goto done;
+ }
+ if (actlen < 12) {
+ DPRINTF("too little data\n");
+ err = USB_ERR_INVAL;
+ goto done;
+ }
+ if (pconinfo.endpoint_numbers_different) {
+ port = pconinfo.connections[0].end_point_info;
+ config[0].endpoint = (port & 0xF); /* output */
+ config[1].endpoint = (port >> 4); /* input */
+ } else {
+ port = pconinfo.connections[0].port;
+ config[0].endpoint = (port & 0xF); /* output */
+ config[1].endpoint = (port & 0xF); /* input */
+ }
+#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(udev, &req, buffer);
+ if (err) {
+ goto done;
+ }
+#endif
+ }
+ if (sc->sc_flag & UVISOR_FLAG_PALM35) {
+ /* get the config number */
+ DPRINTF("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(udev, NULL, &req, buffer);
+ if (err) {
+ goto done;
+ }
+ /* get the interface number */
+ DPRINTF("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(udev, NULL, &req, buffer);
+ if (err) {
+ goto done;
+ }
+ }
+#if 0
+ uWord wAvail;
+
+ DPRINTF("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(wAvail));
+ err = usbd_do_request(udev, NULL, &req, &wAvail);
+ if (err) {
+ goto done;
+ }
+ DPRINTF("avail=%d\n", UGETW(wAvail));
+#endif
+
+ DPRINTF("done\n");
+done:
+ return (err);
+}
+
+static void
+uvisor_cfg_open(struct ucom_softc *ucom)
+{
+ return;
+}
+
+static void
+uvisor_cfg_close(struct ucom_softc *ucom)
+{
+ struct uvisor_softc *sc = ucom->sc_parent;
+ uint8_t buffer[UVISOR_CONNECTION_INFO_SIZE];
+ struct usb_device_request req;
+ usb_error_t err;
+
+ 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);
+
+ err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, buffer, 0, 1000);
+ if (err) {
+ DPRINTFN(0, "close notification failed, error=%s\n",
+ usbd_errstr(err));
+ }
+}
+
+static void
+uvisor_start_read(struct ucom_softc *ucom)
+{
+ struct uvisor_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UVISOR_BULK_DT_RD]);
+}
+
+static void
+uvisor_stop_read(struct ucom_softc *ucom)
+{
+ struct uvisor_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UVISOR_BULK_DT_RD]);
+}
+
+static void
+uvisor_start_write(struct ucom_softc *ucom)
+{
+ struct uvisor_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UVISOR_BULK_DT_WR]);
+}
+
+static void
+uvisor_stop_write(struct ucom_softc *ucom)
+{
+ struct uvisor_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UVISOR_BULK_DT_WR]);
+}
+
+static void
+uvisor_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uvisor_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+ uint8_t x;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ for (x = 0; x != UVISOROFRAMES; x++) {
+ usbd_xfer_set_frame_offset(xfer,
+ x * UVISOROBUFSIZE, x);
+
+ pc = usbd_xfer_get_frame(xfer, x);
+ if (ucom_get_data(&sc->sc_ucom, pc, 0,
+ UVISOROBUFSIZE, &actlen)) {
+ usbd_xfer_set_frame_len(xfer, x, actlen);
+ } else {
+ break;
+ }
+ }
+ /* check for data */
+ if (x != 0) {
+ usbd_xfer_set_frames(xfer, x);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+uvisor_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uvisor_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
diff --git a/sys/dev/usb/serial/uvscom.c b/sys/dev/usb/serial/uvscom.c
new file mode 100644
index 000000000000..b9add5c1b37b
--- /dev/null
+++ b/sys/dev/usb/serial/uvscom.c
@@ -0,0 +1,766 @@
+/* $NetBSD: usb/uvscom.c,v 1.1 2002/03/19 15:08:42 augustss Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ */
+
+/*
+ * 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 <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR uvscom_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+
+#include <dev/usb/serial/usb_serial.h>
+
+#ifdef USB_DEBUG
+static int uvscom_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uvscom, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB uvscom");
+SYSCTL_INT(_hw_usb_uvscom, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &uvscom_debug, 0, "Debug level");
+#endif
+
+#define UVSCOM_MODVER 1 /* module version */
+
+#define UVSCOM_CONFIG_INDEX 0
+#define UVSCOM_IFACE_INDEX 0
+
+/* 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
+#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)
+
+#define UVSCOM_BULK_BUF_SIZE 1024 /* bytes */
+
+enum {
+ UVSCOM_BULK_DT_WR,
+ UVSCOM_BULK_DT_RD,
+ UVSCOM_INTR_DT_RD,
+ UVSCOM_N_TRANSFER,
+};
+
+struct uvscom_softc {
+ struct ucom_super_softc sc_super_ucom;
+ struct ucom_softc sc_ucom;
+
+ struct usb_xfer *sc_xfer[UVSCOM_N_TRANSFER];
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+
+ uint16_t sc_line; /* line control register */
+
+ uint8_t sc_iface_no; /* interface number */
+ uint8_t sc_iface_index; /* interface index */
+ uint8_t sc_lsr; /* local status register */
+ uint8_t sc_msr; /* uvscom status register */
+ uint8_t sc_unit_status; /* unit status */
+};
+
+/* prototypes */
+
+static device_probe_t uvscom_probe;
+static device_attach_t uvscom_attach;
+static device_detach_t uvscom_detach;
+static void uvscom_free_softc(struct uvscom_softc *);
+
+static usb_callback_t uvscom_write_callback;
+static usb_callback_t uvscom_read_callback;
+static usb_callback_t uvscom_intr_callback;
+
+static void uvscom_free(struct ucom_softc *);
+static void uvscom_cfg_set_dtr(struct ucom_softc *, uint8_t);
+static void uvscom_cfg_set_rts(struct ucom_softc *, uint8_t);
+static void uvscom_cfg_set_break(struct ucom_softc *, uint8_t);
+static int uvscom_pre_param(struct ucom_softc *, struct termios *);
+static void uvscom_cfg_param(struct ucom_softc *, struct termios *);
+static int uvscom_pre_open(struct ucom_softc *);
+static void uvscom_cfg_open(struct ucom_softc *);
+static void uvscom_cfg_close(struct ucom_softc *);
+static void uvscom_start_read(struct ucom_softc *);
+static void uvscom_stop_read(struct ucom_softc *);
+static void uvscom_start_write(struct ucom_softc *);
+static void uvscom_stop_write(struct ucom_softc *);
+static void uvscom_cfg_get_status(struct ucom_softc *, uint8_t *,
+ uint8_t *);
+static void uvscom_cfg_write(struct uvscom_softc *, uint8_t, uint16_t);
+static uint16_t uvscom_cfg_read_status(struct uvscom_softc *);
+static void uvscom_poll(struct ucom_softc *ucom);
+
+static const struct usb_config uvscom_config[UVSCOM_N_TRANSFER] = {
+ [UVSCOM_BULK_DT_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UVSCOM_BULK_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = &uvscom_write_callback,
+ },
+
+ [UVSCOM_BULK_DT_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UVSCOM_BULK_BUF_SIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = &uvscom_read_callback,
+ },
+
+ [UVSCOM_INTR_DT_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &uvscom_intr_callback,
+ },
+};
+
+static const struct ucom_callback uvscom_callback = {
+ .ucom_cfg_get_status = &uvscom_cfg_get_status,
+ .ucom_cfg_set_dtr = &uvscom_cfg_set_dtr,
+ .ucom_cfg_set_rts = &uvscom_cfg_set_rts,
+ .ucom_cfg_set_break = &uvscom_cfg_set_break,
+ .ucom_cfg_param = &uvscom_cfg_param,
+ .ucom_cfg_open = &uvscom_cfg_open,
+ .ucom_cfg_close = &uvscom_cfg_close,
+ .ucom_pre_open = &uvscom_pre_open,
+ .ucom_pre_param = &uvscom_pre_param,
+ .ucom_start_read = &uvscom_start_read,
+ .ucom_stop_read = &uvscom_stop_read,
+ .ucom_start_write = &uvscom_start_write,
+ .ucom_stop_write = &uvscom_stop_write,
+ .ucom_poll = &uvscom_poll,
+ .ucom_free = &uvscom_free,
+};
+
+static const STRUCT_USB_HOST_ID uvscom_devs[] = {
+ /* SUNTAC U-Cable type A4 */
+ {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_AS144L4, 0)},
+ /* SUNTAC U-Cable type D2 */
+ {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_DS96L, 0)},
+ /* SUNTAC Ir-Trinity */
+ {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_IS96U, 0)},
+ /* SUNTAC U-Cable type P1 */
+ {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_PS64P1, 0)},
+ /* SUNTAC Slipper U */
+ {USB_VPI(USB_VENDOR_SUNTAC, USB_PRODUCT_SUNTAC_VS10U, 0)},
+};
+
+static device_method_t uvscom_methods[] = {
+ DEVMETHOD(device_probe, uvscom_probe),
+ DEVMETHOD(device_attach, uvscom_attach),
+ DEVMETHOD(device_detach, uvscom_detach),
+ DEVMETHOD_END
+};
+
+static driver_t uvscom_driver = {
+ .name = "uvscom",
+ .methods = uvscom_methods,
+ .size = sizeof(struct uvscom_softc),
+};
+
+DRIVER_MODULE(uvscom, uhub, uvscom_driver, NULL, NULL);
+MODULE_DEPEND(uvscom, ucom, 1, 1, 1);
+MODULE_DEPEND(uvscom, usb, 1, 1, 1);
+MODULE_VERSION(uvscom, UVSCOM_MODVER);
+USB_PNP_HOST_INFO(uvscom_devs);
+
+static int
+uvscom_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ if (uaa->info.bConfigIndex != UVSCOM_CONFIG_INDEX) {
+ return (ENXIO);
+ }
+ if (uaa->info.bIfaceIndex != UVSCOM_IFACE_INDEX) {
+ return (ENXIO);
+ }
+ return (usbd_lookup_id_by_uaa(uvscom_devs, sizeof(uvscom_devs), uaa));
+}
+
+static int
+uvscom_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct uvscom_softc *sc = device_get_softc(dev);
+ int error;
+
+ device_set_usb_desc(dev);
+ mtx_init(&sc->sc_mtx, "uvscom", NULL, MTX_DEF);
+ ucom_ref(&sc->sc_super_ucom);
+
+ sc->sc_udev = uaa->device;
+
+ DPRINTF("sc=%p\n", sc);
+
+ sc->sc_iface_no = uaa->info.bIfaceNum;
+ sc->sc_iface_index = UVSCOM_IFACE_INDEX;
+
+ error = usbd_transfer_setup(uaa->device, &sc->sc_iface_index,
+ sc->sc_xfer, uvscom_config, UVSCOM_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error) {
+ DPRINTF("could not allocate all USB transfers!\n");
+ goto detach;
+ }
+ sc->sc_line = UVSCOM_LINE_INIT;
+
+ /* clear stall at first run */
+ mtx_lock(&sc->sc_mtx);
+ usbd_xfer_set_stall(sc->sc_xfer[UVSCOM_BULK_DT_WR]);
+ usbd_xfer_set_stall(sc->sc_xfer[UVSCOM_BULK_DT_RD]);
+ mtx_unlock(&sc->sc_mtx);
+
+ error = ucom_attach(&sc->sc_super_ucom, &sc->sc_ucom, 1, sc,
+ &uvscom_callback, &sc->sc_mtx);
+ if (error) {
+ goto detach;
+ }
+ ucom_set_pnpinfo_usb(&sc->sc_super_ucom, dev);
+
+ /* start interrupt pipe */
+ mtx_lock(&sc->sc_mtx);
+ usbd_transfer_start(sc->sc_xfer[UVSCOM_INTR_DT_RD]);
+ mtx_unlock(&sc->sc_mtx);
+
+ return (0);
+
+detach:
+ uvscom_detach(dev);
+ return (ENXIO);
+}
+
+static int
+uvscom_detach(device_t dev)
+{
+ struct uvscom_softc *sc = device_get_softc(dev);
+
+ DPRINTF("sc=%p\n", sc);
+
+ /* stop interrupt pipe */
+
+ if (sc->sc_xfer[UVSCOM_INTR_DT_RD])
+ usbd_transfer_stop(sc->sc_xfer[UVSCOM_INTR_DT_RD]);
+
+ ucom_detach(&sc->sc_super_ucom, &sc->sc_ucom);
+ usbd_transfer_unsetup(sc->sc_xfer, UVSCOM_N_TRANSFER);
+
+ device_claim_softc(dev);
+
+ uvscom_free_softc(sc);
+
+ return (0);
+}
+
+UCOM_UNLOAD_DRAIN(uvscom);
+
+static void
+uvscom_free_softc(struct uvscom_softc *sc)
+{
+ if (ucom_unref(&sc->sc_super_ucom)) {
+ mtx_destroy(&sc->sc_mtx);
+ device_free_softc(sc);
+ }
+}
+
+static void
+uvscom_free(struct ucom_softc *ucom)
+{
+ uvscom_free_softc(ucom->sc_parent);
+}
+
+static void
+uvscom_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uvscom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+tr_setup:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (ucom_get_data(&sc->sc_ucom, pc, 0,
+ UVSCOM_BULK_BUF_SIZE, &actlen)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uvscom_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uvscom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ ucom_put_data(&sc->sc_ucom, pc, 0, actlen);
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uvscom_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uvscom_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_page_cache *pc;
+ uint8_t buf[2];
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (actlen >= 2) {
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, buf, sizeof(buf));
+
+ sc->sc_lsr = 0;
+ sc->sc_msr = 0;
+ sc->sc_unit_status = buf[1];
+
+ if (buf[0] & UVSCOM_TXRDY) {
+ sc->sc_lsr |= ULSR_TXRDY;
+ }
+ if (buf[0] & UVSCOM_RXRDY) {
+ sc->sc_lsr |= ULSR_RXRDY;
+ }
+ if (buf[1] & UVSCOM_CTS) {
+ sc->sc_msr |= SER_CTS;
+ }
+ if (buf[1] & UVSCOM_DSR) {
+ sc->sc_msr |= SER_DSR;
+ }
+ if (buf[1] & UVSCOM_DCD) {
+ sc->sc_msr |= SER_DCD;
+ }
+ /*
+ * the UCOM layer will ignore this call if the TTY
+ * device is closed!
+ */
+ ucom_status_change(&sc->sc_ucom);
+ }
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+static void
+uvscom_cfg_set_dtr(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uvscom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ if (onoff)
+ sc->sc_line |= UVSCOM_DTR;
+ else
+ sc->sc_line &= ~UVSCOM_DTR;
+
+ uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line);
+}
+
+static void
+uvscom_cfg_set_rts(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uvscom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ if (onoff)
+ sc->sc_line |= UVSCOM_RTS;
+ else
+ sc->sc_line &= ~UVSCOM_RTS;
+
+ uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line);
+}
+
+static void
+uvscom_cfg_set_break(struct ucom_softc *ucom, uint8_t onoff)
+{
+ struct uvscom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("onoff = %d\n", onoff);
+
+ if (onoff)
+ sc->sc_line |= UVSCOM_BREAK;
+ else
+ sc->sc_line &= ~UVSCOM_BREAK;
+
+ uvscom_cfg_write(sc, UVSCOM_LINE_CTL, sc->sc_line);
+}
+
+static int
+uvscom_pre_param(struct ucom_softc *ucom, struct termios *t)
+{
+ switch (t->c_ospeed) {
+ case B150:
+ case B300:
+ case B600:
+ case B1200:
+ case B2400:
+ case B4800:
+ case B9600:
+ case B19200:
+ case B38400:
+ case B57600:
+ case B115200:
+ default:
+ return (EINVAL);
+ }
+ return (0);
+}
+
+static void
+uvscom_cfg_param(struct ucom_softc *ucom, struct termios *t)
+{
+ struct uvscom_softc *sc = ucom->sc_parent;
+ uint16_t value;
+
+ DPRINTF("\n");
+
+ switch (t->c_ospeed) {
+ case B150:
+ value = UVSCOM_SPEED_150BPS;
+ break;
+ case B300:
+ value = UVSCOM_SPEED_300BPS;
+ break;
+ case B600:
+ value = UVSCOM_SPEED_600BPS;
+ break;
+ case B1200:
+ value = UVSCOM_SPEED_1200BPS;
+ break;
+ case B2400:
+ value = UVSCOM_SPEED_2400BPS;
+ break;
+ case B4800:
+ value = UVSCOM_SPEED_4800BPS;
+ break;
+ case B9600:
+ value = UVSCOM_SPEED_9600BPS;
+ break;
+ case B19200:
+ value = UVSCOM_SPEED_19200BPS;
+ break;
+ case B38400:
+ value = UVSCOM_SPEED_38400BPS;
+ break;
+ case B57600:
+ value = UVSCOM_SPEED_57600BPS;
+ break;
+ case B115200:
+ value = UVSCOM_SPEED_115200BPS;
+ break;
+ default:
+ return;
+ }
+
+ uvscom_cfg_write(sc, UVSCOM_SET_SPEED, value);
+
+ value = 0;
+
+ if (t->c_cflag & CSTOPB) {
+ value |= UVSCOM_STOP_BIT_2;
+ }
+ if (t->c_cflag & PARENB) {
+ if (t->c_cflag & PARODD) {
+ value |= UVSCOM_PARITY_ODD;
+ } else {
+ value |= UVSCOM_PARITY_EVEN;
+ }
+ } else {
+ value |= UVSCOM_PARITY_NONE;
+ }
+
+ switch (t->c_cflag & CSIZE) {
+ case CS5:
+ value |= UVSCOM_DATA_BIT_5;
+ break;
+ case CS6:
+ value |= UVSCOM_DATA_BIT_6;
+ break;
+ case CS7:
+ value |= UVSCOM_DATA_BIT_7;
+ break;
+ default:
+ case CS8:
+ value |= UVSCOM_DATA_BIT_8;
+ break;
+ }
+
+ uvscom_cfg_write(sc, UVSCOM_SET_PARAM, value);
+}
+
+static int
+uvscom_pre_open(struct ucom_softc *ucom)
+{
+ struct uvscom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("sc = %p\n", sc);
+
+ /* check if PC card was inserted */
+
+ if (sc->sc_unit_status & UVSCOM_NOCARD) {
+ DPRINTF("no PC card!\n");
+ return (ENXIO);
+ }
+ return (0);
+}
+
+static void
+uvscom_cfg_open(struct ucom_softc *ucom)
+{
+ struct uvscom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("sc = %p\n", sc);
+
+ uvscom_cfg_read_status(sc);
+}
+
+static void
+uvscom_cfg_close(struct ucom_softc *ucom)
+{
+ struct uvscom_softc *sc = ucom->sc_parent;
+
+ DPRINTF("sc=%p\n", sc);
+
+ uvscom_cfg_write(sc, UVSCOM_SHUTDOWN, 0);
+}
+
+static void
+uvscom_start_read(struct ucom_softc *ucom)
+{
+ struct uvscom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UVSCOM_BULK_DT_RD]);
+}
+
+static void
+uvscom_stop_read(struct ucom_softc *ucom)
+{
+ struct uvscom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UVSCOM_BULK_DT_RD]);
+}
+
+static void
+uvscom_start_write(struct ucom_softc *ucom)
+{
+ struct uvscom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_start(sc->sc_xfer[UVSCOM_BULK_DT_WR]);
+}
+
+static void
+uvscom_stop_write(struct ucom_softc *ucom)
+{
+ struct uvscom_softc *sc = ucom->sc_parent;
+
+ usbd_transfer_stop(sc->sc_xfer[UVSCOM_BULK_DT_WR]);
+}
+
+static void
+uvscom_cfg_get_status(struct ucom_softc *ucom, uint8_t *lsr, uint8_t *msr)
+{
+ struct uvscom_softc *sc = ucom->sc_parent;
+
+ *lsr = sc->sc_lsr;
+ *msr = sc->sc_msr;
+}
+
+static void
+uvscom_cfg_write(struct uvscom_softc *sc, uint8_t index, uint16_t value)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = index;
+ USETW(req.wValue, value);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+
+ err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, NULL, 0, 1000);
+ if (err) {
+ DPRINTFN(0, "device request failed, err=%s "
+ "(ignored)\n", usbd_errstr(err));
+ }
+}
+
+static uint16_t
+uvscom_cfg_read_status(struct uvscom_softc *sc)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+ uint8_t data[2];
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = UVSCOM_READ_STATUS;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 2);
+
+ err = ucom_cfg_do_request(sc->sc_udev, &sc->sc_ucom,
+ &req, data, 0, 1000);
+ if (err) {
+ DPRINTFN(0, "device request failed, err=%s "
+ "(ignored)\n", usbd_errstr(err));
+ }
+ return (data[0] | (data[1] << 8));
+}
+
+static void
+uvscom_poll(struct ucom_softc *ucom)
+{
+ struct uvscom_softc *sc = ucom->sc_parent;
+ usbd_transfer_poll(sc->sc_xfer, UVSCOM_N_TRANSFER);
+}
diff --git a/sys/dev/usb/storage/cfumass.c b/sys/dev/usb/storage/cfumass.c
new file mode 100644
index 000000000000..24b0ca4269b7
--- /dev/null
+++ b/sys/dev/usb/storage/cfumass.c
@@ -0,0 +1,992 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2016 The FreeBSD Foundation
+ *
+ * This software was developed by Edward Tomasz Napierala under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+/*
+ * USB Mass Storage Class Bulk-Only (BBB) Transport target.
+ *
+ * http://www.usb.org/developers/docs/devclass_docs/usbmassbulk_10.pdf
+ *
+ * This code implements the USB Mass Storage frontend driver for the CAM
+ * Target Layer (ctl(4)) subsystem.
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/refcount.h>
+#include <sys/stdint.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include "usbdevs.h"
+#include "usb_if.h"
+
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_da.h>
+#include <cam/ctl/ctl_io.h>
+#include <cam/ctl/ctl.h>
+#include <cam/ctl/ctl_backend.h>
+#include <cam/ctl/ctl_error.h>
+#include <cam/ctl/ctl_frontend.h>
+#include <cam/ctl/ctl_debug.h>
+#include <cam/ctl/ctl_ha.h>
+#include <cam/ctl/ctl_ioctl.h>
+#include <cam/ctl/ctl_private.h>
+
+SYSCTL_NODE(_hw_usb, OID_AUTO, cfumass, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "CAM Target Layer USB Mass Storage Frontend");
+static int debug = 1;
+SYSCTL_INT(_hw_usb_cfumass, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &debug, 1, "Enable debug messages");
+static int max_lun = 0;
+SYSCTL_INT(_hw_usb_cfumass, OID_AUTO, max_lun, CTLFLAG_RWTUN,
+ &max_lun, 1, "Maximum advertised LUN number");
+static int ignore_stop = 1;
+SYSCTL_INT(_hw_usb_cfumass, OID_AUTO, ignore_stop, CTLFLAG_RWTUN,
+ &ignore_stop, 1, "Ignore START STOP UNIT with START and LOEJ bits cleared");
+
+/*
+ * The driver uses a single, global CTL port. It could create its ports
+ * in cfumass_attach() instead, but that would make it impossible to specify
+ * "port cfumass0" in ctl.conf(5), as the port generally wouldn't exist
+ * at the time ctld(8) gets run.
+ */
+struct ctl_port cfumass_port;
+bool cfumass_port_online;
+volatile u_int cfumass_refcount;
+
+#ifndef CFUMASS_BULK_SIZE
+#define CFUMASS_BULK_SIZE (1U << 17) /* bytes */
+#endif
+
+/*
+ * USB transfer definitions.
+ */
+#define CFUMASS_T_COMMAND 0
+#define CFUMASS_T_DATA_OUT 1
+#define CFUMASS_T_DATA_IN 2
+#define CFUMASS_T_STATUS 3
+#define CFUMASS_T_MAX 4
+
+/*
+ * USB interface specific control requests.
+ */
+#define UR_RESET 0xff /* Bulk-Only Mass Storage Reset */
+#define UR_GET_MAX_LUN 0xfe /* Get Max LUN */
+
+/*
+ * Command Block Wrapper.
+ */
+struct cfumass_cbw_t {
+ uDWord dCBWSignature;
+#define CBWSIGNATURE 0x43425355 /* "USBC" */
+ uDWord dCBWTag;
+ uDWord dCBWDataTransferLength;
+ uByte bCBWFlags;
+#define CBWFLAGS_OUT 0x00
+#define CBWFLAGS_IN 0x80
+ uByte bCBWLUN;
+ uByte bCDBLength;
+#define CBWCBLENGTH 16
+ uByte CBWCB[CBWCBLENGTH];
+} __packed;
+
+#define CFUMASS_CBW_SIZE 31
+CTASSERT(sizeof(struct cfumass_cbw_t) == CFUMASS_CBW_SIZE);
+
+/*
+ * Command Status Wrapper.
+ */
+struct cfumass_csw_t {
+ uDWord dCSWSignature;
+#define CSWSIGNATURE 0x53425355 /* "USBS" */
+ uDWord dCSWTag;
+ uDWord dCSWDataResidue;
+ uByte bCSWStatus;
+#define CSWSTATUS_GOOD 0x0
+#define CSWSTATUS_FAILED 0x1
+#define CSWSTATUS_PHASE 0x2
+} __packed;
+
+#define CFUMASS_CSW_SIZE 13
+CTASSERT(sizeof(struct cfumass_csw_t) == CFUMASS_CSW_SIZE);
+
+struct cfumass_softc {
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+ struct usb_xfer *sc_xfer[CFUMASS_T_MAX];
+
+ struct cfumass_cbw_t *sc_cbw;
+ struct cfumass_csw_t *sc_csw;
+
+ struct mtx sc_mtx;
+ int sc_online;
+ int sc_ctl_initid;
+
+ /*
+ * This is used to communicate between CTL callbacks
+ * and USB callbacks; basically, it holds the state
+ * for the current command ("the" command, since there
+ * is no queueing in USB Mass Storage).
+ */
+ bool sc_current_stalled;
+
+ /*
+ * The following are set upon receiving a SCSI command.
+ */
+ int sc_current_tag;
+ int sc_current_transfer_length;
+ int sc_current_flags;
+
+ /*
+ * The following are set in ctl_datamove().
+ */
+ int sc_current_residue;
+ union ctl_io *sc_ctl_io;
+
+ /*
+ * The following is set in cfumass_done().
+ */
+ int sc_current_status;
+
+ /*
+ * Number of requests queued to CTL.
+ */
+ volatile u_int sc_queued;
+};
+
+/*
+ * USB interface.
+ */
+static device_probe_t cfumass_probe;
+static device_attach_t cfumass_attach;
+static device_detach_t cfumass_detach;
+static device_suspend_t cfumass_suspend;
+static device_resume_t cfumass_resume;
+static usb_handle_request_t cfumass_handle_request;
+
+static usb_callback_t cfumass_t_command_callback;
+static usb_callback_t cfumass_t_data_callback;
+static usb_callback_t cfumass_t_status_callback;
+
+static device_method_t cfumass_methods[] = {
+ /* USB interface. */
+ DEVMETHOD(usb_handle_request, cfumass_handle_request),
+
+ /* Device interface. */
+ DEVMETHOD(device_probe, cfumass_probe),
+ DEVMETHOD(device_attach, cfumass_attach),
+ DEVMETHOD(device_detach, cfumass_detach),
+ DEVMETHOD(device_suspend, cfumass_suspend),
+ DEVMETHOD(device_resume, cfumass_resume),
+
+ DEVMETHOD_END
+};
+
+static driver_t cfumass_driver = {
+ .name = "cfumass",
+ .methods = cfumass_methods,
+ .size = sizeof(struct cfumass_softc),
+};
+
+DRIVER_MODULE(cfumass, uhub, cfumass_driver, NULL, NULL);
+MODULE_VERSION(cfumass, 0);
+MODULE_DEPEND(cfumass, usb, 1, 1, 1);
+MODULE_DEPEND(cfumass, usb_template, 1, 1, 1);
+
+static struct usb_config cfumass_config[CFUMASS_T_MAX] = {
+ [CFUMASS_T_COMMAND] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = sizeof(struct cfumass_cbw_t),
+ .callback = &cfumass_t_command_callback,
+ .usb_mode = USB_MODE_DEVICE,
+ },
+
+ [CFUMASS_T_DATA_OUT] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = CFUMASS_BULK_SIZE,
+ .flags = {.proxy_buffer = 1, .short_xfer_ok = 1,
+ .ext_buffer = 1},
+ .callback = &cfumass_t_data_callback,
+ .usb_mode = USB_MODE_DEVICE,
+ },
+
+ [CFUMASS_T_DATA_IN] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = CFUMASS_BULK_SIZE,
+ .flags = {.proxy_buffer = 1, .short_xfer_ok = 1,
+ .ext_buffer = 1},
+ .callback = &cfumass_t_data_callback,
+ .usb_mode = USB_MODE_DEVICE,
+ },
+
+ [CFUMASS_T_STATUS] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = sizeof(struct cfumass_csw_t),
+ .flags = {.short_xfer_ok = 1},
+ .callback = &cfumass_t_status_callback,
+ .usb_mode = USB_MODE_DEVICE,
+ },
+};
+
+/*
+ * CTL frontend interface.
+ */
+static int cfumass_init(void);
+static int cfumass_shutdown(void);
+static void cfumass_online(void *arg);
+static void cfumass_offline(void *arg);
+static void cfumass_datamove(union ctl_io *io);
+static void cfumass_done(union ctl_io *io);
+
+static struct ctl_frontend cfumass_frontend = {
+ .name = "umass",
+ .init = cfumass_init,
+ .shutdown = cfumass_shutdown,
+};
+CTL_FRONTEND_DECLARE(ctlcfumass, cfumass_frontend);
+
+#define CFUMASS_DEBUG(S, X, ...) \
+ do { \
+ if (debug > 1) { \
+ device_printf(S->sc_dev, "%s: " X "\n", \
+ __func__, ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define CFUMASS_WARN(S, X, ...) \
+ do { \
+ if (debug > 0) { \
+ device_printf(S->sc_dev, "WARNING: %s: " X "\n",\
+ __func__, ## __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define CFUMASS_LOCK(X) mtx_lock(&X->sc_mtx)
+#define CFUMASS_UNLOCK(X) mtx_unlock(&X->sc_mtx)
+
+static void cfumass_transfer_start(struct cfumass_softc *sc,
+ uint8_t xfer_index);
+static void cfumass_terminate(struct cfumass_softc *sc);
+
+static int
+cfumass_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa;
+ struct usb_interface_descriptor *id;
+
+ uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_DEVICE)
+ return (ENXIO);
+
+ /*
+ * Check for a compliant device.
+ */
+ id = usbd_get_interface_descriptor(uaa->iface);
+ if ((id == NULL) ||
+ (id->bInterfaceClass != UICLASS_MASS) ||
+ (id->bInterfaceSubClass != UISUBCLASS_SCSI) ||
+ (id->bInterfaceProtocol != UIPROTO_MASS_BBB)) {
+ return (ENXIO);
+ }
+
+ return (BUS_PROBE_GENERIC);
+}
+
+static int
+cfumass_attach(device_t dev)
+{
+ struct cfumass_softc *sc;
+ struct usb_attach_arg *uaa;
+ int error;
+
+ sc = device_get_softc(dev);
+ uaa = device_get_ivars(dev);
+
+ sc->sc_dev = dev;
+ sc->sc_udev = uaa->device;
+
+ CFUMASS_DEBUG(sc, "go");
+
+ usbd_set_power_mode(uaa->device, USB_POWER_MODE_SAVE);
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, "cfumass", NULL, MTX_DEF);
+ refcount_acquire(&cfumass_refcount);
+
+ error = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer, cfumass_config,
+ CFUMASS_T_MAX, sc, &sc->sc_mtx);
+ if (error != 0) {
+ CFUMASS_WARN(sc, "usbd_transfer_setup() failed: %s",
+ usbd_errstr(error));
+ refcount_release(&cfumass_refcount);
+ return (ENXIO);
+ }
+
+ sc->sc_cbw =
+ usbd_xfer_get_frame_buffer(sc->sc_xfer[CFUMASS_T_COMMAND], 0);
+ sc->sc_csw =
+ usbd_xfer_get_frame_buffer(sc->sc_xfer[CFUMASS_T_STATUS], 0);
+
+ sc->sc_ctl_initid = ctl_add_initiator(&cfumass_port, -1, 0, NULL);
+ if (sc->sc_ctl_initid < 0) {
+ CFUMASS_WARN(sc, "ctl_add_initiator() failed with error %d",
+ sc->sc_ctl_initid);
+ usbd_transfer_unsetup(sc->sc_xfer, CFUMASS_T_MAX);
+ refcount_release(&cfumass_refcount);
+ return (ENXIO);
+ }
+
+ refcount_init(&sc->sc_queued, 0);
+
+ CFUMASS_LOCK(sc);
+ cfumass_transfer_start(sc, CFUMASS_T_COMMAND);
+ CFUMASS_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+cfumass_detach(device_t dev)
+{
+ struct cfumass_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ CFUMASS_DEBUG(sc, "go");
+
+ CFUMASS_LOCK(sc);
+ cfumass_terminate(sc);
+ CFUMASS_UNLOCK(sc);
+ usbd_transfer_unsetup(sc->sc_xfer, CFUMASS_T_MAX);
+
+ if (sc->sc_ctl_initid != -1) {
+ error = ctl_remove_initiator(&cfumass_port, sc->sc_ctl_initid);
+ if (error != 0) {
+ CFUMASS_WARN(sc, "ctl_remove_initiator() failed "
+ "with error %d", error);
+ }
+ sc->sc_ctl_initid = -1;
+ }
+
+ mtx_destroy(&sc->sc_mtx);
+ refcount_release(&cfumass_refcount);
+
+ return (0);
+}
+
+static int
+cfumass_suspend(device_t dev)
+{
+ struct cfumass_softc *sc;
+
+ sc = device_get_softc(dev);
+ CFUMASS_DEBUG(sc, "go");
+
+ return (0);
+}
+
+static int
+cfumass_resume(device_t dev)
+{
+ struct cfumass_softc *sc;
+
+ sc = device_get_softc(dev);
+ CFUMASS_DEBUG(sc, "go");
+
+ return (0);
+}
+
+static void
+cfumass_transfer_start(struct cfumass_softc *sc, uint8_t xfer_index)
+{
+
+ usbd_transfer_start(sc->sc_xfer[xfer_index]);
+}
+
+static void
+cfumass_transfer_stop_and_drain(struct cfumass_softc *sc, uint8_t xfer_index)
+{
+
+ usbd_transfer_stop(sc->sc_xfer[xfer_index]);
+ CFUMASS_UNLOCK(sc);
+ usbd_transfer_drain(sc->sc_xfer[xfer_index]);
+ CFUMASS_LOCK(sc);
+}
+
+static void
+cfumass_terminate(struct cfumass_softc *sc)
+{
+ int last;
+
+ for (;;) {
+ cfumass_transfer_stop_and_drain(sc, CFUMASS_T_COMMAND);
+ cfumass_transfer_stop_and_drain(sc, CFUMASS_T_DATA_IN);
+ cfumass_transfer_stop_and_drain(sc, CFUMASS_T_DATA_OUT);
+
+ if (sc->sc_ctl_io != NULL) {
+ CFUMASS_DEBUG(sc, "terminating CTL transfer");
+ ctl_set_data_phase_error(&sc->sc_ctl_io->scsiio);
+ ctl_datamove_done(sc->sc_ctl_io, false);
+ sc->sc_ctl_io = NULL;
+ }
+
+ cfumass_transfer_stop_and_drain(sc, CFUMASS_T_STATUS);
+
+ refcount_acquire(&sc->sc_queued);
+ last = refcount_release(&sc->sc_queued);
+ if (last != 0)
+ break;
+
+ CFUMASS_DEBUG(sc, "%d CTL tasks pending", sc->sc_queued);
+ msleep(__DEVOLATILE(void *, &sc->sc_queued), &sc->sc_mtx,
+ 0, "cfumass_reset", hz / 100);
+ }
+}
+
+static int
+cfumass_handle_request(device_t dev,
+ const void *preq, void **pptr, uint16_t *plen,
+ uint16_t offset, uint8_t *pstate)
+{
+ static uint8_t max_lun_tmp;
+ struct cfumass_softc *sc;
+ const struct usb_device_request *req;
+ uint8_t is_complete;
+
+ sc = device_get_softc(dev);
+ req = preq;
+ is_complete = *pstate;
+
+ CFUMASS_DEBUG(sc, "go");
+
+ if (is_complete)
+ return (ENXIO);
+
+ if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->bRequest == UR_RESET)) {
+ CFUMASS_WARN(sc, "received Bulk-Only Mass Storage Reset");
+ *plen = 0;
+
+ CFUMASS_LOCK(sc);
+ cfumass_terminate(sc);
+ cfumass_transfer_start(sc, CFUMASS_T_COMMAND);
+ CFUMASS_UNLOCK(sc);
+
+ CFUMASS_DEBUG(sc, "Bulk-Only Mass Storage Reset done");
+ return (0);
+ }
+
+ if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) &&
+ (req->bRequest == UR_GET_MAX_LUN)) {
+ CFUMASS_DEBUG(sc, "received Get Max LUN");
+ if (offset == 0) {
+ *plen = 1;
+ /*
+ * The protocol doesn't support LUN numbers higher
+ * than 15. Also, some initiators (namely Windows XP
+ * SP3 Version 2002) can't properly query the number
+ * of LUNs, resulting in inaccessible "fake" ones - thus
+ * the default limit of one LUN.
+ */
+ if (max_lun < 0 || max_lun > 15) {
+ CFUMASS_WARN(sc,
+ "invalid hw.usb.cfumass.max_lun, must be "
+ "between 0 and 15; defaulting to 0");
+ max_lun_tmp = 0;
+ } else {
+ max_lun_tmp = max_lun;
+ }
+ *pptr = &max_lun_tmp;
+ } else {
+ *plen = 0;
+ }
+ return (0);
+ }
+
+ return (ENXIO);
+}
+
+static int
+cfumass_quirk(struct cfumass_softc *sc, unsigned char *cdb, int cdb_len)
+{
+ struct scsi_start_stop_unit *sssu;
+
+ switch (cdb[0]) {
+ case START_STOP_UNIT:
+ /*
+ * Some initiators - eg OSX, Darwin Kernel Version 15.6.0,
+ * root:xnu-3248.60.11~2/RELEASE_X86_64 - attempt to stop
+ * the unit on eject, but fail to start it when it's plugged
+ * back. Just ignore the command.
+ */
+
+ if (cdb_len < sizeof(*sssu)) {
+ CFUMASS_DEBUG(sc, "received START STOP UNIT with "
+ "bCDBLength %d, should be %zd",
+ cdb_len, sizeof(*sssu));
+ break;
+ }
+
+ sssu = (struct scsi_start_stop_unit *)cdb;
+ if ((sssu->how & SSS_PC_MASK) != 0)
+ break;
+
+ if ((sssu->how & SSS_START) != 0)
+ break;
+
+ if ((sssu->how & SSS_LOEJ) != 0)
+ break;
+
+ if (ignore_stop == 0) {
+ break;
+ } else if (ignore_stop == 1) {
+ CFUMASS_WARN(sc, "ignoring START STOP UNIT request");
+ } else {
+ CFUMASS_DEBUG(sc, "ignoring START STOP UNIT request");
+ }
+
+ sc->sc_current_status = 0;
+ cfumass_transfer_start(sc, CFUMASS_T_STATUS);
+
+ return (1);
+ default:
+ break;
+ }
+
+ return (0);
+}
+
+static void
+cfumass_t_command_callback(struct usb_xfer *xfer, usb_error_t usb_error)
+{
+ struct cfumass_softc *sc;
+ uint32_t signature;
+ union ctl_io *io;
+ int error = 0;
+
+ sc = usbd_xfer_softc(xfer);
+
+ KASSERT(sc->sc_ctl_io == NULL,
+ ("sc_ctl_io is %p, should be NULL", sc->sc_ctl_io));
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ CFUMASS_DEBUG(sc, "USB_ST_TRANSFERRED");
+
+ signature = UGETDW(sc->sc_cbw->dCBWSignature);
+ if (signature != CBWSIGNATURE) {
+ CFUMASS_WARN(sc, "wrong dCBWSignature 0x%08x, "
+ "should be 0x%08x", signature, CBWSIGNATURE);
+ break;
+ }
+
+ if (sc->sc_cbw->bCDBLength <= 0 ||
+ sc->sc_cbw->bCDBLength > sizeof(sc->sc_cbw->CBWCB)) {
+ CFUMASS_WARN(sc, "invalid bCDBLength %d, should be <= %zd",
+ sc->sc_cbw->bCDBLength, sizeof(sc->sc_cbw->CBWCB));
+ break;
+ }
+
+ sc->sc_current_stalled = false;
+ sc->sc_current_status = 0;
+ sc->sc_current_tag = UGETDW(sc->sc_cbw->dCBWTag);
+ sc->sc_current_transfer_length =
+ UGETDW(sc->sc_cbw->dCBWDataTransferLength);
+ sc->sc_current_flags = sc->sc_cbw->bCBWFlags;
+
+ /*
+ * Make sure to report proper residue if the datamove wasn't
+ * required, or wasn't called due to SCSI error.
+ */
+ sc->sc_current_residue = sc->sc_current_transfer_length;
+
+ if (cfumass_quirk(sc,
+ sc->sc_cbw->CBWCB, sc->sc_cbw->bCDBLength) != 0)
+ break;
+
+ if (!cfumass_port_online) {
+ CFUMASS_DEBUG(sc, "cfumass port is offline; stalling");
+ usbd_xfer_set_stall(xfer);
+ break;
+ }
+
+ /*
+ * Those CTL functions cannot be called with mutex held.
+ */
+ CFUMASS_UNLOCK(sc);
+ io = ctl_alloc_io(cfumass_port.ctl_pool_ref);
+ ctl_zero_io(io);
+ io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr = sc;
+ io->io_hdr.io_type = CTL_IO_SCSI;
+ io->io_hdr.nexus.initid = sc->sc_ctl_initid;
+ io->io_hdr.nexus.targ_port = cfumass_port.targ_port;
+ io->io_hdr.nexus.targ_lun = ctl_decode_lun(sc->sc_cbw->bCBWLUN);
+ io->scsiio.tag_num = UGETDW(sc->sc_cbw->dCBWTag);
+ io->scsiio.tag_type = CTL_TAG_UNTAGGED;
+ io->scsiio.cdb_len = sc->sc_cbw->bCDBLength;
+ memcpy(io->scsiio.cdb, sc->sc_cbw->CBWCB, sc->sc_cbw->bCDBLength);
+ refcount_acquire(&sc->sc_queued);
+ error = ctl_queue(io);
+ if (error != CTL_RETVAL_COMPLETE) {
+ CFUMASS_WARN(sc,
+ "ctl_queue() failed; error %d; stalling", error);
+ ctl_free_io(io);
+ refcount_release(&sc->sc_queued);
+ CFUMASS_LOCK(sc);
+ usbd_xfer_set_stall(xfer);
+ break;
+ }
+
+ CFUMASS_LOCK(sc);
+ break;
+
+ case USB_ST_SETUP:
+tr_setup:
+ CFUMASS_DEBUG(sc, "USB_ST_SETUP");
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(*sc->sc_cbw));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default:
+ if (usb_error == USB_ERR_CANCELLED) {
+ CFUMASS_DEBUG(sc, "USB_ERR_CANCELLED");
+ break;
+ }
+
+ CFUMASS_DEBUG(sc, "USB_ST_ERROR: %s", usbd_errstr(usb_error));
+
+ goto tr_setup;
+ }
+}
+
+static void
+cfumass_t_data_callback(struct usb_xfer *xfer, usb_error_t usb_error)
+{
+ struct cfumass_softc *sc = usbd_xfer_softc(xfer);
+ union ctl_io *io = sc->sc_ctl_io;
+ uint32_t max_bulk;
+ struct ctl_sg_entry sg_entry, *sglist;
+ int actlen, sumlen, sg_count;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ CFUMASS_DEBUG(sc, "USB_ST_TRANSFERRED");
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+ sc->sc_current_residue -= actlen;
+ io->scsiio.ext_data_filled += actlen;
+ io->scsiio.kern_data_resid -= actlen;
+ if (actlen < sumlen ||
+ sc->sc_current_residue == 0 ||
+ io->scsiio.kern_data_resid == 0) {
+ sc->sc_ctl_io = NULL;
+ ctl_datamove_done(io, false);
+ break;
+ }
+ /* FALLTHROUGH */
+
+ case USB_ST_SETUP:
+tr_setup:
+ CFUMASS_DEBUG(sc, "USB_ST_SETUP");
+
+ if (io->scsiio.kern_sg_entries > 0) {
+ sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr;
+ sg_count = io->scsiio.kern_sg_entries;
+ } else {
+ sglist = &sg_entry;
+ sglist->addr = io->scsiio.kern_data_ptr;
+ sglist->len = io->scsiio.kern_data_len;
+ sg_count = 1;
+ }
+
+ sumlen = io->scsiio.ext_data_filled -
+ io->scsiio.kern_rel_offset;
+ while (sumlen >= sglist->len && sg_count > 0) {
+ sumlen -= sglist->len;
+ sglist++;
+ sg_count--;
+ }
+ KASSERT(sg_count > 0, ("Run out of S/G list entries"));
+
+ max_bulk = usbd_xfer_max_len(xfer);
+ actlen = min(sglist->len - sumlen, max_bulk);
+ actlen = min(actlen, sc->sc_current_transfer_length -
+ io->scsiio.ext_data_filled);
+ CFUMASS_DEBUG(sc, "requested %d, done %d, max_bulk %d, "
+ "segment %zd => transfer %d",
+ sc->sc_current_transfer_length, io->scsiio.ext_data_filled,
+ max_bulk, sglist->len - sumlen, actlen);
+
+ usbd_xfer_set_frame_data(xfer, 0,
+ (uint8_t *)sglist->addr + sumlen, actlen);
+ usbd_transfer_submit(xfer);
+ break;
+
+ default:
+ if (usb_error == USB_ERR_CANCELLED) {
+ CFUMASS_DEBUG(sc, "USB_ERR_CANCELLED");
+ break;
+ }
+ CFUMASS_DEBUG(sc, "USB_ST_ERROR: %s", usbd_errstr(usb_error));
+ goto tr_setup;
+ }
+}
+
+static void
+cfumass_t_status_callback(struct usb_xfer *xfer, usb_error_t usb_error)
+{
+ struct cfumass_softc *sc;
+
+ sc = usbd_xfer_softc(xfer);
+
+ KASSERT(sc->sc_ctl_io == NULL,
+ ("sc_ctl_io is %p, should be NULL", sc->sc_ctl_io));
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ CFUMASS_DEBUG(sc, "USB_ST_TRANSFERRED");
+
+ cfumass_transfer_start(sc, CFUMASS_T_COMMAND);
+ break;
+
+ case USB_ST_SETUP:
+tr_setup:
+ CFUMASS_DEBUG(sc, "USB_ST_SETUP");
+
+ if (sc->sc_current_residue > 0 && !sc->sc_current_stalled) {
+ CFUMASS_DEBUG(sc, "non-zero residue, stalling");
+ usbd_xfer_set_stall(xfer);
+ sc->sc_current_stalled = true;
+ }
+
+ USETDW(sc->sc_csw->dCSWSignature, CSWSIGNATURE);
+ USETDW(sc->sc_csw->dCSWTag, sc->sc_current_tag);
+ USETDW(sc->sc_csw->dCSWDataResidue, sc->sc_current_residue);
+ sc->sc_csw->bCSWStatus = sc->sc_current_status;
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(*sc->sc_csw));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default:
+ if (usb_error == USB_ERR_CANCELLED) {
+ CFUMASS_DEBUG(sc, "USB_ERR_CANCELLED");
+ break;
+ }
+
+ CFUMASS_DEBUG(sc, "USB_ST_ERROR: %s",
+ usbd_errstr(usb_error));
+
+ goto tr_setup;
+ }
+}
+
+static void
+cfumass_online(void *arg __unused)
+{
+
+ cfumass_port_online = true;
+}
+
+static void
+cfumass_offline(void *arg __unused)
+{
+
+ cfumass_port_online = false;
+}
+
+static void
+cfumass_datamove(union ctl_io *io)
+{
+ struct cfumass_softc *sc;
+
+ sc = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr;
+
+ CFUMASS_DEBUG(sc, "go");
+
+ CFUMASS_LOCK(sc);
+
+ KASSERT(sc->sc_ctl_io == NULL,
+ ("sc_ctl_io is %p, should be NULL", sc->sc_ctl_io));
+ sc->sc_ctl_io = io;
+
+ if ((io->io_hdr.flags & CTL_FLAG_DATA_MASK) == CTL_FLAG_DATA_IN) {
+ /*
+ * Verify that CTL wants us to send the data in the direction
+ * expected by the initiator.
+ */
+ if (sc->sc_current_flags != CBWFLAGS_IN) {
+ CFUMASS_WARN(sc, "wrong bCBWFlags 0x%x, should be 0x%x",
+ sc->sc_current_flags, CBWFLAGS_IN);
+ goto fail;
+ }
+
+ cfumass_transfer_start(sc, CFUMASS_T_DATA_IN);
+ } else {
+ if (sc->sc_current_flags != CBWFLAGS_OUT) {
+ CFUMASS_WARN(sc, "wrong bCBWFlags 0x%x, should be 0x%x",
+ sc->sc_current_flags, CBWFLAGS_OUT);
+ goto fail;
+ }
+
+ cfumass_transfer_start(sc, CFUMASS_T_DATA_OUT);
+ }
+
+ CFUMASS_UNLOCK(sc);
+ return;
+
+fail:
+ ctl_set_data_phase_error(&io->scsiio);
+ ctl_datamove_done(io, true);
+ sc->sc_ctl_io = NULL;
+}
+
+static void
+cfumass_done(union ctl_io *io)
+{
+ struct cfumass_softc *sc;
+
+ sc = io->io_hdr.ctl_private[CTL_PRIV_FRONTEND].ptr;
+
+ CFUMASS_DEBUG(sc, "go");
+
+ KASSERT(((io->io_hdr.status & CTL_STATUS_MASK) != CTL_STATUS_NONE),
+ ("invalid CTL status %#x", io->io_hdr.status));
+ KASSERT(sc->sc_ctl_io == NULL,
+ ("sc_ctl_io is %p, should be NULL", sc->sc_ctl_io));
+
+ if (io->io_hdr.io_type == CTL_IO_TASK &&
+ io->taskio.task_action == CTL_TASK_I_T_NEXUS_RESET) {
+ /*
+ * Implicit task termination has just completed; nothing to do.
+ */
+ ctl_free_io(io);
+ return;
+ }
+
+ /*
+ * Do not return status for aborted commands.
+ * There are exceptions, but none supported by CTL yet.
+ */
+ if (((io->io_hdr.flags & CTL_FLAG_ABORT) &&
+ (io->io_hdr.flags & CTL_FLAG_ABORT_STATUS) == 0) ||
+ (io->io_hdr.flags & CTL_FLAG_STATUS_SENT)) {
+ ctl_free_io(io);
+ return;
+ }
+
+ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SUCCESS)
+ sc->sc_current_status = 0;
+ else
+ sc->sc_current_status = 1;
+
+ /* XXX: How should we report BUSY, RESERVATION CONFLICT, etc? */
+ if ((io->io_hdr.status & CTL_STATUS_MASK) == CTL_SCSI_ERROR &&
+ io->scsiio.scsi_status == SCSI_STATUS_CHECK_COND)
+ ctl_queue_sense(io);
+ else
+ ctl_free_io(io);
+
+ CFUMASS_LOCK(sc);
+ cfumass_transfer_start(sc, CFUMASS_T_STATUS);
+ CFUMASS_UNLOCK(sc);
+
+ refcount_release(&sc->sc_queued);
+}
+
+int
+cfumass_init(void)
+{
+ int error;
+
+ cfumass_port.frontend = &cfumass_frontend;
+ cfumass_port.port_type = CTL_PORT_UMASS;
+ cfumass_port.num_requested_ctl_io = 1;
+ cfumass_port.port_name = "cfumass";
+ cfumass_port.physical_port = 0;
+ cfumass_port.virtual_port = 0;
+ cfumass_port.port_online = cfumass_online;
+ cfumass_port.port_offline = cfumass_offline;
+ cfumass_port.onoff_arg = NULL;
+ cfumass_port.fe_datamove = cfumass_datamove;
+ cfumass_port.fe_done = cfumass_done;
+ cfumass_port.targ_port = -1;
+
+ error = ctl_port_register(&cfumass_port);
+ if (error != 0) {
+ printf("%s: ctl_port_register() failed "
+ "with error %d", __func__, error);
+ }
+
+ cfumass_port_online = true;
+ refcount_init(&cfumass_refcount, 0);
+
+ return (error);
+}
+
+int
+cfumass_shutdown(void)
+{
+ int error;
+
+ if (cfumass_refcount > 0) {
+ if (debug > 1) {
+ printf("%s: still have %u attachments; "
+ "returning EBUSY\n", __func__, cfumass_refcount);
+ }
+ return (EBUSY);
+ }
+
+ error = ctl_port_deregister(&cfumass_port);
+ if (error != 0) {
+ printf("%s: ctl_port_deregister() failed "
+ "with error %d\n", __func__, error);
+ }
+
+ return (error);
+}
diff --git a/sys/dev/usb/storage/rio500_usb.h b/sys/dev/usb/storage/rio500_usb.h
new file mode 100644
index 000000000000..0adef69cf1d4
--- /dev/null
+++ b/sys/dev/usb/storage/rio500_usb.h
@@ -0,0 +1,47 @@
+/*-
+ ----------------------------------------------------------------------
+
+ 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 definition
+ 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>
+
+ ---------------------------------------------------------------------- */
+
+
+#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/dev/usb/storage/umass.c b/sys/dev/usb/storage/umass.c
new file mode 100644
index 000000000000..cacf4ddf8f16
--- /dev/null
+++ b/sys/dev/usb/storage/umass.c
@@ -0,0 +1,2990 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ * $NetBSD: umass.c,v 1.28 2000/04/02 23:46:53 augustss Exp $
+ */
+
+/* Also already merged from NetBSD:
+ * $NetBSD: umass.c,v 1.67 2001/11/25 19:05:22 augustss Exp $
+ * $NetBSD: umass.c,v 1.90 2002/11/04 19:17:33 pooka Exp $
+ * $NetBSD: umass.c,v 1.108 2003/11/07 17:03:25 wiz Exp $
+ * $NetBSD: umass.c,v 1.109 2003/12/04 13:57:31 keihan Exp $
+ */
+
+/*
+ * Universal Serial Bus Mass Storage Class specs:
+ * http://www.usb.org/developers/devclass_docs/usb_msc_overview_1.2.pdf
+ * http://www.usb.org/developers/devclass_docs/usbmassbulk_10.pdf
+ * http://www.usb.org/developers/devclass_docs/usb_msc_cbi_1.1.pdf
+ * http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf
+ */
+
+/*
+ * Ported to NetBSD by Lennart Augustsson <augustss@NetBSD.org>.
+ * Parts of the code written by Jason R. Thorpe <thorpej@shagadelic.org>.
+ */
+
+/*
+ * The driver handles 3 Wire Protocols
+ * - Command/Bulk/Interrupt (CBI)
+ * - Command/Bulk/Interrupt with Command Completion Interrupt (CBI with CCI)
+ * - Mass Storage Bulk-Only (BBB)
+ * (BBB refers Bulk/Bulk/Bulk for Command/Data/Status phases)
+ *
+ * Over these wire protocols it handles the following command protocols
+ * - SCSI
+ * - UFI (floppy command set)
+ * - 8070i (ATAPI)
+ *
+ * UFI and 8070i (ATAPI) are transformed versions of the SCSI command set. The
+ * sc->sc_transform method is used to convert the commands into the appropriate
+ * format (if at all necessary). For example, UFI requires all commands to be
+ * 12 bytes in length amongst other things.
+ *
+ * The source code below is marked and can be split into a number of pieces
+ * (in this order):
+ *
+ * - probe/attach/detach
+ * - generic transfer routines
+ * - BBB
+ * - CBI
+ * - CBI_I (in addition to functions from CBI)
+ * - CAM (Common Access Method)
+ * - SCSI
+ * - UFI
+ * - 8070i (ATAPI)
+ *
+ * The protocols are implemented using a state machine, for the transfers as
+ * well as for the resets. The state machine is contained in umass_t_*_callback.
+ * The state machine is started through either umass_command_start() or
+ * umass_reset().
+ *
+ * The reason for doing this is a) CAM performs a lot better this way and b) it
+ * avoids using tsleep from interrupt context (for example after a failed
+ * transfer).
+ */
+
+/*
+ * The SCSI related part of this driver has been derived from the
+ * dev/ppbus/vpo.c driver, by Nicolas Souchu (nsouch@FreeBSD.org).
+ *
+ * The CAM layer uses so called actions which are messages sent to the host
+ * adapter for completion. The actions come in through umass_cam_action. The
+ * appropriate block of routines is called depending on the transport protocol
+ * in use. When the transfer has finished, these routines call
+ * umass_cam_cb again to complete the CAM command.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#include <dev/usb/quirk/usb_quirk.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) \
+ do { \
+ if (umass_debug & (m)) { x ; } \
+ } while (0)
+
+#define DPRINTF(sc, m, fmt, ...) \
+ do { \
+ if (umass_debug & (m)) { \
+ printf("%s:%s: " fmt, \
+ (sc) ? (const char *)(sc)->sc_name : \
+ (const char *)"umassX", \
+ __FUNCTION__ ,## __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define UDMASS_GEN 0x00010000 /* general */
+#define UDMASS_SCSI 0x00020000 /* scsi */
+#define UDMASS_UFI 0x00040000 /* ufi command set */
+#define UDMASS_ATAPI 0x00080000 /* 8070i command set */
+#define UDMASS_CMD (UDMASS_SCSI|UDMASS_UFI|UDMASS_ATAPI)
+#define UDMASS_USB 0x00100000 /* USB general */
+#define UDMASS_BBB 0x00200000 /* Bulk-Only transfers */
+#define UDMASS_CBI 0x00400000 /* CBI transfers */
+#define UDMASS_WIRE (UDMASS_BBB|UDMASS_CBI)
+#define UDMASS_ALL 0xffff0000 /* all of the above */
+static int umass_debug;
+static int umass_throttle;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, umass, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB umass");
+SYSCTL_INT(_hw_usb_umass, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &umass_debug, 0, "umass debug level");
+SYSCTL_INT(_hw_usb_umass, OID_AUTO, throttle, CTLFLAG_RWTUN,
+ &umass_throttle, 0, "Forced delay between commands in milliseconds");
+#else
+#define DIF(...) do { } while (0)
+#define DPRINTF(...) do { } while (0)
+#endif
+
+#define UMASS_BULK_SIZE (1 << 17)
+#define UMASS_CBI_DIAGNOSTIC_CMDLEN 12 /* bytes */
+#define UMASS_MAX_CMDLEN MAX(12, CAM_MAX_CDBLEN) /* bytes */
+
+/* USB transfer definitions */
+
+#define UMASS_T_BBB_RESET1 0 /* Bulk-Only */
+#define UMASS_T_BBB_RESET2 1
+#define UMASS_T_BBB_RESET3 2
+#define UMASS_T_BBB_COMMAND 3
+#define UMASS_T_BBB_DATA_READ 4
+#define UMASS_T_BBB_DATA_RD_CS 5
+#define UMASS_T_BBB_DATA_WRITE 6
+#define UMASS_T_BBB_DATA_WR_CS 7
+#define UMASS_T_BBB_STATUS 8
+#define UMASS_T_BBB_MAX 9
+
+#define UMASS_T_CBI_RESET1 0 /* CBI */
+#define UMASS_T_CBI_RESET2 1
+#define UMASS_T_CBI_RESET3 2
+#define UMASS_T_CBI_COMMAND 3
+#define UMASS_T_CBI_DATA_READ 4
+#define UMASS_T_CBI_DATA_RD_CS 5
+#define UMASS_T_CBI_DATA_WRITE 6
+#define UMASS_T_CBI_DATA_WR_CS 7
+#define UMASS_T_CBI_STATUS 8
+#define UMASS_T_CBI_RESET4 9
+#define UMASS_T_CBI_MAX 10
+
+#define UMASS_T_MAX MAX(UMASS_T_CBI_MAX, UMASS_T_BBB_MAX)
+
+/* Generic definitions */
+
+/* Direction for transfer */
+#define DIR_NONE 0
+#define DIR_IN 1
+#define DIR_OUT 2
+
+/* device name */
+#define DEVNAME "umass"
+#define DEVNAME_SIM "umass-sim"
+
+/* Approximate maximum transfer speeds (assumes 33% overhead). */
+#define UMASS_FULL_TRANSFER_SPEED 1000
+#define UMASS_HIGH_TRANSFER_SPEED 40000
+#define UMASS_SUPER_TRANSFER_SPEED 400000
+#define UMASS_FLOPPY_TRANSFER_SPEED 20
+
+#define UMASS_TIMEOUT 5000 /* ms */
+
+/* CAM specific definitions */
+
+#define UMASS_SCSIID_MAX 1 /* maximum number of drives expected */
+#define UMASS_SCSIID_HOST UMASS_SCSIID_MAX
+
+/* Bulk-Only features */
+
+#define UR_BBB_RESET 0xff /* Bulk-Only reset */
+#define UR_BBB_GET_MAX_LUN 0xfe /* Get maximum lun */
+
+/* Command Block Wrapper */
+typedef struct {
+ uDWord dCBWSignature;
+#define CBWSIGNATURE 0x43425355
+ uDWord dCBWTag;
+ uDWord dCBWDataTransferLength;
+ uByte bCBWFlags;
+#define CBWFLAGS_OUT 0x00
+#define CBWFLAGS_IN 0x80
+ uByte bCBWLUN;
+ uByte bCDBLength;
+#define CBWCDBLENGTH 16
+ uByte CBWCDB[CBWCDBLENGTH];
+} __packed umass_bbb_cbw_t;
+
+#define UMASS_BBB_CBW_SIZE 31
+
+/* Command Status Wrapper */
+typedef struct {
+ uDWord dCSWSignature;
+#define CSWSIGNATURE 0x53425355
+#define CSWSIGNATURE_IMAGINATION_DBX1 0x43425355
+#define CSWSIGNATURE_OLYMPUS_C1 0x55425355
+ uDWord dCSWTag;
+ uDWord dCSWDataResidue;
+ uByte bCSWStatus;
+#define CSWSTATUS_GOOD 0x0
+#define CSWSTATUS_FAILED 0x1
+#define CSWSTATUS_PHASE 0x2
+} __packed umass_bbb_csw_t;
+
+#define UMASS_BBB_CSW_SIZE 13
+
+/* CBI features */
+
+#define UR_CBI_ADSC 0x00
+
+typedef union {
+ struct {
+ uint8_t type;
+#define IDB_TYPE_CCI 0x00
+ uint8_t value;
+#define IDB_VALUE_PASS 0x00
+#define IDB_VALUE_FAIL 0x01
+#define IDB_VALUE_PHASE 0x02
+#define IDB_VALUE_PERSISTENT 0x03
+#define IDB_VALUE_STATUS_MASK 0x03
+ } __packed common;
+
+ struct {
+ uint8_t asc;
+ uint8_t ascq;
+ } __packed ufi;
+} __packed umass_cbi_sbl_t;
+
+struct umass_softc; /* see below */
+
+typedef void (umass_callback_t)(struct umass_softc *sc, union ccb *ccb,
+ uint32_t residue, uint8_t status);
+
+#define STATUS_CMD_OK 0 /* everything ok */
+#define STATUS_CMD_UNKNOWN 1 /* will have to fetch sense */
+#define STATUS_CMD_FAILED 2 /* transfer was ok, command failed */
+#define STATUS_WIRE_FAILED 3 /* couldn't even get command across */
+
+typedef bool (umass_transform_t)(struct umass_softc *sc, uint8_t *cmd_ptr,
+ uint8_t cmd_len);
+
+/* Wire and command protocol */
+#define UMASS_PROTO_BBB 0x0001 /* USB wire protocol */
+#define UMASS_PROTO_CBI 0x0002
+#define UMASS_PROTO_CBI_I 0x0004
+#define UMASS_PROTO_WIRE 0x00ff /* USB wire protocol mask */
+#define UMASS_PROTO_SCSI 0x0100 /* command protocol */
+#define UMASS_PROTO_ATAPI 0x0200
+#define UMASS_PROTO_UFI 0x0400
+#define UMASS_PROTO_RBC 0x0800
+#define UMASS_PROTO_COMMAND 0xff00 /* command protocol mask */
+
+/* Device specific quirks */
+#define NO_QUIRKS 0x0000
+ /*
+ * The drive does not support Test Unit Ready. Convert to Start Unit
+ */
+#define NO_TEST_UNIT_READY 0x0001
+ /*
+ * The drive does not reset the Unit Attention state after REQUEST
+ * SENSE has been sent. The INQUIRY command does not reset the UA
+ * either, and so CAM runs in circles trying to retrieve the initial
+ * INQUIRY data.
+ */
+#define RS_NO_CLEAR_UA 0x0002
+ /* The drive does not support START STOP. */
+#define NO_START_STOP 0x0004
+ /* Don't ask for full inquiry data (255b). */
+#define FORCE_SHORT_INQUIRY 0x0008
+ /* Needs to be initialised the Shuttle way */
+#define SHUTTLE_INIT 0x0010
+ /* Drive needs to be switched to alternate iface 1 */
+#define ALT_IFACE_1 0x0020
+ /* Drive does not do 1Mb/s, but just floppy speeds (20kb/s) */
+#define FLOPPY_SPEED 0x0040
+ /* The device can't count and gets the residue of transfers wrong */
+#define IGNORE_RESIDUE 0x0080
+ /* No GetMaxLun call */
+#define NO_GETMAXLUN 0x0100
+ /* The device uses a weird CSWSIGNATURE. */
+#define WRONG_CSWSIG 0x0200
+ /* Device cannot handle INQUIRY so fake a generic response */
+#define NO_INQUIRY 0x0400
+ /* Device cannot handle INQUIRY EVPD, return CHECK CONDITION */
+#define NO_INQUIRY_EVPD 0x0800
+ /* Pad all RBC requests to 12 bytes. */
+#define RBC_PAD_TO_12 0x1000
+ /*
+ * Device reports number of sectors from READ_CAPACITY, not max
+ * sector number.
+ */
+#define READ_CAPACITY_OFFBY1 0x2000
+ /*
+ * Device cannot handle a SCSI synchronize cache command. Normally
+ * this quirk would be handled in the cam layer, but for IDE bridges
+ * we need to associate the quirk with the bridge and not the
+ * underlying disk device. This is handled by faking a success
+ * result.
+ */
+#define NO_SYNCHRONIZE_CACHE 0x4000
+ /* Device does not support 'PREVENT/ALLOW MEDIUM REMOVAL'. */
+#define NO_PREVENT_ALLOW 0x8000
+
+#define UMASS_QUIRKS_STRING \
+ "\020" \
+ "\001NO_TEST_UNIT_READY" \
+ "\002RS_NO_CLEAR_UA" \
+ "\003NO_START_STOP" \
+ "\004FORCE_SHORT_INQUIRY" \
+ "\005SHUTTLE_INIT" \
+ "\006ALT_IFACE_1" \
+ "\007FLOPPY_SPEED" \
+ "\010IGNORE_RESIDUE" \
+ "\011NO_GETMAXLUN" \
+ "\012WRONG_CSWSIG" \
+ "\013NO_INQUIRY" \
+ "\014NO_INQUIRY_EVPD" \
+ "\015RBC_PAD_TO_12" \
+ "\016READ_CAPACITY_OFFBY1" \
+ "\017NO_SYNCHRONIZE_CACHE" \
+ "\020NO_PREVENT_ALLOW" \
+
+
+struct umass_softc {
+ struct scsi_sense cam_scsi_sense;
+ struct scsi_test_unit_ready cam_scsi_test_unit_ready;
+ struct mtx sc_mtx;
+ struct {
+ uint8_t *data_ptr;
+ union ccb *ccb;
+ umass_callback_t *callback;
+
+ uint32_t data_len; /* bytes */
+ uint32_t data_rem; /* bytes */
+ uint32_t data_timeout; /* ms */
+ uint32_t actlen; /* bytes */
+
+ uint8_t cmd_data[UMASS_MAX_CMDLEN];
+ uint8_t cmd_len; /* bytes */
+ uint8_t dir;
+ uint8_t lun;
+ } sc_transfer;
+
+ /* Bulk specific variables for transfers in progress */
+ umass_bbb_cbw_t cbw; /* command block wrapper */
+ umass_bbb_csw_t csw; /* command status wrapper */
+
+ /* CBI specific variables for transfers in progress */
+ umass_cbi_sbl_t sbl; /* status block */
+
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+ struct cam_sim *sc_sim; /* SCSI Interface Module */
+ struct usb_xfer *sc_xfer[UMASS_T_MAX];
+
+ /*
+ * The command transform function is used to convert the SCSI
+ * commands into their derivatives, like UFI, ATAPI, and friends.
+ */
+ umass_transform_t *sc_transform;
+
+ uint32_t sc_unit;
+ uint32_t sc_quirks; /* they got it almost right */
+ uint32_t sc_proto; /* wire and cmd protocol */
+
+ uint8_t sc_name[16];
+ uint8_t sc_iface_no; /* interface number */
+ uint8_t sc_maxlun; /* maximum LUN number, inclusive */
+ uint8_t sc_last_xfer_index;
+ uint8_t sc_status_try;
+ bool sc_sending_sense;
+};
+
+struct umass_probe_proto {
+ uint32_t quirks;
+ uint32_t proto;
+
+ int error;
+};
+
+/* prototypes */
+
+static device_probe_t umass_probe;
+static device_attach_t umass_attach;
+static device_detach_t umass_detach;
+
+static usb_callback_t umass_tr_error;
+static usb_callback_t umass_t_bbb_reset1_callback;
+static usb_callback_t umass_t_bbb_reset2_callback;
+static usb_callback_t umass_t_bbb_reset3_callback;
+static usb_callback_t umass_t_bbb_command_callback;
+static usb_callback_t umass_t_bbb_data_read_callback;
+static usb_callback_t umass_t_bbb_data_rd_cs_callback;
+static usb_callback_t umass_t_bbb_data_write_callback;
+static usb_callback_t umass_t_bbb_data_wr_cs_callback;
+static usb_callback_t umass_t_bbb_status_callback;
+static usb_callback_t umass_t_cbi_reset1_callback;
+static usb_callback_t umass_t_cbi_reset2_callback;
+static usb_callback_t umass_t_cbi_reset3_callback;
+static usb_callback_t umass_t_cbi_reset4_callback;
+static usb_callback_t umass_t_cbi_command_callback;
+static usb_callback_t umass_t_cbi_data_read_callback;
+static usb_callback_t umass_t_cbi_data_rd_cs_callback;
+static usb_callback_t umass_t_cbi_data_write_callback;
+static usb_callback_t umass_t_cbi_data_wr_cs_callback;
+static usb_callback_t umass_t_cbi_status_callback;
+
+static void umass_cancel_ccb(struct umass_softc *);
+static void umass_init_shuttle(struct umass_softc *);
+static void umass_reset(struct umass_softc *);
+static void umass_t_bbb_data_clear_stall_callback(struct usb_xfer *,
+ uint8_t, uint8_t, usb_error_t);
+static void umass_command_start(struct umass_softc *, uint8_t, void *,
+ uint32_t, uint32_t, umass_callback_t *, union ccb *);
+static uint8_t umass_bbb_get_max_lun(struct umass_softc *);
+static void umass_cbi_start_status(struct umass_softc *);
+static void umass_t_cbi_data_clear_stall_callback(struct usb_xfer *,
+ uint8_t, uint8_t, usb_error_t);
+static int umass_cam_attach_sim(struct umass_softc *);
+static void umass_cam_attach(struct umass_softc *);
+static void umass_cam_detach_sim(struct umass_softc *);
+static void umass_cam_action(struct cam_sim *, union ccb *);
+static void umass_cam_poll(struct cam_sim *);
+static void umass_cam_cb(struct umass_softc *, union ccb *, uint32_t,
+ uint8_t);
+static void umass_cam_sense_cb(struct umass_softc *, union ccb *, uint32_t,
+ uint8_t);
+static void umass_cam_quirk_cb(struct umass_softc *, union ccb *, uint32_t,
+ uint8_t);
+static void umass_cam_illegal_request(union ccb *ccb);
+static bool umass_scsi_transform(struct umass_softc *, uint8_t *, uint8_t);
+static bool umass_rbc_transform(struct umass_softc *, uint8_t *, uint8_t);
+static bool umass_ufi_transform(struct umass_softc *, uint8_t *, uint8_t);
+static bool umass_atapi_transform(struct umass_softc *, uint8_t *, uint8_t);
+static bool umass_no_transform(struct umass_softc *, uint8_t *, uint8_t);
+static bool umass_std_transform(struct umass_softc *, union ccb *, uint8_t
+ *, uint8_t);
+
+#ifdef USB_DEBUG
+static void umass_bbb_dump_cbw(struct umass_softc *, umass_bbb_cbw_t *);
+static void umass_bbb_dump_csw(struct umass_softc *, umass_bbb_csw_t *);
+static void umass_cbi_dump_cmd(struct umass_softc *, void *, uint8_t);
+static void umass_dump_buffer(struct umass_softc *, uint8_t *, uint32_t,
+ uint32_t);
+#endif
+
+static struct usb_config umass_bbb_config[UMASS_T_BBB_MAX] = {
+ [UMASS_T_BBB_RESET1] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &umass_t_bbb_reset1_callback,
+ .timeout = 5000, /* 5 seconds */
+ .interval = 500, /* 500 milliseconds */
+ },
+
+ [UMASS_T_BBB_RESET2] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &umass_t_bbb_reset2_callback,
+ .timeout = 5000, /* 5 seconds */
+ .interval = 50, /* 50 milliseconds */
+ },
+
+ [UMASS_T_BBB_RESET3] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &umass_t_bbb_reset3_callback,
+ .timeout = 5000, /* 5 seconds */
+ .interval = 50, /* 50 milliseconds */
+ },
+
+ [UMASS_T_BBB_COMMAND] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = sizeof(umass_bbb_cbw_t),
+ .callback = &umass_t_bbb_command_callback,
+ .timeout = 5000, /* 5 seconds */
+ },
+
+ [UMASS_T_BBB_DATA_READ] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UMASS_BULK_SIZE,
+ .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer=1,},
+ .callback = &umass_t_bbb_data_read_callback,
+ .timeout = 0, /* overwritten later */
+ },
+
+ [UMASS_T_BBB_DATA_RD_CS] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &umass_t_bbb_data_rd_cs_callback,
+ .timeout = 5000, /* 5 seconds */
+ },
+
+ [UMASS_T_BBB_DATA_WRITE] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UMASS_BULK_SIZE,
+ .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer=1,},
+ .callback = &umass_t_bbb_data_write_callback,
+ .timeout = 0, /* overwritten later */
+ },
+
+ [UMASS_T_BBB_DATA_WR_CS] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &umass_t_bbb_data_wr_cs_callback,
+ .timeout = 5000, /* 5 seconds */
+ },
+
+ [UMASS_T_BBB_STATUS] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = sizeof(umass_bbb_csw_t),
+ .flags = {.short_xfer_ok = 1,},
+ .callback = &umass_t_bbb_status_callback,
+ .timeout = 5000, /* ms */
+ },
+};
+
+static struct usb_config umass_cbi_config[UMASS_T_CBI_MAX] = {
+ [UMASS_T_CBI_RESET1] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = (sizeof(struct usb_device_request) +
+ UMASS_CBI_DIAGNOSTIC_CMDLEN),
+ .callback = &umass_t_cbi_reset1_callback,
+ .timeout = 5000, /* 5 seconds */
+ .interval = 500, /* 500 milliseconds */
+ },
+
+ [UMASS_T_CBI_RESET2] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &umass_t_cbi_reset2_callback,
+ .timeout = 5000, /* 5 seconds */
+ .interval = 50, /* 50 milliseconds */
+ },
+
+ [UMASS_T_CBI_RESET3] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &umass_t_cbi_reset3_callback,
+ .timeout = 5000, /* 5 seconds */
+ .interval = 50, /* 50 milliseconds */
+ },
+
+ [UMASS_T_CBI_COMMAND] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = (sizeof(struct usb_device_request) +
+ UMASS_MAX_CMDLEN),
+ .callback = &umass_t_cbi_command_callback,
+ .timeout = 5000, /* 5 seconds */
+ },
+
+ [UMASS_T_CBI_DATA_READ] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = UMASS_BULK_SIZE,
+ .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer=1,},
+ .callback = &umass_t_cbi_data_read_callback,
+ .timeout = 0, /* overwritten later */
+ },
+
+ [UMASS_T_CBI_DATA_RD_CS] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &umass_t_cbi_data_rd_cs_callback,
+ .timeout = 5000, /* 5 seconds */
+ },
+
+ [UMASS_T_CBI_DATA_WRITE] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = UMASS_BULK_SIZE,
+ .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer=1,},
+ .callback = &umass_t_cbi_data_write_callback,
+ .timeout = 0, /* overwritten later */
+ },
+
+ [UMASS_T_CBI_DATA_WR_CS] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &umass_t_cbi_data_wr_cs_callback,
+ .timeout = 5000, /* 5 seconds */
+ },
+
+ [UMASS_T_CBI_STATUS] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .flags = {.short_xfer_ok = 1,.no_pipe_ok = 1,},
+ .bufsize = sizeof(umass_cbi_sbl_t),
+ .callback = &umass_t_cbi_status_callback,
+ .timeout = 5000, /* ms */
+ },
+
+ [UMASS_T_CBI_RESET4] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &umass_t_cbi_reset4_callback,
+ .timeout = 5000, /* ms */
+ },
+};
+
+/* If device cannot return valid inquiry data, fake it */
+static const uint8_t fake_inq_data[SHORT_INQUIRY_LENGTH] = {
+ 0, /* removable */ 0x80, SCSI_REV_2, SCSI_REV_2,
+ /* additional_length */ 31, 0, 0, 0
+};
+
+#define UFI_COMMAND_LENGTH 12 /* UFI commands are always 12 bytes */
+#define ATAPI_COMMAND_LENGTH 12 /* ATAPI commands are always 12 bytes */
+
+static device_method_t umass_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, umass_probe),
+ DEVMETHOD(device_attach, umass_attach),
+ DEVMETHOD(device_detach, umass_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t umass_driver = {
+ .name = "umass",
+ .methods = umass_methods,
+ .size = sizeof(struct umass_softc),
+};
+
+static const STRUCT_USB_HOST_ID __used umass_devs[] = {
+ /* generic mass storage class */
+ {USB_IFACE_CLASS(UICLASS_MASS),},
+};
+
+DRIVER_MODULE(umass, uhub, umass_driver, NULL, NULL);
+MODULE_DEPEND(umass, usb, 1, 1, 1);
+MODULE_DEPEND(umass, cam, 1, 1, 1);
+MODULE_VERSION(umass, 1);
+USB_PNP_HOST_INFO(umass_devs);
+
+/*
+ * USB device probe/attach/detach
+ */
+
+static uint16_t
+umass_get_proto(struct usb_interface *iface)
+{
+ struct usb_interface_descriptor *id;
+ uint16_t retval;
+
+ retval = 0;
+
+ /* Check for a standards compliant device */
+ id = usbd_get_interface_descriptor(iface);
+ if ((id == NULL) ||
+ (id->bInterfaceClass != UICLASS_MASS)) {
+ goto done;
+ }
+ switch (id->bInterfaceSubClass) {
+ case UISUBCLASS_SCSI:
+ retval |= UMASS_PROTO_SCSI;
+ break;
+ case UISUBCLASS_UFI:
+ retval |= UMASS_PROTO_UFI;
+ break;
+ case UISUBCLASS_RBC:
+ retval |= UMASS_PROTO_RBC;
+ break;
+ case UISUBCLASS_SFF8020I:
+ case UISUBCLASS_SFF8070I:
+ retval |= UMASS_PROTO_ATAPI;
+ break;
+ default:
+ goto done;
+ }
+
+ switch (id->bInterfaceProtocol) {
+ case UIPROTO_MASS_CBI:
+ retval |= UMASS_PROTO_CBI;
+ break;
+ case UIPROTO_MASS_CBI_I:
+ retval |= UMASS_PROTO_CBI_I;
+ break;
+ case UIPROTO_MASS_BBB_OLD:
+ case UIPROTO_MASS_BBB:
+ retval |= UMASS_PROTO_BBB;
+ break;
+ default:
+ goto done;
+ }
+done:
+ return (retval);
+}
+
+/*
+ * Match the device we are seeing with the devices supported.
+ */
+static struct umass_probe_proto
+umass_probe_proto(device_t dev, struct usb_attach_arg *uaa)
+{
+ struct umass_probe_proto ret;
+ uint32_t quirks = NO_QUIRKS;
+ uint32_t proto = umass_get_proto(uaa->iface);
+
+ memset(&ret, 0, sizeof(ret));
+ ret.error = BUS_PROBE_GENERIC;
+
+ /* Check if we should deny probing. */
+ if (usb_test_quirk(uaa, UQ_MSC_IGNORE)) {
+ ret.error = ENXIO;
+ goto done;
+ }
+
+ /* Search for protocol enforcement */
+
+ if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_BBB)) {
+ proto &= ~UMASS_PROTO_WIRE;
+ proto |= UMASS_PROTO_BBB;
+ } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_CBI)) {
+ proto &= ~UMASS_PROTO_WIRE;
+ proto |= UMASS_PROTO_CBI;
+ } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_WIRE_CBI_I)) {
+ proto &= ~UMASS_PROTO_WIRE;
+ proto |= UMASS_PROTO_CBI_I;
+ }
+
+ if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_SCSI)) {
+ proto &= ~UMASS_PROTO_COMMAND;
+ proto |= UMASS_PROTO_SCSI;
+ } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_ATAPI)) {
+ proto &= ~UMASS_PROTO_COMMAND;
+ proto |= UMASS_PROTO_ATAPI;
+ } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_UFI)) {
+ proto &= ~UMASS_PROTO_COMMAND;
+ proto |= UMASS_PROTO_UFI;
+ } else if (usb_test_quirk(uaa, UQ_MSC_FORCE_PROTO_RBC)) {
+ proto &= ~UMASS_PROTO_COMMAND;
+ proto |= UMASS_PROTO_RBC;
+ }
+
+ /* Check if the protocol is invalid */
+
+ if ((proto & UMASS_PROTO_COMMAND) == 0) {
+ ret.error = ENXIO;
+ goto done;
+ }
+
+ if ((proto & UMASS_PROTO_WIRE) == 0) {
+ ret.error = ENXIO;
+ goto done;
+ }
+
+ /* Search for quirks */
+
+ if (usb_test_quirk(uaa, UQ_MSC_NO_TEST_UNIT_READY))
+ quirks |= NO_TEST_UNIT_READY;
+ if (usb_test_quirk(uaa, UQ_MSC_NO_RS_CLEAR_UA))
+ quirks |= RS_NO_CLEAR_UA;
+ if (usb_test_quirk(uaa, UQ_MSC_NO_START_STOP))
+ quirks |= NO_START_STOP;
+ if (usb_test_quirk(uaa, UQ_MSC_NO_GETMAXLUN))
+ quirks |= NO_GETMAXLUN;
+ if (usb_test_quirk(uaa, UQ_MSC_NO_INQUIRY))
+ quirks |= NO_INQUIRY;
+ if (usb_test_quirk(uaa, UQ_MSC_NO_INQUIRY_EVPD))
+ quirks |= NO_INQUIRY_EVPD;
+ if (usb_test_quirk(uaa, UQ_MSC_NO_PREVENT_ALLOW))
+ quirks |= NO_PREVENT_ALLOW;
+ if (usb_test_quirk(uaa, UQ_MSC_NO_SYNC_CACHE))
+ quirks |= NO_SYNCHRONIZE_CACHE;
+ if (usb_test_quirk(uaa, UQ_MSC_SHUTTLE_INIT))
+ quirks |= SHUTTLE_INIT;
+ if (usb_test_quirk(uaa, UQ_MSC_ALT_IFACE_1))
+ quirks |= ALT_IFACE_1;
+ if (usb_test_quirk(uaa, UQ_MSC_FLOPPY_SPEED))
+ quirks |= FLOPPY_SPEED;
+ if (usb_test_quirk(uaa, UQ_MSC_IGNORE_RESIDUE))
+ quirks |= IGNORE_RESIDUE;
+ if (usb_test_quirk(uaa, UQ_MSC_WRONG_CSWSIG))
+ quirks |= WRONG_CSWSIG;
+ if (usb_test_quirk(uaa, UQ_MSC_RBC_PAD_TO_12))
+ quirks |= RBC_PAD_TO_12;
+ if (usb_test_quirk(uaa, UQ_MSC_READ_CAP_OFFBY1))
+ quirks |= READ_CAPACITY_OFFBY1;
+ if (usb_test_quirk(uaa, UQ_MSC_FORCE_SHORT_INQ))
+ quirks |= FORCE_SHORT_INQUIRY;
+
+done:
+ ret.quirks = quirks;
+ ret.proto = proto;
+ return (ret);
+}
+
+static int
+umass_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct umass_probe_proto temp;
+
+ if (uaa->usb_mode != USB_MODE_HOST) {
+ return (ENXIO);
+ }
+ temp = umass_probe_proto(dev, uaa);
+
+ return (temp.error);
+}
+
+static int
+umass_attach(device_t dev)
+{
+ struct umass_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct umass_probe_proto temp = umass_probe_proto(dev, uaa);
+ struct usb_interface_descriptor *id;
+ int err;
+
+ /*
+ * NOTE: the softc struct is cleared in device_set_driver.
+ * We can safely call umass_detach without specifically
+ * initializing the struct.
+ */
+
+ sc->sc_dev = dev;
+ sc->sc_udev = uaa->device;
+ sc->sc_proto = temp.proto;
+ sc->sc_quirks = temp.quirks;
+ sc->sc_unit = device_get_unit(dev);
+
+ snprintf(sc->sc_name, sizeof(sc->sc_name),
+ "%s", device_get_nameunit(dev));
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(dev),
+ NULL, MTX_DEF | MTX_RECURSE);
+
+ /* get interface index */
+
+ id = usbd_get_interface_descriptor(uaa->iface);
+ if (id == NULL) {
+ device_printf(dev, "failed to get "
+ "interface number\n");
+ goto detach;
+ }
+ sc->sc_iface_no = id->bInterfaceNumber;
+
+#ifdef USB_DEBUG
+ device_printf(dev, " ");
+
+ switch (sc->sc_proto & UMASS_PROTO_COMMAND) {
+ case UMASS_PROTO_SCSI:
+ printf("SCSI");
+ break;
+ case UMASS_PROTO_ATAPI:
+ printf("8070i (ATAPI)");
+ break;
+ case UMASS_PROTO_UFI:
+ printf("UFI");
+ break;
+ case UMASS_PROTO_RBC:
+ printf("RBC");
+ break;
+ default:
+ printf("(unknown 0x%02x)",
+ sc->sc_proto & UMASS_PROTO_COMMAND);
+ break;
+ }
+
+ printf(" over ");
+
+ switch (sc->sc_proto & UMASS_PROTO_WIRE) {
+ case UMASS_PROTO_BBB:
+ printf("Bulk-Only");
+ break;
+ case UMASS_PROTO_CBI: /* uses Comand/Bulk pipes */
+ printf("CBI");
+ break;
+ case UMASS_PROTO_CBI_I: /* uses Comand/Bulk/Interrupt pipes */
+ printf("CBI with CCI");
+ break;
+ default:
+ printf("(unknown 0x%02x)",
+ sc->sc_proto & UMASS_PROTO_WIRE);
+ }
+
+ printf("; quirks = 0x%b\n", sc->sc_quirks, UMASS_QUIRKS_STRING);
+#endif
+
+ if (sc->sc_quirks & ALT_IFACE_1) {
+ err = usbd_set_alt_interface_index
+ (uaa->device, uaa->info.bIfaceIndex, 1);
+
+ if (err) {
+ DPRINTF(sc, UDMASS_USB, "could not switch to "
+ "Alt Interface 1\n");
+ goto detach;
+ }
+ }
+ /* allocate all required USB transfers */
+
+ if (sc->sc_proto & UMASS_PROTO_BBB) {
+ err = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer, umass_bbb_config,
+ UMASS_T_BBB_MAX, sc, &sc->sc_mtx);
+
+ /* skip reset first time */
+ sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND;
+
+ } else if (sc->sc_proto & (UMASS_PROTO_CBI | UMASS_PROTO_CBI_I)) {
+ err = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer, umass_cbi_config,
+ UMASS_T_CBI_MAX, sc, &sc->sc_mtx);
+
+ /* skip reset first time */
+ sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND;
+
+ } else {
+ err = USB_ERR_INVAL;
+ }
+
+ if (err) {
+ device_printf(dev, "could not setup required "
+ "transfers, %s\n", usbd_errstr(err));
+ goto detach;
+ }
+#ifdef USB_DEBUG
+ if (umass_throttle > 0) {
+ uint8_t x;
+ int iv;
+
+ iv = umass_throttle;
+
+ if (iv < 1)
+ iv = 1;
+ else if (iv > 8000)
+ iv = 8000;
+
+ for (x = 0; x != UMASS_T_MAX; x++) {
+ if (sc->sc_xfer[x] != NULL)
+ usbd_xfer_set_interval(sc->sc_xfer[x], iv);
+ }
+ }
+#endif
+ sc->sc_transform =
+ (sc->sc_proto & UMASS_PROTO_SCSI) ? &umass_scsi_transform :
+ (sc->sc_proto & UMASS_PROTO_UFI) ? &umass_ufi_transform :
+ (sc->sc_proto & UMASS_PROTO_ATAPI) ? &umass_atapi_transform :
+ (sc->sc_proto & UMASS_PROTO_RBC) ? &umass_rbc_transform :
+ &umass_no_transform;
+
+ /* from here onwards the device can be used. */
+
+ if (sc->sc_quirks & SHUTTLE_INIT) {
+ umass_init_shuttle(sc);
+ }
+ /* get the maximum LUN supported by the device */
+
+ if (((sc->sc_proto & UMASS_PROTO_WIRE) == UMASS_PROTO_BBB) &&
+ !(sc->sc_quirks & NO_GETMAXLUN))
+ sc->sc_maxlun = umass_bbb_get_max_lun(sc);
+ else
+ sc->sc_maxlun = 0;
+
+ /* Prepare the SCSI command block */
+ sc->cam_scsi_sense.opcode = REQUEST_SENSE;
+ sc->cam_scsi_test_unit_ready.opcode = TEST_UNIT_READY;
+
+ /* register the SIM */
+ err = umass_cam_attach_sim(sc);
+ if (err) {
+ goto detach;
+ }
+ /* scan the SIM */
+ umass_cam_attach(sc);
+
+ DPRINTF(sc, UDMASS_GEN, "Attach finished\n");
+
+ return (0); /* success */
+
+detach:
+ umass_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+umass_detach(device_t dev)
+{
+ struct umass_softc *sc = device_get_softc(dev);
+
+ DPRINTF(sc, UDMASS_USB, "\n");
+
+ /* teardown our statemachine */
+
+ usbd_transfer_unsetup(sc->sc_xfer, UMASS_T_MAX);
+
+ mtx_lock(&sc->sc_mtx);
+
+ /* cancel any leftover CCB's */
+
+ umass_cancel_ccb(sc);
+
+ umass_cam_detach_sim(sc);
+
+ mtx_unlock(&sc->sc_mtx);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0); /* success */
+}
+
+static void
+umass_init_shuttle(struct umass_softc *sc)
+{
+ struct usb_device_request req;
+ uint8_t status[2] = {0, 0};
+
+ /*
+ * The Linux driver does this, but no one can tell us what the
+ * command does.
+ */
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = 1; /* XXX unknown command */
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, sizeof(status));
+ usbd_do_request(sc->sc_udev, NULL, &req, &status);
+
+ DPRINTF(sc, UDMASS_GEN, "Shuttle init returned 0x%02x%02x\n",
+ status[0], status[1]);
+}
+
+/*
+ * Generic functions to handle transfers
+ */
+
+static void
+umass_transfer_start(struct umass_softc *sc, uint8_t xfer_index)
+{
+ DPRINTF(sc, UDMASS_GEN, "transfer index = "
+ "%d\n", xfer_index);
+
+ if (sc->sc_xfer[xfer_index]) {
+ sc->sc_last_xfer_index = xfer_index;
+ usbd_transfer_start(sc->sc_xfer[xfer_index]);
+ } else {
+ umass_cancel_ccb(sc);
+ }
+}
+
+static void
+umass_reset(struct umass_softc *sc)
+{
+ DPRINTF(sc, UDMASS_GEN, "resetting device\n");
+
+ /*
+ * stop the last transfer, if not already stopped:
+ */
+ usbd_transfer_stop(sc->sc_xfer[sc->sc_last_xfer_index]);
+ umass_transfer_start(sc, 0);
+}
+
+static void
+umass_cancel_ccb(struct umass_softc *sc)
+{
+ union ccb *ccb;
+
+ USB_MTX_ASSERT(&sc->sc_mtx, MA_OWNED);
+
+ ccb = sc->sc_transfer.ccb;
+ sc->sc_transfer.ccb = NULL;
+ sc->sc_last_xfer_index = 0;
+
+ if (ccb) {
+ (sc->sc_transfer.callback)
+ (sc, ccb, (sc->sc_transfer.data_len -
+ sc->sc_transfer.actlen), STATUS_WIRE_FAILED);
+ }
+}
+
+static void
+umass_tr_error(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+
+ if (error != USB_ERR_CANCELLED) {
+ DPRINTF(sc, UDMASS_GEN, "transfer error, %s -> "
+ "reset\n", usbd_errstr(error));
+ }
+ umass_cancel_ccb(sc);
+}
+
+/*
+ * BBB protocol specific functions
+ */
+
+static void
+umass_t_bbb_reset1_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_device_request req;
+ struct usb_page_cache *pc;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ umass_transfer_start(sc, UMASS_T_BBB_RESET2);
+ return;
+
+ case USB_ST_SETUP:
+ /*
+ * Reset recovery (5.3.4 in Universal Serial Bus Mass Storage Class)
+ *
+ * For Reset Recovery the host shall issue in the following order:
+ * a) a Bulk-Only Mass Storage Reset
+ * b) a Clear Feature HALT to the Bulk-In endpoint
+ * c) a Clear Feature HALT to the Bulk-Out endpoint
+ *
+ * This is done in 3 steps, using 3 transfers:
+ * UMASS_T_BBB_RESET1
+ * UMASS_T_BBB_RESET2
+ * UMASS_T_BBB_RESET3
+ */
+
+ DPRINTF(sc, UDMASS_BBB, "BBB reset!\n");
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UR_BBB_RESET; /* bulk only reset */
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frames(xfer, 1);
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ umass_tr_error(xfer, error);
+ return;
+ }
+}
+
+static void
+umass_t_bbb_reset2_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_RESET3,
+ UMASS_T_BBB_DATA_READ, error);
+}
+
+static void
+umass_t_bbb_reset3_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_COMMAND,
+ UMASS_T_BBB_DATA_WRITE, error);
+}
+
+static void
+umass_t_bbb_data_clear_stall_callback(struct usb_xfer *xfer,
+ uint8_t next_xfer, uint8_t stall_xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+tr_transferred:
+ umass_transfer_start(sc, next_xfer);
+ return;
+
+ case USB_ST_SETUP:
+ if (usbd_clear_stall_callback(xfer, sc->sc_xfer[stall_xfer])) {
+ goto tr_transferred;
+ }
+ return;
+
+ default: /* Error */
+ umass_tr_error(xfer, error);
+ return;
+ }
+}
+
+static void
+umass_t_bbb_command_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+ union ccb *ccb = sc->sc_transfer.ccb;
+ struct usb_page_cache *pc;
+ uint32_t tag;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ umass_transfer_start
+ (sc, ((sc->sc_transfer.dir == DIR_IN) ? UMASS_T_BBB_DATA_READ :
+ (sc->sc_transfer.dir == DIR_OUT) ? UMASS_T_BBB_DATA_WRITE :
+ UMASS_T_BBB_STATUS));
+ return;
+
+ case USB_ST_SETUP:
+
+ sc->sc_status_try = 0;
+
+ if (ccb) {
+ /*
+ * the initial value is not important,
+ * as long as the values are unique:
+ */
+ tag = UGETDW(sc->cbw.dCBWTag) + 1;
+
+ USETDW(sc->cbw.dCBWSignature, CBWSIGNATURE);
+ USETDW(sc->cbw.dCBWTag, tag);
+
+ /*
+ * dCBWDataTransferLength:
+ * This field indicates the number of bytes of data that the host
+ * intends to transfer on the IN or OUT Bulk endpoint(as indicated by
+ * the Direction bit) during the execution of this command. If this
+ * field is set to 0, the device will expect that no data will be
+ * transferred IN or OUT during this command, regardless of the value
+ * of the Direction bit defined in dCBWFlags.
+ */
+ USETDW(sc->cbw.dCBWDataTransferLength, sc->sc_transfer.data_len);
+
+ /*
+ * dCBWFlags:
+ * The bits of the Flags field are defined as follows:
+ * Bits 0-6 reserved
+ * Bit 7 Direction - this bit shall be ignored if the
+ * dCBWDataTransferLength field is zero.
+ * 0 = data Out from host to device
+ * 1 = data In from device to host
+ */
+ sc->cbw.bCBWFlags = ((sc->sc_transfer.dir == DIR_IN) ?
+ CBWFLAGS_IN : CBWFLAGS_OUT);
+ sc->cbw.bCBWLUN = sc->sc_transfer.lun;
+
+ if (sc->sc_transfer.cmd_len > sizeof(sc->cbw.CBWCDB)) {
+ sc->sc_transfer.cmd_len = sizeof(sc->cbw.CBWCDB);
+ DPRINTF(sc, UDMASS_BBB, "Truncating long command!\n");
+ }
+ sc->cbw.bCDBLength = sc->sc_transfer.cmd_len;
+
+ /* copy SCSI command data */
+ memcpy(sc->cbw.CBWCDB, sc->sc_transfer.cmd_data,
+ sc->sc_transfer.cmd_len);
+
+ /* clear remaining command area */
+ memset(sc->cbw.CBWCDB +
+ sc->sc_transfer.cmd_len, 0,
+ sizeof(sc->cbw.CBWCDB) -
+ sc->sc_transfer.cmd_len);
+
+ DIF(UDMASS_BBB, umass_bbb_dump_cbw(sc, &sc->cbw));
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &sc->cbw, sizeof(sc->cbw));
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(sc->cbw));
+
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ umass_tr_error(xfer, error);
+ return;
+ }
+}
+
+static void
+umass_t_bbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+ uint32_t max_bulk = usbd_xfer_max_len(xfer);
+ int actlen, sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ sc->sc_transfer.data_rem -= actlen;
+ sc->sc_transfer.data_ptr += actlen;
+ sc->sc_transfer.actlen += actlen;
+
+ if (actlen < sumlen) {
+ /* short transfer */
+ sc->sc_transfer.data_rem = 0;
+ }
+ case USB_ST_SETUP:
+ DPRINTF(sc, UDMASS_BBB, "max_bulk=%d, data_rem=%d\n",
+ max_bulk, sc->sc_transfer.data_rem);
+
+ if (sc->sc_transfer.data_rem == 0) {
+ umass_transfer_start(sc, UMASS_T_BBB_STATUS);
+ return;
+ }
+ if (max_bulk > sc->sc_transfer.data_rem) {
+ max_bulk = sc->sc_transfer.data_rem;
+ }
+ usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout);
+
+ usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr,
+ max_bulk);
+
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error == USB_ERR_CANCELLED) {
+ umass_tr_error(xfer, error);
+ } else {
+ umass_transfer_start(sc, UMASS_T_BBB_DATA_RD_CS);
+ }
+ return;
+ }
+}
+
+static void
+umass_t_bbb_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_STATUS,
+ UMASS_T_BBB_DATA_READ, error);
+}
+
+static void
+umass_t_bbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+ uint32_t max_bulk = usbd_xfer_max_len(xfer);
+ int actlen, sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ sc->sc_transfer.data_rem -= actlen;
+ sc->sc_transfer.data_ptr += actlen;
+ sc->sc_transfer.actlen += actlen;
+
+ if (actlen < sumlen) {
+ /* short transfer */
+ sc->sc_transfer.data_rem = 0;
+ }
+ case USB_ST_SETUP:
+ DPRINTF(sc, UDMASS_BBB, "max_bulk=%d, data_rem=%d\n",
+ max_bulk, sc->sc_transfer.data_rem);
+
+ if (sc->sc_transfer.data_rem == 0) {
+ umass_transfer_start(sc, UMASS_T_BBB_STATUS);
+ return;
+ }
+ if (max_bulk > sc->sc_transfer.data_rem) {
+ max_bulk = sc->sc_transfer.data_rem;
+ }
+ usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout);
+
+ usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr,
+ max_bulk);
+
+ usbd_transfer_submit(xfer);
+ return;
+
+ default: /* Error */
+ if (error == USB_ERR_CANCELLED) {
+ umass_tr_error(xfer, error);
+ } else {
+ umass_transfer_start(sc, UMASS_T_BBB_DATA_WR_CS);
+ }
+ return;
+ }
+}
+
+static void
+umass_t_bbb_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ umass_t_bbb_data_clear_stall_callback(xfer, UMASS_T_BBB_STATUS,
+ UMASS_T_BBB_DATA_WRITE, error);
+}
+
+static void
+umass_t_bbb_status_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+ union ccb *ccb = sc->sc_transfer.ccb;
+ struct usb_page_cache *pc;
+ uint32_t residue;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ /*
+ * Do a full reset if there is something wrong with the CSW:
+ */
+ sc->sc_status_try = 1;
+
+ /* Zero missing parts of the CSW: */
+
+ if (actlen < (int)sizeof(sc->csw))
+ memset(&sc->csw, 0, sizeof(sc->csw));
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &sc->csw, actlen);
+
+ DIF(UDMASS_BBB, umass_bbb_dump_csw(sc, &sc->csw));
+
+ residue = UGETDW(sc->csw.dCSWDataResidue);
+
+ if ((!residue) || (sc->sc_quirks & IGNORE_RESIDUE)) {
+ residue = (sc->sc_transfer.data_len -
+ sc->sc_transfer.actlen);
+ }
+ if (residue > sc->sc_transfer.data_len) {
+ DPRINTF(sc, UDMASS_BBB, "truncating residue from %d "
+ "to %d bytes\n", residue, sc->sc_transfer.data_len);
+ residue = sc->sc_transfer.data_len;
+ }
+ /* translate weird command-status signatures: */
+ if (sc->sc_quirks & WRONG_CSWSIG) {
+ uint32_t temp = UGETDW(sc->csw.dCSWSignature);
+
+ if ((temp == CSWSIGNATURE_OLYMPUS_C1) ||
+ (temp == CSWSIGNATURE_IMAGINATION_DBX1)) {
+ USETDW(sc->csw.dCSWSignature, CSWSIGNATURE);
+ }
+ }
+ /* check CSW and handle eventual error */
+ if (UGETDW(sc->csw.dCSWSignature) != CSWSIGNATURE) {
+ DPRINTF(sc, UDMASS_BBB, "bad CSW signature 0x%08x != 0x%08x\n",
+ UGETDW(sc->csw.dCSWSignature), CSWSIGNATURE);
+ /*
+ * Invalid CSW: Wrong signature or wrong tag might
+ * indicate that we lost synchronization. Reset the
+ * device.
+ */
+ goto tr_error;
+ } else if (UGETDW(sc->csw.dCSWTag) != UGETDW(sc->cbw.dCBWTag)) {
+ DPRINTF(sc, UDMASS_BBB, "Invalid CSW: tag 0x%08x should be "
+ "0x%08x\n", UGETDW(sc->csw.dCSWTag),
+ UGETDW(sc->cbw.dCBWTag));
+ goto tr_error;
+ } else if (sc->csw.bCSWStatus > CSWSTATUS_PHASE) {
+ DPRINTF(sc, UDMASS_BBB, "Invalid CSW: status %d > %d\n",
+ sc->csw.bCSWStatus, CSWSTATUS_PHASE);
+ goto tr_error;
+ } else if (sc->csw.bCSWStatus == CSWSTATUS_PHASE) {
+ DPRINTF(sc, UDMASS_BBB, "Phase error, residue = "
+ "%d\n", residue);
+ goto tr_error;
+ } else if (sc->sc_transfer.actlen > sc->sc_transfer.data_len) {
+ DPRINTF(sc, UDMASS_BBB, "Buffer overrun %d > %d\n",
+ sc->sc_transfer.actlen, sc->sc_transfer.data_len);
+ goto tr_error;
+ } else if (sc->csw.bCSWStatus == CSWSTATUS_FAILED) {
+ DPRINTF(sc, UDMASS_BBB, "Command failed, residue = "
+ "%d\n", residue);
+
+ sc->sc_transfer.ccb = NULL;
+
+ sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND;
+
+ (sc->sc_transfer.callback)
+ (sc, ccb, residue, STATUS_CMD_FAILED);
+ } else {
+ sc->sc_transfer.ccb = NULL;
+
+ sc->sc_last_xfer_index = UMASS_T_BBB_COMMAND;
+
+ (sc->sc_transfer.callback)
+ (sc, ccb, residue, STATUS_CMD_OK);
+ }
+ return;
+
+ case USB_ST_SETUP:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ return;
+
+ default:
+tr_error:
+ DPRINTF(sc, UDMASS_BBB, "Failed to read CSW: %s, try %d\n",
+ usbd_errstr(error), sc->sc_status_try);
+
+ if ((error == USB_ERR_CANCELLED) ||
+ (sc->sc_status_try)) {
+ umass_tr_error(xfer, error);
+ } else {
+ sc->sc_status_try = 1;
+ umass_transfer_start(sc, UMASS_T_BBB_DATA_RD_CS);
+ }
+ return;
+ }
+}
+
+static void
+umass_command_start(struct umass_softc *sc, uint8_t dir,
+ void *data_ptr, uint32_t data_len,
+ uint32_t data_timeout, umass_callback_t *callback,
+ union ccb *ccb)
+{
+ sc->sc_transfer.lun = ccb->ccb_h.target_lun;
+
+ /*
+ * NOTE: assumes that "sc->sc_transfer.cmd_data" and
+ * "sc->sc_transfer.cmd_len" has been properly
+ * initialized.
+ */
+
+ sc->sc_transfer.dir = data_len ? dir : DIR_NONE;
+ sc->sc_transfer.data_ptr = data_ptr;
+ sc->sc_transfer.data_len = data_len;
+ sc->sc_transfer.data_rem = data_len;
+ sc->sc_transfer.data_timeout = (data_timeout + UMASS_TIMEOUT);
+
+ sc->sc_transfer.actlen = 0;
+ sc->sc_transfer.callback = callback;
+ sc->sc_transfer.ccb = ccb;
+
+ if (sc->sc_xfer[sc->sc_last_xfer_index]) {
+ usbd_transfer_start(sc->sc_xfer[sc->sc_last_xfer_index]);
+ } else {
+ umass_cancel_ccb(sc);
+ }
+}
+
+static uint8_t
+umass_bbb_get_max_lun(struct umass_softc *sc)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+ uint8_t buf = 0;
+
+ /* The Get Max Lun command is a class-specific request. */
+ req.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.bRequest = UR_BBB_GET_MAX_LUN;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 1);
+
+ err = usbd_do_request(sc->sc_udev, NULL, &req, &buf);
+ if (err) {
+ buf = 0;
+
+ /* Device doesn't support Get Max Lun request. */
+ printf("%s: Get Max Lun not supported (%s)\n",
+ sc->sc_name, usbd_errstr(err));
+ }
+ return (buf);
+}
+
+/*
+ * Command/Bulk/Interrupt (CBI) specific functions
+ */
+
+static void
+umass_cbi_start_status(struct umass_softc *sc)
+{
+ if (sc->sc_xfer[UMASS_T_CBI_STATUS]) {
+ umass_transfer_start(sc, UMASS_T_CBI_STATUS);
+ } else {
+ union ccb *ccb = sc->sc_transfer.ccb;
+
+ sc->sc_transfer.ccb = NULL;
+
+ sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND;
+
+ (sc->sc_transfer.callback)
+ (sc, ccb, (sc->sc_transfer.data_len -
+ sc->sc_transfer.actlen), STATUS_CMD_UNKNOWN);
+ }
+}
+
+static void
+umass_t_cbi_reset1_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_device_request req;
+ struct usb_page_cache *pc;
+ uint8_t buf[UMASS_CBI_DIAGNOSTIC_CMDLEN];
+
+ uint8_t i;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ umass_transfer_start(sc, UMASS_T_CBI_RESET2);
+ break;
+
+ case USB_ST_SETUP:
+ /*
+ * Command Block Reset Protocol
+ *
+ * First send a reset request to the device. Then clear
+ * any possibly stalled bulk endpoints.
+ *
+ * This is done in 3 steps, using 3 transfers:
+ * UMASS_T_CBI_RESET1
+ * UMASS_T_CBI_RESET2
+ * UMASS_T_CBI_RESET3
+ * UMASS_T_CBI_RESET4 (only if there is an interrupt endpoint)
+ */
+
+ DPRINTF(sc, UDMASS_CBI, "CBI reset!\n");
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UR_CBI_ADSC;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, UMASS_CBI_DIAGNOSTIC_CMDLEN);
+
+ /*
+ * The 0x1d code is the SEND DIAGNOSTIC command. To
+ * distinguish between the two, the last 10 bytes of the CBL
+ * is filled with 0xff (section 2.2 of the CBI
+ * specification)
+ */
+ buf[0] = 0x1d; /* Command Block Reset */
+ buf[1] = 0x04;
+
+ for (i = 2; i < UMASS_CBI_DIAGNOSTIC_CMDLEN; i++) {
+ buf[i] = 0xff;
+ }
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+ pc = usbd_xfer_get_frame(xfer, 1);
+ usbd_copy_in(pc, 0, buf, sizeof(buf));
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, sizeof(buf));
+ usbd_xfer_set_frames(xfer, 2);
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error == USB_ERR_CANCELLED)
+ umass_tr_error(xfer, error);
+ else
+ umass_transfer_start(sc, UMASS_T_CBI_RESET2);
+ break;
+ }
+}
+
+static void
+umass_t_cbi_reset2_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_RESET3,
+ UMASS_T_CBI_DATA_READ, error);
+}
+
+static void
+umass_t_cbi_reset3_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+
+ umass_t_cbi_data_clear_stall_callback
+ (xfer, (sc->sc_xfer[UMASS_T_CBI_RESET4] &&
+ sc->sc_xfer[UMASS_T_CBI_STATUS]) ?
+ UMASS_T_CBI_RESET4 : UMASS_T_CBI_COMMAND,
+ UMASS_T_CBI_DATA_WRITE, error);
+}
+
+static void
+umass_t_cbi_reset4_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_COMMAND,
+ UMASS_T_CBI_STATUS, error);
+}
+
+static void
+umass_t_cbi_data_clear_stall_callback(struct usb_xfer *xfer,
+ uint8_t next_xfer, uint8_t stall_xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+tr_transferred:
+ if (next_xfer == UMASS_T_CBI_STATUS) {
+ umass_cbi_start_status(sc);
+ } else {
+ umass_transfer_start(sc, next_xfer);
+ }
+ break;
+
+ case USB_ST_SETUP:
+ if (usbd_clear_stall_callback(xfer, sc->sc_xfer[stall_xfer])) {
+ goto tr_transferred; /* should not happen */
+ }
+ break;
+
+ default: /* Error */
+ umass_tr_error(xfer, error);
+ break;
+ }
+}
+
+static void
+umass_t_cbi_command_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+ union ccb *ccb = sc->sc_transfer.ccb;
+ struct usb_device_request req;
+ struct usb_page_cache *pc;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ if (sc->sc_transfer.dir == DIR_NONE) {
+ umass_cbi_start_status(sc);
+ } else {
+ umass_transfer_start
+ (sc, (sc->sc_transfer.dir == DIR_IN) ?
+ UMASS_T_CBI_DATA_READ : UMASS_T_CBI_DATA_WRITE);
+ }
+ break;
+
+ case USB_ST_SETUP:
+
+ if (ccb) {
+ /*
+ * do a CBI transfer with cmd_len bytes from
+ * cmd_data, possibly a data phase of data_len
+ * bytes from/to the device and finally a status
+ * read phase.
+ */
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UR_CBI_ADSC;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = sc->sc_iface_no;
+ req.wIndex[1] = 0;
+ req.wLength[0] = sc->sc_transfer.cmd_len;
+ req.wLength[1] = 0;
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &req, sizeof(req));
+ pc = usbd_xfer_get_frame(xfer, 1);
+ usbd_copy_in(pc, 0, sc->sc_transfer.cmd_data,
+ sc->sc_transfer.cmd_len);
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ usbd_xfer_set_frame_len(xfer, 1, sc->sc_transfer.cmd_len);
+ usbd_xfer_set_frames(xfer,
+ sc->sc_transfer.cmd_len ? 2 : 1);
+
+ DIF(UDMASS_CBI,
+ umass_cbi_dump_cmd(sc,
+ sc->sc_transfer.cmd_data,
+ sc->sc_transfer.cmd_len));
+
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ /*
+ * STALL on the control pipe can be result of the command error.
+ * Attempt to clear this STALL same as for bulk pipe also
+ * results in command completion interrupt, but ASC/ASCQ there
+ * look like not always valid, so don't bother about it.
+ */
+ if ((error == USB_ERR_STALLED) ||
+ (sc->sc_transfer.callback == &umass_cam_cb)) {
+ sc->sc_transfer.ccb = NULL;
+ (sc->sc_transfer.callback)
+ (sc, ccb, sc->sc_transfer.data_len,
+ STATUS_CMD_UNKNOWN);
+ } else {
+ umass_tr_error(xfer, error);
+ /* skip reset */
+ sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND;
+ }
+ break;
+ }
+}
+
+static void
+umass_t_cbi_data_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+ uint32_t max_bulk = usbd_xfer_max_len(xfer);
+ int actlen, sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ sc->sc_transfer.data_rem -= actlen;
+ sc->sc_transfer.data_ptr += actlen;
+ sc->sc_transfer.actlen += actlen;
+
+ if (actlen < sumlen) {
+ /* short transfer */
+ sc->sc_transfer.data_rem = 0;
+ }
+ case USB_ST_SETUP:
+ DPRINTF(sc, UDMASS_CBI, "max_bulk=%d, data_rem=%d\n",
+ max_bulk, sc->sc_transfer.data_rem);
+
+ if (sc->sc_transfer.data_rem == 0) {
+ umass_cbi_start_status(sc);
+ break;
+ }
+ if (max_bulk > sc->sc_transfer.data_rem) {
+ max_bulk = sc->sc_transfer.data_rem;
+ }
+ usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout);
+
+ usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr,
+ max_bulk);
+
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if ((error == USB_ERR_CANCELLED) ||
+ (sc->sc_transfer.callback != &umass_cam_cb)) {
+ umass_tr_error(xfer, error);
+ } else {
+ umass_transfer_start(sc, UMASS_T_CBI_DATA_RD_CS);
+ }
+ break;
+ }
+}
+
+static void
+umass_t_cbi_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_STATUS,
+ UMASS_T_CBI_DATA_READ, error);
+}
+
+static void
+umass_t_cbi_data_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+ uint32_t max_bulk = usbd_xfer_max_len(xfer);
+ int actlen, sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ sc->sc_transfer.data_rem -= actlen;
+ sc->sc_transfer.data_ptr += actlen;
+ sc->sc_transfer.actlen += actlen;
+
+ if (actlen < sumlen) {
+ /* short transfer */
+ sc->sc_transfer.data_rem = 0;
+ }
+ case USB_ST_SETUP:
+ DPRINTF(sc, UDMASS_CBI, "max_bulk=%d, data_rem=%d\n",
+ max_bulk, sc->sc_transfer.data_rem);
+
+ if (sc->sc_transfer.data_rem == 0) {
+ umass_cbi_start_status(sc);
+ break;
+ }
+ if (max_bulk > sc->sc_transfer.data_rem) {
+ max_bulk = sc->sc_transfer.data_rem;
+ }
+ usbd_xfer_set_timeout(xfer, sc->sc_transfer.data_timeout);
+
+ usbd_xfer_set_frame_data(xfer, 0, sc->sc_transfer.data_ptr,
+ max_bulk);
+
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if ((error == USB_ERR_CANCELLED) ||
+ (sc->sc_transfer.callback != &umass_cam_cb)) {
+ umass_tr_error(xfer, error);
+ } else {
+ umass_transfer_start(sc, UMASS_T_CBI_DATA_WR_CS);
+ }
+ break;
+ }
+}
+
+static void
+umass_t_cbi_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ umass_t_cbi_data_clear_stall_callback(xfer, UMASS_T_CBI_STATUS,
+ UMASS_T_CBI_DATA_WRITE, error);
+}
+
+static void
+umass_t_cbi_status_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct umass_softc *sc = usbd_xfer_softc(xfer);
+ union ccb *ccb = sc->sc_transfer.ccb;
+ struct usb_page_cache *pc;
+ uint32_t residue;
+ uint8_t status;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ if (actlen < (int)sizeof(sc->sbl)) {
+ goto tr_setup;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &sc->sbl, sizeof(sc->sbl));
+
+ residue = (sc->sc_transfer.data_len -
+ sc->sc_transfer.actlen);
+
+ /* dissect the information in the buffer */
+
+ if (sc->sc_proto & UMASS_PROTO_UFI) {
+ /*
+ * Section 3.4.3.1.3 specifies that the UFI command
+ * protocol returns an ASC and ASCQ in the interrupt
+ * data block. However, we might also be fetching the
+ * sense explicitly, where they are likely to be
+ * non-zero, in which case we should succeed.
+ */
+
+ DPRINTF(sc, UDMASS_CBI, "UFI CCI, ASC = 0x%02x, "
+ "ASCQ = 0x%02x\n", sc->sbl.ufi.asc,
+ sc->sbl.ufi.ascq);
+
+ if ((sc->sbl.ufi.asc == 0 && sc->sbl.ufi.ascq == 0) ||
+ sc->sc_transfer.cmd_data[0] == REQUEST_SENSE)
+ status = STATUS_CMD_OK;
+ else
+ status = STATUS_CMD_FAILED;
+
+ sc->sc_transfer.ccb = NULL;
+
+ sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND;
+
+ (sc->sc_transfer.callback)
+ (sc, ccb, residue, status);
+
+ break;
+
+ } else {
+ /* Command Interrupt Data Block */
+
+ DPRINTF(sc, UDMASS_CBI, "type=0x%02x, value=0x%02x\n",
+ sc->sbl.common.type, sc->sbl.common.value);
+
+ if (sc->sbl.common.type == IDB_TYPE_CCI) {
+ status = (sc->sbl.common.value & IDB_VALUE_STATUS_MASK);
+
+ status = ((status == IDB_VALUE_PASS) ? STATUS_CMD_OK :
+ (status == IDB_VALUE_FAIL) ? STATUS_CMD_FAILED :
+ (status == IDB_VALUE_PERSISTENT) ? STATUS_CMD_FAILED :
+ STATUS_WIRE_FAILED);
+
+ sc->sc_transfer.ccb = NULL;
+
+ sc->sc_last_xfer_index = UMASS_T_CBI_COMMAND;
+
+ (sc->sc_transfer.callback)
+ (sc, ccb, residue, status);
+
+ break;
+ }
+ }
+
+ /* fallthrough */
+
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ DPRINTF(sc, UDMASS_CBI, "Failed to read CSW: %s\n",
+ usbd_errstr(error));
+ umass_tr_error(xfer, error);
+ break;
+ }
+}
+
+/*
+ * CAM specific functions (used by SCSI, UFI, 8070i (ATAPI))
+ */
+
+static int
+umass_cam_attach_sim(struct umass_softc *sc)
+{
+ struct cam_devq *devq; /* Per device Queue */
+ cam_status status;
+
+ /*
+ * A HBA is attached to the CAM layer.
+ *
+ * The CAM layer will then after a while start probing for devices on
+ * the bus. The number of SIMs is limited to one.
+ */
+
+ devq = cam_simq_alloc(1 /* maximum openings */ );
+ if (devq == NULL) {
+ return (ENOMEM);
+ }
+ sc->sc_sim = cam_sim_alloc
+ (&umass_cam_action, &umass_cam_poll,
+ DEVNAME_SIM,
+ sc /* priv */ ,
+ sc->sc_unit /* unit number */ ,
+ &sc->sc_mtx /* mutex */ ,
+ 1 /* maximum device openings */ ,
+ 0 /* maximum tagged device openings */ ,
+ devq);
+
+ if (sc->sc_sim == NULL) {
+ cam_simq_free(devq);
+ return (ENOMEM);
+ }
+
+ mtx_lock(&sc->sc_mtx);
+ status = xpt_bus_register(sc->sc_sim, sc->sc_dev, sc->sc_unit);
+ if (status != CAM_SUCCESS) {
+ cam_sim_free(sc->sc_sim, /* free_devq */ TRUE);
+ mtx_unlock(&sc->sc_mtx);
+ printf("%s: xpt_bus_register failed with status %#x\n",
+ __func__, status);
+ return (ENOMEM);
+ }
+ mtx_unlock(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+umass_cam_attach(struct umass_softc *sc)
+{
+#ifndef USB_DEBUG
+ if (bootverbose)
+#endif
+ printf("%s:%d:%d: Attached to scbus%d\n",
+ sc->sc_name, cam_sim_path(sc->sc_sim),
+ sc->sc_unit, cam_sim_path(sc->sc_sim));
+}
+
+/* umass_cam_detach
+ * detach from the CAM layer
+ */
+
+static void
+umass_cam_detach_sim(struct umass_softc *sc)
+{
+ int error;
+
+ if (sc->sc_sim != NULL) {
+ error = xpt_bus_deregister(cam_sim_path(sc->sc_sim));
+ if (error == 0) {
+ /* accessing the softc is not possible after this */
+ sc->sc_sim->softc = NULL;
+ DPRINTF(sc, UDMASS_SCSI, "%s: %s:%d:%d caling "
+ "cam_sim_free sim %p refc %u mtx %p\n",
+ __func__, sc->sc_name, cam_sim_path(sc->sc_sim),
+ sc->sc_unit, sc->sc_sim,
+ sc->sc_sim->refcount, sc->sc_sim->mtx);
+ cam_sim_free(sc->sc_sim, /* free_devq */ TRUE);
+ } else {
+ panic("%s: %s: CAM layer is busy: errno %d\n",
+ __func__, sc->sc_name, error);
+ }
+ sc->sc_sim = NULL;
+ }
+}
+
+/* umass_cam_action
+ * CAM requests for action come through here
+ */
+
+static void
+umass_cam_action(struct cam_sim *sim, union ccb *ccb)
+{
+ struct umass_softc *sc = cam_sim_softc(sim);
+
+ if (sc == NULL) {
+ ccb->ccb_h.status = CAM_SEL_TIMEOUT;
+ xpt_done(ccb);
+ return;
+ }
+
+ /* Perform the requested action */
+ switch (ccb->ccb_h.func_code) {
+ case XPT_SCSI_IO:
+ {
+ uint8_t *cmd;
+ uint8_t dir;
+
+ if (ccb->csio.ccb_h.flags & CAM_CDB_POINTER) {
+ cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_ptr);
+ } else {
+ cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_bytes);
+ }
+
+ DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_SCSI_IO: "
+ "cmd: 0x%02x, flags: 0x%02x, "
+ "%db cmd/%db data/%db sense\n",
+ cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id,
+ (uintmax_t)ccb->ccb_h.target_lun, cmd[0],
+ ccb->ccb_h.flags & CAM_DIR_MASK, ccb->csio.cdb_len,
+ ccb->csio.dxfer_len, ccb->csio.sense_len);
+
+ if (sc->sc_transfer.ccb) {
+ DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_SCSI_IO: "
+ "I/O in progress, deferring\n",
+ cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id,
+ (uintmax_t)ccb->ccb_h.target_lun);
+ ccb->ccb_h.status = CAM_SCSI_BUSY;
+ xpt_done(ccb);
+ goto done;
+ }
+ switch (ccb->ccb_h.flags & CAM_DIR_MASK) {
+ case CAM_DIR_IN:
+ dir = DIR_IN;
+ break;
+ case CAM_DIR_OUT:
+ dir = DIR_OUT;
+ DIF(UDMASS_SCSI,
+ umass_dump_buffer(sc, ccb->csio.data_ptr,
+ ccb->csio.dxfer_len, 48));
+ break;
+ default:
+ dir = DIR_NONE;
+ }
+
+ ccb->ccb_h.status = CAM_REQ_INPROG | CAM_SIM_QUEUED;
+
+ /*
+ * sc->sc_transform will convert the command to the
+ * command format needed by the specific command set
+ * and return the converted command in
+ * "sc->sc_transfer.cmd_data"
+ *
+ * For commands we know the device doesn't support, we
+ * either complete them with an illegal request, or fake
+ * the completion, based on what upper layers tolerate.
+ * Ideally, we'd let the periph drivers know and not
+ * fake things up, but some periphs fall short of the
+ * ideal.
+ */
+ if (umass_std_transform(sc, ccb, cmd, ccb->csio.cdb_len)) {
+ if (sc->sc_transfer.cmd_data[0] == INQUIRY) {
+ const char *pserial;
+
+ pserial = usb_get_serial(sc->sc_udev);
+
+ /*
+ * Umass devices don't generally report their serial numbers
+ * in the usual SCSI way. Emulate it here.
+ */
+ if ((sc->sc_transfer.cmd_data[1] & SI_EVPD) &&
+ (sc->sc_transfer.cmd_data[2] == SVPD_UNIT_SERIAL_NUMBER) &&
+ (pserial[0] != '\0')) {
+ struct scsi_vpd_unit_serial_number *vpd_serial;
+
+ vpd_serial = (struct scsi_vpd_unit_serial_number *)ccb->csio.data_ptr;
+ vpd_serial->length = strlen(pserial);
+ if (vpd_serial->length > sizeof(vpd_serial->serial_num))
+ vpd_serial->length = sizeof(vpd_serial->serial_num);
+ memcpy(vpd_serial->serial_num, pserial, vpd_serial->length);
+ ccb->csio.scsi_status = SCSI_STATUS_OK;
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ xpt_done(ccb);
+ goto done;
+ }
+
+ /*
+ * Handle EVPD inquiry for broken devices first
+ * NO_INQUIRY also implies NO_INQUIRY_EVPD
+ */
+ if ((sc->sc_quirks & (NO_INQUIRY_EVPD | NO_INQUIRY)) &&
+ (sc->sc_transfer.cmd_data[1] & SI_EVPD)) {
+ umass_cam_illegal_request(ccb);
+ goto done;
+ }
+ /*
+ * Return fake inquiry data for
+ * broken devices
+ */
+ if (sc->sc_quirks & NO_INQUIRY) {
+ memcpy(ccb->csio.data_ptr, &fake_inq_data,
+ sizeof(fake_inq_data));
+ ccb->csio.scsi_status = SCSI_STATUS_OK;
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ xpt_done(ccb);
+ goto done;
+ }
+ if (sc->sc_quirks & FORCE_SHORT_INQUIRY) {
+ ccb->csio.dxfer_len = SHORT_INQUIRY_LENGTH;
+ }
+ } else if (sc->sc_transfer.cmd_data[0] == PREVENT_ALLOW) {
+ if (sc->sc_quirks & NO_PREVENT_ALLOW) {
+ ccb->csio.scsi_status = SCSI_STATUS_OK;
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ xpt_done(ccb);
+ goto done;
+ }
+ } else if (sc->sc_transfer.cmd_data[0] == SYNCHRONIZE_CACHE) {
+ if (sc->sc_quirks & NO_SYNCHRONIZE_CACHE) {
+ umass_cam_illegal_request(ccb);
+ goto done;
+ }
+ } else if (sc->sc_transfer.cmd_data[0] == START_STOP_UNIT) {
+ if (sc->sc_quirks & NO_START_STOP) {
+ ccb->csio.scsi_status = SCSI_STATUS_OK;
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ xpt_done(ccb);
+ goto done;
+ }
+ }
+ umass_command_start(sc, dir, ccb->csio.data_ptr,
+ ccb->csio.dxfer_len,
+ ccb->ccb_h.timeout,
+ &umass_cam_cb, ccb);
+ }
+ break;
+ }
+ case XPT_PATH_INQ:
+ {
+ struct ccb_pathinq *cpi = &ccb->cpi;
+
+ DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_PATH_INQ:.\n",
+ sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id,
+ (uintmax_t)ccb->ccb_h.target_lun);
+
+ /* host specific information */
+ cpi->version_num = 1;
+ cpi->hba_inquiry = 0;
+ cpi->target_sprt = 0;
+ cpi->hba_misc = PIM_NO_6_BYTE;
+ cpi->hba_eng_cnt = 0;
+ cpi->max_target = UMASS_SCSIID_MAX; /* one target */
+ cpi->initiator_id = UMASS_SCSIID_HOST;
+ strlcpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN);
+ strlcpy(cpi->hba_vid, "USB SCSI", HBA_IDLEN);
+ strlcpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN);
+ cpi->unit_number = cam_sim_unit(sim);
+ cpi->bus_id = sc->sc_unit;
+ 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->sc_quirks & FLOPPY_SPEED) {
+ cpi->base_transfer_speed =
+ UMASS_FLOPPY_TRANSFER_SPEED;
+ } else {
+ switch (usbd_get_speed(sc->sc_udev)) {
+ case USB_SPEED_SUPER:
+ cpi->base_transfer_speed =
+ UMASS_SUPER_TRANSFER_SPEED;
+ cpi->maxio = maxphys;
+ break;
+ case USB_SPEED_HIGH:
+ cpi->base_transfer_speed =
+ UMASS_HIGH_TRANSFER_SPEED;
+ break;
+ default:
+ cpi->base_transfer_speed =
+ UMASS_FULL_TRANSFER_SPEED;
+ break;
+ }
+ }
+ cpi->max_lun = sc->sc_maxlun;
+ }
+
+ cpi->ccb_h.status = CAM_REQ_CMP;
+ xpt_done(ccb);
+ break;
+ }
+ case XPT_RESET_DEV:
+ {
+ DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_RESET_DEV:.\n",
+ cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id,
+ (uintmax_t)ccb->ccb_h.target_lun);
+
+ umass_reset(sc);
+
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ xpt_done(ccb);
+ break;
+ }
+ case XPT_GET_TRAN_SETTINGS:
+ {
+ struct ccb_trans_settings *cts = &ccb->cts;
+
+ DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_GET_TRAN_SETTINGS:.\n",
+ cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id,
+ (uintmax_t)ccb->ccb_h.target_lun);
+
+ 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(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_SET_TRAN_SETTINGS:.\n",
+ cam_sim_path(sc->sc_sim), ccb->ccb_h.target_id,
+ (uintmax_t)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(sc, UDMASS_SCSI, "%d:%d:%jx:XPT_NOOP:.\n",
+ sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id,
+ (uintmax_t)ccb->ccb_h.target_lun);
+
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ xpt_done(ccb);
+ break;
+ }
+ default:
+ DPRINTF(sc, UDMASS_SCSI, "%d:%d:%jx:func_code 0x%04x: "
+ "Not implemented\n",
+ sc ? cam_sim_path(sc->sc_sim) : -1, ccb->ccb_h.target_id,
+ (uintmax_t)ccb->ccb_h.target_lun, ccb->ccb_h.func_code);
+
+ ccb->ccb_h.status = CAM_FUNC_NOTAVAIL;
+ xpt_done(ccb);
+ break;
+ }
+
+done:
+ return;
+}
+
+static void
+umass_cam_poll(struct cam_sim *sim)
+{
+ struct umass_softc *sc = cam_sim_softc(sim);
+
+ if (sc == NULL)
+ return;
+
+ DPRINTF(sc, UDMASS_SCSI, "CAM poll\n");
+
+ usbd_transfer_poll(sc->sc_xfer, UMASS_T_MAX);
+}
+
+/* umass_cam_illegal_request
+ * Complete the command as an illegal command with invalid field
+ */
+
+static void
+umass_cam_illegal_request(union ccb *ccb)
+{
+ scsi_set_sense_data(&ccb->csio.sense_data,
+ /*sense_format*/ SSD_TYPE_NONE,
+ /*current_error*/ 1,
+ /*sense_key*/ SSD_KEY_ILLEGAL_REQUEST,
+ /*asc*/ 0x24, /* 24h/00h INVALID FIELD IN CDB */
+ /*ascq*/ 0x00,
+ /*extra args*/ SSD_ELEM_NONE);
+ ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND;
+ ccb->ccb_h.status =
+ CAM_SCSI_STATUS_ERROR |
+ CAM_AUTOSNS_VALID |
+ CAM_DEV_QFRZN;
+ xpt_freeze_devq(ccb->ccb_h.path, 1);
+ xpt_done(ccb);
+}
+
+/* umass_cam_cb
+ * finalise a completed CAM command
+ */
+
+static void
+umass_cam_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue,
+ uint8_t status)
+{
+ ccb->csio.resid = residue;
+
+ switch (status) {
+ case STATUS_CMD_OK:
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ if ((sc->sc_quirks & READ_CAPACITY_OFFBY1) &&
+ (ccb->ccb_h.func_code == XPT_SCSI_IO) &&
+ (ccb->csio.cdb_io.cdb_bytes[0] == READ_CAPACITY)) {
+ struct scsi_read_capacity_data *rcap;
+ uint32_t maxsector;
+
+ rcap = (void *)(ccb->csio.data_ptr);
+ maxsector = scsi_4btoul(rcap->addr) - 1;
+ scsi_ulto4b(maxsector, rcap->addr);
+ }
+ /*
+ * We have to add SVPD_UNIT_SERIAL_NUMBER to the list
+ * of pages supported by the device - otherwise, CAM
+ * will never ask us for the serial number if the
+ * device cannot handle that by itself.
+ */
+ if (ccb->ccb_h.func_code == XPT_SCSI_IO &&
+ sc->sc_transfer.cmd_data[0] == INQUIRY &&
+ (sc->sc_transfer.cmd_data[1] & SI_EVPD) &&
+ sc->sc_transfer.cmd_data[2] == SVPD_SUPPORTED_PAGE_LIST &&
+ (usb_get_serial(sc->sc_udev)[0] != '\0')) {
+ struct ccb_scsiio *csio;
+ struct scsi_vpd_supported_page_list *page_list;
+
+ csio = &ccb->csio;
+ page_list = (struct scsi_vpd_supported_page_list *)csio->data_ptr;
+ if (page_list->length + 1 < SVPD_SUPPORTED_PAGES_SIZE) {
+ page_list->list[page_list->length] = SVPD_UNIT_SERIAL_NUMBER;
+ page_list->length++;
+ }
+ }
+ xpt_done(ccb);
+ break;
+
+ case STATUS_CMD_UNKNOWN:
+ case STATUS_CMD_FAILED:
+
+ /* fetch sense data */
+
+ /* the rest of the command was filled in at attach */
+ sc->cam_scsi_sense.length = ccb->csio.sense_len;
+
+ DPRINTF(sc, UDMASS_SCSI, "Fetching %d bytes of "
+ "sense data\n", ccb->csio.sense_len);
+
+ if (umass_std_transform(sc, ccb, &sc->cam_scsi_sense.opcode,
+ sizeof(sc->cam_scsi_sense))) {
+ umass_command_start(sc, DIR_IN, &ccb->csio.sense_data.error_code,
+ ccb->csio.sense_len, ccb->ccb_h.timeout,
+ &umass_cam_sense_cb, ccb);
+ }
+ break;
+
+ default:
+ /*
+ * The wire protocol failed and will hopefully have
+ * recovered. We return an error to CAM and let CAM
+ * retry the command if necessary.
+ */
+ xpt_freeze_devq(ccb->ccb_h.path, 1);
+ ccb->ccb_h.status = CAM_REQ_CMP_ERR | CAM_DEV_QFRZN;
+ xpt_done(ccb);
+ break;
+ }
+}
+
+/*
+ * Finalise a completed autosense operation
+ */
+static void
+umass_cam_sense_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue,
+ uint8_t status)
+{
+ uint8_t *cmd;
+
+ switch (status) {
+ case STATUS_CMD_OK:
+ case STATUS_CMD_UNKNOWN:
+ case STATUS_CMD_FAILED: {
+ int key, sense_len;
+
+ ccb->csio.sense_resid = residue;
+ sense_len = ccb->csio.sense_len - ccb->csio.sense_resid;
+ key = scsi_get_sense_key(&ccb->csio.sense_data, sense_len,
+ /*show_errors*/ 1);
+
+ if (ccb->csio.ccb_h.flags & CAM_CDB_POINTER) {
+ cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_ptr);
+ } else {
+ cmd = (uint8_t *)(ccb->csio.cdb_io.cdb_bytes);
+ }
+
+ /*
+ * Getting sense data always succeeds (apart from wire
+ * failures):
+ */
+ if ((sc->sc_quirks & RS_NO_CLEAR_UA) &&
+ (cmd[0] == INQUIRY) &&
+ (key == SSD_KEY_UNIT_ATTENTION)) {
+ /*
+ * Ignore unit attention errors in the case where
+ * the Unit Attention state is not cleared on
+ * REQUEST SENSE. They will appear again at the next
+ * command.
+ */
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ } else if (key == SSD_KEY_NO_SENSE) {
+ /*
+ * No problem after all (in the case of CBI without
+ * CCI)
+ */
+ ccb->ccb_h.status = CAM_REQ_CMP;
+ } else if ((sc->sc_quirks & RS_NO_CLEAR_UA) &&
+ (cmd[0] == READ_CAPACITY) &&
+ (key == SSD_KEY_UNIT_ATTENTION)) {
+ /*
+ * Some devices do not clear the unit attention error
+ * on request sense. We insert a test unit ready
+ * command to make sure we clear the unit attention
+ * condition, then allow the retry to proceed as
+ * usual.
+ */
+
+ xpt_freeze_devq(ccb->ccb_h.path, 1);
+ ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR
+ | CAM_AUTOSNS_VALID | CAM_DEV_QFRZN;
+ ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND;
+
+#if 0
+ DELAY(300000);
+#endif
+ DPRINTF(sc, UDMASS_SCSI, "Doing a sneaky"
+ "TEST_UNIT_READY\n");
+
+ /*
+ * Transform the TUR and if successful, send it. Pass
+ * NULL for the ccb so we don't override the above
+ * status.
+ */
+ if (umass_std_transform(sc, NULL,
+ &sc->cam_scsi_test_unit_ready.opcode,
+ sizeof(sc->cam_scsi_test_unit_ready))) {
+ umass_command_start(sc, DIR_NONE, NULL, 0,
+ ccb->ccb_h.timeout,
+ &umass_cam_quirk_cb, ccb);
+ break;
+ }
+ } else {
+ xpt_freeze_devq(ccb->ccb_h.path, 1);
+ if (key >= 0) {
+ ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR
+ | CAM_AUTOSNS_VALID | CAM_DEV_QFRZN;
+ ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND;
+ } else
+ ccb->ccb_h.status = CAM_AUTOSENSE_FAIL
+ | CAM_DEV_QFRZN;
+ }
+ xpt_done(ccb);
+ break;
+ }
+ default:
+ DPRINTF(sc, UDMASS_SCSI, "Autosense failed, "
+ "status %d\n", status);
+ xpt_freeze_devq(ccb->ccb_h.path, 1);
+ ccb->ccb_h.status = CAM_AUTOSENSE_FAIL | CAM_DEV_QFRZN;
+ 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. The CCB
+ * status for CAM is already set earlier.
+ */
+static void
+umass_cam_quirk_cb(struct umass_softc *sc, union ccb *ccb, uint32_t residue,
+ uint8_t status)
+{
+ DPRINTF(sc, UDMASS_SCSI, "Test unit ready "
+ "returned status %d\n", status);
+
+ xpt_done(ccb);
+}
+
+/*
+ * SCSI specific functions
+ */
+
+static bool
+umass_scsi_transform(struct umass_softc *sc, uint8_t *cmd_ptr,
+ uint8_t cmd_len)
+{
+ sc->sc_transfer.cmd_len = cmd_len;
+
+ return (true);
+}
+
+static bool
+umass_rbc_transform(struct umass_softc *sc, uint8_t *cmd_ptr, uint8_t cmd_len)
+{
+ switch (cmd_ptr[0]) {
+ /* these commands are defined in RBC: */
+ case READ_10:
+ case READ_CAPACITY:
+ case START_STOP_UNIT:
+ case SYNCHRONIZE_CACHE:
+ case WRITE_10:
+ case VERIFY_10:
+ 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->sc_quirks & RBC_PAD_TO_12) && (cmd_len < 12)) {
+ memset(sc->sc_transfer.cmd_data + cmd_len,
+ 0, 12 - cmd_len);
+ cmd_len = 12;
+ }
+ sc->sc_transfer.cmd_len = cmd_len;
+ return (true); /* success */
+
+ /* All other commands are not legal in RBC */
+ default:
+ DPRINTF(sc, UDMASS_SCSI, "Unsupported RBC "
+ "command 0x%02x\n", cmd_ptr[0]);
+ return (false); /* failure */
+ }
+}
+
+static bool
+umass_ufi_transform(struct umass_softc *sc, uint8_t *cmd_ptr,
+ uint8_t cmd_len)
+{
+ /* An UFI command is always 12 bytes in length */
+ sc->sc_transfer.cmd_len = UFI_COMMAND_LENGTH;
+
+ switch (cmd_ptr[0]) {
+ /*
+ * Commands of which the format has been verified. They
+ * should work. Copy the command into the (zeroed out)
+ * destination buffer.
+ */
+
+ case REZERO_UNIT:
+ case REQUEST_SENSE:
+ case FORMAT_UNIT:
+ case INQUIRY:
+ case TEST_UNIT_READY:
+ case START_STOP_UNIT:
+ case SEND_DIAGNOSTIC:
+ case PREVENT_ALLOW:
+ case READ_CAPACITY:
+ case READ_10:
+ case WRITE_10:
+ case POSITION_TO_ELEMENT: /* SEEK_10 */
+ case WRITE_AND_VERIFY:
+ case VERIFY:
+ case MODE_SELECT_10:
+ case MODE_SENSE_10:
+ case READ_12:
+ case WRITE_12:
+ case READ_FORMAT_CAPACITIES:
+ break;
+
+ /*
+ * SYNCHRONIZE_CACHE isn't supported by UFI, nor should it be
+ * required for UFI devices. Just fail it, the upper layers
+ * know what to do.
+ */
+ case SYNCHRONIZE_CACHE:
+ return (false);
+ default:
+ DPRINTF(sc, UDMASS_SCSI, "Unsupported UFI "
+ "command 0x%02x\n", cmd_ptr[0]);
+ return (false); /* failure */
+ }
+
+ return (true); /* success */
+}
+
+/*
+ * 8070i (ATAPI) specific functions
+ */
+static bool
+umass_atapi_transform(struct umass_softc *sc, uint8_t *cmd_ptr,
+ uint8_t cmd_len)
+{
+ /* An ATAPI command is always 12 bytes in length. */
+ sc->sc_transfer.cmd_len = ATAPI_COMMAND_LENGTH;
+
+ switch (cmd_ptr[0]) {
+ /*
+ * Commands of which the format has been verified. They
+ * should work. Copy the command into the destination
+ * buffer.
+ */
+ case REZERO_UNIT:
+ case REQUEST_SENSE:
+ case INQUIRY:
+ case START_STOP_UNIT:
+ case TEST_UNIT_READY:
+ case SEND_DIAGNOSTIC:
+ case PREVENT_ALLOW:
+ case READ_CAPACITY:
+ case READ_10:
+ case WRITE_10:
+ case POSITION_TO_ELEMENT: /* SEEK_10 */
+ case SYNCHRONIZE_CACHE:
+ case MODE_SELECT_10:
+ case MODE_SENSE_10:
+ case READ_BUFFER:
+ case 0x42: /* READ_SUBCHANNEL */
+ case 0x43: /* READ_TOC */
+ case 0x44: /* READ_HEADER */
+ case 0x47: /* PLAY_MSF (Play Minute/Second/Frame) */
+ case 0x48: /* PLAY_TRACK */
+ case 0x49: /* PLAY_TRACK_REL */
+ case 0x4b: /* PAUSE */
+ case 0x51: /* READ_DISK_INFO */
+ case 0x52: /* READ_TRACK_INFO */
+ case 0x54: /* SEND_OPC */
+ case 0x59: /* READ_MASTER_CUE */
+ case 0x5b: /* CLOSE_TR_SESSION */
+ case 0x5c: /* READ_BUFFER_CAP */
+ case 0x5d: /* SEND_CUE_SHEET */
+ case 0xa1: /* BLANK */
+ case 0xa5: /* PLAY_12 */
+ case 0xa6: /* EXCHANGE_MEDIUM */
+ case 0xad: /* READ_DVD_STRUCTURE */
+ case 0xbb: /* SET_CD_SPEED */
+ case 0xe5: /* READ_TRACK_INFO_PHILIPS */
+ break;
+
+ case READ_12:
+ case WRITE_12:
+ default:
+ DPRINTF(sc, UDMASS_SCSI, "Unsupported ATAPI "
+ "command 0x%02x - trying anyway\n",
+ cmd_ptr[0]);
+ break;
+ }
+
+ return (true); /* success */
+}
+
+static bool
+umass_no_transform(struct umass_softc *sc, uint8_t *cmd,
+ uint8_t cmdlen)
+{
+ return (false); /* failure */
+}
+
+static bool
+umass_std_transform(struct umass_softc *sc, union ccb *ccb,
+ uint8_t *cmd, uint8_t cmd_len)
+{
+ if (cmd_len == 0 || cmd_len > sizeof(sc->sc_transfer.cmd_data)) {
+ DPRINTF(sc, UDMASS_SCSI, "Invalid command length: %d bytes\n",
+ cmd_len);
+ return (false); /* failure */
+ }
+
+ /*
+ * Copy the CDB to the cmd_data buffer and then apply the common quirks
+ * to the code. We then pass the transformed command down to allow
+ * further transforms.
+ */
+ memset(sc->sc_transfer.cmd_data, 0, sizeof(sc->sc_transfer.cmd_data));
+ memcpy(sc->sc_transfer.cmd_data, cmd, cmd_len);
+ switch (cmd[0]) {
+ case TEST_UNIT_READY:
+ /*
+ * Some drive choke on TEST UNIT READY. Convert it to START STOP
+ * UNIT to get similar status.
+ */
+ if ((sc->sc_quirks & NO_TEST_UNIT_READY) != 0) {
+ DPRINTF(sc, UDMASS_SCSI,
+ "Converted TEST_UNIT_READY to START_UNIT\n");
+ memset(sc->sc_transfer.cmd_data, 0, cmd_len);
+ sc->sc_transfer.cmd_data[0] = START_STOP_UNIT;
+ sc->sc_transfer.cmd_data[4] = SSS_START;
+ }
+ break;
+
+ case INQUIRY:
+ /*
+ * some drives wedge when asked for full inquiry
+ * information.
+ */
+ if ((sc->sc_quirks & FORCE_SHORT_INQUIRY) != 0)
+ sc->sc_transfer.cmd_data[4] = SHORT_INQUIRY_LENGTH;
+ break;
+ }
+ if (sc->sc_transform(sc, cmd, cmd_len))
+ return (true); /* Execute command */
+ if (ccb)
+ umass_cam_illegal_request(ccb);
+ return (false); /* Already failed -- don't submit */
+}
+
+#ifdef USB_DEBUG
+static void
+umass_bbb_dump_cbw(struct umass_softc *sc, umass_bbb_cbw_t *cbw)
+{
+ uint8_t *c = cbw->CBWCDB;
+
+ uint32_t dlen = UGETDW(cbw->dCBWDataTransferLength);
+ uint32_t tag = UGETDW(cbw->dCBWTag);
+
+ uint8_t clen = cbw->bCDBLength;
+ uint8_t flags = cbw->bCBWFlags;
+ uint8_t lun = cbw->bCBWLUN;
+
+ DPRINTF(sc, UDMASS_BBB, "CBW %d: cmd = %db "
+ "(0x%02x%02x%02x%02x%02x%02x%s), "
+ "data = %db, lun = %d, dir = %s\n",
+ tag, clen,
+ c[0], c[1], c[2], c[3], c[4], c[5], (clen > 6 ? "..." : ""),
+ dlen, lun, (flags == CBWFLAGS_IN ? "in" :
+ (flags == CBWFLAGS_OUT ? "out" : "<invalid>")));
+}
+
+static void
+umass_bbb_dump_csw(struct umass_softc *sc, umass_bbb_csw_t *csw)
+{
+ uint32_t sig = UGETDW(csw->dCSWSignature);
+ uint32_t tag = UGETDW(csw->dCSWTag);
+ uint32_t res = UGETDW(csw->dCSWDataResidue);
+ uint8_t status = csw->bCSWStatus;
+
+ DPRINTF(sc, UDMASS_BBB, "CSW %d: sig = 0x%08x (%s), tag = 0x%08x, "
+ "res = %d, status = 0x%02x (%s)\n",
+ tag, sig, (sig == CSWSIGNATURE ? "valid" : "invalid"),
+ tag, res,
+ status, (status == CSWSTATUS_GOOD ? "good" :
+ (status == CSWSTATUS_FAILED ? "failed" :
+ (status == CSWSTATUS_PHASE ? "phase" : "<invalid>"))));
+}
+
+static void
+umass_cbi_dump_cmd(struct umass_softc *sc, void *cmd, uint8_t cmdlen)
+{
+ uint8_t *c = cmd;
+ uint8_t dir = sc->sc_transfer.dir;
+
+ DPRINTF(sc, UDMASS_BBB, "cmd = %db "
+ "(0x%02x%02x%02x%02x%02x%02x%s), "
+ "data = %db, dir = %s\n",
+ cmdlen,
+ c[0], c[1], c[2], c[3], c[4], c[5], (cmdlen > 6 ? "..." : ""),
+ sc->sc_transfer.data_len,
+ (dir == DIR_IN ? "in" :
+ (dir == DIR_OUT ? "out" :
+ (dir == DIR_NONE ? "no data phase" : "<invalid>"))));
+}
+
+static void
+umass_dump_buffer(struct umass_softc *sc, uint8_t *buffer, uint32_t buflen,
+ uint32_t printlen)
+{
+ uint32_t i, j;
+ char s1[40];
+ char s2[40];
+ char s3[5];
+
+ s1[0] = '\0';
+ s3[0] = '\0';
+
+ sprintf(s2, " buffer=%p, buflen=%d", buffer, buflen);
+ for (i = 0; (i < buflen) && (i < printlen); i++) {
+ j = i % 16;
+ if (j == 0 && i != 0) {
+ DPRINTF(sc, UDMASS_GEN, "0x %s%s\n",
+ s1, s2);
+ s2[0] = '\0';
+ }
+ sprintf(&s1[j * 2], "%02x", buffer[i] & 0xff);
+ }
+ if (buflen > printlen)
+ sprintf(s3, " ...");
+ DPRINTF(sc, UDMASS_GEN, "0x %s%s%s\n",
+ s1, s2, s3);
+}
+
+#endif
diff --git a/sys/dev/usb/storage/urio.c b/sys/dev/usb/storage/urio.c
new file mode 100644
index 000000000000..c13aab44e435
--- /dev/null
+++ b/sys/dev/usb/storage/urio.c
@@ -0,0 +1,493 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ */
+
+/*
+ * 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/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include "usbdevs.h"
+
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_generic.h>
+
+#define USB_DEBUG_VAR urio_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/storage/rio500_usb.h>
+
+#ifdef USB_DEBUG
+static int urio_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, urio, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB urio");
+SYSCTL_INT(_hw_usb_urio, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &urio_debug, 0, "urio debug level");
+#endif
+
+#define URIO_T_WR 0
+#define URIO_T_RD 1
+#define URIO_T_WR_CS 2
+#define URIO_T_RD_CS 3
+#define URIO_T_MAX 4
+
+#define URIO_BSIZE (1<<12) /* bytes */
+#define URIO_IFQ_MAXLEN 2 /* units */
+
+struct urio_softc {
+ struct usb_fifo_sc sc_fifo;
+ struct mtx sc_mtx;
+
+ struct usb_device *sc_udev;
+ struct usb_xfer *sc_xfer[URIO_T_MAX];
+
+ uint8_t sc_flags;
+#define URIO_FLAG_READ_STALL 0x01 /* read transfer stalled */
+#define URIO_FLAG_WRITE_STALL 0x02 /* write transfer stalled */
+
+ uint8_t sc_name[16];
+};
+
+/* prototypes */
+
+static device_probe_t urio_probe;
+static device_attach_t urio_attach;
+static device_detach_t urio_detach;
+
+static usb_callback_t urio_write_callback;
+static usb_callback_t urio_write_clear_stall_callback;
+static usb_callback_t urio_read_callback;
+static usb_callback_t urio_read_clear_stall_callback;
+
+static usb_fifo_close_t urio_close;
+static usb_fifo_cmd_t urio_start_read;
+static usb_fifo_cmd_t urio_start_write;
+static usb_fifo_cmd_t urio_stop_read;
+static usb_fifo_cmd_t urio_stop_write;
+static usb_fifo_ioctl_t urio_ioctl;
+static usb_fifo_open_t urio_open;
+
+static struct usb_fifo_methods urio_fifo_methods = {
+ .f_close = &urio_close,
+ .f_ioctl = &urio_ioctl,
+ .f_open = &urio_open,
+ .f_start_read = &urio_start_read,
+ .f_start_write = &urio_start_write,
+ .f_stop_read = &urio_stop_read,
+ .f_stop_write = &urio_stop_write,
+ .basename[0] = "urio",
+};
+
+static const struct usb_config urio_config[URIO_T_MAX] = {
+ [URIO_T_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = URIO_BSIZE,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,.proxy_buffer = 1,},
+ .callback = &urio_write_callback,
+ },
+
+ [URIO_T_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = URIO_BSIZE,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1,},
+ .callback = &urio_read_callback,
+ },
+
+ [URIO_T_WR_CS] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &urio_write_clear_stall_callback,
+ .timeout = 1000, /* 1 second */
+ .interval = 50, /* 50ms */
+ },
+
+ [URIO_T_RD_CS] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &urio_read_clear_stall_callback,
+ .timeout = 1000, /* 1 second */
+ .interval = 50, /* 50ms */
+ },
+};
+
+static device_method_t urio_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, urio_probe),
+ DEVMETHOD(device_attach, urio_attach),
+ DEVMETHOD(device_detach, urio_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t urio_driver = {
+ .name = "urio",
+ .methods = urio_methods,
+ .size = sizeof(struct urio_softc),
+};
+
+static const STRUCT_USB_HOST_ID urio_devs[] = {
+ {USB_VPI(USB_VENDOR_DIAMOND, USB_PRODUCT_DIAMOND_RIO500USB, 0)},
+ {USB_VPI(USB_VENDOR_DIAMOND2, USB_PRODUCT_DIAMOND2_RIO600USB, 0)},
+ {USB_VPI(USB_VENDOR_DIAMOND2, USB_PRODUCT_DIAMOND2_RIO800USB, 0)},
+};
+
+DRIVER_MODULE(urio, uhub, urio_driver, NULL, NULL);
+MODULE_DEPEND(urio, usb, 1, 1, 1);
+MODULE_VERSION(urio, 1);
+USB_PNP_HOST_INFO(urio_devs);
+
+static int
+urio_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != 0)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != 0)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(urio_devs, sizeof(urio_devs), uaa));
+}
+
+static int
+urio_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct urio_softc *sc = device_get_softc(dev);
+ int error;
+
+ device_set_usb_desc(dev);
+
+ sc->sc_udev = uaa->device;
+
+ mtx_init(&sc->sc_mtx, "urio lock", NULL, MTX_DEF | MTX_RECURSE);
+
+ snprintf(sc->sc_name, sizeof(sc->sc_name),
+ "%s", device_get_nameunit(dev));
+
+ error = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer,
+ urio_config, URIO_T_MAX, sc, &sc->sc_mtx);
+
+ if (error) {
+ DPRINTF("error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+
+ error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx,
+ &urio_fifo_methods, &sc->sc_fifo,
+ device_get_unit(dev), -1, uaa->info.bIfaceIndex,
+ UID_ROOT, GID_OPERATOR, 0644);
+ if (error) {
+ goto detach;
+ }
+ return (0); /* success */
+
+detach:
+ urio_detach(dev);
+ return (ENOMEM); /* failure */
+}
+
+static void
+urio_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct urio_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_fifo *f = sc->sc_fifo.fp[USB_FIFO_TX];
+ struct usb_page_cache *pc;
+ uint32_t actlen;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+ if (sc->sc_flags & URIO_FLAG_WRITE_STALL) {
+ usbd_transfer_start(sc->sc_xfer[URIO_T_WR_CS]);
+ return;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ if (usb_fifo_get_data(f, pc, 0,
+ usbd_xfer_max_len(xfer), &actlen, 0)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ sc->sc_flags |= URIO_FLAG_WRITE_STALL;
+ usbd_transfer_start(sc->sc_xfer[URIO_T_WR_CS]);
+ }
+ return;
+ }
+}
+
+static void
+urio_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct urio_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_xfer *xfer_other = sc->sc_xfer[URIO_T_WR];
+
+ if (usbd_clear_stall_callback(xfer, xfer_other)) {
+ DPRINTF("stall cleared\n");
+ sc->sc_flags &= ~URIO_FLAG_WRITE_STALL;
+ usbd_transfer_start(xfer_other);
+ }
+}
+
+static void
+urio_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct urio_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_fifo *f = sc->sc_fifo.fp[USB_FIFO_RX];
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usb_fifo_put_data(f, pc, 0, actlen, 1);
+
+ case USB_ST_SETUP:
+ if (sc->sc_flags & URIO_FLAG_READ_STALL) {
+ usbd_transfer_start(sc->sc_xfer[URIO_T_RD_CS]);
+ return;
+ }
+ if (usb_fifo_put_bytes_max(f) != 0) {
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ }
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ sc->sc_flags |= URIO_FLAG_READ_STALL;
+ usbd_transfer_start(sc->sc_xfer[URIO_T_RD_CS]);
+ }
+ return;
+ }
+}
+
+static void
+urio_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct urio_softc *sc = usbd_xfer_softc(xfer);
+ struct usb_xfer *xfer_other = sc->sc_xfer[URIO_T_RD];
+
+ if (usbd_clear_stall_callback(xfer, xfer_other)) {
+ DPRINTF("stall cleared\n");
+ sc->sc_flags &= ~URIO_FLAG_READ_STALL;
+ usbd_transfer_start(xfer_other);
+ }
+}
+
+static void
+urio_start_read(struct usb_fifo *fifo)
+{
+ struct urio_softc *sc = usb_fifo_softc(fifo);
+
+ usbd_transfer_start(sc->sc_xfer[URIO_T_RD]);
+}
+
+static void
+urio_stop_read(struct usb_fifo *fifo)
+{
+ struct urio_softc *sc = usb_fifo_softc(fifo);
+
+ usbd_transfer_stop(sc->sc_xfer[URIO_T_RD_CS]);
+ usbd_transfer_stop(sc->sc_xfer[URIO_T_RD]);
+}
+
+static void
+urio_start_write(struct usb_fifo *fifo)
+{
+ struct urio_softc *sc = usb_fifo_softc(fifo);
+
+ usbd_transfer_start(sc->sc_xfer[URIO_T_WR]);
+}
+
+static void
+urio_stop_write(struct usb_fifo *fifo)
+{
+ struct urio_softc *sc = usb_fifo_softc(fifo);
+
+ usbd_transfer_stop(sc->sc_xfer[URIO_T_WR_CS]);
+ usbd_transfer_stop(sc->sc_xfer[URIO_T_WR]);
+}
+
+static int
+urio_open(struct usb_fifo *fifo, int fflags)
+{
+ struct urio_softc *sc = usb_fifo_softc(fifo);
+
+ if (fflags & FREAD) {
+ /* clear stall first */
+ mtx_lock(&sc->sc_mtx);
+ sc->sc_flags |= URIO_FLAG_READ_STALL;
+ mtx_unlock(&sc->sc_mtx);
+
+ if (usb_fifo_alloc_buffer(fifo,
+ usbd_xfer_max_len(sc->sc_xfer[URIO_T_RD]),
+ URIO_IFQ_MAXLEN)) {
+ return (ENOMEM);
+ }
+ }
+ if (fflags & FWRITE) {
+ /* clear stall first */
+ sc->sc_flags |= URIO_FLAG_WRITE_STALL;
+
+ if (usb_fifo_alloc_buffer(fifo,
+ usbd_xfer_max_len(sc->sc_xfer[URIO_T_WR]),
+ URIO_IFQ_MAXLEN)) {
+ return (ENOMEM);
+ }
+ }
+ return (0); /* success */
+}
+
+static void
+urio_close(struct usb_fifo *fifo, int fflags)
+{
+ if (fflags & (FREAD | FWRITE)) {
+ usb_fifo_free_buffer(fifo);
+ }
+}
+
+static int
+urio_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr,
+ int fflags)
+{
+ struct usb_ctl_request ur;
+ struct RioCommand *rio_cmd;
+ int error;
+
+ switch (cmd) {
+ case RIO_RECV_COMMAND:
+ if (!(fflags & FWRITE)) {
+ error = EPERM;
+ goto done;
+ }
+ memset(&ur, 0, sizeof(ur));
+ rio_cmd = addr;
+ ur.ucr_request.bmRequestType =
+ rio_cmd->requesttype | UT_READ_VENDOR_DEVICE;
+ break;
+
+ case RIO_SEND_COMMAND:
+ if (!(fflags & FWRITE)) {
+ error = EPERM;
+ goto done;
+ }
+ memset(&ur, 0, sizeof(ur));
+ rio_cmd = addr;
+ ur.ucr_request.bmRequestType =
+ rio_cmd->requesttype | UT_WRITE_VENDOR_DEVICE;
+ break;
+
+ default:
+ error = EINVAL;
+ goto done;
+ }
+
+ DPRINTFN(2, "Sending command\n");
+
+ /* Send rio control message */
+ ur.ucr_request.bRequest = rio_cmd->request;
+ USETW(ur.ucr_request.wValue, rio_cmd->value);
+ USETW(ur.ucr_request.wIndex, rio_cmd->index);
+ USETW(ur.ucr_request.wLength, rio_cmd->length);
+ ur.ucr_data = rio_cmd->buffer;
+
+ /* reuse generic USB code */
+ error = ugen_do_request(fifo, &ur);
+
+done:
+ return (error);
+}
+
+static int
+urio_detach(device_t dev)
+{
+ struct urio_softc *sc = device_get_softc(dev);
+
+ DPRINTF("\n");
+
+ usb_fifo_detach(&sc->sc_fifo);
+
+ usbd_transfer_unsetup(sc->sc_xfer, URIO_T_MAX);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
diff --git a/sys/dev/usb/storage/ustorage_fs.c b/sys/dev/usb/storage/ustorage_fs.c
new file mode 100644
index 000000000000..e8883a183587
--- /dev/null
+++ b/sys/dev/usb/storage/ustorage_fs.c
@@ -0,0 +1,1942 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (C) 2003-2005 Alan Stern
+ * Copyright (C) 2008 Hans Petter Selasky
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 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 names of the above-listed copyright holders may not be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
+ */
+
+/*
+ * NOTE: Much of the SCSI statemachine handling code derives from the
+ * Linux USB gadget stack.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include "usbdevs.h"
+#include "usb_if.h"
+
+#define USB_DEBUG_VAR ustorage_fs_debug
+#include <dev/usb/usb_debug.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#ifdef USB_DEBUG
+static int ustorage_fs_debug = 0;
+
+SYSCTL_NODE(_hw_usb, OID_AUTO, ustorage_fs, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB ustorage_fs");
+SYSCTL_INT(_hw_usb_ustorage_fs, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &ustorage_fs_debug, 0, "ustorage_fs debug level");
+#endif
+
+/* Define some limits */
+
+#ifndef USTORAGE_FS_BULK_SIZE
+#define USTORAGE_FS_BULK_SIZE (1U << 17) /* bytes */
+#endif
+
+#ifndef USTORAGE_FS_MAX_LUN
+#define USTORAGE_FS_MAX_LUN 8 /* units */
+#endif
+
+#ifndef USTORAGE_QDATA_MAX
+#define USTORAGE_QDATA_MAX 40 /* bytes */
+#endif
+
+/*
+ * The SCSI ID string must be exactly 28 characters long
+ * exluding the terminating zero.
+ */
+#ifndef USTORAGE_FS_ID_STRING
+#define USTORAGE_FS_ID_STRING \
+ "FreeBSD " /* 8 */ \
+ "File-Stor Gadget" /* 16 */ \
+ "0101" /* 4 */
+#endif
+
+/*
+ * The following macro defines the number of
+ * sectors to be allocated for the RAM disk:
+ */
+#ifndef USTORAGE_FS_RAM_SECT
+#define USTORAGE_FS_RAM_SECT (1UL << 13)
+#endif
+
+static uint8_t *ustorage_fs_ramdisk;
+
+/* USB transfer definitions */
+
+#define USTORAGE_FS_T_BBB_COMMAND 0
+#define USTORAGE_FS_T_BBB_DATA_DUMP 1
+#define USTORAGE_FS_T_BBB_DATA_READ 2
+#define USTORAGE_FS_T_BBB_DATA_WRITE 3
+#define USTORAGE_FS_T_BBB_STATUS 4
+#define USTORAGE_FS_T_BBB_MAX 5
+
+/* USB data stage direction */
+
+#define DIR_NONE 0
+#define DIR_READ 1
+#define DIR_WRITE 2
+
+/* USB interface specific control request */
+
+#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 ustorage_fs_bbb_cbw_t;
+
+#define USTORAGE_FS_BBB_CBW_SIZE 31
+
+/* Command Status Wrapper */
+typedef struct {
+ uDWord dCSWSignature;
+#define CSWSIGNATURE 0x53425355
+ uDWord dCSWTag;
+ uDWord dCSWDataResidue;
+ uByte bCSWStatus;
+#define CSWSTATUS_GOOD 0x0
+#define CSWSTATUS_FAILED 0x1
+#define CSWSTATUS_PHASE 0x2
+} __packed ustorage_fs_bbb_csw_t;
+
+#define USTORAGE_FS_BBB_CSW_SIZE 13
+
+struct ustorage_fs_lun {
+ uint8_t *memory_image;
+
+ uint32_t num_sectors;
+ uint32_t sense_data;
+ uint32_t sense_data_info;
+ uint32_t unit_attention_data;
+
+ uint8_t read_only:1;
+ uint8_t prevent_medium_removal:1;
+ uint8_t info_valid:1;
+ uint8_t removable:1;
+};
+
+struct ustorage_fs_softc {
+ ustorage_fs_bbb_cbw_t *sc_cbw; /* Command Wrapper Block */
+ ustorage_fs_bbb_csw_t *sc_csw; /* Command Status Block */
+ void *sc_dma_ptr; /* Main data buffer */
+
+ struct mtx sc_mtx;
+
+ struct ustorage_fs_lun sc_lun[USTORAGE_FS_MAX_LUN];
+
+ struct {
+ uint8_t *data_ptr;
+ struct ustorage_fs_lun *currlun;
+
+ uint32_t data_rem; /* bytes, as reported by the command
+ * block wrapper */
+ uint32_t offset; /* bytes */
+
+ uint8_t cbw_dir;
+ uint8_t cmd_dir;
+ uint8_t lun;
+ uint8_t cmd_len;
+ uint8_t data_short:1;
+ uint8_t data_error:1;
+ } sc_transfer;
+
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+ struct usb_xfer *sc_xfer[USTORAGE_FS_T_BBB_MAX];
+
+ uint8_t sc_iface_no; /* interface number */
+ uint8_t sc_last_lun;
+ uint8_t sc_last_xfer_index;
+ uint8_t sc_qdata[USTORAGE_QDATA_MAX];
+};
+
+/* prototypes */
+
+static device_probe_t ustorage_fs_probe;
+static device_attach_t ustorage_fs_attach;
+static device_detach_t ustorage_fs_detach;
+static device_suspend_t ustorage_fs_suspend;
+static device_resume_t ustorage_fs_resume;
+static usb_handle_request_t ustorage_fs_handle_request;
+
+static usb_callback_t ustorage_fs_t_bbb_command_callback;
+static usb_callback_t ustorage_fs_t_bbb_data_dump_callback;
+static usb_callback_t ustorage_fs_t_bbb_data_read_callback;
+static usb_callback_t ustorage_fs_t_bbb_data_write_callback;
+static usb_callback_t ustorage_fs_t_bbb_status_callback;
+
+static void ustorage_fs_transfer_start(struct ustorage_fs_softc *sc, uint8_t xfer_index);
+static void ustorage_fs_transfer_stop(struct ustorage_fs_softc *sc);
+
+static uint8_t ustorage_fs_verify(struct ustorage_fs_softc *sc);
+static uint8_t ustorage_fs_inquiry(struct ustorage_fs_softc *sc);
+static uint8_t ustorage_fs_request_sense(struct ustorage_fs_softc *sc);
+static uint8_t ustorage_fs_read_capacity(struct ustorage_fs_softc *sc);
+static uint8_t ustorage_fs_mode_sense(struct ustorage_fs_softc *sc);
+static uint8_t ustorage_fs_start_stop(struct ustorage_fs_softc *sc);
+static uint8_t ustorage_fs_prevent_allow(struct ustorage_fs_softc *sc);
+static uint8_t ustorage_fs_read_format_capacities(struct ustorage_fs_softc *sc);
+static uint8_t ustorage_fs_mode_select(struct ustorage_fs_softc *sc);
+static uint8_t ustorage_fs_min_len(struct ustorage_fs_softc *sc, uint32_t len, uint32_t mask);
+static uint8_t ustorage_fs_read(struct ustorage_fs_softc *sc);
+static uint8_t ustorage_fs_write(struct ustorage_fs_softc *sc);
+static uint8_t ustorage_fs_check_cmd(struct ustorage_fs_softc *sc, uint8_t cmd_size, uint16_t mask, uint8_t needs_medium);
+static uint8_t ustorage_fs_do_cmd(struct ustorage_fs_softc *sc);
+
+static device_method_t ustorage_fs_methods[] = {
+ /* USB interface */
+ DEVMETHOD(usb_handle_request, ustorage_fs_handle_request),
+
+ /* Device interface */
+ DEVMETHOD(device_probe, ustorage_fs_probe),
+ DEVMETHOD(device_attach, ustorage_fs_attach),
+ DEVMETHOD(device_detach, ustorage_fs_detach),
+ DEVMETHOD(device_suspend, ustorage_fs_suspend),
+ DEVMETHOD(device_resume, ustorage_fs_resume),
+
+ DEVMETHOD_END
+};
+
+static driver_t ustorage_fs_driver = {
+ .name = "ustorage_fs",
+ .methods = ustorage_fs_methods,
+ .size = sizeof(struct ustorage_fs_softc),
+};
+
+DRIVER_MODULE(ustorage_fs, uhub, ustorage_fs_driver, NULL, NULL);
+MODULE_VERSION(ustorage_fs, 0);
+MODULE_DEPEND(ustorage_fs, usb, 1, 1, 1);
+
+static struct usb_config ustorage_fs_bbb_config[USTORAGE_FS_T_BBB_MAX] = {
+ [USTORAGE_FS_T_BBB_COMMAND] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = sizeof(ustorage_fs_bbb_cbw_t),
+ .callback = &ustorage_fs_t_bbb_command_callback,
+ .usb_mode = USB_MODE_DEVICE,
+ },
+
+ [USTORAGE_FS_T_BBB_DATA_DUMP] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = 0, /* use wMaxPacketSize */
+ .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,},
+ .callback = &ustorage_fs_t_bbb_data_dump_callback,
+ .usb_mode = USB_MODE_DEVICE,
+ },
+
+ [USTORAGE_FS_T_BBB_DATA_READ] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = USTORAGE_FS_BULK_SIZE,
+ .flags = {.proxy_buffer = 1,.short_xfer_ok = 1},
+ .callback = &ustorage_fs_t_bbb_data_read_callback,
+ .usb_mode = USB_MODE_DEVICE,
+ },
+
+ [USTORAGE_FS_T_BBB_DATA_WRITE] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = USTORAGE_FS_BULK_SIZE,
+ .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,.ext_buffer = 1},
+ .callback = &ustorage_fs_t_bbb_data_write_callback,
+ .usb_mode = USB_MODE_DEVICE,
+ },
+
+ [USTORAGE_FS_T_BBB_STATUS] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = sizeof(ustorage_fs_bbb_csw_t),
+ .flags = {.short_xfer_ok = 1},
+ .callback = &ustorage_fs_t_bbb_status_callback,
+ .usb_mode = USB_MODE_DEVICE,
+ },
+};
+
+/*
+ * USB device probe/attach/detach
+ */
+
+static int
+ustorage_fs_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct usb_interface_descriptor *id;
+
+ if (uaa->usb_mode != USB_MODE_DEVICE) {
+ return (ENXIO);
+ }
+ /* Check for a standards compliant device */
+ id = usbd_get_interface_descriptor(uaa->iface);
+ if ((id == NULL) ||
+ (id->bInterfaceClass != UICLASS_MASS) ||
+ (id->bInterfaceSubClass != UISUBCLASS_SCSI) ||
+ (id->bInterfaceProtocol != UIPROTO_MASS_BBB)) {
+ return (ENXIO);
+ }
+ return (BUS_PROBE_GENERIC);
+}
+
+static int
+ustorage_fs_attach(device_t dev)
+{
+ struct ustorage_fs_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct usb_interface_descriptor *id;
+ int err;
+ int unit;
+
+ /*
+ * NOTE: the softc struct is cleared in device_set_driver.
+ * We can safely call ustorage_fs_detach without specifically
+ * initializing the struct.
+ */
+
+ sc->sc_dev = dev;
+ sc->sc_udev = uaa->device;
+ unit = device_get_unit(dev);
+
+ /* enable power saving mode */
+ usbd_set_power_mode(uaa->device, USB_POWER_MODE_SAVE);
+
+ if (unit == 0) {
+ if (ustorage_fs_ramdisk == NULL) {
+ /*
+ * allocate a memory image for our ramdisk until
+ * further
+ */
+ ustorage_fs_ramdisk =
+ malloc(USTORAGE_FS_RAM_SECT << 9, M_USB,
+ M_ZERO | M_WAITOK);
+ }
+ sc->sc_lun[0].memory_image = ustorage_fs_ramdisk;
+ sc->sc_lun[0].num_sectors = USTORAGE_FS_RAM_SECT;
+ sc->sc_lun[0].removable = 1;
+ }
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, "USTORAGE_FS lock",
+ NULL, (MTX_DEF | MTX_RECURSE));
+
+ /* get interface index */
+
+ id = usbd_get_interface_descriptor(uaa->iface);
+ if (id == NULL) {
+ device_printf(dev, "failed to get "
+ "interface number\n");
+ goto detach;
+ }
+ sc->sc_iface_no = id->bInterfaceNumber;
+
+ err = usbd_transfer_setup(uaa->device,
+ &uaa->info.bIfaceIndex, sc->sc_xfer, ustorage_fs_bbb_config,
+ USTORAGE_FS_T_BBB_MAX, sc, &sc->sc_mtx);
+ if (err) {
+ device_printf(dev, "could not setup required "
+ "transfers, %s\n", usbd_errstr(err));
+ goto detach;
+ }
+
+ sc->sc_cbw = usbd_xfer_get_frame_buffer(sc->sc_xfer[
+ USTORAGE_FS_T_BBB_COMMAND], 0);
+ sc->sc_csw = usbd_xfer_get_frame_buffer(sc->sc_xfer[
+ USTORAGE_FS_T_BBB_STATUS], 0);
+ sc->sc_dma_ptr = usbd_xfer_get_frame_buffer(sc->sc_xfer[
+ USTORAGE_FS_T_BBB_DATA_READ], 0);
+
+ /* start Mass Storage State Machine */
+
+ mtx_lock(&sc->sc_mtx);
+ ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_COMMAND);
+ mtx_unlock(&sc->sc_mtx);
+
+ return (0); /* success */
+
+detach:
+ ustorage_fs_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static int
+ustorage_fs_detach(device_t dev)
+{
+ struct ustorage_fs_softc *sc = device_get_softc(dev);
+
+ /* teardown our statemachine */
+
+ usbd_transfer_unsetup(sc->sc_xfer, USTORAGE_FS_T_BBB_MAX);
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0); /* success */
+}
+
+static int
+ustorage_fs_suspend(device_t dev)
+{
+ device_printf(dev, "suspending\n");
+ return (0); /* success */
+}
+
+static int
+ustorage_fs_resume(device_t dev)
+{
+ device_printf(dev, "resuming\n");
+ return (0); /* success */
+}
+
+/*
+ * Generic functions to handle transfers
+ */
+
+static void
+ustorage_fs_transfer_start(struct ustorage_fs_softc *sc, uint8_t xfer_index)
+{
+ if (sc->sc_xfer[xfer_index]) {
+ sc->sc_last_xfer_index = xfer_index;
+ usbd_transfer_start(sc->sc_xfer[xfer_index]);
+ }
+}
+
+static void
+ustorage_fs_transfer_stop(struct ustorage_fs_softc *sc)
+{
+ usbd_transfer_stop(sc->sc_xfer[sc->sc_last_xfer_index]);
+ mtx_unlock(&sc->sc_mtx);
+ usbd_transfer_drain(sc->sc_xfer[sc->sc_last_xfer_index]);
+ mtx_lock(&sc->sc_mtx);
+}
+
+static int
+ustorage_fs_handle_request(device_t dev,
+ const void *preq, void **pptr, uint16_t *plen,
+ uint16_t offset, uint8_t *pstate)
+{
+ struct ustorage_fs_softc *sc = device_get_softc(dev);
+ const struct usb_device_request *req = preq;
+ uint8_t is_complete = *pstate;
+
+ if (!is_complete) {
+ if ((req->bmRequestType == UT_WRITE_CLASS_INTERFACE) &&
+ (req->bRequest == UR_BBB_RESET)) {
+ *plen = 0;
+ mtx_lock(&sc->sc_mtx);
+ ustorage_fs_transfer_stop(sc);
+ sc->sc_transfer.data_error = 1;
+ ustorage_fs_transfer_start(sc,
+ USTORAGE_FS_T_BBB_COMMAND);
+ mtx_unlock(&sc->sc_mtx);
+ return (0);
+ } else if ((req->bmRequestType == UT_READ_CLASS_INTERFACE) &&
+ (req->bRequest == UR_BBB_GET_MAX_LUN)) {
+ if (offset == 0) {
+ *plen = 1;
+ *pptr = &sc->sc_last_lun;
+ } else {
+ *plen = 0;
+ }
+ return (0);
+ }
+ }
+ return (ENXIO); /* use builtin handler */
+}
+
+static void
+ustorage_fs_t_bbb_command_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer);
+ uint32_t tag;
+ uint8_t err = 0;
+
+ DPRINTF("\n");
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ tag = UGETDW(sc->sc_cbw->dCBWSignature);
+
+ if (tag != CBWSIGNATURE) {
+ /* do nothing */
+ DPRINTF("invalid signature 0x%08x\n", tag);
+ break;
+ }
+ tag = UGETDW(sc->sc_cbw->dCBWTag);
+
+ /* echo back tag */
+ USETDW(sc->sc_csw->dCSWTag, tag);
+
+ /* reset status */
+ sc->sc_csw->bCSWStatus = 0;
+
+ /* reset data offset, data length and data remainder */
+ sc->sc_transfer.offset = 0;
+ sc->sc_transfer.data_rem =
+ UGETDW(sc->sc_cbw->dCBWDataTransferLength);
+
+ /* reset data flags */
+ sc->sc_transfer.data_short = 0;
+
+ /* extract LUN */
+ sc->sc_transfer.lun = sc->sc_cbw->bCBWLUN;
+
+ if (sc->sc_transfer.data_rem == 0) {
+ sc->sc_transfer.cbw_dir = DIR_NONE;
+ } else {
+ if (sc->sc_cbw->bCBWFlags & CBWFLAGS_IN) {
+ sc->sc_transfer.cbw_dir = DIR_WRITE;
+ } else {
+ sc->sc_transfer.cbw_dir = DIR_READ;
+ }
+ }
+
+ sc->sc_transfer.cmd_len = sc->sc_cbw->bCDBLength;
+ if ((sc->sc_transfer.cmd_len > sizeof(sc->sc_cbw->CBWCDB)) ||
+ (sc->sc_transfer.cmd_len == 0)) {
+ /* just halt - this is invalid */
+ DPRINTF("invalid command length %d bytes\n",
+ sc->sc_transfer.cmd_len);
+ break;
+ }
+
+ err = ustorage_fs_do_cmd(sc);
+ if (err) {
+ /* got an error */
+ DPRINTF("command failed\n");
+ break;
+ }
+ if ((sc->sc_transfer.data_rem > 0) &&
+ (sc->sc_transfer.cbw_dir != sc->sc_transfer.cmd_dir)) {
+ /* contradicting data transfer direction */
+ err = 1;
+ DPRINTF("data direction mismatch\n");
+ break;
+ }
+ switch (sc->sc_transfer.cbw_dir) {
+ case DIR_READ:
+ ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_DATA_READ);
+ break;
+ case DIR_WRITE:
+ ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_DATA_WRITE);
+ break;
+ default:
+ ustorage_fs_transfer_start(sc,
+ USTORAGE_FS_T_BBB_STATUS);
+ break;
+ }
+ break;
+
+ case USB_ST_SETUP:
+tr_setup:
+ if (sc->sc_transfer.data_error) {
+ sc->sc_transfer.data_error = 0;
+ usbd_xfer_set_stall(xfer);
+ DPRINTF("stall pipe\n");
+ }
+ usbd_xfer_set_frame_len(xfer, 0,
+ sizeof(ustorage_fs_bbb_cbw_t));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ DPRINTF("error\n");
+ if (error == USB_ERR_CANCELLED) {
+ break;
+ }
+ /* If the pipe is already stalled, don't do another stall */
+ if (!usbd_xfer_is_stalled(xfer))
+ sc->sc_transfer.data_error = 1;
+
+ /* try again */
+ goto tr_setup;
+ }
+ if (err) {
+ if (sc->sc_csw->bCSWStatus == 0) {
+ /* set some default error code */
+ sc->sc_csw->bCSWStatus = CSWSTATUS_FAILED;
+ }
+ if (sc->sc_transfer.cbw_dir == DIR_READ) {
+ /* dump all data */
+ ustorage_fs_transfer_start(sc,
+ USTORAGE_FS_T_BBB_DATA_DUMP);
+ return;
+ }
+ if (sc->sc_transfer.cbw_dir == DIR_WRITE) {
+ /* need to stall before status */
+ sc->sc_transfer.data_error = 1;
+ }
+ ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_STATUS);
+ }
+}
+
+static void
+ustorage_fs_t_bbb_data_dump_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer);
+ uint32_t max_bulk = usbd_xfer_max_len(xfer);
+ int actlen, sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ DPRINTF("\n");
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ sc->sc_transfer.data_rem -= actlen;
+ sc->sc_transfer.offset += actlen;
+
+ if (actlen != sumlen || sc->sc_transfer.data_rem == 0) {
+ /* short transfer or end of data */
+ ustorage_fs_transfer_start(sc,
+ USTORAGE_FS_T_BBB_STATUS);
+ break;
+ }
+ /* Fallthrough */
+
+ case USB_ST_SETUP:
+tr_setup:
+ if (max_bulk > sc->sc_transfer.data_rem) {
+ max_bulk = sc->sc_transfer.data_rem;
+ }
+ if (sc->sc_transfer.data_error) {
+ sc->sc_transfer.data_error = 0;
+ usbd_xfer_set_stall(xfer);
+ }
+ usbd_xfer_set_frame_len(xfer, 0, max_bulk);
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error == USB_ERR_CANCELLED) {
+ break;
+ }
+ /*
+ * If the pipe is already stalled, don't do another stall:
+ */
+ if (!usbd_xfer_is_stalled(xfer))
+ sc->sc_transfer.data_error = 1;
+
+ /* try again */
+ goto tr_setup;
+ }
+}
+
+static void
+ustorage_fs_t_bbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer);
+ uint32_t max_bulk = usbd_xfer_max_len(xfer);
+ int actlen, sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ DPRINTF("\n");
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ /* XXX copy data from DMA buffer */
+ memcpy(sc->sc_transfer.data_ptr, sc->sc_dma_ptr, actlen);
+
+ sc->sc_transfer.data_rem -= actlen;
+ sc->sc_transfer.data_ptr += actlen;
+ sc->sc_transfer.offset += actlen;
+
+ if (actlen != sumlen || sc->sc_transfer.data_rem == 0) {
+ /* short transfer or end of data */
+ ustorage_fs_transfer_start(sc,
+ USTORAGE_FS_T_BBB_STATUS);
+ break;
+ }
+ /* Fallthrough */
+
+ case USB_ST_SETUP:
+tr_setup:
+ if (max_bulk > sc->sc_transfer.data_rem) {
+ max_bulk = sc->sc_transfer.data_rem;
+ }
+ if (sc->sc_transfer.data_error) {
+ sc->sc_transfer.data_error = 0;
+ usbd_xfer_set_stall(xfer);
+ }
+
+ usbd_xfer_set_frame_data(xfer, 0, sc->sc_dma_ptr, max_bulk);
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error == USB_ERR_CANCELLED) {
+ break;
+ }
+ /* If the pipe is already stalled, don't do another stall */
+ if (!usbd_xfer_is_stalled(xfer))
+ sc->sc_transfer.data_error = 1;
+
+ /* try again */
+ goto tr_setup;
+ }
+}
+
+static void
+ustorage_fs_t_bbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer);
+ uint32_t max_bulk = usbd_xfer_max_len(xfer);
+ int actlen, sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ DPRINTF("\n");
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ sc->sc_transfer.data_rem -= actlen;
+ sc->sc_transfer.data_ptr += actlen;
+ sc->sc_transfer.offset += actlen;
+
+ if (actlen != sumlen || sc->sc_transfer.data_rem == 0) {
+ /* short transfer or end of data */
+ ustorage_fs_transfer_start(sc,
+ USTORAGE_FS_T_BBB_STATUS);
+ break;
+ }
+ case USB_ST_SETUP:
+tr_setup:
+ if (max_bulk >= sc->sc_transfer.data_rem) {
+ max_bulk = sc->sc_transfer.data_rem;
+ if (sc->sc_transfer.data_short)
+ usbd_xfer_set_flag(xfer, USB_FORCE_SHORT_XFER);
+ else
+ usbd_xfer_clr_flag(xfer, USB_FORCE_SHORT_XFER);
+ } else
+ usbd_xfer_clr_flag(xfer, USB_FORCE_SHORT_XFER);
+
+ if (sc->sc_transfer.data_error) {
+ sc->sc_transfer.data_error = 0;
+ usbd_xfer_set_stall(xfer);
+ }
+
+ /* XXX copy data to DMA buffer */
+ memcpy(sc->sc_dma_ptr, sc->sc_transfer.data_ptr, max_bulk);
+
+ usbd_xfer_set_frame_data(xfer, 0, sc->sc_dma_ptr, max_bulk);
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error == USB_ERR_CANCELLED) {
+ break;
+ }
+ /*
+ * If the pipe is already stalled, don't do another
+ * stall
+ */
+ if (!usbd_xfer_is_stalled(xfer))
+ sc->sc_transfer.data_error = 1;
+
+ /* try again */
+ goto tr_setup;
+ }
+}
+
+static void
+ustorage_fs_t_bbb_status_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ustorage_fs_softc *sc = usbd_xfer_softc(xfer);
+
+ DPRINTF("\n");
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ ustorage_fs_transfer_start(sc, USTORAGE_FS_T_BBB_COMMAND);
+ break;
+
+ case USB_ST_SETUP:
+tr_setup:
+ USETDW(sc->sc_csw->dCSWSignature, CSWSIGNATURE);
+ USETDW(sc->sc_csw->dCSWDataResidue, sc->sc_transfer.data_rem);
+
+ if (sc->sc_transfer.data_error) {
+ sc->sc_transfer.data_error = 0;
+ usbd_xfer_set_stall(xfer);
+ }
+ usbd_xfer_set_frame_len(xfer, 0,
+ sizeof(ustorage_fs_bbb_csw_t));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default:
+ if (error == USB_ERR_CANCELLED) {
+ break;
+ }
+ /* If the pipe is already stalled, don't do another stall */
+ if (!usbd_xfer_is_stalled(xfer))
+ sc->sc_transfer.data_error = 1;
+
+ /* try again */
+ goto tr_setup;
+ }
+}
+
+/* SCSI commands that we recognize */
+#define SC_FORMAT_UNIT 0x04
+#define SC_INQUIRY 0x12
+#define SC_MODE_SELECT_6 0x15
+#define SC_MODE_SELECT_10 0x55
+#define SC_MODE_SENSE_6 0x1a
+#define SC_MODE_SENSE_10 0x5a
+#define SC_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1e
+#define SC_READ_6 0x08
+#define SC_READ_10 0x28
+#define SC_READ_12 0xa8
+#define SC_READ_CAPACITY 0x25
+#define SC_READ_FORMAT_CAPACITIES 0x23
+#define SC_RELEASE 0x17
+#define SC_REQUEST_SENSE 0x03
+#define SC_RESERVE 0x16
+#define SC_SEND_DIAGNOSTIC 0x1d
+#define SC_START_STOP_UNIT 0x1b
+#define SC_SYNCHRONIZE_CACHE 0x35
+#define SC_TEST_UNIT_READY 0x00
+#define SC_VERIFY 0x2f
+#define SC_WRITE_6 0x0a
+#define SC_WRITE_10 0x2a
+#define SC_WRITE_12 0xaa
+
+/* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */
+#define SS_NO_SENSE 0
+#define SS_COMMUNICATION_FAILURE 0x040800
+#define SS_INVALID_COMMAND 0x052000
+#define SS_INVALID_FIELD_IN_CDB 0x052400
+#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x052100
+#define SS_LOGICAL_UNIT_NOT_SUPPORTED 0x052500
+#define SS_MEDIUM_NOT_PRESENT 0x023a00
+#define SS_MEDIUM_REMOVAL_PREVENTED 0x055302
+#define SS_NOT_READY_TO_READY_TRANSITION 0x062800
+#define SS_RESET_OCCURRED 0x062900
+#define SS_SAVING_PARAMETERS_NOT_SUPPORTED 0x053900
+#define SS_UNRECOVERED_READ_ERROR 0x031100
+#define SS_WRITE_ERROR 0x030c02
+#define SS_WRITE_PROTECTED 0x072700
+
+#define SK(x) ((uint8_t) ((x) >> 16)) /* Sense Key byte, etc. */
+#define ASC(x) ((uint8_t) ((x) >> 8))
+#define ASCQ(x) ((uint8_t) (x))
+
+/* Routines for unaligned data access */
+
+static uint16_t
+get_be16(uint8_t *buf)
+{
+ return ((uint16_t)buf[0] << 8) | ((uint16_t)buf[1]);
+}
+
+static uint32_t
+get_be32(uint8_t *buf)
+{
+ return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) |
+ ((uint32_t)buf[2] << 8) | ((uint32_t)buf[3]);
+}
+
+static void
+put_be16(uint8_t *buf, uint16_t val)
+{
+ buf[0] = val >> 8;
+ buf[1] = val;
+}
+
+static void
+put_be32(uint8_t *buf, uint32_t val)
+{
+ buf[0] = val >> 24;
+ buf[1] = val >> 16;
+ buf[2] = val >> 8;
+ buf[3] = val & 0xff;
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_verify
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_verify(struct ustorage_fs_softc *sc)
+{
+ struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun;
+ uint32_t lba;
+ uint32_t vlen;
+
+ /*
+ * Get the starting Logical Block Address
+ */
+ lba = get_be32(&sc->sc_cbw->CBWCDB[2]);
+
+ /*
+ * We allow DPO (Disable Page Out = don't save data in the cache)
+ * but we don't implement it.
+ */
+ if ((sc->sc_cbw->CBWCDB[1] & ~0x10) != 0) {
+ currlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return (1);
+ }
+ vlen = get_be16(&sc->sc_cbw->CBWCDB[7]);
+ if (vlen == 0) {
+ goto done;
+ }
+ /* No default reply */
+
+ /* Range check */
+ vlen += lba;
+
+ if ((vlen < lba) ||
+ (vlen > currlun->num_sectors) ||
+ (lba >= currlun->num_sectors)) {
+ currlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ return (1);
+ }
+ /* XXX TODO: verify that data is readable */
+done:
+ return (ustorage_fs_min_len(sc, 0, -1U));
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_inquiry
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_inquiry(struct ustorage_fs_softc *sc)
+{
+ uint8_t *buf = sc->sc_transfer.data_ptr;
+
+ struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun;
+
+ if (!sc->sc_transfer.currlun) {
+ /* Unsupported LUNs are okay */
+ memset(buf, 0, 36);
+ buf[0] = 0x7f;
+ /* Unsupported, no device - type */
+ return (ustorage_fs_min_len(sc, 36, -1U));
+ }
+ memset(buf, 0, 8);
+ /* Non - removable, direct - access device */
+ if (currlun->removable)
+ buf[1] = 0x80;
+ buf[2] = 2;
+ /* ANSI SCSI level 2 */
+ buf[3] = 2;
+ /* SCSI - 2 INQUIRY data format */
+ buf[4] = 31;
+ /* Additional length */
+ /* No special options */
+ /* Copy in ID string */
+ memcpy(buf + 8, USTORAGE_FS_ID_STRING, 28);
+
+#if (USTORAGE_QDATA_MAX < 36)
+#error "(USTORAGE_QDATA_MAX < 36)"
+#endif
+ return (ustorage_fs_min_len(sc, 36, -1U));
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_request_sense
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_request_sense(struct ustorage_fs_softc *sc)
+{
+ uint8_t *buf = sc->sc_transfer.data_ptr;
+ struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun;
+ uint32_t sd;
+ uint32_t sdinfo;
+ uint8_t valid;
+
+ /*
+ * From the SCSI-2 spec., section 7.9 (Unit attention condition):
+ *
+ * If a REQUEST SENSE command is received from an initiator
+ * with a pending unit attention condition (before the target
+ * generates the contingent allegiance condition), then the
+ * target shall either:
+ * a) report any pending sense data and preserve the unit
+ * attention condition on the logical unit, or,
+ * b) report the unit attention condition, may discard any
+ * pending sense data, and clear the unit attention
+ * condition on the logical unit for that initiator.
+ *
+ * FSG normally uses option a); enable this code to use option b).
+ */
+#if 0
+ if (currlun && currlun->unit_attention_data != SS_NO_SENSE) {
+ currlun->sense_data = currlun->unit_attention_data;
+ currlun->unit_attention_data = SS_NO_SENSE;
+ }
+#endif
+
+ if (!currlun) {
+ /* Unsupported LUNs are okay */
+ sd = SS_LOGICAL_UNIT_NOT_SUPPORTED;
+ sdinfo = 0;
+ valid = 0;
+ } else {
+ sd = currlun->sense_data;
+ sdinfo = currlun->sense_data_info;
+ valid = currlun->info_valid << 7;
+ currlun->sense_data = SS_NO_SENSE;
+ currlun->sense_data_info = 0;
+ currlun->info_valid = 0;
+ }
+
+ memset(buf, 0, 18);
+ buf[0] = valid | 0x70;
+ /* Valid, current error */
+ buf[2] = SK(sd);
+ put_be32(&buf[3], sdinfo);
+ /* Sense information */
+ buf[7] = 18 - 8;
+ /* Additional sense length */
+ buf[12] = ASC(sd);
+ buf[13] = ASCQ(sd);
+
+#if (USTORAGE_QDATA_MAX < 18)
+#error "(USTORAGE_QDATA_MAX < 18)"
+#endif
+ return (ustorage_fs_min_len(sc, 18, -1U));
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_read_capacity
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_read_capacity(struct ustorage_fs_softc *sc)
+{
+ uint8_t *buf = sc->sc_transfer.data_ptr;
+ struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun;
+ uint32_t lba = get_be32(&sc->sc_cbw->CBWCDB[2]);
+ uint8_t pmi = sc->sc_cbw->CBWCDB[8];
+
+ /* Check the PMI and LBA fields */
+ if ((pmi > 1) || ((pmi == 0) && (lba != 0))) {
+ currlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return (1);
+ }
+ /* Max logical block */
+ put_be32(&buf[0], currlun->num_sectors - 1);
+ /* Block length */
+ put_be32(&buf[4], 512);
+
+#if (USTORAGE_QDATA_MAX < 8)
+#error "(USTORAGE_QDATA_MAX < 8)"
+#endif
+ return (ustorage_fs_min_len(sc, 8, -1U));
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_mode_sense
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_mode_sense(struct ustorage_fs_softc *sc)
+{
+ uint8_t *buf = sc->sc_transfer.data_ptr;
+ struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun;
+ uint8_t *buf0;
+ uint16_t len;
+ uint16_t limit;
+ uint8_t mscmnd = sc->sc_cbw->CBWCDB[0];
+ uint8_t pc;
+ uint8_t page_code;
+ uint8_t changeable_values;
+ uint8_t all_pages;
+
+ buf0 = buf;
+
+ if ((sc->sc_cbw->CBWCDB[1] & ~0x08) != 0) {
+ /* Mask away DBD */
+ currlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return (1);
+ }
+ pc = sc->sc_cbw->CBWCDB[2] >> 6;
+ page_code = sc->sc_cbw->CBWCDB[2] & 0x3f;
+ if (pc == 3) {
+ currlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED;
+ return (1);
+ }
+ changeable_values = (pc == 1);
+ all_pages = (page_code == 0x3f);
+
+ /*
+ * Write the mode parameter header. Fixed values are: default
+ * medium type, no cache control (DPOFUA), and no block descriptors.
+ * The only variable value is the WriteProtect bit. We will fill in
+ * the mode data length later.
+ */
+ memset(buf, 0, 8);
+ if (mscmnd == SC_MODE_SENSE_6) {
+ buf[2] = (currlun->read_only ? 0x80 : 0x00);
+ /* WP, DPOFUA */
+ buf += 4;
+ limit = 255;
+ } else {
+ /* SC_MODE_SENSE_10 */
+ buf[3] = (currlun->read_only ? 0x80 : 0x00);
+ /* WP, DPOFUA */
+ buf += 8;
+ limit = 65535;
+ /* Should really be mod_data.buflen */
+ }
+
+ /* No block descriptors */
+
+ /*
+ * The mode pages, in numerical order.
+ */
+ if ((page_code == 0x08) || all_pages) {
+ buf[0] = 0x08;
+ /* Page code */
+ buf[1] = 10;
+ /* Page length */
+ memset(buf + 2, 0, 10);
+ /* None of the fields are changeable */
+
+ if (!changeable_values) {
+ buf[2] = 0x04;
+ /* Write cache enable, */
+ /* Read cache not disabled */
+ /* No cache retention priorities */
+ put_be16(&buf[4], 0xffff);
+ /* Don 't disable prefetch */
+ /* Minimum prefetch = 0 */
+ put_be16(&buf[8], 0xffff);
+ /* Maximum prefetch */
+ put_be16(&buf[10], 0xffff);
+ /* Maximum prefetch ceiling */
+ }
+ buf += 12;
+ }
+ /*
+ * Check that a valid page was requested and the mode data length
+ * isn't too long.
+ */
+ len = buf - buf0;
+ if (len > limit) {
+ currlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return (1);
+ }
+ /* Store the mode data length */
+ if (mscmnd == SC_MODE_SENSE_6)
+ buf0[0] = len - 1;
+ else
+ put_be16(buf0, len - 2);
+
+#if (USTORAGE_QDATA_MAX < 24)
+#error "(USTORAGE_QDATA_MAX < 24)"
+#endif
+ return (ustorage_fs_min_len(sc, len, -1U));
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_start_stop
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_start_stop(struct ustorage_fs_softc *sc)
+{
+ struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun;
+ uint8_t loej;
+ uint8_t start;
+ uint8_t immed;
+
+ if (!currlun->removable) {
+ currlun->sense_data = SS_INVALID_COMMAND;
+ return (1);
+ }
+ immed = sc->sc_cbw->CBWCDB[1] & 0x01;
+ loej = sc->sc_cbw->CBWCDB[4] & 0x02;
+ start = sc->sc_cbw->CBWCDB[4] & 0x01;
+
+ if (immed || loej || start) {
+ /* compile fix */
+ }
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_prevent_allow
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_prevent_allow(struct ustorage_fs_softc *sc)
+{
+ struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun;
+ uint8_t prevent;
+
+ if (!currlun->removable) {
+ currlun->sense_data = SS_INVALID_COMMAND;
+ return (1);
+ }
+ prevent = sc->sc_cbw->CBWCDB[4] & 0x01;
+ if ((sc->sc_cbw->CBWCDB[4] & ~0x01) != 0) {
+ /* Mask away Prevent */
+ currlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return (1);
+ }
+ if (currlun->prevent_medium_removal && !prevent) {
+ //fsync_sub(currlun);
+ }
+ currlun->prevent_medium_removal = prevent;
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_read_format_capacities
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_read_format_capacities(struct ustorage_fs_softc *sc)
+{
+ uint8_t *buf = sc->sc_transfer.data_ptr;
+ struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun;
+
+ buf[0] = buf[1] = buf[2] = 0;
+ buf[3] = 8;
+ /* Only the Current / Maximum Capacity Descriptor */
+ buf += 4;
+
+ /* Number of blocks */
+ put_be32(&buf[0], currlun->num_sectors);
+ /* Block length */
+ put_be32(&buf[4], 512);
+ /* Current capacity */
+ buf[4] = 0x02;
+
+#if (USTORAGE_QDATA_MAX < 12)
+#error "(USTORAGE_QDATA_MAX < 12)"
+#endif
+ return (ustorage_fs_min_len(sc, 12, -1U));
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_mode_select
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_mode_select(struct ustorage_fs_softc *sc)
+{
+ struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun;
+
+ /* We don't support MODE SELECT */
+ currlun->sense_data = SS_INVALID_COMMAND;
+ return (1);
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_synchronize_cache
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_synchronize_cache(struct ustorage_fs_softc *sc)
+{
+#if 0
+ struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun;
+ uint8_t rc;
+
+ /*
+ * We ignore the requested LBA and write out all dirty data buffers.
+ */
+ rc = 0;
+ if (rc) {
+ currlun->sense_data = SS_WRITE_ERROR;
+ }
+#endif
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_read - read data from disk
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_read(struct ustorage_fs_softc *sc)
+{
+ struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun;
+ uint64_t file_offset;
+ uint32_t lba;
+ uint32_t len;
+
+ /*
+ * Get the starting Logical Block Address and check that it's not
+ * too big
+ */
+ if (sc->sc_cbw->CBWCDB[0] == SC_READ_6) {
+ lba = (((uint32_t)sc->sc_cbw->CBWCDB[1]) << 16) |
+ get_be16(&sc->sc_cbw->CBWCDB[2]);
+ } else {
+ lba = get_be32(&sc->sc_cbw->CBWCDB[2]);
+
+ /*
+ * We allow DPO (Disable Page Out = don't save data in the
+ * cache) and FUA (Force Unit Access = don't read from the
+ * cache), but we don't implement them.
+ */
+ if ((sc->sc_cbw->CBWCDB[1] & ~0x18) != 0) {
+ currlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return (1);
+ }
+ }
+ len = sc->sc_transfer.data_rem >> 9;
+ len += lba;
+
+ if ((len < lba) ||
+ (len > currlun->num_sectors) ||
+ (lba >= currlun->num_sectors)) {
+ currlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ return (1);
+ }
+ file_offset = lba;
+ file_offset <<= 9;
+
+ sc->sc_transfer.data_ptr = currlun->memory_image + file_offset;
+
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_write - write data to disk
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_write(struct ustorage_fs_softc *sc)
+{
+ struct ustorage_fs_lun *currlun = sc->sc_transfer.currlun;
+ uint64_t file_offset;
+ uint32_t lba;
+ uint32_t len;
+
+ if (currlun->read_only) {
+ currlun->sense_data = SS_WRITE_PROTECTED;
+ return (1);
+ }
+ /* XXX clear SYNC */
+
+ /*
+ * Get the starting Logical Block Address and check that it's not
+ * too big.
+ */
+ if (sc->sc_cbw->CBWCDB[0] == SC_WRITE_6)
+ lba = (((uint32_t)sc->sc_cbw->CBWCDB[1]) << 16) |
+ get_be16(&sc->sc_cbw->CBWCDB[2]);
+ else {
+ lba = get_be32(&sc->sc_cbw->CBWCDB[2]);
+
+ /*
+ * We allow DPO (Disable Page Out = don't save data in the
+ * cache) and FUA (Force Unit Access = write directly to the
+ * medium). We don't implement DPO; we implement FUA by
+ * performing synchronous output.
+ */
+ if ((sc->sc_cbw->CBWCDB[1] & ~0x18) != 0) {
+ currlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return (1);
+ }
+ if (sc->sc_cbw->CBWCDB[1] & 0x08) {
+ /* FUA */
+ /* XXX set SYNC flag here */
+ }
+ }
+
+ len = sc->sc_transfer.data_rem >> 9;
+ len += lba;
+
+ if ((len < lba) ||
+ (len > currlun->num_sectors) ||
+ (lba >= currlun->num_sectors)) {
+ currlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ return (1);
+ }
+ file_offset = lba;
+ file_offset <<= 9;
+
+ sc->sc_transfer.data_ptr = currlun->memory_image + file_offset;
+
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_min_len
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_min_len(struct ustorage_fs_softc *sc, uint32_t len, uint32_t mask)
+{
+ if (len != sc->sc_transfer.data_rem) {
+ if (sc->sc_transfer.cbw_dir == DIR_READ) {
+ /*
+ * there must be something wrong about this SCSI
+ * command
+ */
+ sc->sc_csw->bCSWStatus = CSWSTATUS_PHASE;
+ return (1);
+ }
+ /* compute the minimum length */
+
+ if (sc->sc_transfer.data_rem > len) {
+ /* data ends prematurely */
+ sc->sc_transfer.data_rem = len;
+ sc->sc_transfer.data_short = 1;
+ }
+ /* check length alignment */
+
+ if (sc->sc_transfer.data_rem & ~mask) {
+ /* data ends prematurely */
+ sc->sc_transfer.data_rem &= mask;
+ sc->sc_transfer.data_short = 1;
+ }
+ }
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_check_cmd - check command routine
+ *
+ * Check whether the command is properly formed and whether its data
+ * size and direction agree with the values we already have.
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_check_cmd(struct ustorage_fs_softc *sc, uint8_t min_cmd_size,
+ uint16_t mask, uint8_t needs_medium)
+{
+ struct ustorage_fs_lun *currlun;
+ uint8_t lun = (sc->sc_cbw->CBWCDB[1] >> 5);
+ uint8_t i;
+
+ /* Verify the length of the command itself */
+ if (min_cmd_size > sc->sc_transfer.cmd_len) {
+ DPRINTF("%u > %u\n",
+ min_cmd_size, sc->sc_transfer.cmd_len);
+ sc->sc_csw->bCSWStatus = CSWSTATUS_PHASE;
+ return (1);
+ }
+ /* Mask away the LUN */
+ sc->sc_cbw->CBWCDB[1] &= 0x1f;
+
+ /* Check if LUN is correct */
+ if (lun != sc->sc_transfer.lun) {
+ }
+ /* Check the LUN */
+ if (sc->sc_transfer.lun <= sc->sc_last_lun) {
+ sc->sc_transfer.currlun = currlun =
+ sc->sc_lun + sc->sc_transfer.lun;
+ if (sc->sc_cbw->CBWCDB[0] != SC_REQUEST_SENSE) {
+ currlun->sense_data = SS_NO_SENSE;
+ currlun->sense_data_info = 0;
+ currlun->info_valid = 0;
+ }
+ /*
+ * If a unit attention condition exists, only INQUIRY
+ * and REQUEST SENSE commands are allowed. Anything
+ * else must fail!
+ */
+ if ((currlun->unit_attention_data != SS_NO_SENSE) &&
+ (sc->sc_cbw->CBWCDB[0] != SC_INQUIRY) &&
+ (sc->sc_cbw->CBWCDB[0] != SC_REQUEST_SENSE)) {
+ currlun->sense_data = currlun->unit_attention_data;
+ currlun->unit_attention_data = SS_NO_SENSE;
+ return (1);
+ }
+ } else {
+ sc->sc_transfer.currlun = currlun = NULL;
+
+ /*
+ * INQUIRY and REQUEST SENSE commands are explicitly allowed
+ * to use unsupported LUNs; all others may not.
+ */
+ if ((sc->sc_cbw->CBWCDB[0] != SC_INQUIRY) &&
+ (sc->sc_cbw->CBWCDB[0] != SC_REQUEST_SENSE)) {
+ return (1);
+ }
+ }
+
+ /*
+ * Check that only command bytes listed in the mask are
+ * non-zero.
+ */
+ for (i = 0; i != min_cmd_size; i++) {
+ if (sc->sc_cbw->CBWCDB[i] && !(mask & (1UL << i))) {
+ if (currlun) {
+ currlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ }
+ return (1);
+ }
+ }
+
+ /*
+ * If the medium isn't mounted and the command needs to access
+ * it, return an error.
+ */
+ if (currlun && (!currlun->memory_image) && needs_medium) {
+ currlun->sense_data = SS_MEDIUM_NOT_PRESENT;
+ return (1);
+ }
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * ustorage_fs_do_cmd - do command
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+ustorage_fs_do_cmd(struct ustorage_fs_softc *sc)
+{
+ uint8_t error = 1;
+ uint8_t i;
+ uint32_t temp;
+ const uint32_t mask9 = (0xFFFFFFFFUL >> 9) << 9;
+
+ /* set default data transfer pointer */
+ sc->sc_transfer.data_ptr = sc->sc_qdata;
+
+ DPRINTF("cmd_data[0]=0x%02x, data_rem=0x%08x\n",
+ sc->sc_cbw->CBWCDB[0], sc->sc_transfer.data_rem);
+
+ switch (sc->sc_cbw->CBWCDB[0]) {
+ case SC_INQUIRY:
+ sc->sc_transfer.cmd_dir = DIR_WRITE;
+ error = ustorage_fs_min_len(sc, sc->sc_cbw->CBWCDB[4], -1U);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 6,
+ (1UL << 4) | 1, 0);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_inquiry(sc);
+
+ break;
+
+ case SC_MODE_SELECT_6:
+ sc->sc_transfer.cmd_dir = DIR_READ;
+ error = ustorage_fs_min_len(sc, sc->sc_cbw->CBWCDB[4], -1U);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 6,
+ (1UL << 1) | (1UL << 4) | 1, 0);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_mode_select(sc);
+
+ break;
+
+ case SC_MODE_SELECT_10:
+ sc->sc_transfer.cmd_dir = DIR_READ;
+ error = ustorage_fs_min_len(sc,
+ get_be16(&sc->sc_cbw->CBWCDB[7]), -1U);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 10,
+ (1UL << 1) | (3UL << 7) | 1, 0);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_mode_select(sc);
+
+ break;
+
+ case SC_MODE_SENSE_6:
+ sc->sc_transfer.cmd_dir = DIR_WRITE;
+ error = ustorage_fs_min_len(sc, sc->sc_cbw->CBWCDB[4], -1U);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 6,
+ (1UL << 1) | (1UL << 2) | (1UL << 4) | 1, 0);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_mode_sense(sc);
+
+ break;
+
+ case SC_MODE_SENSE_10:
+ sc->sc_transfer.cmd_dir = DIR_WRITE;
+ error = ustorage_fs_min_len(sc,
+ get_be16(&sc->sc_cbw->CBWCDB[7]), -1U);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 10,
+ (1UL << 1) | (1UL << 2) | (3UL << 7) | 1, 0);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_mode_sense(sc);
+
+ break;
+
+ case SC_PREVENT_ALLOW_MEDIUM_REMOVAL:
+ error = ustorage_fs_min_len(sc, 0, -1U);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 6,
+ (1UL << 4) | 1, 0);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_prevent_allow(sc);
+
+ break;
+
+ case SC_READ_6:
+ i = sc->sc_cbw->CBWCDB[4];
+ sc->sc_transfer.cmd_dir = DIR_WRITE;
+ temp = ((i == 0) ? 256UL : i);
+ error = ustorage_fs_min_len(sc, temp << 9, mask9);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 6,
+ (7UL << 1) | (1UL << 4) | 1, 1);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_read(sc);
+
+ break;
+
+ case SC_READ_10:
+ sc->sc_transfer.cmd_dir = DIR_WRITE;
+ temp = get_be16(&sc->sc_cbw->CBWCDB[7]);
+ error = ustorage_fs_min_len(sc, temp << 9, mask9);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 10,
+ (1UL << 1) | (0xfUL << 2) | (3UL << 7) | 1, 1);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_read(sc);
+
+ break;
+
+ case SC_READ_12:
+ sc->sc_transfer.cmd_dir = DIR_WRITE;
+ temp = get_be32(&sc->sc_cbw->CBWCDB[6]);
+ if (temp >= (1UL << (32 - 9))) {
+ /* numerical overflow */
+ sc->sc_csw->bCSWStatus = CSWSTATUS_FAILED;
+ error = 1;
+ break;
+ }
+ error = ustorage_fs_min_len(sc, temp << 9, mask9);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 12,
+ (1UL << 1) | (0xfUL << 2) | (0xfUL << 6) | 1, 1);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_read(sc);
+
+ break;
+
+ case SC_READ_CAPACITY:
+ sc->sc_transfer.cmd_dir = DIR_WRITE;
+ error = ustorage_fs_check_cmd(sc, 10,
+ (0xfUL << 2) | (1UL << 8) | 1, 1);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_read_capacity(sc);
+
+ break;
+
+ case SC_READ_FORMAT_CAPACITIES:
+ sc->sc_transfer.cmd_dir = DIR_WRITE;
+ error = ustorage_fs_min_len(sc,
+ get_be16(&sc->sc_cbw->CBWCDB[7]), -1U);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 10,
+ (3UL << 7) | 1, 1);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_read_format_capacities(sc);
+
+ break;
+
+ case SC_REQUEST_SENSE:
+ sc->sc_transfer.cmd_dir = DIR_WRITE;
+ error = ustorage_fs_min_len(sc, sc->sc_cbw->CBWCDB[4], -1U);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 6,
+ (1UL << 4) | 1, 0);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_request_sense(sc);
+
+ break;
+
+ case SC_START_STOP_UNIT:
+ error = ustorage_fs_min_len(sc, 0, -1U);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 6,
+ (1UL << 1) | (1UL << 4) | 1, 0);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_start_stop(sc);
+
+ break;
+
+ case SC_SYNCHRONIZE_CACHE:
+ error = ustorage_fs_min_len(sc, 0, -1U);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 10,
+ (0xfUL << 2) | (3UL << 7) | 1, 1);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_synchronize_cache(sc);
+
+ break;
+
+ case SC_TEST_UNIT_READY:
+ error = ustorage_fs_min_len(sc, 0, -1U);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 6,
+ 0 | 1, 1);
+ break;
+
+ /*
+ * Although optional, this command is used by MS-Windows.
+ * We support a minimal version: BytChk must be 0.
+ */
+ case SC_VERIFY:
+ error = ustorage_fs_min_len(sc, 0, -1U);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 10,
+ (1UL << 1) | (0xfUL << 2) | (3UL << 7) | 1, 1);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_verify(sc);
+
+ break;
+
+ case SC_WRITE_6:
+ i = sc->sc_cbw->CBWCDB[4];
+ sc->sc_transfer.cmd_dir = DIR_READ;
+ temp = ((i == 0) ? 256UL : i);
+ error = ustorage_fs_min_len(sc, temp << 9, mask9);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 6,
+ (7UL << 1) | (1UL << 4) | 1, 1);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_write(sc);
+
+ break;
+
+ case SC_WRITE_10:
+ sc->sc_transfer.cmd_dir = DIR_READ;
+ temp = get_be16(&sc->sc_cbw->CBWCDB[7]);
+ error = ustorage_fs_min_len(sc, temp << 9, mask9);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 10,
+ (1UL << 1) | (0xfUL << 2) | (3UL << 7) | 1, 1);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_write(sc);
+
+ break;
+
+ case SC_WRITE_12:
+ sc->sc_transfer.cmd_dir = DIR_READ;
+ temp = get_be32(&sc->sc_cbw->CBWCDB[6]);
+ if (temp > (mask9 >> 9)) {
+ /* numerical overflow */
+ sc->sc_csw->bCSWStatus = CSWSTATUS_FAILED;
+ error = 1;
+ break;
+ }
+ error = ustorage_fs_min_len(sc, temp << 9, mask9);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, 12,
+ (1UL << 1) | (0xfUL << 2) | (0xfUL << 6) | 1, 1);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_write(sc);
+
+ break;
+
+ /*
+ * Some mandatory commands that we recognize but don't
+ * implement. They don't mean much in this setting.
+ * It's left as an exercise for anyone interested to
+ * implement RESERVE and RELEASE in terms of Posix
+ * locks.
+ */
+ case SC_FORMAT_UNIT:
+ case SC_RELEASE:
+ case SC_RESERVE:
+ case SC_SEND_DIAGNOSTIC:
+ /* Fallthrough */
+
+ default:
+ error = ustorage_fs_min_len(sc, 0, -1U);
+ if (error) {
+ break;
+ }
+ error = ustorage_fs_check_cmd(sc, sc->sc_transfer.cmd_len,
+ 0xff, 0);
+ if (error) {
+ break;
+ }
+ sc->sc_transfer.currlun->sense_data =
+ SS_INVALID_COMMAND;
+ error = 1;
+
+ break;
+ }
+ return (error);
+}
diff --git a/sys/dev/usb/template/usb_template.c b/sys/dev/usb/template/usb_template.c
new file mode 100644
index 000000000000..6e9de2a6dcce
--- /dev/null
+++ b/sys/dev/usb/template/usb_template.c
@@ -0,0 +1,1473 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2007 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains sub-routines to build up USB descriptors from
+ * USB templates.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#include <dev/usb/usb_cdc.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_dynamic.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_util.h>
+
+#define USB_DEBUG_VAR usb_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/usb_request.h>
+#include <dev/usb/template/usb_template.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+MODULE_DEPEND(usb_template, usb, 1, 1, 1);
+MODULE_VERSION(usb_template, 1);
+
+/* function prototypes */
+
+static int sysctl_hw_usb_template_power(SYSCTL_HANDLER_ARGS);
+static void usb_make_raw_desc(struct usb_temp_setup *, const uint8_t *);
+static void usb_make_endpoint_desc(struct usb_temp_setup *,
+ const struct usb_temp_endpoint_desc *);
+static void usb_make_interface_desc(struct usb_temp_setup *,
+ const struct usb_temp_interface_desc *);
+static void usb_make_config_desc(struct usb_temp_setup *,
+ const struct usb_temp_config_desc *);
+static void usb_make_device_desc(struct usb_temp_setup *,
+ const struct usb_temp_device_desc *);
+static uint8_t usb_hw_ep_match(const struct usb_hw_ep_profile *, uint8_t,
+ uint8_t);
+static uint8_t usb_hw_ep_find_match(struct usb_hw_ep_scratch *,
+ struct usb_hw_ep_scratch_sub *, uint8_t);
+static uint8_t usb_hw_ep_get_needs(struct usb_hw_ep_scratch *, uint8_t,
+ uint8_t);
+static usb_error_t usb_hw_ep_resolve(struct usb_device *,
+ struct usb_descriptor *);
+static const struct usb_temp_device_desc *usb_temp_get_tdd(struct usb_device *);
+static void *usb_temp_get_device_desc(struct usb_device *);
+static void *usb_temp_get_qualifier_desc(struct usb_device *);
+static void *usb_temp_get_config_desc(struct usb_device *, uint16_t *,
+ uint8_t);
+static const void *usb_temp_get_string_desc(struct usb_device *, uint16_t,
+ uint8_t);
+static const void *usb_temp_get_vendor_desc(struct usb_device *,
+ const struct usb_device_request *, uint16_t *plen);
+static const void *usb_temp_get_hub_desc(struct usb_device *);
+static usb_error_t usb_temp_get_desc(struct usb_device *,
+ struct usb_device_request *, const void **, uint16_t *);
+static usb_error_t usb_temp_setup_by_index(struct usb_device *,
+ uint16_t index);
+static void usb_temp_init(void *);
+
+SYSCTL_NODE(_hw_usb, OID_AUTO, templates, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB device side templates");
+SYSCTL_PROC(_hw_usb, OID_AUTO, template_power,
+ CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ NULL, 0, sysctl_hw_usb_template_power,
+ "I", "USB bus power consumption in mA at 5V");
+
+static int usb_template_power = 500; /* 500mA */
+
+static int
+sysctl_hw_usb_template_power(SYSCTL_HANDLER_ARGS)
+{
+ int error, val;
+
+ val = usb_template_power;
+ error = sysctl_handle_int(oidp, &val, 0, req);
+ if (error != 0 || req->newptr == NULL)
+ return (error);
+
+ if (val < 0 || val > 500)
+ return (EINVAL);
+
+ usb_template_power = val;
+
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_decode_str_desc
+ *
+ * Helper function to decode string descriptors into a C string.
+ *------------------------------------------------------------------------*/
+void
+usb_decode_str_desc(struct usb_string_descriptor *sd, char *buf, size_t buflen)
+{
+ size_t i;
+
+ if (sd->bLength < 2) {
+ buf[0] = '\0';
+ return;
+ }
+
+ for (i = 0; i < buflen - 1 && i < (sd->bLength / 2) - 1; i++)
+ buf[i] = UGETW(sd->bString[i]);
+
+ buf[i] = '\0';
+}
+
+/*------------------------------------------------------------------------*
+ * usb_temp_sysctl
+ *
+ * Callback for SYSCTL_PROC(9), to set and retrieve template string
+ * descriptors.
+ *------------------------------------------------------------------------*/
+int
+usb_temp_sysctl(SYSCTL_HANDLER_ARGS)
+{
+ char buf[128];
+ struct usb_string_descriptor *sd = arg1;
+ size_t len, sdlen = arg2;
+ int error;
+
+ usb_decode_str_desc(sd, buf, sizeof(buf));
+
+ error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
+ if (error != 0 || req->newptr == NULL)
+ return (error);
+
+ len = usb_make_str_desc(sd, sdlen, buf);
+ if (len == 0)
+ return (EINVAL);
+
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_make_raw_desc
+ *
+ * This function will insert a raw USB descriptor into the generated
+ * USB configuration.
+ *------------------------------------------------------------------------*/
+static void
+usb_make_raw_desc(struct usb_temp_setup *temp,
+ const uint8_t *raw)
+{
+ void *dst;
+ uint8_t len;
+
+ /*
+ * The first byte of any USB descriptor gives the length.
+ */
+ if (raw) {
+ len = raw[0];
+ if (temp->buf) {
+ dst = USB_ADD_BYTES(temp->buf, temp->size);
+ memcpy(dst, raw, len);
+
+ /* check if we have got a CDC union descriptor */
+
+ if ((raw[0] == sizeof(struct usb_cdc_union_descriptor)) &&
+ (raw[1] == UDESC_CS_INTERFACE) &&
+ (raw[2] == UDESCSUB_CDC_UNION)) {
+ struct usb_cdc_union_descriptor *ud = (void *)dst;
+
+ /* update the interface numbers */
+
+ ud->bMasterInterface +=
+ temp->bInterfaceNumber;
+ ud->bSlaveInterface[0] +=
+ temp->bInterfaceNumber;
+ }
+
+ /* check if we have got an interface association descriptor */
+
+ if ((raw[0] == sizeof(struct usb_interface_assoc_descriptor)) &&
+ (raw[1] == UDESC_IFACE_ASSOC)) {
+ struct usb_interface_assoc_descriptor *iad = (void *)dst;
+
+ /* update the interface number */
+
+ iad->bFirstInterface +=
+ temp->bInterfaceNumber;
+ }
+
+ /* check if we have got a call management descriptor */
+
+ if ((raw[0] == sizeof(struct usb_cdc_cm_descriptor)) &&
+ (raw[1] == UDESC_CS_INTERFACE) &&
+ (raw[2] == UDESCSUB_CDC_CM)) {
+ struct usb_cdc_cm_descriptor *ccd = (void *)dst;
+
+ /* update the interface number */
+
+ ccd->bDataInterface +=
+ temp->bInterfaceNumber;
+ }
+ }
+ temp->size += len;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_make_endpoint_desc
+ *
+ * This function will generate an USB endpoint descriptor from the
+ * given USB template endpoint descriptor, which will be inserted into
+ * the USB configuration.
+ *------------------------------------------------------------------------*/
+static void
+usb_make_endpoint_desc(struct usb_temp_setup *temp,
+ const struct usb_temp_endpoint_desc *ted)
+{
+ struct usb_endpoint_descriptor *ed;
+ const void **rd;
+ uint16_t old_size;
+ uint16_t mps;
+ uint8_t ea; /* Endpoint Address */
+ uint8_t et; /* Endpiont Type */
+
+ /* Reserve memory */
+ old_size = temp->size;
+
+ ea = (ted->bEndpointAddress & (UE_ADDR | UE_DIR_IN | UE_DIR_OUT));
+ et = (ted->bmAttributes & UE_XFERTYPE);
+
+ if (et == UE_ISOCHRONOUS) {
+ /* account for extra byte fields */
+ temp->size += sizeof(*ed) + 2;
+ } else {
+ temp->size += sizeof(*ed);
+ }
+
+ /* Scan all Raw Descriptors first */
+ rd = ted->ppRawDesc;
+ if (rd) {
+ while (*rd) {
+ usb_make_raw_desc(temp, *rd);
+ rd++;
+ }
+ }
+ if (ted->pPacketSize == NULL) {
+ /* not initialized */
+ temp->err = USB_ERR_INVAL;
+ return;
+ }
+ mps = ted->pPacketSize->mps[temp->usb_speed];
+ if (mps == 0) {
+ /* not initialized */
+ temp->err = USB_ERR_INVAL;
+ return;
+ } else if (mps == UE_ZERO_MPS) {
+ /* escape for Zero Max Packet Size */
+ mps = 0;
+ }
+
+ /*
+ * Fill out the real USB endpoint descriptor
+ * in case there is a buffer present:
+ */
+ if (temp->buf) {
+ ed = USB_ADD_BYTES(temp->buf, old_size);
+ if (et == UE_ISOCHRONOUS)
+ ed->bLength = sizeof(*ed) + 2;
+ else
+ ed->bLength = sizeof(*ed);
+ ed->bDescriptorType = UDESC_ENDPOINT;
+ ed->bEndpointAddress = ea;
+ ed->bmAttributes = ted->bmAttributes;
+ USETW(ed->wMaxPacketSize, mps);
+
+ /* setup bInterval parameter */
+
+ if (ted->pIntervals &&
+ ted->pIntervals->bInterval[temp->usb_speed]) {
+ ed->bInterval =
+ ted->pIntervals->bInterval[temp->usb_speed];
+ } else {
+ switch (et) {
+ case UE_BULK:
+ case UE_CONTROL:
+ ed->bInterval = 0; /* not used */
+ break;
+ case UE_INTERRUPT:
+ switch (temp->usb_speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ ed->bInterval = 1; /* 1 ms */
+ break;
+ default:
+ ed->bInterval = 4; /* 1 ms */
+ break;
+ }
+ break;
+ default: /* UE_ISOCHRONOUS */
+ switch (temp->usb_speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ ed->bInterval = 1; /* 1 ms */
+ break;
+ default:
+ ed->bInterval = 1; /* 125 us */
+ break;
+ }
+ break;
+ }
+ }
+ }
+ temp->bNumEndpoints++;
+}
+
+/*------------------------------------------------------------------------*
+ * usb_make_interface_desc
+ *
+ * This function will generate an USB interface descriptor from the
+ * given USB template interface descriptor, which will be inserted
+ * into the USB configuration.
+ *------------------------------------------------------------------------*/
+static void
+usb_make_interface_desc(struct usb_temp_setup *temp,
+ const struct usb_temp_interface_desc *tid)
+{
+ struct usb_interface_descriptor *id;
+ const struct usb_temp_endpoint_desc **ted;
+ const void **rd;
+ uint16_t old_size;
+
+ /* Reserve memory */
+
+ old_size = temp->size;
+ temp->size += sizeof(*id);
+
+ /* Update interface and alternate interface numbers */
+
+ if (tid->isAltInterface == 0) {
+ temp->bAlternateSetting = 0;
+ temp->bInterfaceNumber++;
+ } else {
+ temp->bAlternateSetting++;
+ }
+
+ /* Scan all Raw Descriptors first */
+
+ rd = tid->ppRawDesc;
+
+ if (rd) {
+ while (*rd) {
+ usb_make_raw_desc(temp, *rd);
+ rd++;
+ }
+ }
+ /* Reset some counters */
+
+ temp->bNumEndpoints = 0;
+
+ /* Scan all Endpoint Descriptors second */
+
+ ted = tid->ppEndpoints;
+ if (ted) {
+ while (*ted) {
+ usb_make_endpoint_desc(temp, *ted);
+ ted++;
+ }
+ }
+ /*
+ * Fill out the real USB interface descriptor
+ * in case there is a buffer present:
+ */
+ if (temp->buf) {
+ id = USB_ADD_BYTES(temp->buf, old_size);
+ id->bLength = sizeof(*id);
+ id->bDescriptorType = UDESC_INTERFACE;
+ id->bInterfaceNumber = temp->bInterfaceNumber;
+ id->bAlternateSetting = temp->bAlternateSetting;
+ id->bNumEndpoints = temp->bNumEndpoints;
+ id->bInterfaceClass = tid->bInterfaceClass;
+ id->bInterfaceSubClass = tid->bInterfaceSubClass;
+ id->bInterfaceProtocol = tid->bInterfaceProtocol;
+ id->iInterface = tid->iInterface;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_make_config_desc
+ *
+ * This function will generate an USB config descriptor from the given
+ * USB template config descriptor, which will be inserted into the USB
+ * configuration.
+ *------------------------------------------------------------------------*/
+static void
+usb_make_config_desc(struct usb_temp_setup *temp,
+ const struct usb_temp_config_desc *tcd)
+{
+ struct usb_config_descriptor *cd;
+ const struct usb_temp_interface_desc **tid;
+ uint16_t old_size;
+ int power;
+
+ /* Reserve memory */
+
+ old_size = temp->size;
+ temp->size += sizeof(*cd);
+
+ /* Reset some counters */
+
+ temp->bInterfaceNumber = 0xFF;
+ temp->bAlternateSetting = 0;
+
+ /* Scan all the USB interfaces */
+
+ tid = tcd->ppIfaceDesc;
+ if (tid) {
+ while (*tid) {
+ usb_make_interface_desc(temp, *tid);
+ tid++;
+ }
+ }
+ /*
+ * Fill out the real USB config descriptor
+ * in case there is a buffer present:
+ */
+ if (temp->buf) {
+ cd = USB_ADD_BYTES(temp->buf, old_size);
+
+ /* compute total size */
+ old_size = temp->size - old_size;
+
+ cd->bLength = sizeof(*cd);
+ cd->bDescriptorType = UDESC_CONFIG;
+ USETW(cd->wTotalLength, old_size);
+ cd->bNumInterface = temp->bInterfaceNumber + 1;
+ cd->bConfigurationValue = temp->bConfigurationValue;
+ cd->iConfiguration = tcd->iConfiguration;
+ cd->bmAttributes = tcd->bmAttributes;
+
+ power = usb_template_power;
+ cd->bMaxPower = power / 2; /* 2 mA units */
+ cd->bmAttributes |= UC_REMOTE_WAKEUP;
+ if (power > 0) {
+ cd->bmAttributes |= UC_BUS_POWERED;
+ cd->bmAttributes &= ~UC_SELF_POWERED;
+ } else {
+ cd->bmAttributes &= ~UC_BUS_POWERED;
+ cd->bmAttributes |= UC_SELF_POWERED;
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_make_device_desc
+ *
+ * This function will generate an USB device descriptor from the
+ * given USB template device descriptor.
+ *------------------------------------------------------------------------*/
+static void
+usb_make_device_desc(struct usb_temp_setup *temp,
+ const struct usb_temp_device_desc *tdd)
+{
+ struct usb_temp_data *utd;
+ const struct usb_temp_config_desc **tcd;
+ uint16_t old_size;
+
+ /* Reserve memory */
+
+ old_size = temp->size;
+ temp->size += sizeof(*utd);
+
+ /* Scan all the USB configs */
+
+ temp->bConfigurationValue = 1;
+ tcd = tdd->ppConfigDesc;
+ if (tcd) {
+ while (*tcd) {
+ usb_make_config_desc(temp, *tcd);
+ temp->bConfigurationValue++;
+ tcd++;
+ }
+ }
+ /*
+ * Fill out the real USB device descriptor
+ * in case there is a buffer present:
+ */
+
+ if (temp->buf) {
+ utd = USB_ADD_BYTES(temp->buf, old_size);
+
+ /* Store a pointer to our template device descriptor */
+ utd->tdd = tdd;
+
+ /* Fill out USB device descriptor */
+ utd->udd.bLength = sizeof(utd->udd);
+ utd->udd.bDescriptorType = UDESC_DEVICE;
+ utd->udd.bDeviceClass = tdd->bDeviceClass;
+ utd->udd.bDeviceSubClass = tdd->bDeviceSubClass;
+ utd->udd.bDeviceProtocol = tdd->bDeviceProtocol;
+ USETW(utd->udd.idVendor, tdd->idVendor);
+ USETW(utd->udd.idProduct, tdd->idProduct);
+ USETW(utd->udd.bcdDevice, tdd->bcdDevice);
+ utd->udd.iManufacturer = tdd->iManufacturer;
+ utd->udd.iProduct = tdd->iProduct;
+ utd->udd.iSerialNumber = tdd->iSerialNumber;
+ utd->udd.bNumConfigurations = temp->bConfigurationValue - 1;
+
+ /*
+ * Fill out the USB device qualifier. Pretend that we
+ * don't support any other speeds by setting
+ * "bNumConfigurations" equal to zero. That saves us
+ * generating an extra set of configuration
+ * descriptors.
+ */
+ utd->udq.bLength = sizeof(utd->udq);
+ utd->udq.bDescriptorType = UDESC_DEVICE_QUALIFIER;
+ utd->udq.bDeviceClass = tdd->bDeviceClass;
+ utd->udq.bDeviceSubClass = tdd->bDeviceSubClass;
+ utd->udq.bDeviceProtocol = tdd->bDeviceProtocol;
+ utd->udq.bNumConfigurations = 0;
+ USETW(utd->udq.bcdUSB, 0x0200);
+ utd->udq.bMaxPacketSize0 = 0;
+
+ switch (temp->usb_speed) {
+ case USB_SPEED_LOW:
+ USETW(utd->udd.bcdUSB, 0x0110);
+ utd->udd.bMaxPacketSize = 8;
+ break;
+ case USB_SPEED_FULL:
+ USETW(utd->udd.bcdUSB, 0x0110);
+ utd->udd.bMaxPacketSize = 32;
+ break;
+ case USB_SPEED_HIGH:
+ USETW(utd->udd.bcdUSB, 0x0200);
+ utd->udd.bMaxPacketSize = 64;
+ break;
+ case USB_SPEED_VARIABLE:
+ USETW(utd->udd.bcdUSB, 0x0250);
+ utd->udd.bMaxPacketSize = 255; /* 512 bytes */
+ break;
+ case USB_SPEED_SUPER:
+ USETW(utd->udd.bcdUSB, 0x0300);
+ utd->udd.bMaxPacketSize = 9; /* 2**9 = 512 bytes */
+ break;
+ default:
+ temp->err = USB_ERR_INVAL;
+ break;
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_hw_ep_match
+ *
+ * Return values:
+ * 0: The endpoint profile does not match the criteria
+ * Else: The endpoint profile matches the criteria
+ *------------------------------------------------------------------------*/
+static uint8_t
+usb_hw_ep_match(const struct usb_hw_ep_profile *pf,
+ uint8_t ep_type, uint8_t ep_dir_in)
+{
+ if (ep_type == UE_CONTROL) {
+ /* special */
+ return (pf->support_control);
+ }
+ if ((pf->support_in && ep_dir_in) ||
+ (pf->support_out && !ep_dir_in)) {
+ if ((pf->support_interrupt && (ep_type == UE_INTERRUPT)) ||
+ (pf->support_isochronous && (ep_type == UE_ISOCHRONOUS)) ||
+ (pf->support_bulk && (ep_type == UE_BULK))) {
+ return (1);
+ }
+ }
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_hw_ep_find_match
+ *
+ * This function is used to find the best matching endpoint profile
+ * for and endpoint belonging to an USB descriptor.
+ *
+ * Return values:
+ * 0: Success. Got a match.
+ * Else: Failure. No match.
+ *------------------------------------------------------------------------*/
+static uint8_t
+usb_hw_ep_find_match(struct usb_hw_ep_scratch *ues,
+ struct usb_hw_ep_scratch_sub *ep, uint8_t is_simplex)
+{
+ const struct usb_hw_ep_profile *pf;
+ uint16_t distance;
+ uint16_t temp;
+ uint16_t max_frame_size;
+ uint8_t n;
+ uint8_t best_n;
+ uint8_t dir_in;
+ uint8_t dir_out;
+
+ distance = 0xFFFF;
+ best_n = 0;
+
+ if ((!ep->needs_in) && (!ep->needs_out)) {
+ return (0); /* we are done */
+ }
+ if (ep->needs_ep_type == UE_CONTROL) {
+ dir_in = 1;
+ dir_out = 1;
+ } else {
+ if (ep->needs_in) {
+ dir_in = 1;
+ dir_out = 0;
+ } else {
+ dir_in = 0;
+ dir_out = 1;
+ }
+ }
+
+ for (n = 1; n != (USB_EP_MAX / 2); n++) {
+ /* get HW endpoint profile */
+ (ues->methods->get_hw_ep_profile) (ues->udev, &pf, n);
+ if (pf == NULL) {
+ /* end of profiles */
+ break;
+ }
+ /* check if IN-endpoint is reserved */
+ if (dir_in || pf->is_simplex) {
+ if (ues->bmInAlloc[n / 8] & (1 << (n % 8))) {
+ /* mismatch */
+ continue;
+ }
+ }
+ /* check if OUT-endpoint is reserved */
+ if (dir_out || pf->is_simplex) {
+ if (ues->bmOutAlloc[n / 8] & (1 << (n % 8))) {
+ /* mismatch */
+ continue;
+ }
+ }
+ /* check simplex */
+ if (pf->is_simplex == is_simplex) {
+ /* mismatch */
+ continue;
+ }
+ /* check if HW endpoint matches */
+ if (!usb_hw_ep_match(pf, ep->needs_ep_type, dir_in)) {
+ /* mismatch */
+ continue;
+ }
+ /* get maximum frame size */
+ if (dir_in)
+ max_frame_size = pf->max_in_frame_size;
+ else
+ max_frame_size = pf->max_out_frame_size;
+
+ /* check if we have a matching profile */
+ if (max_frame_size >= ep->max_frame_size) {
+ temp = (max_frame_size - ep->max_frame_size);
+ if (distance > temp) {
+ distance = temp;
+ best_n = n;
+ ep->pf = pf;
+ }
+ }
+ }
+
+ /* see if we got a match */
+ if (best_n != 0) {
+ /* get the correct profile */
+ pf = ep->pf;
+
+ /* reserve IN-endpoint */
+ if (dir_in) {
+ ues->bmInAlloc[best_n / 8] |=
+ (1 << (best_n % 8));
+ ep->hw_endpoint_in = best_n | UE_DIR_IN;
+ ep->needs_in = 0;
+ }
+ /* reserve OUT-endpoint */
+ if (dir_out) {
+ ues->bmOutAlloc[best_n / 8] |=
+ (1 << (best_n % 8));
+ ep->hw_endpoint_out = best_n | UE_DIR_OUT;
+ ep->needs_out = 0;
+ }
+ return (0); /* got a match */
+ }
+ return (1); /* failure */
+}
+
+/*------------------------------------------------------------------------*
+ * usb_hw_ep_get_needs
+ *
+ * This function will figure out the type and number of endpoints
+ * which are needed for an USB configuration.
+ *
+ * Return values:
+ * 0: Success.
+ * Else: Failure.
+ *------------------------------------------------------------------------*/
+static uint8_t
+usb_hw_ep_get_needs(struct usb_hw_ep_scratch *ues,
+ uint8_t ep_type, uint8_t is_complete)
+{
+ const struct usb_hw_ep_profile *pf;
+ struct usb_hw_ep_scratch_sub *ep_iface;
+ struct usb_hw_ep_scratch_sub *ep_curr;
+ struct usb_hw_ep_scratch_sub *ep_max;
+ struct usb_hw_ep_scratch_sub *ep_end;
+ struct usb_descriptor *desc;
+ struct usb_interface_descriptor *id;
+ struct usb_endpoint_descriptor *ed;
+ enum usb_dev_speed speed;
+ uint16_t wMaxPacketSize;
+ uint16_t temp;
+ uint8_t ep_no;
+
+ ep_iface = ues->ep_max;
+ ep_curr = ues->ep_max;
+ ep_end = ues->ep + USB_EP_MAX;
+ ep_max = ues->ep_max;
+ desc = NULL;
+ speed = usbd_get_speed(ues->udev);
+
+repeat:
+
+ while ((desc = usb_desc_foreach(ues->cd, desc))) {
+ if ((desc->bDescriptorType == UDESC_INTERFACE) &&
+ (desc->bLength >= sizeof(*id))) {
+ id = (void *)desc;
+
+ if (id->bAlternateSetting == 0) {
+ /* going forward */
+ ep_iface = ep_max;
+ } else {
+ /* reset */
+ ep_curr = ep_iface;
+ }
+ }
+ if ((desc->bDescriptorType == UDESC_ENDPOINT) &&
+ (desc->bLength >= sizeof(*ed))) {
+ ed = (void *)desc;
+
+ goto handle_endpoint_desc;
+ }
+ }
+ ues->ep_max = ep_max;
+ return (0);
+
+handle_endpoint_desc:
+ temp = (ed->bmAttributes & UE_XFERTYPE);
+
+ if (temp == ep_type) {
+ if (ep_curr == ep_end) {
+ /* too many endpoints */
+ return (1); /* failure */
+ }
+ wMaxPacketSize = UGETW(ed->wMaxPacketSize);
+ if ((wMaxPacketSize & 0xF800) &&
+ (speed == USB_SPEED_HIGH)) {
+ /* handle packet multiplier */
+ temp = (wMaxPacketSize >> 11) & 3;
+ wMaxPacketSize &= 0x7FF;
+ if (temp == 1) {
+ wMaxPacketSize *= 2;
+ } else {
+ wMaxPacketSize *= 3;
+ }
+ }
+ /*
+ * Check if we have a fixed endpoint number, else the
+ * endpoint number is allocated dynamically:
+ */
+ ep_no = (ed->bEndpointAddress & UE_ADDR);
+ if (ep_no != 0) {
+ /* get HW endpoint profile */
+ (ues->methods->get_hw_ep_profile)
+ (ues->udev, &pf, ep_no);
+ if (pf == NULL) {
+ /* HW profile does not exist - failure */
+ DPRINTFN(0, "Endpoint profile %u "
+ "does not exist\n", ep_no);
+ return (1);
+ }
+ /* reserve fixed endpoint number */
+ if (ep_type == UE_CONTROL) {
+ ues->bmInAlloc[ep_no / 8] |=
+ (1 << (ep_no % 8));
+ ues->bmOutAlloc[ep_no / 8] |=
+ (1 << (ep_no % 8));
+ if ((pf->max_in_frame_size < wMaxPacketSize) ||
+ (pf->max_out_frame_size < wMaxPacketSize)) {
+ DPRINTFN(0, "Endpoint profile %u "
+ "has too small buffer\n", ep_no);
+ return (1);
+ }
+ } else if (ed->bEndpointAddress & UE_DIR_IN) {
+ ues->bmInAlloc[ep_no / 8] |=
+ (1 << (ep_no % 8));
+ if (pf->max_in_frame_size < wMaxPacketSize) {
+ DPRINTFN(0, "Endpoint profile %u "
+ "has too small buffer\n", ep_no);
+ return (1);
+ }
+ } else {
+ ues->bmOutAlloc[ep_no / 8] |=
+ (1 << (ep_no % 8));
+ if (pf->max_out_frame_size < wMaxPacketSize) {
+ DPRINTFN(0, "Endpoint profile %u "
+ "has too small buffer\n", ep_no);
+ return (1);
+ }
+ }
+ } else if (is_complete) {
+ /* check if we have enough buffer space */
+ if (wMaxPacketSize >
+ ep_curr->max_frame_size) {
+ return (1); /* failure */
+ }
+ if (ed->bEndpointAddress & UE_DIR_IN) {
+ ed->bEndpointAddress =
+ ep_curr->hw_endpoint_in;
+ } else {
+ ed->bEndpointAddress =
+ ep_curr->hw_endpoint_out;
+ }
+
+ } else {
+ /* compute the maximum frame size */
+ if (ep_curr->max_frame_size < wMaxPacketSize) {
+ ep_curr->max_frame_size = wMaxPacketSize;
+ }
+ if (temp == UE_CONTROL) {
+ ep_curr->needs_in = 1;
+ ep_curr->needs_out = 1;
+ } else {
+ if (ed->bEndpointAddress & UE_DIR_IN) {
+ ep_curr->needs_in = 1;
+ } else {
+ ep_curr->needs_out = 1;
+ }
+ }
+ ep_curr->needs_ep_type = ep_type;
+ }
+
+ ep_curr++;
+ if (ep_max < ep_curr) {
+ ep_max = ep_curr;
+ }
+ }
+ goto repeat;
+}
+
+/*------------------------------------------------------------------------*
+ * usb_hw_ep_resolve
+ *
+ * This function will try to resolve endpoint requirements by the
+ * given endpoint profiles that the USB hardware reports.
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static usb_error_t
+usb_hw_ep_resolve(struct usb_device *udev,
+ struct usb_descriptor *desc)
+{
+ struct usb_hw_ep_scratch *ues;
+ struct usb_hw_ep_scratch_sub *ep;
+ const struct usb_hw_ep_profile *pf;
+ const struct usb_bus_methods *methods;
+ struct usb_device_descriptor *dd;
+ uint16_t mps;
+
+ if (desc == NULL)
+ return (USB_ERR_INVAL);
+
+ /* get bus methods */
+ methods = udev->bus->methods;
+
+ if (methods->get_hw_ep_profile == NULL)
+ return (USB_ERR_INVAL);
+
+ if (desc->bDescriptorType == UDESC_DEVICE) {
+ if (desc->bLength < sizeof(*dd))
+ return (USB_ERR_INVAL);
+
+ dd = (void *)desc;
+
+ /* get HW control endpoint 0 profile */
+ (methods->get_hw_ep_profile) (udev, &pf, 0);
+ if (pf == NULL) {
+ return (USB_ERR_INVAL);
+ }
+ if (!usb_hw_ep_match(pf, UE_CONTROL, 0)) {
+ DPRINTFN(0, "Endpoint 0 does not "
+ "support control\n");
+ return (USB_ERR_INVAL);
+ }
+ mps = dd->bMaxPacketSize;
+
+ if (udev->speed == USB_SPEED_FULL) {
+ /*
+ * We can optionally choose another packet size !
+ */
+ while (1) {
+ /* check if "mps" is ok */
+ if (pf->max_in_frame_size >= mps) {
+ break;
+ }
+ /* reduce maximum packet size */
+ mps /= 2;
+
+ /* check if "mps" is too small */
+ if (mps < 8) {
+ return (USB_ERR_INVAL);
+ }
+ }
+
+ dd->bMaxPacketSize = mps;
+
+ } else {
+ /* We only have one choice */
+ if (mps == 255) {
+ mps = 512;
+ }
+ /* Check if we support the specified wMaxPacketSize */
+ if (pf->max_in_frame_size < mps) {
+ return (USB_ERR_INVAL);
+ }
+ }
+ return (0); /* success */
+ }
+ if (desc->bDescriptorType != UDESC_CONFIG)
+ return (USB_ERR_INVAL);
+ if (desc->bLength < sizeof(*(ues->cd)))
+ return (USB_ERR_INVAL);
+
+ ues = udev->scratch.hw_ep_scratch;
+
+ memset(ues, 0, sizeof(*ues));
+
+ ues->ep_max = ues->ep;
+ ues->cd = (void *)desc;
+ ues->methods = methods;
+ ues->udev = udev;
+
+ /* Get all the endpoints we need */
+
+ if (usb_hw_ep_get_needs(ues, UE_ISOCHRONOUS, 0) ||
+ usb_hw_ep_get_needs(ues, UE_INTERRUPT, 0) ||
+ usb_hw_ep_get_needs(ues, UE_CONTROL, 0) ||
+ usb_hw_ep_get_needs(ues, UE_BULK, 0)) {
+ DPRINTFN(0, "Could not get needs\n");
+ return (USB_ERR_INVAL);
+ }
+ for (ep = ues->ep; ep != ues->ep_max; ep++) {
+ while (ep->needs_in || ep->needs_out) {
+ /*
+ * First try to use a simplex endpoint.
+ * Then try to use a duplex endpoint.
+ */
+ if (usb_hw_ep_find_match(ues, ep, 1) &&
+ usb_hw_ep_find_match(ues, ep, 0)) {
+ DPRINTFN(0, "Could not find match\n");
+ return (USB_ERR_INVAL);
+ }
+ }
+ }
+
+ ues->ep_max = ues->ep;
+
+ /* Update all endpoint addresses */
+
+ if (usb_hw_ep_get_needs(ues, UE_ISOCHRONOUS, 1) ||
+ usb_hw_ep_get_needs(ues, UE_INTERRUPT, 1) ||
+ usb_hw_ep_get_needs(ues, UE_CONTROL, 1) ||
+ usb_hw_ep_get_needs(ues, UE_BULK, 1)) {
+ DPRINTFN(0, "Could not update endpoint address\n");
+ return (USB_ERR_INVAL);
+ }
+ return (0); /* success */
+}
+
+/*------------------------------------------------------------------------*
+ * usb_temp_get_tdd
+ *
+ * Returns:
+ * NULL: No USB template device descriptor found.
+ * Else: Pointer to the USB template device descriptor.
+ *------------------------------------------------------------------------*/
+static const struct usb_temp_device_desc *
+usb_temp_get_tdd(struct usb_device *udev)
+{
+ if (udev->usb_template_ptr == NULL) {
+ return (NULL);
+ }
+ return (udev->usb_template_ptr->tdd);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_temp_get_device_desc
+ *
+ * Returns:
+ * NULL: No USB device descriptor found.
+ * Else: Pointer to USB device descriptor.
+ *------------------------------------------------------------------------*/
+static void *
+usb_temp_get_device_desc(struct usb_device *udev)
+{
+ struct usb_device_descriptor *dd;
+
+ if (udev->usb_template_ptr == NULL) {
+ return (NULL);
+ }
+ dd = &udev->usb_template_ptr->udd;
+ if (dd->bDescriptorType != UDESC_DEVICE) {
+ /* sanity check failed */
+ return (NULL);
+ }
+ return (dd);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_temp_get_qualifier_desc
+ *
+ * Returns:
+ * NULL: No USB device_qualifier descriptor found.
+ * Else: Pointer to USB device_qualifier descriptor.
+ *------------------------------------------------------------------------*/
+static void *
+usb_temp_get_qualifier_desc(struct usb_device *udev)
+{
+ struct usb_device_qualifier *dq;
+
+ if (udev->usb_template_ptr == NULL) {
+ return (NULL);
+ }
+ dq = &udev->usb_template_ptr->udq;
+ if (dq->bDescriptorType != UDESC_DEVICE_QUALIFIER) {
+ /* sanity check failed */
+ return (NULL);
+ }
+ return (dq);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_temp_get_config_desc
+ *
+ * Returns:
+ * NULL: No USB config descriptor found.
+ * Else: Pointer to USB config descriptor having index "index".
+ *------------------------------------------------------------------------*/
+static void *
+usb_temp_get_config_desc(struct usb_device *udev,
+ uint16_t *pLength, uint8_t index)
+{
+ struct usb_device_descriptor *dd;
+ struct usb_config_descriptor *cd;
+ uint16_t temp;
+
+ if (udev->usb_template_ptr == NULL) {
+ return (NULL);
+ }
+ dd = &udev->usb_template_ptr->udd;
+ cd = (void *)(udev->usb_template_ptr + 1);
+
+ if (index >= dd->bNumConfigurations) {
+ /* out of range */
+ return (NULL);
+ }
+ while (index--) {
+ if (cd->bDescriptorType != UDESC_CONFIG) {
+ /* sanity check failed */
+ return (NULL);
+ }
+ temp = UGETW(cd->wTotalLength);
+ cd = USB_ADD_BYTES(cd, temp);
+ }
+
+ if (pLength) {
+ *pLength = UGETW(cd->wTotalLength);
+ }
+ return (cd);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_temp_get_vendor_desc
+ *
+ * Returns:
+ * NULL: No vendor descriptor found.
+ * Else: Pointer to a vendor descriptor.
+ *------------------------------------------------------------------------*/
+static const void *
+usb_temp_get_vendor_desc(struct usb_device *udev,
+ const struct usb_device_request *req, uint16_t *plen)
+{
+ const struct usb_temp_device_desc *tdd;
+
+ tdd = usb_temp_get_tdd(udev);
+ if (tdd == NULL) {
+ return (NULL);
+ }
+ if (tdd->getVendorDesc == NULL) {
+ return (NULL);
+ }
+ return ((tdd->getVendorDesc) (req, plen));
+}
+
+/*------------------------------------------------------------------------*
+ * usb_temp_get_string_desc
+ *
+ * Returns:
+ * NULL: No string descriptor found.
+ * Else: Pointer to a string descriptor.
+ *------------------------------------------------------------------------*/
+static const void *
+usb_temp_get_string_desc(struct usb_device *udev,
+ uint16_t lang_id, uint8_t string_index)
+{
+ const struct usb_temp_device_desc *tdd;
+
+ tdd = usb_temp_get_tdd(udev);
+ if (tdd == NULL) {
+ return (NULL);
+ }
+ if (tdd->getStringDesc == NULL) {
+ return (NULL);
+ }
+ return ((tdd->getStringDesc) (lang_id, string_index));
+}
+
+/*------------------------------------------------------------------------*
+ * usb_temp_get_hub_desc
+ *
+ * Returns:
+ * NULL: No USB HUB descriptor found.
+ * Else: Pointer to a USB HUB descriptor.
+ *------------------------------------------------------------------------*/
+static const void *
+usb_temp_get_hub_desc(struct usb_device *udev)
+{
+ return (NULL); /* needs to be implemented */
+}
+
+/*------------------------------------------------------------------------*
+ * usb_temp_get_desc
+ *
+ * This function is a demultiplexer for local USB device side control
+ * endpoint requests.
+ *------------------------------------------------------------------------*/
+static usb_error_t
+usb_temp_get_desc(struct usb_device *udev, struct usb_device_request *req,
+ const void **pPtr, uint16_t *pLength)
+{
+ const uint8_t *buf;
+ uint16_t len;
+
+ buf = NULL;
+ len = 0;
+
+ switch (req->bmRequestType) {
+ case UT_READ_DEVICE:
+ switch (req->bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_descriptor;
+ default:
+ goto tr_stalled;
+ }
+ case UT_READ_CLASS_DEVICE:
+ switch (req->bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_class_descriptor;
+ default:
+ goto tr_stalled;
+ }
+ default:
+ goto tr_stalled;
+ }
+
+tr_handle_get_descriptor:
+ switch (req->wValue[1]) {
+ case UDESC_DEVICE:
+ if (req->wValue[0]) {
+ goto tr_stalled;
+ }
+ buf = usb_temp_get_device_desc(udev);
+ goto tr_valid;
+ case UDESC_DEVICE_QUALIFIER:
+ if (udev->speed != USB_SPEED_HIGH) {
+ goto tr_stalled;
+ }
+ if (req->wValue[0]) {
+ goto tr_stalled;
+ }
+ buf = usb_temp_get_qualifier_desc(udev);
+ goto tr_valid;
+ case UDESC_OTHER_SPEED_CONFIGURATION:
+ if (udev->speed != USB_SPEED_HIGH) {
+ goto tr_stalled;
+ }
+ case UDESC_CONFIG:
+ buf = usb_temp_get_config_desc(udev,
+ &len, req->wValue[0]);
+ goto tr_valid;
+ case UDESC_STRING:
+ buf = usb_temp_get_string_desc(udev,
+ UGETW(req->wIndex), req->wValue[0]);
+ goto tr_valid;
+ default:
+ goto tr_stalled;
+ }
+
+tr_handle_get_class_descriptor:
+ if (req->wValue[0]) {
+ goto tr_stalled;
+ }
+ buf = usb_temp_get_hub_desc(udev);
+ goto tr_valid;
+
+tr_valid:
+ if (buf == NULL)
+ goto tr_stalled;
+ if (len == 0)
+ len = buf[0];
+ *pPtr = buf;
+ *pLength = len;
+ return (0); /* success */
+
+tr_stalled:
+ /* try to get a vendor specific descriptor */
+ len = 0;
+ buf = usb_temp_get_vendor_desc(udev, req, &len);
+ if (buf != NULL)
+ goto tr_valid;
+ *pPtr = NULL;
+ *pLength = 0;
+ return (0); /* we ignore failures */
+}
+
+/*------------------------------------------------------------------------*
+ * usb_temp_setup
+ *
+ * This function generates USB descriptors according to the given USB
+ * template device descriptor. It will also try to figure out the best
+ * matching endpoint addresses using the hardware endpoint profiles.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usb_temp_setup(struct usb_device *udev,
+ const struct usb_temp_device_desc *tdd)
+{
+ struct usb_temp_setup *uts;
+ void *buf;
+ usb_error_t error;
+ uint8_t n;
+ uint8_t do_unlock;
+
+ /* be NULL safe */
+ if (tdd == NULL)
+ return (0);
+
+ /* Protect scratch area */
+ do_unlock = usbd_ctrl_lock(udev);
+
+ uts = udev->scratch.temp_setup;
+
+ memset(uts, 0, sizeof(*uts));
+
+ uts->usb_speed = udev->speed;
+ uts->self_powered = udev->flags.self_powered;
+
+ /* first pass */
+
+ usb_make_device_desc(uts, tdd);
+
+ if (uts->err) {
+ /* some error happened */
+ goto done;
+ }
+ /* sanity check */
+ if (uts->size == 0) {
+ uts->err = USB_ERR_INVAL;
+ goto done;
+ }
+ /* allocate zeroed memory */
+ uts->buf = usbd_alloc_config_desc(udev, uts->size);
+ /*
+ * Allow malloc() to return NULL regardless of M_WAITOK flag.
+ * This helps when porting the software to non-FreeBSD
+ * systems.
+ */
+ if (uts->buf == NULL) {
+ /* could not allocate memory */
+ uts->err = USB_ERR_NOMEM;
+ goto done;
+ }
+ /* second pass */
+
+ uts->size = 0;
+
+ usb_make_device_desc(uts, tdd);
+
+ /*
+ * Store a pointer to our descriptors:
+ */
+ udev->usb_template_ptr = uts->buf;
+
+ if (uts->err) {
+ /* some error happened during second pass */
+ goto done;
+ }
+ /*
+ * Resolve all endpoint addresses !
+ */
+ buf = usb_temp_get_device_desc(udev);
+ uts->err = usb_hw_ep_resolve(udev, buf);
+ if (uts->err) {
+ DPRINTFN(0, "Could not resolve endpoints for "
+ "Device Descriptor, error = %s\n",
+ usbd_errstr(uts->err));
+ goto done;
+ }
+ for (n = 0;; n++) {
+ buf = usb_temp_get_config_desc(udev, NULL, n);
+ if (buf == NULL) {
+ break;
+ }
+ uts->err = usb_hw_ep_resolve(udev, buf);
+ if (uts->err) {
+ DPRINTFN(0, "Could not resolve endpoints for "
+ "Config Descriptor %u, error = %s\n", n,
+ usbd_errstr(uts->err));
+ goto done;
+ }
+ }
+done:
+ error = uts->err;
+ if (error)
+ usb_temp_unsetup(udev);
+ if (do_unlock)
+ usbd_ctrl_unlock(udev);
+ return (error);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_temp_unsetup
+ *
+ * This function frees any memory associated with the currently
+ * setup template, if any.
+ *------------------------------------------------------------------------*/
+void
+usb_temp_unsetup(struct usb_device *udev)
+{
+ usbd_free_config_desc(udev, udev->usb_template_ptr);
+ udev->usb_template_ptr = NULL;
+}
+
+static usb_error_t
+usb_temp_setup_by_index(struct usb_device *udev, uint16_t index)
+{
+ usb_error_t err;
+
+ switch (index) {
+ case USB_TEMP_MSC:
+ err = usb_temp_setup(udev, &usb_template_msc);
+ break;
+ case USB_TEMP_CDCE:
+ err = usb_temp_setup(udev, &usb_template_cdce);
+ break;
+ case USB_TEMP_MTP:
+ err = usb_temp_setup(udev, &usb_template_mtp);
+ break;
+ case USB_TEMP_MODEM:
+ err = usb_temp_setup(udev, &usb_template_modem);
+ break;
+ case USB_TEMP_AUDIO:
+ err = usb_temp_setup(udev, &usb_template_audio);
+ break;
+ case USB_TEMP_KBD:
+ err = usb_temp_setup(udev, &usb_template_kbd);
+ break;
+ case USB_TEMP_MOUSE:
+ err = usb_temp_setup(udev, &usb_template_mouse);
+ break;
+ case USB_TEMP_PHONE:
+ err = usb_temp_setup(udev, &usb_template_phone);
+ break;
+ case USB_TEMP_SERIALNET:
+ err = usb_temp_setup(udev, &usb_template_serialnet);
+ break;
+ case USB_TEMP_MIDI:
+ err = usb_temp_setup(udev, &usb_template_midi);
+ break;
+ case USB_TEMP_MULTI:
+ err = usb_temp_setup(udev, &usb_template_multi);
+ break;
+ case USB_TEMP_CDCEEM:
+ err = usb_temp_setup(udev, &usb_template_cdceem);
+ break;
+ default:
+ return (USB_ERR_INVAL);
+ }
+
+ return (err);
+}
+
+static void
+usb_temp_init(void *arg)
+{
+ /* register our functions */
+ usb_temp_get_desc_p = &usb_temp_get_desc;
+ usb_temp_setup_by_index_p = &usb_temp_setup_by_index;
+ usb_temp_unsetup_p = &usb_temp_unsetup;
+}
+
+SYSINIT(usb_temp_init, SI_SUB_LOCK, SI_ORDER_FIRST, usb_temp_init, NULL);
+SYSUNINIT(usb_temp_unload, SI_SUB_LOCK, SI_ORDER_ANY, usb_temp_unload, NULL);
diff --git a/sys/dev/usb/template/usb_template.h b/sys/dev/usb/template/usb_template.h
new file mode 100644
index 000000000000..e99507b4cc7a
--- /dev/null
+++ b/sys/dev/usb/template/usb_template.h
@@ -0,0 +1,129 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2007 Hans Petter Selasky <hselasky@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.
+ */
+
+/* USB templates are used to build up real USB descriptors */
+
+#ifndef _USB_TEMPLATE_H_
+#define _USB_TEMPLATE_H_
+
+#ifndef USB_TEMPLATE_VENDOR
+/*
+ * https://github.com/obdev/v-usb/blob/master/usbdrv/USB-IDs-for-free.txt
+ */
+#define USB_TEMPLATE_VENDOR 0x16c0
+#define USB_TEMPLATE_MANUFACTURER \
+ "The FreeBSD Project (https://www.FreeBSD.org)"
+#endif
+
+typedef const void *(usb_temp_get_string_desc_t)(uint16_t lang_id, uint8_t string_index);
+typedef const void *(usb_temp_get_vendor_desc_t)(const struct usb_device_request *req, uint16_t *plen);
+
+struct usb_temp_packet_size {
+ uint16_t mps[USB_SPEED_MAX];
+};
+
+struct usb_temp_interval {
+ uint8_t bInterval[USB_SPEED_MAX];
+};
+
+struct usb_temp_endpoint_desc {
+ const void **ppRawDesc;
+ const struct usb_temp_packet_size *pPacketSize;
+ const struct usb_temp_interval *pIntervals;
+ /*
+ * If (bEndpointAddress & UE_ADDR) is non-zero the endpoint number
+ * is pre-selected for this endpoint descriptor. Else an endpoint
+ * number is automatically chosen.
+ */
+ uint8_t bEndpointAddress; /* UE_DIR_IN or UE_DIR_OUT */
+ uint8_t bmAttributes;
+};
+
+struct usb_temp_interface_desc {
+ const void **ppRawDesc;
+ const struct usb_temp_endpoint_desc **ppEndpoints;
+ uint8_t bInterfaceClass;
+ uint8_t bInterfaceSubClass;
+ uint8_t bInterfaceProtocol;
+ uint8_t iInterface;
+ uint8_t isAltInterface;
+};
+
+struct usb_temp_config_desc {
+ const struct usb_temp_interface_desc **ppIfaceDesc;
+ uint8_t bmAttributes;
+ uint8_t bMaxPower;
+ uint8_t iConfiguration;
+};
+
+struct usb_temp_device_desc {
+ usb_temp_get_string_desc_t *getStringDesc;
+ usb_temp_get_vendor_desc_t *getVendorDesc;
+ const struct usb_temp_config_desc **ppConfigDesc;
+ uint16_t idVendor;
+ uint16_t idProduct;
+ uint16_t bcdDevice;
+ uint8_t bDeviceClass;
+ uint8_t bDeviceSubClass;
+ uint8_t bDeviceProtocol;
+ uint8_t iManufacturer;
+ uint8_t iProduct;
+ uint8_t iSerialNumber;
+};
+
+struct usb_temp_data {
+ const struct usb_temp_device_desc *tdd;
+ struct usb_device_descriptor udd; /* device descriptor */
+ struct usb_device_qualifier udq; /* device qualifier */
+};
+
+/* prototypes */
+
+extern struct usb_temp_device_desc usb_template_audio;
+extern struct usb_temp_device_desc usb_template_cdce;
+extern struct usb_temp_device_desc usb_template_kbd;
+extern struct usb_temp_device_desc usb_template_modem;
+extern struct usb_temp_device_desc usb_template_mouse;
+extern struct usb_temp_device_desc usb_template_msc;
+extern struct usb_temp_device_desc usb_template_mtp;
+extern struct usb_temp_device_desc usb_template_phone;
+extern struct usb_temp_device_desc usb_template_serialnet;
+extern struct usb_temp_device_desc usb_template_midi;
+extern struct usb_temp_device_desc usb_template_multi;
+extern struct usb_temp_device_desc usb_template_cdceem;
+
+void usb_decode_str_desc(struct usb_string_descriptor *sd,
+ char *buf, size_t buflen);
+usb_error_t usb_temp_setup(struct usb_device *,
+ const struct usb_temp_device_desc *);
+void usb_temp_unsetup(struct usb_device *);
+int usb_temp_sysctl(SYSCTL_HANDLER_ARGS);
+
+SYSCTL_DECL(_hw_usb_templates);
+
+#endif /* _USB_TEMPLATE_H_ */
diff --git a/sys/dev/usb/template/usb_template_audio.c b/sys/dev/usb/template/usb_template_audio.c
new file mode 100644
index 000000000000..17d59f05a50d
--- /dev/null
+++ b/sys/dev/usb/template/usb_template_audio.c
@@ -0,0 +1,479 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky
+ * Copyright (c) 2018 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the USB template for an USB Audio Device.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_cdc.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/template/usb_template.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+enum {
+ AUDIO_LANG_INDEX,
+ AUDIO_MIXER_INDEX,
+ AUDIO_RECORD_INDEX,
+ AUDIO_PLAYBACK_INDEX,
+ AUDIO_MANUFACTURER_INDEX,
+ AUDIO_PRODUCT_INDEX,
+ AUDIO_SERIAL_NUMBER_INDEX,
+ AUDIO_MAX_INDEX,
+};
+
+#define AUDIO_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR
+#define AUDIO_DEFAULT_PRODUCT_ID 0x27e0
+#define AUDIO_DEFAULT_MIXER "Mixer interface"
+#define AUDIO_DEFAULT_RECORD "Record interface"
+#define AUDIO_DEFAULT_PLAYBACK "Playback interface"
+#define AUDIO_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER
+#define AUDIO_DEFAULT_PRODUCT "Audio Test Device"
+#define AUDIO_DEFAULT_SERIAL_NUMBER "March 2008"
+
+static struct usb_string_descriptor audio_mixer;
+static struct usb_string_descriptor audio_record;
+static struct usb_string_descriptor audio_playback;
+static struct usb_string_descriptor audio_manufacturer;
+static struct usb_string_descriptor audio_product;
+static struct usb_string_descriptor audio_serial_number;
+
+static struct sysctl_ctx_list audio_ctx_list;
+
+/* prototypes */
+
+/*
+ * Audio Mixer description structures
+ *
+ * Some of the audio descriptors were dumped
+ * from a Creative Labs USB audio device.
+ */
+
+static const uint8_t audio_raw_desc_0[] = {
+ 0x0a, 0x24, 0x01, 0x00, 0x01, 0xa9, 0x00, 0x02,
+ 0x01, 0x02
+};
+
+static const uint8_t audio_raw_desc_1[] = {
+ 0x0c, 0x24, 0x02, 0x01, 0x01, 0x01, 0x00, 0x02,
+ 0x03, 0x00, 0x00, 0x00
+};
+
+static const uint8_t audio_raw_desc_2[] = {
+ 0x0c, 0x24, 0x02, 0x02, 0x01, 0x02, 0x00, 0x02,
+ 0x03, 0x00, 0x00, 0x00
+};
+
+static const uint8_t audio_raw_desc_3[] = {
+ 0x0c, 0x24, 0x02, 0x03, 0x03, 0x06, 0x00, 0x02,
+ 0x03, 0x00, 0x00, 0x00
+};
+
+static const uint8_t audio_raw_desc_4[] = {
+ 0x0c, 0x24, 0x02, 0x04, 0x05, 0x06, 0x00, 0x02,
+ 0x03, 0x00, 0x00, 0x00
+};
+
+static const uint8_t audio_raw_desc_5[] = {
+ 0x09, 0x24, 0x03, 0x05, 0x05, 0x06, 0x00, 0x01,
+ 0x00
+};
+
+static const uint8_t audio_raw_desc_6[] = {
+ 0x09, 0x24, 0x03, 0x06, 0x01, 0x03, 0x00, 0x09,
+ 0x00
+};
+
+static const uint8_t audio_raw_desc_7[] = {
+ 0x09, 0x24, 0x03, 0x07, 0x01, 0x01, 0x00, 0x08,
+ 0x00
+};
+
+static const uint8_t audio_raw_desc_8[] = {
+ 0x09, 0x24, 0x05, 0x08, 0x03, 0x0a, 0x0b, 0x0c,
+ 0x00
+};
+
+static const uint8_t audio_raw_desc_9[] = {
+ 0x0a, 0x24, 0x06, 0x09, 0x0f, 0x01, 0x01, 0x02,
+ 0x02, 0x00
+};
+
+static const uint8_t audio_raw_desc_10[] = {
+ 0x0a, 0x24, 0x06, 0x0a, 0x02, 0x01, 0x43, 0x00,
+ 0x00, 0x00
+};
+
+static const uint8_t audio_raw_desc_11[] = {
+ 0x0a, 0x24, 0x06, 0x0b, 0x03, 0x01, 0x01, 0x02,
+ 0x02, 0x00
+};
+
+static const uint8_t audio_raw_desc_12[] = {
+ 0x0a, 0x24, 0x06, 0x0c, 0x04, 0x01, 0x01, 0x00,
+ 0x00, 0x00
+};
+
+static const uint8_t audio_raw_desc_13[] = {
+ 0x0a, 0x24, 0x06, 0x0d, 0x02, 0x01, 0x03, 0x00,
+ 0x00, 0x00
+};
+
+static const uint8_t audio_raw_desc_14[] = {
+ 0x0a, 0x24, 0x06, 0x0e, 0x03, 0x01, 0x01, 0x02,
+ 0x02, 0x00
+};
+
+static const uint8_t audio_raw_desc_15[] = {
+ 0x0f, 0x24, 0x04, 0x0f, 0x03, 0x01, 0x0d, 0x0e,
+ 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static const void *audio_raw_iface_0_desc[] = {
+ audio_raw_desc_0,
+ audio_raw_desc_1,
+ audio_raw_desc_2,
+ audio_raw_desc_3,
+ audio_raw_desc_4,
+ audio_raw_desc_5,
+ audio_raw_desc_6,
+ audio_raw_desc_7,
+ audio_raw_desc_8,
+ audio_raw_desc_9,
+ audio_raw_desc_10,
+ audio_raw_desc_11,
+ audio_raw_desc_12,
+ audio_raw_desc_13,
+ audio_raw_desc_14,
+ audio_raw_desc_15,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc audio_iface_0 = {
+ .ppEndpoints = NULL, /* no endpoints */
+ .ppRawDesc = audio_raw_iface_0_desc,
+ .bInterfaceClass = UICLASS_AUDIO,
+ .bInterfaceSubClass = UISUBCLASS_AUDIOCONTROL,
+ .bInterfaceProtocol = 0,
+ .iInterface = AUDIO_MIXER_INDEX,
+};
+
+static const uint8_t audio_raw_desc_20[] = {
+ 0x07, 0x24, 0x01, 0x01, 0x03, 0x01, 0x00
+
+};
+
+static const uint8_t audio_raw_desc_21[] = {
+ 0x0b, 0x24, 0x02, 0x01, 0x02, 0x02, 0x10, 0x01,
+ /* 48kHz */
+ 0x80, 0xbb, 0x00
+};
+
+static const uint8_t audio_raw_desc_22[] = {
+ 0x07, 0x25, 0x01, 0x00, 0x01, 0x04, 0x00
+};
+
+static const void *audio_raw_iface_1_desc[] = {
+ audio_raw_desc_20,
+ audio_raw_desc_21,
+ NULL,
+};
+
+static const void *audio_raw_ep_1_desc[] = {
+ audio_raw_desc_22,
+ NULL,
+};
+
+static const struct usb_temp_packet_size audio_isoc_mps = {
+ .mps[USB_SPEED_FULL] = 0xC8,
+ .mps[USB_SPEED_HIGH] = 0xC8,
+};
+
+static const struct usb_temp_interval audio_isoc_interval = {
+ .bInterval[USB_SPEED_FULL] = 1, /* 1:1 */
+ .bInterval[USB_SPEED_HIGH] = 4, /* 1:8 */
+};
+
+static const struct usb_temp_endpoint_desc audio_isoc_out_ep = {
+ .ppRawDesc = audio_raw_ep_1_desc,
+ .pPacketSize = &audio_isoc_mps,
+ .pIntervals = &audio_isoc_interval,
+ .bEndpointAddress = UE_DIR_OUT,
+ .bmAttributes = UE_ISOCHRONOUS | UE_ISO_ADAPT,
+};
+
+static const struct usb_temp_endpoint_desc *audio_iface_1_ep[] = {
+ &audio_isoc_out_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc audio_iface_1_alt_0 = {
+ .ppEndpoints = NULL, /* no endpoints */
+ .ppRawDesc = NULL, /* no raw descriptors */
+ .bInterfaceClass = UICLASS_AUDIO,
+ .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM,
+ .bInterfaceProtocol = 0,
+ .iInterface = AUDIO_PLAYBACK_INDEX,
+};
+
+static const struct usb_temp_interface_desc audio_iface_1_alt_1 = {
+ .ppEndpoints = audio_iface_1_ep,
+ .ppRawDesc = audio_raw_iface_1_desc,
+ .bInterfaceClass = UICLASS_AUDIO,
+ .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM,
+ .bInterfaceProtocol = 0,
+ .iInterface = AUDIO_PLAYBACK_INDEX,
+ .isAltInterface = 1, /* this is an alternate setting */
+};
+
+static const uint8_t audio_raw_desc_30[] = {
+ 0x07, 0x24, 0x01, 0x07, 0x01, 0x01, 0x00
+
+};
+
+static const uint8_t audio_raw_desc_31[] = {
+ 0x0b, 0x24, 0x02, 0x01, 0x02, 0x02, 0x10, 0x01,
+ /* 48kHz */
+ 0x80, 0xbb, 0x00
+};
+
+static const uint8_t audio_raw_desc_32[] = {
+ 0x07, 0x25, 0x01, 0x01, 0x00, 0x00, 0x00
+};
+
+static const void *audio_raw_iface_2_desc[] = {
+ audio_raw_desc_30,
+ audio_raw_desc_31,
+ NULL,
+};
+
+static const void *audio_raw_ep_2_desc[] = {
+ audio_raw_desc_32,
+ NULL,
+};
+
+static const struct usb_temp_endpoint_desc audio_isoc_in_ep = {
+ .ppRawDesc = audio_raw_ep_2_desc,
+ .pPacketSize = &audio_isoc_mps,
+ .pIntervals = &audio_isoc_interval,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_ISOCHRONOUS | UE_ISO_ADAPT,
+};
+
+static const struct usb_temp_endpoint_desc *audio_iface_2_ep[] = {
+ &audio_isoc_in_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc audio_iface_2_alt_0 = {
+ .ppEndpoints = NULL, /* no endpoints */
+ .ppRawDesc = NULL, /* no raw descriptors */
+ .bInterfaceClass = UICLASS_AUDIO,
+ .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM,
+ .bInterfaceProtocol = 0,
+ .iInterface = AUDIO_RECORD_INDEX,
+};
+
+static const struct usb_temp_interface_desc audio_iface_2_alt_1 = {
+ .ppEndpoints = audio_iface_2_ep,
+ .ppRawDesc = audio_raw_iface_2_desc,
+ .bInterfaceClass = UICLASS_AUDIO,
+ .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM,
+ .bInterfaceProtocol = 0,
+ .iInterface = AUDIO_RECORD_INDEX,
+ .isAltInterface = 1, /* this is an alternate setting */
+};
+
+static const struct usb_temp_interface_desc *audio_interfaces[] = {
+ &audio_iface_0,
+ &audio_iface_1_alt_0,
+ &audio_iface_1_alt_1,
+ &audio_iface_2_alt_0,
+ &audio_iface_2_alt_1,
+ NULL,
+};
+
+static const struct usb_temp_config_desc audio_config_desc = {
+ .ppIfaceDesc = audio_interfaces,
+ .bmAttributes = 0,
+ .bMaxPower = 0,
+ .iConfiguration = AUDIO_PRODUCT_INDEX,
+};
+
+static const struct usb_temp_config_desc *audio_configs[] = {
+ &audio_config_desc,
+ NULL,
+};
+
+static usb_temp_get_string_desc_t audio_get_string_desc;
+
+struct usb_temp_device_desc usb_template_audio = {
+ .getStringDesc = &audio_get_string_desc,
+ .ppConfigDesc = audio_configs,
+ .idVendor = AUDIO_DEFAULT_VENDOR_ID,
+ .idProduct = AUDIO_DEFAULT_PRODUCT_ID,
+ .bcdDevice = 0x0100,
+ .bDeviceClass = UDCLASS_COMM,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .iManufacturer = AUDIO_MANUFACTURER_INDEX,
+ .iProduct = AUDIO_PRODUCT_INDEX,
+ .iSerialNumber = AUDIO_SERIAL_NUMBER_INDEX,
+};
+
+/*------------------------------------------------------------------------*
+ * audio_get_string_desc
+ *
+ * Return values:
+ * NULL: Failure. No such string.
+ * Else: Success. Pointer to string descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+audio_get_string_desc(uint16_t lang_id, uint8_t string_index)
+{
+ static const void *ptr[AUDIO_MAX_INDEX] = {
+ [AUDIO_LANG_INDEX] = &usb_string_lang_en,
+ [AUDIO_MIXER_INDEX] = &audio_mixer,
+ [AUDIO_RECORD_INDEX] = &audio_record,
+ [AUDIO_PLAYBACK_INDEX] = &audio_playback,
+ [AUDIO_MANUFACTURER_INDEX] = &audio_manufacturer,
+ [AUDIO_PRODUCT_INDEX] = &audio_product,
+ [AUDIO_SERIAL_NUMBER_INDEX] = &audio_serial_number,
+ };
+
+ if (string_index == 0) {
+ return (&usb_string_lang_en);
+ }
+ if (lang_id != 0x0409) {
+ return (NULL);
+ }
+ if (string_index < AUDIO_MAX_INDEX) {
+ return (ptr[string_index]);
+ }
+ return (NULL);
+}
+
+static void
+audio_init(void *arg __unused)
+{
+ struct sysctl_oid *parent;
+ char parent_name[3];
+
+ usb_make_str_desc(&audio_mixer, sizeof(audio_mixer),
+ AUDIO_DEFAULT_MIXER);
+ usb_make_str_desc(&audio_record, sizeof(audio_record),
+ AUDIO_DEFAULT_RECORD);
+ usb_make_str_desc(&audio_playback, sizeof(audio_playback),
+ AUDIO_DEFAULT_PLAYBACK);
+ usb_make_str_desc(&audio_manufacturer, sizeof(audio_manufacturer),
+ AUDIO_DEFAULT_MANUFACTURER);
+ usb_make_str_desc(&audio_product, sizeof(audio_product),
+ AUDIO_DEFAULT_PRODUCT);
+ usb_make_str_desc(&audio_serial_number, sizeof(audio_serial_number),
+ AUDIO_DEFAULT_SERIAL_NUMBER);
+
+ snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_AUDIO);
+ sysctl_ctx_init(&audio_ctx_list);
+
+ parent = SYSCTL_ADD_NODE(&audio_ctx_list,
+ SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO,
+ parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, "USB Audio Interface device side template");
+ SYSCTL_ADD_U16(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "vendor_id", CTLFLAG_RWTUN, &usb_template_audio.idVendor,
+ 1, "Vendor identifier");
+ SYSCTL_ADD_U16(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product_id", CTLFLAG_RWTUN, &usb_template_audio.idProduct,
+ 1, "Product identifier");
+#if 0
+ SYSCTL_ADD_PROC(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "mixer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &audio_mixer, sizeof(audio_mixer), usb_temp_sysctl,
+ "A", "Mixer interface string");
+ SYSCTL_ADD_PROC(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "record", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &audio_record, sizeof(audio_record), usb_temp_sysctl,
+ "A", "Record interface string");
+ SYSCTL_ADD_PROC(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "playback", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &audio_playback, sizeof(audio_playback), usb_temp_sysctl,
+ "A", "Playback interface string");
+#endif
+ SYSCTL_ADD_PROC(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &audio_manufacturer, sizeof(audio_manufacturer), usb_temp_sysctl,
+ "A", "Manufacturer string");
+ SYSCTL_ADD_PROC(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &audio_product, sizeof(audio_product), usb_temp_sysctl,
+ "A", "Product string");
+ SYSCTL_ADD_PROC(&audio_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &audio_serial_number, sizeof(audio_serial_number), usb_temp_sysctl,
+ "A", "Serial number string");
+}
+
+static void
+audio_uninit(void *arg __unused)
+{
+
+ sysctl_ctx_free(&audio_ctx_list);
+}
+
+SYSINIT(audio_init, SI_SUB_LOCK, SI_ORDER_FIRST, audio_init, NULL);
+SYSUNINIT(audio_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, audio_uninit, NULL);
diff --git a/sys/dev/usb/template/usb_template_cdce.c b/sys/dev/usb/template/usb_template_cdce.c
new file mode 100644
index 000000000000..11fd9a64b87e
--- /dev/null
+++ b/sys/dev/usb/template/usb_template_cdce.c
@@ -0,0 +1,352 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2007 Hans Petter Selasky <hselasky@FreeBSD.org>
+ * Copyright (c) 2018 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the USB templates for a CDC USB ethernet device.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_cdc.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/template/usb_template.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+enum {
+ ETH_LANG_INDEX,
+ ETH_MAC_INDEX,
+ ETH_CONTROL_INDEX,
+ ETH_DATA_INDEX,
+ ETH_CONFIGURATION_INDEX,
+ ETH_MANUFACTURER_INDEX,
+ ETH_PRODUCT_INDEX,
+ ETH_SERIAL_NUMBER_INDEX,
+ ETH_MAX_INDEX,
+};
+
+#define ETH_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR
+#define ETH_DEFAULT_PRODUCT_ID 0x27e1
+#define ETH_DEFAULT_MAC "2A02030405060789AB"
+#define ETH_DEFAULT_CONTROL "USB Ethernet Comm Interface"
+#define ETH_DEFAULT_DATA "USB Ethernet Data Interface"
+#define ETH_DEFAULT_CONFIG "Default Config"
+#define ETH_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER
+#define ETH_DEFAULT_PRODUCT "USB Ethernet Adapter"
+#define ETH_DEFAULT_SERIAL_NUMBER "December 2007"
+
+static struct usb_string_descriptor eth_mac;
+static struct usb_string_descriptor eth_control;
+static struct usb_string_descriptor eth_data;
+static struct usb_string_descriptor eth_configuration;
+static struct usb_string_descriptor eth_manufacturer;
+static struct usb_string_descriptor eth_product;
+static struct usb_string_descriptor eth_serial_number;
+
+static struct sysctl_ctx_list eth_ctx_list;
+
+/* prototypes */
+
+static usb_temp_get_string_desc_t eth_get_string_desc;
+
+static const struct usb_cdc_union_descriptor eth_union_desc = {
+ .bLength = sizeof(eth_union_desc),
+ .bDescriptorType = UDESC_CS_INTERFACE,
+ .bDescriptorSubtype = UDESCSUB_CDC_UNION,
+ .bMasterInterface = 0, /* this is automatically updated */
+ .bSlaveInterface[0] = 1, /* this is automatically updated */
+};
+
+static const struct usb_cdc_header_descriptor eth_header_desc = {
+ .bLength = sizeof(eth_header_desc),
+ .bDescriptorType = UDESC_CS_INTERFACE,
+ .bDescriptorSubtype = UDESCSUB_CDC_HEADER,
+ .bcdCDC[0] = 0x10,
+ .bcdCDC[1] = 0x01,
+};
+
+static const struct usb_cdc_ethernet_descriptor eth_enf_desc = {
+ .bLength = sizeof(eth_enf_desc),
+ .bDescriptorType = UDESC_CS_INTERFACE,
+ .bDescriptorSubtype = UDESCSUB_CDC_ENF,
+ .iMacAddress = ETH_MAC_INDEX,
+ .bmEthernetStatistics = {0, 0, 0, 0},
+ .wMaxSegmentSize = {0xEA, 0x05},/* 1514 bytes */
+ .wNumberMCFilters = {0, 0},
+ .bNumberPowerFilters = 0,
+};
+
+static const void *eth_control_if_desc[] = {
+ &eth_union_desc,
+ &eth_header_desc,
+ &eth_enf_desc,
+ NULL,
+};
+
+static const struct usb_temp_packet_size bulk_mps = {
+ .mps[USB_SPEED_FULL] = 64,
+ .mps[USB_SPEED_HIGH] = 512,
+};
+
+static const struct usb_temp_packet_size intr_mps = {
+ .mps[USB_SPEED_FULL] = 8,
+ .mps[USB_SPEED_HIGH] = 8,
+};
+
+static const struct usb_temp_endpoint_desc bulk_in_ep = {
+ .pPacketSize = &bulk_mps,
+#ifdef USB_HIP_IN_EP_0
+ .bEndpointAddress = USB_HIP_IN_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_IN,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc bulk_out_ep = {
+ .pPacketSize = &bulk_mps,
+#ifdef USB_HIP_OUT_EP_0
+ .bEndpointAddress = USB_HIP_OUT_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_OUT,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc intr_in_ep = {
+ .pPacketSize = &intr_mps,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_INTERRUPT,
+};
+
+static const struct usb_temp_endpoint_desc *eth_intr_endpoints[] = {
+ &intr_in_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc eth_control_interface = {
+ .ppEndpoints = eth_intr_endpoints,
+ .ppRawDesc = eth_control_if_desc,
+ .bInterfaceClass = UICLASS_CDC,
+ .bInterfaceSubClass = UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL,
+ .bInterfaceProtocol = 0,
+ .iInterface = ETH_CONTROL_INDEX,
+};
+
+static const struct usb_temp_endpoint_desc *eth_data_endpoints[] = {
+ &bulk_in_ep,
+ &bulk_out_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc eth_data_null_interface = {
+ .ppEndpoints = NULL, /* no endpoints */
+ .bInterfaceClass = UICLASS_CDC_DATA,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+ .iInterface = ETH_DATA_INDEX,
+};
+
+static const struct usb_temp_interface_desc eth_data_interface = {
+ .ppEndpoints = eth_data_endpoints,
+ .bInterfaceClass = UICLASS_CDC_DATA,
+ .bInterfaceSubClass = UISUBCLASS_DATA,
+ .bInterfaceProtocol = 0,
+ .iInterface = ETH_DATA_INDEX,
+ .isAltInterface = 1, /* this is an alternate setting */
+};
+
+static const struct usb_temp_interface_desc *eth_interfaces[] = {
+ &eth_control_interface,
+ &eth_data_null_interface,
+ &eth_data_interface,
+ NULL,
+};
+
+static const struct usb_temp_config_desc eth_config_desc = {
+ .ppIfaceDesc = eth_interfaces,
+ .bmAttributes = 0,
+ .bMaxPower = 0,
+ .iConfiguration = ETH_CONFIGURATION_INDEX,
+};
+
+static const struct usb_temp_config_desc *eth_configs[] = {
+ &eth_config_desc,
+ NULL,
+};
+
+struct usb_temp_device_desc usb_template_cdce = {
+ .getStringDesc = &eth_get_string_desc,
+ .ppConfigDesc = eth_configs,
+ .idVendor = ETH_DEFAULT_VENDOR_ID,
+ .idProduct = ETH_DEFAULT_PRODUCT_ID,
+ .bcdDevice = 0x0100,
+ .bDeviceClass = UDCLASS_COMM,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .iManufacturer = ETH_MANUFACTURER_INDEX,
+ .iProduct = ETH_PRODUCT_INDEX,
+ .iSerialNumber = ETH_SERIAL_NUMBER_INDEX,
+};
+
+/*------------------------------------------------------------------------*
+ * eth_get_string_desc
+ *
+ * Return values:
+ * NULL: Failure. No such string.
+ * Else: Success. Pointer to string descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+eth_get_string_desc(uint16_t lang_id, uint8_t string_index)
+{
+ static const void *ptr[ETH_MAX_INDEX] = {
+ [ETH_LANG_INDEX] = &usb_string_lang_en,
+ [ETH_MAC_INDEX] = &eth_mac,
+ [ETH_CONTROL_INDEX] = &eth_control,
+ [ETH_DATA_INDEX] = &eth_data,
+ [ETH_CONFIGURATION_INDEX] = &eth_configuration,
+ [ETH_MANUFACTURER_INDEX] = &eth_manufacturer,
+ [ETH_PRODUCT_INDEX] = &eth_product,
+ [ETH_SERIAL_NUMBER_INDEX] = &eth_serial_number,
+ };
+
+ if (string_index == 0) {
+ return (&usb_string_lang_en);
+ }
+ if (lang_id != 0x0409) {
+ return (NULL);
+ }
+ if (string_index < ETH_MAX_INDEX) {
+ return (ptr[string_index]);
+ }
+ return (NULL);
+}
+
+static void
+eth_init(void *arg __unused)
+{
+ struct sysctl_oid *parent;
+ char parent_name[3];
+
+ usb_make_str_desc(&eth_mac, sizeof(eth_mac),
+ ETH_DEFAULT_MAC);
+ usb_make_str_desc(&eth_control, sizeof(eth_control),
+ ETH_DEFAULT_CONTROL);
+ usb_make_str_desc(&eth_data, sizeof(eth_data),
+ ETH_DEFAULT_DATA);
+ usb_make_str_desc(&eth_configuration, sizeof(eth_configuration),
+ ETH_DEFAULT_CONFIG);
+ usb_make_str_desc(&eth_manufacturer, sizeof(eth_manufacturer),
+ ETH_DEFAULT_MANUFACTURER);
+ usb_make_str_desc(&eth_product, sizeof(eth_product),
+ ETH_DEFAULT_PRODUCT);
+ usb_make_str_desc(&eth_serial_number, sizeof(eth_serial_number),
+ ETH_DEFAULT_SERIAL_NUMBER);
+
+ snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_CDCE);
+ sysctl_ctx_init(&eth_ctx_list);
+
+ parent = SYSCTL_ADD_NODE(&eth_ctx_list,
+ SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO,
+ parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, "USB CDC Ethernet device side template");
+ SYSCTL_ADD_U16(&eth_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "vendor_id", CTLFLAG_RWTUN,
+ &usb_template_cdce.idVendor, 1, "Vendor identifier");
+ SYSCTL_ADD_U16(&eth_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product_id", CTLFLAG_RWTUN,
+ &usb_template_cdce.idProduct, 1, "Product identifier");
+ SYSCTL_ADD_PROC(&eth_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "mac", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &eth_mac, sizeof(eth_mac), usb_temp_sysctl,
+ "A", "MAC address string");
+#if 0
+ SYSCTL_ADD_PROC(&eth_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "control", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &eth_control, sizeof(eth_control), usb_temp_sysctl,
+ "A", "Control interface string");
+ SYSCTL_ADD_PROC(&eth_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "data", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &eth_data, sizeof(eth_data), usb_temp_sysctl,
+ "A", "Data interface string");
+ SYSCTL_ADD_PROC(&eth_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "configuration", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &eth_configuration, sizeof(eth_configuration), usb_temp_sysctl,
+ "A", "Configuration string");
+#endif
+ SYSCTL_ADD_PROC(&eth_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &eth_manufacturer, sizeof(eth_manufacturer), usb_temp_sysctl,
+ "A", "Manufacturer string");
+ SYSCTL_ADD_PROC(&eth_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &eth_product, sizeof(eth_product), usb_temp_sysctl,
+ "A", "Product string");
+ SYSCTL_ADD_PROC(&eth_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &eth_serial_number, sizeof(eth_serial_number), usb_temp_sysctl,
+ "A", "Serial number string");
+}
+
+static void
+eth_uninit(void *arg __unused)
+{
+
+ sysctl_ctx_free(&eth_ctx_list);
+}
+
+SYSINIT(eth_init, SI_SUB_LOCK, SI_ORDER_FIRST, eth_init, NULL);
+SYSUNINIT(eth_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, eth_uninit, NULL);
diff --git a/sys/dev/usb/template/usb_template_cdceem.c b/sys/dev/usb/template/usb_template_cdceem.c
new file mode 100644
index 000000000000..2412f8728b78
--- /dev/null
+++ b/sys/dev/usb/template/usb_template_cdceem.c
@@ -0,0 +1,262 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky <hselasky@FreeBSD.org>
+ * Copyright (c) 2018 The FreeBSD Foundation
+ * Copyright (c) 2019 Edward Tomasz Napierala <trasz@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the USB templates for an USB Mass Storage Device.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/template/usb_template.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+enum {
+ CDCEEM_LANG_INDEX,
+ CDCEEM_INTERFACE_INDEX,
+ CDCEEM_CONFIGURATION_INDEX,
+ CDCEEM_MANUFACTURER_INDEX,
+ CDCEEM_PRODUCT_INDEX,
+ CDCEEM_SERIAL_NUMBER_INDEX,
+ CDCEEM_MAX_INDEX,
+};
+
+#define CDCEEM_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR
+#define CDCEEM_DEFAULT_PRODUCT_ID 0x27df
+#define CDCEEM_DEFAULT_INTERFACE "USB CDC EEM Interface"
+#define CDCEEM_DEFAULT_CONFIGURATION "Default Config"
+#define CDCEEM_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER
+#define CDCEEM_DEFAULT_PRODUCT "CDC EEM"
+#define CDCEEM_DEFAULT_SERIAL_NUMBER "March 2008"
+
+static struct usb_string_descriptor cdceem_interface;
+static struct usb_string_descriptor cdceem_configuration;
+static struct usb_string_descriptor cdceem_manufacturer;
+static struct usb_string_descriptor cdceem_product;
+static struct usb_string_descriptor cdceem_serial_number;
+
+static struct sysctl_ctx_list cdceem_ctx_list;
+
+/* prototypes */
+
+static usb_temp_get_string_desc_t cdceem_get_string_desc;
+
+static const struct usb_temp_packet_size bulk_mps = {
+ .mps[USB_SPEED_FULL] = 64,
+ .mps[USB_SPEED_HIGH] = 512,
+};
+
+static const struct usb_temp_endpoint_desc bulk_in_ep = {
+ .pPacketSize = &bulk_mps,
+#ifdef USB_HIP_IN_EP_0
+ .bEndpointAddress = USB_HIP_IN_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_IN,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc bulk_out_ep = {
+ .pPacketSize = &bulk_mps,
+#ifdef USB_HIP_OUT_EP_0
+ .bEndpointAddress = USB_HIP_OUT_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_OUT,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc *cdceem_data_endpoints[] = {
+ &bulk_in_ep,
+ &bulk_out_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc cdceem_data_interface = {
+ .ppEndpoints = cdceem_data_endpoints,
+ .bInterfaceClass = UICLASS_CDC,
+ .bInterfaceSubClass = UISUBCLASS_ETHERNET_EMULATION_MODEL,
+ .bInterfaceProtocol = UIPROTO_CDC_EEM,
+ .iInterface = CDCEEM_INTERFACE_INDEX,
+};
+
+static const struct usb_temp_interface_desc *cdceem_interfaces[] = {
+ &cdceem_data_interface,
+ NULL,
+};
+
+static const struct usb_temp_config_desc cdceem_config_desc = {
+ .ppIfaceDesc = cdceem_interfaces,
+ .bmAttributes = 0,
+ .bMaxPower = 0,
+ .iConfiguration = CDCEEM_CONFIGURATION_INDEX,
+};
+
+static const struct usb_temp_config_desc *cdceem_configs[] = {
+ &cdceem_config_desc,
+ NULL,
+};
+
+struct usb_temp_device_desc usb_template_cdceem = {
+ .getStringDesc = &cdceem_get_string_desc,
+ .ppConfigDesc = cdceem_configs,
+ .idVendor = CDCEEM_DEFAULT_VENDOR_ID,
+ .idProduct = CDCEEM_DEFAULT_PRODUCT_ID,
+ .bcdDevice = 0x0100,
+ .bDeviceClass = UDCLASS_COMM,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .iManufacturer = CDCEEM_MANUFACTURER_INDEX,
+ .iProduct = CDCEEM_PRODUCT_INDEX,
+ .iSerialNumber = CDCEEM_SERIAL_NUMBER_INDEX,
+};
+
+/*------------------------------------------------------------------------*
+ * cdceem_get_string_desc
+ *
+ * Return values:
+ * NULL: Failure. No such string.
+ * Else: Success. Pointer to string descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+cdceem_get_string_desc(uint16_t lang_id, uint8_t string_index)
+{
+ static const void *ptr[CDCEEM_MAX_INDEX] = {
+ [CDCEEM_LANG_INDEX] = &usb_string_lang_en,
+ [CDCEEM_INTERFACE_INDEX] = &cdceem_interface,
+ [CDCEEM_CONFIGURATION_INDEX] = &cdceem_configuration,
+ [CDCEEM_MANUFACTURER_INDEX] = &cdceem_manufacturer,
+ [CDCEEM_PRODUCT_INDEX] = &cdceem_product,
+ [CDCEEM_SERIAL_NUMBER_INDEX] = &cdceem_serial_number,
+ };
+
+ if (string_index == 0) {
+ return (&usb_string_lang_en);
+ }
+ if (lang_id != 0x0409) {
+ return (NULL);
+ }
+ if (string_index < CDCEEM_MAX_INDEX) {
+ return (ptr[string_index]);
+ }
+ return (NULL);
+}
+
+static void
+cdceem_init(void *arg __unused)
+{
+ struct sysctl_oid *parent;
+ char parent_name[3];
+
+ usb_make_str_desc(&cdceem_interface, sizeof(cdceem_interface),
+ CDCEEM_DEFAULT_INTERFACE);
+ usb_make_str_desc(&cdceem_configuration, sizeof(cdceem_configuration),
+ CDCEEM_DEFAULT_CONFIGURATION);
+ usb_make_str_desc(&cdceem_manufacturer, sizeof(cdceem_manufacturer),
+ CDCEEM_DEFAULT_MANUFACTURER);
+ usb_make_str_desc(&cdceem_product, sizeof(cdceem_product),
+ CDCEEM_DEFAULT_PRODUCT);
+ usb_make_str_desc(&cdceem_serial_number, sizeof(cdceem_serial_number),
+ CDCEEM_DEFAULT_SERIAL_NUMBER);
+
+ snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_CDCEEM);
+ sysctl_ctx_init(&cdceem_ctx_list);
+
+ parent = SYSCTL_ADD_NODE(&cdceem_ctx_list,
+ SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO,
+ parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, "USB CDC EEM device side template");
+ SYSCTL_ADD_U16(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "vendor_id", CTLFLAG_RWTUN,
+ &usb_template_cdceem.idVendor, 1, "Vendor identifier");
+ SYSCTL_ADD_U16(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product_id", CTLFLAG_RWTUN,
+ &usb_template_cdceem.idProduct, 1, "Product identifier");
+#if 0
+ SYSCTL_ADD_PROC(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &cdceem_interface, sizeof(cdceem_interface), usb_temp_sysctl,
+ "A", "Interface string");
+ SYSCTL_ADD_PROC(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "configuration", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &cdceem_configuration, sizeof(cdceem_configuration), usb_temp_sysctl,
+ "A", "Configuration string");
+#endif
+ SYSCTL_ADD_PROC(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &cdceem_manufacturer, sizeof(cdceem_manufacturer), usb_temp_sysctl,
+ "A", "Manufacturer string");
+ SYSCTL_ADD_PROC(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &cdceem_product, sizeof(cdceem_product), usb_temp_sysctl,
+ "A", "Product string");
+ SYSCTL_ADD_PROC(&cdceem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &cdceem_serial_number, sizeof(cdceem_serial_number), usb_temp_sysctl,
+ "A", "Serial number string");
+}
+
+static void
+cdceem_uninit(void *arg __unused)
+{
+
+ sysctl_ctx_free(&cdceem_ctx_list);
+}
+
+SYSINIT(cdceem_init, SI_SUB_LOCK, SI_ORDER_FIRST, cdceem_init, NULL);
+SYSUNINIT(cdceem_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, cdceem_uninit, NULL);
diff --git a/sys/dev/usb/template/usb_template_kbd.c b/sys/dev/usb/template/usb_template_kbd.c
new file mode 100644
index 000000000000..6a88da623c92
--- /dev/null
+++ b/sys/dev/usb/template/usb_template_kbd.c
@@ -0,0 +1,292 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky
+ * Copyright (c) 2018 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the USB template for an USB Keyboard Device.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_cdc.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/template/usb_template.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+enum {
+ KBD_LANG_INDEX,
+ KBD_INTERFACE_INDEX,
+ KBD_MANUFACTURER_INDEX,
+ KBD_PRODUCT_INDEX,
+ KBD_SERIAL_NUMBER_INDEX,
+ KBD_MAX_INDEX,
+};
+
+#define KBD_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR
+#define KBD_DEFAULT_PRODUCT_ID 0x27db
+#define KBD_DEFAULT_INTERFACE "Keyboard Interface"
+#define KBD_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER
+#define KBD_DEFAULT_PRODUCT "Keyboard Test Device"
+#define KBD_DEFAULT_SERIAL_NUMBER "March 2008"
+
+static struct usb_string_descriptor kbd_interface;
+static struct usb_string_descriptor kbd_manufacturer;
+static struct usb_string_descriptor kbd_product;
+static struct usb_string_descriptor kbd_serial_number;
+
+static struct sysctl_ctx_list kbd_ctx_list;
+
+/* prototypes */
+
+static const struct usb_temp_packet_size keyboard_intr_mps = {
+ .mps[USB_SPEED_LOW] = 16,
+ .mps[USB_SPEED_FULL] = 16,
+ .mps[USB_SPEED_HIGH] = 16,
+};
+
+static const struct usb_temp_interval keyboard_intr_interval = {
+ .bInterval[USB_SPEED_LOW] = 2, /* 2 ms */
+ .bInterval[USB_SPEED_FULL] = 2, /* 2 ms */
+ .bInterval[USB_SPEED_HIGH] = 5, /* 2 ms */
+};
+
+/* The following HID descriptor was dumped from a HP keyboard. */
+
+static uint8_t keyboard_hid_descriptor[] = {
+ 0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07,
+ 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, 0x25, 0x01,
+ 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01,
+ 0x75, 0x08, 0x81, 0x01, 0x95, 0x03, 0x75, 0x01,
+ 0x05, 0x08, 0x19, 0x01, 0x29, 0x03, 0x91, 0x02,
+ 0x95, 0x05, 0x75, 0x01, 0x91, 0x01, 0x95, 0x06,
+ 0x75, 0x08, 0x15, 0x00, 0x26, 0xff, 0x00, 0x05,
+ 0x07, 0x19, 0x00, 0x2a, 0xff, 0x00, 0x81, 0x00,
+ 0xc0
+};
+
+static const struct usb_temp_endpoint_desc keyboard_ep_0 = {
+ .ppRawDesc = NULL, /* no raw descriptors */
+ .pPacketSize = &keyboard_intr_mps,
+ .pIntervals = &keyboard_intr_interval,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_INTERRUPT,
+};
+
+static const struct usb_temp_endpoint_desc *keyboard_endpoints[] = {
+ &keyboard_ep_0,
+ NULL,
+};
+
+static const uint8_t keyboard_raw_desc[] = {
+ 0x09, 0x21, 0x10, 0x01, 0x00, 0x01, 0x22, sizeof(keyboard_hid_descriptor),
+ 0x00
+};
+
+static const void *keyboard_iface_0_desc[] = {
+ keyboard_raw_desc,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc keyboard_iface_0 = {
+ .ppRawDesc = keyboard_iface_0_desc,
+ .ppEndpoints = keyboard_endpoints,
+ .bInterfaceClass = UICLASS_HID,
+ .bInterfaceSubClass = UISUBCLASS_BOOT,
+ .bInterfaceProtocol = UIPROTO_BOOT_KEYBOARD,
+ .iInterface = KBD_INTERFACE_INDEX,
+};
+
+static const struct usb_temp_interface_desc *keyboard_interfaces[] = {
+ &keyboard_iface_0,
+ NULL,
+};
+
+static const struct usb_temp_config_desc keyboard_config_desc = {
+ .ppIfaceDesc = keyboard_interfaces,
+ .bmAttributes = 0,
+ .bMaxPower = 0,
+ .iConfiguration = KBD_PRODUCT_INDEX,
+};
+
+static const struct usb_temp_config_desc *keyboard_configs[] = {
+ &keyboard_config_desc,
+ NULL,
+};
+
+static usb_temp_get_string_desc_t keyboard_get_string_desc;
+static usb_temp_get_vendor_desc_t keyboard_get_vendor_desc;
+
+struct usb_temp_device_desc usb_template_kbd = {
+ .getStringDesc = &keyboard_get_string_desc,
+ .getVendorDesc = &keyboard_get_vendor_desc,
+ .ppConfigDesc = keyboard_configs,
+ .idVendor = KBD_DEFAULT_VENDOR_ID,
+ .idProduct = KBD_DEFAULT_PRODUCT_ID,
+ .bcdDevice = 0x0100,
+ .bDeviceClass = UDCLASS_COMM,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .iManufacturer = KBD_MANUFACTURER_INDEX,
+ .iProduct = KBD_PRODUCT_INDEX,
+ .iSerialNumber = KBD_SERIAL_NUMBER_INDEX,
+};
+
+/*------------------------------------------------------------------------*
+ * keyboard_get_vendor_desc
+ *
+ * Return values:
+ * NULL: Failure. No such vendor descriptor.
+ * Else: Success. Pointer to vendor descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+keyboard_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen)
+{
+ if ((req->bmRequestType == 0x81) && (req->bRequest == 0x06) &&
+ (req->wValue[0] == 0x00) && (req->wValue[1] == 0x22) &&
+ (req->wIndex[1] == 0) && (req->wIndex[0] == 0)) {
+ *plen = sizeof(keyboard_hid_descriptor);
+ return (keyboard_hid_descriptor);
+ }
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * keyboard_get_string_desc
+ *
+ * Return values:
+ * NULL: Failure. No such string.
+ * Else: Success. Pointer to string descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+keyboard_get_string_desc(uint16_t lang_id, uint8_t string_index)
+{
+ static const void *ptr[KBD_MAX_INDEX] = {
+ [KBD_LANG_INDEX] = &usb_string_lang_en,
+ [KBD_INTERFACE_INDEX] = &kbd_interface,
+ [KBD_MANUFACTURER_INDEX] = &kbd_manufacturer,
+ [KBD_PRODUCT_INDEX] = &kbd_product,
+ [KBD_SERIAL_NUMBER_INDEX] = &kbd_serial_number,
+ };
+
+ if (string_index == 0) {
+ return (&usb_string_lang_en);
+ }
+ if (lang_id != 0x0409) {
+ return (NULL);
+ }
+ if (string_index < KBD_MAX_INDEX) {
+ return (ptr[string_index]);
+ }
+ return (NULL);
+}
+
+static void
+kbd_init(void *arg __unused)
+{
+ struct sysctl_oid *parent;
+ char parent_name[3];
+
+ usb_make_str_desc(&kbd_interface, sizeof(kbd_interface),
+ KBD_DEFAULT_INTERFACE);
+ usb_make_str_desc(&kbd_manufacturer, sizeof(kbd_manufacturer),
+ KBD_DEFAULT_MANUFACTURER);
+ usb_make_str_desc(&kbd_product, sizeof(kbd_product),
+ KBD_DEFAULT_PRODUCT);
+ usb_make_str_desc(&kbd_serial_number, sizeof(kbd_serial_number),
+ KBD_DEFAULT_SERIAL_NUMBER);
+
+ snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_KBD);
+ sysctl_ctx_init(&kbd_ctx_list);
+
+ parent = SYSCTL_ADD_NODE(&kbd_ctx_list,
+ SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO,
+ parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, "USB Keyboard device side template");
+ SYSCTL_ADD_U16(&kbd_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "vendor_id", CTLFLAG_RWTUN,
+ &usb_template_kbd.idVendor, 1, "Vendor identifier");
+ SYSCTL_ADD_U16(&kbd_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product_id", CTLFLAG_RWTUN,
+ &usb_template_kbd.idProduct, 1, "Product identifier");
+#if 0
+ SYSCTL_ADD_PROC(&kbd_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &kbd_interface, sizeof(kbd_interface), usb_temp_sysctl,
+ "A", "Interface string");
+#endif
+ SYSCTL_ADD_PROC(&kbd_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &kbd_manufacturer, sizeof(kbd_manufacturer), usb_temp_sysctl,
+ "A", "Manufacturer string");
+ SYSCTL_ADD_PROC(&kbd_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &kbd_product, sizeof(kbd_product), usb_temp_sysctl,
+ "A", "Product string");
+ SYSCTL_ADD_PROC(&kbd_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &kbd_serial_number, sizeof(kbd_serial_number), usb_temp_sysctl,
+ "A", "Serial number string");
+}
+
+static void
+kbd_uninit(void *arg __unused)
+{
+
+ sysctl_ctx_free(&kbd_ctx_list);
+}
+
+SYSINIT(kbd_init, SI_SUB_LOCK, SI_ORDER_FIRST, kbd_init, NULL);
+SYSUNINIT(kbd_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, kbd_uninit, NULL);
diff --git a/sys/dev/usb/template/usb_template_midi.c b/sys/dev/usb/template/usb_template_midi.c
new file mode 100644
index 000000000000..92374bb7af3a
--- /dev/null
+++ b/sys/dev/usb/template/usb_template_midi.c
@@ -0,0 +1,313 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2015 Hans Petter Selasky
+ * Copyright (c) 2018 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the USB template for an USB MIDI Device.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/template/usb_template.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+enum {
+ MIDI_LANG_INDEX,
+ MIDI_INTERFACE_INDEX,
+ MIDI_MANUFACTURER_INDEX,
+ MIDI_PRODUCT_INDEX,
+ MIDI_SERIAL_NUMBER_INDEX,
+ MIDI_MAX_INDEX,
+};
+
+#define MIDI_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR
+#define MIDI_DEFAULT_PRODUCT_ID 0x27de
+#define MIDI_DEFAULT_INTERFACE "MIDI interface"
+#define MIDI_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER
+#define MIDI_DEFAULT_PRODUCT "MIDI Test Device"
+#define MIDI_DEFAULT_SERIAL_NUMBER "March 2008"
+
+static struct usb_string_descriptor midi_interface;
+static struct usb_string_descriptor midi_manufacturer;
+static struct usb_string_descriptor midi_product;
+static struct usb_string_descriptor midi_serial_number;
+
+static struct sysctl_ctx_list midi_ctx_list;
+
+/* prototypes */
+
+static const uint8_t midi_desc_raw_0[9] = {
+ 0x09, 0x24, 0x01, 0x00, 0x01, 0x09, 0x00, 0x01, 0x01
+};
+
+static const void *midi_descs_0[] = {
+ &midi_desc_raw_0,
+ NULL
+};
+
+static const struct usb_temp_interface_desc midi_iface_0 = {
+ .ppEndpoints = NULL, /* no endpoints */
+ .ppRawDesc = midi_descs_0,
+ .bInterfaceClass = UICLASS_AUDIO,
+ .bInterfaceSubClass = UISUBCLASS_AUDIOCONTROL,
+ .bInterfaceProtocol = 0,
+ .iInterface = MIDI_INTERFACE_INDEX,
+};
+
+static const struct usb_temp_packet_size midi_mps = {
+ .mps[USB_SPEED_LOW] = 8,
+ .mps[USB_SPEED_FULL] = 64,
+ .mps[USB_SPEED_HIGH] = 512,
+};
+
+static const uint8_t midi_desc_raw_7[5] = {
+ 0x05, 0x25, 0x01, 0x01, 0x01
+};
+
+static const void *midi_descs_2[] = {
+ &midi_desc_raw_7,
+ NULL
+};
+
+static const struct usb_temp_endpoint_desc midi_bulk_out_ep = {
+ .ppRawDesc = midi_descs_2,
+ .pPacketSize = &midi_mps,
+ .bEndpointAddress = UE_DIR_OUT,
+ .bmAttributes = UE_BULK,
+};
+
+static const uint8_t midi_desc_raw_6[5] = {
+ 0x05, 0x25, 0x01, 0x01, 0x03,
+};
+
+static const void *midi_descs_3[] = {
+ &midi_desc_raw_6,
+ NULL
+};
+
+static const struct usb_temp_endpoint_desc midi_bulk_in_ep = {
+ .ppRawDesc = midi_descs_3,
+ .pPacketSize = &midi_mps,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc *midi_iface_1_ep[] = {
+ &midi_bulk_out_ep,
+ &midi_bulk_in_ep,
+ NULL,
+};
+
+static const uint8_t midi_desc_raw_1[7] = {
+ 0x07, 0x24, 0x01, 0x00, 0x01, /* wTotalLength: */ 0x41, 0x00
+};
+
+static const uint8_t midi_desc_raw_2[6] = {
+ 0x06, 0x24, 0x02, 0x01, 0x01, 0x00
+};
+
+static const uint8_t midi_desc_raw_3[6] = {
+ 0x06, 0x24, 0x02, 0x02, 0x02, 0x00
+};
+
+static const uint8_t midi_desc_raw_4[9] = {
+ 0x09, 0x24, 0x03, 0x01, 0x03, 0x01, 0x02, 0x01, 0x00
+};
+
+static const uint8_t midi_desc_raw_5[9] = {
+ 0x09, 0x24, 0x03, 0x02, 0x04, 0x01, 0x01, 0x01, 0x00
+};
+
+static const void *midi_descs_1[] = {
+ &midi_desc_raw_1,
+ &midi_desc_raw_2,
+ &midi_desc_raw_3,
+ &midi_desc_raw_4,
+ &midi_desc_raw_5,
+ NULL
+};
+
+static const struct usb_temp_interface_desc midi_iface_1 = {
+ .ppRawDesc = midi_descs_1,
+ .ppEndpoints = midi_iface_1_ep,
+ .bInterfaceClass = UICLASS_AUDIO,
+ .bInterfaceSubClass = UISUBCLASS_MIDISTREAM,
+ .bInterfaceProtocol = 0,
+ .iInterface = MIDI_INTERFACE_INDEX,
+};
+
+static const struct usb_temp_interface_desc *midi_interfaces[] = {
+ &midi_iface_0,
+ &midi_iface_1,
+ NULL,
+};
+
+static const struct usb_temp_config_desc midi_config_desc = {
+ .ppIfaceDesc = midi_interfaces,
+ .bmAttributes = 0,
+ .bMaxPower = 0,
+ .iConfiguration = MIDI_PRODUCT_INDEX,
+};
+
+static const struct usb_temp_config_desc *midi_configs[] = {
+ &midi_config_desc,
+ NULL,
+};
+
+static usb_temp_get_string_desc_t midi_get_string_desc;
+
+struct usb_temp_device_desc usb_template_midi = {
+ .getStringDesc = &midi_get_string_desc,
+ .ppConfigDesc = midi_configs,
+ .idVendor = MIDI_DEFAULT_VENDOR_ID,
+ .idProduct = MIDI_DEFAULT_PRODUCT_ID,
+ .bcdDevice = 0x0100,
+ .bDeviceClass = 0,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .iManufacturer = MIDI_MANUFACTURER_INDEX,
+ .iProduct = MIDI_PRODUCT_INDEX,
+ .iSerialNumber = MIDI_SERIAL_NUMBER_INDEX,
+};
+
+/*------------------------------------------------------------------------*
+ * midi_get_string_desc
+ *
+ * Return values:
+ * NULL: Failure. No such string.
+ * Else: Success. Pointer to string descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+midi_get_string_desc(uint16_t lang_id, uint8_t string_index)
+{
+ static const void *ptr[MIDI_MAX_INDEX] = {
+ [MIDI_LANG_INDEX] = &usb_string_lang_en,
+ [MIDI_INTERFACE_INDEX] = &midi_interface,
+ [MIDI_MANUFACTURER_INDEX] = &midi_manufacturer,
+ [MIDI_PRODUCT_INDEX] = &midi_product,
+ [MIDI_SERIAL_NUMBER_INDEX] = &midi_serial_number,
+ };
+
+ if (string_index == 0) {
+ return (&usb_string_lang_en);
+ }
+ if (lang_id != 0x0409) {
+ return (NULL);
+ }
+ if (string_index < MIDI_MAX_INDEX) {
+ return (ptr[string_index]);
+ }
+ return (NULL);
+}
+
+static void
+midi_init(void *arg __unused)
+{
+ struct sysctl_oid *parent;
+ char parent_name[3];
+
+ usb_make_str_desc(&midi_interface, sizeof(midi_interface),
+ MIDI_DEFAULT_INTERFACE);
+ usb_make_str_desc(&midi_manufacturer, sizeof(midi_manufacturer),
+ MIDI_DEFAULT_MANUFACTURER);
+ usb_make_str_desc(&midi_product, sizeof(midi_product),
+ MIDI_DEFAULT_PRODUCT);
+ usb_make_str_desc(&midi_serial_number, sizeof(midi_serial_number),
+ MIDI_DEFAULT_SERIAL_NUMBER);
+
+ snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_MIDI);
+ sysctl_ctx_init(&midi_ctx_list);
+
+ parent = SYSCTL_ADD_NODE(&midi_ctx_list,
+ SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO,
+ parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, "USB MIDI device side template");
+ SYSCTL_ADD_U16(&midi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "vendor_id", CTLFLAG_RWTUN,
+ &usb_template_midi.idVendor, 1, "Vendor identifier");
+ SYSCTL_ADD_U16(&midi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product_id", CTLFLAG_RWTUN,
+ &usb_template_midi.idProduct, 1, "Product identifier");
+#if 0
+ SYSCTL_ADD_PROC(&midi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &midi_interface, sizeof(midi_interface), usb_temp_sysctl,
+ "A", "Interface string");
+#endif
+ SYSCTL_ADD_PROC(&midi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &midi_manufacturer, sizeof(midi_manufacturer), usb_temp_sysctl,
+ "A", "Manufacturer string");
+ SYSCTL_ADD_PROC(&midi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &midi_product, sizeof(midi_product), usb_temp_sysctl,
+ "A", "Product string");
+ SYSCTL_ADD_PROC(&midi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &midi_serial_number, sizeof(midi_serial_number), usb_temp_sysctl,
+ "A", "Serial number string");
+}
+
+static void
+midi_uninit(void *arg __unused)
+{
+
+ sysctl_ctx_free(&midi_ctx_list);
+}
+
+SYSINIT(midi_init, SI_SUB_LOCK, SI_ORDER_FIRST, midi_init, NULL);
+SYSUNINIT(midi_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, midi_uninit, NULL);
diff --git a/sys/dev/usb/template/usb_template_modem.c b/sys/dev/usb/template/usb_template_modem.c
new file mode 100644
index 000000000000..7c4418e633ce
--- /dev/null
+++ b/sys/dev/usb/template/usb_template_modem.c
@@ -0,0 +1,327 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky
+ * Copyright (c) 2018 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the USB template for an USB Modem Device.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_cdc.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/template/usb_template.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+enum {
+ MODEM_LANG_INDEX,
+ MODEM_INTERFACE_INDEX,
+ MODEM_MANUFACTURER_INDEX,
+ MODEM_PRODUCT_INDEX,
+ MODEM_SERIAL_NUMBER_INDEX,
+ MODEM_MAX_INDEX,
+};
+
+#define MODEM_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR
+#define MODEM_DEFAULT_PRODUCT_ID 0x27dd
+#define MODEM_DEFAULT_INTERFACE "Virtual serial port"
+#define MODEM_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER
+#define MODEM_DEFAULT_PRODUCT "Virtual serial port"
+/*
+ * The reason for this being called like this is that OSX
+ * derives the device node name from it, resulting in a somewhat
+ * user-friendly "/dev/cu.usbmodemFreeBSD1". And yes, the "1"
+ * needs to be there, otherwise OSX will mangle it.
+ */
+#define MODEM_DEFAULT_SERIAL_NUMBER "FreeBSD1"
+
+static struct usb_string_descriptor modem_interface;
+static struct usb_string_descriptor modem_manufacturer;
+static struct usb_string_descriptor modem_product;
+static struct usb_string_descriptor modem_serial_number;
+
+static struct sysctl_ctx_list modem_ctx_list;
+
+#define MODEM_IFACE_0 0
+#define MODEM_IFACE_1 1
+
+/* prototypes */
+
+static const struct usb_temp_packet_size modem_bulk_mps = {
+ .mps[USB_SPEED_LOW] = 8,
+ .mps[USB_SPEED_FULL] = 64,
+ .mps[USB_SPEED_HIGH] = 512,
+};
+
+static const struct usb_temp_packet_size modem_intr_mps = {
+ .mps[USB_SPEED_LOW] = 8,
+ .mps[USB_SPEED_FULL] = 8,
+ .mps[USB_SPEED_HIGH] = 8,
+};
+
+static const struct usb_temp_interval modem_intr_interval = {
+ .bInterval[USB_SPEED_LOW] = 8, /* 8ms */
+ .bInterval[USB_SPEED_FULL] = 8, /* 8ms */
+ .bInterval[USB_SPEED_HIGH] = 7, /* 8ms */
+};
+
+static const struct usb_temp_endpoint_desc modem_ep_0 = {
+ .pPacketSize = &modem_intr_mps,
+ .pIntervals = &modem_intr_interval,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_INTERRUPT,
+};
+
+static const struct usb_temp_endpoint_desc modem_ep_1 = {
+ .pPacketSize = &modem_bulk_mps,
+ .bEndpointAddress = UE_DIR_OUT,
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc modem_ep_2 = {
+ .pPacketSize = &modem_bulk_mps,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc *modem_iface_0_ep[] = {
+ &modem_ep_0,
+ NULL,
+};
+
+static const struct usb_temp_endpoint_desc *modem_iface_1_ep[] = {
+ &modem_ep_1,
+ &modem_ep_2,
+ NULL,
+};
+
+static const uint8_t modem_raw_desc_0[] = {
+ 0x05, 0x24, 0x00, 0x10, 0x01
+};
+
+static const uint8_t modem_raw_desc_1[] = {
+ 0x05, 0x24, 0x06, MODEM_IFACE_0, MODEM_IFACE_1
+};
+
+static const uint8_t modem_raw_desc_2[] = {
+ 0x05, 0x24, 0x01, 0x03, MODEM_IFACE_1
+};
+
+static const uint8_t modem_raw_desc_3[] = {
+ 0x04, 0x24, 0x02, 0x07
+};
+
+static const void *modem_iface_0_desc[] = {
+ &modem_raw_desc_0,
+ &modem_raw_desc_1,
+ &modem_raw_desc_2,
+ &modem_raw_desc_3,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc modem_iface_0 = {
+ .ppRawDesc = modem_iface_0_desc,
+ .ppEndpoints = modem_iface_0_ep,
+ .bInterfaceClass = UICLASS_CDC,
+ .bInterfaceSubClass = UISUBCLASS_ABSTRACT_CONTROL_MODEL,
+ .bInterfaceProtocol = UIPROTO_CDC_NONE,
+ .iInterface = MODEM_INTERFACE_INDEX,
+};
+
+static const struct usb_temp_interface_desc modem_iface_1 = {
+ .ppEndpoints = modem_iface_1_ep,
+ .bInterfaceClass = UICLASS_CDC_DATA,
+ .bInterfaceSubClass = UISUBCLASS_DATA,
+ .bInterfaceProtocol = UIPROTO_CDC_NONE,
+ .iInterface = MODEM_INTERFACE_INDEX,
+};
+
+static const struct usb_temp_interface_desc *modem_interfaces[] = {
+ &modem_iface_0,
+ &modem_iface_1,
+ NULL,
+};
+
+static const struct usb_temp_config_desc modem_config_desc = {
+ .ppIfaceDesc = modem_interfaces,
+ .bmAttributes = 0,
+ .bMaxPower = 0,
+ .iConfiguration = MODEM_PRODUCT_INDEX,
+};
+
+static const struct usb_temp_config_desc *modem_configs[] = {
+ &modem_config_desc,
+ NULL,
+};
+
+static usb_temp_get_string_desc_t modem_get_string_desc;
+static usb_temp_get_vendor_desc_t modem_get_vendor_desc;
+
+struct usb_temp_device_desc usb_template_modem = {
+ .getStringDesc = &modem_get_string_desc,
+ .getVendorDesc = &modem_get_vendor_desc,
+ .ppConfigDesc = modem_configs,
+ .idVendor = MODEM_DEFAULT_VENDOR_ID,
+ .idProduct = MODEM_DEFAULT_PRODUCT_ID,
+ .bcdDevice = 0x0100,
+ .bDeviceClass = UDCLASS_COMM,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .iManufacturer = MODEM_MANUFACTURER_INDEX,
+ .iProduct = MODEM_PRODUCT_INDEX,
+ .iSerialNumber = MODEM_SERIAL_NUMBER_INDEX,
+};
+
+/*------------------------------------------------------------------------*
+ * modem_get_vendor_desc
+ *
+ * Return values:
+ * NULL: Failure. No such vendor descriptor.
+ * Else: Success. Pointer to vendor descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+modem_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen)
+{
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * modem_get_string_desc
+ *
+ * Return values:
+ * NULL: Failure. No such string.
+ * Else: Success. Pointer to string descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+modem_get_string_desc(uint16_t lang_id, uint8_t string_index)
+{
+ static const void *ptr[MODEM_MAX_INDEX] = {
+ [MODEM_LANG_INDEX] = &usb_string_lang_en,
+ [MODEM_INTERFACE_INDEX] = &modem_interface,
+ [MODEM_MANUFACTURER_INDEX] = &modem_manufacturer,
+ [MODEM_PRODUCT_INDEX] = &modem_product,
+ [MODEM_SERIAL_NUMBER_INDEX] = &modem_serial_number,
+ };
+
+ if (string_index == 0) {
+ return (&usb_string_lang_en);
+ }
+ if (lang_id != 0x0409) {
+ return (NULL);
+ }
+ if (string_index < MODEM_MAX_INDEX) {
+ return (ptr[string_index]);
+ }
+ return (NULL);
+}
+
+static void
+modem_init(void *arg __unused)
+{
+ struct sysctl_oid *parent;
+ char parent_name[3];
+
+ usb_make_str_desc(&modem_interface, sizeof(modem_interface),
+ MODEM_DEFAULT_INTERFACE);
+ usb_make_str_desc(&modem_manufacturer, sizeof(modem_manufacturer),
+ MODEM_DEFAULT_MANUFACTURER);
+ usb_make_str_desc(&modem_product, sizeof(modem_product),
+ MODEM_DEFAULT_PRODUCT);
+ usb_make_str_desc(&modem_serial_number, sizeof(modem_serial_number),
+ MODEM_DEFAULT_SERIAL_NUMBER);
+
+ snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_MODEM);
+ sysctl_ctx_init(&modem_ctx_list);
+
+ parent = SYSCTL_ADD_NODE(&modem_ctx_list,
+ SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO,
+ parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, "Virtual serial port device side template");
+ SYSCTL_ADD_U16(&modem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "vendor_id", CTLFLAG_RWTUN,
+ &usb_template_modem.idVendor, 1, "Vendor identifier");
+ SYSCTL_ADD_U16(&modem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product_id", CTLFLAG_RWTUN,
+ &usb_template_modem.idProduct, 1, "Product identifier");
+#if 0
+ SYSCTL_ADD_PROC(&modem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "keyboard", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &modem_interface, sizeof(modem_interface), usb_temp_sysctl,
+ "A", "Interface string");
+#endif
+ SYSCTL_ADD_PROC(&modem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &modem_manufacturer, sizeof(modem_manufacturer), usb_temp_sysctl,
+ "A", "Manufacturer string");
+ SYSCTL_ADD_PROC(&modem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &modem_product, sizeof(modem_product), usb_temp_sysctl,
+ "A", "Product string");
+ SYSCTL_ADD_PROC(&modem_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &modem_serial_number, sizeof(modem_serial_number), usb_temp_sysctl,
+ "A", "Serial number string");
+}
+
+static void
+modem_uninit(void *arg __unused)
+{
+
+ sysctl_ctx_free(&modem_ctx_list);
+}
+
+SYSINIT(modem_init, SI_SUB_LOCK, SI_ORDER_FIRST, modem_init, NULL);
+SYSUNINIT(modem_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, modem_uninit, NULL);
diff --git a/sys/dev/usb/template/usb_template_mouse.c b/sys/dev/usb/template/usb_template_mouse.c
new file mode 100644
index 000000000000..a6cda143a6ea
--- /dev/null
+++ b/sys/dev/usb/template/usb_template_mouse.c
@@ -0,0 +1,290 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2010 Hans Petter Selasky
+ * Copyright (c) 2018 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the USB template for an USB Mouse Device.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_cdc.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/template/usb_template.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+enum {
+ MOUSE_LANG_INDEX,
+ MOUSE_INTERFACE_INDEX,
+ MOUSE_MANUFACTURER_INDEX,
+ MOUSE_PRODUCT_INDEX,
+ MOUSE_SERIAL_NUMBER_INDEX,
+ MOUSE_MAX_INDEX,
+};
+
+#define MOUSE_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR
+#define MOUSE_DEFAULT_PRODUCT_ID 0x27da
+#define MOUSE_DEFAULT_INTERFACE "Mouse interface"
+#define MOUSE_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER
+#define MOUSE_DEFAULT_PRODUCT "Mouse Test Interface"
+#define MOUSE_DEFAULT_SERIAL_NUMBER "March 2008"
+
+static struct usb_string_descriptor mouse_interface;
+static struct usb_string_descriptor mouse_manufacturer;
+static struct usb_string_descriptor mouse_product;
+static struct usb_string_descriptor mouse_serial_number;
+
+static struct sysctl_ctx_list mouse_ctx_list;
+
+/* prototypes */
+
+/* The following HID descriptor was dumped from a HP mouse. */
+
+static uint8_t mouse_hid_descriptor[] = {
+ 0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x09, 0x01,
+ 0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x03,
+ 0x15, 0x00, 0x25, 0x01, 0x95, 0x03, 0x75, 0x01,
+ 0x81, 0x02, 0x95, 0x05, 0x81, 0x03, 0x05, 0x01,
+ 0x09, 0x30, 0x09, 0x31, 0x09, 0x38, 0x15, 0x81,
+ 0x25, 0x7f, 0x75, 0x08, 0x95, 0x03, 0x81, 0x06,
+ 0xc0, 0xc0
+};
+
+static const struct usb_temp_packet_size mouse_intr_mps = {
+ .mps[USB_SPEED_LOW] = 8,
+ .mps[USB_SPEED_FULL] = 8,
+ .mps[USB_SPEED_HIGH] = 8,
+};
+
+static const struct usb_temp_interval mouse_intr_interval = {
+ .bInterval[USB_SPEED_LOW] = 2, /* 2ms */
+ .bInterval[USB_SPEED_FULL] = 2, /* 2ms */
+ .bInterval[USB_SPEED_HIGH] = 5, /* 2ms */
+};
+
+static const struct usb_temp_endpoint_desc mouse_ep_0 = {
+ .ppRawDesc = NULL, /* no raw descriptors */
+ .pPacketSize = &mouse_intr_mps,
+ .pIntervals = &mouse_intr_interval,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_INTERRUPT,
+};
+
+static const struct usb_temp_endpoint_desc *mouse_endpoints[] = {
+ &mouse_ep_0,
+ NULL,
+};
+
+static const uint8_t mouse_raw_desc[] = {
+ 0x09, 0x21, 0x10, 0x01, 0x00, 0x01, 0x22, sizeof(mouse_hid_descriptor),
+ 0x00
+};
+
+static const void *mouse_iface_0_desc[] = {
+ mouse_raw_desc,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc mouse_iface_0 = {
+ .ppRawDesc = mouse_iface_0_desc,
+ .ppEndpoints = mouse_endpoints,
+ .bInterfaceClass = UICLASS_HID,
+ .bInterfaceSubClass = UISUBCLASS_BOOT,
+ .bInterfaceProtocol = UIPROTO_MOUSE,
+ .iInterface = MOUSE_INTERFACE_INDEX,
+};
+
+static const struct usb_temp_interface_desc *mouse_interfaces[] = {
+ &mouse_iface_0,
+ NULL,
+};
+
+static const struct usb_temp_config_desc mouse_config_desc = {
+ .ppIfaceDesc = mouse_interfaces,
+ .bmAttributes = 0,
+ .bMaxPower = 0,
+ .iConfiguration = MOUSE_INTERFACE_INDEX,
+};
+
+static const struct usb_temp_config_desc *mouse_configs[] = {
+ &mouse_config_desc,
+ NULL,
+};
+
+static usb_temp_get_string_desc_t mouse_get_string_desc;
+static usb_temp_get_vendor_desc_t mouse_get_vendor_desc;
+
+struct usb_temp_device_desc usb_template_mouse = {
+ .getStringDesc = &mouse_get_string_desc,
+ .getVendorDesc = &mouse_get_vendor_desc,
+ .ppConfigDesc = mouse_configs,
+ .idVendor = MOUSE_DEFAULT_VENDOR_ID,
+ .idProduct = MOUSE_DEFAULT_PRODUCT_ID,
+ .bcdDevice = 0x0100,
+ .bDeviceClass = UDCLASS_COMM,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .iManufacturer = MOUSE_MANUFACTURER_INDEX,
+ .iProduct = MOUSE_PRODUCT_INDEX,
+ .iSerialNumber = MOUSE_SERIAL_NUMBER_INDEX,
+};
+
+/*------------------------------------------------------------------------*
+ * mouse_get_vendor_desc
+ *
+ * Return values:
+ * NULL: Failure. No such vendor descriptor.
+ * Else: Success. Pointer to vendor descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+mouse_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen)
+{
+ if ((req->bmRequestType == 0x81) && (req->bRequest == 0x06) &&
+ (req->wValue[0] == 0x00) && (req->wValue[1] == 0x22) &&
+ (req->wIndex[1] == 0) && (req->wIndex[0] == 0)) {
+ *plen = sizeof(mouse_hid_descriptor);
+ return (mouse_hid_descriptor);
+ }
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * mouse_get_string_desc
+ *
+ * Return values:
+ * NULL: Failure. No such string.
+ * Else: Success. Pointer to string descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+mouse_get_string_desc(uint16_t lang_id, uint8_t string_index)
+{
+ static const void *ptr[MOUSE_MAX_INDEX] = {
+ [MOUSE_LANG_INDEX] = &usb_string_lang_en,
+ [MOUSE_INTERFACE_INDEX] = &mouse_interface,
+ [MOUSE_MANUFACTURER_INDEX] = &mouse_manufacturer,
+ [MOUSE_PRODUCT_INDEX] = &mouse_product,
+ [MOUSE_SERIAL_NUMBER_INDEX] = &mouse_serial_number,
+ };
+
+ if (string_index == 0) {
+ return (&usb_string_lang_en);
+ }
+ if (lang_id != 0x0409) {
+ return (NULL);
+ }
+ if (string_index < MOUSE_MAX_INDEX) {
+ return (ptr[string_index]);
+ }
+ return (NULL);
+}
+
+static void
+mouse_init(void *arg __unused)
+{
+ struct sysctl_oid *parent;
+ char parent_name[3];
+
+ usb_make_str_desc(&mouse_interface, sizeof(mouse_interface),
+ MOUSE_DEFAULT_INTERFACE);
+ usb_make_str_desc(&mouse_manufacturer, sizeof(mouse_manufacturer),
+ MOUSE_DEFAULT_MANUFACTURER);
+ usb_make_str_desc(&mouse_product, sizeof(mouse_product),
+ MOUSE_DEFAULT_PRODUCT);
+ usb_make_str_desc(&mouse_serial_number, sizeof(mouse_serial_number),
+ MOUSE_DEFAULT_SERIAL_NUMBER);
+
+ snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_MOUSE);
+ sysctl_ctx_init(&mouse_ctx_list);
+
+ parent = SYSCTL_ADD_NODE(&mouse_ctx_list,
+ SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO,
+ parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, "USB Mouse device side template");
+ SYSCTL_ADD_U16(&mouse_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "vendor_id", CTLFLAG_RWTUN,
+ &usb_template_mouse.idVendor, 1, "Vendor identifier");
+ SYSCTL_ADD_U16(&mouse_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product_id", CTLFLAG_RWTUN,
+ &usb_template_mouse.idProduct, 1, "Product identifier");
+#if 0
+ SYSCTL_ADD_PROC(&mouse_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &mouse_interface, sizeof(mouse_interface), usb_temp_sysctl,
+ "A", "Interface string");
+#endif
+ SYSCTL_ADD_PROC(&mouse_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &mouse_manufacturer, sizeof(mouse_manufacturer), usb_temp_sysctl,
+ "A", "Manufacturer string");
+ SYSCTL_ADD_PROC(&mouse_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &mouse_product, sizeof(mouse_product), usb_temp_sysctl,
+ "A", "Product string");
+ SYSCTL_ADD_PROC(&mouse_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &mouse_serial_number, sizeof(mouse_serial_number), usb_temp_sysctl,
+ "A", "Serial number string");
+}
+
+static void
+mouse_uninit(void *arg __unused)
+{
+
+ sysctl_ctx_free(&mouse_ctx_list);
+}
+
+SYSINIT(mouse_init, SI_SUB_LOCK, SI_ORDER_FIRST, mouse_init, NULL);
+SYSUNINIT(mouse_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, mouse_uninit, NULL);
diff --git a/sys/dev/usb/template/usb_template_msc.c b/sys/dev/usb/template/usb_template_msc.c
new file mode 100644
index 000000000000..705077b6e890
--- /dev/null
+++ b/sys/dev/usb/template/usb_template_msc.c
@@ -0,0 +1,261 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky <hselasky@FreeBSD.org>
+ * Copyright (c) 2018 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the USB templates for an USB Mass Storage Device.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/template/usb_template.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+enum {
+ MSC_LANG_INDEX,
+ MSC_INTERFACE_INDEX,
+ MSC_CONFIGURATION_INDEX,
+ MSC_MANUFACTURER_INDEX,
+ MSC_PRODUCT_INDEX,
+ MSC_SERIAL_NUMBER_INDEX,
+ MSC_MAX_INDEX,
+};
+
+#define MSC_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR
+#define MSC_DEFAULT_PRODUCT_ID 0x27df
+#define MSC_DEFAULT_INTERFACE "USB Mass Storage Interface"
+#define MSC_DEFAULT_CONFIGURATION "Default Config"
+#define MSC_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER
+#define MSC_DEFAULT_PRODUCT "USB Memory Stick"
+#define MSC_DEFAULT_SERIAL_NUMBER "March 2008"
+
+static struct usb_string_descriptor msc_interface;
+static struct usb_string_descriptor msc_configuration;
+static struct usb_string_descriptor msc_manufacturer;
+static struct usb_string_descriptor msc_product;
+static struct usb_string_descriptor msc_serial_number;
+
+static struct sysctl_ctx_list msc_ctx_list;
+
+/* prototypes */
+
+static usb_temp_get_string_desc_t msc_get_string_desc;
+
+static const struct usb_temp_packet_size bulk_mps = {
+ .mps[USB_SPEED_FULL] = 64,
+ .mps[USB_SPEED_HIGH] = 512,
+};
+
+static const struct usb_temp_endpoint_desc bulk_in_ep = {
+ .pPacketSize = &bulk_mps,
+#ifdef USB_HIP_IN_EP_0
+ .bEndpointAddress = USB_HIP_IN_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_IN,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc bulk_out_ep = {
+ .pPacketSize = &bulk_mps,
+#ifdef USB_HIP_OUT_EP_0
+ .bEndpointAddress = USB_HIP_OUT_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_OUT,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc *msc_data_endpoints[] = {
+ &bulk_in_ep,
+ &bulk_out_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc msc_data_interface = {
+ .ppEndpoints = msc_data_endpoints,
+ .bInterfaceClass = UICLASS_MASS,
+ .bInterfaceSubClass = UISUBCLASS_SCSI,
+ .bInterfaceProtocol = UIPROTO_MASS_BBB,
+ .iInterface = MSC_INTERFACE_INDEX,
+};
+
+static const struct usb_temp_interface_desc *msc_interfaces[] = {
+ &msc_data_interface,
+ NULL,
+};
+
+static const struct usb_temp_config_desc msc_config_desc = {
+ .ppIfaceDesc = msc_interfaces,
+ .bmAttributes = 0,
+ .bMaxPower = 0,
+ .iConfiguration = MSC_CONFIGURATION_INDEX,
+};
+
+static const struct usb_temp_config_desc *msc_configs[] = {
+ &msc_config_desc,
+ NULL,
+};
+
+struct usb_temp_device_desc usb_template_msc = {
+ .getStringDesc = &msc_get_string_desc,
+ .ppConfigDesc = msc_configs,
+ .idVendor = MSC_DEFAULT_VENDOR_ID,
+ .idProduct = MSC_DEFAULT_PRODUCT_ID,
+ .bcdDevice = 0x0100,
+ .bDeviceClass = UDCLASS_COMM,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .iManufacturer = MSC_MANUFACTURER_INDEX,
+ .iProduct = MSC_PRODUCT_INDEX,
+ .iSerialNumber = MSC_SERIAL_NUMBER_INDEX,
+};
+
+/*------------------------------------------------------------------------*
+ * msc_get_string_desc
+ *
+ * Return values:
+ * NULL: Failure. No such string.
+ * Else: Success. Pointer to string descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+msc_get_string_desc(uint16_t lang_id, uint8_t string_index)
+{
+ static const void *ptr[MSC_MAX_INDEX] = {
+ [MSC_LANG_INDEX] = &usb_string_lang_en,
+ [MSC_INTERFACE_INDEX] = &msc_interface,
+ [MSC_CONFIGURATION_INDEX] = &msc_configuration,
+ [MSC_MANUFACTURER_INDEX] = &msc_manufacturer,
+ [MSC_PRODUCT_INDEX] = &msc_product,
+ [MSC_SERIAL_NUMBER_INDEX] = &msc_serial_number,
+ };
+
+ if (string_index == 0) {
+ return (&usb_string_lang_en);
+ }
+ if (lang_id != 0x0409) {
+ return (NULL);
+ }
+ if (string_index < MSC_MAX_INDEX) {
+ return (ptr[string_index]);
+ }
+ return (NULL);
+}
+
+static void
+msc_init(void *arg __unused)
+{
+ struct sysctl_oid *parent;
+ char parent_name[3];
+
+ usb_make_str_desc(&msc_interface, sizeof(msc_interface),
+ MSC_DEFAULT_INTERFACE);
+ usb_make_str_desc(&msc_configuration, sizeof(msc_configuration),
+ MSC_DEFAULT_CONFIGURATION);
+ usb_make_str_desc(&msc_manufacturer, sizeof(msc_manufacturer),
+ MSC_DEFAULT_MANUFACTURER);
+ usb_make_str_desc(&msc_product, sizeof(msc_product),
+ MSC_DEFAULT_PRODUCT);
+ usb_make_str_desc(&msc_serial_number, sizeof(msc_serial_number),
+ MSC_DEFAULT_SERIAL_NUMBER);
+
+ snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_MSC);
+ sysctl_ctx_init(&msc_ctx_list);
+
+ parent = SYSCTL_ADD_NODE(&msc_ctx_list,
+ SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO,
+ parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, "USB Mass Storage device side template");
+ SYSCTL_ADD_U16(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "vendor_id", CTLFLAG_RWTUN,
+ &usb_template_msc.idVendor, 1, "Vendor identifier");
+ SYSCTL_ADD_U16(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product_id", CTLFLAG_RWTUN,
+ &usb_template_msc.idProduct, 1, "Product identifier");
+#if 0
+ SYSCTL_ADD_PROC(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &msc_interface, sizeof(msc_interface), usb_temp_sysctl,
+ "A", "Interface string");
+ SYSCTL_ADD_PROC(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "configuration", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &msc_configuration, sizeof(msc_configuration), usb_temp_sysctl,
+ "A", "Configuration string");
+#endif
+ SYSCTL_ADD_PROC(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &msc_manufacturer, sizeof(msc_manufacturer), usb_temp_sysctl,
+ "A", "Manufacturer string");
+ SYSCTL_ADD_PROC(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &msc_product, sizeof(msc_product), usb_temp_sysctl,
+ "A", "Product string");
+ SYSCTL_ADD_PROC(&msc_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &msc_serial_number, sizeof(msc_serial_number), usb_temp_sysctl,
+ "A", "Serial number string");
+}
+
+static void
+msc_uninit(void *arg __unused)
+{
+
+ sysctl_ctx_free(&msc_ctx_list);
+}
+
+SYSINIT(msc_init, SI_SUB_LOCK, SI_ORDER_FIRST, msc_init, NULL);
+SYSUNINIT(msc_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, msc_uninit, NULL);
diff --git a/sys/dev/usb/template/usb_template_mtp.c b/sys/dev/usb/template/usb_template_mtp.c
new file mode 100644
index 000000000000..ef1112e3ff37
--- /dev/null
+++ b/sys/dev/usb/template/usb_template_mtp.c
@@ -0,0 +1,328 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky <hselasky@FreeBSD.org>
+ * Copyright (c) 2018 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the USB templates for an USB Media Transfer
+ * Protocol device.
+ *
+ * NOTE: It is common practice that MTP devices use some dummy
+ * descriptor cludges to be automatically detected by the host
+ * operating system. These descriptors are documented in the LibMTP
+ * library at sourceforge.net. The alternative is to supply the host
+ * operating system the VID and PID of your device.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/template/usb_template.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#define MTP_BREQUEST 0x08
+
+enum {
+ MTP_LANG_INDEX,
+ MTP_INTERFACE_INDEX,
+ MTP_CONFIGURATION_INDEX,
+ MTP_MANUFACTURER_INDEX,
+ MTP_PRODUCT_INDEX,
+ MTP_SERIAL_NUMBER_INDEX,
+ MTP_MAX_INDEX,
+};
+
+#define MTP_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR
+#define MTP_DEFAULT_PRODUCT_ID 0x27e2
+#define MTP_DEFAULT_INTERFACE "USB MTP Interface"
+#define MTP_DEFAULT_CONFIGURATION "Default Config"
+#define MTP_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER
+#define MTP_DEFAULT_PRODUCT "USB MTP"
+#define MTP_DEFAULT_SERIAL_NUMBER "June 2008"
+
+static struct usb_string_descriptor mtp_interface;
+static struct usb_string_descriptor mtp_configuration;
+static struct usb_string_descriptor mtp_manufacturer;
+static struct usb_string_descriptor mtp_product;
+static struct usb_string_descriptor mtp_serial_number;
+
+static struct sysctl_ctx_list mtp_ctx_list;
+
+/* prototypes */
+
+static usb_temp_get_string_desc_t mtp_get_string_desc;
+static usb_temp_get_vendor_desc_t mtp_get_vendor_desc;
+
+static const struct usb_temp_packet_size bulk_mps = {
+ .mps[USB_SPEED_FULL] = 64,
+ .mps[USB_SPEED_HIGH] = 512,
+};
+
+static const struct usb_temp_packet_size intr_mps = {
+ .mps[USB_SPEED_FULL] = 64,
+ .mps[USB_SPEED_HIGH] = 64,
+};
+
+static const struct usb_temp_endpoint_desc bulk_out_ep = {
+ .pPacketSize = &bulk_mps,
+#ifdef USB_HIP_OUT_EP_0
+ .bEndpointAddress = USB_HIP_OUT_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_OUT,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc intr_in_ep = {
+ .pPacketSize = &intr_mps,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_INTERRUPT,
+};
+
+static const struct usb_temp_endpoint_desc bulk_in_ep = {
+ .pPacketSize = &bulk_mps,
+#ifdef USB_HIP_IN_EP_0
+ .bEndpointAddress = USB_HIP_IN_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_IN,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc *mtp_data_endpoints[] = {
+ &bulk_in_ep,
+ &bulk_out_ep,
+ &intr_in_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc mtp_data_interface = {
+ .ppEndpoints = mtp_data_endpoints,
+ .bInterfaceClass = UICLASS_IMAGE,
+ .bInterfaceSubClass = UISUBCLASS_SIC, /* Still Image Class */
+ .bInterfaceProtocol = 1, /* PIMA 15740 */
+ .iInterface = MTP_INTERFACE_INDEX,
+};
+
+static const struct usb_temp_interface_desc *mtp_interfaces[] = {
+ &mtp_data_interface,
+ NULL,
+};
+
+static const struct usb_temp_config_desc mtp_config_desc = {
+ .ppIfaceDesc = mtp_interfaces,
+ .bmAttributes = 0,
+ .bMaxPower = 0,
+ .iConfiguration = MTP_CONFIGURATION_INDEX,
+};
+
+static const struct usb_temp_config_desc *mtp_configs[] = {
+ &mtp_config_desc,
+ NULL,
+};
+
+struct usb_temp_device_desc usb_template_mtp = {
+ .getStringDesc = &mtp_get_string_desc,
+ .getVendorDesc = &mtp_get_vendor_desc,
+ .ppConfigDesc = mtp_configs,
+ .idVendor = MTP_DEFAULT_VENDOR_ID,
+ .idProduct = MTP_DEFAULT_PRODUCT_ID,
+ .bcdDevice = 0x0100,
+ .bDeviceClass = 0,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .iManufacturer = MTP_MANUFACTURER_INDEX,
+ .iProduct = MTP_PRODUCT_INDEX,
+ .iSerialNumber = MTP_SERIAL_NUMBER_INDEX,
+};
+
+/*------------------------------------------------------------------------*
+ * mtp_get_vendor_desc
+ *
+ * Return values:
+ * NULL: Failure. No such vendor descriptor.
+ * Else: Success. Pointer to vendor descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+mtp_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen)
+{
+ static const uint8_t dummy_desc[0x28] = {
+ 0x28, 0, 0, 0, 0, 1, 4, 0,
+ 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0x4D, 0x54, 0x50, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+
+ if ((req->bmRequestType == UT_READ_VENDOR_DEVICE) &&
+ (req->bRequest == MTP_BREQUEST) && (req->wValue[0] == 0) &&
+ (req->wValue[1] == 0) && (req->wIndex[1] == 0) &&
+ ((req->wIndex[0] == 4) || (req->wIndex[0] == 5))) {
+ /*
+ * By returning this descriptor LibMTP will
+ * automatically pickup our device.
+ */
+ return (dummy_desc);
+ }
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * mtp_get_string_desc
+ *
+ * Return values:
+ * NULL: Failure. No such string.
+ * Else: Success. Pointer to string descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+mtp_get_string_desc(uint16_t lang_id, uint8_t string_index)
+{
+ static const void *ptr[MTP_MAX_INDEX] = {
+ [MTP_LANG_INDEX] = &usb_string_lang_en,
+ [MTP_INTERFACE_INDEX] = &mtp_interface,
+ [MTP_CONFIGURATION_INDEX] = &mtp_configuration,
+ [MTP_MANUFACTURER_INDEX] = &mtp_manufacturer,
+ [MTP_PRODUCT_INDEX] = &mtp_product,
+ [MTP_SERIAL_NUMBER_INDEX] = &mtp_serial_number,
+ };
+
+ static const uint8_t dummy_desc[0x12] = {
+ 0x12, 0x03, 0x4D, 0x00, 0x53, 0x00, 0x46, 0x00,
+ 0x54, 0x00, 0x31, 0x00, 0x30, 0x00, 0x30, 0x00,
+ MTP_BREQUEST, 0x00,
+ };
+
+ if (string_index == 0xEE) {
+ /*
+ * By returning this string LibMTP will automatically
+ * pickup our device.
+ */
+ return (dummy_desc);
+ }
+ if (string_index == 0) {
+ return (&usb_string_lang_en);
+ }
+ if (lang_id != 0x0409) {
+ return (NULL);
+ }
+ if (string_index < MTP_MAX_INDEX) {
+ return (ptr[string_index]);
+ }
+ return (NULL);
+}
+
+static void
+mtp_init(void *arg __unused)
+{
+ struct sysctl_oid *parent;
+ char parent_name[3];
+
+ usb_make_str_desc(&mtp_interface, sizeof(mtp_interface),
+ MTP_DEFAULT_INTERFACE);
+ usb_make_str_desc(&mtp_configuration, sizeof(mtp_configuration),
+ MTP_DEFAULT_CONFIGURATION);
+ usb_make_str_desc(&mtp_manufacturer, sizeof(mtp_manufacturer),
+ MTP_DEFAULT_MANUFACTURER);
+ usb_make_str_desc(&mtp_product, sizeof(mtp_product),
+ MTP_DEFAULT_PRODUCT);
+ usb_make_str_desc(&mtp_serial_number, sizeof(mtp_serial_number),
+ MTP_DEFAULT_SERIAL_NUMBER);
+
+ snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_MTP);
+ sysctl_ctx_init(&mtp_ctx_list);
+
+ parent = SYSCTL_ADD_NODE(&mtp_ctx_list,
+ SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO,
+ parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, "USB Media Transfer Protocol device side template");
+ SYSCTL_ADD_U16(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "vendor_id", CTLFLAG_RWTUN,
+ &usb_template_mtp.idVendor, 1, "Vendor identifier");
+ SYSCTL_ADD_U16(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product_id", CTLFLAG_RWTUN,
+ &usb_template_mtp.idProduct, 1, "Product identifier");
+#if 0
+ SYSCTL_ADD_PROC(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &mtp_interface, sizeof(mtp_interface), usb_temp_sysctl,
+ "A", "Interface string");
+ SYSCTL_ADD_PROC(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "configuration", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &mtp_configuration, sizeof(mtp_configuration), usb_temp_sysctl,
+ "A", "Configuration string");
+#endif
+ SYSCTL_ADD_PROC(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &mtp_manufacturer, sizeof(mtp_manufacturer), usb_temp_sysctl,
+ "A", "Manufacturer string");
+ SYSCTL_ADD_PROC(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &mtp_product, sizeof(mtp_product), usb_temp_sysctl,
+ "A", "Product string");
+ SYSCTL_ADD_PROC(&mtp_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &mtp_serial_number, sizeof(mtp_serial_number), usb_temp_sysctl,
+ "A", "Serial number string");
+}
+
+static void
+mtp_uninit(void *arg __unused)
+{
+
+ sysctl_ctx_free(&mtp_ctx_list);
+}
+
+SYSINIT(mtp_init, SI_SUB_LOCK, SI_ORDER_FIRST, mtp_init, NULL);
+SYSUNINIT(mtp_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, mtp_uninit, NULL);
diff --git a/sys/dev/usb/template/usb_template_multi.c b/sys/dev/usb/template/usb_template_multi.c
new file mode 100644
index 000000000000..be36e5ea70df
--- /dev/null
+++ b/sys/dev/usb/template/usb_template_multi.c
@@ -0,0 +1,514 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2015 Ruslan Bukin <br@bsdpad.com>
+ * Copyright (c) 2018 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by SRI International and the University of
+ * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
+ * ("CTSRD"), as part of the DARPA CRASH research programme.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * USB template for CDC ACM (serial), CDC ECM (network), and CDC MSC (storage).
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_cdc.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/template/usb_template.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#define MODEM_IFACE_0 0
+#define MODEM_IFACE_1 1
+
+enum {
+ MULTI_LANG_INDEX,
+ MULTI_MODEM_INDEX,
+ MULTI_ETH_MAC_INDEX,
+ MULTI_ETH_CONTROL_INDEX,
+ MULTI_ETH_DATA_INDEX,
+ MULTI_STORAGE_INDEX,
+ MULTI_CONFIGURATION_INDEX,
+ MULTI_MANUFACTURER_INDEX,
+ MULTI_PRODUCT_INDEX,
+ MULTI_SERIAL_NUMBER_INDEX,
+ MULTI_MAX_INDEX,
+};
+
+#define MULTI_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR
+#define MULTI_DEFAULT_PRODUCT_ID 0x05dc
+#define MULTI_DEFAULT_MODEM "Virtual serial port"
+#define MULTI_DEFAULT_ETH_MAC "2A02030405060789AB"
+#define MULTI_DEFAULT_ETH_CONTROL "Ethernet Comm Interface"
+#define MULTI_DEFAULT_ETH_DATA "Ethernet Data Interface"
+#define MULTI_DEFAULT_STORAGE "Mass Storage Interface"
+#define MULTI_DEFAULT_CONFIGURATION "Default configuration"
+#define MULTI_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER
+#define MULTI_DEFAULT_PRODUCT "Multifunction Device"
+/*
+ * The reason for this being called like this is that OSX
+ * derives the device node name from it, resulting in a somewhat
+ * user-friendly "/dev/cu.usbmodemFreeBSD1". And yes, the "1"
+ * needs to be there, otherwise OSX will mangle it.
+ */
+#define MULTI_DEFAULT_SERIAL_NUMBER "FreeBSD1"
+
+static struct usb_string_descriptor multi_modem;
+static struct usb_string_descriptor multi_eth_mac;
+static struct usb_string_descriptor multi_eth_control;
+static struct usb_string_descriptor multi_eth_data;
+static struct usb_string_descriptor multi_storage;
+static struct usb_string_descriptor multi_configuration;
+static struct usb_string_descriptor multi_manufacturer;
+static struct usb_string_descriptor multi_product;
+static struct usb_string_descriptor multi_serial_number;
+
+static struct sysctl_ctx_list multi_ctx_list;
+
+/* prototypes */
+
+static usb_temp_get_string_desc_t multi_get_string_desc;
+
+static const struct usb_cdc_union_descriptor eth_union_desc = {
+ .bLength = sizeof(eth_union_desc),
+ .bDescriptorType = UDESC_CS_INTERFACE,
+ .bDescriptorSubtype = UDESCSUB_CDC_UNION,
+ .bMasterInterface = 0, /* this is automatically updated */
+ .bSlaveInterface[0] = 1, /* this is automatically updated */
+};
+
+static const struct usb_cdc_header_descriptor eth_header_desc = {
+ .bLength = sizeof(eth_header_desc),
+ .bDescriptorType = UDESC_CS_INTERFACE,
+ .bDescriptorSubtype = UDESCSUB_CDC_HEADER,
+ .bcdCDC[0] = 0x10,
+ .bcdCDC[1] = 0x01,
+};
+
+static const struct usb_cdc_ethernet_descriptor eth_enf_desc = {
+ .bLength = sizeof(eth_enf_desc),
+ .bDescriptorType = UDESC_CS_INTERFACE,
+ .bDescriptorSubtype = UDESCSUB_CDC_ENF,
+ .iMacAddress = MULTI_ETH_MAC_INDEX,
+ .bmEthernetStatistics = {0, 0, 0, 0},
+ .wMaxSegmentSize = {0xEA, 0x05},/* 1514 bytes */
+ .wNumberMCFilters = {0, 0},
+ .bNumberPowerFilters = 0,
+};
+
+static const void *eth_control_if_desc[] = {
+ &eth_union_desc,
+ &eth_header_desc,
+ &eth_enf_desc,
+ NULL,
+};
+
+static const struct usb_temp_packet_size bulk_mps = {
+ .mps[USB_SPEED_FULL] = 64,
+ .mps[USB_SPEED_HIGH] = 512,
+};
+
+static const struct usb_temp_packet_size intr_mps = {
+ .mps[USB_SPEED_FULL] = 8,
+ .mps[USB_SPEED_HIGH] = 8,
+};
+
+static const struct usb_temp_endpoint_desc bulk_in_ep = {
+ .pPacketSize = &bulk_mps,
+#ifdef USB_HIP_IN_EP_0
+ .bEndpointAddress = USB_HIP_IN_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_IN,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc bulk_out_ep = {
+ .pPacketSize = &bulk_mps,
+#ifdef USB_HIP_OUT_EP_0
+ .bEndpointAddress = USB_HIP_OUT_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_OUT,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc intr_in_ep = {
+ .pPacketSize = &intr_mps,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_INTERRUPT,
+};
+
+static const struct usb_temp_endpoint_desc *eth_intr_endpoints[] = {
+ &intr_in_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc eth_control_interface = {
+ .ppEndpoints = eth_intr_endpoints,
+ .ppRawDesc = eth_control_if_desc,
+ .bInterfaceClass = UICLASS_CDC,
+ .bInterfaceSubClass = UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL,
+ .bInterfaceProtocol = UIPROTO_CDC_NONE,
+ .iInterface = MULTI_ETH_CONTROL_INDEX,
+};
+
+static const struct usb_temp_endpoint_desc *eth_data_endpoints[] = {
+ &bulk_in_ep,
+ &bulk_out_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc eth_data_null_interface = {
+ .ppEndpoints = NULL, /* no endpoints */
+ .bInterfaceClass = UICLASS_CDC_DATA,
+ .bInterfaceSubClass = UISUBCLASS_DATA,
+ .bInterfaceProtocol = 0,
+ .iInterface = MULTI_ETH_DATA_INDEX,
+};
+
+static const struct usb_temp_interface_desc eth_data_interface = {
+ .ppEndpoints = eth_data_endpoints,
+ .bInterfaceClass = UICLASS_CDC_DATA,
+ .bInterfaceSubClass = UISUBCLASS_DATA,
+ .bInterfaceProtocol = 0,
+ .iInterface = MULTI_ETH_DATA_INDEX,
+ .isAltInterface = 1, /* this is an alternate setting */
+};
+
+static const struct usb_temp_packet_size modem_bulk_mps = {
+ .mps[USB_SPEED_LOW] = 8,
+ .mps[USB_SPEED_FULL] = 64,
+ .mps[USB_SPEED_HIGH] = 512,
+};
+
+static const struct usb_temp_packet_size modem_intr_mps = {
+ .mps[USB_SPEED_LOW] = 8,
+ .mps[USB_SPEED_FULL] = 8,
+ .mps[USB_SPEED_HIGH] = 8,
+};
+
+static const struct usb_temp_interval modem_intr_interval = {
+ .bInterval[USB_SPEED_LOW] = 8, /* 8ms */
+ .bInterval[USB_SPEED_FULL] = 8, /* 8ms */
+ .bInterval[USB_SPEED_HIGH] = 7, /* 8ms */
+};
+
+static const struct usb_temp_endpoint_desc modem_ep_0 = {
+ .pPacketSize = &modem_intr_mps,
+ .pIntervals = &modem_intr_interval,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_INTERRUPT,
+};
+
+static const struct usb_temp_endpoint_desc modem_ep_1 = {
+ .pPacketSize = &modem_bulk_mps,
+ .bEndpointAddress = UE_DIR_OUT,
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc modem_ep_2 = {
+ .pPacketSize = &modem_bulk_mps,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc *modem_iface_0_ep[] = {
+ &modem_ep_0,
+ NULL,
+};
+
+static const struct usb_temp_endpoint_desc *modem_iface_1_ep[] = {
+ &modem_ep_1,
+ &modem_ep_2,
+ NULL,
+};
+
+static const uint8_t modem_raw_desc_0[] = {
+ 0x05, 0x24, 0x00, 0x10, 0x01
+};
+
+static const uint8_t modem_raw_desc_1[] = {
+ 0x05, 0x24, 0x06, MODEM_IFACE_0, MODEM_IFACE_1
+};
+
+static const uint8_t modem_raw_desc_2[] = {
+ 0x05, 0x24, 0x01, 0x03, MODEM_IFACE_1
+};
+
+static const uint8_t modem_raw_desc_3[] = {
+ 0x04, 0x24, 0x02, 0x07
+};
+
+static const void *modem_iface_0_desc[] = {
+ &modem_raw_desc_0,
+ &modem_raw_desc_1,
+ &modem_raw_desc_2,
+ &modem_raw_desc_3,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc modem_iface_0 = {
+ .ppRawDesc = modem_iface_0_desc,
+ .ppEndpoints = modem_iface_0_ep,
+ .bInterfaceClass = UICLASS_CDC,
+ .bInterfaceSubClass = UISUBCLASS_ABSTRACT_CONTROL_MODEL,
+ .bInterfaceProtocol = UIPROTO_CDC_NONE,
+ .iInterface = MULTI_MODEM_INDEX,
+};
+
+static const struct usb_temp_interface_desc modem_iface_1 = {
+ .ppEndpoints = modem_iface_1_ep,
+ .bInterfaceClass = UICLASS_CDC_DATA,
+ .bInterfaceSubClass = UISUBCLASS_DATA,
+ .bInterfaceProtocol = 0,
+ .iInterface = MULTI_MODEM_INDEX,
+};
+
+static const struct usb_temp_packet_size msc_bulk_mps = {
+ .mps[USB_SPEED_FULL] = 64,
+ .mps[USB_SPEED_HIGH] = 512,
+};
+
+static const struct usb_temp_endpoint_desc msc_bulk_in_ep = {
+ .pPacketSize = &msc_bulk_mps,
+#ifdef USB_HIP_IN_EP_0
+ .bEndpointAddress = USB_HIP_IN_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_IN,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc msc_bulk_out_ep = {
+ .pPacketSize = &msc_bulk_mps,
+#ifdef USB_HIP_OUT_EP_0
+ .bEndpointAddress = USB_HIP_OUT_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_OUT,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc *msc_data_endpoints[] = {
+ &msc_bulk_in_ep,
+ &msc_bulk_out_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc msc_data_interface = {
+ .ppEndpoints = msc_data_endpoints,
+ .bInterfaceClass = UICLASS_MASS,
+ .bInterfaceSubClass = UISUBCLASS_SCSI,
+ .bInterfaceProtocol = UIPROTO_MASS_BBB,
+ .iInterface = MULTI_STORAGE_INDEX,
+};
+
+static const struct usb_temp_interface_desc *multi_interfaces[] = {
+ &modem_iface_0,
+ &modem_iface_1,
+ &eth_control_interface,
+ &eth_data_null_interface,
+ &eth_data_interface,
+ &msc_data_interface,
+ NULL,
+};
+
+static const struct usb_temp_config_desc multi_config_desc = {
+ .ppIfaceDesc = multi_interfaces,
+ .bmAttributes = 0,
+ .bMaxPower = 0,
+ .iConfiguration = MULTI_CONFIGURATION_INDEX,
+};
+static const struct usb_temp_config_desc *multi_configs[] = {
+ &multi_config_desc,
+ NULL,
+};
+
+struct usb_temp_device_desc usb_template_multi = {
+ .getStringDesc = &multi_get_string_desc,
+ .ppConfigDesc = multi_configs,
+ .idVendor = MULTI_DEFAULT_VENDOR_ID,
+ .idProduct = MULTI_DEFAULT_PRODUCT_ID,
+ .bcdDevice = 0x0100,
+ .bDeviceClass = UDCLASS_IN_INTERFACE,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .iManufacturer = MULTI_MANUFACTURER_INDEX,
+ .iProduct = MULTI_PRODUCT_INDEX,
+ .iSerialNumber = MULTI_SERIAL_NUMBER_INDEX,
+};
+
+/*------------------------------------------------------------------------*
+ * multi_get_string_desc
+ *
+ * Return values:
+ * NULL: Failure. No such string.
+ * Else: Success. Pointer to string descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+multi_get_string_desc(uint16_t lang_id, uint8_t string_index)
+{
+ static const void *ptr[MULTI_MAX_INDEX] = {
+ [MULTI_LANG_INDEX] = &usb_string_lang_en,
+ [MULTI_MODEM_INDEX] = &multi_modem,
+ [MULTI_ETH_MAC_INDEX] = &multi_eth_mac,
+ [MULTI_ETH_CONTROL_INDEX] = &multi_eth_control,
+ [MULTI_ETH_DATA_INDEX] = &multi_eth_data,
+ [MULTI_STORAGE_INDEX] = &multi_storage,
+ [MULTI_CONFIGURATION_INDEX] = &multi_configuration,
+ [MULTI_MANUFACTURER_INDEX] = &multi_manufacturer,
+ [MULTI_PRODUCT_INDEX] = &multi_product,
+ [MULTI_SERIAL_NUMBER_INDEX] = &multi_serial_number,
+ };
+
+ if (string_index == 0) {
+ return (&usb_string_lang_en);
+ }
+ if (lang_id != 0x0409) {
+ return (NULL);
+ }
+ if (string_index < MULTI_MAX_INDEX) {
+ return (ptr[string_index]);
+ }
+ return (NULL);
+}
+
+static void
+multi_init(void *arg __unused)
+{
+ struct sysctl_oid *parent;
+ char parent_name[3];
+
+ usb_make_str_desc(&multi_modem, sizeof(multi_modem),
+ MULTI_DEFAULT_MODEM);
+ usb_make_str_desc(&multi_eth_mac, sizeof(multi_eth_mac),
+ MULTI_DEFAULT_ETH_MAC);
+ usb_make_str_desc(&multi_eth_control, sizeof(multi_eth_control),
+ MULTI_DEFAULT_ETH_CONTROL);
+ usb_make_str_desc(&multi_eth_data, sizeof(multi_eth_data),
+ MULTI_DEFAULT_ETH_DATA);
+ usb_make_str_desc(&multi_storage, sizeof(multi_storage),
+ MULTI_DEFAULT_STORAGE);
+ usb_make_str_desc(&multi_configuration, sizeof(multi_configuration),
+ MULTI_DEFAULT_CONFIGURATION);
+ usb_make_str_desc(&multi_manufacturer, sizeof(multi_manufacturer),
+ MULTI_DEFAULT_MANUFACTURER);
+ usb_make_str_desc(&multi_product, sizeof(multi_product),
+ MULTI_DEFAULT_PRODUCT);
+ usb_make_str_desc(&multi_serial_number, sizeof(multi_serial_number),
+ MULTI_DEFAULT_SERIAL_NUMBER);
+
+ snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_MULTI);
+ sysctl_ctx_init(&multi_ctx_list);
+
+ parent = SYSCTL_ADD_NODE(&multi_ctx_list,
+ SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO,
+ parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, "USB Multifunction device side template");
+ SYSCTL_ADD_U16(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "vendor_id", CTLFLAG_RWTUN,
+ &usb_template_multi.idVendor, 1, "Vendor identifier");
+ SYSCTL_ADD_U16(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product_id", CTLFLAG_RWTUN,
+ &usb_template_multi.idProduct, 1, "Product identifier");
+ SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "eth_mac", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &multi_eth_mac, sizeof(multi_eth_mac), usb_temp_sysctl,
+ "A", "Ethernet MAC address string");
+#if 0
+ SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "modem", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &multi_modem, sizeof(multi_modem), usb_temp_sysctl,
+ "A", "Modem interface string");
+ SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "eth_control", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &multi_eth_control, sizeof(multi_eth_data), usb_temp_sysctl,
+ "A", "Ethernet control interface string");
+ SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "eth_data", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &multi_eth_data, sizeof(multi_eth_data), usb_temp_sysctl,
+ "A", "Ethernet data interface string");
+ SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "interface", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &multi_storage, sizeof(multi_storage), usb_temp_sysctl,
+ "A", "Storage interface string");
+ SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "configuration", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &multi_configuration, sizeof(multi_configuration), usb_temp_sysctl,
+ "A", "Configuration string");
+#endif
+ SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &multi_manufacturer, sizeof(multi_manufacturer), usb_temp_sysctl,
+ "A", "Manufacturer string");
+ SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &multi_product, sizeof(multi_product), usb_temp_sysctl,
+ "A", "Product string");
+ SYSCTL_ADD_PROC(&multi_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &multi_serial_number, sizeof(multi_serial_number), usb_temp_sysctl,
+ "A", "Serial number string");
+}
+
+static void
+multi_uninit(void *arg __unused)
+{
+
+ sysctl_ctx_free(&multi_ctx_list);
+}
+
+SYSINIT(multi_init, SI_SUB_LOCK, SI_ORDER_FIRST, multi_init, NULL);
+SYSUNINIT(multi_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, multi_uninit, NULL);
diff --git a/sys/dev/usb/template/usb_template_phone.c b/sys/dev/usb/template/usb_template_phone.c
new file mode 100644
index 000000000000..4e77bd86ef76
--- /dev/null
+++ b/sys/dev/usb/template/usb_template_phone.c
@@ -0,0 +1,503 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2014 Hans Petter Selasky
+ * Copyright (c) 2018 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains the USB template for an USB phone device.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_cdc.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/template/usb_template.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+enum {
+ PHONE_LANG_INDEX,
+ PHONE_MIXER_INDEX,
+ PHONE_RECORD_INDEX,
+ PHONE_PLAYBACK_INDEX,
+ PHONE_HID_INDEX,
+ PHONE_MANUFACTURER_INDEX,
+ PHONE_PRODUCT_INDEX,
+ PHONE_SERIAL_NUMBER_INDEX,
+ PHONE_MAX_INDEX,
+};
+
+#define PHONE_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR
+#define PHONE_DEFAULT_PRODUCT_ID 0x05dc
+#define PHONE_DEFAULT_MIXER "Mixer interface"
+#define PHONE_DEFAULT_RECORD "Record interface"
+#define PHONE_DEFAULT_PLAYBACK "Playback interface"
+#define PHONE_DEFAULT_HID "HID interface"
+#define PHONE_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER
+#define PHONE_DEFAULT_PRODUCT "USB Phone Device"
+#define PHONE_DEFAULT_SERIAL_NUMBER "March 2008"
+
+static struct usb_string_descriptor phone_mixer;
+static struct usb_string_descriptor phone_record;
+static struct usb_string_descriptor phone_playback;
+static struct usb_string_descriptor phone_hid;
+static struct usb_string_descriptor phone_manufacturer;
+static struct usb_string_descriptor phone_product;
+static struct usb_string_descriptor phone_serial_number;
+
+static struct sysctl_ctx_list phone_ctx_list;
+
+/* prototypes */
+
+/*
+ * Phone Mixer description structures
+ *
+ * Some of the phone descriptors were dumped from no longer in
+ * production Yealink VOIP USB phone adapter:
+ */
+static uint8_t phone_hid_descriptor[] = {
+ 0x05, 0x0b, 0x09, 0x01, 0xa1, 0x01, 0x05, 0x09,
+ 0x19, 0x01, 0x29, 0x3f, 0x15, 0x00, 0x25, 0x01,
+ 0x75, 0x01, 0x95, 0x80, 0x81, 0x00, 0x05, 0x08,
+ 0x19, 0x01, 0x29, 0x10, 0x15, 0x00, 0x25, 0x01,
+ 0x75, 0x01, 0x95, 0x80, 0x91, 0x00, 0xc0
+};
+
+static const uint8_t phone_raw_desc_0[] = {
+ 0x0a, 0x24, 0x01, 0x00, 0x01, 0x4a, 0x00, 0x02,
+ 0x01, 0x02
+};
+
+static const uint8_t phone_raw_desc_1[] = {
+ 0x0c, 0x24, 0x02, 0x01, 0x01, 0x02, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t phone_raw_desc_2[] = {
+ 0x0c, 0x24, 0x02, 0x02, 0x01, 0x01, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t phone_raw_desc_3[] = {
+ 0x09, 0x24, 0x03, 0x03, 0x01, 0x03, 0x00, 0x06,
+ 0x00
+};
+
+static const uint8_t phone_raw_desc_4[] = {
+ 0x09, 0x24, 0x03, 0x04, 0x01, 0x01, 0x00, 0x05,
+ 0x00
+};
+
+static const uint8_t phone_raw_desc_5[] = {
+ 0x0b, 0x24, 0x06, 0x05, 0x01, 0x02, 0x03, 0x00,
+ 0x03, 0x00, 0x00
+};
+
+static const uint8_t phone_raw_desc_6[] = {
+ 0x0b, 0x24, 0x06, 0x06, 0x02, 0x02, 0x03, 0x00,
+ 0x03, 0x00, 0x00
+};
+
+static const void *phone_raw_iface_0_desc[] = {
+ phone_raw_desc_0,
+ phone_raw_desc_1,
+ phone_raw_desc_2,
+ phone_raw_desc_3,
+ phone_raw_desc_4,
+ phone_raw_desc_5,
+ phone_raw_desc_6,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc phone_iface_0 = {
+ .ppEndpoints = NULL, /* no endpoints */
+ .ppRawDesc = phone_raw_iface_0_desc,
+ .bInterfaceClass = UICLASS_AUDIO,
+ .bInterfaceSubClass = UISUBCLASS_AUDIOCONTROL,
+ .bInterfaceProtocol = 0,
+ .iInterface = PHONE_MIXER_INDEX,
+};
+
+static const uint8_t phone_raw_desc_20[] = {
+ 0x07, 0x24, 0x01, 0x04, 0x01, 0x01, 0x00
+};
+
+static const uint8_t phone_raw_desc_21[] = {
+ 0x0b, 0x24, 0x02, 0x01, 0x01, 0x02, 0x10, 0x01,
+ /* 8kHz */
+ 0x40, 0x1f, 0x00
+};
+
+static const uint8_t phone_raw_desc_22[] = {
+ 0x07, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00
+};
+
+static const void *phone_raw_iface_1_desc[] = {
+ phone_raw_desc_20,
+ phone_raw_desc_21,
+ NULL,
+};
+
+static const void *phone_raw_ep_1_desc[] = {
+ phone_raw_desc_22,
+ NULL,
+};
+
+static const struct usb_temp_packet_size phone_isoc_mps = {
+ .mps[USB_SPEED_FULL] = 0x10,
+ .mps[USB_SPEED_HIGH] = 0x10,
+};
+
+static const struct usb_temp_interval phone_isoc_interval = {
+ .bInterval[USB_SPEED_FULL] = 1, /* 1:1 */
+ .bInterval[USB_SPEED_HIGH] = 4, /* 1:8 */
+};
+
+static const struct usb_temp_endpoint_desc phone_isoc_in_ep = {
+ .ppRawDesc = phone_raw_ep_1_desc,
+ .pPacketSize = &phone_isoc_mps,
+ .pIntervals = &phone_isoc_interval,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_ISOCHRONOUS,
+};
+
+static const struct usb_temp_endpoint_desc *phone_iface_1_ep[] = {
+ &phone_isoc_in_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc phone_iface_1_alt_0 = {
+ .ppEndpoints = NULL, /* no endpoints */
+ .ppRawDesc = NULL, /* no raw descriptors */
+ .bInterfaceClass = UICLASS_AUDIO,
+ .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM,
+ .bInterfaceProtocol = 0,
+ .iInterface = PHONE_PLAYBACK_INDEX,
+};
+
+static const struct usb_temp_interface_desc phone_iface_1_alt_1 = {
+ .ppEndpoints = phone_iface_1_ep,
+ .ppRawDesc = phone_raw_iface_1_desc,
+ .bInterfaceClass = UICLASS_AUDIO,
+ .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM,
+ .bInterfaceProtocol = 0,
+ .iInterface = PHONE_PLAYBACK_INDEX,
+ .isAltInterface = 1, /* this is an alternate setting */
+};
+
+static const uint8_t phone_raw_desc_30[] = {
+ 0x07, 0x24, 0x01, 0x02, 0x01, 0x01, 0x00
+};
+
+static const uint8_t phone_raw_desc_31[] = {
+ 0x0b, 0x24, 0x02, 0x01, 0x01, 0x02, 0x10, 0x01,
+ /* 8kHz */
+ 0x40, 0x1f, 0x00
+};
+
+static const uint8_t phone_raw_desc_32[] = {
+ 0x07, 0x25, 0x01, 0x00, 0x00, 0x00, 0x00
+};
+
+static const void *phone_raw_iface_2_desc[] = {
+ phone_raw_desc_30,
+ phone_raw_desc_31,
+ NULL,
+};
+
+static const void *phone_raw_ep_2_desc[] = {
+ phone_raw_desc_32,
+ NULL,
+};
+
+static const struct usb_temp_endpoint_desc phone_isoc_out_ep = {
+ .ppRawDesc = phone_raw_ep_2_desc,
+ .pPacketSize = &phone_isoc_mps,
+ .pIntervals = &phone_isoc_interval,
+ .bEndpointAddress = UE_DIR_OUT,
+ .bmAttributes = UE_ISOCHRONOUS,
+};
+
+static const struct usb_temp_endpoint_desc *phone_iface_2_ep[] = {
+ &phone_isoc_out_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc phone_iface_2_alt_0 = {
+ .ppEndpoints = NULL, /* no endpoints */
+ .ppRawDesc = NULL, /* no raw descriptors */
+ .bInterfaceClass = UICLASS_AUDIO,
+ .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM,
+ .bInterfaceProtocol = 0,
+ .iInterface = PHONE_RECORD_INDEX,
+};
+
+static const struct usb_temp_interface_desc phone_iface_2_alt_1 = {
+ .ppEndpoints = phone_iface_2_ep,
+ .ppRawDesc = phone_raw_iface_2_desc,
+ .bInterfaceClass = UICLASS_AUDIO,
+ .bInterfaceSubClass = UISUBCLASS_AUDIOSTREAM,
+ .bInterfaceProtocol = 0,
+ .iInterface = PHONE_RECORD_INDEX,
+ .isAltInterface = 1, /* this is an alternate setting */
+};
+
+static const uint8_t phone_hid_raw_desc_0[] = {
+ 0x09, 0x21, 0x00, 0x01, 0x00, 0x01, 0x22, sizeof(phone_hid_descriptor),
+ 0x00
+};
+
+static const void *phone_hid_desc_0[] = {
+ phone_hid_raw_desc_0,
+ NULL,
+};
+
+static const struct usb_temp_packet_size phone_hid_mps = {
+ .mps[USB_SPEED_FULL] = 0x10,
+ .mps[USB_SPEED_HIGH] = 0x10,
+};
+
+static const struct usb_temp_interval phone_hid_interval = {
+ .bInterval[USB_SPEED_FULL] = 2, /* 2ms */
+ .bInterval[USB_SPEED_HIGH] = 2, /* 2ms */
+};
+
+static const struct usb_temp_endpoint_desc phone_hid_in_ep = {
+ .pPacketSize = &phone_hid_mps,
+ .pIntervals = &phone_hid_interval,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_INTERRUPT,
+};
+
+static const struct usb_temp_endpoint_desc *phone_iface_3_ep[] = {
+ &phone_hid_in_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc phone_iface_3 = {
+ .ppEndpoints = phone_iface_3_ep,
+ .ppRawDesc = phone_hid_desc_0,
+ .bInterfaceClass = UICLASS_HID,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+ .iInterface = PHONE_HID_INDEX,
+};
+
+static const struct usb_temp_interface_desc *phone_interfaces[] = {
+ &phone_iface_0,
+ &phone_iface_1_alt_0,
+ &phone_iface_1_alt_1,
+ &phone_iface_2_alt_0,
+ &phone_iface_2_alt_1,
+ &phone_iface_3,
+ NULL,
+};
+
+static const struct usb_temp_config_desc phone_config_desc = {
+ .ppIfaceDesc = phone_interfaces,
+ .bmAttributes = 0,
+ .bMaxPower = 0,
+ .iConfiguration = PHONE_PRODUCT_INDEX,
+};
+
+static const struct usb_temp_config_desc *phone_configs[] = {
+ &phone_config_desc,
+ NULL,
+};
+
+static usb_temp_get_string_desc_t phone_get_string_desc;
+static usb_temp_get_vendor_desc_t phone_get_vendor_desc;
+
+struct usb_temp_device_desc usb_template_phone = {
+ .getStringDesc = &phone_get_string_desc,
+ .getVendorDesc = &phone_get_vendor_desc,
+ .ppConfigDesc = phone_configs,
+ .idVendor = PHONE_DEFAULT_VENDOR_ID,
+ .idProduct = PHONE_DEFAULT_PRODUCT_ID,
+ .bcdDevice = 0x0100,
+ .bDeviceClass = UDCLASS_IN_INTERFACE,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .iManufacturer = PHONE_MANUFACTURER_INDEX,
+ .iProduct = PHONE_PRODUCT_INDEX,
+ .iSerialNumber = PHONE_SERIAL_NUMBER_INDEX,
+};
+
+/*------------------------------------------------------------------------*
+ * phone_get_vendor_desc
+ *
+ * Return values:
+ * NULL: Failure. No such vendor descriptor.
+ * Else: Success. Pointer to vendor descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+phone_get_vendor_desc(const struct usb_device_request *req, uint16_t *plen)
+{
+ if ((req->bmRequestType == 0x81) && (req->bRequest == 0x06) &&
+ (req->wValue[0] == 0x00) && (req->wValue[1] == 0x22) &&
+ (req->wIndex[1] == 0) && (req->wIndex[0] == 3 /* iface */)) {
+ *plen = sizeof(phone_hid_descriptor);
+ return (phone_hid_descriptor);
+ }
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * phone_get_string_desc
+ *
+ * Return values:
+ * NULL: Failure. No such string.
+ * Else: Success. Pointer to string descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+phone_get_string_desc(uint16_t lang_id, uint8_t string_index)
+{
+ static const void *ptr[PHONE_MAX_INDEX] = {
+ [PHONE_LANG_INDEX] = &usb_string_lang_en,
+ [PHONE_MIXER_INDEX] = &phone_mixer,
+ [PHONE_RECORD_INDEX] = &phone_record,
+ [PHONE_PLAYBACK_INDEX] = &phone_playback,
+ [PHONE_HID_INDEX] = &phone_hid,
+ [PHONE_MANUFACTURER_INDEX] = &phone_manufacturer,
+ [PHONE_PRODUCT_INDEX] = &phone_product,
+ [PHONE_SERIAL_NUMBER_INDEX] = &phone_serial_number,
+ };
+
+ if (string_index == 0) {
+ return (&usb_string_lang_en);
+ }
+ if (lang_id != 0x0409) {
+ return (NULL);
+ }
+ if (string_index < PHONE_MAX_INDEX) {
+ return (ptr[string_index]);
+ }
+ return (NULL);
+}
+
+static void
+phone_init(void *arg __unused)
+{
+ struct sysctl_oid *parent;
+ char parent_name[3];
+
+ usb_make_str_desc(&phone_mixer, sizeof(phone_mixer),
+ PHONE_DEFAULT_MIXER);
+ usb_make_str_desc(&phone_record, sizeof(phone_record),
+ PHONE_DEFAULT_RECORD);
+ usb_make_str_desc(&phone_playback, sizeof(phone_playback),
+ PHONE_DEFAULT_PLAYBACK);
+ usb_make_str_desc(&phone_hid, sizeof(phone_hid),
+ PHONE_DEFAULT_HID);
+ usb_make_str_desc(&phone_manufacturer, sizeof(phone_manufacturer),
+ PHONE_DEFAULT_MANUFACTURER);
+ usb_make_str_desc(&phone_product, sizeof(phone_product),
+ PHONE_DEFAULT_PRODUCT);
+ usb_make_str_desc(&phone_serial_number, sizeof(phone_serial_number),
+ PHONE_DEFAULT_SERIAL_NUMBER);
+
+ snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_PHONE);
+ sysctl_ctx_init(&phone_ctx_list);
+
+ parent = SYSCTL_ADD_NODE(&phone_ctx_list,
+ SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO,
+ parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, "USB Phone device side template");
+ SYSCTL_ADD_U16(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "vendor_id", CTLFLAG_RWTUN,
+ &usb_template_cdce.idVendor, 1, "Vendor identifier");
+ SYSCTL_ADD_U16(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product_id", CTLFLAG_RWTUN,
+ &usb_template_cdce.idProduct, 1, "Product identifier");
+#if 0
+ SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "mixer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &phone_mixer, sizeof(phone_mixer), usb_temp_sysctl,
+ "A", "Mixer interface string");
+ SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "record", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &phone_record, sizeof(phone_record), usb_temp_sysctl,
+ "A", "Record interface string");
+ SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "playback", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &phone_playback, sizeof(phone_playback), usb_temp_sysctl,
+ "A", "Playback interface string");
+ SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "hid", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &phone_hid, sizeof(phone_hid), usb_temp_sysctl,
+ "A", "HID interface string");
+#endif
+ SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &phone_manufacturer, sizeof(phone_manufacturer), usb_temp_sysctl,
+ "A", "Manufacturer string");
+ SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &phone_product, sizeof(phone_product), usb_temp_sysctl,
+ "A", "Product string");
+ SYSCTL_ADD_PROC(&phone_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &phone_serial_number, sizeof(phone_serial_number), usb_temp_sysctl,
+ "A", "Serial number string");
+}
+
+static void
+phone_uninit(void *arg __unused)
+{
+
+ sysctl_ctx_free(&phone_ctx_list);
+}
+
+SYSINIT(phone_init, SI_SUB_LOCK, SI_ORDER_FIRST, phone_init, NULL);
+SYSUNINIT(phone_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, phone_uninit, NULL);
diff --git a/sys/dev/usb/template/usb_template_serialnet.c b/sys/dev/usb/template/usb_template_serialnet.c
new file mode 100644
index 000000000000..6ee43f7f1f28
--- /dev/null
+++ b/sys/dev/usb/template/usb_template_serialnet.c
@@ -0,0 +1,464 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2015 Ruslan Bukin <br@bsdpad.com>
+ * Copyright (c) 2018 The FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed by SRI International and the University of
+ * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
+ * ("CTSRD"), as part of the DARPA CRASH research programme.
+ *
+ * Portions of this software were developed by Edward Tomasz Napierala
+ * under sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * This file contains the USB template for USB Networking and Serial
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_cdc.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/template/usb_template.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#define MODEM_IFACE_0 0
+#define MODEM_IFACE_1 1
+
+enum {
+ SERIALNET_LANG_INDEX,
+ SERIALNET_MODEM_INDEX,
+ SERIALNET_ETH_MAC_INDEX,
+ SERIALNET_ETH_CONTROL_INDEX,
+ SERIALNET_ETH_DATA_INDEX,
+ SERIALNET_CONFIGURATION_INDEX,
+ SERIALNET_MANUFACTURER_INDEX,
+ SERIALNET_PRODUCT_INDEX,
+ SERIALNET_SERIAL_NUMBER_INDEX,
+ SERIALNET_MAX_INDEX,
+};
+
+#define SERIALNET_DEFAULT_VENDOR_ID USB_TEMPLATE_VENDOR
+#define SERIALNET_DEFAULT_PRODUCT_ID 0x05dc
+#define SERIALNET_DEFAULT_MODEM "Virtual serial port"
+#define SERIALNET_DEFAULT_ETH_MAC "2A02030405060789AB"
+#define SERIALNET_DEFAULT_ETH_CONTROL "USB Ethernet Comm Interface"
+#define SERIALNET_DEFAULT_ETH_DATA "USB Ethernet Data Interface"
+#define SERIALNET_DEFAULT_CONFIGURATION "Default configuration"
+#define SERIALNET_DEFAULT_MANUFACTURER USB_TEMPLATE_MANUFACTURER
+#define SERIALNET_DEFAULT_PRODUCT "Serial/Ethernet device"
+/*
+ * The reason for this being called like this is that OSX
+ * derives the device node name from it, resulting in a somewhat
+ * user-friendly "/dev/cu.usbmodemFreeBSD1". And yes, the "1"
+ * needs to be there, otherwise OSX will mangle it.
+ */
+#define SERIALNET_DEFAULT_SERIAL_NUMBER "FreeBSD1"
+
+static struct usb_string_descriptor serialnet_modem;
+static struct usb_string_descriptor serialnet_eth_mac;
+static struct usb_string_descriptor serialnet_eth_control;
+static struct usb_string_descriptor serialnet_eth_data;
+static struct usb_string_descriptor serialnet_configuration;
+static struct usb_string_descriptor serialnet_manufacturer;
+static struct usb_string_descriptor serialnet_product;
+static struct usb_string_descriptor serialnet_serial_number;
+
+static struct sysctl_ctx_list serialnet_ctx_list;
+
+/* prototypes */
+
+static usb_temp_get_string_desc_t serialnet_get_string_desc;
+
+static const struct usb_cdc_union_descriptor eth_union_desc = {
+ .bLength = sizeof(eth_union_desc),
+ .bDescriptorType = UDESC_CS_INTERFACE,
+ .bDescriptorSubtype = UDESCSUB_CDC_UNION,
+ .bMasterInterface = 0, /* this is automatically updated */
+ .bSlaveInterface[0] = 1, /* this is automatically updated */
+};
+
+static const struct usb_cdc_header_descriptor eth_header_desc = {
+ .bLength = sizeof(eth_header_desc),
+ .bDescriptorType = UDESC_CS_INTERFACE,
+ .bDescriptorSubtype = UDESCSUB_CDC_HEADER,
+ .bcdCDC[0] = 0x10,
+ .bcdCDC[1] = 0x01,
+};
+
+static const struct usb_cdc_ethernet_descriptor eth_enf_desc = {
+ .bLength = sizeof(eth_enf_desc),
+ .bDescriptorType = UDESC_CS_INTERFACE,
+ .bDescriptorSubtype = UDESCSUB_CDC_ENF,
+ .iMacAddress = SERIALNET_ETH_MAC_INDEX,
+ .bmEthernetStatistics = {0, 0, 0, 0},
+ .wMaxSegmentSize = {0xEA, 0x05},/* 1514 bytes */
+ .wNumberMCFilters = {0, 0},
+ .bNumberPowerFilters = 0,
+};
+
+static const void *eth_control_if_desc[] = {
+ &eth_union_desc,
+ &eth_header_desc,
+ &eth_enf_desc,
+ NULL,
+};
+
+static const struct usb_temp_packet_size bulk_mps = {
+ .mps[USB_SPEED_FULL] = 64,
+ .mps[USB_SPEED_HIGH] = 512,
+};
+
+static const struct usb_temp_packet_size intr_mps = {
+ .mps[USB_SPEED_FULL] = 8,
+ .mps[USB_SPEED_HIGH] = 8,
+};
+
+static const struct usb_temp_endpoint_desc bulk_in_ep = {
+ .pPacketSize = &bulk_mps,
+#ifdef USB_HIP_IN_EP_0
+ .bEndpointAddress = USB_HIP_IN_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_IN,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc bulk_out_ep = {
+ .pPacketSize = &bulk_mps,
+#ifdef USB_HIP_OUT_EP_0
+ .bEndpointAddress = USB_HIP_OUT_EP_0,
+#else
+ .bEndpointAddress = UE_DIR_OUT,
+#endif
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc intr_in_ep = {
+ .pPacketSize = &intr_mps,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_INTERRUPT,
+};
+
+static const struct usb_temp_endpoint_desc *eth_intr_endpoints[] = {
+ &intr_in_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc eth_control_interface = {
+ .ppEndpoints = eth_intr_endpoints,
+ .ppRawDesc = eth_control_if_desc,
+ .bInterfaceClass = UICLASS_CDC,
+ .bInterfaceSubClass = UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL,
+ .bInterfaceProtocol = UIPROTO_CDC_NONE,
+ .iInterface = SERIALNET_ETH_CONTROL_INDEX,
+};
+
+static const struct usb_temp_endpoint_desc *eth_data_endpoints[] = {
+ &bulk_in_ep,
+ &bulk_out_ep,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc eth_data_null_interface = {
+ .ppEndpoints = NULL, /* no endpoints */
+ .bInterfaceClass = UICLASS_CDC_DATA,
+ .bInterfaceSubClass = UISUBCLASS_DATA,
+ .bInterfaceProtocol = 0,
+ .iInterface = SERIALNET_ETH_DATA_INDEX,
+};
+
+static const struct usb_temp_interface_desc eth_data_interface = {
+ .ppEndpoints = eth_data_endpoints,
+ .bInterfaceClass = UICLASS_CDC_DATA,
+ .bInterfaceSubClass = UISUBCLASS_DATA,
+ .bInterfaceProtocol = 0,
+ .iInterface = SERIALNET_ETH_DATA_INDEX,
+ .isAltInterface = 1, /* this is an alternate setting */
+};
+
+static const struct usb_temp_packet_size modem_bulk_mps = {
+ .mps[USB_SPEED_LOW] = 8,
+ .mps[USB_SPEED_FULL] = 64,
+ .mps[USB_SPEED_HIGH] = 512,
+};
+
+static const struct usb_temp_packet_size modem_intr_mps = {
+ .mps[USB_SPEED_LOW] = 8,
+ .mps[USB_SPEED_FULL] = 8,
+ .mps[USB_SPEED_HIGH] = 8,
+};
+
+static const struct usb_temp_interval modem_intr_interval = {
+ .bInterval[USB_SPEED_LOW] = 8, /* 8ms */
+ .bInterval[USB_SPEED_FULL] = 8, /* 8ms */
+ .bInterval[USB_SPEED_HIGH] = 7, /* 8ms */
+};
+
+static const struct usb_temp_endpoint_desc modem_ep_0 = {
+ .pPacketSize = &modem_intr_mps,
+ .pIntervals = &modem_intr_interval,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_INTERRUPT,
+};
+
+static const struct usb_temp_endpoint_desc modem_ep_1 = {
+ .pPacketSize = &modem_bulk_mps,
+ .bEndpointAddress = UE_DIR_OUT,
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc modem_ep_2 = {
+ .pPacketSize = &modem_bulk_mps,
+ .bEndpointAddress = UE_DIR_IN,
+ .bmAttributes = UE_BULK,
+};
+
+static const struct usb_temp_endpoint_desc *modem_iface_0_ep[] = {
+ &modem_ep_0,
+ NULL,
+};
+
+static const struct usb_temp_endpoint_desc *modem_iface_1_ep[] = {
+ &modem_ep_1,
+ &modem_ep_2,
+ NULL,
+};
+
+static const uint8_t modem_raw_desc_0[] = {
+ 0x05, 0x24, 0x00, 0x10, 0x01
+};
+
+static const uint8_t modem_raw_desc_1[] = {
+ 0x05, 0x24, 0x06, MODEM_IFACE_0, MODEM_IFACE_1
+};
+
+static const uint8_t modem_raw_desc_2[] = {
+ 0x05, 0x24, 0x01, 0x03, MODEM_IFACE_1
+};
+
+static const uint8_t modem_raw_desc_3[] = {
+ 0x04, 0x24, 0x02, 0x07
+};
+
+static const void *modem_iface_0_desc[] = {
+ &modem_raw_desc_0,
+ &modem_raw_desc_1,
+ &modem_raw_desc_2,
+ &modem_raw_desc_3,
+ NULL,
+};
+
+static const struct usb_temp_interface_desc modem_iface_0 = {
+ .ppRawDesc = modem_iface_0_desc,
+ .ppEndpoints = modem_iface_0_ep,
+ .bInterfaceClass = UICLASS_CDC,
+ .bInterfaceSubClass = UISUBCLASS_ABSTRACT_CONTROL_MODEL,
+ .bInterfaceProtocol = UIPROTO_CDC_NONE,
+ .iInterface = SERIALNET_MODEM_INDEX,
+};
+
+static const struct usb_temp_interface_desc modem_iface_1 = {
+ .ppEndpoints = modem_iface_1_ep,
+ .bInterfaceClass = UICLASS_CDC_DATA,
+ .bInterfaceSubClass = UISUBCLASS_DATA,
+ .bInterfaceProtocol = 0,
+ .iInterface = SERIALNET_MODEM_INDEX,
+};
+
+static const struct usb_temp_interface_desc *serialnet_interfaces[] = {
+ &modem_iface_0,
+ &modem_iface_1,
+ &eth_control_interface,
+ &eth_data_null_interface,
+ &eth_data_interface,
+ NULL,
+};
+
+static const struct usb_temp_config_desc serialnet_config_desc = {
+ .ppIfaceDesc = serialnet_interfaces,
+ .bmAttributes = 0,
+ .bMaxPower = 0,
+ .iConfiguration = SERIALNET_CONFIGURATION_INDEX,
+};
+static const struct usb_temp_config_desc *serialnet_configs[] = {
+ &serialnet_config_desc,
+ NULL,
+};
+
+struct usb_temp_device_desc usb_template_serialnet = {
+ .getStringDesc = &serialnet_get_string_desc,
+ .ppConfigDesc = serialnet_configs,
+ .idVendor = SERIALNET_DEFAULT_VENDOR_ID,
+ .idProduct = SERIALNET_DEFAULT_PRODUCT_ID,
+ .bcdDevice = 0x0100,
+ .bDeviceClass = UDCLASS_IN_INTERFACE,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ .iManufacturer = SERIALNET_MANUFACTURER_INDEX,
+ .iProduct = SERIALNET_PRODUCT_INDEX,
+ .iSerialNumber = SERIALNET_SERIAL_NUMBER_INDEX,
+};
+
+/*------------------------------------------------------------------------*
+ * serialnet_get_string_desc
+ *
+ * Return values:
+ * NULL: Failure. No such string.
+ * Else: Success. Pointer to string descriptor is returned.
+ *------------------------------------------------------------------------*/
+static const void *
+serialnet_get_string_desc(uint16_t lang_id, uint8_t string_index)
+{
+ static const void *ptr[SERIALNET_MAX_INDEX] = {
+ [SERIALNET_LANG_INDEX] = &usb_string_lang_en,
+ [SERIALNET_MODEM_INDEX] = &serialnet_modem,
+ [SERIALNET_ETH_MAC_INDEX] = &serialnet_eth_mac,
+ [SERIALNET_ETH_CONTROL_INDEX] = &serialnet_eth_control,
+ [SERIALNET_ETH_DATA_INDEX] = &serialnet_eth_data,
+ [SERIALNET_CONFIGURATION_INDEX] = &serialnet_configuration,
+ [SERIALNET_MANUFACTURER_INDEX] = &serialnet_manufacturer,
+ [SERIALNET_PRODUCT_INDEX] = &serialnet_product,
+ [SERIALNET_SERIAL_NUMBER_INDEX] = &serialnet_serial_number,
+ };
+
+ if (string_index == 0) {
+ return (&usb_string_lang_en);
+ }
+ if (lang_id != 0x0409) {
+ return (NULL);
+ }
+ if (string_index < SERIALNET_MAX_INDEX) {
+ return (ptr[string_index]);
+ }
+ return (NULL);
+}
+
+static void
+serialnet_init(void *arg __unused)
+{
+ struct sysctl_oid *parent;
+ char parent_name[3];
+
+ usb_make_str_desc(&serialnet_modem, sizeof(serialnet_modem),
+ SERIALNET_DEFAULT_MODEM);
+ usb_make_str_desc(&serialnet_eth_mac, sizeof(serialnet_eth_mac),
+ SERIALNET_DEFAULT_ETH_MAC);
+ usb_make_str_desc(&serialnet_eth_control, sizeof(serialnet_eth_control),
+ SERIALNET_DEFAULT_ETH_CONTROL);
+ usb_make_str_desc(&serialnet_eth_data, sizeof(serialnet_eth_data),
+ SERIALNET_DEFAULT_ETH_DATA);
+ usb_make_str_desc(&serialnet_configuration, sizeof(serialnet_configuration),
+ SERIALNET_DEFAULT_CONFIGURATION);
+ usb_make_str_desc(&serialnet_manufacturer, sizeof(serialnet_manufacturer),
+ SERIALNET_DEFAULT_MANUFACTURER);
+ usb_make_str_desc(&serialnet_product, sizeof(serialnet_product),
+ SERIALNET_DEFAULT_PRODUCT);
+ usb_make_str_desc(&serialnet_serial_number, sizeof(serialnet_serial_number),
+ SERIALNET_DEFAULT_SERIAL_NUMBER);
+
+ snprintf(parent_name, sizeof(parent_name), "%d", USB_TEMP_SERIALNET);
+ sysctl_ctx_init(&serialnet_ctx_list);
+
+ parent = SYSCTL_ADD_NODE(&serialnet_ctx_list,
+ SYSCTL_STATIC_CHILDREN(_hw_usb_templates), OID_AUTO,
+ parent_name, CTLFLAG_RW | CTLFLAG_MPSAFE,
+ 0, "USB CDC Serial/Ethernet device side template");
+ SYSCTL_ADD_U16(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "vendor_id", CTLFLAG_RWTUN,
+ &usb_template_serialnet.idVendor, 1, "Vendor identifier");
+ SYSCTL_ADD_U16(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product_id", CTLFLAG_RWTUN,
+ &usb_template_serialnet.idProduct, 1, "Product identifier");
+ SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "eth_mac", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &serialnet_eth_mac, sizeof(serialnet_eth_mac), usb_temp_sysctl,
+ "A", "Ethernet MAC address string");
+#if 0
+ SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "modem", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &serialnet_modem, sizeof(serialnet_modem), usb_temp_sysctl,
+ "A", "Modem interface string");
+ SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "eth_control", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &serialnet_eth_control, sizeof(serialnet_eth_data), usb_temp_sysctl,
+ "A", "Ethernet control interface string");
+ SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "eth_data", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &serialnet_eth_data, sizeof(serialnet_eth_data), usb_temp_sysctl,
+ "A", "Ethernet data interface string");
+ SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "configuration", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &serialnet_configuration, sizeof(serialnet_configuration), usb_temp_sysctl,
+ "A", "Configuration string");
+#endif
+ SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "manufacturer", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &serialnet_manufacturer, sizeof(serialnet_manufacturer), usb_temp_sysctl,
+ "A", "Manufacturer string");
+ SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "product", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &serialnet_product, sizeof(serialnet_product), usb_temp_sysctl,
+ "A", "Product string");
+ SYSCTL_ADD_PROC(&serialnet_ctx_list, SYSCTL_CHILDREN(parent), OID_AUTO,
+ "serial_number", CTLTYPE_STRING | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &serialnet_serial_number, sizeof(serialnet_serial_number), usb_temp_sysctl,
+ "A", "Serial number string");
+}
+
+static void
+serialnet_uninit(void *arg __unused)
+{
+
+ sysctl_ctx_free(&serialnet_ctx_list);
+}
+
+SYSINIT(serialnet_init, SI_SUB_LOCK, SI_ORDER_FIRST, serialnet_init, NULL);
+SYSUNINIT(serialnet_uninit, SI_SUB_LOCK, SI_ORDER_FIRST, serialnet_uninit, NULL);
diff --git a/sys/dev/usb/ufm_ioctl.h b/sys/dev/usb/ufm_ioctl.h
new file mode 100644
index 000000000000..a95741b71e2c
--- /dev/null
+++ b/sys/dev/usb/ufm_ioctl.h
@@ -0,0 +1,43 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2001 M. Warner Losh <imp@FreeBSD.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.
+ *
+ * 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.
+ */
+
+#ifndef _UFM_IOCTL_H_
+#define _UFM_IOCTL_H_
+
+#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)
+
+#endif /* _UFM_IOCTL_H_ */
diff --git a/sys/dev/usb/uftdiio.h b/sys/dev/usb/uftdiio.h
new file mode 100644
index 000000000000..033e09437c63
--- /dev/null
+++ b/sys/dev/usb/uftdiio.h
@@ -0,0 +1,98 @@
+/*-
+ * Copyright 2008-2012 - Symmetricom, 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.
+ *
+ * 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.
+ *
+ */
+
+/*
+ * FTDI USB serial converter chip ioctl commands.
+ */
+
+#ifndef _USB_UFTDIIO_H_
+#define _USB_UFTDIIO_H_
+
+#include <sys/ioccom.h>
+
+enum uftdi_bitmodes
+{
+ UFTDI_BITMODE_ASYNC = 0,
+ UFTDI_BITMODE_MPSSE = 1,
+ UFTDI_BITMODE_SYNC = 2,
+ UFTDI_BITMODE_CPU_EMUL = 3,
+ UFTDI_BITMODE_FAST_SERIAL = 4,
+ UFTDI_BITMODE_CBUS = 5,
+ UFTDI_BITMODE_NONE = 0xff, /* aka UART mode. */
+};
+
+/*
+ * For UFTDIIOC_SET_BITMODE:
+ * mode = One of the uftdi_bitmodes enum values.
+ * iomask = Mask of bits enabled for bitbang output.
+ *
+ * For UFTDIIOC_GET_BITMODE:
+ * mode = Mode most recently set using UFTDIIOC_SET_BITMODE.
+ * iomask = Returned snapshot of DBUS0..DBUS7 pin states at time of call.
+ * Pin states can be read in any mode, not just bitbang modes.
+ */
+struct uftdi_bitmode
+{
+ uint8_t mode;
+ uint8_t iomask;
+};
+
+/*
+ * For UFTDIIOC_READ_EEPROM, UFTDIIOC_WRITE_EEPROM:
+ *
+ * IO is done in 16-bit words at the chip level; offset and length are in bytes,
+ * but must each be evenly divisible by two.
+ *
+ * It is not necessary to erase before writing. For the FT232R device (only)
+ * you must set the latency timer to 0x77 before doing a series of eeprom writes
+ * (and restore it to the prior value when done).
+ */
+struct uftdi_eeio
+{
+ uint16_t offset;
+ uint16_t length;
+ uint16_t data[64];
+};
+
+/* Pass this value to confirm that eeprom erase request is not accidental. */
+#define UFTDI_CONFIRM_ERASE 0x26139108
+
+#define UFTDIIOC_RESET_IO _IO('c', 0) /* Reset config, flush fifos.*/
+#define UFTDIIOC_RESET_RX _IO('c', 1) /* Flush input fifo. */
+#define UFTDIIOC_RESET_TX _IO('c', 2) /* Flush output fifo. */
+#define UFTDIIOC_SET_BITMODE _IOW('c', 3, struct uftdi_bitmode)
+#define UFTDIIOC_GET_BITMODE _IOR('c', 4, struct uftdi_bitmode)
+#define UFTDIIOC_SET_ERROR_CHAR _IOW('c', 5, int) /* -1 to disable */
+#define UFTDIIOC_SET_EVENT_CHAR _IOW('c', 6, int) /* -1 to disable */
+#define UFTDIIOC_SET_LATENCY _IOW('c', 7, int) /* 1-255 ms */
+#define UFTDIIOC_GET_LATENCY _IOR('c', 8, int)
+#define UFTDIIOC_GET_HWREV _IOR('c', 9, int)
+#define UFTDIIOC_READ_EEPROM _IOWR('c', 10, struct uftdi_eeio)
+#define UFTDIIOC_WRITE_EEPROM _IOW('c', 11, struct uftdi_eeio)
+#define UFTDIIOC_ERASE_EEPROM _IOW('c', 12, int)
+
+#endif
diff --git a/sys/dev/usb/uled_ioctl.h b/sys/dev/usb/uled_ioctl.h
new file mode 100644
index 000000000000..ee7e6844ca06
--- /dev/null
+++ b/sys/dev/usb/uled_ioctl.h
@@ -0,0 +1,41 @@
+/*-
+ * Copyright (c) 2014 Kevin Lo
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _ULED_IOCTL_H_
+#define _ULED_IOCTL_H_
+
+#include <sys/ioccom.h>
+
+struct uled_color {
+ uint8_t red;
+ uint8_t green;
+ uint8_t blue;
+};
+
+#define ULED_GET_COLOR _IOR('U', 205, struct uled_color)
+#define ULED_SET_COLOR _IOW('U', 206, struct uled_color)
+
+#endif /* _ULED_IOCTL_H_ */
diff --git a/sys/dev/usb/usb.h b/sys/dev/usb/usb.h
new file mode 100644
index 000000000000..a6c3c8030c73
--- /dev/null
+++ b/sys/dev/usb/usb.h
@@ -0,0 +1,810 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This file contains standard definitions for the following USB
+ * protocol versions:
+ *
+ * USB v1.0
+ * USB v1.1
+ * USB v2.0
+ * USB v3.0
+ */
+
+#ifndef _USB_STANDARD_H_
+#define _USB_STANDARD_H_
+
+#if defined(_KERNEL) || defined(_STANDALONE)
+#ifndef USB_GLOBAL_INCLUDE_FILE
+#include "opt_usb.h"
+#endif
+
+/* Declare parent SYSCTL USB node. */
+#ifdef SYSCTL_DECL
+SYSCTL_DECL(_hw_usb);
+#endif
+
+#ifndef USB_GLOBAL_INCLUDE_FILE
+#include <sys/malloc.h>
+#endif
+
+MALLOC_DECLARE(M_USB);
+MALLOC_DECLARE(M_USBDEV);
+#endif /* _KERNEL || _STANDALONE */
+
+#ifndef USB_GLOBAL_INCLUDE_FILE
+#include <dev/usb/usb_endian.h>
+#include <dev/usb/usb_freebsd.h>
+#endif
+
+#define USB_STACK_VERSION 2000 /* 2.0 */
+
+/* Definition of some hardcoded USB constants. */
+
+#define USB_MAX_IPACKET 8 /* initial USB packet size */
+#define USB_EP_MAX (2*16) /* hardcoded */
+#define USB_ROOT_HUB_ADDR 1 /* index */
+#define USB_MIN_DEVICES 2 /* unused + root HUB */
+#define USB_UNCONFIG_INDEX 0xFF /* internal use only */
+#define USB_IFACE_INDEX_ANY 0xFF /* internal use only */
+#define USB_START_ADDR 0 /* default USB device BUS address
+ * after USB bus reset */
+#define USB_CONTROL_ENDPOINT 0 /* default control endpoint */
+
+#define USB_FRAMES_PER_SECOND_FS 1000 /* full speed */
+#define USB_FRAMES_PER_SECOND_HS 8000 /* high speed */
+
+#define USB_FS_BYTES_PER_HS_UFRAME 188 /* bytes */
+#define USB_HS_MICRO_FRAMES_MAX 8 /* units */
+
+#define USB_ISOC_TIME_MAX 128 /* ms */
+
+/*
+ * Minimum time a device needs to be powered down to go through a
+ * power cycle. These values are not in the USB specification.
+ */
+#define USB_POWER_DOWN_TIME 200 /* ms */
+#define USB_PORT_POWER_DOWN_TIME 100 /* ms */
+
+/* Definition of software USB power modes */
+#define USB_POWER_MODE_OFF 0 /* turn off device */
+#define USB_POWER_MODE_ON 1 /* always on */
+#define USB_POWER_MODE_SAVE 2 /* automatic suspend and resume */
+#define USB_POWER_MODE_SUSPEND 3 /* force suspend */
+#define USB_POWER_MODE_RESUME 4 /* force resume */
+
+/* These are the values from the USB specification. */
+#define USB_PORT_RESET_DELAY_SPEC 10 /* ms */
+#define USB_PORT_ROOT_RESET_DELAY_SPEC 50 /* ms */
+#define USB_PORT_RESET_RECOVERY_SPEC 10 /* ms */
+#define USB_PORT_POWERUP_DELAY_SPEC 100 /* ms */
+#define USB_PORT_RESUME_DELAY_SPEC 20 /* ms */
+#define USB_SET_ADDRESS_SETTLE_SPEC 2 /* ms */
+#define USB_RESUME_DELAY_SPEC (20*5) /* ms */
+#define USB_RESUME_WAIT_SPEC 10 /* ms */
+#define USB_RESUME_RECOVERY_SPEC 10 /* ms */
+#define USB_EXTRA_POWER_UP_TIME_SPEC 0 /* ms */
+
+/* Allow for marginal and non-conforming devices. */
+#define USB_PORT_RESET_DELAY 50 /* ms */
+#define USB_PORT_ROOT_RESET_DELAY 200 /* ms */
+#define USB_PORT_RESET_RECOVERY 20 /* ms */
+#define USB_PORT_POWERUP_DELAY 300 /* ms */
+#define USB_PORT_RESUME_DELAY (20*2) /* ms */
+#define USB_SET_ADDRESS_SETTLE 10 /* ms */
+#define USB_RESUME_DELAY (50*5) /* ms */
+#define USB_RESUME_WAIT 50 /* ms */
+#define USB_RESUME_RECOVERY 50 /* ms */
+#define USB_EXTRA_POWER_UP_TIME 20 /* ms */
+#define USB_ENUM_NICE_TIME 16 /* ms */
+
+#define USB_MIN_POWER 100 /* mA */
+#define USB_MAX_POWER 500 /* mA */
+
+#define USB_BUS_RESET_DELAY 100 /* ms */
+
+/*
+ * USB record layout in memory:
+ *
+ * - USB config 0
+ * - USB interfaces
+ * - USB alternative interfaces
+ * - USB endpoints
+ *
+ * - USB config 1
+ * - USB interfaces
+ * - USB alternative interfaces
+ * - USB endpoints
+ */
+
+/* Declaration of USB records */
+
+struct usb_device_request {
+ uByte bmRequestType;
+ uByte bRequest;
+ uWord wValue;
+ uWord wIndex;
+ uWord wLength;
+} __packed;
+typedef struct usb_device_request usb_device_request_t;
+
+#define UT_WRITE 0x00
+#define UT_READ 0x80
+#define UT_STANDARD 0x00
+#define UT_CLASS 0x20
+#define UT_VENDOR 0x40
+#define UT_DEVICE 0x00
+#define UT_INTERFACE 0x01
+#define UT_ENDPOINT 0x02
+#define UT_OTHER 0x03
+
+#define UT_READ_DEVICE (UT_READ | UT_STANDARD | UT_DEVICE)
+#define UT_READ_INTERFACE (UT_READ | UT_STANDARD | UT_INTERFACE)
+#define UT_READ_ENDPOINT (UT_READ | UT_STANDARD | UT_ENDPOINT)
+#define UT_WRITE_DEVICE (UT_WRITE | UT_STANDARD | UT_DEVICE)
+#define UT_WRITE_INTERFACE (UT_WRITE | UT_STANDARD | UT_INTERFACE)
+#define UT_WRITE_ENDPOINT (UT_WRITE | UT_STANDARD | UT_ENDPOINT)
+#define UT_READ_CLASS_DEVICE (UT_READ | UT_CLASS | UT_DEVICE)
+#define UT_READ_CLASS_INTERFACE (UT_READ | UT_CLASS | UT_INTERFACE)
+#define UT_READ_CLASS_OTHER (UT_READ | UT_CLASS | UT_OTHER)
+#define UT_READ_CLASS_ENDPOINT (UT_READ | UT_CLASS | UT_ENDPOINT)
+#define UT_WRITE_CLASS_DEVICE (UT_WRITE | UT_CLASS | UT_DEVICE)
+#define UT_WRITE_CLASS_INTERFACE (UT_WRITE | UT_CLASS | UT_INTERFACE)
+#define UT_WRITE_CLASS_OTHER (UT_WRITE | UT_CLASS | UT_OTHER)
+#define UT_WRITE_CLASS_ENDPOINT (UT_WRITE | UT_CLASS | UT_ENDPOINT)
+#define UT_READ_VENDOR_DEVICE (UT_READ | UT_VENDOR | UT_DEVICE)
+#define UT_READ_VENDOR_INTERFACE (UT_READ | UT_VENDOR | UT_INTERFACE)
+#define UT_READ_VENDOR_OTHER (UT_READ | UT_VENDOR | UT_OTHER)
+#define UT_READ_VENDOR_ENDPOINT (UT_READ | UT_VENDOR | UT_ENDPOINT)
+#define UT_WRITE_VENDOR_DEVICE (UT_WRITE | UT_VENDOR | UT_DEVICE)
+#define UT_WRITE_VENDOR_INTERFACE (UT_WRITE | UT_VENDOR | UT_INTERFACE)
+#define UT_WRITE_VENDOR_OTHER (UT_WRITE | UT_VENDOR | UT_OTHER)
+#define UT_WRITE_VENDOR_ENDPOINT (UT_WRITE | UT_VENDOR | UT_ENDPOINT)
+
+/* Requests */
+#define UR_GET_STATUS 0x00
+#define UR_CLEAR_FEATURE 0x01
+#define UR_SET_FEATURE 0x03
+#define UR_SET_ADDRESS 0x05
+#define UR_GET_DESCRIPTOR 0x06
+#define UDESC_DEVICE 0x01
+#define UDESC_CONFIG 0x02
+#define UDESC_STRING 0x03
+#define USB_LANGUAGE_TABLE 0x00 /* language ID string index */
+#define UDESC_INTERFACE 0x04
+#define UDESC_ENDPOINT 0x05
+#define UDESC_DEVICE_QUALIFIER 0x06
+#define UDESC_OTHER_SPEED_CONFIGURATION 0x07
+#define UDESC_INTERFACE_POWER 0x08
+#define UDESC_OTG 0x09
+#define UDESC_DEBUG 0x0A
+#define UDESC_IFACE_ASSOC 0x0B /* interface association */
+#define UDESC_BOS 0x0F /* binary object store */
+#define UDESC_DEVICE_CAPABILITY 0x10
+#define UDESC_CS_DEVICE 0x21 /* class specific */
+#define UDESC_CS_CONFIG 0x22
+#define UDESC_CS_STRING 0x23
+#define UDESC_CS_INTERFACE 0x24
+#define UDESC_CS_ENDPOINT 0x25
+#define UDESC_HUB 0x29
+#define UDESC_SS_HUB 0x2A /* super speed */
+#define UDESC_ENDPOINT_SS_COMP 0x30 /* super speed */
+#define UR_SET_DESCRIPTOR 0x07
+#define UR_GET_CONFIG 0x08
+#define UR_SET_CONFIG 0x09
+#define UR_GET_INTERFACE 0x0a
+#define UR_SET_INTERFACE 0x0b
+#define UR_SYNCH_FRAME 0x0c
+#define UR_SET_SEL 0x30
+#define UR_ISOCH_DELAY 0x31
+
+/* HUB specific request */
+#define UR_GET_BUS_STATE 0x02
+#define UR_CLEAR_TT_BUFFER 0x08
+#define UR_RESET_TT 0x09
+#define UR_GET_TT_STATE 0x0a
+#define UR_STOP_TT 0x0b
+#define UR_SET_AND_TEST 0x0c /* USB 2.0 only */
+#define UR_SET_HUB_DEPTH 0x0c /* USB 3.0 only */
+#define USB_SS_HUB_DEPTH_MAX 5
+#define UR_GET_PORT_ERR_COUNT 0x0d
+
+/* Feature numbers */
+#define UF_ENDPOINT_HALT 0
+#define UF_DEVICE_REMOTE_WAKEUP 1
+#define UF_TEST_MODE 2
+#define UF_U1_ENABLE 0x30
+#define UF_U2_ENABLE 0x31
+#define UF_LTM_ENABLE 0x32
+
+/* HUB specific features */
+#define UHF_C_HUB_LOCAL_POWER 0
+#define UHF_C_HUB_OVER_CURRENT 1
+#define UHF_PORT_CONNECTION 0
+#define UHF_PORT_ENABLE 1
+#define UHF_PORT_SUSPEND 2
+#define UHF_PORT_OVER_CURRENT 3
+#define UHF_PORT_RESET 4
+#define UHF_PORT_LINK_STATE 5
+#define UHF_PORT_POWER 8
+#define UHF_PORT_LOW_SPEED 9
+#define UHF_PORT_L1 10
+#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
+#define UHF_C_PORT_L1 23
+
+/* SuperSpeed HUB specific features */
+#define UHF_PORT_U1_TIMEOUT 23
+#define UHF_PORT_U2_TIMEOUT 24
+#define UHF_C_PORT_LINK_STATE 25
+#define UHF_C_PORT_CONFIG_ERROR 26
+#define UHF_PORT_REMOTE_WAKE_MASK 27
+#define UHF_BH_PORT_RESET 28
+#define UHF_C_BH_PORT_RESET 29
+#define UHF_FORCE_LINKPM_ACCEPT 30
+
+/* SuperSpeed suspend support */
+#define USB_INTERFACE_FUNC_SUSPEND 0
+#define USB_INTERFACE_FUNC_SUSPEND_LP (1 << 8)
+#define USB_INTERFACE_FUNC_SUSPEND_RW (1 << 9)
+
+struct usb_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+} __packed;
+typedef struct usb_descriptor usb_descriptor_t;
+
+struct usb_device_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uWord bcdUSB;
+#define UD_USB_2_0 0x0200
+#define UD_USB_3_0 0x0300
+#define UD_IS_USB2(d) ((d)->bcdUSB[1] == 0x02)
+#define UD_IS_USB3(d) ((d)->bcdUSB[1] == 0x03)
+ uByte bDeviceClass;
+ uByte bDeviceSubClass;
+ uByte bDeviceProtocol;
+ uByte bMaxPacketSize;
+ /* The fields below are not part of the initial descriptor. */
+ uWord idVendor;
+ uWord idProduct;
+ uWord bcdDevice;
+ uByte iManufacturer;
+ uByte iProduct;
+ uByte iSerialNumber;
+ uByte bNumConfigurations;
+} __packed;
+typedef struct usb_device_descriptor usb_device_descriptor_t;
+
+/* Binary Device Object Store (BOS) */
+struct usb_bos_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uWord wTotalLength;
+ uByte bNumDeviceCaps;
+} __packed;
+typedef struct usb_bos_descriptor usb_bos_descriptor_t;
+
+/* Binary Device Object Store Capability */
+struct usb_bos_cap_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDevCapabilityType;
+#define USB_DEVCAP_RESERVED 0x00
+#define USB_DEVCAP_WUSB 0x01
+#define USB_DEVCAP_USB2EXT 0x02
+#define USB_DEVCAP_SUPER_SPEED 0x03
+#define USB_DEVCAP_CONTAINER_ID 0x04
+ /* data ... */
+} __packed;
+typedef struct usb_bos_cap_descriptor usb_bos_cap_descriptor_t;
+
+struct usb_devcap_usb2ext_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDevCapabilityType;
+ uDWord bmAttributes;
+#define USB_V2EXT_LPM (1U << 1)
+#define USB_V2EXT_BESL_SUPPORTED (1U << 2)
+#define USB_V2EXT_BESL_BASELINE_VALID (1U << 3)
+#define USB_V2EXT_BESL_DEEP_VALID (1U << 4)
+#define USB_V2EXT_BESL_BASELINE_GET(x) (((x) >> 8) & 0xF)
+#define USB_V2EXT_BESL_DEEP_GET(x) (((x) >> 12) & 0xF)
+} __packed;
+typedef struct usb_devcap_usb2ext_descriptor usb_devcap_usb2ext_descriptor_t;
+
+struct usb_devcap_ss_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDevCapabilityType;
+ uByte bmAttributes;
+ uWord wSpeedsSupported;
+ uByte bFunctionalitySupport;
+ uByte bU1DevExitLat;
+ uWord wU2DevExitLat;
+} __packed;
+typedef struct usb_devcap_ss_descriptor usb_devcap_ss_descriptor_t;
+
+struct usb_devcap_container_id_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDevCapabilityType;
+ uByte bReserved;
+ uByte bContainerID;
+} __packed;
+typedef struct usb_devcap_container_id_descriptor
+ usb_devcap_container_id_descriptor_t;
+
+/* Device class codes */
+#define UDCLASS_IN_INTERFACE 0x00
+#define UDCLASS_COMM 0x02
+#define UDCLASS_HUB 0x09
+#define UDSUBCLASS_HUB 0x00
+#define UDPROTO_FSHUB 0x00
+#define UDPROTO_HSHUBSTT 0x01
+#define UDPROTO_HSHUBMTT 0x02
+#define UDPROTO_SSHUB 0x03
+#define UDCLASS_DIAGNOSTIC 0xdc
+#define UDCLASS_WIRELESS 0xe0
+#define UDSUBCLASS_RF 0x01
+#define UDPROTO_BLUETOOTH 0x01
+#define UDCLASS_VENDOR 0xff
+
+struct usb_config_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uWord wTotalLength;
+ uByte bNumInterface;
+ uByte bConfigurationValue;
+#define USB_UNCONFIG_NO 0
+ uByte iConfiguration;
+ uByte bmAttributes;
+#define UC_BUS_POWERED 0x80
+#define UC_SELF_POWERED 0x40
+#define UC_REMOTE_WAKEUP 0x20
+ uByte bMaxPower; /* max current in 2 mA units */
+#define UC_POWER_FACTOR 2
+} __packed;
+typedef struct usb_config_descriptor usb_config_descriptor_t;
+
+struct usb_interface_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bInterfaceNumber;
+ uByte bAlternateSetting;
+ uByte bNumEndpoints;
+ uByte bInterfaceClass;
+ uByte bInterfaceSubClass;
+ uByte bInterfaceProtocol;
+ uByte iInterface;
+} __packed;
+typedef struct usb_interface_descriptor usb_interface_descriptor_t;
+
+struct usb_interface_assoc_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bFirstInterface;
+ uByte bInterfaceCount;
+ uByte bFunctionClass;
+ uByte bFunctionSubClass;
+ uByte bFunctionProtocol;
+ uByte iFunction;
+} __packed;
+typedef struct usb_interface_assoc_descriptor usb_interface_assoc_descriptor_t;
+
+/* Interface class codes */
+#define UICLASS_UNSPEC 0x00
+#define UICLASS_AUDIO 0x01 /* audio */
+#define UISUBCLASS_AUDIOCONTROL 1
+#define UISUBCLASS_AUDIOSTREAM 2
+#define UISUBCLASS_MIDISTREAM 3
+
+#define UICLASS_CDC 0x02 /* communication */
+#define UISUBCLASS_DIRECT_LINE_CONTROL_MODEL 1
+#define UISUBCLASS_ABSTRACT_CONTROL_MODEL 2
+#define UISUBCLASS_TELEPHONE_CONTROL_MODEL 3
+#define UISUBCLASS_MULTICHANNEL_CONTROL_MODEL 4
+#define UISUBCLASS_CAPI_CONTROLMODEL 5
+#define UISUBCLASS_ETHERNET_NETWORKING_CONTROL_MODEL 6
+#define UISUBCLASS_ATM_NETWORKING_CONTROL_MODEL 7
+#define UISUBCLASS_WIRELESS_HANDSET_CM 8
+#define UISUBCLASS_DEVICE_MGMT 9
+#define UISUBCLASS_MOBILE_DIRECT_LINE_MODEL 10
+#define UISUBCLASS_OBEX 11
+#define UISUBCLASS_ETHERNET_EMULATION_MODEL 12
+#define UISUBCLASS_NETWORK_CONTROL_MODEL 13
+
+#define UIPROTO_CDC_NONE 0
+#define UIPROTO_CDC_AT 1
+#define UIPROTO_CDC_EEM 7
+
+#define UICLASS_HID 0x03
+#define UISUBCLASS_BOOT 1
+#define UIPROTO_BOOT_KEYBOARD 1
+#define UIPROTO_MOUSE 2
+
+#define UICLASS_PHYSICAL 0x05
+#define UICLASS_IMAGE 0x06
+#define UISUBCLASS_SIC 1 /* still image class */
+#define UICLASS_PRINTER 0x07
+#define UISUBCLASS_PRINTER 1
+#define UIPROTO_PRINTER_UNI 1
+#define UIPROTO_PRINTER_BI 2
+#define UIPROTO_PRINTER_1284 3
+
+#define UICLASS_MASS 0x08
+#define UISUBCLASS_RBC 1
+#define UISUBCLASS_SFF8020I 2
+#define UISUBCLASS_QIC157 3
+#define UISUBCLASS_UFI 4
+#define UISUBCLASS_SFF8070I 5
+#define UISUBCLASS_SCSI 6
+#define UIPROTO_MASS_CBI_I 0
+#define UIPROTO_MASS_CBI 1
+#define UIPROTO_MASS_BBB_OLD 2 /* Not in the spec anymore */
+#define UIPROTO_MASS_BBB 80 /* 'P' for the Iomega Zip drive */
+
+#define UICLASS_HUB 0x09
+#define UISUBCLASS_HUB 0
+#define UIPROTO_FSHUB 0
+#define UIPROTO_HSHUBSTT 0 /* Yes, same as previous */
+#define UIPROTO_HSHUBMTT 1
+
+#define UICLASS_CDC_DATA 0x0a
+#define UISUBCLASS_DATA 0x00
+#define UIPROTO_DATA_ISDNBRI 0x30 /* Physical iface */
+#define UIPROTO_DATA_HDLC 0x31 /* HDLC */
+#define UIPROTO_DATA_TRANSPARENT 0x32 /* Transparent */
+#define UIPROTO_DATA_Q921M 0x50 /* Management for Q921 */
+#define UIPROTO_DATA_Q921 0x51 /* Data for Q921 */
+#define UIPROTO_DATA_Q921TM 0x52 /* TEI multiplexer for Q921 */
+#define UIPROTO_DATA_V42BIS 0x90 /* Data compression */
+#define UIPROTO_DATA_Q931 0x91 /* Euro-ISDN */
+#define UIPROTO_DATA_V120 0x92 /* V.24 rate adaption */
+#define UIPROTO_DATA_CAPI 0x93 /* CAPI 2.0 commands */
+#define UIPROTO_DATA_HOST_BASED 0xfd /* Host based driver */
+#define UIPROTO_DATA_PUF 0xfe /* see Prot. Unit Func. Desc. */
+#define UIPROTO_DATA_VENDOR 0xff /* Vendor specific */
+#define UIPROTO_DATA_NCM 0x01 /* Network Control Model */
+
+#define UICLASS_SMARTCARD 0x0b
+#define UICLASS_FIRM_UPD 0x0c
+#define UICLASS_SECURITY 0x0d
+#define UICLASS_DIAGNOSTIC 0xdc
+#define UICLASS_WIRELESS 0xe0
+#define UISUBCLASS_RF 0x01
+#define UIPROTO_BLUETOOTH 0x01
+#define UIPROTO_RNDIS 0x03
+
+#define UICLASS_IAD 0xEF /* Interface Association Descriptor */
+#define UISUBCLASS_SYNC 0x01
+#define UIPROTO_ACTIVESYNC 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 UISUBCLASS_VENDOR 0xff
+#define UIPROTO_XBOX360_GAMEPAD 0x01
+
+struct usb_endpoint_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bEndpointAddress;
+#define UE_GET_DIR(a) ((a) & 0x80)
+#define UE_SET_DIR(a,d) ((a) | (((d)&1) << 7))
+#define UE_DIR_IN 0x80 /* IN-token endpoint, fixed */
+#define UE_DIR_OUT 0x00 /* OUT-token endpoint, fixed */
+#define UE_DIR_RX 0xfd /* for internal use only! */
+#define UE_DIR_TX 0xfe /* for internal use only! */
+#define UE_DIR_ANY 0xff /* for internal use only! */
+#define UE_ADDR 0x0f
+#define UE_ADDR_ANY 0xff /* for internal use only! */
+#define UE_GET_ADDR(a) ((a) & UE_ADDR)
+ uByte bmAttributes;
+#define UE_XFERTYPE 0x03
+#define UE_CONTROL 0x00
+#define UE_ISOCHRONOUS 0x01
+#define UE_BULK 0x02
+#define UE_INTERRUPT 0x03
+#define UE_BULK_INTR 0xfe /* for internal use only! */
+#define UE_TYPE_ANY 0xff /* for internal use only! */
+#define UE_GET_XFERTYPE(a) ((a) & UE_XFERTYPE)
+#define UE_ISO_TYPE 0x0c
+#define UE_ISO_ASYNC 0x04
+#define UE_ISO_ADAPT 0x08
+#define UE_ISO_SYNC 0x0c
+#define UE_GET_ISO_TYPE(a) ((a) & UE_ISO_TYPE)
+#define UE_ISO_USAGE 0x30
+#define UE_ISO_USAGE_DATA 0x00
+#define UE_ISO_USAGE_FEEDBACK 0x10
+#define UE_ISO_USAGE_IMPLICT_FB 0x20
+#define UE_GET_ISO_USAGE(a) ((a) & UE_ISO_USAGE)
+ uWord wMaxPacketSize;
+#define UE_ZERO_MPS 0xFFFF /* for internal use only */
+ uByte bInterval;
+} __packed;
+typedef struct usb_endpoint_descriptor usb_endpoint_descriptor_t;
+
+struct usb_endpoint_ss_comp_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bMaxBurst;
+ uByte bmAttributes;
+#define UE_GET_BULK_STREAMS(x) ((x) & 0x0F)
+#define UE_GET_SS_ISO_MULT(x) ((x) & 0x03)
+ uWord wBytesPerInterval;
+} __packed;
+typedef struct usb_endpoint_ss_comp_descriptor
+ usb_endpoint_ss_comp_descriptor_t;
+
+struct usb_string_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uWord bString[126];
+ uByte bUnused;
+} __packed;
+typedef struct usb_string_descriptor usb_string_descriptor_t;
+
+#define USB_MAKE_STRING_DESC(m,name) \
+static const struct { \
+ uByte bLength; \
+ uByte bDescriptorType; \
+ uByte bData[sizeof((uint8_t []){m})]; \
+} __packed name = { \
+ .bLength = sizeof(name), \
+ .bDescriptorType = UDESC_STRING, \
+ .bData = { m }, \
+}
+
+struct usb_string_lang {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bData[2];
+} __packed;
+typedef struct usb_string_lang usb_string_lang_t;
+
+struct usb_hub_descriptor {
+ uByte bDescLength;
+ uByte bDescriptorType;
+ uByte bNbrPorts;
+ uWord wHubCharacteristics;
+#define UHD_PWR 0x0003
+#define UHD_PWR_GANGED 0x0000
+#define UHD_PWR_INDIVIDUAL 0x0001
+#define UHD_PWR_NO_SWITCH 0x0002
+#define UHD_COMPOUND 0x0004
+#define UHD_OC 0x0018
+#define UHD_OC_GLOBAL 0x0000
+#define UHD_OC_INDIVIDUAL 0x0008
+#define UHD_OC_NONE 0x0010
+#define UHD_TT_THINK 0x0060
+#define UHD_TT_THINK_8 0x0000
+#define UHD_TT_THINK_16 0x0020
+#define UHD_TT_THINK_24 0x0040
+#define UHD_TT_THINK_32 0x0060
+#define UHD_PORT_IND 0x0080
+ uByte bPwrOn2PwrGood; /* delay in 2 ms units */
+#define UHD_PWRON_FACTOR 2
+ uByte bHubContrCurrent;
+ uByte DeviceRemovable[32]; /* max 255 ports */
+#define UHD_NOT_REMOV(desc, i) \
+ (((desc)->DeviceRemovable[(i)/8] >> ((i) % 8)) & 1)
+ uByte PortPowerCtrlMask[1]; /* deprecated */
+} __packed;
+typedef struct usb_hub_descriptor usb_hub_descriptor_t;
+
+struct usb_hub_ss_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bNbrPorts;
+ uWord wHubCharacteristics;
+ uByte bPwrOn2PwrGood; /* delay in 2 ms units */
+ uByte bHubContrCurrent;
+ uByte bHubHdrDecLat;
+ uWord wHubDelay;
+ uByte DeviceRemovable[32]; /* max 255 ports */
+} __packed;
+typedef struct usb_hub_ss_descriptor usb_hub_ss_descriptor_t;
+
+/* minimum HUB descriptor (8-ports maximum) */
+struct usb_hub_descriptor_min {
+ uByte bDescLength;
+ uByte bDescriptorType;
+ uByte bNbrPorts;
+ uWord wHubCharacteristics;
+ uByte bPwrOn2PwrGood;
+ uByte bHubContrCurrent;
+ uByte DeviceRemovable[1];
+ uByte PortPowerCtrlMask[1];
+} __packed;
+typedef struct usb_hub_descriptor_min usb_hub_descriptor_min_t;
+
+struct usb_device_qualifier {
+ uByte bLength;
+ uByte bDescriptorType;
+ uWord bcdUSB;
+ uByte bDeviceClass;
+ uByte bDeviceSubClass;
+ uByte bDeviceProtocol;
+ uByte bMaxPacketSize0;
+ uByte bNumConfigurations;
+ uByte bReserved;
+} __packed;
+typedef struct usb_device_qualifier usb_device_qualifier_t;
+
+struct usb_otg_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bmAttributes;
+#define UOTG_SRP 0x01
+#define UOTG_HNP 0x02
+} __packed;
+typedef struct usb_otg_descriptor usb_otg_descriptor_t;
+
+/* OTG feature selectors */
+#define UOTG_B_HNP_ENABLE 3
+#define UOTG_A_HNP_SUPPORT 4
+#define UOTG_A_ALT_HNP_SUPPORT 5
+
+struct usb_status {
+ uWord wStatus;
+/* Device status flags */
+#define UDS_SELF_POWERED 0x0001
+#define UDS_REMOTE_WAKEUP 0x0002
+/* Endpoint status flags */
+#define UES_HALT 0x0001
+} __packed;
+typedef struct usb_status usb_status_t;
+
+struct usb_hub_status {
+ uWord wHubStatus;
+#define UHS_LOCAL_POWER 0x0001
+#define UHS_OVER_CURRENT 0x0002
+ uWord wHubChange;
+} __packed;
+typedef struct usb_hub_status usb_hub_status_t;
+
+struct usb_port_status {
+ uWord wPortStatus;
+#define UPS_CURRENT_CONNECT_STATUS 0x0001
+#define UPS_PORT_ENABLED 0x0002
+#define UPS_SUSPEND 0x0004
+#define UPS_OVERCURRENT_INDICATOR 0x0008
+#define UPS_RESET 0x0010
+#define UPS_PORT_L1 0x0020 /* USB 2.0 only */
+/* The link-state bits are valid for Super-Speed USB HUBs */
+#define UPS_PORT_LINK_STATE_GET(x) (((x) >> 5) & 0xF)
+#define UPS_PORT_LINK_STATE_SET(x) (((x) & 0xF) << 5)
+#define UPS_PORT_LS_U0 0x00
+#define UPS_PORT_LS_U1 0x01
+#define UPS_PORT_LS_U2 0x02
+#define UPS_PORT_LS_U3 0x03
+#define UPS_PORT_LS_SS_DIS 0x04
+#define UPS_PORT_LS_RX_DET 0x05
+#define UPS_PORT_LS_SS_INA 0x06
+#define UPS_PORT_LS_POLL 0x07
+#define UPS_PORT_LS_RECOVER 0x08
+#define UPS_PORT_LS_HOT_RST 0x09
+#define UPS_PORT_LS_COMP_MODE 0x0A
+#define UPS_PORT_LS_LOOPBACK 0x0B
+#define UPS_PORT_LS_RESUME 0x0F
+#define UPS_PORT_POWER 0x0100
+#define UPS_PORT_POWER_SS 0x0200 /* super-speed only */
+#define UPS_LOW_SPEED 0x0200
+#define UPS_HIGH_SPEED 0x0400
+#define UPS_OTHER_SPEED 0x0600 /* currently FreeBSD specific */
+#define UPS_PORT_TEST 0x0800
+#define UPS_PORT_INDICATOR 0x1000
+#define UPS_PORT_MODE_DEVICE 0x8000 /* currently FreeBSD specific */
+ uWord wPortChange;
+#define UPS_C_CONNECT_STATUS 0x0001
+#define UPS_C_PORT_ENABLED 0x0002
+#define UPS_C_SUSPEND 0x0004
+#define UPS_C_OVERCURRENT_INDICATOR 0x0008
+#define UPS_C_PORT_RESET 0x0010
+#define UPS_C_PORT_L1 0x0020 /* USB 2.0 only */
+#define UPS_C_BH_PORT_RESET 0x0020 /* USB 3.0 only */
+#define UPS_C_PORT_LINK_STATE 0x0040
+#define UPS_C_PORT_CONFIG_ERROR 0x0080
+} __packed;
+typedef struct usb_port_status usb_port_status_t;
+
+/*
+ * The "USB_SPEED" macros defines all the supported USB speeds.
+ */
+enum usb_dev_speed {
+ USB_SPEED_VARIABLE,
+ USB_SPEED_LOW,
+ USB_SPEED_FULL,
+ USB_SPEED_HIGH,
+ USB_SPEED_SUPER,
+};
+#define USB_SPEED_MAX (USB_SPEED_SUPER+1)
+
+/*
+ * The "USB_REV" macros defines all the supported USB revisions.
+ */
+enum usb_revision {
+ USB_REV_UNKNOWN,
+ USB_REV_PRE_1_0,
+ USB_REV_1_0,
+ USB_REV_1_1,
+ USB_REV_2_0,
+ USB_REV_2_5,
+ USB_REV_3_0
+};
+#define USB_REV_MAX (USB_REV_3_0+1)
+
+/*
+ * Supported host controller modes.
+ */
+enum usb_hc_mode {
+ USB_MODE_HOST, /* initiates transfers */
+ USB_MODE_DEVICE, /* bus transfer target */
+ USB_MODE_DUAL /* can be host or device */
+};
+#define USB_MODE_MAX (USB_MODE_DUAL+1)
+
+/*
+ * The "USB_STATE" enums define all the supported device states.
+ */
+enum usb_dev_state {
+ USB_STATE_DETACHED,
+ USB_STATE_ATTACHED,
+ USB_STATE_POWERED,
+ USB_STATE_ADDRESSED,
+ USB_STATE_CONFIGURED,
+};
+#define USB_STATE_MAX (USB_STATE_CONFIGURED+1)
+
+/*
+ * The "USB_EP_MODE" macros define all the currently supported
+ * endpoint modes.
+ */
+enum usb_ep_mode {
+ USB_EP_MODE_DEFAULT,
+ USB_EP_MODE_STREAMS, /* USB3.0 specific */
+ USB_EP_MODE_HW_MASS_STORAGE,
+ USB_EP_MODE_HW_SERIAL,
+ USB_EP_MODE_HW_ETHERNET_CDC,
+ USB_EP_MODE_HW_ETHERNET_NCM,
+ USB_EP_MODE_MAX
+};
+#endif /* _USB_STANDARD_H_ */
diff --git a/sys/dev/usb/usb_bus.h b/sys/dev/usb/usb_bus.h
new file mode 100644
index 000000000000..ad7b661c2ac0
--- /dev/null
+++ b/sys/dev/usb/usb_bus.h
@@ -0,0 +1,127 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008-2019 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_BUS_H_
+#define _USB_BUS_H_
+
+struct usb_fs_privdata;
+
+/*
+ * The following structure defines the USB explore message sent to the USB
+ * explore process.
+ */
+
+struct usb_bus_msg {
+ struct usb_proc_msg hdr;
+ struct usb_bus *bus;
+};
+
+/*
+ * The following structure defines an USB BUS. There is one USB BUS
+ * for every Host or Device controller.
+ */
+struct usb_bus {
+#if USB_HAVE_ROOT_MOUNT_HOLD
+ struct root_hold_token *bus_roothold;
+#endif
+
+/* convenience macros */
+#define USB_BUS_TT_PROC(bus) USB_BUS_NON_GIANT_ISOC_PROC(bus)
+#define USB_BUS_CS_PROC(bus) USB_BUS_NON_GIANT_ISOC_PROC(bus)
+
+#if USB_HAVE_PER_BUS_PROCESS
+#define USB_BUS_GIANT_PROC(bus) (&(bus)->giant_callback_proc)
+#define USB_BUS_NON_GIANT_ISOC_PROC(bus) (&(bus)->non_giant_isoc_callback_proc)
+#define USB_BUS_NON_GIANT_BULK_PROC(bus) (&(bus)->non_giant_bulk_callback_proc)
+#define USB_BUS_EXPLORE_PROC(bus) (&(bus)->explore_proc)
+#define USB_BUS_CONTROL_XFER_PROC(bus) (&(bus)->control_xfer_proc)
+ /*
+ * There are three callback processes. One for Giant locked
+ * callbacks. One for non-Giant locked non-periodic callbacks
+ * and one for non-Giant locked periodic callbacks. This
+ * should avoid congestion and reduce response time in most
+ * cases.
+ */
+ struct usb_process giant_callback_proc;
+ struct usb_process non_giant_isoc_callback_proc;
+ struct usb_process non_giant_bulk_callback_proc;
+
+ /* Explore process */
+ struct usb_process explore_proc;
+
+ /* Control request process */
+ struct usb_process control_xfer_proc;
+#endif
+
+ struct usb_bus_msg explore_msg[2];
+ struct usb_bus_msg detach_msg[2];
+ struct usb_bus_msg attach_msg[2];
+ struct usb_bus_msg suspend_msg[2];
+ struct usb_bus_msg resume_msg[2];
+ struct usb_bus_msg reset_msg[2];
+ struct usb_bus_msg shutdown_msg[2];
+#if USB_HAVE_UGEN
+ struct usb_bus_msg cleanup_msg[2];
+ SLIST_HEAD(,usb_fs_privdata) pd_cleanup_list;
+#endif
+ /*
+ * This mutex protects the USB hardware:
+ */
+ struct mtx bus_mtx;
+ struct mtx bus_spin_lock;
+ struct usb_xfer_queue intr_q;
+ struct usb_callout power_wdog; /* power management */
+
+ device_t parent;
+ device_t bdev; /* filled by HC driver */
+
+#if USB_HAVE_BUSDMA
+ struct usb_dma_parent_tag dma_parent_tag[1];
+ struct usb_dma_tag dma_tags[USB_BUS_DMA_TAG_MAX];
+#endif
+ const struct usb_bus_methods *methods; /* filled by HC driver */
+ struct usb_device **devices;
+
+ struct ifnet *ifp; /* only for USB Packet Filter */
+
+ usb_power_mask_t hw_power_state; /* see USB_HW_POWER_XXX */
+ usb_size_t uframe_usage[USB_HS_MICRO_FRAMES_MAX];
+
+ uint16_t isoc_time_last; /* in milliseconds */
+
+ uint8_t alloc_failed; /* Set if memory allocation failed. */
+ uint8_t driver_added_refcount; /* Current driver generation count */
+ enum usb_revision usbrev; /* USB revision. See "USB_REV_XXX". */
+
+ uint8_t devices_max; /* maximum number of USB devices */
+ uint8_t do_probe; /* set if USB should be re-probed */
+ uint8_t no_explore; /* don't explore USB ports */
+ uint8_t dma_bits; /* number of DMA address lines */
+ uint8_t control_ep_quirk; /* need 64kByte buffer for data stage */
+};
+
+#endif /* _USB_BUS_H_ */
diff --git a/sys/dev/usb/usb_busdma.c b/sys/dev/usb/usb_busdma.c
new file mode 100644
index 000000000000..c653fcad5394
--- /dev/null
+++ b/sys/dev/usb/usb_busdma.c
@@ -0,0 +1,1110 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+
+#define USB_DEBUG_VAR usb_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#if USB_HAVE_BUSDMA
+static void usb_dma_tag_create(struct usb_dma_tag *, usb_size_t, usb_size_t);
+static void usb_dma_tag_destroy(struct usb_dma_tag *);
+static void usb_dma_lock_cb(void *, bus_dma_lock_op_t);
+static void usb_pc_alloc_mem_cb(void *, bus_dma_segment_t *, int, int);
+static void usb_pc_load_mem_cb(void *, bus_dma_segment_t *, int, int);
+static void usb_pc_common_mem_cb(void *, bus_dma_segment_t *, int, int,
+ uint8_t);
+#endif
+
+/*------------------------------------------------------------------------*
+ * usbd_get_page - lookup DMA-able memory for the given offset
+ *
+ * NOTE: Only call this function when the "page_cache" structure has
+ * been properly initialized !
+ *------------------------------------------------------------------------*/
+void
+usbd_get_page(struct usb_page_cache *pc, usb_frlength_t offset,
+ struct usb_page_search *res)
+{
+#if USB_HAVE_BUSDMA
+ struct usb_page *page;
+
+ if (pc->page_start) {
+ /* Case 1 - something has been loaded into DMA */
+
+ if (pc->buffer) {
+ /* Case 1a - Kernel Virtual Address */
+
+ res->buffer = USB_ADD_BYTES(pc->buffer, offset);
+ }
+ offset += pc->page_offset_buf;
+
+ /* compute destination page */
+
+ page = pc->page_start;
+
+ if (pc->ismultiseg) {
+ page += (offset / USB_PAGE_SIZE);
+
+ offset %= USB_PAGE_SIZE;
+
+ res->length = USB_PAGE_SIZE - offset;
+ res->physaddr = page->physaddr + offset;
+ } else {
+ res->length = (usb_size_t)-1;
+ res->physaddr = page->physaddr + offset;
+ }
+ if (!pc->buffer) {
+ /* Case 1b - Non Kernel Virtual Address */
+
+ res->buffer = USB_ADD_BYTES(page->buffer, offset);
+ }
+ return;
+ }
+#endif
+ /* Case 2 - Plain PIO */
+
+ res->buffer = USB_ADD_BYTES(pc->buffer, offset);
+ res->length = (usb_size_t)-1;
+#if USB_HAVE_BUSDMA
+ res->physaddr = 0;
+#endif
+}
+
+/*------------------------------------------------------------------------*
+ * usb_pc_buffer_is_aligned - verify alignment
+ *
+ * This function is used to check if a page cache buffer is properly
+ * aligned to reduce the use of bounce buffers in PIO mode.
+ *------------------------------------------------------------------------*/
+uint8_t
+usb_pc_buffer_is_aligned(struct usb_page_cache *pc, usb_frlength_t offset,
+ usb_frlength_t len, usb_frlength_t mask)
+{
+ struct usb_page_search buf_res;
+
+ while (len != 0) {
+ usbd_get_page(pc, offset, &buf_res);
+
+ if (buf_res.length > len)
+ buf_res.length = len;
+ if (USB_P2U(buf_res.buffer) & mask)
+ return (0);
+ if (buf_res.length & mask)
+ return (0);
+
+ offset += buf_res.length;
+ len -= buf_res.length;
+ }
+ return (1);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_copy_in - copy directly to DMA-able memory
+ *------------------------------------------------------------------------*/
+void
+usbd_copy_in(struct usb_page_cache *cache, usb_frlength_t offset,
+ const void *ptr, usb_frlength_t len)
+{
+ struct usb_page_search buf_res;
+
+ while (len != 0) {
+ usbd_get_page(cache, offset, &buf_res);
+
+ if (buf_res.length > len) {
+ buf_res.length = len;
+ }
+ memcpy(buf_res.buffer, ptr, buf_res.length);
+
+ offset += buf_res.length;
+ len -= buf_res.length;
+ ptr = USB_ADD_BYTES(ptr, buf_res.length);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_copy_in_user - copy directly to DMA-able memory from userland
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_USER_IO
+int
+usbd_copy_in_user(struct usb_page_cache *cache, usb_frlength_t offset,
+ const void *ptr, usb_frlength_t len)
+{
+ struct usb_page_search buf_res;
+ int error;
+
+ while (len != 0) {
+ usbd_get_page(cache, offset, &buf_res);
+
+ if (buf_res.length > len) {
+ buf_res.length = len;
+ }
+ error = copyin(ptr, buf_res.buffer, buf_res.length);
+ if (error)
+ return (error);
+
+ offset += buf_res.length;
+ len -= buf_res.length;
+ ptr = USB_ADD_BYTES(ptr, buf_res.length);
+ }
+ return (0); /* success */
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usbd_m_copy_in - copy a mbuf chain directly into DMA-able memory
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_MBUF
+struct usb_m_copy_in_arg {
+ struct usb_page_cache *cache;
+ usb_frlength_t dst_offset;
+};
+
+static int
+usbd_m_copy_in_cb(void *arg, void *src, uint32_t count)
+{
+ struct usb_m_copy_in_arg *ua = arg;
+
+ usbd_copy_in(ua->cache, ua->dst_offset, src, count);
+ ua->dst_offset += count;
+ return (0);
+}
+
+void
+usbd_m_copy_in(struct usb_page_cache *cache, usb_frlength_t dst_offset,
+ struct mbuf *m, usb_size_t src_offset, usb_frlength_t src_len)
+{
+ struct usb_m_copy_in_arg arg = {cache, dst_offset};
+ (void) m_apply(m, src_offset, src_len, &usbd_m_copy_in_cb, &arg);
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usb_uiomove - factored out code
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_USER_IO
+int
+usb_uiomove(struct usb_page_cache *pc, struct uio *uio,
+ usb_frlength_t pc_offset, usb_frlength_t len)
+{
+ struct usb_page_search res;
+ int error = 0;
+
+ while (len != 0) {
+ usbd_get_page(pc, pc_offset, &res);
+
+ if (res.length > len) {
+ res.length = len;
+ }
+ /*
+ * "uiomove()" can sleep so one needs to make a wrapper,
+ * exiting the mutex and checking things
+ */
+ error = uiomove(res.buffer, res.length, uio);
+
+ if (error) {
+ break;
+ }
+ pc_offset += res.length;
+ len -= res.length;
+ }
+ return (error);
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usbd_copy_out - copy directly from DMA-able memory
+ *------------------------------------------------------------------------*/
+void
+usbd_copy_out(struct usb_page_cache *cache, usb_frlength_t offset,
+ void *ptr, usb_frlength_t len)
+{
+ struct usb_page_search res;
+
+ while (len != 0) {
+ usbd_get_page(cache, offset, &res);
+
+ if (res.length > len) {
+ res.length = len;
+ }
+ memcpy(ptr, res.buffer, res.length);
+
+ offset += res.length;
+ len -= res.length;
+ ptr = USB_ADD_BYTES(ptr, res.length);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_copy_out_user - copy directly from DMA-able memory to userland
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_USER_IO
+int
+usbd_copy_out_user(struct usb_page_cache *cache, usb_frlength_t offset,
+ void *ptr, usb_frlength_t len)
+{
+ struct usb_page_search res;
+ int error;
+
+ while (len != 0) {
+ usbd_get_page(cache, offset, &res);
+
+ if (res.length > len) {
+ res.length = len;
+ }
+ error = copyout(res.buffer, ptr, res.length);
+ if (error)
+ return (error);
+
+ offset += res.length;
+ len -= res.length;
+ ptr = USB_ADD_BYTES(ptr, res.length);
+ }
+ return (0); /* success */
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usbd_frame_zero - zero DMA-able memory
+ *------------------------------------------------------------------------*/
+void
+usbd_frame_zero(struct usb_page_cache *cache, usb_frlength_t offset,
+ usb_frlength_t len)
+{
+ struct usb_page_search res;
+
+ while (len != 0) {
+ usbd_get_page(cache, offset, &res);
+
+ if (res.length > len) {
+ res.length = len;
+ }
+ memset(res.buffer, 0, res.length);
+
+ offset += res.length;
+ len -= res.length;
+ }
+}
+
+#if USB_HAVE_BUSDMA
+
+/*------------------------------------------------------------------------*
+ * usb_dma_lock_cb - dummy callback
+ *------------------------------------------------------------------------*/
+static void
+usb_dma_lock_cb(void *arg, bus_dma_lock_op_t op)
+{
+ /* we use "mtx_owned()" instead of this function */
+}
+
+/*------------------------------------------------------------------------*
+ * usb_dma_tag_create - allocate a DMA tag
+ *
+ * NOTE: If the "align" parameter has a value of 1 the DMA-tag will
+ * allow multi-segment mappings. Else all mappings are single-segment.
+ *------------------------------------------------------------------------*/
+static void
+usb_dma_tag_create(struct usb_dma_tag *udt,
+ usb_size_t size, usb_size_t align)
+{
+ bus_dma_tag_t tag;
+
+ if (bus_dma_tag_create
+ ( /* parent */ udt->tag_parent->tag,
+ /* alignment */ align,
+ /* boundary */ 0,
+ /* lowaddr */ (2ULL << (udt->tag_parent->dma_bits - 1)) - 1,
+ /* highaddr */ BUS_SPACE_MAXADDR,
+ /* filter */ NULL,
+ /* filterarg */ NULL,
+ /* maxsize */ size,
+ /* nsegments */ (align == 1 && size > 1) ?
+ (2 + (size / USB_PAGE_SIZE)) : 1,
+ /* maxsegsz */ (align == 1 && size > USB_PAGE_SIZE) ?
+ USB_PAGE_SIZE : size,
+ /* flags */ BUS_DMA_KEEP_PG_OFFSET,
+ /* lockfn */ &usb_dma_lock_cb,
+ /* lockarg */ NULL,
+ &tag)) {
+ tag = NULL;
+ }
+ udt->tag = tag;
+}
+
+/*------------------------------------------------------------------------*
+ * usb_dma_tag_free - free a DMA tag
+ *------------------------------------------------------------------------*/
+static void
+usb_dma_tag_destroy(struct usb_dma_tag *udt)
+{
+ bus_dma_tag_destroy(udt->tag);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_pc_alloc_mem_cb - BUS-DMA callback function
+ *------------------------------------------------------------------------*/
+static void
+usb_pc_alloc_mem_cb(void *arg, bus_dma_segment_t *segs,
+ int nseg, int error)
+{
+ usb_pc_common_mem_cb(arg, segs, nseg, error, 0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_pc_load_mem_cb - BUS-DMA callback function
+ *------------------------------------------------------------------------*/
+static void
+usb_pc_load_mem_cb(void *arg, bus_dma_segment_t *segs,
+ int nseg, int error)
+{
+ usb_pc_common_mem_cb(arg, segs, nseg, error, 1);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_pc_common_mem_cb - BUS-DMA callback function
+ *------------------------------------------------------------------------*/
+static void
+usb_pc_common_mem_cb(void *arg, bus_dma_segment_t *segs,
+ int nseg, int error, uint8_t isload)
+{
+ struct usb_dma_parent_tag *uptag;
+ struct usb_page_cache *pc;
+ struct usb_page *pg;
+ usb_size_t rem;
+ bus_size_t off;
+ uint8_t owned;
+
+ pc = arg;
+ uptag = pc->tag_parent;
+
+ /*
+ * XXX There is sometimes recursive locking here.
+ * XXX We should try to find a better solution.
+ * XXX Until further the "owned" variable does
+ * XXX the trick.
+ */
+
+ if (error) {
+ goto done;
+ }
+
+ off = 0;
+ pg = pc->page_start;
+ pg->physaddr = rounddown2(segs->ds_addr, USB_PAGE_SIZE);
+ rem = segs->ds_addr & (USB_PAGE_SIZE - 1);
+ pc->page_offset_buf = rem;
+ pc->page_offset_end += rem;
+#ifdef USB_DEBUG
+ if (nseg > 1) {
+ int x;
+
+ for (x = 0; x != nseg - 1; x++) {
+ if (((segs[x].ds_addr + segs[x].ds_len) & (USB_PAGE_SIZE - 1)) ==
+ ((segs[x + 1].ds_addr & (USB_PAGE_SIZE - 1))))
+ continue;
+ /*
+ * This check verifies there is no page offset
+ * hole between any of the segments. See the
+ * BUS_DMA_KEEP_PG_OFFSET flag.
+ */
+ DPRINTFN(0, "Page offset was not preserved\n");
+ error = 1;
+ goto done;
+ }
+ }
+#endif
+ while (pc->ismultiseg) {
+ off += USB_PAGE_SIZE;
+ if (off >= (segs->ds_len + rem)) {
+ /* page crossing */
+ nseg--;
+ segs++;
+ off = 0;
+ rem = 0;
+ if (nseg == 0)
+ break;
+ }
+ pg++;
+ pg->physaddr = rounddown2(segs->ds_addr + off, USB_PAGE_SIZE);
+ }
+
+done:
+ owned = mtx_owned(uptag->mtx);
+ if (!owned)
+ USB_MTX_LOCK(uptag->mtx);
+
+ uptag->dma_error = (error ? 1 : 0);
+ if (isload) {
+ (uptag->func) (uptag);
+ } else {
+ cv_broadcast(uptag->cv);
+ }
+ if (!owned)
+ USB_MTX_UNLOCK(uptag->mtx);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_pc_alloc_mem - allocate DMA'able memory
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+uint8_t
+usb_pc_alloc_mem(struct usb_page_cache *pc, struct usb_page *pg,
+ usb_size_t size, usb_size_t align)
+{
+ struct usb_dma_parent_tag *uptag;
+ struct usb_dma_tag *utag;
+ bus_dmamap_t map;
+ void *ptr;
+ int err;
+
+ uptag = pc->tag_parent;
+
+ if (align != 1) {
+ /*
+ * The alignment must be greater or equal to the
+ * "size" else the object can be split between two
+ * memory pages and we get a problem!
+ */
+ while (align < size) {
+ align *= 2;
+ if (align == 0) {
+ goto error;
+ }
+ }
+#if 1
+ /*
+ * XXX BUS-DMA workaround - FIXME later:
+ *
+ * We assume that the alignment at this point of
+ * the code is greater than or equal to the size and
+ * less than two times the size, so that if we double
+ * the size, the size will be greater than the
+ * alignment.
+ *
+ * The bus-dma system has a check for "alignment"
+ * being less than "size". If that check fails we end
+ * up using contigmalloc which is page based even for
+ * small allocations. Try to avoid that to save
+ * memory, hence we sometimes to a large number of
+ * small allocations!
+ */
+ if (size <= (USB_PAGE_SIZE / 2)) {
+ size *= 2;
+ }
+#endif
+ }
+ /* get the correct DMA tag */
+ utag = usb_dma_tag_find(uptag, size, align);
+ if (utag == NULL) {
+ goto error;
+ }
+ /* allocate memory */
+ if (bus_dmamem_alloc(
+ utag->tag, &ptr, (BUS_DMA_WAITOK | BUS_DMA_COHERENT), &map)) {
+ goto error;
+ }
+ /* setup page cache */
+ pc->buffer = ptr;
+ pc->page_start = pg;
+ pc->page_offset_buf = 0;
+ pc->page_offset_end = size;
+ pc->map = map;
+ pc->tag = utag->tag;
+ pc->ismultiseg = (align == 1);
+
+ USB_MTX_LOCK(uptag->mtx);
+
+ /* load memory into DMA */
+ err = bus_dmamap_load(
+ utag->tag, map, ptr, size, &usb_pc_alloc_mem_cb,
+ pc, (BUS_DMA_WAITOK | BUS_DMA_COHERENT));
+
+ if (err == EINPROGRESS) {
+ cv_wait(uptag->cv, uptag->mtx);
+ err = 0;
+ }
+ USB_MTX_UNLOCK(uptag->mtx);
+
+ if (err || uptag->dma_error) {
+ bus_dmamem_free(utag->tag, ptr, map);
+ goto error;
+ }
+ pc->isloaded = 1;
+ memset(ptr, 0, size);
+
+ usb_pc_cpu_flush(pc);
+
+ return (0);
+
+error:
+ /* reset most of the page cache */
+ pc->buffer = NULL;
+ pc->page_start = NULL;
+ pc->page_offset_buf = 0;
+ pc->page_offset_end = 0;
+ pc->isloaded = 0;
+ pc->map = NULL;
+ pc->tag = NULL;
+ return (1);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_pc_free_mem - free DMA memory
+ *
+ * This function is NULL safe.
+ *------------------------------------------------------------------------*/
+void
+usb_pc_free_mem(struct usb_page_cache *pc)
+{
+ if (pc && pc->buffer) {
+ if (pc->isloaded)
+ bus_dmamap_unload(pc->tag, pc->map);
+
+ bus_dmamem_free(pc->tag, pc->buffer, pc->map);
+
+ pc->buffer = NULL;
+ pc->isloaded = 0;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_pc_load_mem - load virtual memory into DMA
+ *
+ * Return values:
+ * 0: Success
+ * Else: Error
+ *------------------------------------------------------------------------*/
+uint8_t
+usb_pc_load_mem(struct usb_page_cache *pc, usb_size_t size, uint8_t sync)
+{
+ /* setup page cache */
+ pc->page_offset_buf = 0;
+ pc->page_offset_end = size;
+ pc->ismultiseg = 1;
+
+ USB_MTX_ASSERT(pc->tag_parent->mtx, MA_OWNED);
+
+ if (size > 0) {
+ if (sync) {
+ struct usb_dma_parent_tag *uptag;
+ int err;
+
+ uptag = pc->tag_parent;
+
+ /*
+ * We have to unload the previous loaded DMA
+ * pages before trying to load a new one!
+ */
+ if (pc->isloaded)
+ bus_dmamap_unload(pc->tag, pc->map);
+
+ /*
+ * Try to load memory into DMA.
+ */
+ err = bus_dmamap_load(
+ pc->tag, pc->map, pc->buffer, size,
+ &usb_pc_alloc_mem_cb, pc, BUS_DMA_WAITOK);
+ if (err == EINPROGRESS) {
+ cv_wait(uptag->cv, uptag->mtx);
+ err = 0;
+ }
+ if (err || uptag->dma_error) {
+ pc->isloaded = 0;
+ return (1);
+ }
+ } else {
+ /*
+ * We have to unload the previous loaded DMA
+ * pages before trying to load a new one!
+ */
+ if (pc->isloaded)
+ bus_dmamap_unload(pc->tag, pc->map);
+
+ /*
+ * Try to load memory into DMA. The callback
+ * will be called in all cases:
+ */
+ if (bus_dmamap_load(
+ pc->tag, pc->map, pc->buffer, size,
+ &usb_pc_load_mem_cb, pc, BUS_DMA_WAITOK)) {
+ }
+ }
+ pc->isloaded = 1;
+ } else {
+ if (!sync) {
+ /*
+ * Call callback so that refcount is decremented
+ * properly:
+ */
+ pc->tag_parent->dma_error = 0;
+ (pc->tag_parent->func) (pc->tag_parent);
+ }
+ }
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_pc_cpu_invalidate - invalidate CPU cache
+ *------------------------------------------------------------------------*/
+void
+usb_pc_cpu_invalidate(struct usb_page_cache *pc)
+{
+ if (pc->page_offset_end == pc->page_offset_buf) {
+ /* nothing has been loaded into this page cache! */
+ return;
+ }
+
+ /*
+ * TODO: We currently do XXX_POSTREAD and XXX_PREREAD at the
+ * same time, but in the future we should try to isolate the
+ * different cases to optimise the code. --HPS
+ */
+ bus_dmamap_sync(pc->tag, pc->map, BUS_DMASYNC_POSTREAD);
+ bus_dmamap_sync(pc->tag, pc->map, BUS_DMASYNC_PREREAD);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_pc_cpu_flush - flush CPU cache
+ *------------------------------------------------------------------------*/
+void
+usb_pc_cpu_flush(struct usb_page_cache *pc)
+{
+ if (pc->page_offset_end == pc->page_offset_buf) {
+ /* nothing has been loaded into this page cache! */
+ return;
+ }
+ bus_dmamap_sync(pc->tag, pc->map, BUS_DMASYNC_PREWRITE);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_pc_dmamap_create - create a DMA map
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+uint8_t
+usb_pc_dmamap_create(struct usb_page_cache *pc, usb_size_t size)
+{
+ struct usb_xfer_root *info;
+ struct usb_dma_tag *utag;
+
+ /* get info */
+ info = USB_DMATAG_TO_XROOT(pc->tag_parent);
+
+ /* sanity check */
+ if (info == NULL) {
+ goto error;
+ }
+ utag = usb_dma_tag_find(pc->tag_parent, size, 1);
+ if (utag == NULL) {
+ goto error;
+ }
+ /* create DMA map */
+ if (bus_dmamap_create(utag->tag, 0, &pc->map)) {
+ goto error;
+ }
+ pc->tag = utag->tag;
+ return 0; /* success */
+
+error:
+ pc->map = NULL;
+ pc->tag = NULL;
+ return 1; /* failure */
+}
+
+/*------------------------------------------------------------------------*
+ * usb_pc_dmamap_destroy
+ *
+ * This function is NULL safe.
+ *------------------------------------------------------------------------*/
+void
+usb_pc_dmamap_destroy(struct usb_page_cache *pc)
+{
+ if (pc && pc->tag) {
+ if (pc->isloaded)
+ bus_dmamap_unload(pc->tag, pc->map);
+ bus_dmamap_destroy(pc->tag, pc->map);
+ pc->tag = NULL;
+ pc->map = NULL;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_dma_tag_find - factored out code
+ *------------------------------------------------------------------------*/
+struct usb_dma_tag *
+usb_dma_tag_find(struct usb_dma_parent_tag *udpt,
+ usb_size_t size, usb_size_t align)
+{
+ struct usb_dma_tag *udt;
+ uint8_t nudt;
+
+ USB_ASSERT(align > 0, ("Invalid parameter align = 0\n"));
+ USB_ASSERT(size > 0, ("Invalid parameter size = 0\n"));
+
+ udt = udpt->utag_first;
+ nudt = udpt->utag_max;
+
+ while (nudt--) {
+ if (udt->align == 0) {
+ usb_dma_tag_create(udt, size, align);
+ if (udt->tag == NULL) {
+ return (NULL);
+ }
+ udt->align = align;
+ udt->size = size;
+ return (udt);
+ }
+ if ((udt->align == align) && (udt->size == size)) {
+ return (udt);
+ }
+ udt++;
+ }
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_dma_tag_setup - initialise USB DMA tags
+ *------------------------------------------------------------------------*/
+void
+usb_dma_tag_setup(struct usb_dma_parent_tag *udpt,
+ struct usb_dma_tag *udt, bus_dma_tag_t dmat,
+ struct mtx *mtx, usb_dma_callback_t *func,
+ uint8_t ndmabits, uint8_t nudt)
+{
+ memset(udpt, 0, sizeof(*udpt));
+
+ /* sanity checking */
+ if ((nudt == 0) ||
+ (ndmabits == 0) ||
+ (mtx == NULL)) {
+ /* something is corrupt */
+ return;
+ }
+ /* initialise condition variable */
+ cv_init(udpt->cv, "USB DMA CV");
+
+ /* store some information */
+ udpt->mtx = mtx;
+ udpt->func = func;
+ udpt->tag = dmat;
+ udpt->utag_first = udt;
+ udpt->utag_max = nudt;
+ udpt->dma_bits = ndmabits;
+
+ while (nudt--) {
+ memset(udt, 0, sizeof(*udt));
+ udt->tag_parent = udpt;
+ udt++;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bus_tag_unsetup - factored out code
+ *------------------------------------------------------------------------*/
+void
+usb_dma_tag_unsetup(struct usb_dma_parent_tag *udpt)
+{
+ struct usb_dma_tag *udt;
+ uint8_t nudt;
+
+ udt = udpt->utag_first;
+ nudt = udpt->utag_max;
+
+ while (nudt--) {
+ if (udt->align) {
+ /* destroy the USB DMA tag */
+ usb_dma_tag_destroy(udt);
+ udt->align = 0;
+ }
+ udt++;
+ }
+
+ if (udpt->utag_max) {
+ /* destroy the condition variable */
+ cv_destroy(udpt->cv);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bdma_work_loop
+ *
+ * This function handles loading of virtual buffers into DMA and is
+ * only called when "dma_refcount" is zero.
+ *------------------------------------------------------------------------*/
+void
+usb_bdma_work_loop(struct usb_xfer_queue *pq)
+{
+ struct usb_xfer_root *info;
+ struct usb_xfer *xfer;
+ usb_frcount_t nframes;
+
+ xfer = pq->curr;
+ info = xfer->xroot;
+
+ USB_MTX_ASSERT(info->xfer_mtx, MA_OWNED);
+
+ if (xfer->error) {
+ /* some error happened */
+ USB_BUS_LOCK(info->bus);
+ usbd_transfer_done(xfer, 0);
+ USB_BUS_UNLOCK(info->bus);
+ return;
+ }
+ if (!xfer->flags_int.bdma_setup) {
+ struct usb_page *pg;
+ usb_frlength_t frlength_0;
+ uint8_t isread;
+
+ xfer->flags_int.bdma_setup = 1;
+
+ /* reset BUS-DMA load state */
+
+ info->dma_error = 0;
+
+ if (xfer->flags_int.isochronous_xfr) {
+ /* only one frame buffer */
+ nframes = 1;
+ frlength_0 = xfer->sumlen;
+ } else {
+ /* can be multiple frame buffers */
+ nframes = xfer->nframes;
+ frlength_0 = xfer->frlengths[0];
+ }
+
+ /*
+ * Set DMA direction first. This is needed to
+ * select the correct cache invalidate and cache
+ * flush operations.
+ */
+ isread = USB_GET_DATA_ISREAD(xfer);
+ pg = xfer->dma_page_ptr;
+
+ if (xfer->flags_int.control_xfr &&
+ xfer->flags_int.control_hdr) {
+ /* special case */
+ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
+ /* The device controller writes to memory */
+ xfer->frbuffers[0].isread = 1;
+ } else {
+ /* The host controller reads from memory */
+ xfer->frbuffers[0].isread = 0;
+ }
+ } else {
+ /* default case */
+ xfer->frbuffers[0].isread = isread;
+ }
+
+ /*
+ * Setup the "page_start" pointer which points to an array of
+ * USB pages where information about the physical address of a
+ * page will be stored. Also initialise the "isread" field of
+ * the USB page caches.
+ */
+ xfer->frbuffers[0].page_start = pg;
+
+ info->dma_nframes = nframes;
+ info->dma_currframe = 0;
+ info->dma_frlength_0 = frlength_0;
+
+ pg += (frlength_0 / USB_PAGE_SIZE);
+ pg += 2;
+
+ while (--nframes > 0) {
+ xfer->frbuffers[nframes].isread = isread;
+ xfer->frbuffers[nframes].page_start = pg;
+
+ pg += (xfer->frlengths[nframes] / USB_PAGE_SIZE);
+ pg += 2;
+ }
+ }
+ if (info->dma_error) {
+ USB_BUS_LOCK(info->bus);
+ usbd_transfer_done(xfer, USB_ERR_DMA_LOAD_FAILED);
+ USB_BUS_UNLOCK(info->bus);
+ return;
+ }
+ if (info->dma_currframe != info->dma_nframes) {
+ if (info->dma_currframe == 0) {
+ /* special case */
+ usb_pc_load_mem(xfer->frbuffers,
+ info->dma_frlength_0, 0);
+ } else {
+ /* default case */
+ nframes = info->dma_currframe;
+ usb_pc_load_mem(xfer->frbuffers + nframes,
+ xfer->frlengths[nframes], 0);
+ }
+
+ /* advance frame index */
+ info->dma_currframe++;
+
+ return;
+ }
+ /* go ahead */
+ usb_bdma_pre_sync(xfer);
+
+ /* start loading next USB transfer, if any */
+ usb_command_wrapper(pq, NULL);
+
+ /* finally start the hardware */
+ usbd_pipe_enter(xfer);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bdma_done_event
+ *
+ * This function is called when the BUS-DMA has loaded virtual memory
+ * into DMA, if any.
+ *------------------------------------------------------------------------*/
+void
+usb_bdma_done_event(struct usb_dma_parent_tag *udpt)
+{
+ struct usb_xfer_root *info;
+
+ info = USB_DMATAG_TO_XROOT(udpt);
+
+ USB_MTX_ASSERT(info->xfer_mtx, MA_OWNED);
+
+ /* copy error */
+ info->dma_error = udpt->dma_error;
+
+ /* enter workloop again */
+ usb_command_wrapper(&info->dma_q,
+ info->dma_q.curr);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bdma_pre_sync
+ *
+ * This function handles DMA synchronisation that must be done before
+ * an USB transfer is started.
+ *------------------------------------------------------------------------*/
+void
+usb_bdma_pre_sync(struct usb_xfer *xfer)
+{
+ struct usb_page_cache *pc;
+ usb_frcount_t nframes;
+
+ if (xfer->flags_int.isochronous_xfr) {
+ /* only one frame buffer */
+ nframes = 1;
+ } else {
+ /* can be multiple frame buffers */
+ nframes = xfer->nframes;
+ }
+
+ pc = xfer->frbuffers;
+
+ while (nframes--) {
+ if (pc->isread) {
+ usb_pc_cpu_invalidate(pc);
+ } else {
+ usb_pc_cpu_flush(pc);
+ }
+ pc++;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bdma_post_sync
+ *
+ * This function handles DMA synchronisation that must be done after
+ * an USB transfer is complete.
+ *------------------------------------------------------------------------*/
+void
+usb_bdma_post_sync(struct usb_xfer *xfer)
+{
+ struct usb_page_cache *pc;
+ usb_frcount_t nframes;
+
+ if (xfer->flags_int.isochronous_xfr) {
+ /* only one frame buffer */
+ nframes = 1;
+ } else {
+ /* can be multiple frame buffers */
+ nframes = xfer->nframes;
+ }
+
+ pc = xfer->frbuffers;
+
+ while (nframes--) {
+ if (pc->isread) {
+ usb_pc_cpu_invalidate(pc);
+ }
+ pc++;
+ }
+}
+
+#endif
diff --git a/sys/dev/usb/usb_busdma.h b/sys/dev/usb/usb_busdma.h
new file mode 100644
index 000000000000..c2b3b6872eec
--- /dev/null
+++ b/sys/dev/usb/usb_busdma.h
@@ -0,0 +1,163 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_BUSDMA_H_
+#define _USB_BUSDMA_H_
+
+#ifndef USB_GLOBAL_INCLUDE_FILE
+#include <sys/uio.h>
+#include <sys/mbuf.h>
+
+#include <machine/bus.h>
+#endif
+
+/* defines */
+
+#define USB_PAGE_SIZE PAGE_SIZE /* use system PAGE_SIZE */
+
+#define USB_GET_DMA_TAG(dev) bus_get_dma_tag(dev)
+
+/* structure prototypes */
+
+struct usb_xfer_root;
+struct usb_dma_parent_tag;
+struct usb_dma_tag;
+
+/*
+ * The following typedef defines the USB DMA load done callback.
+ */
+
+typedef void (usb_dma_callback_t)(struct usb_dma_parent_tag *udpt);
+
+/*
+ * The following structure defines physical and non kernel virtual
+ * address of a memory page having size USB_PAGE_SIZE.
+ */
+struct usb_page {
+#if USB_HAVE_BUSDMA
+ bus_addr_t physaddr;
+ void *buffer; /* non Kernel Virtual Address */
+#endif
+};
+
+/*
+ * The following structure is used when needing the kernel virtual
+ * pointer and the physical address belonging to an offset in an USB
+ * page cache.
+ */
+struct usb_page_search {
+ void *buffer;
+#if USB_HAVE_BUSDMA
+ bus_addr_t physaddr;
+#endif
+ usb_size_t length;
+};
+
+/*
+ * The following structure is used to keep information about a DMA
+ * memory allocation.
+ */
+struct usb_page_cache {
+#if USB_HAVE_BUSDMA
+ bus_dma_tag_t tag;
+ bus_dmamap_t map;
+ struct usb_page *page_start;
+#endif
+ struct usb_dma_parent_tag *tag_parent; /* always set */
+ void *buffer; /* virtual buffer pointer */
+#if USB_HAVE_BUSDMA
+ usb_size_t page_offset_buf;
+ usb_size_t page_offset_end;
+ uint8_t isread:1; /* set if we are currently reading
+ * from the memory. Else write. */
+ uint8_t ismultiseg:1; /* set if we can have multiple
+ * segments */
+ uint8_t isloaded:1; /* Set if map is currently loaded. */
+#endif
+};
+
+/*
+ * The following structure describes the parent USB DMA tag.
+ */
+#if USB_HAVE_BUSDMA
+struct usb_dma_parent_tag {
+ struct cv cv[1]; /* internal condition variable */
+ bus_dma_tag_t tag; /* always set */
+
+ struct mtx *mtx; /* private mutex, always set */
+ usb_dma_callback_t *func; /* load complete callback function */
+ struct usb_dma_tag *utag_first;/* pointer to first USB DMA tag */
+ uint8_t dma_error; /* set if DMA load operation failed */
+ uint8_t dma_bits; /* number of DMA address lines */
+ uint8_t utag_max; /* number of USB DMA tags */
+};
+#else
+struct usb_dma_parent_tag {}; /* empty struct */
+#endif
+
+/*
+ * The following structure describes an USB DMA tag.
+ */
+#if USB_HAVE_BUSDMA
+struct usb_dma_tag {
+ struct usb_dma_parent_tag *tag_parent;
+ bus_dma_tag_t tag;
+ usb_size_t align;
+ usb_size_t size;
+};
+#else
+struct usb_dma_tag {}; /* empty struct */
+#endif
+
+/* function prototypes */
+
+int usb_uiomove(struct usb_page_cache *pc, struct uio *uio,
+ usb_frlength_t pc_offset, usb_frlength_t len);
+struct usb_dma_tag *usb_dma_tag_find(struct usb_dma_parent_tag *udpt,
+ usb_size_t size, usb_size_t align);
+uint8_t usb_pc_alloc_mem(struct usb_page_cache *pc, struct usb_page *pg,
+ usb_size_t size, usb_size_t align);
+uint8_t usb_pc_dmamap_create(struct usb_page_cache *pc, usb_size_t size);
+uint8_t usb_pc_load_mem(struct usb_page_cache *pc, usb_size_t size,
+ uint8_t sync);
+void usb_bdma_done_event(struct usb_dma_parent_tag *udpt);
+void usb_bdma_post_sync(struct usb_xfer *xfer);
+void usb_bdma_pre_sync(struct usb_xfer *xfer);
+void usb_bdma_work_loop(struct usb_xfer_queue *pq);
+void usb_dma_tag_setup(struct usb_dma_parent_tag *udpt,
+ struct usb_dma_tag *udt, bus_dma_tag_t dmat, struct mtx *mtx,
+ usb_dma_callback_t *func, uint8_t ndmabits, uint8_t nudt);
+void usb_dma_tag_unsetup(struct usb_dma_parent_tag *udpt);
+void usb_pc_cpu_flush(struct usb_page_cache *pc);
+void usb_pc_cpu_invalidate(struct usb_page_cache *pc);
+void usb_pc_dmamap_destroy(struct usb_page_cache *pc);
+void usb_pc_free_mem(struct usb_page_cache *pc);
+uint8_t usb_pc_buffer_is_aligned(struct usb_page_cache *pc,
+ usb_frlength_t offset, usb_frlength_t len,
+ usb_frlength_t mask);
+
+#endif /* _USB_BUSDMA_H_ */
diff --git a/sys/dev/usb/usb_cdc.h b/sys/dev/usb/usb_cdc.h
new file mode 100644
index 000000000000..9dd976da588b
--- /dev/null
+++ b/sys/dev/usb/usb_cdc.h
@@ -0,0 +1,289 @@
+/* $NetBSD: usbcdc.h,v 1.9 2004/10/23 13:24:24 augustss Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _USB_CDC_H_
+#define _USB_CDC_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
+
+struct usb_cdc_header_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uWord bcdCDC;
+} __packed;
+
+struct usb_cdc_cm_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bmCapabilities;
+#define USB_CDC_CM_DOES_CM 0x01
+#define USB_CDC_CM_OVER_DATA 0x02
+ uByte bDataInterface;
+} __packed;
+
+struct usb_cdc_acm_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bmCapabilities;
+#define USB_CDC_ACM_HAS_FEATURE 0x01
+#define USB_CDC_ACM_HAS_LINE 0x02
+#define USB_CDC_ACM_HAS_BREAK 0x04
+#define USB_CDC_ACM_HAS_NETWORK_CONN 0x08
+} __packed;
+
+struct usb_cdc_union_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bMasterInterface;
+ uByte bSlaveInterface[1];
+} __packed;
+
+struct usb_cdc_ethernet_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte iMacAddress;
+ uDWord bmEthernetStatistics;
+ uWord wMaxSegmentSize;
+ uWord wNumberMCFilters;
+ uByte bNumberPowerFilters;
+} __packed;
+
+#define UCDC_SEND_ENCAPSULATED_COMMAND 0x00
+#define UCDC_GET_ENCAPSULATED_RESPONSE 0x01
+#define UCDC_SET_COMM_FEATURE 0x02
+#define UCDC_GET_COMM_FEATURE 0x03
+#define UCDC_ABSTRACT_STATE 0x01
+#define UCDC_COUNTRY_SETTING 0x02
+#define UCDC_CLEAR_COMM_FEATURE 0x04
+#define UCDC_SET_LINE_CODING 0x20
+#define UCDC_GET_LINE_CODING 0x21
+#define UCDC_SET_CONTROL_LINE_STATE 0x22
+#define UCDC_LINE_DTR 0x0001
+#define UCDC_LINE_RTS 0x0002
+#define UCDC_SEND_BREAK 0x23
+#define UCDC_BREAK_ON 0xffff
+#define UCDC_BREAK_OFF 0x0000
+
+struct usb_cdc_abstract_state {
+ uWord wState;
+#define UCDC_IDLE_SETTING 0x0001
+#define UCDC_DATA_MULTIPLEXED 0x0002
+} __packed;
+
+#define UCDC_ABSTRACT_STATE_LENGTH 2
+
+struct usb_cdc_line_state {
+ uDWord dwDTERate;
+ uByte bCharFormat;
+#define UCDC_STOP_BIT_1 0
+#define UCDC_STOP_BIT_1_5 1
+#define UCDC_STOP_BIT_2 2
+ uByte bParityType;
+#define UCDC_PARITY_NONE 0
+#define UCDC_PARITY_ODD 1
+#define UCDC_PARITY_EVEN 2
+#define UCDC_PARITY_MARK 3
+#define UCDC_PARITY_SPACE 4
+ uByte bDataBits;
+} __packed;
+
+#define UCDC_LINE_STATE_LENGTH 7
+
+struct usb_cdc_notification {
+ uByte bmRequestType;
+#define UCDC_NOTIFICATION 0xa1
+ uByte bNotification;
+#define UCDC_N_NETWORK_CONNECTION 0x00
+#define UCDC_N_RESPONSE_AVAILABLE 0x01
+#define UCDC_N_AUX_JACK_HOOK_STATE 0x08
+#define UCDC_N_RING_DETECT 0x09
+#define UCDC_N_SERIAL_STATE 0x20
+#define UCDC_N_CALL_STATE_CHANGED 0x28
+#define UCDC_N_LINE_STATE_CHANGED 0x29
+#define UCDC_N_CONNECTION_SPEED_CHANGE 0x2a
+ uWord wValue;
+ uWord wIndex;
+ uWord wLength;
+ uByte data[16];
+} __packed;
+
+#define UCDC_NOTIFICATION_LENGTH 8
+
+/*
+ * Bits set in the SERIAL STATE notification (first byte of data)
+ */
+
+#define UCDC_N_SERIAL_OVERRUN 0x40
+#define UCDC_N_SERIAL_PARITY 0x20
+#define UCDC_N_SERIAL_FRAMING 0x10
+#define UCDC_N_SERIAL_RI 0x08
+#define UCDC_N_SERIAL_BREAK 0x04
+#define UCDC_N_SERIAL_DSR 0x02
+#define UCDC_N_SERIAL_DCD 0x01
+
+/* Serial state bit masks */
+#define UCDC_MDM_RXCARRIER 0x01
+#define UCDC_MDM_TXCARRIER 0x02
+#define UCDC_MDM_BREAK 0x04
+#define UCDC_MDM_RING 0x08
+#define UCDC_MDM_FRAMING_ERR 0x10
+#define UCDC_MDM_PARITY_ERR 0x20
+#define UCDC_MDM_OVERRUN_ERR 0x40
+
+/*
+ * Network Control Model, NCM16 + NCM32, protocol definitions
+ */
+struct usb_ncm16_hdr {
+ uDWord dwSignature;
+ uWord wHeaderLength;
+ uWord wSequence;
+ uWord wBlockLength;
+ uWord wDptIndex;
+} __packed;
+
+struct usb_ncm16_dp {
+ uWord wFrameIndex;
+ uWord wFrameLength;
+} __packed;
+
+struct usb_ncm16_dpt {
+ uDWord dwSignature;
+ uWord wLength;
+ uWord wNextNdpIndex;
+ struct usb_ncm16_dp dp[0];
+} __packed;
+
+struct usb_ncm32_hdr {
+ uDWord dwSignature;
+ uWord wHeaderLength;
+ uWord wSequence;
+ uDWord dwBlockLength;
+ uDWord dwDptIndex;
+} __packed;
+
+struct usb_ncm32_dp {
+ uDWord dwFrameIndex;
+ uDWord dwFrameLength;
+} __packed;
+
+struct usb_ncm32_dpt {
+ uDWord dwSignature;
+ uWord wLength;
+ uWord wReserved6;
+ uDWord dwNextNdpIndex;
+ uDWord dwReserved12;
+ struct usb_ncm32_dp dp[0];
+} __packed;
+
+/* Communications interface class specific descriptors */
+
+#define UCDC_NCM_FUNC_DESC_SUBTYPE 0x1A
+
+struct usb_ncm_func_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uByte bDescriptorSubtype;
+ uByte bcdNcmVersion[2];
+ uByte bmNetworkCapabilities;
+#define UCDC_NCM_CAP_FILTER 0x01
+#define UCDC_NCM_CAP_MAC_ADDR 0x02
+#define UCDC_NCM_CAP_ENCAP 0x04
+#define UCDC_NCM_CAP_MAX_DATA 0x08
+#define UCDC_NCM_CAP_CRCMODE 0x10
+#define UCDC_NCM_CAP_MAX_DGRAM 0x20
+} __packed;
+
+/* Communications interface specific class request codes */
+
+#define UCDC_NCM_SET_ETHERNET_MULTICAST_FILTERS 0x40
+#define UCDC_NCM_SET_ETHERNET_POWER_MGMT_PATTERN_FILTER 0x41
+#define UCDC_NCM_GET_ETHERNET_POWER_MGMT_PATTERN_FILTER 0x42
+#define UCDC_NCM_SET_ETHERNET_PACKET_FILTER 0x43
+#define UCDC_NCM_GET_ETHERNET_STATISTIC 0x44
+#define UCDC_NCM_GET_NTB_PARAMETERS 0x80
+#define UCDC_NCM_GET_NET_ADDRESS 0x81
+#define UCDC_NCM_SET_NET_ADDRESS 0x82
+#define UCDC_NCM_GET_NTB_FORMAT 0x83
+#define UCDC_NCM_SET_NTB_FORMAT 0x84
+#define UCDC_NCM_GET_NTB_INPUT_SIZE 0x85
+#define UCDC_NCM_SET_NTB_INPUT_SIZE 0x86
+#define UCDC_NCM_GET_MAX_DATAGRAM_SIZE 0x87
+#define UCDC_NCM_SET_MAX_DATAGRAM_SIZE 0x88
+#define UCDC_NCM_GET_CRC_MODE 0x89
+#define UCDC_NCM_SET_CRC_MODE 0x8A
+
+struct usb_ncm_parameters {
+ uWord wLength;
+ uWord bmNtbFormatsSupported;
+#define UCDC_NCM_FORMAT_NTB16 0x0001
+#define UCDC_NCM_FORMAT_NTB32 0x0002
+ uDWord dwNtbInMaxSize;
+ uWord wNdpInDivisor;
+ uWord wNdpInPayloadRemainder;
+ uWord wNdpInAlignment;
+ uWord wReserved14;
+ uDWord dwNtbOutMaxSize;
+ uWord wNdpOutDivisor;
+ uWord wNdpOutPayloadRemainder;
+ uWord wNdpOutAlignment;
+ uWord wNtbOutMaxDatagrams;
+} __packed;
+
+/* Communications interface specific class notification codes */
+#define UCDC_NCM_NOTIF_NETWORK_CONNECTION 0x00
+#define UCDC_NCM_NOTIF_RESPONSE_AVAILABLE 0x01
+#define UCDC_NCM_NOTIF_CONNECTION_SPEED_CHANGE 0x2A
+
+#endif /* _USB_CDC_H_ */
diff --git a/sys/dev/usb/usb_controller.h b/sys/dev/usb/usb_controller.h
new file mode 100644
index 000000000000..be0a468e5ad0
--- /dev/null
+++ b/sys/dev/usb/usb_controller.h
@@ -0,0 +1,198 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_CONTROLLER_H_
+#define _USB_CONTROLLER_H_
+
+/* defines */
+
+#define USB_BUS_DMA_TAG_MAX 8
+
+/* structure prototypes */
+
+struct usb_bus;
+struct usb_page;
+struct usb_endpoint;
+struct usb_page_cache;
+struct usb_setup_params;
+struct usb_hw_ep_profile;
+struct usb_fs_isoc_schedule;
+struct usb_endpoint_descriptor;
+
+/* typedefs */
+
+typedef void (usb_bus_mem_sub_cb_t)(struct usb_bus *bus, struct usb_page_cache *pc, struct usb_page *pg, usb_size_t size, usb_size_t align);
+typedef void (usb_bus_mem_cb_t)(struct usb_bus *bus, usb_bus_mem_sub_cb_t *scb);
+
+/*
+ * The following structure is used to define all the USB BUS
+ * callbacks.
+ */
+struct usb_bus_methods {
+ /* USB Device and Host mode - Mandatory */
+
+ usb_handle_req_t *roothub_exec;
+
+ void (*endpoint_init) (struct usb_device *,
+ struct usb_endpoint_descriptor *, struct usb_endpoint *);
+ void (*xfer_setup) (struct usb_setup_params *);
+ void (*xfer_unsetup) (struct usb_xfer *);
+ void (*get_dma_delay) (struct usb_device *, uint32_t *);
+ void (*device_suspend) (struct usb_device *);
+ void (*device_resume) (struct usb_device *);
+ void (*set_hw_power) (struct usb_bus *);
+ void (*set_hw_power_sleep) (struct usb_bus *, uint32_t);
+ /*
+ * The following flag is set if one or more control transfers are
+ * active:
+ */
+#define USB_HW_POWER_CONTROL 0x01
+ /*
+ * The following flag is set if one or more bulk transfers are
+ * active:
+ */
+#define USB_HW_POWER_BULK 0x02
+ /*
+ * The following flag is set if one or more interrupt transfers are
+ * active:
+ */
+#define USB_HW_POWER_INTERRUPT 0x04
+ /*
+ * The following flag is set if one or more isochronous transfers
+ * are active:
+ */
+#define USB_HW_POWER_ISOC 0x08
+ /*
+ * The following flag is set if one or more non-root-HUB devices
+ * are present on the given USB bus:
+ */
+#define USB_HW_POWER_NON_ROOT_HUB 0x10
+ /*
+ * The following flag is set if we are suspending
+ */
+#define USB_HW_POWER_SUSPEND 0x20
+ /*
+ * The following flag is set if we are resuming
+ */
+#define USB_HW_POWER_RESUME 0x40
+ /*
+ * The following flag is set if we are shutting down
+ */
+#define USB_HW_POWER_SHUTDOWN 0x60
+
+ /* USB Device mode only - Mandatory */
+
+ void (*get_hw_ep_profile) (struct usb_device *udev, const struct usb_hw_ep_profile **ppf, uint8_t ep_addr);
+ void (*xfer_stall) (struct usb_xfer *xfer);
+ void (*set_stall) (struct usb_device *udev, struct usb_endpoint *ep, uint8_t *did_stall);
+
+ /* USB Device mode mandatory. USB Host mode optional. */
+
+ void (*clear_stall) (struct usb_device *udev, struct usb_endpoint *ep);
+
+ /* Optional transfer polling support */
+
+ void (*xfer_poll) (struct usb_bus *);
+
+ /* Optional fixed power mode support */
+
+ void (*get_power_mode) (struct usb_device *udev, int8_t *pmode);
+
+ /* Optional endpoint uninit */
+
+ void (*endpoint_uninit) (struct usb_device *, struct usb_endpoint *);
+
+ /* Optional device init */
+
+ usb_error_t (*device_init) (struct usb_device *);
+
+ /* Optional device uninit */
+
+ void (*device_uninit) (struct usb_device *);
+
+ /* Optional for device and host mode */
+
+ void (*start_dma_delay) (struct usb_xfer *);
+
+ void (*device_state_change) (struct usb_device *);
+
+ /* Optional for host mode */
+
+ usb_error_t (*set_address) (struct usb_device *, struct mtx *, uint16_t);
+
+ /* Optional for device and host mode */
+
+ usb_error_t (*set_endpoint_mode) (struct usb_device *, struct usb_endpoint *, uint8_t);
+};
+
+/*
+ * The following structure is used to define all the USB pipe
+ * callbacks.
+ */
+struct usb_pipe_methods {
+ /* Mandatory USB Device and Host mode callbacks: */
+
+ void (*open)(struct usb_xfer *);
+ void (*close)(struct usb_xfer *);
+
+ void (*enter)(struct usb_xfer *);
+ void (*start)(struct usb_xfer *);
+
+ /* Optional */
+
+ void *info;
+};
+
+/*
+ * The following structure keeps information about what a hardware USB
+ * endpoint supports.
+ */
+struct usb_hw_ep_profile {
+ uint16_t max_in_frame_size; /* IN-token direction */
+ uint16_t max_out_frame_size; /* OUT-token direction */
+ uint8_t is_simplex:1;
+ uint8_t support_multi_buffer:1;
+ uint8_t support_bulk:1;
+ uint8_t support_control:1;
+ uint8_t support_interrupt:1;
+ uint8_t support_isochronous:1;
+ uint8_t support_in:1; /* IN-token is supported */
+ uint8_t support_out:1; /* OUT-token is supported */
+};
+
+/* prototypes */
+
+void usb_bus_mem_flush_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb);
+uint8_t usb_bus_mem_alloc_all(struct usb_bus *bus, bus_dma_tag_t dmat, usb_bus_mem_cb_t *cb);
+void usb_bus_mem_free_all(struct usb_bus *bus, usb_bus_mem_cb_t *cb);
+uint16_t usb_isoc_time_expand(struct usb_bus *bus, uint16_t isoc_time_curr);
+void usb_bus_reset_async_locked(struct usb_bus *bus);
+#if USB_HAVE_TT_SUPPORT
+uint8_t usbd_fs_isoc_schedule_alloc_slot(struct usb_xfer *isoc_xfer, uint16_t isoc_time);
+#endif
+
+#endif /* _USB_CONTROLLER_H_ */
diff --git a/sys/dev/usb/usb_core.c b/sys/dev/usb/usb_core.c
new file mode 100644
index 000000000000..04b428a9b656
--- /dev/null
+++ b/sys/dev/usb/usb_core.c
@@ -0,0 +1,82 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * USB specifications and other documentation can be found at
+ * http://www.usb.org/developers/docs/ and
+ * http://www.usb.org/developers/devclass_docs/
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/proc.h>
+#include <sys/kdb.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+const struct usb_string_lang usb_string_lang_en = {
+ sizeof(usb_string_lang_en), UDESC_STRING,
+ { 0x09, 0x04 } /* American English */
+};
+
+MALLOC_DEFINE(M_USB, "USB", "USB");
+MALLOC_DEFINE(M_USBDEV, "USBdev", "USB device");
+
+int
+usbd_in_polling_mode(void)
+{
+ return (USB_IN_POLLING_MODE_VALUE());
+}
+
+void
+usbd_dummy_timeout(void *arg)
+{
+ /* NOP */
+}
+
+MODULE_VERSION(usb, 1);
diff --git a/sys/dev/usb/usb_core.h b/sys/dev/usb/usb_core.h
new file mode 100644
index 000000000000..ef1ad08527c7
--- /dev/null
+++ b/sys/dev/usb/usb_core.h
@@ -0,0 +1,195 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Including this file is mandatory for all USB related c-files in the kernel.
+ */
+
+#ifndef _USB_CORE_H_
+#define _USB_CORE_H_
+
+/*
+ * The following macro will tell if an USB transfer is currently
+ * receiving or transferring data.
+ */
+#define USB_GET_DATA_ISREAD(xfer) ((xfer)->flags_int.usb_mode == \
+ USB_MODE_DEVICE ? (((xfer)->endpointno & UE_DIR_IN) ? 0 : 1) : \
+ (((xfer)->endpointno & UE_DIR_IN) ? 1 : 0))
+
+/* locking wrappers for BUS lock */
+#define USB_BUS_LOCK(_b) USB_MTX_LOCK(&(_b)->bus_mtx)
+#define USB_BUS_UNLOCK(_b) USB_MTX_UNLOCK(&(_b)->bus_mtx)
+#define USB_BUS_LOCK_ASSERT(_b, _t) USB_MTX_ASSERT(&(_b)->bus_mtx, _t)
+
+/* locking wrappers for BUS spin lock */
+#define USB_BUS_SPIN_LOCK(_b) USB_MTX_LOCK_SPIN(&(_b)->bus_spin_lock)
+#define USB_BUS_SPIN_UNLOCK(_b) USB_MTX_UNLOCK_SPIN(&(_b)->bus_spin_lock)
+#define USB_BUS_SPIN_LOCK_ASSERT(_b, _t) USB_MTX_ASSERT(&(_b)->bus_spin_lock, _t)
+
+/* locking wrappers for XFER lock */
+#define USB_XFER_LOCK(_x) USB_MTX_LOCK((_x)->xroot->xfer_mtx)
+#define USB_XFER_UNLOCK(_x) USB_MTX_UNLOCK((_x)->xroot->xfer_mtx)
+#define USB_XFER_LOCK_ASSERT(_x, _t) USB_MTX_ASSERT((_x)->xroot->xfer_mtx, _t)
+
+/* helper for converting pointers to integers */
+#define USB_P2U(ptr) \
+ ((uintptr_t)(ptr))
+
+/* helper for computing offsets */
+#define USB_ADD_BYTES(ptr,size) \
+ ((void *)(__DECONST(char *, (ptr)) + (size)))
+
+/* debug macro */
+#define USB_ASSERT KASSERT
+
+/* structure prototypes */
+
+struct file;
+struct usb_bus;
+struct usb_device;
+struct usb_device_request;
+struct usb_page;
+struct usb_page_cache;
+struct usb_xfer;
+struct usb_xfer_root;
+struct usb_string_lang;
+
+/* typedefs */
+
+/* structures */
+
+/*
+ * The following structure defines a set of internal USB transfer
+ * flags.
+ */
+struct usb_xfer_flags_int {
+ enum usb_hc_mode usb_mode; /* shadow copy of "udev->usb_mode" */
+ uint16_t control_rem; /* remainder in bytes */
+
+ uint8_t open:1; /* set if USB pipe has been opened */
+ uint8_t transferring:1; /* set if an USB transfer is in
+ * progress */
+ uint8_t did_dma_delay:1; /* set if we waited for HW DMA */
+ uint8_t did_close:1; /* set if we closed the USB transfer */
+ uint8_t draining:1; /* set if we are draining an USB
+ * transfer */
+ uint8_t started:1; /* keeps track of started or stopped */
+ uint8_t bandwidth_reclaimed:1;
+ uint8_t control_xfr:1; /* set if control transfer */
+ uint8_t control_hdr:1; /* set if control header should be
+ * sent */
+ uint8_t control_act:1; /* set if control transfer is active */
+ uint8_t control_stall:1; /* set if control transfer should be stalled */
+ uint8_t control_did_data:1; /* set if control DATA has been transferred */
+
+ uint8_t short_frames_ok:1; /* filtered version */
+ uint8_t short_xfer_ok:1; /* filtered version */
+#if USB_HAVE_BUSDMA
+ uint8_t bdma_enable:1; /* filtered version (only set if
+ * hardware supports DMA) */
+ uint8_t bdma_no_post_sync:1; /* set if the USB callback wrapper
+ * should not do the BUS-DMA post sync
+ * operation */
+ uint8_t bdma_setup:1; /* set if BUS-DMA has been setup */
+#endif
+ uint8_t isochronous_xfr:1; /* set if isochronous transfer */
+ uint8_t curr_dma_set:1; /* used by USB HC/DC driver */
+ uint8_t can_cancel_immed:1; /* set if USB transfer can be
+ * cancelled immediately */
+ uint8_t doing_callback:1; /* set if executing the callback */
+ uint8_t maxp_was_clamped:1; /* set if the max packet size
+ * was outside its allowed range */
+};
+
+/*
+ * The following structure defines an USB transfer.
+ */
+struct usb_xfer {
+ struct usb_callout timeout_handle;
+ TAILQ_ENTRY(usb_xfer) wait_entry; /* used at various places */
+
+ struct usb_page_cache *buf_fixup; /* fixup buffer(s) */
+ struct usb_xfer_queue *wait_queue; /* pointer to queue that we
+ * are waiting on */
+ struct usb_page *dma_page_ptr;
+ struct usb_endpoint *endpoint; /* our USB endpoint */
+ struct usb_xfer_root *xroot; /* used by HC driver */
+ void *qh_start[2]; /* used by HC driver */
+ void *td_start[2]; /* used by HC driver */
+ void *td_transfer_first; /* used by HC driver */
+ void *td_transfer_last; /* used by HC driver */
+ void *td_transfer_cache; /* used by HC driver */
+ void *priv_sc; /* device driver data pointer 1 */
+ void *priv_fifo; /* device driver data pointer 2 */
+ void *local_buffer;
+ usb_frlength_t *frlengths;
+ struct usb_page_cache *frbuffers;
+ usb_callback_t *callback;
+
+ usb_frlength_t max_hc_frame_size;
+ usb_frlength_t max_data_length;
+ usb_frlength_t sumlen; /* sum of all lengths in bytes */
+ usb_frlength_t actlen; /* actual length in bytes */
+ usb_timeout_t timeout; /* milliseconds */
+
+ usb_frcount_t max_frame_count; /* initial value of "nframes" after
+ * setup */
+ usb_frcount_t nframes; /* number of USB frames to transfer */
+ usb_frcount_t aframes; /* actual number of USB frames
+ * transferred */
+ usb_stream_t stream_id; /* USB3.0 specific field */
+
+ uint16_t max_packet_size;
+ uint16_t max_frame_size;
+ uint16_t qh_pos;
+ uint16_t isoc_time_complete; /* in ms */
+ usb_timeout_t interval; /* milliseconds */
+
+ uint8_t address; /* physical USB address */
+ uint8_t endpointno; /* physical USB endpoint */
+ uint8_t max_packet_count;
+ uint8_t usb_state;
+ uint8_t fps_shift; /* down shift of FPS, 0..3 */
+
+ usb_error_t error;
+
+ struct usb_xfer_flags flags;
+ struct usb_xfer_flags_int flags_int;
+};
+
+/* external variables */
+
+extern struct mtx usb_ref_lock;
+extern const struct usb_string_lang usb_string_lang_en;
+
+/* typedefs */
+
+typedef struct malloc_type *usb_malloc_type;
+
+/* prototypes */
+
+#endif /* _USB_CORE_H_ */
diff --git a/sys/dev/usb/usb_debug.c b/sys/dev/usb/usb_debug.c
new file mode 100644
index 000000000000..2cc0fe5102b3
--- /dev/null
+++ b/sys/dev/usb/usb_debug.c
@@ -0,0 +1,292 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008-2022 Hans Petter Selasky
+ *
+ * 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.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_transfer.h>
+
+#include <ddb/ddb.h>
+#include <ddb/db_sym.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+/*
+ * Define this unconditionally in case a kernel module is loaded that
+ * has been compiled with debugging options.
+ */
+int usb_debug = 0;
+
+SYSCTL_NODE(_hw, OID_AUTO, usb, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB debugging");
+SYSCTL_INT(_hw_usb, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &usb_debug, 0, "Debug level");
+
+#ifdef USB_DEBUG
+/*
+ * Sysctls to modify timings/delays
+ */
+static SYSCTL_NODE(_hw_usb, OID_AUTO, timings, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "Timings");
+static int usb_timings_sysctl_handler(SYSCTL_HANDLER_ARGS);
+
+SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_reset_delay,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_port_reset_delay,
+ sizeof(usb_port_reset_delay), usb_timings_sysctl_handler, "IU",
+ "Port Reset Delay");
+SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_root_reset_delay,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &usb_port_root_reset_delay, sizeof(usb_port_root_reset_delay),
+ usb_timings_sysctl_handler, "IU",
+ "Root Port Reset Delay");
+SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_reset_recovery,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ &usb_port_reset_recovery, sizeof(usb_port_reset_recovery),
+ usb_timings_sysctl_handler, "IU",
+ "Port Reset Recovery");
+SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_powerup_delay,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_port_powerup_delay,
+ sizeof(usb_port_powerup_delay), usb_timings_sysctl_handler, "IU",
+ "Port PowerUp Delay");
+SYSCTL_PROC(_hw_usb_timings, OID_AUTO, port_resume_delay,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_port_resume_delay,
+ sizeof(usb_port_resume_delay), usb_timings_sysctl_handler, "IU",
+ "Port Resume Delay");
+SYSCTL_PROC(_hw_usb_timings, OID_AUTO, set_address_settle,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_set_address_settle,
+ sizeof(usb_set_address_settle), usb_timings_sysctl_handler, "IU",
+ "Set Address Settle");
+SYSCTL_PROC(_hw_usb_timings, OID_AUTO, resume_delay,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_resume_delay,
+ sizeof(usb_resume_delay), usb_timings_sysctl_handler, "IU",
+ "Resume Delay");
+SYSCTL_PROC(_hw_usb_timings, OID_AUTO, resume_wait,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_resume_wait,
+ sizeof(usb_resume_wait), usb_timings_sysctl_handler, "IU",
+ "Resume Wait");
+SYSCTL_PROC(_hw_usb_timings, OID_AUTO, resume_recovery,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_resume_recovery,
+ sizeof(usb_resume_recovery), usb_timings_sysctl_handler, "IU",
+ "Resume Recovery");
+SYSCTL_PROC(_hw_usb_timings, OID_AUTO, extra_power_up_time,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_extra_power_up_time,
+ sizeof(usb_extra_power_up_time), usb_timings_sysctl_handler, "IU",
+ "Extra PowerUp Time");
+SYSCTL_PROC(_hw_usb_timings, OID_AUTO, enum_nice_time,
+ CTLTYPE_UINT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, &usb_enum_nice_time,
+ sizeof(usb_enum_nice_time), usb_timings_sysctl_handler, "IU",
+ "Enumeration thread nice time");
+#endif
+
+/*------------------------------------------------------------------------*
+ * usb_dump_iface
+ *
+ * This function dumps information about an USB interface.
+ *------------------------------------------------------------------------*/
+void
+usb_dump_iface(struct usb_interface *iface)
+{
+ printf("usb_dump_iface: iface=%p\n", iface);
+ if (iface == NULL) {
+ return;
+ }
+ printf(" iface=%p idesc=%p altindex=%d\n",
+ iface, iface->idesc, iface->alt_index);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_dump_device
+ *
+ * This function dumps information about an USB device.
+ *------------------------------------------------------------------------*/
+void
+usb_dump_device(struct usb_device *udev)
+{
+ printf("usb_dump_device: dev=%p\n", udev);
+ if (udev == NULL) {
+ return;
+ }
+ printf(" bus=%p \n"
+ " address=%d config=%d depth=%d speed=%d self_powered=%d\n"
+ " power=%d langid=%d\n",
+ udev->bus,
+ udev->address, udev->curr_config_no, udev->depth, udev->speed,
+ udev->flags.self_powered, udev->power, udev->langid);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_dump_queue
+ *
+ * This function dumps the USB transfer that are queued up on an USB endpoint.
+ *------------------------------------------------------------------------*/
+void
+usb_dump_queue(struct usb_endpoint *ep)
+{
+ struct usb_xfer *xfer;
+ usb_stream_t x;
+
+ printf("usb_dump_queue: endpoint=%p xfer: ", ep);
+ for (x = 0; x != USB_MAX_EP_STREAMS; x++) {
+ TAILQ_FOREACH(xfer, &ep->endpoint_q[x].head, wait_entry)
+ printf(" %p", xfer);
+ }
+ printf("\n");
+}
+
+/*------------------------------------------------------------------------*
+ * usb_dump_endpoint
+ *
+ * This function dumps information about an USB endpoint.
+ *------------------------------------------------------------------------*/
+void
+usb_dump_endpoint(struct usb_endpoint *ep)
+{
+ if (ep) {
+ printf("usb_dump_endpoint: endpoint=%p", ep);
+
+ printf(" edesc=%p isoc_next=%d toggle_next=%d",
+ ep->edesc, ep->isoc_next, ep->toggle_next);
+
+ if (ep->edesc) {
+ printf(" bEndpointAddress=0x%02x",
+ ep->edesc->bEndpointAddress);
+ }
+ printf("\n");
+ usb_dump_queue(ep);
+ } else {
+ printf("usb_dump_endpoint: endpoint=NULL\n");
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_dump_xfer
+ *
+ * This function dumps information about an USB transfer.
+ *------------------------------------------------------------------------*/
+void
+usb_dump_xfer(struct usb_xfer *xfer)
+{
+ struct usb_device *udev;
+ printf("usb_dump_xfer: xfer=%p\n", xfer);
+ if (xfer == NULL) {
+ return;
+ }
+ if (xfer->endpoint == NULL) {
+ printf("xfer %p: endpoint=NULL\n",
+ xfer);
+ return;
+ }
+ udev = xfer->xroot->udev;
+ printf("xfer %p: udev=%p vid=0x%04x pid=0x%04x addr=%d "
+ "endpoint=%p ep=0x%02x attr=0x%02x\n",
+ xfer, udev,
+ UGETW(udev->ddesc.idVendor),
+ UGETW(udev->ddesc.idProduct),
+ udev->address, xfer->endpoint,
+ xfer->endpoint->edesc->bEndpointAddress,
+ xfer->endpoint->edesc->bmAttributes);
+}
+
+#ifdef USB_DEBUG
+unsigned usb_port_reset_delay = USB_PORT_RESET_DELAY;
+unsigned usb_port_root_reset_delay = USB_PORT_ROOT_RESET_DELAY;
+unsigned usb_port_reset_recovery = USB_PORT_RESET_RECOVERY;
+unsigned usb_port_powerup_delay = USB_PORT_POWERUP_DELAY;
+unsigned usb_port_resume_delay = USB_PORT_RESUME_DELAY;
+unsigned usb_set_address_settle = USB_SET_ADDRESS_SETTLE;
+unsigned usb_resume_delay = USB_RESUME_DELAY;
+unsigned usb_resume_wait = USB_RESUME_WAIT;
+unsigned usb_resume_recovery = USB_RESUME_RECOVERY;
+unsigned usb_extra_power_up_time = USB_EXTRA_POWER_UP_TIME;
+unsigned usb_enum_nice_time = USB_ENUM_NICE_TIME;
+
+/*------------------------------------------------------------------------*
+ * usb_timings_sysctl_handler
+ *
+ * This function is used to update USB timing variables.
+ *------------------------------------------------------------------------*/
+static int usb_timings_sysctl_handler(SYSCTL_HANDLER_ARGS)
+{
+ int error = 0;
+ unsigned val;
+
+ /*
+ * Attempt to get a coherent snapshot by making a copy of the data.
+ */
+ if (arg1)
+ val = *(unsigned *)arg1;
+ else
+ val = arg2;
+ error = SYSCTL_OUT(req, &val, sizeof(unsigned));
+ if (error || !req->newptr)
+ return (error);
+
+ if (!arg1)
+ return (EPERM);
+
+ error = SYSCTL_IN(req, &val, sizeof(unsigned));
+ if (error)
+ return (error);
+
+ /*
+ * Make sure the specified value is not too big. Accept any
+ * value from 0 milliseconds to 2 seconds inclusivly for all
+ * parameters.
+ */
+ if (val > 2000)
+ return (EINVAL);
+
+ *(unsigned *)arg1 = val;
+ return (0);
+}
+#endif
diff --git a/sys/dev/usb/usb_debug.h b/sys/dev/usb/usb_debug.h
new file mode 100644
index 000000000000..46b5382e9f4a
--- /dev/null
+++ b/sys/dev/usb/usb_debug.h
@@ -0,0 +1,91 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* This file contains various factored out debug macros. */
+
+#ifndef _USB_DEBUG_H_
+#define _USB_DEBUG_H_
+
+/* Declare global USB debug variable. */
+extern int usb_debug;
+
+/* Check if USB debugging is enabled. */
+#ifdef USB_DEBUG_VAR
+#ifdef USB_DEBUG
+#define DPRINTFN(n,fmt,...) do { \
+ if ((USB_DEBUG_VAR) >= (n)) { \
+ printf("%s: " fmt, \
+ __FUNCTION__ ,##__VA_ARGS__); \
+ } \
+} while (0)
+#define DPRINTF(...) DPRINTFN(1, __VA_ARGS__)
+#define __usbdebug_used
+#else
+#define DPRINTF(...) do { } while (0)
+#define DPRINTFN(...) do { } while (0)
+#define __usbdebug_used __unused
+#endif
+#endif
+
+struct usb_interface;
+struct usb_device;
+struct usb_endpoint;
+struct usb_xfer;
+
+void usb_dump_iface(struct usb_interface *iface);
+void usb_dump_device(struct usb_device *udev);
+void usb_dump_queue(struct usb_endpoint *ep);
+void usb_dump_endpoint(struct usb_endpoint *ep);
+void usb_dump_xfer(struct usb_xfer *xfer);
+
+#ifdef USB_DEBUG
+extern unsigned usb_port_reset_delay;
+extern unsigned usb_port_root_reset_delay;
+extern unsigned usb_port_reset_recovery;
+extern unsigned usb_port_powerup_delay;
+extern unsigned usb_port_resume_delay;
+extern unsigned usb_set_address_settle;
+extern unsigned usb_resume_delay;
+extern unsigned usb_resume_wait;
+extern unsigned usb_resume_recovery;
+extern unsigned usb_extra_power_up_time;
+extern unsigned usb_enum_nice_time;
+#else
+#define usb_port_reset_delay USB_PORT_RESET_DELAY
+#define usb_port_root_reset_delay USB_PORT_ROOT_RESET_DELAY
+#define usb_port_reset_recovery USB_PORT_RESET_RECOVERY
+#define usb_port_powerup_delay USB_PORT_POWERUP_DELAY
+#define usb_port_resume_delay USB_PORT_RESUME_DELAY
+#define usb_set_address_settle USB_SET_ADDRESS_SETTLE
+#define usb_resume_delay USB_RESUME_DELAY
+#define usb_resume_wait USB_RESUME_WAIT
+#define usb_resume_recovery USB_RESUME_RECOVERY
+#define usb_extra_power_up_time USB_EXTRA_POWER_UP_TIME
+#define usb_enum_nice_time USB_ENUM_NICE_TIME
+#endif
+
+#endif /* _USB_DEBUG_H_ */
diff --git a/sys/dev/usb/usb_dev.c b/sys/dev/usb/usb_dev.c
new file mode 100644
index 000000000000..293b0c72587f
--- /dev/null
+++ b/sys/dev/usb/usb_dev.c
@@ -0,0 +1,2470 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2006-2023 Hans Petter Selasky
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *
+ * usb_dev.c - An abstraction layer for creating devices under /dev/...
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#ifdef COMPAT_FREEBSD32
+#include <sys/abi_compat.h>
+#endif
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/vnode.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+
+#define USB_DEBUG_VAR usb_fifo_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_dev.h>
+#include <dev/usb/usb_mbuf.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_generic.h>
+#include <dev/usb/usb_dynamic.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+
+#include <sys/filio.h>
+#include <sys/ttycom.h>
+#include <sys/syscallsubr.h>
+#include <sys/stdarg.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#if USB_HAVE_UGEN
+
+#ifdef USB_DEBUG
+static int usb_fifo_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, dev, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB device");
+SYSCTL_INT(_hw_usb_dev, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &usb_fifo_debug, 0, "Debug Level");
+#endif
+
+#define USB_UCRED struct ucred *ucred,
+
+/* prototypes */
+
+static int usb_fifo_open(struct usb_cdev_privdata *,
+ struct usb_fifo *, int);
+static void usb_fifo_close(struct usb_fifo *, int);
+static void usb_dev_init(void *);
+static void usb_dev_init_post(void *);
+static void usb_dev_uninit(void *);
+static int usb_fifo_uiomove(struct usb_fifo *, void *, int,
+ struct uio *);
+static void usb_fifo_check_methods(struct usb_fifo_methods *);
+static struct usb_fifo *usb_fifo_alloc(struct mtx *);
+static struct usb_endpoint *usb_dev_get_ep(struct usb_device *, uint8_t,
+ uint8_t);
+static void usb_loc_fill(struct usb_fs_privdata *,
+ struct usb_cdev_privdata *);
+static void usb_close(void *);
+static usb_error_t usb_ref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *, int);
+static usb_error_t usb_usb_ref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *);
+static void usb_unref_device(struct usb_cdev_privdata *, struct usb_cdev_refdata *);
+
+static d_open_t usb_open;
+static d_ioctl_t usb_ioctl;
+static d_read_t usb_read;
+static d_write_t usb_write;
+static d_poll_t usb_poll;
+static d_kqfilter_t usb_kqfilter;
+
+static d_ioctl_t usb_static_ioctl;
+
+static usb_fifo_open_t usb_fifo_dummy_open;
+static usb_fifo_close_t usb_fifo_dummy_close;
+static usb_fifo_ioctl_t usb_fifo_dummy_ioctl;
+static usb_fifo_cmd_t usb_fifo_dummy_cmd;
+
+/* character device structure used for devices (/dev/ugenX.Y and /dev/uXXX) */
+struct cdevsw usb_devsw = {
+ .d_version = D_VERSION,
+ .d_open = usb_open,
+ .d_ioctl = usb_ioctl,
+ .d_name = "usbdev",
+ .d_flags = D_TRACKCLOSE,
+ .d_read = usb_read,
+ .d_write = usb_write,
+ .d_poll = usb_poll,
+ .d_kqfilter = usb_kqfilter,
+};
+
+static struct cdev* usb_dev = NULL;
+
+/* character device structure used for /dev/usb */
+static struct cdevsw usb_static_devsw = {
+ .d_version = D_VERSION,
+ .d_ioctl = usb_static_ioctl,
+ .d_name = "usb"
+};
+
+static TAILQ_HEAD(, usb_symlink) usb_sym_head;
+static struct sx usb_sym_lock;
+
+struct mtx usb_ref_lock;
+
+/*------------------------------------------------------------------------*
+ * usb_loc_fill
+ *
+ * This is used to fill out a usb_cdev_privdata structure based on the
+ * device's address as contained in usb_fs_privdata.
+ *------------------------------------------------------------------------*/
+static void
+usb_loc_fill(struct usb_fs_privdata* pd, struct usb_cdev_privdata *cpd)
+{
+ cpd->bus_index = pd->bus_index;
+ cpd->dev_index = pd->dev_index;
+ cpd->ep_addr = pd->ep_addr;
+ cpd->fifo_index = pd->fifo_index;
+}
+
+/*------------------------------------------------------------------------*
+ * usb_ref_device
+ *
+ * This function is used to atomically refer an USB device by its
+ * device location. If this function returns success the USB device
+ * will not disappear until the USB device is unreferenced.
+ *
+ * Return values:
+ * 0: Success, refcount incremented on the given USB device.
+ * Else: Failure.
+ *------------------------------------------------------------------------*/
+static usb_error_t
+usb_ref_device(struct usb_cdev_privdata *cpd,
+ struct usb_cdev_refdata *crd, int need_uref)
+{
+ struct usb_fifo **ppf;
+ struct usb_fifo *f;
+
+ DPRINTFN(2, "cpd=%p need uref=%d\n", cpd, need_uref);
+
+ /* clear all refs */
+ memset(crd, 0, sizeof(*crd));
+
+ mtx_lock(&usb_ref_lock);
+ cpd->bus = devclass_get_softc(usb_devclass_ptr, cpd->bus_index);
+ if (cpd->bus == NULL) {
+ DPRINTFN(2, "no bus at %u\n", cpd->bus_index);
+ goto error;
+ }
+ cpd->udev = cpd->bus->devices[cpd->dev_index];
+ if (cpd->udev == NULL) {
+ DPRINTFN(2, "no device at %u\n", cpd->dev_index);
+ goto error;
+ }
+ if (cpd->udev->state == USB_STATE_DETACHED &&
+ (need_uref != 2)) {
+ DPRINTFN(2, "device is detached\n");
+ goto error;
+ }
+ if (need_uref) {
+ DPRINTFN(2, "ref udev - needed\n");
+
+ if (cpd->udev->refcount == USB_DEV_REF_MAX) {
+ DPRINTFN(2, "no dev ref\n");
+ goto error;
+ }
+ cpd->udev->refcount++;
+
+ mtx_unlock(&usb_ref_lock);
+
+ /*
+ * We need to grab the enumeration SX-lock before
+ * grabbing the FIFO refs to avoid deadlock at detach!
+ */
+ crd->do_unlock = usbd_enum_lock_sig(cpd->udev);
+
+ mtx_lock(&usb_ref_lock);
+
+ /*
+ * Set "is_uref" after grabbing the default SX lock
+ */
+ crd->is_uref = 1;
+
+ /* check for signal */
+ if (crd->do_unlock > 1) {
+ crd->do_unlock = 0;
+ goto error;
+ }
+ }
+
+ /* check if we are doing an open */
+ if (cpd->fflags == 0) {
+ /* use zero defaults */
+ } else {
+ /* check for write */
+ if (cpd->fflags & FWRITE) {
+ ppf = cpd->udev->fifo;
+ f = ppf[cpd->fifo_index + USB_FIFO_TX];
+ crd->txfifo = f;
+ crd->is_write = 1; /* ref */
+ if (f == NULL || f->refcount == USB_FIFO_REF_MAX)
+ goto error;
+ if (f->curr_cpd != cpd)
+ goto error;
+ /* check if USB-FS is active */
+ if (f->fs_ep_max != 0) {
+ crd->is_usbfs = 1;
+ }
+ }
+
+ /* check for read */
+ if (cpd->fflags & FREAD) {
+ ppf = cpd->udev->fifo;
+ f = ppf[cpd->fifo_index + USB_FIFO_RX];
+ crd->rxfifo = f;
+ crd->is_read = 1; /* ref */
+ if (f == NULL || f->refcount == USB_FIFO_REF_MAX)
+ goto error;
+ if (f->curr_cpd != cpd)
+ goto error;
+ /* check if USB-FS is active */
+ if (f->fs_ep_max != 0) {
+ crd->is_usbfs = 1;
+ }
+ }
+ }
+
+ /* when everything is OK we increment the refcounts */
+ if (crd->is_write) {
+ DPRINTFN(2, "ref write\n");
+ crd->txfifo->refcount++;
+ }
+ if (crd->is_read) {
+ DPRINTFN(2, "ref read\n");
+ crd->rxfifo->refcount++;
+ }
+ mtx_unlock(&usb_ref_lock);
+
+ return (0);
+
+error:
+ if (crd->do_unlock)
+ usbd_enum_unlock(cpd->udev);
+
+ if (crd->is_uref) {
+ if (--(cpd->udev->refcount) == 0)
+ cv_broadcast(&cpd->udev->ref_cv);
+ }
+ mtx_unlock(&usb_ref_lock);
+ DPRINTFN(2, "fail\n");
+
+ /* clear all refs */
+ memset(crd, 0, sizeof(*crd));
+
+ return (USB_ERR_INVAL);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_usb_ref_device
+ *
+ * This function is used to upgrade an USB reference to include the
+ * USB device reference on a USB location.
+ *
+ * Return values:
+ * 0: Success, refcount incremented on the given USB device.
+ * Else: Failure.
+ *------------------------------------------------------------------------*/
+static usb_error_t
+usb_usb_ref_device(struct usb_cdev_privdata *cpd,
+ struct usb_cdev_refdata *crd)
+{
+ /*
+ * Check if we already got an USB reference on this location:
+ */
+ if (crd->is_uref)
+ return (0); /* success */
+
+ /*
+ * To avoid deadlock at detach we need to drop the FIFO ref
+ * and re-acquire a new ref!
+ */
+ usb_unref_device(cpd, crd);
+
+ return (usb_ref_device(cpd, crd, 1 /* need uref */));
+}
+
+/*------------------------------------------------------------------------*
+ * usb_unref_device
+ *
+ * This function will release the reference count by one unit for the
+ * given USB device.
+ *------------------------------------------------------------------------*/
+static void
+usb_unref_device(struct usb_cdev_privdata *cpd,
+ struct usb_cdev_refdata *crd)
+{
+
+ DPRINTFN(2, "cpd=%p is_uref=%d\n", cpd, crd->is_uref);
+
+ if (crd->do_unlock)
+ usbd_enum_unlock(cpd->udev);
+
+ mtx_lock(&usb_ref_lock);
+ if (crd->is_read) {
+ if (--(crd->rxfifo->refcount) == 0) {
+ cv_signal(&crd->rxfifo->cv_drain);
+ }
+ crd->is_read = 0;
+ }
+ if (crd->is_write) {
+ if (--(crd->txfifo->refcount) == 0) {
+ cv_signal(&crd->txfifo->cv_drain);
+ }
+ crd->is_write = 0;
+ }
+ if (crd->is_uref) {
+ crd->is_uref = 0;
+ if (--(cpd->udev->refcount) == 0)
+ cv_broadcast(&cpd->udev->ref_cv);
+ }
+ mtx_unlock(&usb_ref_lock);
+}
+
+static struct usb_fifo *
+usb_fifo_alloc(struct mtx *mtx)
+{
+ struct usb_fifo *f;
+
+ f = malloc(sizeof(*f), M_USBDEV, M_WAITOK | M_ZERO);
+ cv_init(&f->cv_io, "FIFO-IO");
+ cv_init(&f->cv_drain, "FIFO-DRAIN");
+ sx_init(&f->fs_fastpath_lock, "FIFO-FP");
+ f->priv_mtx = mtx;
+ f->refcount = 1;
+ knlist_init_mtx(&f->selinfo.si_note, mtx);
+ return (f);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_fifo_create
+ *------------------------------------------------------------------------*/
+static int
+usb_fifo_create(struct usb_cdev_privdata *cpd,
+ struct usb_cdev_refdata *crd)
+{
+ struct usb_device *udev = cpd->udev;
+ struct usb_fifo *f;
+ struct usb_endpoint *ep;
+ uint8_t n;
+ uint8_t is_tx;
+ uint8_t is_rx;
+ uint8_t no_null;
+ uint8_t is_busy;
+ int e = cpd->ep_addr;
+
+ is_tx = (cpd->fflags & FWRITE) ? 1 : 0;
+ is_rx = (cpd->fflags & FREAD) ? 1 : 0;
+ no_null = 1;
+ is_busy = 0;
+
+ /* Preallocated FIFO */
+ if (e < 0) {
+ DPRINTFN(5, "Preallocated FIFO\n");
+ if (is_tx) {
+ f = udev->fifo[cpd->fifo_index + USB_FIFO_TX];
+ if (f == NULL)
+ return (EINVAL);
+ crd->txfifo = f;
+ }
+ if (is_rx) {
+ f = udev->fifo[cpd->fifo_index + USB_FIFO_RX];
+ if (f == NULL)
+ return (EINVAL);
+ crd->rxfifo = f;
+ }
+ return (0);
+ }
+
+ KASSERT(e >= 0 && e <= 15, ("endpoint %d out of range", e));
+
+ /* search for a free FIFO slot */
+ DPRINTFN(5, "Endpoint device, searching for 0x%02x\n", e);
+ for (n = 0;; n += 2) {
+ if (n == USB_FIFO_MAX) {
+ if (no_null) {
+ no_null = 0;
+ n = 0;
+ } else {
+ /* end of FIFOs reached */
+ DPRINTFN(5, "out of FIFOs\n");
+ return (ENOMEM);
+ }
+ }
+ /* Check for TX FIFO */
+ if (is_tx) {
+ f = udev->fifo[n + USB_FIFO_TX];
+ if (f != NULL) {
+ if (f->dev_ep_index != e) {
+ /* wrong endpoint index */
+ continue;
+ }
+ if (f->curr_cpd != NULL) {
+ /* FIFO is opened */
+ is_busy = 1;
+ continue;
+ }
+ } else if (no_null) {
+ continue;
+ }
+ }
+ /* Check for RX FIFO */
+ if (is_rx) {
+ f = udev->fifo[n + USB_FIFO_RX];
+ if (f != NULL) {
+ if (f->dev_ep_index != e) {
+ /* wrong endpoint index */
+ continue;
+ }
+ if (f->curr_cpd != NULL) {
+ /* FIFO is opened */
+ is_busy = 1;
+ continue;
+ }
+ } else if (no_null) {
+ continue;
+ }
+ }
+ break;
+ }
+
+ if (no_null == 0) {
+ if (e >= (USB_EP_MAX / 2)) {
+ /* we don't create any endpoints in this range */
+ DPRINTFN(5, "ep out of range\n");
+ return (is_busy ? EBUSY : EINVAL);
+ }
+ }
+
+ if ((e != 0) && is_busy) {
+ /*
+ * Only the default control endpoint is allowed to be
+ * opened multiple times!
+ */
+ DPRINTFN(5, "busy\n");
+ return (EBUSY);
+ }
+
+ /* Check TX FIFO */
+ if (is_tx &&
+ (udev->fifo[n + USB_FIFO_TX] == NULL)) {
+ ep = usb_dev_get_ep(udev, e, USB_FIFO_TX);
+ DPRINTFN(5, "dev_get_endpoint(%d, 0x%x)\n", e, USB_FIFO_TX);
+ if (ep == NULL) {
+ DPRINTFN(5, "dev_get_endpoint returned NULL\n");
+ return (EINVAL);
+ }
+ f = usb_fifo_alloc(&udev->device_mtx);
+ if (f == NULL) {
+ DPRINTFN(5, "could not alloc tx fifo\n");
+ return (ENOMEM);
+ }
+ /* update some fields */
+ f->fifo_index = n + USB_FIFO_TX;
+ f->dev_ep_index = e;
+ f->priv_sc0 = ep;
+ f->methods = &usb_ugen_methods;
+ f->iface_index = ep->iface_index;
+ f->udev = udev;
+ mtx_lock(&usb_ref_lock);
+ udev->fifo[n + USB_FIFO_TX] = f;
+ mtx_unlock(&usb_ref_lock);
+ }
+ /* Check RX FIFO */
+ if (is_rx &&
+ (udev->fifo[n + USB_FIFO_RX] == NULL)) {
+ ep = usb_dev_get_ep(udev, e, USB_FIFO_RX);
+ DPRINTFN(5, "dev_get_endpoint(%d, 0x%x)\n", e, USB_FIFO_RX);
+ if (ep == NULL) {
+ DPRINTFN(5, "dev_get_endpoint returned NULL\n");
+ return (EINVAL);
+ }
+ f = usb_fifo_alloc(&udev->device_mtx);
+ if (f == NULL) {
+ DPRINTFN(5, "could not alloc rx fifo\n");
+ return (ENOMEM);
+ }
+ /* update some fields */
+ f->fifo_index = n + USB_FIFO_RX;
+ f->dev_ep_index = e;
+ f->priv_sc0 = ep;
+ f->methods = &usb_ugen_methods;
+ f->iface_index = ep->iface_index;
+ f->udev = udev;
+ mtx_lock(&usb_ref_lock);
+ udev->fifo[n + USB_FIFO_RX] = f;
+ mtx_unlock(&usb_ref_lock);
+ }
+ if (is_tx) {
+ crd->txfifo = udev->fifo[n + USB_FIFO_TX];
+ }
+ if (is_rx) {
+ crd->rxfifo = udev->fifo[n + USB_FIFO_RX];
+ }
+ /* fill out fifo index */
+ DPRINTFN(5, "fifo index = %d\n", n);
+ cpd->fifo_index = n;
+
+ /* complete */
+
+ return (0);
+}
+
+void
+usb_fifo_free(struct usb_fifo *f)
+{
+ uint8_t n;
+
+ if (f == NULL) {
+ /* be NULL safe */
+ return;
+ }
+ /* destroy symlink devices, if any */
+ for (n = 0; n != 2; n++) {
+ if (f->symlink[n]) {
+ usb_free_symlink(f->symlink[n]);
+ f->symlink[n] = NULL;
+ }
+ }
+ mtx_lock(&usb_ref_lock);
+
+ /* delink ourselves to stop calls from userland */
+ if ((f->fifo_index < USB_FIFO_MAX) &&
+ (f->udev != NULL) &&
+ (f->udev->fifo[f->fifo_index] == f)) {
+ f->udev->fifo[f->fifo_index] = NULL;
+ } else {
+ DPRINTFN(0, "USB FIFO %p has not been linked\n", f);
+ }
+
+ /* decrease refcount */
+ f->refcount--;
+ /* need to wait until all callers have exited */
+ while (f->refcount != 0) {
+ mtx_unlock(&usb_ref_lock); /* avoid LOR */
+ mtx_lock(f->priv_mtx);
+ /* prevent write flush, if any */
+ f->flag_iserror = 1;
+ /* get I/O thread out of any sleep state */
+ if (f->flag_sleeping) {
+ f->flag_sleeping = 0;
+ cv_broadcast(&f->cv_io);
+ }
+ mtx_unlock(f->priv_mtx);
+ mtx_lock(&usb_ref_lock);
+
+ /*
+ * Check if the "f->refcount" variable reached zero
+ * during the unlocked time before entering wait:
+ */
+ if (f->refcount == 0)
+ break;
+
+ /* wait for sync */
+ cv_wait(&f->cv_drain, &usb_ref_lock);
+ }
+ mtx_unlock(&usb_ref_lock);
+
+ /* take care of closing the device here, if any */
+ usb_fifo_close(f, 0);
+
+ cv_destroy(&f->cv_io);
+ cv_destroy(&f->cv_drain);
+ sx_destroy(&f->fs_fastpath_lock);
+
+ knlist_clear(&f->selinfo.si_note, 0);
+ seldrain(&f->selinfo);
+ knlist_destroy(&f->selinfo.si_note);
+
+ free(f, M_USBDEV);
+}
+
+static struct usb_endpoint *
+usb_dev_get_ep(struct usb_device *udev, uint8_t ep_index, uint8_t dir)
+{
+ struct usb_endpoint *ep;
+ uint8_t ep_dir;
+
+ if (ep_index == 0) {
+ ep = &udev->ctrl_ep;
+ } else {
+ if (dir == USB_FIFO_RX) {
+ if (udev->flags.usb_mode == USB_MODE_HOST) {
+ ep_dir = UE_DIR_IN;
+ } else {
+ ep_dir = UE_DIR_OUT;
+ }
+ } else {
+ if (udev->flags.usb_mode == USB_MODE_HOST) {
+ ep_dir = UE_DIR_OUT;
+ } else {
+ ep_dir = UE_DIR_IN;
+ }
+ }
+ ep = usbd_get_ep_by_addr(udev, ep_index | ep_dir);
+ }
+
+ if (ep == NULL) {
+ /* if the endpoint does not exist then return */
+ return (NULL);
+ }
+ if (ep->edesc == NULL) {
+ /* invalid endpoint */
+ return (NULL);
+ }
+ return (ep); /* success */
+}
+
+/*------------------------------------------------------------------------*
+ * usb_fifo_open
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static int
+usb_fifo_open(struct usb_cdev_privdata *cpd,
+ struct usb_fifo *f, int fflags)
+{
+ int err;
+
+ if (f == NULL) {
+ /* no FIFO there */
+ DPRINTFN(2, "no FIFO\n");
+ return (ENXIO);
+ }
+ /* remove FWRITE and FREAD flags */
+ fflags &= ~(FWRITE | FREAD);
+
+ /* set correct file flags */
+ if ((f->fifo_index & 1) == USB_FIFO_TX) {
+ fflags |= FWRITE;
+ } else {
+ fflags |= FREAD;
+ }
+
+ /* check if we are already opened */
+ /* we don't need any locks when checking this variable */
+ if (f->curr_cpd != NULL) {
+ err = EBUSY;
+ goto done;
+ }
+
+ /* reset short flag before open */
+ f->flag_short = 0;
+
+ /* call open method */
+ err = (f->methods->f_open) (f, fflags);
+ if (err) {
+ goto done;
+ }
+ mtx_lock(f->priv_mtx);
+
+ /* reset sleep flag */
+ f->flag_sleeping = 0;
+
+ /* reset error flag */
+ f->flag_iserror = 0;
+
+ /* reset complete flag */
+ f->flag_iscomplete = 0;
+
+ /* reset select flag */
+ f->flag_isselect = 0;
+
+ /* reset flushing flag */
+ f->flag_flushing = 0;
+
+ /* reset ASYNC proc flag */
+ f->async_p = NULL;
+
+ mtx_lock(&usb_ref_lock);
+ /* flag the fifo as opened to prevent others */
+ f->curr_cpd = cpd;
+ mtx_unlock(&usb_ref_lock);
+
+ /* reset queue */
+ usb_fifo_reset(f);
+
+ mtx_unlock(f->priv_mtx);
+done:
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_fifo_reset
+ *------------------------------------------------------------------------*/
+void
+usb_fifo_reset(struct usb_fifo *f)
+{
+ struct usb_mbuf *m;
+
+ if (f == NULL) {
+ return;
+ }
+ while (1) {
+ USB_IF_DEQUEUE(&f->used_q, m);
+ if (m) {
+ USB_IF_ENQUEUE(&f->free_q, m);
+ } else {
+ break;
+ }
+ }
+ /* reset have fragment flag */
+ f->flag_have_fragment = 0;
+}
+
+/*------------------------------------------------------------------------*
+ * usb_fifo_close
+ *------------------------------------------------------------------------*/
+static void
+usb_fifo_close(struct usb_fifo *f, int fflags)
+{
+ int err;
+
+ /* check if we are not opened */
+ if (f->curr_cpd == NULL) {
+ /* nothing to do - already closed */
+ return;
+ }
+ mtx_lock(f->priv_mtx);
+
+ /* clear current cdev private data pointer */
+ mtx_lock(&usb_ref_lock);
+ f->curr_cpd = NULL;
+ mtx_unlock(&usb_ref_lock);
+
+ /* check if we are watched by kevent */
+ KNOTE_LOCKED(&f->selinfo.si_note, 0);
+
+ /* check if we are selected */
+ if (f->flag_isselect) {
+ selwakeup(&f->selinfo);
+ f->flag_isselect = 0;
+ }
+ /* check if a thread wants SIGIO */
+ if (f->async_p != NULL) {
+ PROC_LOCK(f->async_p);
+ kern_psignal(f->async_p, SIGIO);
+ PROC_UNLOCK(f->async_p);
+ f->async_p = NULL;
+ }
+ /* remove FWRITE and FREAD flags */
+ fflags &= ~(FWRITE | FREAD);
+
+ /* flush written data, if any */
+ if ((f->fifo_index & 1) == USB_FIFO_TX) {
+ if (!f->flag_iserror) {
+ /* set flushing flag */
+ f->flag_flushing = 1;
+
+ /* get the last packet in */
+ if (f->flag_have_fragment) {
+ struct usb_mbuf *m;
+ f->flag_have_fragment = 0;
+ USB_IF_DEQUEUE(&f->free_q, m);
+ if (m) {
+ USB_IF_ENQUEUE(&f->used_q, m);
+ }
+ }
+
+ /* start write transfer, if not already started */
+ (f->methods->f_start_write) (f);
+
+ /* check if flushed already */
+ while (f->flag_flushing &&
+ (!f->flag_iserror)) {
+ /* wait until all data has been written */
+ f->flag_sleeping = 1;
+ err = cv_timedwait_sig(&f->cv_io, f->priv_mtx,
+ USB_MS_TO_TICKS(USB_DEFAULT_TIMEOUT));
+ if (err) {
+ DPRINTF("signal received\n");
+ break;
+ }
+ }
+ }
+ fflags |= FWRITE;
+
+ /* stop write transfer, if not already stopped */
+ (f->methods->f_stop_write) (f);
+ } else {
+ fflags |= FREAD;
+
+ /* stop write transfer, if not already stopped */
+ (f->methods->f_stop_read) (f);
+ }
+
+ /* check if we are sleeping */
+ if (f->flag_sleeping) {
+ DPRINTFN(2, "Sleeping at close!\n");
+ }
+ mtx_unlock(f->priv_mtx);
+
+ /* call close method */
+ (f->methods->f_close) (f, fflags);
+
+ DPRINTF("closed\n");
+}
+
+/*------------------------------------------------------------------------*
+ * usb_open - cdev callback
+ *------------------------------------------------------------------------*/
+static int
+usb_open(struct cdev *dev, int fflags, int devtype, struct thread *td)
+{
+ struct usb_fs_privdata* pd = (struct usb_fs_privdata*)dev->si_drv1;
+ struct usb_cdev_refdata refs;
+ struct usb_cdev_privdata *cpd;
+ int err;
+
+ DPRINTFN(2, "%s fflags=0x%08x\n", devtoname(dev), fflags);
+
+ KASSERT(fflags & (FREAD|FWRITE), ("invalid open flags"));
+ if (((fflags & FREAD) && !(pd->mode & FREAD)) ||
+ ((fflags & FWRITE) && !(pd->mode & FWRITE))) {
+ DPRINTFN(2, "access mode not supported\n");
+ return (EPERM);
+ }
+
+ cpd = malloc(sizeof(*cpd), M_USBDEV, M_WAITOK | M_ZERO);
+
+ usb_loc_fill(pd, cpd);
+ err = usb_ref_device(cpd, &refs, 1);
+ if (err) {
+ DPRINTFN(2, "cannot ref device\n");
+ free(cpd, M_USBDEV);
+ return (ENXIO);
+ }
+ cpd->fflags = fflags; /* access mode for open lifetime */
+
+ /* create FIFOs, if any */
+ err = usb_fifo_create(cpd, &refs);
+ /* check for error */
+ if (err) {
+ DPRINTFN(2, "cannot create fifo\n");
+ usb_unref_device(cpd, &refs);
+ free(cpd, M_USBDEV);
+ return (err);
+ }
+ if (fflags & FREAD) {
+ err = usb_fifo_open(cpd, refs.rxfifo, fflags);
+ if (err) {
+ DPRINTFN(2, "read open failed\n");
+ usb_unref_device(cpd, &refs);
+ free(cpd, M_USBDEV);
+ return (err);
+ }
+ }
+ if (fflags & FWRITE) {
+ err = usb_fifo_open(cpd, refs.txfifo, fflags);
+ if (err) {
+ DPRINTFN(2, "write open failed\n");
+ if (fflags & FREAD) {
+ usb_fifo_close(refs.rxfifo, fflags);
+ }
+ usb_unref_device(cpd, &refs);
+ free(cpd, M_USBDEV);
+ return (err);
+ }
+ }
+ usb_unref_device(cpd, &refs);
+ devfs_set_cdevpriv(cpd, usb_close);
+
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_close - cdev callback
+ *------------------------------------------------------------------------*/
+static void
+usb_close(void *arg)
+{
+ struct usb_cdev_refdata refs;
+ struct usb_cdev_privdata *cpd = arg;
+ int err;
+
+ DPRINTFN(2, "cpd=%p\n", cpd);
+
+ err = usb_ref_device(cpd, &refs,
+ 2 /* uref and allow detached state */);
+ if (err) {
+ DPRINTFN(2, "Cannot grab USB reference when "
+ "closing USB file handle\n");
+ goto done;
+ }
+ if (cpd->fflags & FREAD) {
+ usb_fifo_close(refs.rxfifo, cpd->fflags);
+ }
+ if (cpd->fflags & FWRITE) {
+ usb_fifo_close(refs.txfifo, cpd->fflags);
+ }
+ usb_unref_device(cpd, &refs);
+done:
+ free(cpd, M_USBDEV);
+}
+
+static void
+usb_dev_init(void *arg)
+{
+ mtx_init(&usb_ref_lock, "USB ref mutex", NULL, MTX_DEF);
+ sx_init(&usb_sym_lock, "USB sym mutex");
+ TAILQ_INIT(&usb_sym_head);
+
+ /* check the UGEN methods */
+ usb_fifo_check_methods(&usb_ugen_methods);
+}
+
+SYSINIT(usb_dev_init, SI_SUB_KLD, SI_ORDER_FIRST, usb_dev_init, NULL);
+
+static void
+usb_dev_init_post(void *arg)
+{
+ /*
+ * Create /dev/usb - this is needed for usbconfig(8), which
+ * needs a well-known device name to access.
+ */
+ usb_dev = make_dev(&usb_static_devsw, 0, UID_ROOT, GID_OPERATOR,
+ 0644, USB_DEVICE_NAME);
+ if (usb_dev == NULL) {
+ DPRINTFN(0, "Could not create usb bus device\n");
+ }
+}
+
+SYSINIT(usb_dev_init_post, SI_SUB_KICK_SCHEDULER, SI_ORDER_FIRST, usb_dev_init_post, NULL);
+
+static void
+usb_dev_uninit(void *arg)
+{
+ if (usb_dev != NULL) {
+ destroy_dev(usb_dev);
+ usb_dev = NULL;
+ }
+ mtx_destroy(&usb_ref_lock);
+ sx_destroy(&usb_sym_lock);
+}
+
+SYSUNINIT(usb_dev_uninit, SI_SUB_KICK_SCHEDULER, SI_ORDER_ANY, usb_dev_uninit, NULL);
+
+static int
+usb_ioctl_f_sub(struct usb_fifo *f, u_long cmd, void *addr,
+ struct thread *td)
+{
+ int error = 0;
+
+ switch (cmd) {
+ case FIODTYPE:
+ *(int *)addr = 0; /* character device */
+ break;
+
+ case FIONBIO:
+ /* handled by upper FS layer */
+ break;
+
+ case FIOASYNC:
+ if (*(int *)addr) {
+ if (f->async_p != NULL) {
+ error = EBUSY;
+ break;
+ }
+ f->async_p = USB_TD_GET_PROC(td);
+ } else {
+ f->async_p = NULL;
+ }
+ break;
+
+ /* XXX this is not the most general solution */
+ case TIOCSPGRP:
+ if (f->async_p == NULL) {
+ error = EINVAL;
+ break;
+ }
+ if (*(int *)addr != USB_PROC_GET_GID(f->async_p)) {
+ error = EPERM;
+ break;
+ }
+ break;
+ default:
+ return (ENOIOCTL);
+ }
+ DPRINTFN(3, "cmd 0x%lx = %d\n", cmd, error);
+ return (error);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_ioctl - cdev callback
+ *------------------------------------------------------------------------*/
+static int
+usb_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int fflag, struct thread* td)
+{
+ struct usb_cdev_refdata refs;
+ struct usb_cdev_privdata* cpd;
+ struct usb_fifo *f;
+ int fflags;
+ int err;
+
+ DPRINTFN(2, "cmd=0x%lx\n", cmd);
+
+ err = devfs_get_cdevpriv((void **)&cpd);
+ if (err != 0)
+ return (err);
+
+ /*
+ * Performance optimisation: We try to check for IOCTL's that
+ * don't need the USB reference first. Then we grab the USB
+ * reference if we need it!
+ */
+ err = usb_ref_device(cpd, &refs, 0 /* no uref */ );
+ if (err)
+ return (ENXIO);
+
+ fflags = cpd->fflags;
+
+ f = NULL; /* set default value */
+ err = ENOIOCTL; /* set default value */
+
+ if (fflags & FWRITE) {
+ f = refs.txfifo;
+ err = usb_ioctl_f_sub(f, cmd, addr, td);
+ }
+ if (fflags & FREAD) {
+ f = refs.rxfifo;
+ err = usb_ioctl_f_sub(f, cmd, addr, td);
+ }
+ KASSERT(f != NULL, ("fifo not found"));
+ if (err != ENOIOCTL)
+ goto done;
+
+ err = (f->methods->f_ioctl) (f, cmd, addr, fflags);
+
+ DPRINTFN(2, "f_ioctl cmd 0x%lx = %d\n", cmd, err);
+
+ if (err != ENOIOCTL)
+ goto done;
+
+ if (usb_usb_ref_device(cpd, &refs)) {
+ /* we lost the reference */
+ return (ENXIO);
+ }
+
+ err = (f->methods->f_ioctl_post) (f, cmd, addr, fflags);
+
+ DPRINTFN(2, "f_ioctl_post cmd 0x%lx = %d\n", cmd, err);
+
+ if (err == ENOIOCTL)
+ err = ENOTTY;
+
+ if (err)
+ goto done;
+
+ /* Wait for re-enumeration, if any */
+
+ while (f->udev->re_enumerate_wait != USB_RE_ENUM_DONE) {
+ usb_unref_device(cpd, &refs);
+
+ usb_pause_mtx(NULL, hz / 128);
+
+ while (usb_ref_device(cpd, &refs, 1 /* need uref */)) {
+ if (usb_ref_device(cpd, &refs, 0)) {
+ /* device no longer exists */
+ return (ENXIO);
+ }
+ usb_unref_device(cpd, &refs);
+ usb_pause_mtx(NULL, hz / 128);
+ }
+ }
+
+done:
+ usb_unref_device(cpd, &refs);
+ return (err);
+}
+
+static void
+usb_filter_detach(struct knote *kn)
+{
+ struct usb_fifo *f = kn->kn_hook;
+ knlist_remove(&f->selinfo.si_note, kn, 0);
+}
+
+static int
+usb_filter_write(struct knote *kn, long hint)
+{
+ struct usb_cdev_privdata* cpd;
+ struct usb_fifo *f;
+ struct usb_mbuf *m;
+
+ DPRINTFN(2, "\n");
+
+ f = kn->kn_hook;
+
+ USB_MTX_ASSERT(f->priv_mtx, MA_OWNED);
+
+ cpd = f->curr_cpd;
+ if (cpd == NULL) {
+ m = (void *)1;
+ } else if (f->fs_ep_max == 0) {
+ if (f->flag_iserror) {
+ /* we got an error */
+ m = (void *)1;
+ } else {
+ if (f->queue_data == NULL) {
+ /*
+ * start write transfer, if not
+ * already started
+ */
+ (f->methods->f_start_write) (f);
+ }
+ /* check if any packets are available */
+ USB_IF_POLL(&f->free_q, m);
+ }
+ } else {
+ if (f->flag_iscomplete) {
+ m = (void *)1;
+ } else {
+ m = NULL;
+ }
+ }
+ return (m ? 1 : 0);
+}
+
+static int
+usb_filter_read(struct knote *kn, long hint)
+{
+ struct usb_cdev_privdata* cpd;
+ struct usb_fifo *f;
+ struct usb_mbuf *m;
+
+ DPRINTFN(2, "\n");
+
+ f = kn->kn_hook;
+
+ USB_MTX_ASSERT(f->priv_mtx, MA_OWNED);
+
+ cpd = f->curr_cpd;
+ if (cpd == NULL) {
+ m = (void *)1;
+ } else if (f->fs_ep_max == 0) {
+ if (f->flag_iserror) {
+ /* we have an error */
+ m = (void *)1;
+ } else {
+ if (f->queue_data == NULL) {
+ /*
+ * start read transfer, if not
+ * already started
+ */
+ (f->methods->f_start_read) (f);
+ }
+ /* check if any packets are available */
+ USB_IF_POLL(&f->used_q, m);
+
+ /* start reading data, if any */
+ if (m == NULL)
+ (f->methods->f_start_read) (f);
+ }
+ } else {
+ if (f->flag_iscomplete) {
+ m = (void *)1;
+ } else {
+ m = NULL;
+ }
+ }
+ return (m ? 1 : 0);
+}
+
+static const struct filterops usb_filtops_write = {
+ .f_isfd = 1,
+ .f_detach = usb_filter_detach,
+ .f_event = usb_filter_write,
+};
+
+static const struct filterops usb_filtops_read = {
+ .f_isfd = 1,
+ .f_detach = usb_filter_detach,
+ .f_event = usb_filter_read,
+};
+
+/* ARGSUSED */
+static int
+usb_kqfilter(struct cdev* dev, struct knote *kn)
+{
+ struct usb_cdev_refdata refs;
+ struct usb_cdev_privdata* cpd;
+ struct usb_fifo *f;
+ int fflags;
+ int err = EINVAL;
+
+ DPRINTFN(2, "\n");
+
+ if (devfs_get_cdevpriv((void **)&cpd) != 0 ||
+ usb_ref_device(cpd, &refs, 0) != 0)
+ return (ENXIO);
+
+ fflags = cpd->fflags;
+
+ /* Figure out who needs service */
+ switch (kn->kn_filter) {
+ case EVFILT_WRITE:
+ if (fflags & FWRITE) {
+ f = refs.txfifo;
+ kn->kn_fop = &usb_filtops_write;
+ err = 0;
+ }
+ break;
+ case EVFILT_READ:
+ if (fflags & FREAD) {
+ f = refs.rxfifo;
+ kn->kn_fop = &usb_filtops_read;
+ err = 0;
+ }
+ break;
+ default:
+ err = EOPNOTSUPP;
+ break;
+ }
+
+ if (err == 0) {
+ kn->kn_hook = f;
+ mtx_lock(f->priv_mtx);
+ knlist_add(&f->selinfo.si_note, kn, 1);
+ mtx_unlock(f->priv_mtx);
+ }
+
+ usb_unref_device(cpd, &refs);
+ return (err);
+}
+
+/* ARGSUSED */
+static int
+usb_poll(struct cdev* dev, int events, struct thread* td)
+{
+ struct usb_cdev_refdata refs;
+ struct usb_cdev_privdata* cpd;
+ struct usb_fifo *f;
+ struct usb_mbuf *m;
+ int fflags, revents;
+
+ if (devfs_get_cdevpriv((void **)&cpd) != 0 ||
+ usb_ref_device(cpd, &refs, 0) != 0)
+ return (events &
+ (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM));
+
+ fflags = cpd->fflags;
+
+ /* Figure out who needs service */
+ revents = 0;
+ if ((events & (POLLOUT | POLLWRNORM)) &&
+ (fflags & FWRITE)) {
+ f = refs.txfifo;
+
+ mtx_lock(f->priv_mtx);
+
+ if (!refs.is_usbfs) {
+ if (f->flag_iserror) {
+ /* we got an error */
+ m = (void *)1;
+ } else {
+ if (f->queue_data == NULL) {
+ /*
+ * start write transfer, if not
+ * already started
+ */
+ (f->methods->f_start_write) (f);
+ }
+ /* check if any packets are available */
+ USB_IF_POLL(&f->free_q, m);
+ }
+ } else {
+ if (f->flag_iscomplete) {
+ m = (void *)1;
+ } else {
+ m = NULL;
+ }
+ }
+
+ if (m) {
+ revents |= events & (POLLOUT | POLLWRNORM);
+ } else {
+ f->flag_isselect = 1;
+ selrecord(td, &f->selinfo);
+ }
+
+ mtx_unlock(f->priv_mtx);
+ }
+ if ((events & (POLLIN | POLLRDNORM)) &&
+ (fflags & FREAD)) {
+ f = refs.rxfifo;
+
+ mtx_lock(f->priv_mtx);
+
+ if (!refs.is_usbfs) {
+ if (f->flag_iserror) {
+ /* we have an error */
+ m = (void *)1;
+ } else {
+ if (f->queue_data == NULL) {
+ /*
+ * start read transfer, if not
+ * already started
+ */
+ (f->methods->f_start_read) (f);
+ }
+ /* check if any packets are available */
+ USB_IF_POLL(&f->used_q, m);
+ }
+ } else {
+ if (f->flag_iscomplete) {
+ m = (void *)1;
+ } else {
+ m = NULL;
+ }
+ }
+
+ if (m) {
+ revents |= events & (POLLIN | POLLRDNORM);
+ } else {
+ f->flag_isselect = 1;
+ selrecord(td, &f->selinfo);
+
+ if (!refs.is_usbfs) {
+ /* start reading data */
+ (f->methods->f_start_read) (f);
+ }
+ }
+
+ mtx_unlock(f->priv_mtx);
+ }
+ usb_unref_device(cpd, &refs);
+ return (revents);
+}
+
+static int
+usb_read(struct cdev *dev, struct uio *uio, int ioflag)
+{
+ struct usb_cdev_refdata refs;
+ struct usb_cdev_privdata* cpd;
+ struct usb_fifo *f;
+ struct usb_mbuf *m;
+ int io_len;
+ int err;
+ uint8_t tr_data = 0;
+
+ err = devfs_get_cdevpriv((void **)&cpd);
+ if (err != 0)
+ return (err);
+
+ err = usb_ref_device(cpd, &refs, 0 /* no uref */ );
+ if (err)
+ return (ENXIO);
+
+ f = refs.rxfifo;
+ if (f == NULL) {
+ /* should not happen */
+ usb_unref_device(cpd, &refs);
+ return (EPERM);
+ }
+
+ mtx_lock(f->priv_mtx);
+
+ /* check for permanent read error */
+ if (f->flag_iserror) {
+ err = EIO;
+ goto done;
+ }
+ /* check if USB-FS interface is active */
+ if (refs.is_usbfs) {
+ /*
+ * The queue is used for events that should be
+ * retrieved using the "USB_FS_COMPLETE" ioctl.
+ */
+ err = EINVAL;
+ goto done;
+ }
+ while (uio->uio_resid > 0) {
+ USB_IF_DEQUEUE(&f->used_q, m);
+
+ if (m == NULL) {
+ /* start read transfer, if not already started */
+
+ (f->methods->f_start_read) (f);
+
+ if (ioflag & IO_NDELAY) {
+ if (tr_data) {
+ /* return length before error */
+ break;
+ }
+ err = EWOULDBLOCK;
+ break;
+ }
+ DPRINTF("sleeping\n");
+
+ err = usb_fifo_wait(f);
+ if (err) {
+ break;
+ }
+ continue;
+ }
+ if (f->methods->f_filter_read) {
+ /*
+ * Sometimes it is convenient to process data at the
+ * expense of a userland process instead of a kernel
+ * process.
+ */
+ (f->methods->f_filter_read) (f, m);
+ }
+ tr_data = 1;
+
+ io_len = MIN(m->cur_data_len, uio->uio_resid);
+
+ DPRINTFN(2, "transfer %d bytes from %p\n",
+ io_len, m->cur_data_ptr);
+
+ err = usb_fifo_uiomove(f,
+ m->cur_data_ptr, io_len, uio);
+
+ m->cur_data_len -= io_len;
+ m->cur_data_ptr += io_len;
+
+ if (m->cur_data_len == 0) {
+ uint8_t last_packet;
+
+ last_packet = m->last_packet;
+
+ USB_IF_ENQUEUE(&f->free_q, m);
+
+ if (last_packet) {
+ /* keep framing */
+ break;
+ }
+ } else {
+ USB_IF_PREPEND(&f->used_q, m);
+ }
+
+ if (err) {
+ break;
+ }
+ }
+done:
+ mtx_unlock(f->priv_mtx);
+
+ usb_unref_device(cpd, &refs);
+
+ return (err);
+}
+
+static int
+usb_write(struct cdev *dev, struct uio *uio, int ioflag)
+{
+ struct usb_cdev_refdata refs;
+ struct usb_cdev_privdata* cpd;
+ struct usb_fifo *f;
+ struct usb_mbuf *m;
+ uint8_t *pdata;
+ int io_len;
+ int err;
+ uint8_t tr_data = 0;
+
+ DPRINTFN(2, "\n");
+
+ err = devfs_get_cdevpriv((void **)&cpd);
+ if (err != 0)
+ return (err);
+
+ err = usb_ref_device(cpd, &refs, 0 /* no uref */ );
+ if (err)
+ return (ENXIO);
+
+ f = refs.txfifo;
+ if (f == NULL) {
+ /* should not happen */
+ usb_unref_device(cpd, &refs);
+ return (EPERM);
+ }
+
+ mtx_lock(f->priv_mtx);
+
+ /* check for permanent write error */
+ if (f->flag_iserror) {
+ err = EIO;
+ goto done;
+ }
+ /* check if USB-FS interface is active */
+ if (refs.is_usbfs) {
+ /*
+ * The queue is used for events that should be
+ * retrieved using the "USB_FS_COMPLETE" ioctl.
+ */
+ err = EINVAL;
+ goto done;
+ }
+ if (f->queue_data == NULL) {
+ /* start write transfer, if not already started */
+ (f->methods->f_start_write) (f);
+ }
+ /* we allow writing zero length data */
+ do {
+ USB_IF_DEQUEUE(&f->free_q, m);
+
+ if (m == NULL) {
+ if (ioflag & IO_NDELAY) {
+ if (tr_data) {
+ /* return length before error */
+ break;
+ }
+ err = EWOULDBLOCK;
+ break;
+ }
+ DPRINTF("sleeping\n");
+
+ err = usb_fifo_wait(f);
+ if (err) {
+ break;
+ }
+ continue;
+ }
+ tr_data = 1;
+
+ if (f->flag_have_fragment == 0) {
+ USB_MBUF_RESET(m);
+ io_len = m->cur_data_len;
+ pdata = m->cur_data_ptr;
+ if (io_len > uio->uio_resid)
+ io_len = uio->uio_resid;
+ m->cur_data_len = io_len;
+ } else {
+ io_len = m->max_data_len - m->cur_data_len;
+ pdata = m->cur_data_ptr + m->cur_data_len;
+ if (io_len > uio->uio_resid)
+ io_len = uio->uio_resid;
+ m->cur_data_len += io_len;
+ }
+
+ DPRINTFN(2, "transfer %d bytes to %p\n",
+ io_len, pdata);
+
+ err = usb_fifo_uiomove(f, pdata, io_len, uio);
+
+ if (err) {
+ f->flag_have_fragment = 0;
+ USB_IF_ENQUEUE(&f->free_q, m);
+ break;
+ }
+
+ /* check if the buffer is ready to be transmitted */
+
+ if ((f->flag_write_defrag == 0) ||
+ (m->cur_data_len == m->max_data_len)) {
+ f->flag_have_fragment = 0;
+
+ /*
+ * Check for write filter:
+ *
+ * Sometimes it is convenient to process data
+ * at the expense of a userland process
+ * instead of a kernel process.
+ */
+ if (f->methods->f_filter_write) {
+ (f->methods->f_filter_write) (f, m);
+ }
+
+ /* Put USB mbuf in the used queue */
+ USB_IF_ENQUEUE(&f->used_q, m);
+
+ /* Start writing data, if not already started */
+ (f->methods->f_start_write) (f);
+ } else {
+ /* Wait for more data or close */
+ f->flag_have_fragment = 1;
+ USB_IF_PREPEND(&f->free_q, m);
+ }
+
+ } while (uio->uio_resid > 0);
+done:
+ mtx_unlock(f->priv_mtx);
+
+ usb_unref_device(cpd, &refs);
+
+ return (err);
+}
+
+int
+usb_static_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
+ struct thread *td)
+{
+ union {
+ struct usb_read_dir *urd;
+#ifdef COMPAT_FREEBSD32
+ struct usb_read_dir32 *urd32;
+#endif
+ void* data;
+ } u;
+ int err;
+
+ u.data = data;
+ switch (cmd) {
+ case USB_READ_DIR:
+ err = usb_read_symlink(u.urd->urd_data,
+ u.urd->urd_startentry, u.urd->urd_maxlen);
+ break;
+#ifdef COMPAT_FREEBSD32
+ case USB_READ_DIR32:
+ err = usb_read_symlink(PTRIN(u.urd32->urd_data),
+ u.urd32->urd_startentry, u.urd32->urd_maxlen);
+ break;
+#endif
+ case USB_DEV_QUIRK_GET:
+ case USB_QUIRK_NAME_GET:
+ case USB_DEV_QUIRK_ADD:
+ case USB_DEV_QUIRK_REMOVE:
+ err = usb_quirk_ioctl_p(cmd, data, fflag, td);
+ break;
+ case USB_GET_TEMPLATE:
+ *(int *)data = usb_template;
+ err = 0;
+ break;
+ case USB_SET_TEMPLATE:
+ err = priv_check(curthread, PRIV_DRIVER);
+ if (err)
+ break;
+ usb_template = *(int *)data;
+ break;
+ default:
+ err = ENOTTY;
+ break;
+ }
+ return (err);
+}
+
+static int
+usb_fifo_uiomove(struct usb_fifo *f, void *cp,
+ int n, struct uio *uio)
+{
+ int error;
+
+ mtx_unlock(f->priv_mtx);
+
+ /*
+ * "uiomove()" can sleep so one needs to make a wrapper,
+ * exiting the mutex and checking things:
+ */
+ error = uiomove(cp, n, uio);
+
+ mtx_lock(f->priv_mtx);
+
+ return (error);
+}
+
+int
+usb_fifo_wait(struct usb_fifo *f)
+{
+ int err;
+
+ USB_MTX_ASSERT(f->priv_mtx, MA_OWNED);
+
+ if (f->flag_iserror) {
+ /* we are gone */
+ return (EIO);
+ }
+ f->flag_sleeping = 1;
+
+ err = cv_wait_sig(&f->cv_io, f->priv_mtx);
+
+ if (f->flag_iserror) {
+ /* we are gone */
+ err = EIO;
+ }
+ return (err);
+}
+
+void
+usb_fifo_signal(struct usb_fifo *f)
+{
+ if (f->flag_sleeping) {
+ f->flag_sleeping = 0;
+ cv_broadcast(&f->cv_io);
+ }
+}
+
+void
+usb_fifo_wakeup(struct usb_fifo *f)
+{
+ usb_fifo_signal(f);
+
+ KNOTE_LOCKED(&f->selinfo.si_note, 0);
+
+ if (f->flag_isselect) {
+ selwakeup(&f->selinfo);
+ f->flag_isselect = 0;
+ }
+ if (f->async_p != NULL) {
+ PROC_LOCK(f->async_p);
+ kern_psignal(f->async_p, SIGIO);
+ PROC_UNLOCK(f->async_p);
+ }
+}
+
+static int
+usb_fifo_dummy_open(struct usb_fifo *fifo, int fflags)
+{
+ return (0);
+}
+
+static void
+usb_fifo_dummy_close(struct usb_fifo *fifo, int fflags)
+{
+ return;
+}
+
+static int
+usb_fifo_dummy_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
+{
+ return (ENOIOCTL);
+}
+
+static void
+usb_fifo_dummy_cmd(struct usb_fifo *fifo)
+{
+ fifo->flag_flushing = 0; /* not flushing */
+}
+
+static void
+usb_fifo_check_methods(struct usb_fifo_methods *pm)
+{
+ /* check that all callback functions are OK */
+
+ if (pm->f_open == NULL)
+ pm->f_open = &usb_fifo_dummy_open;
+
+ if (pm->f_close == NULL)
+ pm->f_close = &usb_fifo_dummy_close;
+
+ if (pm->f_ioctl == NULL)
+ pm->f_ioctl = &usb_fifo_dummy_ioctl;
+
+ if (pm->f_ioctl_post == NULL)
+ pm->f_ioctl_post = &usb_fifo_dummy_ioctl;
+
+ if (pm->f_start_read == NULL)
+ pm->f_start_read = &usb_fifo_dummy_cmd;
+
+ if (pm->f_stop_read == NULL)
+ pm->f_stop_read = &usb_fifo_dummy_cmd;
+
+ if (pm->f_start_write == NULL)
+ pm->f_start_write = &usb_fifo_dummy_cmd;
+
+ if (pm->f_stop_write == NULL)
+ pm->f_stop_write = &usb_fifo_dummy_cmd;
+}
+
+/*------------------------------------------------------------------------*
+ * usb_fifo_attach
+ *
+ * The following function will create a duplex FIFO.
+ *
+ * Return values:
+ * 0: Success.
+ * Else: Failure.
+ *------------------------------------------------------------------------*/
+int
+usb_fifo_attach(struct usb_device *udev, void *priv_sc,
+ struct mtx *priv_mtx, struct usb_fifo_methods *pm,
+ struct usb_fifo_sc *f_sc, uint16_t unit, int16_t subunit,
+ uint8_t iface_index, uid_t uid, gid_t gid, int mode)
+{
+ struct usb_fifo *f_tx;
+ struct usb_fifo *f_rx;
+ char devname[32];
+ uint8_t n;
+
+ f_sc->fp[USB_FIFO_TX] = NULL;
+ f_sc->fp[USB_FIFO_RX] = NULL;
+
+ if (pm == NULL)
+ return (EINVAL);
+
+ /* check the methods */
+ usb_fifo_check_methods(pm);
+
+ if (priv_mtx == NULL)
+ priv_mtx = &Giant;
+
+ /* search for a free FIFO slot */
+ for (n = 0;; n += 2) {
+ if (n == USB_FIFO_MAX) {
+ /* end of FIFOs reached */
+ return (ENOMEM);
+ }
+ /* Check for TX FIFO */
+ if (udev->fifo[n + USB_FIFO_TX] != NULL) {
+ continue;
+ }
+ /* Check for RX FIFO */
+ if (udev->fifo[n + USB_FIFO_RX] != NULL) {
+ continue;
+ }
+ break;
+ }
+
+ f_tx = usb_fifo_alloc(priv_mtx);
+ f_rx = usb_fifo_alloc(priv_mtx);
+
+ if ((f_tx == NULL) || (f_rx == NULL)) {
+ usb_fifo_free(f_tx);
+ usb_fifo_free(f_rx);
+ return (ENOMEM);
+ }
+ /* initialise FIFO structures */
+
+ f_tx->fifo_index = n + USB_FIFO_TX;
+ f_tx->dev_ep_index = -1;
+ f_tx->priv_sc0 = priv_sc;
+ f_tx->methods = pm;
+ f_tx->iface_index = iface_index;
+ f_tx->udev = udev;
+
+ f_rx->fifo_index = n + USB_FIFO_RX;
+ f_rx->dev_ep_index = -1;
+ f_rx->priv_sc0 = priv_sc;
+ f_rx->methods = pm;
+ f_rx->iface_index = iface_index;
+ f_rx->udev = udev;
+
+ f_sc->fp[USB_FIFO_TX] = f_tx;
+ f_sc->fp[USB_FIFO_RX] = f_rx;
+
+ mtx_lock(&usb_ref_lock);
+ udev->fifo[f_tx->fifo_index] = f_tx;
+ udev->fifo[f_rx->fifo_index] = f_rx;
+ mtx_unlock(&usb_ref_lock);
+
+ for (n = 0; n != 4; n++) {
+ if (pm->basename[n] == NULL) {
+ continue;
+ }
+ if (subunit < 0) {
+ if (snprintf(devname, sizeof(devname),
+ "%s%u%s", pm->basename[n],
+ unit, pm->postfix[n] ?
+ pm->postfix[n] : "")) {
+ /* ignore */
+ }
+ } else {
+ if (snprintf(devname, sizeof(devname),
+ "%s%u.%d%s", pm->basename[n],
+ unit, subunit, pm->postfix[n] ?
+ pm->postfix[n] : "")) {
+ /* ignore */
+ }
+ }
+
+ /*
+ * Distribute the symbolic links into two FIFO structures:
+ */
+ if (n & 1) {
+ f_rx->symlink[n / 2] =
+ usb_alloc_symlink(devname);
+ } else {
+ f_tx->symlink[n / 2] =
+ usb_alloc_symlink(devname);
+ }
+
+ /* Create the device */
+ f_sc->dev = usb_make_dev(udev, devname, -1,
+ f_tx->fifo_index & f_rx->fifo_index,
+ FREAD|FWRITE, uid, gid, mode);
+ }
+
+ DPRINTFN(2, "attached %p/%p\n", f_tx, f_rx);
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_fifo_alloc_buffer
+ *
+ * Return values:
+ * 0: Success
+ * Else failure
+ *------------------------------------------------------------------------*/
+int
+usb_fifo_alloc_buffer(struct usb_fifo *f, usb_size_t bufsize,
+ uint16_t nbuf)
+{
+ struct usb_ifqueue temp_q = {};
+ void *queue_data;
+
+ usb_fifo_free_buffer(f);
+
+ temp_q.ifq_maxlen = nbuf;
+
+ queue_data = usb_alloc_mbufs(
+ M_USBDEV, &temp_q, bufsize, nbuf);
+
+ if (queue_data == NULL && bufsize != 0 && nbuf != 0)
+ return (ENOMEM);
+
+ mtx_lock(f->priv_mtx);
+
+ /*
+ * Setup queues and sizes under lock to avoid early use by
+ * concurrent FIFO access:
+ */
+ f->free_q = temp_q;
+ f->used_q.ifq_maxlen = nbuf;
+ f->queue_data = queue_data;
+ mtx_unlock(f->priv_mtx);
+
+ return (0); /* success */
+}
+
+/*------------------------------------------------------------------------*
+ * usb_fifo_free_buffer
+ *
+ * This function will free the buffers associated with a FIFO. This
+ * function can be called multiple times in a row.
+ *------------------------------------------------------------------------*/
+void
+usb_fifo_free_buffer(struct usb_fifo *f)
+{
+ void *queue_data;
+
+ mtx_lock(f->priv_mtx);
+
+ /* Get and clear pointer to free, if any. */
+ queue_data = f->queue_data;
+ f->queue_data = NULL;
+
+ /*
+ * Reset queues under lock to avoid use of freed buffers by
+ * concurrent FIFO activity:
+ */
+ memset(&f->free_q, 0, sizeof(f->free_q));
+ memset(&f->used_q, 0, sizeof(f->used_q));
+ mtx_unlock(f->priv_mtx);
+
+ /* Free old buffer, if any. */
+ free(queue_data, M_USBDEV);
+}
+
+void
+usb_fifo_detach(struct usb_fifo_sc *f_sc)
+{
+ if (f_sc == NULL) {
+ return;
+ }
+ usb_fifo_free(f_sc->fp[USB_FIFO_TX]);
+ usb_fifo_free(f_sc->fp[USB_FIFO_RX]);
+
+ f_sc->fp[USB_FIFO_TX] = NULL;
+ f_sc->fp[USB_FIFO_RX] = NULL;
+
+ usb_destroy_dev(f_sc->dev);
+
+ f_sc->dev = NULL;
+
+ DPRINTFN(2, "detached %p\n", f_sc);
+}
+
+usb_size_t
+usb_fifo_put_bytes_max(struct usb_fifo *f)
+{
+ struct usb_mbuf *m;
+ usb_size_t len;
+
+ USB_IF_POLL(&f->free_q, m);
+
+ if (m) {
+ len = m->max_data_len;
+ } else {
+ len = 0;
+ }
+ return (len);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_fifo_put_data
+ *
+ * what:
+ * 0 - normal operation
+ * 1 - set last packet flag to enforce framing
+ *------------------------------------------------------------------------*/
+void
+usb_fifo_put_data(struct usb_fifo *f, struct usb_page_cache *pc,
+ usb_frlength_t offset, usb_frlength_t len, uint8_t what)
+{
+ struct usb_mbuf *m;
+ usb_frlength_t io_len;
+
+ while (len || (what == 1)) {
+ USB_IF_DEQUEUE(&f->free_q, m);
+
+ if (m) {
+ USB_MBUF_RESET(m);
+
+ io_len = MIN(len, m->cur_data_len);
+
+ usbd_copy_out(pc, offset, m->cur_data_ptr, io_len);
+
+ m->cur_data_len = io_len;
+ offset += io_len;
+ len -= io_len;
+
+ if ((len == 0) && (what == 1)) {
+ m->last_packet = 1;
+ }
+ USB_IF_ENQUEUE(&f->used_q, m);
+
+ usb_fifo_wakeup(f);
+
+ if ((len == 0) || (what == 1)) {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+}
+
+void
+usb_fifo_put_data_linear(struct usb_fifo *f, void *ptr,
+ usb_size_t len, uint8_t what)
+{
+ struct usb_mbuf *m;
+ usb_size_t io_len;
+
+ while (len || (what == 1)) {
+ USB_IF_DEQUEUE(&f->free_q, m);
+
+ if (m) {
+ USB_MBUF_RESET(m);
+
+ io_len = MIN(len, m->cur_data_len);
+
+ memcpy(m->cur_data_ptr, ptr, io_len);
+
+ m->cur_data_len = io_len;
+ ptr = USB_ADD_BYTES(ptr, io_len);
+ len -= io_len;
+
+ if ((len == 0) && (what == 1)) {
+ m->last_packet = 1;
+ }
+ USB_IF_ENQUEUE(&f->used_q, m);
+
+ usb_fifo_wakeup(f);
+
+ if ((len == 0) || (what == 1)) {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+}
+
+uint8_t
+usb_fifo_put_data_buffer(struct usb_fifo *f, void *ptr, usb_size_t len)
+{
+ struct usb_mbuf *m;
+
+ USB_IF_DEQUEUE(&f->free_q, m);
+
+ if (m) {
+ m->cur_data_len = len;
+ m->cur_data_ptr = ptr;
+ USB_IF_ENQUEUE(&f->used_q, m);
+ usb_fifo_wakeup(f);
+ return (1);
+ }
+ return (0);
+}
+
+void
+usb_fifo_put_data_error(struct usb_fifo *f)
+{
+ f->flag_iserror = 1;
+ usb_fifo_wakeup(f);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_fifo_get_data
+ *
+ * what:
+ * 0 - normal operation
+ * 1 - only get one "usb_mbuf"
+ *
+ * returns:
+ * 0 - no more data
+ * 1 - data in buffer
+ *------------------------------------------------------------------------*/
+uint8_t
+usb_fifo_get_data(struct usb_fifo *f, struct usb_page_cache *pc,
+ usb_frlength_t offset, usb_frlength_t len, usb_frlength_t *actlen,
+ uint8_t what)
+{
+ struct usb_mbuf *m;
+ usb_frlength_t io_len;
+ uint8_t tr_data = 0;
+
+ actlen[0] = 0;
+
+ while (1) {
+ USB_IF_DEQUEUE(&f->used_q, m);
+
+ if (m) {
+ tr_data = 1;
+
+ io_len = MIN(len, m->cur_data_len);
+
+ usbd_copy_in(pc, offset, m->cur_data_ptr, io_len);
+
+ len -= io_len;
+ offset += io_len;
+ actlen[0] += io_len;
+ m->cur_data_ptr += io_len;
+ m->cur_data_len -= io_len;
+
+ if ((m->cur_data_len == 0) || (what == 1)) {
+ USB_IF_ENQUEUE(&f->free_q, m);
+
+ usb_fifo_wakeup(f);
+
+ if (what == 1) {
+ break;
+ }
+ } else {
+ USB_IF_PREPEND(&f->used_q, m);
+ }
+ } else {
+ if (tr_data) {
+ /* wait for data to be written out */
+ break;
+ }
+ if (f->flag_flushing) {
+ /* check if we should send a short packet */
+ if (f->flag_short != 0) {
+ f->flag_short = 0;
+ tr_data = 1;
+ break;
+ }
+ /* flushing complete */
+ f->flag_flushing = 0;
+ usb_fifo_wakeup(f);
+ }
+ break;
+ }
+ if (len == 0) {
+ break;
+ }
+ }
+ return (tr_data);
+}
+
+uint8_t
+usb_fifo_get_data_linear(struct usb_fifo *f, void *ptr,
+ usb_size_t len, usb_size_t *actlen, uint8_t what)
+{
+ struct usb_mbuf *m;
+ usb_size_t io_len;
+ uint8_t tr_data = 0;
+
+ actlen[0] = 0;
+
+ while (1) {
+ USB_IF_DEQUEUE(&f->used_q, m);
+
+ if (m) {
+ tr_data = 1;
+
+ io_len = MIN(len, m->cur_data_len);
+
+ memcpy(ptr, m->cur_data_ptr, io_len);
+
+ len -= io_len;
+ ptr = USB_ADD_BYTES(ptr, io_len);
+ actlen[0] += io_len;
+ m->cur_data_ptr += io_len;
+ m->cur_data_len -= io_len;
+
+ if ((m->cur_data_len == 0) || (what == 1)) {
+ USB_IF_ENQUEUE(&f->free_q, m);
+
+ usb_fifo_wakeup(f);
+
+ if (what == 1) {
+ break;
+ }
+ } else {
+ USB_IF_PREPEND(&f->used_q, m);
+ }
+ } else {
+ if (tr_data) {
+ /* wait for data to be written out */
+ break;
+ }
+ if (f->flag_flushing) {
+ /* check if we should send a short packet */
+ if (f->flag_short != 0) {
+ f->flag_short = 0;
+ tr_data = 1;
+ break;
+ }
+ /* flushing complete */
+ f->flag_flushing = 0;
+ usb_fifo_wakeup(f);
+ }
+ break;
+ }
+ if (len == 0) {
+ break;
+ }
+ }
+ return (tr_data);
+}
+
+uint8_t
+usb_fifo_get_data_buffer(struct usb_fifo *f, void **pptr, usb_size_t *plen)
+{
+ struct usb_mbuf *m;
+
+ USB_IF_POLL(&f->used_q, m);
+
+ if (m) {
+ *plen = m->cur_data_len;
+ *pptr = m->cur_data_ptr;
+
+ return (1);
+ }
+ return (0);
+}
+
+void
+usb_fifo_get_data_error(struct usb_fifo *f)
+{
+ f->flag_iserror = 1;
+ usb_fifo_wakeup(f);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_alloc_symlink
+ *
+ * Return values:
+ * NULL: Failure
+ * Else: Pointer to symlink entry
+ *------------------------------------------------------------------------*/
+struct usb_symlink *
+usb_alloc_symlink(const char *target)
+{
+ struct usb_symlink *ps;
+
+ ps = malloc(sizeof(*ps), M_USBDEV, M_WAITOK);
+ /* XXX no longer needed */
+ strlcpy(ps->src_path, target, sizeof(ps->src_path));
+ ps->src_len = strlen(ps->src_path);
+ strlcpy(ps->dst_path, target, sizeof(ps->dst_path));
+ ps->dst_len = strlen(ps->dst_path);
+
+ sx_xlock(&usb_sym_lock);
+ TAILQ_INSERT_TAIL(&usb_sym_head, ps, sym_entry);
+ sx_unlock(&usb_sym_lock);
+ return (ps);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_free_symlink
+ *------------------------------------------------------------------------*/
+void
+usb_free_symlink(struct usb_symlink *ps)
+{
+ if (ps == NULL) {
+ return;
+ }
+ sx_xlock(&usb_sym_lock);
+ TAILQ_REMOVE(&usb_sym_head, ps, sym_entry);
+ sx_unlock(&usb_sym_lock);
+
+ free(ps, M_USBDEV);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_read_symlink
+ *
+ * Return value:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+int
+usb_read_symlink(uint8_t *user_ptr, uint32_t startentry, uint32_t user_len)
+{
+ struct usb_symlink *ps;
+ uint32_t temp;
+ uint32_t delta = 0;
+ uint8_t len;
+ int error = 0;
+
+ sx_xlock(&usb_sym_lock);
+
+ TAILQ_FOREACH(ps, &usb_sym_head, sym_entry) {
+ /*
+ * Compute total length of source and destination symlink
+ * strings pluss one length byte and two NUL bytes:
+ */
+ temp = ps->src_len + ps->dst_len + 3;
+
+ if (temp > 255) {
+ /*
+ * Skip entry because this length cannot fit
+ * into one byte:
+ */
+ continue;
+ }
+ if (startentry != 0) {
+ /* decrement read offset */
+ startentry--;
+ continue;
+ }
+ if (temp > user_len) {
+ /* out of buffer space */
+ break;
+ }
+ len = temp;
+
+ /* copy out total length */
+
+ error = copyout(&len,
+ USB_ADD_BYTES(user_ptr, delta), 1);
+ if (error) {
+ break;
+ }
+ delta += 1;
+
+ /* copy out source string */
+
+ error = copyout(ps->src_path,
+ USB_ADD_BYTES(user_ptr, delta), ps->src_len);
+ if (error) {
+ break;
+ }
+ len = 0;
+ delta += ps->src_len;
+ error = copyout(&len,
+ USB_ADD_BYTES(user_ptr, delta), 1);
+ if (error) {
+ break;
+ }
+ delta += 1;
+
+ /* copy out destination string */
+
+ error = copyout(ps->dst_path,
+ USB_ADD_BYTES(user_ptr, delta), ps->dst_len);
+ if (error) {
+ break;
+ }
+ len = 0;
+ delta += ps->dst_len;
+ error = copyout(&len,
+ USB_ADD_BYTES(user_ptr, delta), 1);
+ if (error) {
+ break;
+ }
+ delta += 1;
+
+ user_len -= temp;
+ }
+
+ /* a zero length entry indicates the end */
+
+ if ((user_len != 0) && (error == 0)) {
+ len = 0;
+
+ error = copyout(&len,
+ USB_ADD_BYTES(user_ptr, delta), 1);
+ }
+ sx_unlock(&usb_sym_lock);
+ return (error);
+}
+
+void
+usb_fifo_set_close_zlp(struct usb_fifo *f, uint8_t onoff)
+{
+ if (f == NULL)
+ return;
+
+ /* send a Zero Length Packet, ZLP, before close */
+ f->flag_short = onoff;
+}
+
+void
+usb_fifo_set_write_defrag(struct usb_fifo *f, uint8_t onoff)
+{
+ if (f == NULL)
+ return;
+
+ /* defrag written data */
+ f->flag_write_defrag = onoff;
+ /* reset defrag state */
+ f->flag_have_fragment = 0;
+}
+
+void *
+usb_fifo_softc(struct usb_fifo *f)
+{
+ return (f->priv_sc0);
+}
+#endif /* USB_HAVE_UGEN */
diff --git a/sys/dev/usb/usb_dev.h b/sys/dev/usb/usb_dev.h
new file mode 100644
index 000000000000..381fa1654c95
--- /dev/null
+++ b/sys/dev/usb/usb_dev.h
@@ -0,0 +1,161 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008-2023 Hans Petter Selasky
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_DEV_H_
+#define _USB_DEV_H_
+
+#ifndef USB_GLOBAL_INCLUDE_FILE
+#include <sys/file.h>
+#include <sys/selinfo.h>
+#include <sys/poll.h>
+#include <sys/signalvar.h>
+#include <sys/proc.h>
+#endif
+
+struct usb_fifo;
+struct usb_mbuf;
+
+struct usb_symlink {
+ TAILQ_ENTRY(usb_symlink) sym_entry;
+ char src_path[32]; /* Source path - including terminating
+ * zero */
+ char dst_path[32]; /* Destination path - including
+ * terminating zero */
+ uint8_t src_len; /* String length */
+ uint8_t dst_len; /* String length */
+};
+
+/*
+ * Private per-device information.
+ */
+struct usb_cdev_privdata {
+ struct usb_bus *bus;
+ struct usb_device *udev;
+ struct usb_interface *iface;
+ int bus_index; /* bus index */
+ int dev_index; /* device index */
+ int ep_addr; /* endpoint address */
+ int fflags;
+ uint8_t fifo_index; /* FIFO index */
+};
+
+/*
+ * The following structure defines a minimum re-implementation of the
+ * ifqueue structure in the kernel.
+ */
+struct usb_ifqueue {
+ struct usb_mbuf *ifq_head;
+ struct usb_mbuf *ifq_tail;
+
+ usb_size_t ifq_len;
+ usb_size_t ifq_maxlen;
+};
+
+/*
+ * Private per-device and per-thread reference information
+ */
+struct usb_cdev_refdata {
+ struct usb_fifo *rxfifo;
+ struct usb_fifo *txfifo;
+ uint8_t is_read; /* location has read access */
+ uint8_t is_write; /* location has write access */
+ uint8_t is_uref; /* USB refcount decr. needed */
+ uint8_t is_usbfs; /* USB-FS is active */
+ uint8_t do_unlock; /* USB enum unlock needed */
+};
+
+struct usb_fs_privdata {
+ int bus_index;
+ int dev_index;
+ int ep_addr;
+ int mode;
+ int fifo_index;
+ struct cdev *cdev;
+
+ SLIST_ENTRY(usb_fs_privdata) pd_next;
+};
+
+/*
+ * Most of the fields in the "usb_fifo" structure are used by the
+ * generic USB access layer.
+ */
+struct usb_fifo {
+ struct usb_ifqueue free_q;
+ struct usb_ifqueue used_q;
+ struct selinfo selinfo;
+ struct cv cv_io;
+ struct cv cv_drain;
+ struct sx fs_fastpath_lock;
+ struct usb_fifo_methods *methods;
+ struct usb_symlink *symlink[2];/* our symlinks */
+ struct proc *async_p; /* process that wants SIGIO */
+ struct usb_fs_endpoint *fs_ep_ptr;
+ struct usb_device *udev;
+#define USB_FS_XFER_MAX 126
+ struct usb_xfer *xfer[2];
+ struct usb_xfer *fs_xfer[USB_FS_XFER_MAX];
+ struct mtx *priv_mtx; /* client data */
+ /* set if FIFO is opened by a FILE: */
+ struct usb_cdev_privdata *curr_cpd;
+ void *priv_sc0; /* client data */
+ void *priv_sc1; /* client data */
+ void *queue_data;
+ usb_size_t fs_ep_sz;
+ usb_timeout_t timeout; /* timeout in milliseconds */
+ usb_frlength_t bufsize; /* BULK and INTERRUPT buffer size */
+ usb_frcount_t nframes; /* for isochronous mode */
+ uint16_t dev_ep_index; /* our device endpoint index */
+ uint8_t flag_sleeping; /* set if FIFO is sleeping */
+ uint8_t flag_iscomplete; /* set if a USB transfer is complete */
+ uint8_t flag_iserror; /* set if FIFO error happened */
+ uint8_t flag_isselect; /* set if FIFO is selected */
+ uint8_t flag_flushing; /* set if FIFO is flushing data */
+ uint8_t flag_short; /* set if short_ok or force_short
+ * transfer flags should be set */
+ uint8_t flag_stall; /* set if clear stall should be run */
+ uint8_t flag_write_defrag; /* set to defrag written data */
+ uint8_t flag_have_fragment; /* set if defragging */
+ uint8_t iface_index; /* set to the interface we belong to */
+ uint8_t fifo_index; /* set to the FIFO index in "struct
+ * usb_device" */
+ uint8_t fs_ep_max;
+ uint8_t fifo_zlp; /* zero length packet count */
+ uint8_t refcount;
+#define USB_FIFO_REF_MAX 0xFF
+};
+
+extern struct cdevsw usb_devsw;
+
+int usb_fifo_wait(struct usb_fifo *fifo);
+void usb_fifo_signal(struct usb_fifo *fifo);
+uint8_t usb_fifo_opened(struct usb_fifo *fifo);
+struct usb_symlink *usb_alloc_symlink(const char *target);
+void usb_free_symlink(struct usb_symlink *ps);
+int usb_read_symlink(uint8_t *user_ptr, uint32_t startentry,
+ uint32_t user_len);
+
+#endif /* _USB_DEV_H_ */
diff --git a/sys/dev/usb/usb_device.c b/sys/dev/usb/usb_device.c
new file mode 100644
index 000000000000..60c2d6745b3f
--- /dev/null
+++ b/sys/dev/usb/usb_device.c
@@ -0,0 +1,3113 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008-2023 Hans Petter Selasky
+ *
+ * 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.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/eventhandler.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_ioctl.h>
+
+#if USB_HAVE_UGEN
+#include <sys/sbuf.h>
+#endif
+
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR usb_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_request.h>
+#include <dev/usb/usb_dynamic.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/usb_msctest.h>
+#if USB_HAVE_UGEN
+#include <dev/usb/usb_dev.h>
+#include <dev/usb/usb_generic.h>
+#endif
+
+#include <dev/usb/quirk/usb_quirk.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+/* function prototypes */
+
+static int sysctl_hw_usb_template(SYSCTL_HANDLER_ARGS);
+static void usb_init_endpoint(struct usb_device *, uint8_t,
+ struct usb_endpoint_descriptor *,
+ struct usb_endpoint_ss_comp_descriptor *,
+ struct usb_endpoint *);
+static void usb_unconfigure(struct usb_device *, uint8_t);
+static void usb_detach_device_sub(struct usb_device *, device_t *,
+ char **, uint8_t);
+static uint8_t usb_probe_and_attach_sub(struct usb_device *,
+ struct usb_attach_arg *);
+static void usb_init_attach_arg(struct usb_device *,
+ struct usb_attach_arg *);
+static void usb_suspend_resume_sub(struct usb_device *, device_t,
+ uint8_t);
+static usb_proc_callback_t usbd_clear_stall_proc;
+static usb_error_t usb_config_parse(struct usb_device *, uint8_t, uint8_t);
+#if USB_HAVE_DEVCTL
+static void usb_notify_addq(const char *type, struct usb_device *);
+#endif
+#if USB_HAVE_UGEN
+static void usb_fifo_free_wrap(struct usb_device *, uint8_t, uint8_t);
+static void usb_cdev_create(struct usb_device *);
+static void usb_cdev_free(struct usb_device *);
+#endif
+
+/* This variable is global to allow easy access to it: */
+
+#ifdef USB_TEMPLATE
+int usb_template = USB_TEMPLATE;
+#else
+int usb_template = -1;
+#endif
+
+SYSCTL_PROC(_hw_usb, OID_AUTO, template,
+ CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
+ NULL, 0, sysctl_hw_usb_template,
+ "I", "Selected USB device side template");
+
+/*------------------------------------------------------------------------*
+ * usb_trigger_reprobe_on_off
+ *
+ * This function sets the pull up resistors for all ports currently
+ * operating in device mode either on (when on_not_off is 1), or off
+ * (when it's 0).
+ *------------------------------------------------------------------------*/
+static void
+usb_trigger_reprobe_on_off(int on_not_off)
+{
+ struct usb_port_status ps;
+ struct usb_bus *bus;
+ struct usb_device *udev;
+ usb_error_t err;
+ int do_unlock, max;
+
+ max = devclass_get_maxunit(usb_devclass_ptr);
+ while (max >= 0) {
+ mtx_lock(&usb_ref_lock);
+ bus = devclass_get_softc(usb_devclass_ptr, max);
+ max--;
+
+ if (bus == NULL || bus->devices == NULL ||
+ bus->devices[USB_ROOT_HUB_ADDR] == NULL) {
+ mtx_unlock(&usb_ref_lock);
+ continue;
+ }
+
+ udev = bus->devices[USB_ROOT_HUB_ADDR];
+
+ if (udev->refcount == USB_DEV_REF_MAX) {
+ mtx_unlock(&usb_ref_lock);
+ continue;
+ }
+
+ udev->refcount++;
+ mtx_unlock(&usb_ref_lock);
+
+ do_unlock = usbd_enum_lock(udev);
+ if (do_unlock > 1) {
+ do_unlock = 0;
+ goto next;
+ }
+
+ err = usbd_req_get_port_status(udev, NULL, &ps, 1);
+ if (err != 0) {
+ DPRINTF("usbd_req_get_port_status() "
+ "failed: %s\n", usbd_errstr(err));
+ goto next;
+ }
+
+ if ((UGETW(ps.wPortStatus) & UPS_PORT_MODE_DEVICE) == 0)
+ goto next;
+
+ if (on_not_off) {
+ err = usbd_req_set_port_feature(udev, NULL, 1,
+ UHF_PORT_POWER);
+ if (err != 0) {
+ DPRINTF("usbd_req_set_port_feature() "
+ "failed: %s\n", usbd_errstr(err));
+ }
+ } else {
+ err = usbd_req_clear_port_feature(udev, NULL, 1,
+ UHF_PORT_POWER);
+ if (err != 0) {
+ DPRINTF("usbd_req_clear_port_feature() "
+ "failed: %s\n", usbd_errstr(err));
+ }
+ }
+
+next:
+ mtx_lock(&usb_ref_lock);
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+ if (--(udev->refcount) == 0)
+ cv_broadcast(&udev->ref_cv);
+ mtx_unlock(&usb_ref_lock);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_trigger_reprobe_all
+ *
+ * This function toggles the pull up resistors for all ports currently
+ * operating in device mode, causing the host machine to reenumerate them.
+ *------------------------------------------------------------------------*/
+static void
+usb_trigger_reprobe_all(void)
+{
+
+ /*
+ * Set the pull up resistors off for all ports in device mode.
+ */
+ usb_trigger_reprobe_on_off(0);
+
+ /*
+ * According to the DWC OTG spec this must be at least 3ms.
+ */
+ usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_POWER_DOWN_TIME));
+
+ /*
+ * Set the pull up resistors back on.
+ */
+ usb_trigger_reprobe_on_off(1);
+}
+
+static int
+sysctl_hw_usb_template(SYSCTL_HANDLER_ARGS)
+{
+ int error, val;
+
+ val = usb_template;
+ error = sysctl_handle_int(oidp, &val, 0, req);
+ if (error != 0 || req->newptr == NULL || usb_template == val)
+ return (error);
+
+ usb_template = val;
+
+ if (usb_template < 0) {
+ usb_trigger_reprobe_on_off(0);
+ } else {
+ usb_trigger_reprobe_all();
+ }
+
+ return (0);
+}
+
+/* English is default language */
+
+static int usb_lang_id = 0x0009;
+static int usb_lang_mask = 0x00FF;
+
+SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_id, CTLFLAG_RWTUN,
+ &usb_lang_id, 0, "Preferred USB language ID");
+
+SYSCTL_INT(_hw_usb, OID_AUTO, usb_lang_mask, CTLFLAG_RWTUN,
+ &usb_lang_mask, 0, "Preferred USB language mask");
+
+static const char* statestr[USB_STATE_MAX] = {
+ [USB_STATE_DETACHED] = "DETACHED",
+ [USB_STATE_ATTACHED] = "ATTACHED",
+ [USB_STATE_POWERED] = "POWERED",
+ [USB_STATE_ADDRESSED] = "ADDRESSED",
+ [USB_STATE_CONFIGURED] = "CONFIGURED",
+};
+
+const char *
+usb_statestr(enum usb_dev_state state)
+{
+ return ((state < USB_STATE_MAX) ? statestr[state] : "UNKNOWN");
+}
+
+const char *
+usb_get_manufacturer(struct usb_device *udev)
+{
+ return (udev->manufacturer ? udev->manufacturer : "Unknown");
+}
+
+const char *
+usb_get_product(struct usb_device *udev)
+{
+ return (udev->product ? udev->product : "");
+}
+
+const char *
+usb_get_serial(struct usb_device *udev)
+{
+ return (udev->serial ? udev->serial : "");
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_get_ep_by_addr
+ *
+ * This function searches for an USB ep by endpoint address and
+ * direction.
+ *
+ * Returns:
+ * NULL: Failure
+ * Else: Success
+ *------------------------------------------------------------------------*/
+struct usb_endpoint *
+usbd_get_ep_by_addr(struct usb_device *udev, uint8_t ea_val)
+{
+ struct usb_endpoint *ep = udev->endpoints;
+ struct usb_endpoint *ep_end = udev->endpoints + udev->endpoints_max;
+ enum {
+ EA_MASK = (UE_DIR_IN | UE_DIR_OUT | UE_ADDR),
+ };
+
+ /*
+ * According to the USB specification not all bits are used
+ * for the endpoint address. Keep defined bits only:
+ */
+ ea_val &= EA_MASK;
+
+ /*
+ * Iterate across all the USB endpoints searching for a match
+ * based on the endpoint address:
+ */
+ for (; ep != ep_end; ep++) {
+ if (ep->edesc == NULL) {
+ continue;
+ }
+ /* do the mask and check the value */
+ if ((ep->edesc->bEndpointAddress & EA_MASK) == ea_val) {
+ goto found;
+ }
+ }
+
+ /*
+ * The default endpoint is always present and is checked separately:
+ */
+ if ((udev->ctrl_ep.edesc != NULL) &&
+ ((udev->ctrl_ep.edesc->bEndpointAddress & EA_MASK) == ea_val)) {
+ ep = &udev->ctrl_ep;
+ goto found;
+ }
+ return (NULL);
+
+found:
+ return (ep);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_get_endpoint
+ *
+ * This function searches for an USB endpoint based on the information
+ * given by the passed "struct usb_config" pointer.
+ *
+ * Return values:
+ * NULL: No match.
+ * Else: Pointer to "struct usb_endpoint".
+ *------------------------------------------------------------------------*/
+struct usb_endpoint *
+usbd_get_endpoint(struct usb_device *udev, uint8_t iface_index,
+ const struct usb_config *setup)
+{
+ struct usb_endpoint *ep = udev->endpoints;
+ struct usb_endpoint *ep_end = udev->endpoints + udev->endpoints_max;
+ uint8_t index = setup->ep_index;
+ uint8_t ea_mask;
+ uint8_t ea_val;
+ uint8_t type_mask;
+ uint8_t type_val;
+
+ DPRINTFN(10, "udev=%p iface_index=%d address=0x%x "
+ "type=0x%x dir=0x%x index=%d\n",
+ udev, iface_index, setup->endpoint,
+ setup->type, setup->direction, setup->ep_index);
+
+ /* check USB mode */
+
+ if (setup->usb_mode != USB_MODE_DUAL &&
+ udev->flags.usb_mode != setup->usb_mode) {
+ /* wrong mode - no endpoint */
+ return (NULL);
+ }
+
+ /* setup expected endpoint direction mask and value */
+
+ if (setup->direction == UE_DIR_RX) {
+ ea_mask = (UE_DIR_IN | UE_DIR_OUT);
+ ea_val = (udev->flags.usb_mode == USB_MODE_DEVICE) ?
+ UE_DIR_OUT : UE_DIR_IN;
+ } else if (setup->direction == UE_DIR_TX) {
+ ea_mask = (UE_DIR_IN | UE_DIR_OUT);
+ ea_val = (udev->flags.usb_mode == USB_MODE_DEVICE) ?
+ UE_DIR_IN : UE_DIR_OUT;
+ } else if (setup->direction == UE_DIR_ANY) {
+ /* match any endpoint direction */
+ ea_mask = 0;
+ ea_val = 0;
+ } else {
+ /* match the given endpoint direction */
+ ea_mask = (UE_DIR_IN | UE_DIR_OUT);
+ ea_val = (setup->direction & (UE_DIR_IN | UE_DIR_OUT));
+ }
+
+ /* setup expected endpoint address */
+
+ if (setup->endpoint == UE_ADDR_ANY) {
+ /* match any endpoint address */
+ } else {
+ /* match the given endpoint address */
+ ea_mask |= UE_ADDR;
+ ea_val |= (setup->endpoint & UE_ADDR);
+ }
+
+ /* setup expected endpoint type */
+
+ if (setup->type == UE_BULK_INTR) {
+ /* this will match BULK and INTERRUPT endpoints */
+ type_mask = 2;
+ type_val = 2;
+ } else if (setup->type == UE_TYPE_ANY) {
+ /* match any endpoint type */
+ type_mask = 0;
+ type_val = 0;
+ } else {
+ /* match the given endpoint type */
+ type_mask = UE_XFERTYPE;
+ type_val = (setup->type & UE_XFERTYPE);
+ }
+
+ /*
+ * Iterate across all the USB endpoints searching for a match
+ * based on the endpoint address. Note that we are searching
+ * the endpoints from the beginning of the "udev->endpoints" array.
+ */
+ for (; ep != ep_end; ep++) {
+ if ((ep->edesc == NULL) ||
+ (ep->iface_index != iface_index)) {
+ continue;
+ }
+ /* do the masks and check the values */
+
+ if (((ep->edesc->bEndpointAddress & ea_mask) == ea_val) &&
+ ((ep->edesc->bmAttributes & type_mask) == type_val)) {
+ if (!index--) {
+ goto found;
+ }
+ }
+ }
+
+ /*
+ * Match against default endpoint last, so that "any endpoint", "any
+ * address" and "any direction" returns the first endpoint of the
+ * interface. "iface_index" and "direction" is ignored:
+ */
+ if ((udev->ctrl_ep.edesc != NULL) &&
+ ((udev->ctrl_ep.edesc->bEndpointAddress & ea_mask) == ea_val) &&
+ ((udev->ctrl_ep.edesc->bmAttributes & type_mask) == type_val) &&
+ (!index)) {
+ ep = &udev->ctrl_ep;
+ goto found;
+ }
+ return (NULL);
+
+found:
+ return (ep);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_interface_count
+ *
+ * This function stores the number of USB interfaces excluding
+ * alternate settings, which the USB config descriptor reports into
+ * the unsigned 8-bit integer pointed to by "count".
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_interface_count(struct usb_device *udev, uint8_t *count)
+{
+ if (udev->cdesc == NULL) {
+ *count = 0;
+ return (USB_ERR_NOT_CONFIGURED);
+ }
+ *count = udev->ifaces_max;
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_init_endpoint
+ *
+ * This function will initialise the USB endpoint structure pointed to by
+ * the "endpoint" argument. The structure pointed to by "endpoint" must be
+ * zeroed before calling this function.
+ *------------------------------------------------------------------------*/
+static void
+usb_init_endpoint(struct usb_device *udev, uint8_t iface_index,
+ struct usb_endpoint_descriptor *edesc,
+ struct usb_endpoint_ss_comp_descriptor *ecomp,
+ struct usb_endpoint *ep)
+{
+ const struct usb_bus_methods *methods;
+ usb_stream_t x;
+
+ methods = udev->bus->methods;
+
+ (methods->endpoint_init) (udev, edesc, ep);
+
+ /* initialise USB endpoint structure */
+ ep->edesc = edesc;
+ ep->ecomp = ecomp;
+ ep->iface_index = iface_index;
+
+ /* setup USB stream queues */
+ for (x = 0; x != USB_MAX_EP_STREAMS; x++) {
+ TAILQ_INIT(&ep->endpoint_q[x].head);
+ ep->endpoint_q[x].command = &usbd_pipe_start;
+ }
+
+ /* the pipe is not supported by the hardware */
+ if (ep->methods == NULL)
+ return;
+
+ /* check for SUPER-speed streams mode endpoint */
+ if (udev->speed == USB_SPEED_SUPER && ecomp != NULL &&
+ (edesc->bmAttributes & UE_XFERTYPE) == UE_BULK &&
+ (UE_GET_BULK_STREAMS(ecomp->bmAttributes) != 0)) {
+ usbd_set_endpoint_mode(udev, ep, USB_EP_MODE_STREAMS);
+ } else {
+ usbd_set_endpoint_mode(udev, ep, USB_EP_MODE_DEFAULT);
+ }
+
+ /* clear stall, if any */
+ if (methods->clear_stall != NULL) {
+ USB_BUS_LOCK(udev->bus);
+ (methods->clear_stall) (udev, ep);
+ USB_BUS_UNLOCK(udev->bus);
+ }
+}
+
+/*-----------------------------------------------------------------------*
+ * usb_endpoint_foreach
+ *
+ * This function will iterate all the USB endpoints except the control
+ * endpoint. This function is NULL safe.
+ *
+ * Return values:
+ * NULL: End of USB endpoints
+ * Else: Pointer to next USB endpoint
+ *------------------------------------------------------------------------*/
+struct usb_endpoint *
+usb_endpoint_foreach(struct usb_device *udev, struct usb_endpoint *ep)
+{
+ struct usb_endpoint *ep_end;
+
+ /* be NULL safe */
+ if (udev == NULL)
+ return (NULL);
+
+ ep_end = udev->endpoints + udev->endpoints_max;
+
+ /* get next endpoint */
+ if (ep == NULL)
+ ep = udev->endpoints;
+ else
+ ep++;
+
+ /* find next allocated ep */
+ while (ep != ep_end) {
+ if (ep->edesc != NULL)
+ return (ep);
+ ep++;
+ }
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_wait_pending_refs
+ *
+ * This function will wait for any USB references to go away before
+ * returning. This function is used before freeing a USB device.
+ *------------------------------------------------------------------------*/
+static void
+usb_wait_pending_refs(struct usb_device *udev)
+{
+#if USB_HAVE_UGEN
+ DPRINTF("Refcount = %d\n", (int)udev->refcount);
+
+ mtx_lock(&usb_ref_lock);
+ udev->refcount--;
+ while (1) {
+ /* wait for any pending references to go away */
+ if (udev->refcount == 0) {
+ /* prevent further refs being taken, if any */
+ udev->refcount = USB_DEV_REF_MAX;
+ break;
+ }
+ cv_wait(&udev->ref_cv, &usb_ref_lock);
+ }
+ mtx_unlock(&usb_ref_lock);
+#endif
+}
+
+/*------------------------------------------------------------------------*
+ * usb_unconfigure
+ *
+ * This function will free all USB interfaces and USB endpoints belonging
+ * to an USB device.
+ *
+ * Flag values, see "USB_UNCFG_FLAG_XXX".
+ *------------------------------------------------------------------------*/
+static void
+usb_unconfigure(struct usb_device *udev, uint8_t flag)
+{
+ uint8_t do_unlock;
+
+ /* Prevent re-enumeration */
+ do_unlock = usbd_enum_lock(udev);
+
+ /* detach all interface drivers */
+ usb_detach_device(udev, USB_IFACE_INDEX_ANY, flag);
+
+#if USB_HAVE_UGEN
+ /* free all FIFOs except control endpoint FIFOs */
+ usb_fifo_free_wrap(udev, USB_IFACE_INDEX_ANY, flag);
+
+ /*
+ * Free all cdev's, if any.
+ */
+ usb_cdev_free(udev);
+#endif
+
+#if USB_HAVE_COMPAT_LINUX
+ /* free Linux compat device, if any */
+ if (udev->linux_endpoint_start != NULL) {
+ usb_linux_free_device_p(udev);
+ udev->linux_endpoint_start = NULL;
+ }
+#endif
+
+ usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_FREE);
+
+ /* free "cdesc" after "ifaces" and "endpoints", if any */
+ if (udev->cdesc != NULL) {
+ if (udev->flags.usb_mode != USB_MODE_DEVICE)
+ usbd_free_config_desc(udev, udev->cdesc);
+ udev->cdesc = NULL;
+ }
+ /* set unconfigured state */
+ udev->curr_config_no = USB_UNCONFIG_NO;
+ udev->curr_config_index = USB_UNCONFIG_INDEX;
+
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_set_config_index
+ *
+ * This function selects configuration by index, independent of the
+ * actual configuration number. This function should not be used by
+ * USB drivers.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_set_config_index(struct usb_device *udev, uint8_t index)
+{
+ struct usb_status ds;
+ struct usb_config_descriptor *cdp;
+ uint16_t power;
+ uint16_t max_power;
+ uint8_t selfpowered;
+ uint8_t do_unlock;
+ usb_error_t err;
+
+ DPRINTFN(6, "udev=%p index=%d\n", udev, index);
+
+ /* Prevent re-enumeration */
+ do_unlock = usbd_enum_lock(udev);
+
+ usb_unconfigure(udev, 0);
+
+ if (index == USB_UNCONFIG_INDEX) {
+ /*
+ * Leave unallocated when unconfiguring the
+ * device. "usb_unconfigure()" will also reset
+ * the current config number and index.
+ */
+ err = usbd_req_set_config(udev, NULL, USB_UNCONFIG_NO);
+ if (udev->state == USB_STATE_CONFIGURED)
+ usb_set_device_state(udev, USB_STATE_ADDRESSED);
+ goto done;
+ }
+ /* get the full config descriptor */
+ if (udev->flags.usb_mode == USB_MODE_DEVICE) {
+ /* save some memory */
+ err = usbd_req_get_descriptor_ptr(udev, &cdp,
+ (UDESC_CONFIG << 8) | index);
+ } else {
+ /* normal request */
+ err = usbd_req_get_config_desc_full(udev,
+ NULL, &cdp, index);
+ }
+ if (err) {
+ goto done;
+ }
+ /* set the new config descriptor */
+
+ udev->cdesc = cdp;
+
+ /* Figure out if the device is self or bus powered. */
+ selfpowered = 0;
+ if ((!udev->flags.uq_bus_powered) &&
+ (cdp->bmAttributes & UC_SELF_POWERED) &&
+ (udev->flags.usb_mode == USB_MODE_HOST)) {
+ /* May be self powered. */
+ if (cdp->bmAttributes & UC_BUS_POWERED) {
+ /* Must ask device. */
+ err = usbd_req_get_device_status(udev, NULL, &ds);
+ if (err) {
+ DPRINTFN(0, "could not read "
+ "device status: %s\n",
+ usbd_errstr(err));
+ } else if (UGETW(ds.wStatus) & UDS_SELF_POWERED) {
+ selfpowered = 1;
+ }
+ DPRINTF("status=0x%04x \n",
+ UGETW(ds.wStatus));
+ } else
+ selfpowered = 1;
+ }
+ DPRINTF("udev=%p cdesc=%p (addr %d) cno=%d attr=0x%02x, "
+ "selfpowered=%d, power=%d\n",
+ udev, cdp,
+ udev->address, cdp->bConfigurationValue, cdp->bmAttributes,
+ selfpowered, cdp->bMaxPower * 2);
+
+ /* Check if we have enough power. */
+ power = cdp->bMaxPower * 2;
+
+ if (udev->parent_hub) {
+ max_power = udev->parent_hub->hub->portpower;
+ } else {
+ max_power = USB_MAX_POWER;
+ }
+
+ if (power > max_power) {
+ DPRINTFN(0, "power exceeded %d > %d\n", power, max_power);
+ err = USB_ERR_NO_POWER;
+ goto done;
+ }
+ /* Only update "self_powered" in USB Host Mode */
+ if (udev->flags.usb_mode == USB_MODE_HOST) {
+ udev->flags.self_powered = selfpowered;
+ }
+ udev->power = power;
+ udev->curr_config_no = cdp->bConfigurationValue;
+ udev->curr_config_index = index;
+ usb_set_device_state(udev, USB_STATE_CONFIGURED);
+
+ /* Set the actual configuration value. */
+ err = usbd_req_set_config(udev, NULL, cdp->bConfigurationValue);
+ if (err) {
+ goto done;
+ }
+
+ err = usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_ALLOC);
+ if (err) {
+ goto done;
+ }
+
+ err = usb_config_parse(udev, USB_IFACE_INDEX_ANY, USB_CFG_INIT);
+ if (err) {
+ goto done;
+ }
+
+#if USB_HAVE_UGEN
+ /* create device nodes for each endpoint */
+ usb_cdev_create(udev);
+#endif
+
+done:
+ DPRINTF("error=%s\n", usbd_errstr(err));
+ if (err) {
+ usb_unconfigure(udev, 0);
+ }
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_config_parse
+ *
+ * This function will allocate and free USB interfaces and USB endpoints,
+ * parse the USB configuration structure and initialise the USB endpoints
+ * and interfaces. If "iface_index" is not equal to
+ * "USB_IFACE_INDEX_ANY" then the "cmd" parameter is the
+ * alternate_setting to be selected for the given interface. Else the
+ * "cmd" parameter is defined by "USB_CFG_XXX". "iface_index" can be
+ * "USB_IFACE_INDEX_ANY" or a valid USB interface index. This function
+ * is typically called when setting the configuration or when setting
+ * an alternate interface.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static usb_error_t
+usb_config_parse(struct usb_device *udev, uint8_t iface_index, uint8_t cmd)
+{
+ struct usb_idesc_parse_state ips;
+ struct usb_interface_descriptor *id;
+ struct usb_endpoint_descriptor *ed;
+ struct usb_interface *iface;
+ struct usb_endpoint *ep;
+ usb_error_t err;
+ uint8_t ep_curr;
+ uint8_t ep_max;
+ uint8_t temp;
+ uint8_t do_init;
+ uint8_t alt_index;
+
+ if (iface_index != USB_IFACE_INDEX_ANY) {
+ /* parameter overload */
+ alt_index = cmd;
+ cmd = USB_CFG_INIT;
+ } else {
+ /* not used */
+ alt_index = 0;
+ }
+
+ err = 0;
+
+ DPRINTFN(5, "iface_index=%d cmd=%d\n",
+ iface_index, cmd);
+
+ if (cmd == USB_CFG_INIT || cmd == USB_CFG_FREE) {
+ sx_assert(&udev->enum_sx, SA_LOCKED);
+
+ /* check for in-use endpoints */
+
+ if (cmd == USB_CFG_INIT) {
+ ep = udev->endpoints;
+ ep_max = udev->endpoints_max;
+ while (ep_max--) {
+ /* look for matching endpoints */
+ if (iface_index == USB_IFACE_INDEX_ANY ||
+ iface_index == ep->iface_index) {
+ if (ep->refcount_alloc != 0)
+ return (USB_ERR_IN_USE);
+ }
+ ep++;
+ }
+ }
+
+ ep = udev->endpoints;
+ ep_max = udev->endpoints_max;
+ while (ep_max--) {
+ /* look for matching endpoints */
+ if (iface_index == USB_IFACE_INDEX_ANY ||
+ iface_index == ep->iface_index) {
+ /*
+ * Check if hardware needs a callback
+ * to unconfigure the endpoint. This
+ * may happen multiple times,
+ * because the requested alternate
+ * setting may fail. The callback
+ * implementation should be aware of
+ * and handle that.
+ */
+ if (ep->edesc != NULL &&
+ udev->bus->methods->endpoint_uninit != NULL)
+ udev->bus->methods->endpoint_uninit(udev, ep);
+
+ /* reset endpoint */
+ memset(ep, 0, sizeof(*ep));
+ /* make sure we don't zero the endpoint again */
+ ep->iface_index = USB_IFACE_INDEX_ANY;
+ }
+ ep++;
+ }
+
+ if (cmd == USB_CFG_FREE)
+ goto cleanup;
+ }
+
+ memset(&ips, 0, sizeof(ips));
+
+ ep_curr = 0;
+ ep_max = 0;
+
+ while ((id = usb_idesc_foreach(udev->cdesc, &ips))) {
+ iface = udev->ifaces + ips.iface_index;
+
+ /* check for specific interface match */
+
+ if (cmd == USB_CFG_INIT) {
+ if ((iface_index != USB_IFACE_INDEX_ANY) &&
+ (iface_index != ips.iface_index)) {
+ /* wrong interface */
+ do_init = 0;
+ } else if (alt_index != ips.iface_index_alt) {
+ /* wrong alternate setting */
+ do_init = 0;
+ } else {
+ /* initialise interface */
+ do_init = 1;
+ }
+ /* update number of alternate settings, if any */
+ if (iface_index == USB_IFACE_INDEX_ANY)
+ iface->num_altsetting = ips.iface_index_alt + 1;
+ } else
+ do_init = 0;
+
+ /* check for new interface */
+ if (ips.iface_index_alt == 0) {
+ /* update current number of endpoints */
+ ep_curr = ep_max;
+ }
+
+ /* check for init */
+ if (do_init) {
+ /* setup the USB interface structure */
+ iface->idesc = id;
+ /* set alternate index */
+ iface->alt_index = alt_index;
+ /* set default interface parent */
+ if (iface_index == USB_IFACE_INDEX_ANY) {
+ iface->parent_iface_index =
+ USB_IFACE_INDEX_ANY;
+ }
+ }
+
+ DPRINTFN(5, "found idesc nendpt=%d\n", id->bNumEndpoints);
+
+ ed = (struct usb_endpoint_descriptor *)id;
+
+ temp = ep_curr;
+
+ /* iterate all the endpoint descriptors */
+ while ((ed = usb_edesc_foreach(udev->cdesc, ed))) {
+ /* check if endpoint limit has been reached */
+ if (temp >= USB_MAX_EP_UNITS) {
+ DPRINTF("Endpoint limit reached\n");
+ break;
+ }
+
+ ep = udev->endpoints + temp;
+
+ if (do_init) {
+ void *ecomp;
+
+ ecomp = usb_ed_comp_foreach(udev->cdesc, (void *)ed);
+ if (ecomp != NULL)
+ DPRINTFN(5, "Found endpoint companion descriptor\n");
+
+ usb_init_endpoint(udev,
+ ips.iface_index, ed, ecomp, ep);
+ }
+
+ temp ++;
+
+ /* find maximum number of endpoints */
+ if (ep_max < temp)
+ ep_max = temp;
+ }
+ }
+
+ /* NOTE: It is valid to have no interfaces and no endpoints! */
+
+ if (cmd == USB_CFG_ALLOC) {
+ udev->ifaces_max = ips.iface_index;
+#if (USB_HAVE_FIXED_IFACE == 0)
+ udev->ifaces = NULL;
+ if (udev->ifaces_max != 0) {
+ udev->ifaces = malloc(sizeof(*iface) * udev->ifaces_max,
+ M_USB, M_WAITOK | M_ZERO);
+ if (udev->ifaces == NULL) {
+ err = USB_ERR_NOMEM;
+ goto done;
+ }
+ }
+#endif
+#if (USB_HAVE_FIXED_ENDPOINT == 0)
+ if (ep_max != 0) {
+ udev->endpoints = malloc(sizeof(*ep) * ep_max,
+ M_USB, M_WAITOK | M_ZERO);
+ if (udev->endpoints == NULL) {
+ err = USB_ERR_NOMEM;
+ goto done;
+ }
+ } else {
+ udev->endpoints = NULL;
+ }
+#endif
+ USB_BUS_LOCK(udev->bus);
+ udev->endpoints_max = ep_max;
+ /* reset any ongoing clear-stall */
+ udev->ep_curr = NULL;
+ USB_BUS_UNLOCK(udev->bus);
+ }
+#if (USB_HAVE_FIXED_IFACE == 0) || (USB_HAVE_FIXED_ENDPOINT == 0)
+done:
+#endif
+ if (err) {
+ if (cmd == USB_CFG_ALLOC) {
+cleanup:
+ USB_BUS_LOCK(udev->bus);
+ udev->endpoints_max = 0;
+ /* reset any ongoing clear-stall */
+ udev->ep_curr = NULL;
+ USB_BUS_UNLOCK(udev->bus);
+
+#if (USB_HAVE_FIXED_IFACE == 0)
+ free(udev->ifaces, M_USB);
+ udev->ifaces = NULL;
+#endif
+#if (USB_HAVE_FIXED_ENDPOINT == 0)
+ free(udev->endpoints, M_USB);
+ udev->endpoints = NULL;
+#endif
+ udev->ifaces_max = 0;
+ }
+ }
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_set_alt_interface_index
+ *
+ * This function will select an alternate interface index for the
+ * given interface index. The interface should not be in use when this
+ * function is called. That means there should not be any open USB
+ * transfers. Else an error is returned. If the alternate setting is
+ * already set this function will simply return success. This function
+ * is called in Host mode and Device mode!
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_set_alt_interface_index(struct usb_device *udev,
+ uint8_t iface_index, uint8_t alt_index)
+{
+ struct usb_interface *iface = usbd_get_iface(udev, iface_index);
+ usb_error_t err;
+ uint8_t do_unlock;
+
+ /* Prevent re-enumeration */
+ do_unlock = usbd_enum_lock(udev);
+
+ if (iface == NULL) {
+ err = USB_ERR_INVAL;
+ goto done;
+ }
+ if (iface->alt_index == alt_index) {
+ /*
+ * Optimise away duplicate setting of
+ * alternate setting in USB Host Mode!
+ */
+ err = 0;
+ goto done;
+ }
+#if USB_HAVE_UGEN
+ /*
+ * Free all generic FIFOs for this interface, except control
+ * endpoint FIFOs:
+ */
+ usb_fifo_free_wrap(udev, iface_index, 0);
+#endif
+
+ err = usb_config_parse(udev, iface_index, alt_index);
+ if (err) {
+ goto done;
+ }
+ if (iface->alt_index != alt_index) {
+ /* the alternate setting does not exist */
+ err = USB_ERR_INVAL;
+ goto done;
+ }
+
+ err = usbd_req_set_alt_interface_no(udev, NULL, iface_index,
+ iface->idesc->bAlternateSetting);
+
+done:
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_set_endpoint_stall
+ *
+ * This function is used to make a BULK or INTERRUPT endpoint send
+ * STALL tokens in USB device mode.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_set_endpoint_stall(struct usb_device *udev, struct usb_endpoint *ep,
+ uint8_t do_stall)
+{
+ struct usb_xfer *xfer;
+ usb_stream_t x;
+ uint8_t et;
+ uint8_t was_stalled;
+
+ if (ep == NULL) {
+ /* nothing to do */
+ DPRINTF("Cannot find endpoint\n");
+ /*
+ * Pretend that the clear or set stall request is
+ * successful else some USB host stacks can do
+ * strange things, especially when a control endpoint
+ * stalls.
+ */
+ return (0);
+ }
+ et = (ep->edesc->bmAttributes & UE_XFERTYPE);
+
+ if ((et != UE_BULK) &&
+ (et != UE_INTERRUPT)) {
+ /*
+ * Should not stall control
+ * nor isochronous endpoints.
+ */
+ DPRINTF("Invalid endpoint\n");
+ return (0);
+ }
+ USB_BUS_LOCK(udev->bus);
+
+ /* store current stall state */
+ was_stalled = ep->is_stalled;
+
+ /* check for no change */
+ if (was_stalled && do_stall) {
+ /* if the endpoint is already stalled do nothing */
+ USB_BUS_UNLOCK(udev->bus);
+ DPRINTF("No change\n");
+ return (0);
+ }
+ /* set stalled state */
+ ep->is_stalled = 1;
+
+ if (do_stall || (!was_stalled)) {
+ if (!was_stalled) {
+ for (x = 0; x != USB_MAX_EP_STREAMS; x++) {
+ /* lookup the current USB transfer, if any */
+ xfer = ep->endpoint_q[x].curr;
+ if (xfer != NULL) {
+ /*
+ * The "xfer_stall" method
+ * will complete the USB
+ * transfer like in case of a
+ * timeout setting the error
+ * code "USB_ERR_STALLED".
+ */
+ (udev->bus->methods->xfer_stall) (xfer);
+ }
+ }
+ }
+ (udev->bus->methods->set_stall) (udev, ep, &do_stall);
+ }
+ if (!do_stall) {
+ ep->toggle_next = 0; /* reset data toggle */
+ ep->is_stalled = 0; /* clear stalled state */
+
+ (udev->bus->methods->clear_stall) (udev, ep);
+
+ /* start the current or next transfer, if any */
+ for (x = 0; x != USB_MAX_EP_STREAMS; x++) {
+ usb_command_wrapper(&ep->endpoint_q[x],
+ ep->endpoint_q[x].curr);
+ }
+ }
+ USB_BUS_UNLOCK(udev->bus);
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_reset_iface_endpoints - used in USB device side mode
+ *------------------------------------------------------------------------*/
+usb_error_t
+usb_reset_iface_endpoints(struct usb_device *udev, uint8_t iface_index)
+{
+ struct usb_endpoint *ep;
+ struct usb_endpoint *ep_end;
+
+ ep = udev->endpoints;
+ ep_end = udev->endpoints + udev->endpoints_max;
+
+ for (; ep != ep_end; ep++) {
+ if ((ep->edesc == NULL) ||
+ (ep->iface_index != iface_index)) {
+ continue;
+ }
+ /* simulate a clear stall from the peer */
+ usbd_set_endpoint_stall(udev, ep, 0);
+ }
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_detach_device_sub
+ *
+ * This function will try to detach an USB device. If it fails a panic
+ * will result.
+ *
+ * Flag values, see "USB_UNCFG_FLAG_XXX".
+ *------------------------------------------------------------------------*/
+static void
+usb_detach_device_sub(struct usb_device *udev, device_t *ppdev,
+ char **ppnpinfo, uint8_t flag)
+{
+ device_t dev;
+ char *pnpinfo;
+ int err;
+
+ dev = *ppdev;
+ if (dev) {
+ /*
+ * NOTE: It is important to clear "*ppdev" before deleting
+ * the child due to some device methods being called late
+ * during the delete process !
+ */
+ *ppdev = NULL;
+
+ if (!rebooting) {
+ device_printf(dev, "at %s, port %d, addr %d "
+ "(disconnected)\n",
+ device_get_nameunit(udev->parent_dev),
+ udev->port_no, udev->address);
+ }
+
+ if (device_is_attached(dev)) {
+ if (udev->flags.peer_suspended) {
+ err = DEVICE_RESUME(dev);
+ if (err) {
+ device_printf(dev, "Resume failed\n");
+ }
+ }
+ }
+ /* detach and delete child */
+ if (device_delete_child(udev->parent_dev, dev)) {
+ goto error;
+ }
+ }
+
+ pnpinfo = *ppnpinfo;
+ if (pnpinfo != NULL) {
+ *ppnpinfo = NULL;
+ free(pnpinfo, M_USBDEV);
+ }
+ return;
+
+error:
+ /* Detach is not allowed to fail in the USB world */
+ panic("usb_detach_device_sub: A USB driver would not detach\n");
+}
+
+/*------------------------------------------------------------------------*
+ * usb_detach_device
+ *
+ * The following function will detach the matching interfaces.
+ * This function is NULL safe.
+ *
+ * Flag values, see "USB_UNCFG_FLAG_XXX".
+ *------------------------------------------------------------------------*/
+void
+usb_detach_device(struct usb_device *udev, uint8_t iface_index,
+ uint8_t flag)
+{
+ struct usb_interface *iface;
+ uint8_t i;
+
+ if (udev == NULL) {
+ /* nothing to do */
+ return;
+ }
+ DPRINTFN(4, "udev=%p\n", udev);
+
+ sx_assert(&udev->enum_sx, SA_LOCKED);
+
+ /*
+ * First detach the child to give the child's detach routine a
+ * chance to detach the sub-devices in the correct order.
+ * Then delete the child using "device_delete_child()" which
+ * will detach all sub-devices from the bottom and upwards!
+ */
+ if (iface_index != USB_IFACE_INDEX_ANY) {
+ i = iface_index;
+ iface_index = i + 1;
+ } else {
+ i = 0;
+ iface_index = USB_IFACE_MAX;
+ }
+
+ /* do the detach */
+
+ for (; i != iface_index; i++) {
+ iface = usbd_get_iface(udev, i);
+ if (iface == NULL) {
+ /* looks like the end of the USB interfaces */
+ break;
+ }
+ usb_detach_device_sub(udev, &iface->subdev,
+ &iface->pnpinfo, flag);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_probe_and_attach_sub
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+usb_probe_and_attach_sub(struct usb_device *udev,
+ struct usb_attach_arg *uaa)
+{
+ struct usb_interface *iface;
+ device_t dev;
+ int err;
+
+ iface = uaa->iface;
+ if (iface->parent_iface_index != USB_IFACE_INDEX_ANY) {
+ /* leave interface alone */
+ return (0);
+ }
+ dev = iface->subdev;
+ if (dev) {
+ /* clean up after module unload */
+
+ if (device_is_attached(dev)) {
+ /* already a device there */
+ return (0);
+ }
+ /* clear "iface->subdev" as early as possible */
+
+ iface->subdev = NULL;
+
+ if (device_delete_child(udev->parent_dev, dev)) {
+ /*
+ * Panic here, else one can get a double call
+ * to device_detach(). USB devices should
+ * never fail on detach!
+ */
+ panic("device_delete_child() failed\n");
+ }
+ }
+ if (uaa->temp_dev == NULL) {
+ /* create a new child */
+ uaa->temp_dev = device_add_child(udev->parent_dev, NULL, DEVICE_UNIT_ANY);
+ if (uaa->temp_dev == NULL) {
+ device_printf(udev->parent_dev,
+ "Device creation failed\n");
+ return (1); /* failure */
+ }
+ device_set_ivars(uaa->temp_dev, uaa);
+ device_quiet(uaa->temp_dev);
+ }
+ /*
+ * Set "subdev" before probe and attach so that "devd" gets
+ * the information it needs.
+ */
+ iface->subdev = uaa->temp_dev;
+
+ if (device_probe_and_attach(iface->subdev) == 0) {
+ /*
+ * The USB attach arguments are only available during probe
+ * and attach !
+ */
+ uaa->temp_dev = NULL;
+ device_set_ivars(iface->subdev, NULL);
+
+ if (udev->flags.peer_suspended) {
+ err = DEVICE_SUSPEND(iface->subdev);
+ if (err)
+ device_printf(iface->subdev, "Suspend failed\n");
+ }
+ return (0); /* success */
+ } else {
+ /* No USB driver found */
+ iface->subdev = NULL;
+ }
+ return (1); /* failure */
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_set_parent_iface
+ *
+ * Using this function will lock the alternate interface setting on an
+ * interface. It is typically used for multi interface drivers. In USB
+ * device side mode it is assumed that the alternate interfaces all
+ * have the same endpoint descriptors. The default parent index value
+ * is "USB_IFACE_INDEX_ANY". Then the alternate setting value is not
+ * locked.
+ *------------------------------------------------------------------------*/
+void
+usbd_set_parent_iface(struct usb_device *udev, uint8_t iface_index,
+ uint8_t parent_index)
+{
+ struct usb_interface *iface;
+
+ if (udev == NULL || iface_index == parent_index) {
+ /* nothing to do */
+ return;
+ }
+ iface = usbd_get_iface(udev, iface_index);
+ if (iface != NULL)
+ iface->parent_iface_index = parent_index;
+}
+
+static void
+usb_init_attach_arg(struct usb_device *udev,
+ struct usb_attach_arg *uaa)
+{
+ memset(uaa, 0, sizeof(*uaa));
+
+ uaa->device = udev;
+ uaa->usb_mode = udev->flags.usb_mode;
+ uaa->port = udev->port_no;
+ uaa->dev_state = UAA_DEV_READY;
+
+ uaa->info.idVendor = UGETW(udev->ddesc.idVendor);
+ uaa->info.idProduct = UGETW(udev->ddesc.idProduct);
+ uaa->info.bcdDevice = UGETW(udev->ddesc.bcdDevice);
+ uaa->info.bDeviceClass = udev->ddesc.bDeviceClass;
+ uaa->info.bDeviceSubClass = udev->ddesc.bDeviceSubClass;
+ uaa->info.bDeviceProtocol = udev->ddesc.bDeviceProtocol;
+ uaa->info.bConfigIndex = udev->curr_config_index;
+ uaa->info.bConfigNum = udev->curr_config_no;
+}
+
+/*------------------------------------------------------------------------*
+ * usb_probe_and_attach
+ *
+ * This function is called from "uhub_explore_sub()",
+ * "usb_handle_set_config()" and "usb_handle_request()".
+ *
+ * Returns:
+ * 0: Success
+ * Else: A control transfer failed
+ *------------------------------------------------------------------------*/
+usb_error_t
+usb_probe_and_attach(struct usb_device *udev, uint8_t iface_index)
+{
+ struct usb_attach_arg uaa;
+ struct usb_interface *iface;
+ uint8_t i;
+ uint8_t j;
+ uint8_t do_unlock;
+
+ if (udev == NULL) {
+ DPRINTF("udev == NULL\n");
+ return (USB_ERR_INVAL);
+ }
+ /* Prevent re-enumeration */
+ do_unlock = usbd_enum_lock(udev);
+
+ if (udev->curr_config_index == USB_UNCONFIG_INDEX) {
+ /* do nothing - no configuration has been set */
+ goto done;
+ }
+ /* setup USB attach arguments */
+
+ usb_init_attach_arg(udev, &uaa);
+
+ /*
+ * If the whole USB device is targeted, invoke the USB event
+ * handler(s):
+ */
+ if (iface_index == USB_IFACE_INDEX_ANY) {
+ if (usb_test_quirk(&uaa, UQ_MSC_DYMO_EJECT) != 0 &&
+ usb_dymo_eject(udev, 0) == 0) {
+ /* success, mark the udev as disappearing */
+ uaa.dev_state = UAA_DEV_EJECTING;
+ }
+
+ EVENTHANDLER_INVOKE(usb_dev_configured, udev, &uaa);
+
+ if (uaa.dev_state != UAA_DEV_READY) {
+ /* leave device unconfigured */
+ usb_unconfigure(udev, 0);
+ goto done;
+ }
+ }
+
+ /* Check if only one interface should be probed: */
+ if (iface_index != USB_IFACE_INDEX_ANY) {
+ i = iface_index;
+ j = i + 1;
+ } else {
+ i = 0;
+ j = USB_IFACE_MAX;
+ }
+
+ /* Do the probe and attach */
+ for (; i != j; i++) {
+ iface = usbd_get_iface(udev, i);
+ if (iface == NULL) {
+ /*
+ * Looks like the end of the USB
+ * interfaces !
+ */
+ DPRINTFN(2, "end of interfaces "
+ "at %u\n", i);
+ break;
+ }
+ if (iface->idesc == NULL) {
+ /* no interface descriptor */
+ continue;
+ }
+ uaa.iface = iface;
+
+ uaa.info.bInterfaceClass =
+ iface->idesc->bInterfaceClass;
+ uaa.info.bInterfaceSubClass =
+ iface->idesc->bInterfaceSubClass;
+ uaa.info.bInterfaceProtocol =
+ iface->idesc->bInterfaceProtocol;
+ uaa.info.bIfaceIndex = i;
+ uaa.info.bIfaceNum =
+ iface->idesc->bInterfaceNumber;
+ uaa.driver_info = 0; /* reset driver_info */
+
+ DPRINTFN(2, "iclass=%u/%u/%u iindex=%u/%u\n",
+ uaa.info.bInterfaceClass,
+ uaa.info.bInterfaceSubClass,
+ uaa.info.bInterfaceProtocol,
+ uaa.info.bIfaceIndex,
+ uaa.info.bIfaceNum);
+
+ usb_probe_and_attach_sub(udev, &uaa);
+
+ /*
+ * Remove the leftover child, if any, to enforce that
+ * a new nomatch devd event is generated for the next
+ * interface if no driver is found:
+ */
+ if (uaa.temp_dev == NULL)
+ continue;
+ if (device_delete_child(udev->parent_dev, uaa.temp_dev))
+ DPRINTFN(0, "device delete child failed\n");
+ uaa.temp_dev = NULL;
+ }
+done:
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_suspend_resume_sub
+ *
+ * This function is called when the suspend or resume methods should
+ * be executed on an USB device.
+ *------------------------------------------------------------------------*/
+static void
+usb_suspend_resume_sub(struct usb_device *udev, device_t dev, uint8_t do_suspend)
+{
+ int err;
+
+ if (dev == NULL) {
+ return;
+ }
+ if (!device_is_attached(dev)) {
+ return;
+ }
+ if (do_suspend) {
+ err = DEVICE_SUSPEND(dev);
+ } else {
+ err = DEVICE_RESUME(dev);
+ }
+ if (err) {
+ device_printf(dev, "%s failed\n",
+ do_suspend ? "Suspend" : "Resume");
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_suspend_resume
+ *
+ * The following function will suspend or resume the USB device.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usb_suspend_resume(struct usb_device *udev, uint8_t do_suspend)
+{
+ struct usb_interface *iface;
+ uint8_t i;
+
+ if (udev == NULL) {
+ /* nothing to do */
+ return (0);
+ }
+ DPRINTFN(4, "udev=%p do_suspend=%d\n", udev, do_suspend);
+
+ sx_assert(&udev->sr_sx, SA_LOCKED);
+
+ USB_BUS_LOCK(udev->bus);
+ /* filter the suspend events */
+ if (udev->flags.peer_suspended == do_suspend) {
+ USB_BUS_UNLOCK(udev->bus);
+ /* nothing to do */
+ return (0);
+ }
+ udev->flags.peer_suspended = do_suspend;
+ USB_BUS_UNLOCK(udev->bus);
+
+ /* do the suspend or resume */
+
+ for (i = 0; i != USB_IFACE_MAX; i++) {
+ iface = usbd_get_iface(udev, i);
+ if (iface == NULL) {
+ /* looks like the end of the USB interfaces */
+ break;
+ }
+ usb_suspend_resume_sub(udev, iface->subdev, do_suspend);
+ }
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_clear_stall_proc
+ *
+ * This function performs generic USB clear stall operations.
+ *------------------------------------------------------------------------*/
+static void
+usbd_clear_stall_proc(struct usb_proc_msg *_pm)
+{
+ struct usb_udev_msg *pm = (void *)_pm;
+ struct usb_device *udev = pm->udev;
+
+ /* Change lock */
+ USB_BUS_UNLOCK(udev->bus);
+ USB_MTX_LOCK(&udev->device_mtx);
+
+ /* Start clear stall callback */
+ usbd_transfer_start(udev->ctrl_xfer[1]);
+
+ /* Change lock */
+ USB_MTX_UNLOCK(&udev->device_mtx);
+ USB_BUS_LOCK(udev->bus);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_get_langid
+ *
+ * This function tries to figure out the USB string language to use.
+ *------------------------------------------------------------------------*/
+void
+usb_get_langid(struct usb_device *udev)
+{
+ uint8_t *scratch_ptr;
+ uint8_t do_unlock;
+ int err;
+
+ /*
+ * Workaround for buggy USB devices.
+ *
+ * It appears that some string-less USB chips will crash and
+ * disappear if any attempts are made to read any string
+ * descriptors.
+ *
+ * Try to detect such chips by checking the strings in the USB
+ * device descriptor. If no strings are present there we
+ * simply disable all USB strings.
+ */
+
+ /* Protect scratch area */
+ do_unlock = usbd_ctrl_lock(udev);
+
+ scratch_ptr = udev->scratch.data;
+
+ if (udev->flags.no_strings) {
+ err = USB_ERR_INVAL;
+ } else if (udev->ddesc.iManufacturer ||
+ udev->ddesc.iProduct ||
+ udev->ddesc.iSerialNumber) {
+ /* read out the language ID string */
+ err = usbd_req_get_string_desc(udev, NULL,
+ (char *)scratch_ptr, 4, 0, USB_LANGUAGE_TABLE);
+ } else {
+ err = USB_ERR_INVAL;
+ }
+
+ if (err || (scratch_ptr[0] < 4)) {
+ udev->flags.no_strings = 1;
+ } else {
+ uint16_t langid;
+ uint16_t pref;
+ uint16_t mask;
+ uint8_t x;
+
+ /* load preferred value and mask */
+ pref = usb_lang_id;
+ mask = usb_lang_mask;
+
+ /* align length correctly */
+ scratch_ptr[0] &= ~1U;
+
+ /* fix compiler warning */
+ langid = 0;
+
+ /* search for preferred language */
+ for (x = 2; x < scratch_ptr[0]; x += 2) {
+ langid = UGETW(scratch_ptr + x);
+ if ((langid & mask) == pref)
+ break;
+ }
+ if (x >= scratch_ptr[0]) {
+ /* pick the first language as the default */
+ DPRINTFN(1, "Using first language\n");
+ langid = UGETW(scratch_ptr + 2);
+ }
+
+ DPRINTFN(1, "Language selected: 0x%04x\n", langid);
+ udev->langid = langid;
+ }
+
+ if (do_unlock)
+ usbd_ctrl_unlock(udev);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_alloc_device
+ *
+ * This function allocates a new USB device. This function is called
+ * when a new device has been put in the powered state, but not yet in
+ * the addressed state. Get initial descriptor, set the address, get
+ * full descriptor and get strings.
+ *
+ * Return values:
+ * 0: Failure
+ * Else: Success
+ *------------------------------------------------------------------------*/
+struct usb_device *
+usb_alloc_device(device_t parent_dev, struct usb_bus *bus,
+ struct usb_device *parent_hub, uint8_t depth, uint8_t port_index,
+ uint8_t port_no, enum usb_dev_speed speed, enum usb_hc_mode mode)
+{
+ struct usb_attach_arg uaa;
+ struct usb_device *udev;
+ struct usb_device *adev;
+ struct usb_device *hub;
+ usb_error_t err;
+ uint8_t device_index;
+ uint8_t config_index;
+ uint8_t config_quirk;
+ uint8_t set_config_failed;
+
+ DPRINTF("parent_dev=%p, bus=%p, parent_hub=%p, depth=%u, "
+ "port_index=%u, port_no=%u, speed=%u, usb_mode=%u\n",
+ parent_dev, bus, parent_hub, depth, port_index, port_no,
+ speed, mode);
+
+ /*
+ * Find an unused device index. In USB Host mode this is the
+ * same as the device address.
+ *
+ * Device index zero is not used and device index 1 should
+ * always be the root hub.
+ */
+ for (device_index = USB_ROOT_HUB_ADDR;
+ (device_index != bus->devices_max) &&
+ (bus->devices[device_index] != NULL);
+ device_index++) /* nop */;
+
+ if (device_index == bus->devices_max) {
+ device_printf(bus->bdev,
+ "No free USB device index for new device\n");
+ return (NULL);
+ }
+
+ if (depth > 0x10) {
+ device_printf(bus->bdev,
+ "Invalid device depth\n");
+ return (NULL);
+ }
+ udev = malloc(sizeof(*udev), M_USB, M_WAITOK | M_ZERO);
+#if (USB_HAVE_MALLOC_WAITOK == 0)
+ if (udev == NULL) {
+ return (NULL);
+ }
+#endif
+ /* initialise our SX-lock */
+ sx_init_flags(&udev->enum_sx, "USB config SX lock", SX_DUPOK);
+ sx_init_flags(&udev->sr_sx, "USB suspend and resume SX lock", SX_NOWITNESS);
+ sx_init_flags(&udev->ctrl_sx, "USB control transfer SX lock", SX_DUPOK);
+
+ cv_init(&udev->ctrlreq_cv, "WCTRL");
+ cv_init(&udev->ref_cv, "UGONE");
+
+ /* initialise our mutex */
+ mtx_init(&udev->device_mtx, "USB device mutex", NULL, MTX_DEF);
+
+ /* initialise generic clear stall */
+ udev->cs_msg[0].hdr.pm_callback = &usbd_clear_stall_proc;
+ udev->cs_msg[0].udev = udev;
+ udev->cs_msg[1].hdr.pm_callback = &usbd_clear_stall_proc;
+ udev->cs_msg[1].udev = udev;
+
+ /* initialise some USB device fields */
+ udev->parent_hub = parent_hub;
+ udev->parent_dev = parent_dev;
+ udev->port_index = port_index;
+ udev->port_no = port_no;
+ udev->depth = depth;
+ udev->bus = bus;
+ udev->address = USB_START_ADDR; /* default value */
+ udev->plugtime = (usb_ticks_t)ticks;
+ /*
+ * We need to force the power mode to "on" because there are plenty
+ * of USB devices out there that do not work very well with
+ * automatic suspend and resume!
+ */
+ udev->power_mode = usbd_filter_power_mode(udev, USB_POWER_MODE_ON);
+ udev->pwr_save.last_xfer_time = ticks;
+ /* we are not ready yet */
+ udev->refcount = 1;
+
+ /* set up default endpoint descriptor */
+ udev->ctrl_ep_desc.bLength = sizeof(udev->ctrl_ep_desc);
+ udev->ctrl_ep_desc.bDescriptorType = UDESC_ENDPOINT;
+ udev->ctrl_ep_desc.bEndpointAddress = USB_CONTROL_ENDPOINT;
+ udev->ctrl_ep_desc.bmAttributes = UE_CONTROL;
+ udev->ctrl_ep_desc.wMaxPacketSize[0] = USB_MAX_IPACKET;
+ udev->ctrl_ep_desc.wMaxPacketSize[1] = 0;
+ udev->ctrl_ep_desc.bInterval = 0;
+
+ /* set up default endpoint companion descriptor */
+ udev->ctrl_ep_comp_desc.bLength = sizeof(udev->ctrl_ep_comp_desc);
+ udev->ctrl_ep_comp_desc.bDescriptorType = UDESC_ENDPOINT_SS_COMP;
+
+ udev->ddesc.bMaxPacketSize = USB_MAX_IPACKET;
+
+ udev->speed = speed;
+ udev->flags.usb_mode = mode;
+
+ /* search for our High Speed USB HUB, if any */
+
+ adev = udev;
+ hub = udev->parent_hub;
+
+ while (hub) {
+ if (hub->speed == USB_SPEED_HIGH) {
+ udev->hs_hub_addr = hub->address;
+ udev->parent_hs_hub = hub;
+ udev->hs_port_no = adev->port_no;
+ break;
+ }
+ adev = hub;
+ hub = hub->parent_hub;
+ }
+
+ /* init the default endpoint */
+ usb_init_endpoint(udev, 0,
+ &udev->ctrl_ep_desc,
+ &udev->ctrl_ep_comp_desc,
+ &udev->ctrl_ep);
+
+ /* set device index */
+ udev->device_index = device_index;
+
+#if USB_HAVE_UGEN
+ /* Create ugen name */
+ snprintf(udev->ugen_name, sizeof(udev->ugen_name),
+ USB_GENERIC_NAME "%u.%u", device_get_unit(bus->bdev),
+ device_index);
+ SLIST_INIT(&udev->pd_list);
+
+ /* Create the control endpoint device */
+ udev->ctrl_dev = usb_make_dev(udev, NULL, 0, 0,
+ FREAD|FWRITE, UID_ROOT, GID_OPERATOR, 0600);
+
+ /* Create a link from /dev/ugenX.X to the default endpoint */
+ if (udev->ctrl_dev != NULL)
+ make_dev_alias(udev->ctrl_dev->cdev, "%s", udev->ugen_name);
+#endif
+ /* Initialise device */
+ if (bus->methods->device_init != NULL) {
+ err = (bus->methods->device_init) (udev);
+ if (err != 0) {
+ DPRINTFN(0, "device init %d failed "
+ "(%s, ignored)\n", device_index,
+ usbd_errstr(err));
+ goto done;
+ }
+ }
+ /* set powered device state after device init is complete */
+ usb_set_device_state(udev, USB_STATE_POWERED);
+
+ if (udev->flags.usb_mode == USB_MODE_HOST) {
+ err = usbd_req_set_address(udev, NULL, device_index);
+
+ /*
+ * This is the new USB device address from now on, if
+ * the set address request didn't set it already.
+ */
+ if (udev->address == USB_START_ADDR)
+ udev->address = device_index;
+
+ /*
+ * We ignore any set-address errors, hence there are
+ * buggy USB devices out there that actually receive
+ * the SETUP PID, but manage to set the address before
+ * the STATUS stage is ACK'ed. If the device responds
+ * to the subsequent get-descriptor at the new
+ * address, then we know that the set-address command
+ * was successful.
+ */
+ if (err) {
+ DPRINTFN(0, "set address %d failed "
+ "(%s, ignored)\n", udev->address,
+ usbd_errstr(err));
+ }
+ } else {
+ /* We are not self powered */
+ udev->flags.self_powered = 0;
+
+ /* Set unconfigured state */
+ udev->curr_config_no = USB_UNCONFIG_NO;
+ udev->curr_config_index = USB_UNCONFIG_INDEX;
+
+ /* Setup USB descriptors */
+ err = (usb_temp_setup_by_index_p) (udev, usb_template);
+ if (err) {
+ DPRINTFN(0, "setting up USB template failed - "
+ "usb_template(4) not loaded?\n");
+ goto done;
+ }
+ }
+ usb_set_device_state(udev, USB_STATE_ADDRESSED);
+
+ /* setup the device descriptor and the initial "wMaxPacketSize" */
+ err = usbd_setup_device_desc(udev, NULL);
+
+ if (err != 0) {
+ /* try to enumerate two more times */
+ err = usbd_req_re_enumerate(udev, NULL);
+ if (err != 0) {
+ err = usbd_req_re_enumerate(udev, NULL);
+ if (err != 0) {
+ goto done;
+ }
+ }
+ }
+
+ /*
+ * Setup temporary USB attach args so that we can figure out some
+ * basic quirks for this device.
+ */
+ usb_init_attach_arg(udev, &uaa);
+
+ if (usb_test_quirk(&uaa, UQ_BUS_POWERED)) {
+ udev->flags.uq_bus_powered = 1;
+ }
+ if (usb_test_quirk(&uaa, UQ_NO_STRINGS)) {
+ udev->flags.no_strings = 1;
+ }
+
+ usb_get_langid(udev);
+
+ /* assume 100mA bus powered for now. Changed when configured. */
+ udev->power = USB_MIN_POWER;
+ /* fetch the vendor and product strings from the device */
+ usb_set_device_strings(udev);
+
+ if (udev->flags.usb_mode == USB_MODE_DEVICE) {
+ /* USB device mode setup is complete */
+ err = 0;
+ goto config_done;
+ }
+
+ /*
+ * Most USB devices should attach to config index 0 by
+ * default
+ */
+ if (usb_test_quirk(&uaa, UQ_CFG_INDEX_0)) {
+ config_index = 0;
+ config_quirk = 1;
+ } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_1)) {
+ config_index = 1;
+ config_quirk = 1;
+ } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_2)) {
+ config_index = 2;
+ config_quirk = 1;
+ } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_3)) {
+ config_index = 3;
+ config_quirk = 1;
+ } else if (usb_test_quirk(&uaa, UQ_CFG_INDEX_4)) {
+ config_index = 4;
+ config_quirk = 1;
+ } else {
+ config_index = 0;
+ config_quirk = 0;
+ }
+
+ set_config_failed = 0;
+repeat_set_config:
+
+ DPRINTF("setting config %u\n", config_index);
+
+ /* get the USB device configured */
+ err = usbd_set_config_index(udev, config_index);
+ if (err) {
+ if (udev->ddesc.bNumConfigurations != 0) {
+ if (!set_config_failed) {
+ set_config_failed = 1;
+ /* XXX try to re-enumerate the device */
+ err = usbd_req_re_enumerate(udev, NULL);
+ if (err == 0)
+ goto repeat_set_config;
+ }
+ DPRINTFN(0, "Failure selecting configuration index %u:"
+ "%s, port %u, addr %u (ignored)\n",
+ config_index, usbd_errstr(err), udev->port_no,
+ udev->address);
+ }
+ /*
+ * Some USB devices do not have any configurations. Ignore any
+ * set config failures!
+ */
+ err = 0;
+ goto config_done;
+ }
+ if (!config_quirk && config_index + 1 < udev->ddesc.bNumConfigurations) {
+ if ((udev->cdesc->bNumInterface < 2) &&
+ usbd_get_no_descriptors(udev->cdesc, UDESC_ENDPOINT) == 0) {
+ DPRINTFN(0, "Found no endpoints, trying next config\n");
+ config_index++;
+ goto repeat_set_config;
+ }
+#if USB_HAVE_MSCTEST
+ if (config_index == 0 &&
+ usb_test_quirk(&uaa, UQ_MSC_NO_INQUIRY) == 0) {
+ /*
+ * Try to figure out if we have an
+ * auto-install disk there:
+ */
+ if (usb_iface_is_cdrom(udev, 0)) {
+ DPRINTFN(0, "Found possible auto-install "
+ "disk (trying next config)\n");
+ config_index++;
+ goto repeat_set_config;
+ }
+ }
+#endif
+ }
+#if USB_HAVE_MSCTEST_AUTOQUIRK
+ if (set_config_failed == 0 && config_index == 0 &&
+ usb_test_quirk(&uaa, UQ_MSC_NO_START_STOP) == 0 &&
+ usb_test_quirk(&uaa, UQ_MSC_NO_PREVENT_ALLOW) == 0 &&
+ usb_test_quirk(&uaa, UQ_MSC_NO_SYNC_CACHE) == 0 &&
+ usb_test_quirk(&uaa, UQ_MSC_NO_TEST_UNIT_READY) == 0 &&
+ usb_test_quirk(&uaa, UQ_MSC_NO_GETMAXLUN) == 0 &&
+ usb_test_quirk(&uaa, UQ_MSC_NO_INQUIRY) == 0 &&
+ usb_test_quirk(&uaa, UQ_MSC_IGNORE) == 0) {
+ /*
+ * Try to figure out if there are any MSC quirks we
+ * should apply automatically:
+ */
+ err = usb_msc_auto_quirk(udev, 0, &uaa);
+
+ if (err != 0) {
+ set_config_failed = 1;
+ goto repeat_set_config;
+ }
+ }
+#endif
+
+config_done:
+ DPRINTF("new dev (addr %d), udev=%p, parent_hub=%p\n",
+ udev->address, udev, udev->parent_hub);
+
+ /* register our device - we are ready */
+ usb_bus_port_set_device(bus, parent_hub ?
+ parent_hub->hub->ports + port_index : NULL, udev, device_index);
+
+#if USB_HAVE_UGEN
+ /* Symlink the ugen device name */
+ udev->ugen_symlink = usb_alloc_symlink(udev->ugen_name);
+
+ /* Announce device */
+ printf("%s: <%s %s> at %s\n", udev->ugen_name,
+ usb_get_manufacturer(udev), usb_get_product(udev),
+ device_get_nameunit(udev->bus->bdev));
+#endif
+
+#if USB_HAVE_DEVCTL
+ usb_notify_addq("ATTACH", udev);
+#endif
+done:
+ if (err) {
+ /*
+ * Free USB device and all subdevices, if any.
+ */
+ usb_free_device(udev, 0);
+ udev = NULL;
+ }
+ return (udev);
+}
+
+#if USB_HAVE_UGEN
+struct usb_fs_privdata *
+usb_make_dev(struct usb_device *udev, const char *devname, int ep,
+ int fi, int rwmode, uid_t uid, gid_t gid, int mode)
+{
+ struct usb_fs_privdata* pd;
+ struct make_dev_args args;
+ char buffer[32];
+
+ /* Store information to locate ourselves again later */
+ pd = malloc(sizeof(struct usb_fs_privdata), M_USBDEV,
+ M_WAITOK | M_ZERO);
+ pd->bus_index = device_get_unit(udev->bus->bdev);
+ pd->dev_index = udev->device_index;
+ pd->ep_addr = ep;
+ pd->fifo_index = fi;
+ pd->mode = rwmode;
+
+ /* Now, create the device itself */
+ if (devname == NULL) {
+ devname = buffer;
+ snprintf(buffer, sizeof(buffer), USB_DEVICE_DIR "/%u.%u.%u",
+ pd->bus_index, pd->dev_index, pd->ep_addr);
+ }
+
+ /* Setup arguments for make_dev_s() */
+ make_dev_args_init(&args);
+ args.mda_devsw = &usb_devsw;
+ args.mda_uid = uid;
+ args.mda_gid = gid;
+ args.mda_mode = mode;
+ args.mda_si_drv1 = pd;
+
+ if (make_dev_s(&args, &pd->cdev, "%s", devname) != 0) {
+ DPRINTFN(0, "Failed to create device %s\n", devname);
+ free(pd, M_USBDEV);
+ return (NULL);
+ }
+ return (pd);
+}
+
+void
+usb_destroy_dev_sync(struct usb_fs_privdata *pd)
+{
+ DPRINTFN(1, "Destroying device at ugen%d.%d\n",
+ pd->bus_index, pd->dev_index);
+
+ /*
+ * Destroy character device synchronously. After this
+ * all system calls are returned. Can block.
+ */
+ destroy_dev(pd->cdev);
+
+ free(pd, M_USBDEV);
+}
+
+void
+usb_destroy_dev(struct usb_fs_privdata *pd)
+{
+ struct usb_bus *bus;
+
+ if (pd == NULL)
+ return;
+
+ mtx_lock(&usb_ref_lock);
+ bus = devclass_get_softc(usb_devclass_ptr, pd->bus_index);
+ mtx_unlock(&usb_ref_lock);
+
+ if (bus == NULL) {
+ usb_destroy_dev_sync(pd);
+ return;
+ }
+
+ /* make sure we can re-use the device name */
+ delist_dev(pd->cdev);
+
+ USB_BUS_LOCK(bus);
+ SLIST_INSERT_HEAD(&bus->pd_cleanup_list, pd, pd_next);
+ /* get cleanup going */
+ usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus),
+ &bus->cleanup_msg[0], &bus->cleanup_msg[1]);
+ USB_BUS_UNLOCK(bus);
+}
+
+static void
+usb_cdev_create(struct usb_device *udev)
+{
+ struct usb_config_descriptor *cd;
+ struct usb_endpoint_descriptor *ed;
+ struct usb_descriptor *desc;
+ struct usb_fs_privdata* pd;
+ int inmode, outmode, inmask, outmask, mode;
+ uint8_t ep;
+
+ KASSERT(SLIST_FIRST(&udev->pd_list) == NULL, ("stale cdev entries"));
+
+ DPRINTFN(2, "Creating device nodes\n");
+
+ if (usbd_get_mode(udev) == USB_MODE_DEVICE) {
+ inmode = FWRITE;
+ outmode = FREAD;
+ } else { /* USB_MODE_HOST */
+ inmode = FREAD;
+ outmode = FWRITE;
+ }
+
+ inmask = 0;
+ outmask = 0;
+ desc = NULL;
+
+ /*
+ * Collect all used endpoint numbers instead of just
+ * generating 16 static endpoints.
+ */
+ cd = usbd_get_config_descriptor(udev);
+ while ((desc = usb_desc_foreach(cd, desc))) {
+ /* filter out all endpoint descriptors */
+ if ((desc->bDescriptorType == UDESC_ENDPOINT) &&
+ (desc->bLength >= sizeof(*ed))) {
+ ed = (struct usb_endpoint_descriptor *)desc;
+
+ /* update masks */
+ ep = ed->bEndpointAddress;
+ if (UE_GET_DIR(ep) == UE_DIR_OUT)
+ outmask |= 1 << UE_GET_ADDR(ep);
+ else
+ inmask |= 1 << UE_GET_ADDR(ep);
+ }
+ }
+
+ /* Create all available endpoints except EP0 */
+ for (ep = 1; ep < 16; ep++) {
+ mode = (inmask & (1 << ep)) ? inmode : 0;
+ mode |= (outmask & (1 << ep)) ? outmode : 0;
+ if (mode == 0)
+ continue; /* no IN or OUT endpoint */
+
+ pd = usb_make_dev(udev, NULL, ep, 0,
+ mode, UID_ROOT, GID_OPERATOR, 0600);
+
+ if (pd != NULL)
+ SLIST_INSERT_HEAD(&udev->pd_list, pd, pd_next);
+ }
+}
+
+static void
+usb_cdev_free(struct usb_device *udev)
+{
+ struct usb_fs_privdata* pd;
+
+ DPRINTFN(2, "Freeing device nodes\n");
+
+ while ((pd = SLIST_FIRST(&udev->pd_list)) != NULL) {
+ KASSERT(pd->cdev->si_drv1 == pd, ("privdata corrupt"));
+
+ SLIST_REMOVE(&udev->pd_list, pd, usb_fs_privdata, pd_next);
+
+ usb_destroy_dev(pd);
+ }
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usb_free_device
+ *
+ * This function is NULL safe and will free an USB device and its
+ * children devices, if any.
+ *
+ * Flag values: Reserved, set to zero.
+ *------------------------------------------------------------------------*/
+void
+usb_free_device(struct usb_device *udev, uint8_t flag)
+{
+ struct usb_bus *bus;
+
+ if (udev == NULL)
+ return; /* already freed */
+
+ DPRINTFN(4, "udev=%p port=%d\n", udev, udev->port_no);
+
+ bus = udev->bus;
+
+ /* set DETACHED state to prevent any further references */
+ usb_set_device_state(udev, USB_STATE_DETACHED);
+
+#if USB_HAVE_DEVCTL
+ usb_notify_addq("DETACH", udev);
+#endif
+
+#if USB_HAVE_UGEN
+ if (!rebooting) {
+ printf("%s: <%s %s> at %s (disconnected)\n", udev->ugen_name,
+ usb_get_manufacturer(udev), usb_get_product(udev),
+ device_get_nameunit(bus->bdev));
+ }
+
+ /* Destroy UGEN symlink, if any */
+ if (udev->ugen_symlink) {
+ usb_free_symlink(udev->ugen_symlink);
+ udev->ugen_symlink = NULL;
+ }
+
+ usb_destroy_dev(udev->ctrl_dev);
+#endif
+
+ if (udev->flags.usb_mode == USB_MODE_DEVICE) {
+ /* stop receiving any control transfers (Device Side Mode) */
+ usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX);
+ }
+
+ /* the following will get the device unconfigured in software */
+ usb_unconfigure(udev, USB_UNCFG_FLAG_FREE_EP0);
+
+ /* final device unregister after all character devices are closed */
+ usb_bus_port_set_device(bus, udev->parent_hub ?
+ udev->parent_hub->hub->ports + udev->port_index : NULL,
+ NULL, USB_ROOT_HUB_ADDR);
+
+ /* unsetup any leftover default USB transfers */
+ usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX);
+
+ /* template unsetup, if any */
+ (usb_temp_unsetup_p) (udev);
+
+ /*
+ * Make sure that our clear-stall messages are not queued
+ * anywhere:
+ */
+ USB_BUS_LOCK(udev->bus);
+ usb_proc_mwait(USB_BUS_CS_PROC(udev->bus),
+ &udev->cs_msg[0], &udev->cs_msg[1]);
+ USB_BUS_UNLOCK(udev->bus);
+
+ /* wait for all references to go away */
+ usb_wait_pending_refs(udev);
+
+ sx_destroy(&udev->enum_sx);
+ sx_destroy(&udev->sr_sx);
+ sx_destroy(&udev->ctrl_sx);
+
+ cv_destroy(&udev->ctrlreq_cv);
+ cv_destroy(&udev->ref_cv);
+
+ mtx_destroy(&udev->device_mtx);
+#if USB_HAVE_UGEN
+ KASSERT(SLIST_FIRST(&udev->pd_list) == NULL, ("leaked cdev entries"));
+#endif
+
+ /* Uninitialise device */
+ if (bus->methods->device_uninit != NULL)
+ (bus->methods->device_uninit) (udev);
+
+ /* free device */
+ free(udev->serial, M_USB);
+ free(udev->manufacturer, M_USB);
+ free(udev->product, M_USB);
+ free(udev, M_USB);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_get_iface
+ *
+ * This function is the safe way to get the USB interface structure
+ * pointer by interface index.
+ *
+ * Return values:
+ * NULL: Interface not present.
+ * Else: Pointer to USB interface structure.
+ *------------------------------------------------------------------------*/
+struct usb_interface *
+usbd_get_iface(struct usb_device *udev, uint8_t iface_index)
+{
+ struct usb_interface *iface = udev->ifaces + iface_index;
+
+ if (iface_index >= udev->ifaces_max)
+ return (NULL);
+ return (iface);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_find_descriptor
+ *
+ * This function will lookup the first descriptor that matches the
+ * criteria given by the arguments "type" and "subtype". Descriptors
+ * will only be searched within the interface having the index
+ * "iface_index". If the "id" argument points to an USB descriptor,
+ * it will be skipped before the search is started. This allows
+ * searching for multiple descriptors using the same criteria. Else
+ * the search is started after the interface descriptor.
+ *
+ * Return values:
+ * NULL: End of descriptors
+ * Else: A descriptor matching the criteria
+ *------------------------------------------------------------------------*/
+void *
+usbd_find_descriptor(struct usb_device *udev, void *id, uint8_t iface_index,
+ uint8_t type, uint8_t type_mask,
+ uint8_t subtype, uint8_t subtype_mask)
+{
+ struct usb_descriptor *desc;
+ struct usb_config_descriptor *cd;
+ struct usb_interface *iface;
+
+ cd = usbd_get_config_descriptor(udev);
+ if (cd == NULL) {
+ return (NULL);
+ }
+ if (id == NULL) {
+ iface = usbd_get_iface(udev, iface_index);
+ if (iface == NULL) {
+ return (NULL);
+ }
+ id = usbd_get_interface_descriptor(iface);
+ if (id == NULL) {
+ return (NULL);
+ }
+ }
+ desc = (void *)id;
+
+ while ((desc = usb_desc_foreach(cd, desc))) {
+ if (desc->bDescriptorType == UDESC_INTERFACE) {
+ break;
+ }
+ if (((desc->bDescriptorType & type_mask) == type) &&
+ ((desc->bDescriptorSubtype & subtype_mask) == subtype)) {
+ return (desc);
+ }
+ }
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_devinfo
+ *
+ * This function will dump information from the device descriptor
+ * belonging to the USB device pointed to by "udev", to the string
+ * pointed to by "dst_ptr" having a maximum length of "dst_len" bytes
+ * including the terminating zero.
+ *------------------------------------------------------------------------*/
+void
+usb_devinfo(struct usb_device *udev, char *dst_ptr, uint16_t dst_len)
+{
+ struct usb_device_descriptor *udd = &udev->ddesc;
+ uint16_t bcdDevice;
+ uint16_t bcdUSB;
+
+ bcdUSB = UGETW(udd->bcdUSB);
+ bcdDevice = UGETW(udd->bcdDevice);
+
+ if (udd->bDeviceClass != 0xFF) {
+ snprintf(dst_ptr, dst_len, "%s %s, class %d/%d, rev %x.%02x/"
+ "%x.%02x, addr %d",
+ usb_get_manufacturer(udev),
+ usb_get_product(udev),
+ udd->bDeviceClass, udd->bDeviceSubClass,
+ (bcdUSB >> 8), bcdUSB & 0xFF,
+ (bcdDevice >> 8), bcdDevice & 0xFF,
+ udev->address);
+ } else {
+ snprintf(dst_ptr, dst_len, "%s %s, rev %x.%02x/"
+ "%x.%02x, addr %d",
+ usb_get_manufacturer(udev),
+ usb_get_product(udev),
+ (bcdUSB >> 8), bcdUSB & 0xFF,
+ (bcdDevice >> 8), bcdDevice & 0xFF,
+ udev->address);
+ }
+}
+
+#ifdef USB_VERBOSE
+/*
+ * Descriptions of known vendors and devices ("products").
+ */
+struct usb_knowndev {
+ uint16_t vendor;
+ uint16_t product;
+ uint32_t flags;
+ const char *vendorname;
+ const char *productname;
+};
+
+#define USB_KNOWNDEV_NOPROD 0x01 /* match on vendor only */
+
+#include "usbdevs.h"
+#include "usbdevs_data.h"
+#endif /* USB_VERBOSE */
+
+void
+usb_set_device_strings(struct usb_device *udev)
+{
+ struct usb_device_descriptor *udd = &udev->ddesc;
+#ifdef USB_VERBOSE
+ const struct usb_knowndev *kdp;
+#endif
+ char *temp_ptr;
+ size_t temp_size;
+ uint16_t vendor_id;
+ uint16_t product_id;
+ uint8_t do_unlock;
+
+ /* Protect scratch area */
+ do_unlock = usbd_ctrl_lock(udev);
+
+ temp_ptr = (char *)udev->scratch.data;
+ temp_size = sizeof(udev->scratch.data);
+
+ vendor_id = UGETW(udd->idVendor);
+ product_id = UGETW(udd->idProduct);
+
+ /* cleanup old strings, if any */
+ free(udev->serial, M_USB);
+ free(udev->manufacturer, M_USB);
+ free(udev->product, M_USB);
+
+ /* zero the string pointers */
+ udev->serial = NULL;
+ udev->manufacturer = NULL;
+ udev->product = NULL;
+
+ /* get serial number string */
+ usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size,
+ udev->ddesc.iSerialNumber);
+ udev->serial = strdup(temp_ptr, M_USB);
+
+ /* get manufacturer string */
+ usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size,
+ udev->ddesc.iManufacturer);
+ usb_trim_spaces(temp_ptr);
+ if (temp_ptr[0] != '\0')
+ udev->manufacturer = strdup(temp_ptr, M_USB);
+
+ /* get product string */
+ usbd_req_get_string_any(udev, NULL, temp_ptr, temp_size,
+ udev->ddesc.iProduct);
+ usb_trim_spaces(temp_ptr);
+ if (temp_ptr[0] != '\0')
+ udev->product = strdup(temp_ptr, M_USB);
+
+#ifdef USB_VERBOSE
+ if (udev->manufacturer == NULL || udev->product == NULL) {
+ for (kdp = usb_knowndevs; kdp->vendorname != NULL; kdp++) {
+ if (kdp->vendor == vendor_id &&
+ (kdp->product == product_id ||
+ (kdp->flags & USB_KNOWNDEV_NOPROD) != 0))
+ break;
+ }
+ if (kdp->vendorname != NULL) {
+ /* XXX should use pointer to knowndevs string */
+ if (udev->manufacturer == NULL) {
+ udev->manufacturer = strdup(kdp->vendorname,
+ M_USB);
+ }
+ if (udev->product == NULL &&
+ (kdp->flags & USB_KNOWNDEV_NOPROD) == 0) {
+ udev->product = strdup(kdp->productname,
+ M_USB);
+ }
+ }
+ }
+#endif
+ /* Provide default strings if none were found */
+ if (udev->manufacturer == NULL) {
+ snprintf(temp_ptr, temp_size, "vendor 0x%04x", vendor_id);
+ udev->manufacturer = strdup(temp_ptr, M_USB);
+ }
+ if (udev->product == NULL) {
+ snprintf(temp_ptr, temp_size, "product 0x%04x", product_id);
+ udev->product = strdup(temp_ptr, M_USB);
+ }
+
+ if (do_unlock)
+ usbd_ctrl_unlock(udev);
+}
+
+/*
+ * Returns:
+ * See: USB_MODE_XXX
+ */
+enum usb_hc_mode
+usbd_get_mode(struct usb_device *udev)
+{
+ return (udev->flags.usb_mode);
+}
+
+/*
+ * Returns:
+ * See: USB_SPEED_XXX
+ */
+enum usb_dev_speed
+usbd_get_speed(struct usb_device *udev)
+{
+ return (udev->speed);
+}
+
+uint32_t
+usbd_get_isoc_fps(struct usb_device *udev)
+{
+ ; /* indent fix */
+ switch (udev->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ return (1000);
+ default:
+ return (8000);
+ }
+}
+
+struct usb_device_descriptor *
+usbd_get_device_descriptor(struct usb_device *udev)
+{
+ if (udev == NULL)
+ return (NULL); /* be NULL safe */
+ return (&udev->ddesc);
+}
+
+struct usb_config_descriptor *
+usbd_get_config_descriptor(struct usb_device *udev)
+{
+ if (udev == NULL)
+ return (NULL); /* be NULL safe */
+ return (udev->cdesc);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_test_quirk - test a device for a given quirk
+ *
+ * Return values:
+ * 0: The USB device does not have the given quirk.
+ * Else: The USB device has the given quirk.
+ *------------------------------------------------------------------------*/
+uint8_t
+usb_test_quirk(const struct usb_attach_arg *uaa, uint16_t quirk)
+{
+ uint8_t found;
+ uint8_t x;
+
+ if (quirk == UQ_NONE)
+ return (0);
+
+ /* search the automatic per device quirks first */
+
+ for (x = 0; x != USB_MAX_AUTO_QUIRK; x++) {
+ if (uaa->device->autoQuirk[x] == quirk)
+ return (1);
+ }
+
+ /* search global quirk table, if any */
+
+ found = (usb_test_quirk_p) (&uaa->info, quirk);
+
+ return (found);
+}
+
+struct usb_interface_descriptor *
+usbd_get_interface_descriptor(struct usb_interface *iface)
+{
+ if (iface == NULL)
+ return (NULL); /* be NULL safe */
+ return (iface->idesc);
+}
+
+uint8_t
+usbd_get_interface_altindex(struct usb_interface *iface)
+{
+ return (iface->alt_index);
+}
+
+uint8_t
+usbd_get_bus_index(struct usb_device *udev)
+{
+ return ((uint8_t)device_get_unit(udev->bus->bdev));
+}
+
+uint8_t
+usbd_get_device_index(struct usb_device *udev)
+{
+ return (udev->device_index);
+}
+
+#if USB_HAVE_DEVCTL
+static void
+usb_notify_addq(const char *type, struct usb_device *udev)
+{
+ struct usb_interface *iface;
+ struct sbuf *sb;
+ int i;
+
+ /* announce the device */
+ sb = sbuf_new_auto();
+ sbuf_printf(sb,
+#if USB_HAVE_UGEN
+ "ugen=%s "
+ "cdev=%s "
+#endif
+ "vendor=0x%04x "
+ "product=0x%04x "
+ "devclass=0x%02x "
+ "devsubclass=0x%02x "
+ "sernum=\"%s\" "
+ "release=0x%04x "
+ "mode=%s "
+ "port=%u "
+#if USB_HAVE_UGEN
+ "parent=%s"
+#endif
+ "",
+#if USB_HAVE_UGEN
+ udev->ugen_name,
+ udev->ugen_name,
+#endif
+ UGETW(udev->ddesc.idVendor),
+ UGETW(udev->ddesc.idProduct),
+ udev->ddesc.bDeviceClass,
+ udev->ddesc.bDeviceSubClass,
+ usb_get_serial(udev),
+ UGETW(udev->ddesc.bcdDevice),
+ (udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device",
+ udev->port_no
+#if USB_HAVE_UGEN
+ , udev->parent_hub != NULL ?
+ udev->parent_hub->ugen_name :
+ device_get_nameunit(device_get_parent(udev->bus->bdev))
+#endif
+ );
+ sbuf_finish(sb);
+ devctl_notify("USB", "DEVICE", type, sbuf_data(sb));
+ sbuf_delete(sb);
+
+ /* announce each interface */
+ for (i = 0; i < USB_IFACE_MAX; i++) {
+ iface = usbd_get_iface(udev, i);
+ if (iface == NULL)
+ break; /* end of interfaces */
+ if (iface->idesc == NULL)
+ continue; /* no interface descriptor */
+
+ sb = sbuf_new_auto();
+ sbuf_printf(sb,
+#if USB_HAVE_UGEN
+ "ugen=%s "
+ "cdev=%s "
+#endif
+ "vendor=0x%04x "
+ "product=0x%04x "
+ "devclass=0x%02x "
+ "devsubclass=0x%02x "
+ "sernum=\"%s\" "
+ "release=0x%04x "
+ "mode=%s "
+ "interface=%d "
+ "endpoints=%d "
+ "intclass=0x%02x "
+ "intsubclass=0x%02x "
+ "intprotocol=0x%02x",
+#if USB_HAVE_UGEN
+ udev->ugen_name,
+ udev->ugen_name,
+#endif
+ UGETW(udev->ddesc.idVendor),
+ UGETW(udev->ddesc.idProduct),
+ udev->ddesc.bDeviceClass,
+ udev->ddesc.bDeviceSubClass,
+ usb_get_serial(udev),
+ UGETW(udev->ddesc.bcdDevice),
+ (udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device",
+ iface->idesc->bInterfaceNumber,
+ iface->idesc->bNumEndpoints,
+ iface->idesc->bInterfaceClass,
+ iface->idesc->bInterfaceSubClass,
+ iface->idesc->bInterfaceProtocol);
+ sbuf_finish(sb);
+ devctl_notify("USB", "INTERFACE", type, sbuf_data(sb));
+ sbuf_delete(sb);
+ }
+}
+#endif
+
+#if USB_HAVE_UGEN
+/*------------------------------------------------------------------------*
+ * usb_fifo_free_wrap
+ *
+ * This function will free the FIFOs.
+ *
+ * Description of "flag" argument: If the USB_UNCFG_FLAG_FREE_EP0 flag
+ * is set and "iface_index" is set to "USB_IFACE_INDEX_ANY", we free
+ * all FIFOs. If the USB_UNCFG_FLAG_FREE_EP0 flag is not set and
+ * "iface_index" is set to "USB_IFACE_INDEX_ANY", we free all non
+ * control endpoint FIFOs. If "iface_index" is not set to
+ * "USB_IFACE_INDEX_ANY" the flag has no effect.
+ *------------------------------------------------------------------------*/
+static void
+usb_fifo_free_wrap(struct usb_device *udev,
+ uint8_t iface_index, uint8_t flag)
+{
+ struct usb_fifo *f;
+ uint16_t i;
+
+ /*
+ * Free any USB FIFOs on the given interface:
+ */
+ for (i = 0; i != USB_FIFO_MAX; i++) {
+ f = udev->fifo[i];
+ if (f == NULL) {
+ continue;
+ }
+ /* Check if the interface index matches */
+ if (iface_index == f->iface_index) {
+ if (f->methods != &usb_ugen_methods) {
+ /*
+ * Don't free any non-generic FIFOs in
+ * this case.
+ */
+ continue;
+ }
+ if ((f->dev_ep_index == 0) &&
+ (f->fs_ep_max == 0)) {
+ /* no need to free this FIFO */
+ continue;
+ }
+ } else if (iface_index == USB_IFACE_INDEX_ANY) {
+ if ((f->methods == &usb_ugen_methods) &&
+ (f->dev_ep_index == 0) &&
+ (!(flag & USB_UNCFG_FLAG_FREE_EP0)) &&
+ (f->fs_ep_max == 0)) {
+ /* no need to free this FIFO */
+ continue;
+ }
+ } else {
+ /* no need to free this FIFO */
+ continue;
+ }
+ /* free this FIFO */
+ usb_fifo_free(f);
+ }
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usb_peer_can_wakeup
+ *
+ * Return values:
+ * 0: Peer cannot do resume signalling.
+ * Else: Peer can do resume signalling.
+ *------------------------------------------------------------------------*/
+uint8_t
+usb_peer_can_wakeup(struct usb_device *udev)
+{
+ const struct usb_config_descriptor *cdp;
+
+ cdp = udev->cdesc;
+ if ((cdp != NULL) && (udev->flags.usb_mode == USB_MODE_HOST)) {
+ return (cdp->bmAttributes & UC_REMOTE_WAKEUP);
+ }
+ return (0); /* not supported */
+}
+
+void
+usb_set_device_state(struct usb_device *udev, enum usb_dev_state state)
+{
+
+ KASSERT(state < USB_STATE_MAX, ("invalid udev state"));
+
+ DPRINTF("udev %p state %s -> %s\n", udev,
+ usb_statestr(udev->state), usb_statestr(state));
+
+#if USB_HAVE_UGEN
+ mtx_lock(&usb_ref_lock);
+#endif
+ udev->state = state;
+#if USB_HAVE_UGEN
+ mtx_unlock(&usb_ref_lock);
+#endif
+ if (udev->bus->methods->device_state_change != NULL)
+ (udev->bus->methods->device_state_change) (udev);
+}
+
+enum usb_dev_state
+usb_get_device_state(struct usb_device *udev)
+{
+ if (udev == NULL)
+ return (USB_STATE_DETACHED);
+ return (udev->state);
+}
+
+uint8_t
+usbd_device_attached(struct usb_device *udev)
+{
+ return (udev->state > USB_STATE_DETACHED);
+}
+
+/*
+ * The following function locks enumerating the given USB device. If
+ * the lock is already grabbed this function returns zero. Else a
+ * a value of one is returned.
+ */
+uint8_t
+usbd_enum_lock(struct usb_device *udev)
+{
+ if (sx_xlocked(&udev->enum_sx))
+ return (0);
+
+ sx_xlock(&udev->enum_sx);
+ sx_xlock(&udev->sr_sx);
+ /*
+ * NEWBUS LOCK NOTE: We should check if any parent SX locks
+ * are locked before locking Giant. Else the lock can be
+ * locked multiple times.
+ */
+ bus_topo_lock();
+ return (1);
+}
+
+#if USB_HAVE_UGEN
+/*
+ * This function is the same like usbd_enum_lock() except a value of
+ * 255 is returned when a signal is pending:
+ */
+uint8_t
+usbd_enum_lock_sig(struct usb_device *udev)
+{
+ if (sx_xlocked(&udev->enum_sx))
+ return (0);
+ if (sx_xlock_sig(&udev->enum_sx))
+ return (255);
+ if (sx_xlock_sig(&udev->sr_sx)) {
+ sx_xunlock(&udev->enum_sx);
+ return (255);
+ }
+ bus_topo_lock();
+ return (1);
+}
+#endif
+
+/* The following function unlocks enumerating the given USB device. */
+
+void
+usbd_enum_unlock(struct usb_device *udev)
+{
+ bus_topo_unlock();
+ sx_xunlock(&udev->enum_sx);
+ sx_xunlock(&udev->sr_sx);
+}
+
+/* The following function locks suspend and resume. */
+
+void
+usbd_sr_lock(struct usb_device *udev)
+{
+ sx_xlock(&udev->sr_sx);
+ /*
+ * NEWBUS LOCK NOTE: We should check if any parent SX locks
+ * are locked before locking Giant. Else the lock can be
+ * locked multiple times.
+ */
+ bus_topo_lock();
+}
+
+/* The following function unlocks suspend and resume. */
+
+void
+usbd_sr_unlock(struct usb_device *udev)
+{
+ bus_topo_unlock();
+ sx_xunlock(&udev->sr_sx);
+}
+
+/*
+ * The following function checks the enumerating lock for the given
+ * USB device.
+ */
+
+uint8_t
+usbd_enum_is_locked(struct usb_device *udev)
+{
+ return (sx_xlocked(&udev->enum_sx));
+}
+
+/*
+ * The following function is used to serialize access to USB control
+ * transfers and the USB scratch area. If the lock is already grabbed
+ * this function returns zero. Else a value of one is returned.
+ */
+uint8_t
+usbd_ctrl_lock(struct usb_device *udev)
+{
+ if (sx_xlocked(&udev->ctrl_sx))
+ return (0);
+ sx_xlock(&udev->ctrl_sx);
+
+ /*
+ * We need to allow suspend and resume at this point, else the
+ * control transfer will timeout if the device is suspended!
+ */
+ if (usbd_enum_is_locked(udev))
+ usbd_sr_unlock(udev);
+ return (1);
+}
+
+void
+usbd_ctrl_unlock(struct usb_device *udev)
+{
+ sx_xunlock(&udev->ctrl_sx);
+
+ /*
+ * Restore the suspend and resume lock after we have unlocked
+ * the USB control transfer lock to avoid LOR:
+ */
+ if (usbd_enum_is_locked(udev))
+ usbd_sr_lock(udev);
+}
+
+/*
+ * The following function is used to set the per-interface specific
+ * plug and play information. The string referred to by the pnpinfo
+ * argument can safely be freed after calling this function. The
+ * pnpinfo of an interface will be reset at device detach or when
+ * passing a NULL argument to this function. This function
+ * returns zero on success, else a USB_ERR_XXX failure code.
+ */
+
+usb_error_t
+usbd_set_pnpinfo(struct usb_device *udev, uint8_t iface_index, const char *pnpinfo)
+{
+ struct usb_interface *iface;
+
+ iface = usbd_get_iface(udev, iface_index);
+ if (iface == NULL)
+ return (USB_ERR_INVAL);
+
+ if (iface->pnpinfo != NULL) {
+ free(iface->pnpinfo, M_USBDEV);
+ iface->pnpinfo = NULL;
+ }
+
+ if (pnpinfo == NULL || pnpinfo[0] == 0)
+ return (0); /* success */
+
+ iface->pnpinfo = strdup(pnpinfo, M_USBDEV);
+ if (iface->pnpinfo == NULL)
+ return (USB_ERR_NOMEM);
+
+ return (0); /* success */
+}
+
+usb_error_t
+usbd_add_dynamic_quirk(struct usb_device *udev, uint16_t quirk)
+{
+ uint8_t x;
+
+ for (x = 0; x != USB_MAX_AUTO_QUIRK; x++) {
+ if (udev->autoQuirk[x] == 0 ||
+ udev->autoQuirk[x] == quirk) {
+ udev->autoQuirk[x] = quirk;
+ return (0); /* success */
+ }
+ }
+ return (USB_ERR_NOMEM);
+}
+
+/*
+ * The following function is used to select the endpoint mode. It
+ * should not be called outside enumeration context.
+ */
+
+usb_error_t
+usbd_set_endpoint_mode(struct usb_device *udev, struct usb_endpoint *ep,
+ uint8_t ep_mode)
+{
+ usb_error_t error;
+ uint8_t do_unlock;
+
+ /* Prevent re-enumeration */
+ do_unlock = usbd_enum_lock(udev);
+
+ if (udev->bus->methods->set_endpoint_mode != NULL) {
+ error = (udev->bus->methods->set_endpoint_mode) (
+ udev, ep, ep_mode);
+ } else if (ep_mode != USB_EP_MODE_DEFAULT) {
+ error = USB_ERR_INVAL;
+ } else {
+ error = 0;
+ }
+
+ /* only set new mode regardless of error */
+ ep->ep_mode = ep_mode;
+
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+ return (error);
+}
+
+uint8_t
+usbd_get_endpoint_mode(struct usb_device *udev, struct usb_endpoint *ep)
+{
+ return (ep->ep_mode);
+}
diff --git a/sys/dev/usb/usb_device.h b/sys/dev/usb/usb_device.h
new file mode 100644
index 000000000000..87596cc1d2bd
--- /dev/null
+++ b/sys/dev/usb/usb_device.h
@@ -0,0 +1,350 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008-2019 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_DEVICE_H_
+#define _USB_DEVICE_H_
+
+#ifndef USB_GLOBAL_INCLUDE_FILE
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_transfer.h>
+#endif
+
+struct usb_bus_methods;
+struct usb_config_descriptor;
+struct usb_device; /* linux compat */
+struct usb_fs_privdata;
+struct usb_hw_ep_profile;
+struct usb_symlink; /* UGEN */
+
+#define USB_CTRL_XFER_MAX 2
+
+/* "usb_config_parse()" commands */
+
+#define USB_CFG_ALLOC 0
+#define USB_CFG_FREE 1
+#define USB_CFG_INIT 2
+
+/* "usb_unconfigure()" flags */
+
+#define USB_UNCFG_FLAG_NONE 0x00
+#define USB_UNCFG_FLAG_FREE_EP0 0x02 /* endpoint zero is freed */
+
+struct usb_udev_msg {
+ struct usb_proc_msg hdr;
+ struct usb_device *udev;
+};
+
+/* The following four structures makes up a tree, where we have the
+ * leaf structure, "usb_host_endpoint", first, and the root structure,
+ * "usb_device", last. The four structures below mirror the structure
+ * of the USB descriptors belonging to an USB configuration. Please
+ * refer to the USB specification for a definition of "endpoints" and
+ * "interfaces".
+ */
+struct usb_host_endpoint {
+ struct usb_endpoint_descriptor desc;
+ TAILQ_HEAD(, urb) bsd_urb_list;
+ struct usb_xfer *bsd_xfer[2];
+ uint8_t *extra; /* Extra descriptors */
+ usb_frlength_t fbsd_buf_size;
+ uint16_t extralen;
+ uint8_t bsd_iface_index;
+} __aligned(USB_HOST_ALIGN);
+
+struct usb_host_interface {
+ struct usb_interface_descriptor desc;
+ /* the following array has size "desc.bNumEndpoint" */
+ struct usb_host_endpoint *endpoint;
+ const char *string; /* iInterface string, if present */
+ uint8_t *extra; /* Extra descriptors */
+ uint16_t extralen;
+ uint8_t bsd_iface_index;
+} __aligned(USB_HOST_ALIGN);
+
+/*
+ * The following structure defines the USB device flags.
+ */
+struct usb_device_flags {
+ enum usb_hc_mode usb_mode; /* host or device mode */
+ uint8_t self_powered:1; /* set if USB device is self powered */
+ uint8_t no_strings:1; /* set if USB device does not support
+ * strings */
+ uint8_t remote_wakeup:1; /* set if remote wakeup is enabled */
+ uint8_t uq_bus_powered:1; /* set if BUS powered quirk is present */
+
+ /*
+ * NOTE: Although the flags below will reach the same value
+ * over time, but the instant values may differ, and
+ * consequently the flags cannot be merged into one!
+ */
+ uint8_t peer_suspended:1; /* set if peer is suspended */
+ uint8_t self_suspended:1; /* set if self is suspended */
+};
+
+/*
+ * The following structure is used for power-save purposes. The data
+ * in this structure is protected by the USB BUS lock.
+ */
+struct usb_power_save {
+ usb_ticks_t last_xfer_time; /* copy of "ticks" */
+ usb_size_t type_refs[4]; /* transfer reference count */
+ usb_size_t read_refs; /* data read references */
+ usb_size_t write_refs; /* data write references */
+};
+
+/*
+ * The following structure is used when trying to allocate hardware
+ * endpoints for an USB configuration in USB device side mode.
+ */
+struct usb_hw_ep_scratch_sub {
+ const struct usb_hw_ep_profile *pf;
+ uint16_t max_frame_size;
+ uint8_t hw_endpoint_out;
+ uint8_t hw_endpoint_in;
+ uint8_t needs_ep_type;
+ uint8_t needs_in:1;
+ uint8_t needs_out:1;
+};
+
+/*
+ * The following structure is used when trying to allocate hardware
+ * endpoints for an USB configuration in USB device side mode.
+ */
+struct usb_hw_ep_scratch {
+ struct usb_hw_ep_scratch_sub ep[USB_EP_MAX];
+ struct usb_hw_ep_scratch_sub *ep_max;
+ struct usb_config_descriptor *cd;
+ struct usb_device *udev;
+ const struct usb_bus_methods *methods;
+ uint8_t bmOutAlloc[(USB_EP_MAX + 15) / 16];
+ uint8_t bmInAlloc[(USB_EP_MAX + 15) / 16];
+};
+
+/*
+ * The following structure is used when generating USB descriptors
+ * from USB templates.
+ */
+struct usb_temp_setup {
+ void *buf;
+ usb_size_t size;
+ enum usb_dev_speed usb_speed;
+ uint8_t self_powered;
+ uint8_t bNumEndpoints;
+ uint8_t bInterfaceNumber;
+ uint8_t bAlternateSetting;
+ uint8_t bConfigurationValue;
+ usb_error_t err;
+};
+
+/*
+ * The scratch area for USB devices. Access to this structure is
+ * protected by the control SX lock.
+ */
+union usb_device_scratch {
+ struct usb_hw_ep_scratch hw_ep_scratch[1];
+ struct usb_temp_setup temp_setup[1];
+ struct {
+ struct usb_xfer dummy;
+ struct usb_setup_params parm;
+ } xfer_setup[1];
+ uint8_t data[255];
+};
+
+/*
+ * Helper structure to keep track of USB device statistics.
+ */
+struct usb_device_statistics {
+ uint32_t uds_requests[4];
+};
+
+/*
+ * The following structure defines an USB device. There exists one of
+ * these structures for every USB device.
+ */
+struct usb_device {
+ /* statistics */
+ struct usb_device_statistics stats_err;
+ struct usb_device_statistics stats_ok;
+ struct usb_device_statistics stats_cancelled;
+
+ /* generic clear stall message */
+ struct usb_udev_msg cs_msg[2];
+ struct sx enum_sx;
+ struct sx sr_sx;
+ struct sx ctrl_sx;
+ struct mtx device_mtx;
+ struct cv ctrlreq_cv;
+ struct cv ref_cv;
+#if (USB_HAVE_FIXED_IFACE == 0)
+ struct usb_interface *ifaces;
+#else
+ struct usb_interface ifaces[USB_IFACE_MAX];
+#endif
+ struct usb_endpoint ctrl_ep; /* Control Endpoint 0 */
+#if (USB_HAVE_FIXED_ENDPOINT == 0)
+ struct usb_endpoint *endpoints;
+#else
+ struct usb_endpoint endpoints[USB_MAX_EP_UNITS];
+#endif
+ struct usb_power_save pwr_save;/* power save data */
+ struct usb_bus *bus; /* our USB BUS */
+ device_t parent_dev; /* parent device */
+ struct usb_device *parent_hub;
+ struct usb_device *parent_hs_hub; /* high-speed parent HUB */
+ struct usb_config_descriptor *cdesc; /* full config descr */
+ struct usb_hub *hub; /* only if this is a hub */
+ struct usb_xfer *ctrl_xfer[USB_CTRL_XFER_MAX];
+ struct usb_temp_data *usb_template_ptr;
+ struct usb_endpoint *ep_curr; /* current clear stall endpoint */
+#if USB_HAVE_UGEN
+ struct usb_fifo *fifo[USB_FIFO_MAX];
+ struct usb_symlink *ugen_symlink; /* our generic symlink */
+ struct usb_fs_privdata *ctrl_dev; /* Control Endpoint 0 device node */
+ SLIST_HEAD(,usb_fs_privdata) pd_list;
+ char ugen_name[20]; /* name of ugenX.X device */
+#endif
+ usb_ticks_t plugtime; /* copy of "ticks" */
+
+ enum usb_dev_state state;
+ enum usb_dev_speed speed;
+ uint16_t refcount;
+#define USB_DEV_REF_MAX 0xffff
+
+ uint16_t power; /* mA the device uses */
+ uint16_t langid; /* language for strings */
+ uint16_t autoQuirk[USB_MAX_AUTO_QUIRK]; /* dynamic quirks */
+
+ uint8_t address; /* device addess */
+ uint8_t device_index; /* device index in "bus->devices" */
+ uint8_t controller_slot_id; /* controller specific value */
+ uint8_t next_config_index; /* used by USB_RE_ENUM_SET_CONFIG */
+ uint8_t curr_config_index; /* current configuration index */
+ uint8_t curr_config_no; /* current configuration number */
+ uint8_t depth; /* distance from root HUB */
+ uint8_t port_index; /* parent HUB port index */
+ uint8_t port_no; /* parent HUB port number */
+ uint8_t hs_hub_addr; /* high-speed HUB address */
+ uint8_t hs_port_no; /* high-speed HUB port number */
+ uint8_t driver_added_refcount; /* our driver added generation count */
+ uint8_t power_mode; /* see USB_POWER_XXX */
+ uint8_t re_enumerate_wait; /* set if re-enum. is in progress */
+#define USB_RE_ENUM_DONE 0
+#define USB_RE_ENUM_START 1
+#define USB_RE_ENUM_PWR_OFF 2
+#define USB_RE_ENUM_SET_CONFIG 3
+ uint8_t ifaces_max; /* number of interfaces present */
+ uint8_t endpoints_max; /* number of endpoints present */
+
+ /* the "flags" field is write-protected by "bus->mtx" */
+
+ struct usb_device_flags flags;
+
+ struct usb_endpoint_descriptor ctrl_ep_desc; /* for endpoint 0 */
+ struct usb_endpoint_ss_comp_descriptor ctrl_ep_comp_desc; /* for endpoint 0 */
+ struct usb_device_descriptor ddesc; /* device descriptor */
+
+ char *serial; /* serial number, can be NULL */
+ char *manufacturer; /* manufacturer string, can be NULL */
+ char *product; /* product string, can be NULL */
+
+#if USB_HAVE_COMPAT_LINUX
+ /* Linux compat */
+ struct usb_device_descriptor descriptor;
+ struct usb_host_endpoint ep0;
+ struct usb_interface *linux_iface_start;
+ struct usb_interface *linux_iface_end;
+ struct usb_host_endpoint *linux_endpoint_start;
+ struct usb_host_endpoint *linux_endpoint_end;
+ uint16_t devnum;
+#endif
+
+ uint32_t clear_stall_errors; /* number of clear-stall failures */
+
+ union usb_device_scratch scratch;
+
+#if (USB_HAVE_FIXED_CONFIG != 0)
+ uint32_t config_data[(USB_CONFIG_MAX + 3) / 4];
+#endif
+};
+
+/* globals */
+
+extern int usb_template;
+
+/* function prototypes */
+
+const char *usb_statestr(enum usb_dev_state state);
+struct usb_device *usb_alloc_device(device_t parent_dev, struct usb_bus *bus,
+ struct usb_device *parent_hub, uint8_t depth,
+ uint8_t port_index, uint8_t port_no,
+ enum usb_dev_speed speed, enum usb_hc_mode mode);
+#if USB_HAVE_UGEN
+struct usb_fs_privdata *usb_make_dev(struct usb_device *, const char *,
+ int, int, int, uid_t, gid_t, int);
+void usb_destroy_dev(struct usb_fs_privdata *);
+void usb_destroy_dev_sync(struct usb_fs_privdata *);
+#endif
+usb_error_t usb_probe_and_attach(struct usb_device *udev,
+ uint8_t iface_index);
+void usb_detach_device(struct usb_device *, uint8_t, uint8_t);
+usb_error_t usb_reset_iface_endpoints(struct usb_device *udev,
+ uint8_t iface_index);
+usb_error_t usbd_set_config_index(struct usb_device *udev, uint8_t index);
+usb_error_t usbd_set_endpoint_stall(struct usb_device *udev,
+ struct usb_endpoint *ep, uint8_t do_stall);
+usb_error_t usb_suspend_resume(struct usb_device *udev,
+ uint8_t do_suspend);
+void usb_devinfo(struct usb_device *udev, char *dst_ptr, uint16_t dst_len);
+void usb_free_device(struct usb_device *, uint8_t);
+void usb_linux_free_device(struct usb_device *dev);
+uint8_t usb_peer_can_wakeup(struct usb_device *udev);
+struct usb_endpoint *usb_endpoint_foreach(struct usb_device *udev, struct usb_endpoint *ep);
+void usb_set_device_state(struct usb_device *, enum usb_dev_state);
+enum usb_dev_state usb_get_device_state(struct usb_device *);
+
+void usb_set_device_strings(struct usb_device *);
+void usb_get_langid(struct usb_device *);
+
+uint8_t usbd_enum_lock(struct usb_device *);
+#if USB_HAVE_UGEN
+uint8_t usbd_enum_lock_sig(struct usb_device *);
+#endif
+void usbd_enum_unlock(struct usb_device *);
+void usbd_sr_lock(struct usb_device *);
+void usbd_sr_unlock(struct usb_device *);
+uint8_t usbd_ctrl_lock(struct usb_device *);
+void usbd_ctrl_unlock(struct usb_device *);
+uint8_t usbd_enum_is_locked(struct usb_device *);
+
+#if USB_HAVE_TT_SUPPORT
+void uhub_tt_buffer_reset_async_locked(struct usb_device *, struct usb_endpoint *);
+#endif
+
+uint8_t uhub_count_active_host_ports(struct usb_device *, enum usb_dev_speed);
+
+#endif /* _USB_DEVICE_H_ */
diff --git a/sys/dev/usb/usb_dynamic.c b/sys/dev/usb/usb_dynamic.c
new file mode 100644
index 000000000000..b9ec83dc78e9
--- /dev/null
+++ b/sys/dev/usb/usb_dynamic.c
@@ -0,0 +1,180 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_dynamic.h>
+#include <dev/usb/usb_request.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+/* function prototypes */
+static usb_handle_req_t usb_temp_get_desc_w;
+static usb_temp_setup_by_index_t usb_temp_setup_by_index_w;
+#if USB_HAVE_COMPAT_LINUX
+static usb_linux_free_device_t usb_linux_free_device_w;
+#endif
+static usb_temp_unsetup_t usb_temp_unsetup_w;
+static usb_test_quirk_t usb_test_quirk_w;
+static usb_quirk_ioctl_t usb_quirk_ioctl_w;
+
+/* global variables */
+usb_handle_req_t *usb_temp_get_desc_p = &usb_temp_get_desc_w;
+usb_temp_setup_by_index_t *usb_temp_setup_by_index_p = &usb_temp_setup_by_index_w;
+#if USB_HAVE_COMPAT_LINUX
+usb_linux_free_device_t *usb_linux_free_device_p = &usb_linux_free_device_w;
+#endif
+usb_temp_unsetup_t *usb_temp_unsetup_p = &usb_temp_unsetup_w;
+usb_test_quirk_t *usb_test_quirk_p = &usb_test_quirk_w;
+usb_quirk_ioctl_t *usb_quirk_ioctl_p = &usb_quirk_ioctl_w;
+devclass_t usb_devclass_ptr;
+
+static usb_error_t
+usb_temp_setup_by_index_w(struct usb_device *udev, uint16_t index)
+{
+ return (USB_ERR_INVAL);
+}
+
+static uint8_t
+usb_test_quirk_w(const struct usbd_lookup_info *info, uint16_t quirk)
+{
+ return (0); /* no match */
+}
+
+static int
+usb_quirk_ioctl_w(unsigned long cmd, caddr_t data, int fflag, struct thread *td)
+{
+ return (ENOIOCTL);
+}
+
+static usb_error_t
+usb_temp_get_desc_w(struct usb_device *udev, struct usb_device_request *req, const void **pPtr, uint16_t *pLength)
+{
+ /* stall */
+ return (USB_ERR_STALLED);
+}
+
+static void
+usb_temp_unsetup_w(struct usb_device *udev)
+{
+ usbd_free_config_desc(udev, udev->usb_template_ptr);
+ udev->usb_template_ptr = NULL;
+}
+
+#if USB_HAVE_COMPAT_LINUX
+static void
+usb_linux_free_device_w(struct usb_device *udev)
+{
+ /* NOP */
+}
+#endif
+
+void
+usb_quirk_unload(void *arg)
+{
+ /* reset function pointers */
+
+ usb_test_quirk_p = &usb_test_quirk_w;
+ usb_quirk_ioctl_p = &usb_quirk_ioctl_w;
+
+ /* wait for CPU to exit the loaded functions, if any */
+
+ /* XXX this is a tradeoff */
+
+ pause("WAIT", hz);
+}
+
+void
+usb_temp_unload(void *arg)
+{
+ /* reset function pointers */
+
+ usb_temp_get_desc_p = &usb_temp_get_desc_w;
+ usb_temp_setup_by_index_p = &usb_temp_setup_by_index_w;
+ usb_temp_unsetup_p = &usb_temp_unsetup_w;
+
+ /* wait for CPU to exit the loaded functions, if any */
+
+ /* XXX this is a tradeoff */
+
+ pause("WAIT", hz);
+}
+
+void
+usb_bus_unload(void *arg)
+{
+ /* reset function pointers */
+
+ usb_devclass_ptr = NULL;
+
+ /* wait for CPU to exit the loaded functions, if any */
+
+ /* XXX this is a tradeoff */
+
+ pause("WAIT", hz);
+}
+
+#if USB_HAVE_COMPAT_LINUX
+void
+usb_linux_unload(void *arg)
+{
+ /* reset function pointers */
+
+ usb_linux_free_device_p = &usb_linux_free_device_w;
+
+ /* wait for CPU to exit the loaded functions, if any */
+
+ /* XXX this is a tradeoff */
+
+ pause("WAIT", hz);
+}
+#endif
diff --git a/sys/dev/usb/usb_dynamic.h b/sys/dev/usb/usb_dynamic.h
new file mode 100644
index 000000000000..15327f59af79
--- /dev/null
+++ b/sys/dev/usb/usb_dynamic.h
@@ -0,0 +1,65 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_DYNAMIC_H_
+#define _USB_DYNAMIC_H_
+
+/* prototypes */
+
+struct usb_device;
+struct usbd_lookup_info;
+struct usb_device_request;
+
+/* typedefs */
+
+typedef usb_error_t (usb_temp_setup_by_index_t)(struct usb_device *udev,
+ uint16_t index);
+typedef uint8_t (usb_test_quirk_t)(const struct usbd_lookup_info *info,
+ uint16_t quirk);
+typedef int (usb_quirk_ioctl_t)(unsigned long cmd, caddr_t data,
+ int fflag, struct thread *td);
+typedef void (usb_temp_unsetup_t)(struct usb_device *udev);
+typedef void (usb_linux_free_device_t)(struct usb_device *udev);
+
+/* global function pointers */
+
+extern usb_handle_req_t *usb_temp_get_desc_p;
+extern usb_temp_setup_by_index_t *usb_temp_setup_by_index_p;
+extern usb_linux_free_device_t *usb_linux_free_device_p;
+extern usb_temp_unsetup_t *usb_temp_unsetup_p;
+extern usb_test_quirk_t *usb_test_quirk_p;
+extern usb_quirk_ioctl_t *usb_quirk_ioctl_p;
+extern devclass_t usb_devclass_ptr;
+
+/* function prototypes */
+
+void usb_linux_unload(void *);
+void usb_temp_unload(void *);
+void usb_quirk_unload(void *);
+void usb_bus_unload(void *);
+
+#endif /* _USB_DYNAMIC_H_ */
diff --git a/sys/dev/usb/usb_endian.h b/sys/dev/usb/usb_endian.h
new file mode 100644
index 000000000000..a9fcf06c3b40
--- /dev/null
+++ b/sys/dev/usb/usb_endian.h
@@ -0,0 +1,122 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_ENDIAN_H_
+#define _USB_ENDIAN_H_
+
+#ifndef USB_GLOBAL_INCLUDE_FILE
+#include <sys/stdint.h>
+#include <sys/endian.h>
+#endif
+
+/*
+ * Declare the basic USB record types. USB records have an alignment
+ * of 1 byte and are always packed.
+ */
+typedef uint8_t uByte;
+typedef uint8_t uWord[2];
+typedef uint8_t uDWord[4];
+typedef uint8_t uQWord[8];
+
+/*
+ * Define a set of macros that can get and set data independent of
+ * CPU endianness and CPU alignment requirements:
+ */
+#define UGETB(w) \
+ ((w)[0])
+
+#define UGETW(w) \
+ ((w)[0] | \
+ (((uint16_t)((w)[1])) << 8))
+
+#define UGETDW(w) \
+ ((w)[0] | \
+ (((uint16_t)((w)[1])) << 8) | \
+ (((uint32_t)((w)[2])) << 16) | \
+ (((uint32_t)((w)[3])) << 24))
+
+#define UGETQW(w) \
+ ((w)[0] | \
+ (((uint16_t)((w)[1])) << 8) | \
+ (((uint32_t)((w)[2])) << 16) | \
+ (((uint32_t)((w)[3])) << 24) | \
+ (((uint64_t)((w)[4])) << 32) | \
+ (((uint64_t)((w)[5])) << 40) | \
+ (((uint64_t)((w)[6])) << 48) | \
+ (((uint64_t)((w)[7])) << 56))
+
+#define USETB(w,v) do { \
+ (w)[0] = (uint8_t)(v); \
+} while (0)
+
+#define USETW(w,v) do { \
+ (w)[0] = (uint8_t)(v); \
+ (w)[1] = (uint8_t)((v) >> 8); \
+} while (0)
+
+#define USETDW(w,v) do { \
+ (w)[0] = (uint8_t)(v); \
+ (w)[1] = (uint8_t)((v) >> 8); \
+ (w)[2] = (uint8_t)((v) >> 16); \
+ (w)[3] = (uint8_t)((v) >> 24); \
+} while (0)
+
+#define USETQW(w,v) do { \
+ (w)[0] = (uint8_t)(v); \
+ (w)[1] = (uint8_t)((v) >> 8); \
+ (w)[2] = (uint8_t)((v) >> 16); \
+ (w)[3] = (uint8_t)((v) >> 24); \
+ (w)[4] = (uint8_t)((v) >> 32); \
+ (w)[5] = (uint8_t)((v) >> 40); \
+ (w)[6] = (uint8_t)((v) >> 48); \
+ (w)[7] = (uint8_t)((v) >> 56); \
+} while (0)
+
+#define USETW2(w,b1,b0) do { \
+ (w)[0] = (uint8_t)(b0); \
+ (w)[1] = (uint8_t)(b1); \
+} while (0)
+
+#define USETW4(w,b3,b2,b1,b0) do { \
+ (w)[0] = (uint8_t)(b0); \
+ (w)[1] = (uint8_t)(b1); \
+ (w)[2] = (uint8_t)(b2); \
+ (w)[3] = (uint8_t)(b3); \
+} while (0)
+
+#define USETW8(w,b7,b6,b5,b4,b3,b2,b1,b0) do { \
+ (w)[0] = (uint8_t)(b0); \
+ (w)[1] = (uint8_t)(b1); \
+ (w)[2] = (uint8_t)(b2); \
+ (w)[3] = (uint8_t)(b3); \
+ (w)[4] = (uint8_t)(b4); \
+ (w)[5] = (uint8_t)(b5); \
+ (w)[6] = (uint8_t)(b6); \
+ (w)[7] = (uint8_t)(b7); \
+} while (0)
+
+#endif /* _USB_ENDIAN_H_ */
diff --git a/sys/dev/usb/usb_error.c b/sys/dev/usb/usb_error.c
new file mode 100644
index 000000000000..7d961c58b9f7
--- /dev/null
+++ b/sys/dev/usb/usb_error.c
@@ -0,0 +1,95 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+static const char* usb_errstr_table[USB_ERR_MAX] = {
+ [USB_ERR_NORMAL_COMPLETION] = "USB_ERR_NORMAL_COMPLETION",
+ [USB_ERR_PENDING_REQUESTS] = "USB_ERR_PENDING_REQUESTS",
+ [USB_ERR_NOT_STARTED] = "USB_ERR_NOT_STARTED",
+ [USB_ERR_INVAL] = "USB_ERR_INVAL",
+ [USB_ERR_NOMEM] = "USB_ERR_NOMEM",
+ [USB_ERR_CANCELLED] = "USB_ERR_CANCELLED",
+ [USB_ERR_BAD_ADDRESS] = "USB_ERR_BAD_ADDRESS",
+ [USB_ERR_BAD_BUFSIZE] = "USB_ERR_BAD_BUFSIZE",
+ [USB_ERR_BAD_FLAG] = "USB_ERR_BAD_FLAG",
+ [USB_ERR_NO_CALLBACK] = "USB_ERR_NO_CALLBACK",
+ [USB_ERR_IN_USE] = "USB_ERR_IN_USE",
+ [USB_ERR_NO_ADDR] = "USB_ERR_NO_ADDR",
+ [USB_ERR_NO_PIPE] = "USB_ERR_NO_PIPE",
+ [USB_ERR_ZERO_NFRAMES] = "USB_ERR_ZERO_NFRAMES",
+ [USB_ERR_ZERO_MAXP] = "USB_ERR_ZERO_MAXP",
+ [USB_ERR_SET_ADDR_FAILED] = "USB_ERR_SET_ADDR_FAILED",
+ [USB_ERR_NO_POWER] = "USB_ERR_NO_POWER",
+ [USB_ERR_TOO_DEEP] = "USB_ERR_TOO_DEEP",
+ [USB_ERR_IOERROR] = "USB_ERR_IOERROR",
+ [USB_ERR_NOT_CONFIGURED] = "USB_ERR_NOT_CONFIGURED",
+ [USB_ERR_TIMEOUT] = "USB_ERR_TIMEOUT",
+ [USB_ERR_SHORT_XFER] = "USB_ERR_SHORT_XFER",
+ [USB_ERR_STALLED] = "USB_ERR_STALLED",
+ [USB_ERR_INTERRUPTED] = "USB_ERR_INTERRUPTED",
+ [USB_ERR_DMA_LOAD_FAILED] = "USB_ERR_DMA_LOAD_FAILED",
+ [USB_ERR_BAD_CONTEXT] = "USB_ERR_BAD_CONTEXT",
+ [USB_ERR_NO_ROOT_HUB] = "USB_ERR_NO_ROOT_HUB",
+ [USB_ERR_NO_INTR_THREAD] = "USB_ERR_NO_INTR_THREAD",
+ [USB_ERR_NOT_LOCKED] = "USB_ERR_NOT_LOCKED",
+};
+
+/*------------------------------------------------------------------------*
+ * usbd_errstr
+ *
+ * This function converts an USB error code into a string.
+ *------------------------------------------------------------------------*/
+const char *
+usbd_errstr(usb_error_t err)
+{
+ return (err < USB_ERR_MAX ? usb_errstr_table[err] : "USB_ERR_UNKNOWN");
+}
diff --git a/sys/dev/usb/usb_fdt_support.c b/sys/dev/usb/usb_fdt_support.c
new file mode 100644
index 000000000000..5e84e8bbb388
--- /dev/null
+++ b/sys/dev/usb/usb_fdt_support.c
@@ -0,0 +1,165 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Ian Lepore <ian@FreeBSD.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.
+ *
+ * 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/types.h>
+#include <sys/condvar.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/ofw/openfirm.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_freebsd.h>
+#include <dev/usb/usb_fdt_support.h>
+#include <dev/usb/net/usb_ethernet.h>
+
+/*
+ * Define a constant for allocating an array pointers to serve as a stack of
+ * devices between the controller and any arbitrary device on the bus. The
+ * stack ends with the device itself, so add 1 to the max hub nesting depth.
+ */
+#define MAX_UDEV_NEST (MAX(USB_HUB_MAX_DEPTH, USB_SS_HUB_DEPTH_MAX) + 1)
+
+static phandle_t
+find_udev_in_children(phandle_t parent, struct usb_device *udev)
+{
+ phandle_t child;
+ ssize_t proplen;
+ uint32_t port;
+ char compat[16]; /* big enough for "usb1234,abcd" */
+
+ /*
+ * USB device nodes in FDT have a compatible string of "usb" followed by
+ * the vendorId,productId rendered in hex. The port number is encoded
+ * in the standard 'reg' property; it is one-based in the FDT data, but
+ * usb_device.port_index is zero-based. To uniquely identify a device,
+ * both the compatible string and the port number must match.
+ */
+ snprintf(compat, sizeof(compat), "usb%x,%x",
+ UGETW(udev->ddesc.idVendor), UGETW(udev->ddesc.idProduct));
+ for (child = OF_child(parent); child != 0; child = OF_peer(child)) {
+ if (!ofw_bus_node_is_compatible(child, compat))
+ continue;
+ proplen = OF_getencprop(child, "reg", &port, sizeof(port));
+ if (proplen != sizeof(port))
+ continue;
+ if (port == (udev->port_index + 1))
+ return (child);
+ }
+ return (-1);
+}
+
+static bool
+is_valid_mac_addr(uint8_t *addr)
+{
+
+ /*
+ * All-bits-zero and all-bits-one are a couple common cases of what
+ * might get read from unprogrammed eeprom or OTP data, weed them out.
+ */
+ if ((addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]) == 0x00)
+ return (false);
+ if ((addr[0] & addr[1] & addr[2] & addr[3] & addr[4] & addr[5]) == 0xff)
+ return (false);
+ return (true);
+}
+
+int
+usb_fdt_get_mac_addr(device_t dev, struct usb_ether* ue)
+{
+ phandle_t node;
+ ssize_t i, proplen;
+ uint8_t mac[sizeof(ue->ue_eaddr)];
+ static const char *properties[] = {
+ "mac-address",
+ "local-mac-address"
+ };
+
+ if ((node = usb_fdt_get_node(ue->ue_dev, ue->ue_udev)) == -1)
+ return (ENXIO);
+ for (i = 0; i < nitems(properties); ++i) {
+ proplen = OF_getprop(node, properties[i], mac, sizeof(mac));
+ if (proplen == sizeof(mac) && is_valid_mac_addr(mac)) {
+ memcpy(ue->ue_eaddr, mac, sizeof(ue->ue_eaddr));
+ return (0);
+ }
+ }
+ return (ENXIO);
+}
+
+phandle_t
+usb_fdt_get_node(device_t dev, struct usb_device *udev)
+{
+ struct usb_device *ud;
+ struct usb_device *udev_stack[MAX_UDEV_NEST];
+ phandle_t controller_node, node;
+ int idx;
+
+ /*
+ * Start searching at the controller node. The usb_device links to the
+ * bus, and its parent is the controller. If we can't get the
+ * controller node, the requesting device cannot be in the fdt data.
+ */
+ if ((controller_node = ofw_bus_get_node(udev->bus->parent)) == -1)
+ return (-1);
+
+ /*
+ * Walk up the usb hub ancestor hierarchy, building a stack of devices
+ * that begins with the requesting device and includes all the hubs
+ * between it and the controller, NOT including the root hub (the FDT
+ * bindings treat the controller and root hub as the same thing).
+ */
+ for (ud = udev, idx = 0; ud->parent_hub != NULL; ud = ud->parent_hub) {
+ KASSERT(idx < nitems(udev_stack), ("Too many hubs"));
+ udev_stack[idx++] = ud;
+ }
+
+ /*
+ * Now walk down the stack of udevs from the controller to the
+ * requesting device, and also down the hierarchy of nested children of
+ * the controller node in the fdt data. At each nesting level of fdt
+ * data look for a child node whose properties match the vID,pID,portIdx
+ * tuple for the udev at the corresponding layer of the udev stack. As
+ * long as we keep matching up child nodes with udevs, loop and search
+ * within the children of the just-found child for the next-deepest hub.
+ * If at any level we fail to find a matching node, stop searching and
+ * return. When we hit the end of the stack (the requesting device) we
+ * return whatever the result was for the search at that nesting level.
+ */
+ for (node = controller_node;;) {
+ node = find_udev_in_children(node, udev_stack[--idx]);
+ if (idx == 0 || node == -1)
+ break;
+ }
+ return (node);
+}
diff --git a/sys/dev/usb/usb_fdt_support.h b/sys/dev/usb/usb_fdt_support.h
new file mode 100644
index 000000000000..c901d6859e24
--- /dev/null
+++ b/sys/dev/usb/usb_fdt_support.h
@@ -0,0 +1,46 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Ian Lepore <ian@FreeBSD.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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_FDT_SUPPORT_H_
+#define _USB_FDT_SUPPORT_H_
+
+struct usb_device;
+struct usb_ether;
+
+/*
+ * Get the device's MAC address from the FDT data. Fills in ue->ue_eaddr and
+ * returns 0 on success, otherwise leaves ue_eaddr untouched and returns
+ * non-zero. This first attempts to get the address from the "mac-address"
+ * property, and if that's not valid it tries the "local-mac-address" property;
+ * this matches the linux interpretation of the precedence of those properties.
+ */
+int usb_fdt_get_mac_addr(device_t dev, struct usb_ether* ue);
+
+/* Get the FDT node for dev. Returns -1 if dev is not in the FDT data. */
+phandle_t usb_fdt_get_node(device_t dev, struct usb_device* udev);
+
+#endif
diff --git a/sys/dev/usb/usb_freebsd.h b/sys/dev/usb/usb_freebsd.h
new file mode 100644
index 000000000000..02ae6b245134
--- /dev/null
+++ b/sys/dev/usb/usb_freebsd.h
@@ -0,0 +1,107 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Including this file is mandatory for all USB related c-files in the kernel.
+ */
+
+#ifndef _USB_FREEBSD_H_
+#define _USB_FREEBSD_H_
+
+/* Default USB configuration */
+#define USB_HAVE_UGEN 1
+#define USB_HAVE_DEVCTL 1
+#define USB_HAVE_BUSDMA 1
+#define USB_HAVE_COMPAT_LINUX 1
+#define USB_HAVE_USER_IO 1
+#define USB_HAVE_MBUF 1
+#define USB_HAVE_TT_SUPPORT 1
+#define USB_HAVE_POWERD 1
+#define USB_HAVE_MSCTEST 1
+#define USB_HAVE_MSCTEST_AUTOQUIRK 0
+#define USB_HAVE_MSCTEST_DETACH 1
+#define USB_HAVE_PF 1
+#define USB_HAVE_ROOT_MOUNT_HOLD 1
+#define USB_HAVE_ID_SECTION 1
+#define USB_HAVE_PER_BUS_PROCESS 1
+#define USB_HAVE_FIXED_ENDPOINT 0
+#define USB_HAVE_FIXED_IFACE 0
+#define USB_HAVE_FIXED_CONFIG 0
+#define USB_HAVE_FIXED_PORT 0
+#define USB_HAVE_DISABLE_ENUM 1
+#define USB_HAVE_MALLOC_WAITOK 1
+
+/* define zero ticks callout value */
+#define USB_CALLOUT_ZERO_TICKS 1
+
+#define USB_TD_GET_PROC(td) (td)->td_proc
+#define USB_PROC_GET_GID(td) (td)->p_pgid
+
+#if (!defined(USB_HOST_ALIGN)) || (USB_HOST_ALIGN <= 0)
+/* Use default value. */
+#undef USB_HOST_ALIGN
+#if defined(__arm__) || defined(__powerpc__)
+#define USB_HOST_ALIGN 32 /* Arm and PowerPC need at least this much, if not more */
+#else
+#define USB_HOST_ALIGN 8 /* bytes, must be power of two */
+#endif
+#endif
+/* Sanity check for USB_HOST_ALIGN: Verify power of two. */
+#if ((-USB_HOST_ALIGN) & USB_HOST_ALIGN) != USB_HOST_ALIGN
+#error "USB_HOST_ALIGN is not power of two."
+#endif
+#define USB_FS_ISOC_UFRAME_MAX 4 /* exclusive unit */
+#define USB_BUS_MAX 256 /* units */
+#define USB_MAX_DEVICES 128 /* units */
+#define USB_CONFIG_MAX 65535 /* bytes */
+#define USB_IFACE_MAX 32 /* units */
+#define USB_FIFO_MAX 128 /* units */
+#define USB_MAX_EP_STREAMS 8 /* units */
+#define USB_MAX_EP_UNITS 32 /* units */
+#define USB_MAX_PORTS 255 /* units */
+
+#define USB_MAX_FS_ISOC_FRAMES_PER_XFER (120) /* units */
+#define USB_MAX_HS_ISOC_FRAMES_PER_XFER (8*120) /* units */
+
+#define USB_HUB_MAX_DEPTH 5
+#define USB_EP0_BUFSIZE 1024 /* bytes */
+#define USB_CS_RESET_LIMIT 20 /* failures = 20 * 50 ms = 1sec */
+
+#define USB_MAX_AUTO_QUIRK 8 /* maximum number of dynamic quirks */
+
+#define USB_IN_POLLING_MODE_FUNC() usbd_in_polling_mode()
+#define USB_IN_POLLING_MODE_VALUE() (SCHEDULER_STOPPED() || kdb_active)
+
+typedef uint32_t usb_timeout_t; /* milliseconds */
+typedef uint32_t usb_frlength_t; /* bytes */
+typedef uint32_t usb_frcount_t; /* units */
+typedef uint32_t usb_size_t; /* bytes */
+typedef uint32_t usb_ticks_t; /* system defined */
+typedef uint16_t usb_power_mask_t; /* see "USB_HW_POWER_XXX" */
+typedef uint16_t usb_stream_t; /* stream ID */
+
+#endif /* _USB_FREEBSD_H_ */
diff --git a/sys/dev/usb/usb_freebsd_loader.h b/sys/dev/usb/usb_freebsd_loader.h
new file mode 100644
index 000000000000..edc6bcc9720c
--- /dev/null
+++ b/sys/dev/usb/usb_freebsd_loader.h
@@ -0,0 +1,102 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2013 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Including this file is mandatory for all USB related c-files in the loader.
+ */
+
+#ifndef _USB_FREEBSD_LOADER_H_
+#define _USB_FREEBSD_LOADER_H_
+
+/* Default USB configuration */
+#define USB_HAVE_UGEN 0
+#define USB_HAVE_DEVCTL 0
+#define USB_HAVE_BUSDMA 1
+#define USB_HAVE_COMPAT_LINUX 0
+#define USB_HAVE_USER_IO 0
+#define USB_HAVE_MBUF 0
+#define USB_HAVE_TT_SUPPORT 1
+#define USB_HAVE_POWERD 1
+#define USB_HAVE_MSCTEST 1
+#define USB_HAVE_MSCTEST_AUTOQUIRK 0
+#define USB_HAVE_MSCTEST_DETACH 0
+#define USB_HAVE_PF 0
+#define USB_HAVE_ROOT_MOUNT_HOLD 0
+#define USB_HAVE_ID_SECTION 0
+#define USB_HAVE_PER_BUS_PROCESS 0
+#define USB_HAVE_FIXED_ENDPOINT 0
+#define USB_HAVE_FIXED_IFACE 0
+#define USB_HAVE_FIXED_CONFIG 0
+#define USB_HAVE_FIXED_PORT 0
+#define USB_HAVE_DISABLE_ENUM 0
+#define USB_HAVE_MALLOC_WAITOK 0
+
+#define USB_CALLOUT_ZERO_TICKS 1
+
+#define USB_TD_GET_PROC(td) (td)->td_proc
+#define USB_PROC_GET_GID(td) (td)->p_pgid
+
+#if (!defined(USB_HOST_ALIGN)) || (USB_HOST_ALIGN <= 0)
+/* Use default value. */
+#undef USB_HOST_ALIGN
+#define USB_HOST_ALIGN 8 /* bytes, must be power of two */
+#endif
+/* Sanity check for USB_HOST_ALIGN: Verify power of two. */
+#if ((-USB_HOST_ALIGN) & USB_HOST_ALIGN) != USB_HOST_ALIGN
+#error "USB_HOST_ALIGN is not power of two."
+#endif
+#define USB_FS_ISOC_UFRAME_MAX 4 /* exclusive unit */
+#define USB_BUS_MAX 256 /* units */
+#define USB_MAX_DEVICES 128 /* units */
+#define USB_CONFIG_MAX 65535 /* bytes */
+#define USB_IFACE_MAX 32 /* units */
+#define USB_FIFO_MAX 128 /* units */
+#define USB_MAX_EP_UNITS 32 /* units */
+#define USB_MAX_EP_STREAMS 8 /* units */
+#define USB_MAX_PORTS 255 /* units */
+
+#define USB_MAX_FS_ISOC_FRAMES_PER_XFER (120) /* units */
+#define USB_MAX_HS_ISOC_FRAMES_PER_XFER (8*120) /* units */
+
+#define USB_HUB_MAX_DEPTH 5
+#define USB_EP0_BUFSIZE 1024 /* bytes */
+#define USB_CS_RESET_LIMIT 20 /* failures = 20 * 50 ms = 1sec */
+
+#define USB_MAX_AUTO_QUIRK 8 /* maximum number of dynamic quirks */
+
+#define USB_IN_POLLING_MODE_FUNC() 0
+#define USB_IN_POLLING_MODE_VALUE() 0
+
+typedef uint32_t usb_timeout_t; /* milliseconds */
+typedef uint32_t usb_frlength_t; /* bytes */
+typedef uint32_t usb_frcount_t; /* units */
+typedef uint32_t usb_size_t; /* bytes */
+typedef uint32_t usb_ticks_t; /* system defined */
+typedef uint16_t usb_power_mask_t; /* see "USB_HW_POWER_XXX" */
+typedef uint16_t usb_stream_t; /* stream ID */
+
+#endif /* _USB_FREEBSD_LOADER_H_ */
diff --git a/sys/dev/usb/usb_generic.c b/sys/dev/usb/usb_generic.c
new file mode 100644
index 000000000000..c0af27d77e5d
--- /dev/null
+++ b/sys/dev/usb/usb_generic.c
@@ -0,0 +1,2570 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008-2023 Hans Petter Selasky
+ *
+ * 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.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#ifdef COMPAT_FREEBSD32
+#include <sys/abi_compat.h>
+#endif
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usb_ioctl.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+
+#define USB_DEBUG_VAR ugen_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_dev.h>
+#include <dev/usb/usb_mbuf.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_request.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_generic.h>
+#include <dev/usb/usb_transfer.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#if USB_HAVE_UGEN
+
+/* defines */
+
+#define UGEN_BULK_FS_BUFFER_SIZE (64*32) /* bytes */
+#define UGEN_BULK_HS_BUFFER_SIZE (1024*32) /* bytes */
+#define UGEN_HW_FRAMES 50 /* number of milliseconds per transfer */
+
+/* function prototypes */
+
+static usb_callback_t ugen_read_clear_stall_callback;
+static usb_callback_t ugen_write_clear_stall_callback;
+static usb_callback_t ugen_ctrl_read_callback;
+static usb_callback_t ugen_ctrl_write_callback;
+static usb_callback_t ugen_isoc_read_callback;
+static usb_callback_t ugen_isoc_write_callback;
+static usb_callback_t ugen_ctrl_fs_callback;
+
+static usb_fifo_open_t ugen_open;
+static usb_fifo_close_t ugen_close;
+static usb_fifo_ioctl_t ugen_ioctl;
+static usb_fifo_ioctl_t ugen_ioctl_post;
+static usb_fifo_cmd_t ugen_start_read;
+static usb_fifo_cmd_t ugen_start_write;
+static usb_fifo_cmd_t ugen_stop_io;
+
+static int ugen_transfer_setup(struct usb_fifo *,
+ const struct usb_config *, uint8_t);
+static int ugen_open_pipe_write(struct usb_fifo *);
+static int ugen_open_pipe_read(struct usb_fifo *);
+static int ugen_set_config(struct usb_fifo *, uint8_t);
+static int ugen_set_interface(struct usb_fifo *, uint8_t, uint8_t);
+static int ugen_get_cdesc(struct usb_fifo *, struct usb_gen_descriptor *);
+static int ugen_get_sdesc(struct usb_fifo *, struct usb_gen_descriptor *);
+static int ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd);
+#ifdef COMPAT_FREEBSD32
+static int ugen_get32(u_long cmd, struct usb_fifo *f, struct usb_gen_descriptor32 *ugd32);
+#endif
+static int ugen_re_enumerate(struct usb_fifo *);
+static int ugen_iface_ioctl(struct usb_fifo *, u_long, void *, int);
+static uint8_t ugen_fs_get_complete(struct usb_fifo *, uint8_t *);
+static int ugen_fs_uninit(struct usb_fifo *f);
+static int ugen_fs_copyin(struct usb_fifo *, uint8_t, struct usb_fs_endpoint*);
+static uint8_t ugen_fifo_in_use(struct usb_fifo *, int fflags);
+
+/* structures */
+
+struct usb_fifo_methods usb_ugen_methods = {
+ .f_open = &ugen_open,
+ .f_close = &ugen_close,
+ .f_ioctl = &ugen_ioctl,
+ .f_ioctl_post = &ugen_ioctl_post,
+ .f_start_read = &ugen_start_read,
+ .f_stop_read = &ugen_stop_io,
+ .f_start_write = &ugen_start_write,
+ .f_stop_write = &ugen_stop_io,
+};
+
+#ifdef USB_DEBUG
+static int ugen_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ugen, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB generic");
+SYSCTL_INT(_hw_usb_ugen, OID_AUTO, debug, CTLFLAG_RWTUN, &ugen_debug,
+ 0, "Debug level");
+#endif
+
+/* prototypes */
+
+static int
+ugen_transfer_setup(struct usb_fifo *f,
+ const struct usb_config *setup, uint8_t n_setup)
+{
+ struct usb_endpoint *ep = usb_fifo_softc(f);
+ struct usb_device *udev = f->udev;
+ uint8_t iface_index = ep->iface_index;
+ int error;
+
+ mtx_unlock(f->priv_mtx);
+
+ /*
+ * "usbd_transfer_setup()" can sleep so one needs to make a wrapper,
+ * exiting the mutex and checking things
+ */
+ error = usbd_transfer_setup(udev, &iface_index, f->xfer,
+ setup, n_setup, f, f->priv_mtx);
+ if (error == 0) {
+ if (f->xfer[0]->nframes == 1) {
+ error = usb_fifo_alloc_buffer(f,
+ f->xfer[0]->max_data_length, 2);
+ } else {
+ error = usb_fifo_alloc_buffer(f,
+ f->xfer[0]->max_frame_size,
+ 2 * f->xfer[0]->nframes);
+ }
+ if (error) {
+ usbd_transfer_unsetup(f->xfer, n_setup);
+ }
+ }
+ mtx_lock(f->priv_mtx);
+
+ return (error);
+}
+
+static int
+ugen_open(struct usb_fifo *f, int fflags)
+{
+ struct usb_endpoint *ep = usb_fifo_softc(f);
+ struct usb_endpoint_descriptor *ed = ep->edesc;
+ uint8_t type;
+
+ DPRINTFN(1, "flag=0x%x pid=%d name=%s\n", fflags,
+ curthread->td_proc->p_pid, curthread->td_proc->p_comm);
+
+ mtx_lock(f->priv_mtx);
+ switch (usbd_get_speed(f->udev)) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ f->nframes = UGEN_HW_FRAMES;
+ f->bufsize = UGEN_BULK_FS_BUFFER_SIZE;
+ break;
+ default:
+ f->nframes = UGEN_HW_FRAMES * 8;
+ f->bufsize = UGEN_BULK_HS_BUFFER_SIZE;
+ break;
+ }
+
+ type = ed->bmAttributes & UE_XFERTYPE;
+ if (type == UE_INTERRUPT) {
+ f->bufsize = 0; /* use "wMaxPacketSize" */
+ }
+ f->timeout = USB_NO_TIMEOUT;
+ f->flag_short = 0;
+ f->fifo_zlp = 0;
+ mtx_unlock(f->priv_mtx);
+
+ return (0);
+}
+
+static void
+ugen_close(struct usb_fifo *f, int fflags)
+{
+
+ DPRINTFN(1, "flag=0x%x pid=%d name=%s\n", fflags,
+ curthread->td_proc->p_pid, curthread->td_proc->p_comm);
+
+ /* cleanup */
+
+ mtx_lock(f->priv_mtx);
+ usbd_transfer_stop(f->xfer[0]);
+ usbd_transfer_stop(f->xfer[1]);
+ mtx_unlock(f->priv_mtx);
+
+ usbd_transfer_unsetup(f->xfer, 2);
+ usb_fifo_free_buffer(f);
+
+ if (ugen_fs_uninit(f)) {
+ /* ignore any errors - we are closing */
+ DPRINTFN(6, "no FIFOs\n");
+ }
+}
+
+static int
+ugen_open_pipe_write(struct usb_fifo *f)
+{
+ struct usb_config usb_config[2];
+ struct usb_endpoint *ep = usb_fifo_softc(f);
+ struct usb_endpoint_descriptor *ed = ep->edesc;
+
+ USB_MTX_ASSERT(f->priv_mtx, MA_OWNED);
+
+ if (f->xfer[0] || f->xfer[1]) {
+ /* transfers are already opened */
+ return (0);
+ }
+ memset(usb_config, 0, sizeof(usb_config));
+
+ usb_config[1].type = UE_CONTROL;
+ usb_config[1].endpoint = 0;
+ usb_config[1].direction = UE_DIR_ANY;
+ usb_config[1].timeout = 1000; /* 1 second */
+ usb_config[1].interval = 50;/* 50 milliseconds */
+ usb_config[1].bufsize = sizeof(struct usb_device_request);
+ usb_config[1].callback = &ugen_write_clear_stall_callback;
+ usb_config[1].usb_mode = USB_MODE_HOST;
+
+ usb_config[0].type = ed->bmAttributes & UE_XFERTYPE;
+ usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR;
+ usb_config[0].stream_id = 0; /* XXX support more stream ID's */
+ usb_config[0].direction = UE_DIR_TX;
+ usb_config[0].interval = USB_DEFAULT_INTERVAL;
+ usb_config[0].flags.proxy_buffer = 1;
+ usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */
+
+ switch (ed->bmAttributes & UE_XFERTYPE) {
+ case UE_INTERRUPT:
+ case UE_BULK:
+ if (f->flag_short) {
+ usb_config[0].flags.force_short_xfer = 1;
+ }
+ usb_config[0].callback = &ugen_ctrl_write_callback;
+ usb_config[0].timeout = f->timeout;
+ usb_config[0].frames = 1;
+ usb_config[0].bufsize = f->bufsize;
+ if (ugen_transfer_setup(f, usb_config, 2)) {
+ return (EIO);
+ }
+ /* first transfer does not clear stall */
+ f->flag_stall = 0;
+ break;
+
+ case UE_ISOCHRONOUS:
+ usb_config[0].flags.short_xfer_ok = 1;
+ usb_config[0].bufsize = 0; /* use default */
+ usb_config[0].frames = f->nframes;
+ usb_config[0].callback = &ugen_isoc_write_callback;
+ usb_config[0].timeout = 0;
+
+ /* clone configuration */
+ usb_config[1] = usb_config[0];
+
+ if (ugen_transfer_setup(f, usb_config, 2)) {
+ return (EIO);
+ }
+ break;
+ default:
+ return (EINVAL);
+ }
+ return (0);
+}
+
+static int
+ugen_open_pipe_read(struct usb_fifo *f)
+{
+ struct usb_config usb_config[2];
+ struct usb_endpoint *ep = usb_fifo_softc(f);
+ struct usb_endpoint_descriptor *ed = ep->edesc;
+
+ USB_MTX_ASSERT(f->priv_mtx, MA_OWNED);
+
+ if (f->xfer[0] || f->xfer[1]) {
+ /* transfers are already opened */
+ return (0);
+ }
+ memset(usb_config, 0, sizeof(usb_config));
+
+ usb_config[1].type = UE_CONTROL;
+ usb_config[1].endpoint = 0;
+ usb_config[1].direction = UE_DIR_ANY;
+ usb_config[1].timeout = 1000; /* 1 second */
+ usb_config[1].interval = 50;/* 50 milliseconds */
+ usb_config[1].bufsize = sizeof(struct usb_device_request);
+ usb_config[1].callback = &ugen_read_clear_stall_callback;
+ usb_config[1].usb_mode = USB_MODE_HOST;
+
+ usb_config[0].type = ed->bmAttributes & UE_XFERTYPE;
+ usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR;
+ usb_config[0].stream_id = 0; /* XXX support more stream ID's */
+ usb_config[0].direction = UE_DIR_RX;
+ usb_config[0].interval = USB_DEFAULT_INTERVAL;
+ usb_config[0].flags.proxy_buffer = 1;
+ usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */
+
+ switch (ed->bmAttributes & UE_XFERTYPE) {
+ case UE_INTERRUPT:
+ case UE_BULK:
+ if (f->flag_short) {
+ usb_config[0].flags.short_xfer_ok = 1;
+ }
+ usb_config[0].timeout = f->timeout;
+ usb_config[0].frames = 1;
+ usb_config[0].callback = &ugen_ctrl_read_callback;
+ usb_config[0].bufsize = f->bufsize;
+
+ if (ugen_transfer_setup(f, usb_config, 2)) {
+ return (EIO);
+ }
+ /* first transfer does not clear stall */
+ f->flag_stall = 0;
+ break;
+
+ case UE_ISOCHRONOUS:
+ usb_config[0].flags.short_xfer_ok = 1;
+ usb_config[0].bufsize = 0; /* use default */
+ usb_config[0].frames = f->nframes;
+ usb_config[0].callback = &ugen_isoc_read_callback;
+ usb_config[0].timeout = 0;
+
+ /* clone configuration */
+ usb_config[1] = usb_config[0];
+
+ if (ugen_transfer_setup(f, usb_config, 2)) {
+ return (EIO);
+ }
+ break;
+
+ default:
+ return (EINVAL);
+ }
+ return (0);
+}
+
+static void
+ugen_start_read(struct usb_fifo *f)
+{
+ /* check that pipes are open */
+ if (ugen_open_pipe_read(f)) {
+ /* signal error */
+ usb_fifo_put_data_error(f);
+ }
+ /* start transfers */
+ usbd_transfer_start(f->xfer[0]);
+ usbd_transfer_start(f->xfer[1]);
+}
+
+static void
+ugen_start_write(struct usb_fifo *f)
+{
+ /* check that pipes are open */
+ if (ugen_open_pipe_write(f)) {
+ /* signal error */
+ usb_fifo_get_data_error(f);
+ }
+ /* start transfers */
+ usbd_transfer_start(f->xfer[0]);
+ usbd_transfer_start(f->xfer[1]);
+}
+
+static void
+ugen_stop_io(struct usb_fifo *f)
+{
+ /* stop transfers */
+ usbd_transfer_stop(f->xfer[0]);
+ usbd_transfer_stop(f->xfer[1]);
+}
+
+static void
+ugen_ctrl_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usb_fifo *f = usbd_xfer_softc(xfer);
+ struct usb_mbuf *m;
+
+ DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ if (xfer->actlen == 0) {
+ if (f->fifo_zlp != 4) {
+ f->fifo_zlp++;
+ } else {
+ /*
+ * Throttle a little bit we have multiple ZLPs
+ * in a row!
+ */
+ xfer->interval = 64; /* ms */
+ }
+ } else {
+ /* clear throttle */
+ xfer->interval = 0;
+ f->fifo_zlp = 0;
+ }
+ usb_fifo_put_data(f, xfer->frbuffers, 0,
+ xfer->actlen, 1);
+
+ case USB_ST_SETUP:
+ if (f->flag_stall) {
+ usbd_transfer_start(f->xfer[1]);
+ break;
+ }
+ USB_IF_POLL(&f->free_q, m);
+ if (m) {
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ if (xfer->error != USB_ERR_CANCELLED) {
+ /* send a zero length packet to userland */
+ usb_fifo_put_data(f, xfer->frbuffers, 0, 0, 1);
+ f->flag_stall = 1;
+ f->fifo_zlp = 0;
+ usbd_transfer_start(f->xfer[1]);
+ }
+ break;
+ }
+}
+
+static void
+ugen_ctrl_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usb_fifo *f = usbd_xfer_softc(xfer);
+ usb_frlength_t actlen;
+
+ DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+ /*
+ * If writing is in stall, just jump to clear stall
+ * callback and solve the situation.
+ */
+ if (f->flag_stall) {
+ usbd_transfer_start(f->xfer[1]);
+ break;
+ }
+ /*
+ * Write data, setup and perform hardware transfer.
+ */
+ if (usb_fifo_get_data(f, xfer->frbuffers, 0,
+ xfer->max_data_length, &actlen, 0)) {
+ usbd_xfer_set_frame_len(xfer, 0, actlen);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+
+ default: /* Error */
+ if (xfer->error != USB_ERR_CANCELLED) {
+ f->flag_stall = 1;
+ usbd_transfer_start(f->xfer[1]);
+ }
+ break;
+ }
+}
+
+static void
+ugen_read_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usb_fifo *f = usbd_xfer_softc(xfer);
+ struct usb_xfer *xfer_other = f->xfer[0];
+
+ if (f->flag_stall == 0) {
+ /* nothing to do */
+ return;
+ }
+ if (usbd_clear_stall_callback(xfer, xfer_other)) {
+ DPRINTFN(5, "f=%p: stall cleared\n", f);
+ f->flag_stall = 0;
+ usbd_transfer_start(xfer_other);
+ }
+}
+
+static void
+ugen_write_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usb_fifo *f = usbd_xfer_softc(xfer);
+ struct usb_xfer *xfer_other = f->xfer[0];
+
+ if (f->flag_stall == 0) {
+ /* nothing to do */
+ return;
+ }
+ if (usbd_clear_stall_callback(xfer, xfer_other)) {
+ DPRINTFN(5, "f=%p: stall cleared\n", f);
+ f->flag_stall = 0;
+ usbd_transfer_start(xfer_other);
+ }
+}
+
+static void
+ugen_isoc_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usb_fifo *f = usbd_xfer_softc(xfer);
+ usb_frlength_t offset;
+ usb_frcount_t n;
+
+ DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ DPRINTFN(6, "actlen=%d\n", xfer->actlen);
+
+ offset = 0;
+
+ for (n = 0; n != xfer->aframes; n++) {
+ usb_fifo_put_data(f, xfer->frbuffers, offset,
+ xfer->frlengths[n], 1);
+ offset += xfer->max_frame_size;
+ }
+
+ case USB_ST_SETUP:
+tr_setup:
+ for (n = 0; n != xfer->nframes; n++) {
+ /* setup size for next transfer */
+ usbd_xfer_set_frame_len(xfer, n, xfer->max_frame_size);
+ }
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (xfer->error == USB_ERR_CANCELLED) {
+ break;
+ }
+ goto tr_setup;
+ }
+}
+
+static void
+ugen_isoc_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usb_fifo *f = usbd_xfer_softc(xfer);
+ usb_frlength_t actlen;
+ usb_frlength_t offset;
+ usb_frcount_t n;
+
+ DPRINTFN(4, "actlen=%u, aframes=%u\n", xfer->actlen, xfer->aframes);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+tr_setup:
+ offset = 0;
+ for (n = 0; n != xfer->nframes; n++) {
+ if (usb_fifo_get_data(f, xfer->frbuffers, offset,
+ xfer->max_frame_size, &actlen, 1)) {
+ usbd_xfer_set_frame_len(xfer, n, actlen);
+ offset += actlen;
+ } else {
+ break;
+ }
+ }
+
+ for (; n != xfer->nframes; n++) {
+ /* fill in zero frames */
+ usbd_xfer_set_frame_len(xfer, n, 0);
+ }
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (xfer->error == USB_ERR_CANCELLED) {
+ break;
+ }
+ goto tr_setup;
+ }
+}
+
+static int
+ugen_set_config(struct usb_fifo *f, uint8_t index)
+{
+ DPRINTFN(2, "index %u\n", index);
+
+ if (f->udev->flags.usb_mode != USB_MODE_HOST) {
+ /* not possible in device side mode */
+ return (ENOTTY);
+ }
+
+ /* make sure all FIFO's are gone */
+ /* else there can be a deadlock */
+ if (ugen_fs_uninit(f)) {
+ /* ignore any errors */
+ DPRINTFN(6, "no FIFOs\n");
+ }
+
+ if (usbd_start_set_config(f->udev, index) != 0)
+ return (EIO);
+
+ return (0);
+}
+
+static int
+ugen_set_interface(struct usb_fifo *f,
+ uint8_t iface_index, uint8_t alt_index)
+{
+ DPRINTFN(2, "%u, %u\n", iface_index, alt_index);
+
+ if (f->udev->flags.usb_mode != USB_MODE_HOST) {
+ /* not possible in device side mode */
+ return (ENOTTY);
+ }
+ /* make sure all FIFO's are gone */
+ /* else there can be a deadlock */
+ if (ugen_fs_uninit(f)) {
+ /* ignore any errors */
+ DPRINTFN(6, "no FIFOs\n");
+ }
+ /* change setting - will free generic FIFOs, if any */
+ if (usbd_set_alt_interface_index(f->udev, iface_index, alt_index)) {
+ return (EIO);
+ }
+ /* probe and attach */
+ if (usb_probe_and_attach(f->udev, iface_index)) {
+ return (EIO);
+ }
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * ugen_get_cdesc
+ *
+ * This function will retrieve the complete configuration descriptor
+ * at the given index.
+ *------------------------------------------------------------------------*/
+static int
+ugen_get_cdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd)
+{
+ struct usb_config_descriptor *cdesc;
+ struct usb_device *udev = f->udev;
+ int error;
+ uint16_t len;
+ uint8_t free_data;
+
+ DPRINTFN(6, "\n");
+
+ if (ugd->ugd_data == NULL) {
+ /* userland pointer should not be zero */
+ return (EINVAL);
+ }
+ if ((ugd->ugd_config_index == USB_UNCONFIG_INDEX) ||
+ (ugd->ugd_config_index == udev->curr_config_index)) {
+ cdesc = usbd_get_config_descriptor(udev);
+ if (cdesc == NULL)
+ return (ENXIO);
+ free_data = 0;
+
+ } else {
+#if (USB_HAVE_FIXED_CONFIG == 0)
+ if (usbd_req_get_config_desc_full(udev,
+ NULL, &cdesc, ugd->ugd_config_index)) {
+ return (ENXIO);
+ }
+ free_data = 1;
+#else
+ /* configuration descriptor data is shared */
+ return (EINVAL);
+#endif
+ }
+
+ len = UGETW(cdesc->wTotalLength);
+ if (len > ugd->ugd_maxlen) {
+ len = ugd->ugd_maxlen;
+ }
+ DPRINTFN(6, "len=%u\n", len);
+
+ ugd->ugd_actlen = len;
+ ugd->ugd_offset = 0;
+
+ error = copyout(cdesc, ugd->ugd_data, len);
+
+ if (free_data)
+ usbd_free_config_desc(udev, cdesc);
+
+ return (error);
+}
+
+static int
+ugen_get_sdesc(struct usb_fifo *f, struct usb_gen_descriptor *ugd)
+{
+ void *ptr;
+ uint16_t size;
+ int error;
+ uint8_t do_unlock;
+
+ /* Protect scratch area */
+ do_unlock = usbd_ctrl_lock(f->udev);
+
+ ptr = f->udev->scratch.data;
+ size = sizeof(f->udev->scratch.data);
+
+ if (usbd_req_get_string_desc(f->udev, NULL, ptr,
+ size, ugd->ugd_lang_id, ugd->ugd_string_index)) {
+ error = EINVAL;
+ } else {
+ if (size > ((uint8_t *)ptr)[0]) {
+ size = ((uint8_t *)ptr)[0];
+ }
+ if (size > ugd->ugd_maxlen) {
+ size = ugd->ugd_maxlen;
+ }
+ ugd->ugd_actlen = size;
+ ugd->ugd_offset = 0;
+
+ error = copyout(ptr, ugd->ugd_data, size);
+ }
+ if (do_unlock)
+ usbd_ctrl_unlock(f->udev);
+
+ return (error);
+}
+
+/*------------------------------------------------------------------------*
+ * ugen_get_iface_driver
+ *
+ * This function generates an USB interface description for userland.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static int
+ugen_get_iface_driver(struct usb_fifo *f, struct usb_gen_descriptor *ugd)
+{
+ struct usb_device *udev = f->udev;
+ struct usb_interface *iface;
+ const char *ptr;
+ const char *desc;
+ unsigned len;
+ unsigned maxlen;
+ char buf[128];
+ int error;
+
+ DPRINTFN(6, "\n");
+
+ if ((ugd->ugd_data == NULL) || (ugd->ugd_maxlen == 0)) {
+ /* userland pointer should not be zero */
+ return (EINVAL);
+ }
+
+ iface = usbd_get_iface(udev, ugd->ugd_iface_index);
+ if ((iface == NULL) || (iface->idesc == NULL)) {
+ /* invalid interface index */
+ return (EINVAL);
+ }
+
+ /* read out device nameunit string, if any */
+ if ((iface->subdev != NULL) &&
+ device_is_attached(iface->subdev) &&
+ (ptr = device_get_nameunit(iface->subdev)) &&
+ (desc = device_get_desc(iface->subdev))) {
+ /* print description */
+ snprintf(buf, sizeof(buf), "%s: <%s>", ptr, desc);
+
+ /* range checks */
+ maxlen = ugd->ugd_maxlen - 1;
+ len = strlen(buf);
+ if (len > maxlen)
+ len = maxlen;
+
+ /* update actual length, including terminating zero */
+ ugd->ugd_actlen = len + 1;
+
+ /* copy out interface description */
+ error = copyout(buf, ugd->ugd_data, ugd->ugd_actlen);
+ } else {
+ /* zero length string is default */
+ error = copyout("", ugd->ugd_data, 1);
+ }
+ return (error);
+}
+
+/*------------------------------------------------------------------------*
+ * ugen_fill_deviceinfo
+ *
+ * This function dumps information about an USB device to the
+ * structure pointed to by the "di" argument.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+int
+ugen_fill_deviceinfo(struct usb_fifo *f, struct usb_device_info *di)
+{
+ struct usb_device *udev;
+ struct usb_device *hub;
+
+ udev = f->udev;
+
+ bzero(di, sizeof(di[0]));
+
+ di->udi_bus = device_get_unit(udev->bus->bdev);
+ di->udi_addr = udev->address;
+ di->udi_index = udev->device_index;
+ strlcpy(di->udi_serial, usb_get_serial(udev), sizeof(di->udi_serial));
+ strlcpy(di->udi_vendor, usb_get_manufacturer(udev), sizeof(di->udi_vendor));
+ strlcpy(di->udi_product, usb_get_product(udev), sizeof(di->udi_product));
+ usb_printbcd(di->udi_release, sizeof(di->udi_release),
+ UGETW(udev->ddesc.bcdDevice));
+ di->udi_vendorNo = UGETW(udev->ddesc.idVendor);
+ di->udi_productNo = UGETW(udev->ddesc.idProduct);
+ di->udi_releaseNo = UGETW(udev->ddesc.bcdDevice);
+ di->udi_class = udev->ddesc.bDeviceClass;
+ di->udi_subclass = udev->ddesc.bDeviceSubClass;
+ di->udi_protocol = udev->ddesc.bDeviceProtocol;
+ di->udi_config_no = udev->curr_config_no;
+ di->udi_config_index = udev->curr_config_index;
+ di->udi_power = udev->flags.self_powered ? 0 : udev->power;
+ di->udi_speed = udev->speed;
+ di->udi_mode = udev->flags.usb_mode;
+ di->udi_power_mode = udev->power_mode;
+ di->udi_suspended = udev->flags.peer_suspended;
+
+ hub = udev->parent_hub;
+ if (hub) {
+ di->udi_hubaddr = hub->address;
+ di->udi_hubindex = hub->device_index;
+ di->udi_hubport = udev->port_no;
+ }
+ return (0);
+}
+
+int
+ugen_do_request(struct usb_fifo *f, struct usb_ctl_request *ur)
+{
+ int error;
+ uint16_t len;
+ uint16_t actlen;
+
+ if (usb_check_request(f->udev, &ur->ucr_request)) {
+ return (EPERM);
+ }
+ len = UGETW(ur->ucr_request.wLength);
+
+ /* check if "ucr_data" is valid */
+ if (len != 0) {
+ if (ur->ucr_data == NULL) {
+ return (EFAULT);
+ }
+ }
+ /* do the USB request */
+ error = usbd_do_request_flags
+ (f->udev, NULL, &ur->ucr_request, ur->ucr_data,
+ (ur->ucr_flags & USB_SHORT_XFER_OK) |
+ USB_USER_DATA_PTR, &actlen,
+ USB_DEFAULT_TIMEOUT);
+
+ ur->ucr_actlen = actlen;
+
+ if (error) {
+ error = EIO;
+ }
+ return (error);
+}
+
+#ifdef COMPAT_FREEBSD32
+static int
+ugen_do_request32(struct usb_fifo *f, struct usb_ctl_request32 *ur32)
+{
+ struct usb_ctl_request ur;
+ int error;
+
+ PTRIN_CP(*ur32, ur, ucr_data);
+ CP(*ur32, ur, ucr_flags);
+ CP(*ur32, ur, ucr_actlen);
+ CP(*ur32, ur, ucr_addr);
+ CP(*ur32, ur, ucr_request);
+
+ error = ugen_do_request(f, &ur);
+
+ /* Don't update ucr_data pointer */
+ CP(ur, *ur32, ucr_flags);
+ CP(ur, *ur32, ucr_actlen);
+ CP(ur, *ur32, ucr_addr);
+ CP(ur, *ur32, ucr_request);
+
+ return (error);
+}
+#endif
+
+/*------------------------------------------------------------------------
+ * ugen_re_enumerate
+ *------------------------------------------------------------------------*/
+static int
+ugen_re_enumerate(struct usb_fifo *f)
+{
+ struct usb_device *udev = f->udev;
+ int error;
+
+ /*
+ * This request can be useful for testing USB drivers:
+ */
+ error = priv_check(curthread, PRIV_DRIVER);
+ if (error) {
+ return (error);
+ }
+ if (udev->flags.usb_mode != USB_MODE_HOST) {
+ /* not possible in device side mode */
+ DPRINTFN(6, "device mode\n");
+ return (ENOTTY);
+ }
+ /* make sure all FIFO's are gone */
+ /* else there can be a deadlock */
+ if (ugen_fs_uninit(f)) {
+ /* ignore any errors */
+ DPRINTFN(6, "no FIFOs\n");
+ }
+ /* start re-enumeration of device */
+ usbd_start_re_enumerate(udev);
+ return (0);
+}
+
+static int
+ugen_fs_init(struct usb_fifo *f,
+ struct usb_fs_endpoint *fs_ep_ptr, usb_size_t fs_ep_sz,
+ int fflags, uint8_t ep_index_max)
+{
+ int error;
+
+ /* verify input parameters */
+ if (fs_ep_ptr == NULL || ep_index_max > USB_FS_XFER_MAX)
+ return (EINVAL);
+
+ if (f->fs_ep_max != 0)
+ return (EBUSY);
+
+ if (f->dev_ep_index != 0 || ep_index_max == 0)
+ return (EINVAL);
+
+ if (ugen_fifo_in_use(f, fflags))
+ return (EBUSY);
+
+ error = usb_fifo_alloc_buffer(f, 1, ep_index_max);
+ if (error == 0) {
+ mtx_lock(f->priv_mtx);
+ f->fs_ep_max = ep_index_max;
+ f->fs_ep_ptr = fs_ep_ptr;
+ f->fs_ep_sz = fs_ep_sz;
+ mtx_unlock(f->priv_mtx);
+ }
+ return (error);
+}
+
+int
+ugen_fs_uninit(struct usb_fifo *f)
+{
+ if (f->fs_ep_max == 0)
+ return (EINVAL);
+
+ /*
+ * Prevent calls into the fast-path code, by setting fs_ep_max
+ * to zero:
+ */
+ sx_xlock(&f->fs_fastpath_lock);
+ mtx_lock(f->priv_mtx);
+ f->fs_ep_max = 0;
+ mtx_unlock(f->priv_mtx);
+ sx_xunlock(&f->fs_fastpath_lock);
+
+ usbd_transfer_unsetup(f->fs_xfer, USB_FS_XFER_MAX);
+
+ mtx_lock(f->priv_mtx);
+ f->fs_ep_ptr = NULL;
+ f->flag_iscomplete = 0;
+ mtx_unlock(f->priv_mtx);
+
+ usb_fifo_free_buffer(f);
+ return (0);
+}
+
+static int
+usb_fs_open(struct usb_fifo *f, struct usb_fs_open *popen,
+ int fflags, usb_stream_t stream_id)
+{
+ struct usb_config usb_config[1] = {};
+ struct usb_endpoint *ep;
+ struct usb_endpoint_descriptor *ed;
+ uint8_t iface_index;
+ uint8_t pre_scale;
+ uint8_t isread;
+ int error;
+
+ if (popen->ep_index >= f->fs_ep_max)
+ return (EINVAL);
+
+ if (f->fs_xfer[popen->ep_index] != NULL)
+ return (EBUSY);
+
+ if (popen->max_bufsize > USB_FS_MAX_BUFSIZE)
+ popen->max_bufsize = USB_FS_MAX_BUFSIZE;
+
+ if (popen->max_frames & USB_FS_MAX_FRAMES_PRE_SCALE) {
+ pre_scale = 1;
+ popen->max_frames &= ~USB_FS_MAX_FRAMES_PRE_SCALE;
+ } else {
+ pre_scale = 0;
+ }
+
+ if (popen->max_frames > USB_FS_MAX_FRAMES)
+ popen->max_frames = USB_FS_MAX_FRAMES;
+
+ if (popen->max_frames == 0)
+ return (EINVAL);
+
+ ep = usbd_get_ep_by_addr(f->udev, popen->ep_no);
+ if (ep == NULL)
+ return (EINVAL);
+
+ ed = ep->edesc;
+ if (ed == NULL)
+ return (ENXIO);
+
+ iface_index = ep->iface_index;
+
+ usb_config[0].type = ed->bmAttributes & UE_XFERTYPE;
+ usb_config[0].endpoint = ed->bEndpointAddress & UE_ADDR;
+ usb_config[0].direction = ed->bEndpointAddress & (UE_DIR_OUT | UE_DIR_IN);
+ usb_config[0].interval = USB_DEFAULT_INTERVAL;
+ usb_config[0].flags.proxy_buffer = 1;
+ if (pre_scale != 0)
+ usb_config[0].flags.pre_scale_frames = 1;
+
+ usb_config[0].callback = &ugen_ctrl_fs_callback;
+ usb_config[0].timeout = 0; /* no timeout */
+ usb_config[0].frames = popen->max_frames;
+ usb_config[0].bufsize = popen->max_bufsize;
+ usb_config[0].usb_mode = USB_MODE_DUAL; /* both modes */
+ usb_config[0].stream_id = stream_id;
+
+ if (usb_config[0].type == UE_CONTROL) {
+ if (f->udev->flags.usb_mode != USB_MODE_HOST)
+ return (EINVAL);
+ } else {
+ isread = ((usb_config[0].endpoint &
+ (UE_DIR_IN | UE_DIR_OUT)) == UE_DIR_IN);
+
+ if (f->udev->flags.usb_mode != USB_MODE_HOST)
+ isread = !isread;
+
+ /* check permissions */
+ if (isread) {
+ if (!(fflags & FREAD))
+ return (EPERM);
+ } else {
+ if (!(fflags & FWRITE))
+ return (EPERM);
+ }
+ }
+ error = usbd_transfer_setup(f->udev, &iface_index,
+ f->fs_xfer + popen->ep_index, usb_config, 1,
+ f, f->priv_mtx);
+ if (error == 0) {
+ /* update maximums */
+ popen->max_packet_length =
+ f->fs_xfer[popen->ep_index]->max_frame_size;
+ popen->max_bufsize =
+ f->fs_xfer[popen->ep_index]->max_data_length;
+ /* update number of frames */
+ popen->max_frames =
+ f->fs_xfer[popen->ep_index]->nframes;
+ /* store index of endpoint */
+ f->fs_xfer[popen->ep_index]->priv_fifo =
+ ((uint8_t *)0) + popen->ep_index;
+ } else {
+ error = ENOMEM;
+ }
+ return (error);
+}
+
+static int
+usb_fs_close(struct usb_fifo *f, struct usb_fs_close *pclose)
+{
+ struct usb_xfer *xfer;
+
+ if (pclose->ep_index >= f->fs_ep_max)
+ return (EINVAL);
+
+ /*
+ * Prevent calls into the fast-path code, by setting the
+ * fs_xfer[] in question to NULL:
+ */
+ sx_xlock(&f->fs_fastpath_lock);
+ mtx_lock(f->priv_mtx);
+ xfer = f->fs_xfer[pclose->ep_index];
+ f->fs_xfer[pclose->ep_index] = NULL;
+ mtx_unlock(f->priv_mtx);
+ sx_xunlock(&f->fs_fastpath_lock);
+
+ if (xfer == NULL)
+ return (EINVAL);
+
+ usbd_transfer_unsetup(&xfer, 1);
+ return (0);
+}
+
+static int
+usb_fs_clear_stall_sync(struct usb_fifo *f, struct usb_fs_clear_stall_sync *pstall)
+{
+ struct usb_device_request req;
+ struct usb_endpoint *ep;
+ int error;
+
+ if (pstall->ep_index >= f->fs_ep_max)
+ return (EINVAL);
+
+ if (f->fs_xfer[pstall->ep_index] == NULL)
+ return (EINVAL);
+
+ if (f->udev->flags.usb_mode != USB_MODE_HOST)
+ return (EINVAL);
+
+ mtx_lock(f->priv_mtx);
+ error = usbd_transfer_pending(f->fs_xfer[pstall->ep_index]);
+ mtx_unlock(f->priv_mtx);
+
+ if (error)
+ return (EBUSY);
+
+ ep = f->fs_xfer[pstall->ep_index]->endpoint;
+
+ /* setup a clear-stall packet */
+ req.bmRequestType = UT_WRITE_ENDPOINT;
+ req.bRequest = UR_CLEAR_FEATURE;
+ USETW(req.wValue, UF_ENDPOINT_HALT);
+ req.wIndex[0] = ep->edesc->bEndpointAddress;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ error = usbd_do_request(f->udev, NULL, &req, NULL);
+ if (error == 0) {
+ usbd_clear_data_toggle(f->udev, ep);
+ } else {
+ error = ENXIO;
+ }
+ return (error);
+}
+
+static uint8_t
+ugen_fs_get_complete(struct usb_fifo *f, uint8_t *pindex)
+{
+ struct usb_mbuf *m;
+
+ USB_IF_DEQUEUE(&f->used_q, m);
+
+ if (m) {
+ *pindex = *((uint8_t *)(m->cur_data_ptr));
+
+ USB_IF_ENQUEUE(&f->free_q, m);
+
+ return (0); /* success */
+ } else {
+ *pindex = 0; /* fix compiler warning */
+
+ f->flag_iscomplete = 0;
+ }
+ return (1); /* failure */
+}
+
+static void
+ugen_fs_set_complete(struct usb_fifo *f, uint8_t index)
+{
+ struct usb_mbuf *m;
+
+ USB_IF_DEQUEUE(&f->free_q, m);
+
+ if (m == NULL) {
+ /* can happen during close */
+ DPRINTF("out of buffers\n");
+ return;
+ }
+ USB_MBUF_RESET(m);
+
+ *((uint8_t *)(m->cur_data_ptr)) = index;
+
+ USB_IF_ENQUEUE(&f->used_q, m);
+
+ f->flag_iscomplete = 1;
+
+ usb_fifo_wakeup(f);
+}
+
+static int
+ugen_fs_getbuffer(void **uptrp, struct usb_fifo *f, void *buffer,
+ usb_frcount_t n)
+{
+ union {
+ void **ppBuffer;
+#ifdef COMPAT_FREEBSD32
+ uint32_t *ppBuffer32;
+#endif
+ } u;
+#ifdef COMPAT_FREEBSD32
+ uint32_t uptr32;
+#endif
+
+ u.ppBuffer = buffer;
+ switch (f->fs_ep_sz) {
+ case sizeof(struct usb_fs_endpoint):
+ if (fueword(u.ppBuffer + n, (long *)uptrp) != 0)
+ return (EFAULT);
+ return (0);
+#ifdef COMPAT_FREEBSD32
+ case sizeof(struct usb_fs_endpoint32):
+ if (fueword32(u.ppBuffer32 + n, &uptr32) != 0)
+ return (EFAULT);
+ *uptrp = PTRIN(uptr32);
+ return (0);
+#endif
+ default:
+ panic("%s: unhandled fs_ep_sz %#x", __func__, f->fs_ep_sz);
+ }
+}
+
+static int
+ugen_fs_copy_in(struct usb_fifo *f, uint8_t ep_index)
+{
+ struct usb_device_request *req;
+ struct usb_xfer *xfer;
+ struct usb_fs_endpoint fs_ep;
+ void *uaddr; /* userland pointer */
+ void *kaddr;
+ usb_frlength_t offset;
+ usb_frlength_t rem;
+ usb_frcount_t n;
+ uint32_t length;
+ int error;
+ uint8_t isread;
+
+ mtx_lock(f->priv_mtx);
+ if (ep_index >= f->fs_ep_max) {
+ mtx_unlock(f->priv_mtx);
+ return (EINVAL);
+ }
+ xfer = f->fs_xfer[ep_index];
+ if (xfer == NULL) {
+ mtx_unlock(f->priv_mtx);
+ return (EINVAL);
+ }
+ if (usbd_transfer_pending(xfer)) {
+ mtx_unlock(f->priv_mtx);
+ return (EBUSY); /* should not happen */
+ }
+ mtx_unlock(f->priv_mtx);
+
+ error = ugen_fs_copyin(f, ep_index, &fs_ep);
+ if (error) {
+ return (error);
+ }
+ /* security checks */
+
+ if (fs_ep.nFrames > xfer->max_frame_count) {
+ xfer->error = USB_ERR_INVAL;
+ goto complete;
+ }
+ if (fs_ep.nFrames == 0) {
+ xfer->error = USB_ERR_INVAL;
+ goto complete;
+ }
+ error = ugen_fs_getbuffer(&uaddr, f, fs_ep.ppBuffer, 0);
+ if (error) {
+ return (error);
+ }
+ /* reset first frame */
+ usbd_xfer_set_frame_offset(xfer, 0, 0);
+
+ if (xfer->flags_int.control_xfr) {
+ req = xfer->frbuffers[0].buffer;
+
+ if (fueword32(fs_ep.pLength, &length) != 0) {
+ return (EFAULT);
+ }
+ if (length != sizeof(*req)) {
+ xfer->error = USB_ERR_INVAL;
+ goto complete;
+ }
+ if (length != 0) {
+ error = copyin(uaddr, req, length);
+ if (error) {
+ return (error);
+ }
+ }
+ if (usb_check_request(f->udev, req)) {
+ xfer->error = USB_ERR_INVAL;
+ goto complete;
+ }
+ usbd_xfer_set_frame_len(xfer, 0, length);
+
+ /* Host mode only ! */
+ if ((req->bmRequestType &
+ (UT_READ | UT_WRITE)) == UT_READ) {
+ isread = 1;
+ } else {
+ isread = 0;
+ }
+ n = 1;
+ offset = sizeof(*req);
+
+ } else {
+ /* Device and Host mode */
+ if (USB_GET_DATA_ISREAD(xfer)) {
+ isread = 1;
+ } else {
+ isread = 0;
+ }
+ n = 0;
+ offset = 0;
+ }
+
+ rem = usbd_xfer_max_len(xfer);
+ xfer->nframes = fs_ep.nFrames;
+ xfer->timeout = fs_ep.timeout;
+ if (xfer->timeout > 65535) {
+ xfer->timeout = 65535;
+ }
+ if (fs_ep.flags & USB_FS_FLAG_SINGLE_SHORT_OK)
+ xfer->flags.short_xfer_ok = 1;
+ else
+ xfer->flags.short_xfer_ok = 0;
+
+ if (fs_ep.flags & USB_FS_FLAG_MULTI_SHORT_OK)
+ xfer->flags.short_frames_ok = 1;
+ else
+ xfer->flags.short_frames_ok = 0;
+
+ if (fs_ep.flags & USB_FS_FLAG_FORCE_SHORT)
+ xfer->flags.force_short_xfer = 1;
+ else
+ xfer->flags.force_short_xfer = 0;
+
+ if (fs_ep.flags & USB_FS_FLAG_CLEAR_STALL)
+ usbd_xfer_set_stall(xfer);
+ else
+ xfer->flags.stall_pipe = 0;
+
+ for (; n != xfer->nframes; n++) {
+ if (fueword32(fs_ep.pLength + n, &length) != 0) {
+ break;
+ }
+ usbd_xfer_set_frame_len(xfer, n, length);
+
+ if (length > rem) {
+ xfer->error = USB_ERR_INVAL;
+ goto complete;
+ }
+ rem -= length;
+
+ if (!isread) {
+ /* we need to know the source buffer */
+ error = ugen_fs_getbuffer(&uaddr, f, fs_ep.ppBuffer, n);
+ if (error) {
+ break;
+ }
+ if (xfer->flags_int.isochronous_xfr) {
+ /* get kernel buffer address */
+ kaddr = xfer->frbuffers[0].buffer;
+ kaddr = USB_ADD_BYTES(kaddr, offset);
+ } else {
+ /* set current frame offset */
+ usbd_xfer_set_frame_offset(xfer, offset, n);
+
+ /* get kernel buffer address */
+ kaddr = xfer->frbuffers[n].buffer;
+ }
+
+ /* move data */
+ error = copyin(uaddr, kaddr, length);
+ if (error) {
+ break;
+ }
+ }
+ offset += length;
+ }
+ return (error);
+
+complete:
+ mtx_lock(f->priv_mtx);
+ ugen_fs_set_complete(f, ep_index);
+ mtx_unlock(f->priv_mtx);
+ return (0);
+}
+
+static struct usb_fs_endpoint *
+ugen_fs_ep_uptr(struct usb_fifo *f, uint8_t ep_index)
+{
+ return ((struct usb_fs_endpoint *)
+ ((char *)f->fs_ep_ptr + (ep_index * f->fs_ep_sz)));
+}
+
+static int
+ugen_fs_copyin(struct usb_fifo *f, uint8_t ep_index,
+ struct usb_fs_endpoint* fs_ep)
+{
+#ifdef COMPAT_FREEBSD32
+ struct usb_fs_endpoint32 fs_ep32;
+#endif
+ int error;
+
+ switch (f->fs_ep_sz) {
+ case sizeof(struct usb_fs_endpoint):
+ error = copyin(ugen_fs_ep_uptr(f, ep_index), fs_ep,
+ f->fs_ep_sz);
+ if (error != 0)
+ return (error);
+ break;
+
+#ifdef COMPAT_FREEBSD32
+ case sizeof(struct usb_fs_endpoint32):
+ error = copyin(ugen_fs_ep_uptr(f, ep_index), &fs_ep32,
+ f->fs_ep_sz);
+ if (error != 0)
+ return (error);
+ PTRIN_CP(fs_ep32, *fs_ep, ppBuffer);
+ PTRIN_CP(fs_ep32, *fs_ep, pLength);
+ CP(fs_ep32, *fs_ep, nFrames);
+ CP(fs_ep32, *fs_ep, aFrames);
+ CP(fs_ep32, *fs_ep, flags);
+ CP(fs_ep32, *fs_ep, timeout);
+ CP(fs_ep32, *fs_ep, isoc_time_complete);
+ CP(fs_ep32, *fs_ep, status);
+ break;
+#endif
+ default:
+ panic("%s: unhandled fs_ep_sz %#x", __func__, f->fs_ep_sz);
+ }
+
+ return (0);
+}
+
+static int
+ugen_fs_update(const struct usb_fs_endpoint *fs_ep,
+ struct usb_fifo *f, uint8_t ep_index)
+{
+ union {
+ struct usb_fs_endpoint *fs_ep_uptr;
+#ifdef COMPAT_FREEBSD32
+ struct usb_fs_endpoint32 *fs_ep_uptr32;
+#endif
+ } u;
+ uint32_t *aFrames_uptr;
+ uint16_t *isoc_time_complete_uptr;
+ int *status_uptr;
+
+ switch (f->fs_ep_sz) {
+ case sizeof(struct usb_fs_endpoint):
+ u.fs_ep_uptr = ugen_fs_ep_uptr(f, ep_index);
+ aFrames_uptr = &u.fs_ep_uptr->aFrames;
+ isoc_time_complete_uptr = &u.fs_ep_uptr->isoc_time_complete;
+ status_uptr = &u.fs_ep_uptr->status;
+ break;
+#ifdef COMPAT_FREEBSD32
+ case sizeof(struct usb_fs_endpoint32):
+ u.fs_ep_uptr32 = (struct usb_fs_endpoint32 *)
+ ugen_fs_ep_uptr(f, ep_index);
+ aFrames_uptr = &u.fs_ep_uptr32->aFrames;
+ isoc_time_complete_uptr = &u.fs_ep_uptr32->isoc_time_complete;
+ status_uptr = &u.fs_ep_uptr32->status;
+ break;
+#endif
+ default:
+ panic("%s: unhandled fs_ep_sz %#x", __func__, f->fs_ep_sz);
+ }
+
+ /* update "aFrames" */
+ if (suword32(aFrames_uptr, fs_ep->aFrames) != 0)
+ return (EFAULT);
+
+ /* update "isoc_time_complete" */
+ if (suword16(isoc_time_complete_uptr, fs_ep->isoc_time_complete) != 0)
+ return (EFAULT);
+
+ /* update "status" */
+ if (suword32(status_uptr, fs_ep->status) != 0)
+ return (EFAULT);
+
+ return (0);
+}
+
+static int
+ugen_fs_copy_out_cancelled(struct usb_fifo *f, uint8_t ep_index)
+{
+ struct usb_fs_endpoint fs_ep;
+ int error;
+
+ error = ugen_fs_copyin(f, ep_index, &fs_ep);
+ if (error)
+ return (error);
+
+ fs_ep.status = USB_ERR_CANCELLED;
+ fs_ep.aFrames = 0;
+ fs_ep.isoc_time_complete = 0;
+
+ return (ugen_fs_update(&fs_ep, f, ep_index));
+}
+
+static int
+ugen_fs_copy_out(struct usb_fifo *f, uint8_t ep_index)
+{
+ struct usb_device_request *req;
+ struct usb_xfer *xfer;
+ struct usb_fs_endpoint fs_ep;
+ void *uaddr; /* userland ptr */
+ void *kaddr;
+ usb_frlength_t offset;
+ usb_frlength_t rem;
+ usb_frcount_t n;
+ uint32_t length;
+ uint32_t temp;
+ int error;
+ uint8_t isread;
+
+ mtx_lock(f->priv_mtx);
+ if (ep_index >= f->fs_ep_max) {
+ mtx_unlock(f->priv_mtx);
+ return (EINVAL);
+ }
+ xfer = f->fs_xfer[ep_index];
+ if (xfer == NULL) {
+ mtx_unlock(f->priv_mtx);
+ return (EINVAL);
+ }
+ if (!xfer->flags_int.transferring &&
+ !xfer->flags_int.started) {
+ mtx_unlock(f->priv_mtx);
+ DPRINTF("Returning fake cancel event\n");
+ return (ugen_fs_copy_out_cancelled(f, ep_index));
+ } else if (usbd_transfer_pending(xfer)) {
+ mtx_unlock(f->priv_mtx);
+ return (EBUSY); /* should not happen */
+ }
+ mtx_unlock(f->priv_mtx);
+
+ error = ugen_fs_copyin(f, ep_index, &fs_ep);
+ if (error) {
+ return (error);
+ }
+
+ fs_ep.status = xfer->error;
+ fs_ep.aFrames = xfer->aframes;
+ fs_ep.isoc_time_complete = xfer->isoc_time_complete;
+ if (xfer->error) {
+ goto complete;
+ }
+ if (xfer->flags_int.control_xfr) {
+ req = xfer->frbuffers[0].buffer;
+
+ /* Host mode only ! */
+ if ((req->bmRequestType & (UT_READ | UT_WRITE)) == UT_READ) {
+ isread = 1;
+ } else {
+ isread = 0;
+ }
+ if (xfer->nframes == 0)
+ n = 0; /* should never happen */
+ else
+ n = 1;
+ } else {
+ /* Device and Host mode */
+ if (USB_GET_DATA_ISREAD(xfer)) {
+ isread = 1;
+ } else {
+ isread = 0;
+ }
+ n = 0;
+ }
+
+ /* Update lengths and copy out data */
+
+ rem = usbd_xfer_max_len(xfer);
+ offset = 0;
+
+ for (; n != xfer->nframes; n++) {
+ /* get initial length into "temp" */
+ if (fueword32(fs_ep.pLength + n, &temp) != 0) {
+ return (EFAULT);
+ }
+ if (temp > rem) {
+ /* the userland length has been corrupted */
+ DPRINTF("corrupt userland length "
+ "%u > %u\n", temp, rem);
+ fs_ep.status = USB_ERR_INVAL;
+ goto complete;
+ }
+ rem -= temp;
+
+ /* get actual transfer length */
+ length = xfer->frlengths[n];
+ if (length > temp) {
+ /* data overflow */
+ fs_ep.status = USB_ERR_INVAL;
+ DPRINTF("data overflow %u > %u\n",
+ length, temp);
+ goto complete;
+ }
+ if (isread) {
+ /* we need to know the destination buffer */
+ error = ugen_fs_getbuffer(&uaddr, f, fs_ep.ppBuffer, n);
+ if (error) {
+ return (error);
+ }
+ if (xfer->flags_int.isochronous_xfr) {
+ /* only one frame buffer */
+ kaddr = USB_ADD_BYTES(
+ xfer->frbuffers[0].buffer, offset);
+ } else {
+ /* multiple frame buffers */
+ kaddr = xfer->frbuffers[n].buffer;
+ }
+
+ /* move data */
+ error = copyout(kaddr, uaddr, length);
+ if (error) {
+ goto complete;
+ }
+ }
+ /*
+ * Update offset according to initial length, which is
+ * needed by isochronous transfers!
+ */
+ offset += temp;
+
+ /* update length */
+ if (suword32(fs_ep.pLength + n, length) != 0)
+ goto complete;
+ }
+
+complete:
+ if (error == 0)
+ error = ugen_fs_update(&fs_ep, f, ep_index);
+ return (error);
+}
+
+static uint8_t
+ugen_fifo_in_use(struct usb_fifo *f, int fflags)
+{
+ struct usb_fifo *f_rx;
+ struct usb_fifo *f_tx;
+
+ f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX];
+ f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX];
+
+ if ((fflags & FREAD) && f_rx &&
+ (f_rx->xfer[0] || f_rx->xfer[1])) {
+ return (1); /* RX FIFO in use */
+ }
+ if ((fflags & FWRITE) && f_tx &&
+ (f_tx->xfer[0] || f_tx->xfer[1])) {
+ return (1); /* TX FIFO in use */
+ }
+ return (0); /* not in use */
+}
+
+static int
+ugen_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags)
+{
+ union {
+ struct usb_fs_complete *pcomp;
+ struct usb_fs_start *pstart;
+ struct usb_fs_stop *pstop;
+ void *addr;
+ } u;
+ struct usb_xfer *xfer;
+ int error;
+ uint8_t ep_index;
+
+ u.addr = addr;
+
+ DPRINTFN(6, "cmd=0x%08lx\n", cmd);
+
+ switch (cmd) {
+ case USB_FS_COMPLETE:
+ sx_slock(&f->fs_fastpath_lock);
+ mtx_lock(f->priv_mtx);
+ error = ugen_fs_get_complete(f, &ep_index);
+ mtx_unlock(f->priv_mtx);
+
+ if (error != 0) {
+ error = EBUSY;
+ } else {
+ u.pcomp->ep_index = ep_index;
+ error = ugen_fs_copy_out(f, u.pcomp->ep_index);
+ }
+ sx_sunlock(&f->fs_fastpath_lock);
+ break;
+
+ case USB_FS_START:
+ sx_slock(&f->fs_fastpath_lock);
+ error = ugen_fs_copy_in(f, u.pstart->ep_index);
+ if (error == 0) {
+ mtx_lock(f->priv_mtx);
+ xfer = f->fs_xfer[u.pstart->ep_index];
+ usbd_transfer_start(xfer);
+ mtx_unlock(f->priv_mtx);
+ }
+ sx_sunlock(&f->fs_fastpath_lock);
+ break;
+
+ case USB_FS_STOP:
+ mtx_lock(f->priv_mtx);
+ if (u.pstop->ep_index >= f->fs_ep_max) {
+ error = EINVAL;
+ } else {
+ error = 0;
+ xfer = f->fs_xfer[u.pstart->ep_index];
+ if (usbd_transfer_pending(xfer)) {
+ usbd_transfer_stop(xfer);
+
+ /*
+ * Check if the USB transfer was
+ * stopped before it was even started
+ * and fake a cancel event.
+ */
+ if (!xfer->flags_int.transferring &&
+ !xfer->flags_int.started) {
+ DPRINTF("Issuing fake completion event\n");
+ ugen_fs_set_complete(xfer->priv_sc,
+ USB_P2U(xfer->priv_fifo));
+ }
+ }
+ }
+ mtx_unlock(f->priv_mtx);
+ break;
+
+ default:
+ error = ENOIOCTL;
+ break;
+ }
+
+ DPRINTFN(6, "error=%d\n", error);
+
+ return (error);
+}
+
+static int
+ugen_set_short_xfer(struct usb_fifo *f, void *addr)
+{
+ uint8_t t;
+
+ if (*(int *)addr)
+ t = 1;
+ else
+ t = 0;
+
+ if (f->flag_short == t) {
+ /* same value like before - accept */
+ return (0);
+ }
+ if (f->xfer[0] || f->xfer[1]) {
+ /* cannot change this during transfer */
+ return (EBUSY);
+ }
+ f->flag_short = t;
+ return (0);
+}
+
+static int
+ugen_set_timeout(struct usb_fifo *f, void *addr)
+{
+ f->timeout = *(int *)addr;
+ if (f->timeout > 65535) {
+ /* limit user input */
+ f->timeout = 65535;
+ }
+ return (0);
+}
+
+static int
+ugen_get_frame_size(struct usb_fifo *f, void *addr)
+{
+ if (f->xfer[0]) {
+ *(int *)addr = f->xfer[0]->max_frame_size;
+ } else {
+ return (EINVAL);
+ }
+ return (0);
+}
+
+static int
+ugen_set_buffer_size(struct usb_fifo *f, void *addr)
+{
+ usb_frlength_t t;
+
+ if (*(int *)addr < 0)
+ t = 0; /* use "wMaxPacketSize" */
+ else if (*(int *)addr < (256 * 1024))
+ t = *(int *)addr;
+ else
+ t = 256 * 1024;
+
+ if (f->bufsize == t) {
+ /* same value like before - accept */
+ return (0);
+ }
+ if (f->xfer[0] || f->xfer[1]) {
+ /* cannot change this during transfer */
+ return (EBUSY);
+ }
+ f->bufsize = t;
+ return (0);
+}
+
+static int
+ugen_get_buffer_size(struct usb_fifo *f, void *addr)
+{
+ *(int *)addr = f->bufsize;
+ return (0);
+}
+
+static int
+ugen_get_iface_desc(struct usb_fifo *f,
+ struct usb_interface_descriptor *idesc)
+{
+ struct usb_interface *iface;
+
+ iface = usbd_get_iface(f->udev, f->iface_index);
+ if (iface && iface->idesc) {
+ *idesc = *(iface->idesc);
+ } else {
+ return (EIO);
+ }
+ return (0);
+}
+
+static int
+ugen_get_endpoint_desc(struct usb_fifo *f,
+ struct usb_endpoint_descriptor *ed)
+{
+ struct usb_endpoint *ep;
+
+ ep = usb_fifo_softc(f);
+
+ if (ep && ep->edesc) {
+ *ed = *ep->edesc;
+ } else {
+ return (EINVAL);
+ }
+ return (0);
+}
+
+static int
+ugen_set_power_mode(struct usb_fifo *f, int mode)
+{
+ struct usb_device *udev = f->udev;
+ int err;
+ uint8_t old_mode;
+
+ if ((udev == NULL) ||
+ (udev->parent_hub == NULL)) {
+ return (EINVAL);
+ }
+ err = priv_check(curthread, PRIV_DRIVER);
+ if (err)
+ return (err);
+
+ /* get old power mode */
+ old_mode = udev->power_mode;
+
+ /* if no change, then just return */
+ if (old_mode == mode)
+ return (0);
+
+ switch (mode) {
+ case USB_POWER_MODE_OFF:
+ if (udev->flags.usb_mode == USB_MODE_HOST &&
+ udev->re_enumerate_wait == USB_RE_ENUM_DONE) {
+ udev->re_enumerate_wait = USB_RE_ENUM_PWR_OFF;
+ }
+ /* set power mode will wake up the explore thread */
+ break;
+
+ case USB_POWER_MODE_ON:
+ case USB_POWER_MODE_SAVE:
+ break;
+
+ case USB_POWER_MODE_RESUME:
+#if USB_HAVE_POWERD
+ /* let USB-powerd handle resume */
+ USB_BUS_LOCK(udev->bus);
+ udev->pwr_save.write_refs++;
+ udev->pwr_save.last_xfer_time = ticks;
+ USB_BUS_UNLOCK(udev->bus);
+
+ /* set new power mode */
+ usbd_set_power_mode(udev, USB_POWER_MODE_SAVE);
+
+ /* wait for resume to complete */
+ usb_pause_mtx(NULL, hz / 4);
+
+ /* clear write reference */
+ USB_BUS_LOCK(udev->bus);
+ udev->pwr_save.write_refs--;
+ USB_BUS_UNLOCK(udev->bus);
+#endif
+ mode = USB_POWER_MODE_SAVE;
+ break;
+
+ case USB_POWER_MODE_SUSPEND:
+#if USB_HAVE_POWERD
+ /* let USB-powerd handle suspend */
+ USB_BUS_LOCK(udev->bus);
+ udev->pwr_save.last_xfer_time = ticks - (256 * hz);
+ USB_BUS_UNLOCK(udev->bus);
+#endif
+ mode = USB_POWER_MODE_SAVE;
+ break;
+
+ default:
+ return (EINVAL);
+ }
+
+ if (err)
+ return (ENXIO); /* I/O failure */
+
+ /* if we are powered off we need to re-enumerate first */
+ if (old_mode == USB_POWER_MODE_OFF) {
+ if (udev->flags.usb_mode == USB_MODE_HOST &&
+ udev->re_enumerate_wait == USB_RE_ENUM_DONE) {
+ udev->re_enumerate_wait = USB_RE_ENUM_START;
+ }
+ /* set power mode will wake up the explore thread */
+ }
+
+ /* set new power mode */
+ usbd_set_power_mode(udev, mode);
+
+ return (0); /* success */
+}
+
+static int
+ugen_get_power_mode(struct usb_fifo *f)
+{
+ struct usb_device *udev = f->udev;
+
+ if (udev == NULL)
+ return (USB_POWER_MODE_ON);
+
+ return (udev->power_mode);
+}
+
+static int
+ugen_get_port_path(struct usb_fifo *f, struct usb_device_port_path *dpp)
+{
+ struct usb_device *udev = f->udev;
+ struct usb_device *next;
+ unsigned nlevel = 0;
+
+ if (udev == NULL)
+ goto error;
+
+ dpp->udp_bus = device_get_unit(udev->bus->bdev);
+ dpp->udp_index = udev->device_index;
+
+ /* count port levels */
+ next = udev;
+ while (next->parent_hub != NULL) {
+ nlevel++;
+ next = next->parent_hub;
+ }
+
+ /* check if too many levels */
+ if (nlevel > USB_DEVICE_PORT_PATH_MAX)
+ goto error;
+
+ /* store total level of ports */
+ dpp->udp_port_level = nlevel;
+
+ /* store port index array */
+ next = udev;
+ while (next->parent_hub != NULL) {
+ dpp->udp_port_no[--nlevel] = next->port_no;
+ next = next->parent_hub;
+ }
+ return (0); /* success */
+
+error:
+ return (EINVAL); /* failure */
+}
+
+static int
+ugen_get_power_usage(struct usb_fifo *f)
+{
+ struct usb_device *udev = f->udev;
+
+ if (udev == NULL)
+ return (0);
+
+ return (udev->power);
+}
+
+static int
+ugen_do_port_feature(struct usb_fifo *f, uint8_t port_no,
+ uint8_t set, uint16_t feature)
+{
+ struct usb_device *udev = f->udev;
+ struct usb_hub *hub;
+ int err;
+
+ err = priv_check(curthread, PRIV_DRIVER);
+ if (err) {
+ return (err);
+ }
+ if (port_no == 0) {
+ return (EINVAL);
+ }
+ if ((udev == NULL) ||
+ (udev->hub == NULL)) {
+ return (EINVAL);
+ }
+ hub = udev->hub;
+
+ if (port_no > hub->nports) {
+ return (EINVAL);
+ }
+ if (set)
+ err = usbd_req_set_port_feature(udev,
+ NULL, port_no, feature);
+ else
+ err = usbd_req_clear_port_feature(udev,
+ NULL, port_no, feature);
+
+ if (err)
+ return (ENXIO); /* failure */
+
+ return (0); /* success */
+}
+
+static int
+ugen_iface_ioctl(struct usb_fifo *f, u_long cmd, void *addr, int fflags)
+{
+ struct usb_fifo *f_rx;
+ struct usb_fifo *f_tx;
+ int error = 0;
+
+ f_rx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_RX];
+ f_tx = f->udev->fifo[(f->fifo_index & ~1) + USB_FIFO_TX];
+
+ switch (cmd) {
+ case USB_SET_RX_SHORT_XFER:
+ if (fflags & FREAD) {
+ error = ugen_set_short_xfer(f_rx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_SET_TX_FORCE_SHORT:
+ if (fflags & FWRITE) {
+ error = ugen_set_short_xfer(f_tx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_SET_RX_TIMEOUT:
+ if (fflags & FREAD) {
+ error = ugen_set_timeout(f_rx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_SET_TX_TIMEOUT:
+ if (fflags & FWRITE) {
+ error = ugen_set_timeout(f_tx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_GET_RX_FRAME_SIZE:
+ if (fflags & FREAD) {
+ error = ugen_get_frame_size(f_rx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_GET_TX_FRAME_SIZE:
+ if (fflags & FWRITE) {
+ error = ugen_get_frame_size(f_tx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_SET_RX_BUFFER_SIZE:
+ if (fflags & FREAD) {
+ error = ugen_set_buffer_size(f_rx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_SET_TX_BUFFER_SIZE:
+ if (fflags & FWRITE) {
+ error = ugen_set_buffer_size(f_tx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_GET_RX_BUFFER_SIZE:
+ if (fflags & FREAD) {
+ error = ugen_get_buffer_size(f_rx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_GET_TX_BUFFER_SIZE:
+ if (fflags & FWRITE) {
+ error = ugen_get_buffer_size(f_tx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_GET_RX_INTERFACE_DESC:
+ if (fflags & FREAD) {
+ error = ugen_get_iface_desc(f_rx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_GET_TX_INTERFACE_DESC:
+ if (fflags & FWRITE) {
+ error = ugen_get_iface_desc(f_tx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_GET_RX_ENDPOINT_DESC:
+ if (fflags & FREAD) {
+ error = ugen_get_endpoint_desc(f_rx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_GET_TX_ENDPOINT_DESC:
+ if (fflags & FWRITE) {
+ error = ugen_get_endpoint_desc(f_tx, addr);
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_SET_RX_STALL_FLAG:
+ if ((fflags & FREAD) && (*(int *)addr)) {
+ f_rx->flag_stall = 1;
+ }
+ break;
+
+ case USB_SET_TX_STALL_FLAG:
+ if ((fflags & FWRITE) && (*(int *)addr)) {
+ f_tx->flag_stall = 1;
+ }
+ break;
+
+ default:
+ error = ENOIOCTL;
+ break;
+ }
+ return (error);
+}
+
+static int
+ugen_ioctl_post(struct usb_fifo *f, u_long cmd, void *addr, int fflags)
+{
+ union {
+ struct usb_interface_descriptor *idesc;
+ struct usb_alt_interface *ai;
+ struct usb_device_descriptor *ddesc;
+ struct usb_config_descriptor *cdesc;
+ struct usb_device_stats *stat;
+ struct usb_fs_init *pinit;
+#ifdef COMPAT_FREEBSD32
+ struct usb_fs_init32 *pinit32;
+#endif
+ struct usb_fs_uninit *puninit;
+ struct usb_fs_open *popen;
+ struct usb_fs_open_stream *popen_stream;
+ struct usb_fs_close *pclose;
+ struct usb_fs_clear_stall_sync *pstall;
+ struct usb_device_port_path *dpp;
+ uint32_t *ptime;
+ void *addr;
+ int *pint;
+ } u;
+ struct usb_device_descriptor *dtemp;
+ struct usb_config_descriptor *ctemp;
+ struct usb_interface *iface;
+ int error = 0;
+ uint8_t n;
+
+ u.addr = addr;
+
+ DPRINTFN(6, "cmd=0x%08lx\n", cmd);
+
+ switch (cmd) {
+ case USB_DISCOVER:
+ usb_needs_explore_all();
+ break;
+
+ case USB_SETDEBUG:
+ if (!(fflags & FWRITE)) {
+ error = EPERM;
+ break;
+ }
+ usb_debug = *(int *)addr;
+ break;
+
+ case USB_GET_CONFIG:
+ *(int *)addr = f->udev->curr_config_index;
+ break;
+
+ case USB_SET_CONFIG:
+ if (!(fflags & FWRITE)) {
+ error = EPERM;
+ break;
+ }
+ error = ugen_set_config(f, *(int *)addr);
+ break;
+
+ case USB_GET_ALTINTERFACE:
+ iface = usbd_get_iface(f->udev,
+ u.ai->uai_interface_index);
+ if (iface && iface->idesc) {
+ u.ai->uai_alt_index = iface->alt_index;
+ } else {
+ error = EINVAL;
+ }
+ break;
+
+ case USB_SET_ALTINTERFACE:
+ if (!(fflags & FWRITE)) {
+ error = EPERM;
+ break;
+ }
+ error = ugen_set_interface(f,
+ u.ai->uai_interface_index, u.ai->uai_alt_index);
+ break;
+
+ case USB_GET_DEVICE_DESC:
+ dtemp = usbd_get_device_descriptor(f->udev);
+ if (!dtemp) {
+ error = EIO;
+ break;
+ }
+ *u.ddesc = *dtemp;
+ break;
+
+ case USB_GET_CONFIG_DESC:
+ ctemp = usbd_get_config_descriptor(f->udev);
+ if (!ctemp) {
+ error = EIO;
+ break;
+ }
+ *u.cdesc = *ctemp;
+ break;
+
+ case USB_GET_FULL_DESC:
+ error = ugen_get_cdesc(f, addr);
+ break;
+
+ case USB_GET_STRING_DESC:
+ error = ugen_get_sdesc(f, addr);
+ break;
+
+ case USB_GET_IFACE_DRIVER:
+ error = ugen_get_iface_driver(f, addr);
+ break;
+
+#ifdef COMPAT_FREEBSD32
+ case USB_GET_FULL_DESC32:
+ case USB_GET_STRING_DESC32:
+ case USB_GET_IFACE_DRIVER32:
+ error = ugen_get32(cmd, f, addr);
+ break;
+#endif
+
+ case USB_REQUEST:
+ case USB_DO_REQUEST:
+ if (!(fflags & FWRITE)) {
+ error = EPERM;
+ break;
+ }
+ error = ugen_do_request(f, addr);
+ break;
+
+#ifdef COMPAT_FREEBSD32
+ case USB_REQUEST32:
+ case USB_DO_REQUEST32:
+ if (!(fflags & FWRITE)) {
+ error = EPERM;
+ break;
+ }
+ error = ugen_do_request32(f, addr);
+ break;
+#endif
+
+ case USB_DEVICEINFO:
+ case USB_GET_DEVICEINFO:
+ error = ugen_fill_deviceinfo(f, addr);
+ break;
+
+ case USB_DEVICESTATS:
+ for (n = 0; n != 4; n++) {
+ u.stat->uds_requests_fail[n] =
+ f->udev->stats_err.uds_requests[n];
+ u.stat->uds_requests_ok[n] =
+ f->udev->stats_ok.uds_requests[n];
+ }
+ break;
+
+ case USB_DEVICEENUMERATE:
+ error = ugen_re_enumerate(f);
+ break;
+
+ case USB_GET_PLUGTIME:
+ *u.ptime = f->udev->plugtime;
+ break;
+
+ case USB_CLAIM_INTERFACE:
+ case USB_RELEASE_INTERFACE:
+ /* TODO */
+ break;
+
+ case USB_IFACE_DRIVER_ACTIVE:
+ n = *u.pint & 0xFF;
+ iface = usbd_get_iface(f->udev, n);
+ if (iface != NULL && iface->subdev != NULL &&
+ device_is_alive(iface->subdev))
+ error = 0;
+ else
+ error = ENXIO;
+ break;
+
+ case USB_IFACE_DRIVER_DETACH:
+
+ error = priv_check(curthread, PRIV_DRIVER);
+
+ if (error)
+ break;
+
+ n = *u.pint & 0xFF;
+
+ if (n == USB_IFACE_INDEX_ANY) {
+ error = EINVAL;
+ break;
+ }
+
+ /*
+ * Detach the currently attached driver.
+ */
+ usb_detach_device(f->udev, n, 0);
+
+ /*
+ * Set parent to self, this should keep attach away
+ * until the next set configuration event.
+ */
+ usbd_set_parent_iface(f->udev, n, n);
+ break;
+
+ case USB_SET_POWER_MODE:
+ error = ugen_set_power_mode(f, *u.pint);
+ break;
+
+ case USB_GET_POWER_MODE:
+ *u.pint = ugen_get_power_mode(f);
+ break;
+
+ case USB_GET_DEV_PORT_PATH:
+ error = ugen_get_port_path(f, u.dpp);
+ break;
+
+ case USB_GET_POWER_USAGE:
+ *u.pint = ugen_get_power_usage(f);
+ break;
+
+ case USB_SET_PORT_ENABLE:
+ error = ugen_do_port_feature(f,
+ *u.pint, 1, UHF_PORT_ENABLE);
+ break;
+
+ case USB_SET_PORT_DISABLE:
+ error = ugen_do_port_feature(f,
+ *u.pint, 0, UHF_PORT_ENABLE);
+ break;
+
+ case USB_FS_INIT:
+ error = ugen_fs_init(f, u.pinit->pEndpoints,
+ sizeof(struct usb_fs_endpoint), fflags,
+ u.pinit->ep_index_max);
+ break;
+#ifdef COMPAT_FREEBSD32
+ case USB_FS_INIT32:
+ error = ugen_fs_init(f, PTRIN(u.pinit32->pEndpoints),
+ sizeof(struct usb_fs_endpoint32), fflags,
+ u.pinit32->ep_index_max);
+ break;
+#endif
+ case USB_FS_UNINIT:
+ if (u.puninit->dummy != 0) {
+ error = EINVAL;
+ break;
+ }
+ error = ugen_fs_uninit(f);
+ break;
+
+ case USB_FS_OPEN:
+ case USB_FS_OPEN_STREAM:
+ error = usb_fs_open(f, u.popen, fflags,
+ (cmd == USB_FS_OPEN_STREAM) ? u.popen_stream->stream_id : 0);
+ break;
+
+ case USB_FS_CLOSE:
+ error = usb_fs_close(f, u.pclose);
+ break;
+
+ case USB_FS_CLEAR_STALL_SYNC:
+ error = usb_fs_clear_stall_sync(f, u.pstall);
+ break;
+
+ default:
+ mtx_lock(f->priv_mtx);
+ error = ugen_iface_ioctl(f, cmd, addr, fflags);
+ mtx_unlock(f->priv_mtx);
+ break;
+ }
+ DPRINTFN(6, "error=%d\n", error);
+ return (error);
+}
+
+static void
+ugen_ctrl_fs_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ ; /* workaround for a bug in "indent" */
+
+ DPRINTF("st=%u alen=%u aframes=%u\n",
+ USB_GET_STATE(xfer), xfer->actlen, xfer->aframes);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ ugen_fs_set_complete(xfer->priv_sc, USB_P2U(xfer->priv_fifo));
+ break;
+ }
+}
+
+#ifdef COMPAT_FREEBSD32
+void
+usb_gen_descriptor_from32(struct usb_gen_descriptor *ugd,
+ const struct usb_gen_descriptor32 *ugd32)
+{
+ PTRIN_CP(*ugd32, *ugd, ugd_data);
+ CP(*ugd32, *ugd, ugd_lang_id);
+ CP(*ugd32, *ugd, ugd_maxlen);
+ CP(*ugd32, *ugd, ugd_actlen);
+ CP(*ugd32, *ugd, ugd_offset);
+ CP(*ugd32, *ugd, ugd_config_index);
+ CP(*ugd32, *ugd, ugd_string_index);
+ CP(*ugd32, *ugd, ugd_iface_index);
+ CP(*ugd32, *ugd, ugd_altif_index);
+ CP(*ugd32, *ugd, ugd_endpt_index);
+ CP(*ugd32, *ugd, ugd_report_type);
+ /* Don't copy reserved */
+}
+
+void
+update_usb_gen_descriptor32(struct usb_gen_descriptor32 *ugd32,
+ struct usb_gen_descriptor *ugd)
+{
+ /* Don't update ugd_data pointer */
+ CP(*ugd32, *ugd, ugd_lang_id);
+ CP(*ugd32, *ugd, ugd_maxlen);
+ CP(*ugd32, *ugd, ugd_actlen);
+ CP(*ugd32, *ugd, ugd_offset);
+ CP(*ugd32, *ugd, ugd_config_index);
+ CP(*ugd32, *ugd, ugd_string_index);
+ CP(*ugd32, *ugd, ugd_iface_index);
+ CP(*ugd32, *ugd, ugd_altif_index);
+ CP(*ugd32, *ugd, ugd_endpt_index);
+ CP(*ugd32, *ugd, ugd_report_type);
+ /* Don't update reserved */
+}
+
+static int
+ugen_get32(u_long cmd, struct usb_fifo *f, struct usb_gen_descriptor32 *ugd32)
+{
+ struct usb_gen_descriptor ugd;
+ int error;
+
+ usb_gen_descriptor_from32(&ugd, ugd32);
+ switch (cmd) {
+ case USB_GET_FULL_DESC32:
+ error = ugen_get_cdesc(f, &ugd);
+ break;
+
+ case USB_GET_STRING_DESC32:
+ error = ugen_get_sdesc(f, &ugd);
+ break;
+
+ case USB_GET_IFACE_DRIVER32:
+ error = ugen_get_iface_driver(f, &ugd);
+ break;
+ default:
+ /* Can't happen except by programmer error */
+ panic("%s: called with invalid cmd %lx", __func__, cmd);
+ }
+ update_usb_gen_descriptor32(ugd32, &ugd);
+
+ return (error);
+}
+
+#endif /* COMPAT_FREEBSD32 */
+
+#endif /* USB_HAVE_UGEN */
diff --git a/sys/dev/usb/usb_generic.h b/sys/dev/usb/usb_generic.h
new file mode 100644
index 000000000000..9b93b22f4254
--- /dev/null
+++ b/sys/dev/usb/usb_generic.h
@@ -0,0 +1,35 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_GENERIC_H_
+#define _USB_GENERIC_H_
+
+extern struct usb_fifo_methods usb_ugen_methods;
+int ugen_do_request(struct usb_fifo *f, struct usb_ctl_request *ur);
+int ugen_fill_deviceinfo(struct usb_fifo *f, struct usb_device_info *di);
+
+#endif /* _USB_GENERIC_H_ */
diff --git a/sys/dev/usb/usb_handle_request.c b/sys/dev/usb/usb_handle_request.c
new file mode 100644
index 000000000000..7b50400d7eb9
--- /dev/null
+++ b/sys/dev/usb/usb_handle_request.c
@@ -0,0 +1,811 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usb_if.h"
+
+#define USB_DEBUG_VAR usb_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_dynamic.h>
+#include <dev/usb/usb_hub.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+/* function prototypes */
+
+static uint8_t usb_handle_get_stall(struct usb_device *, uint8_t);
+static usb_error_t usb_handle_remote_wakeup(struct usb_xfer *, uint8_t);
+static usb_error_t usb_handle_request(struct usb_xfer *);
+static usb_error_t usb_handle_set_config(struct usb_xfer *, uint8_t);
+static usb_error_t usb_handle_set_stall(struct usb_xfer *, uint8_t,
+ uint8_t);
+static usb_error_t usb_handle_iface_request(struct usb_xfer *, void **,
+ uint16_t *, struct usb_device_request, uint16_t,
+ uint8_t);
+
+/*------------------------------------------------------------------------*
+ * usb_handle_request_callback
+ *
+ * This function is the USB callback for generic USB Device control
+ * transfers.
+ *------------------------------------------------------------------------*/
+void
+usb_handle_request_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ usb_error_t err;
+
+ /* check the current transfer state */
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+
+ /* handle the request */
+ err = usb_handle_request(xfer);
+
+ if (err) {
+ if (err == USB_ERR_BAD_CONTEXT) {
+ /* we need to re-setup the control transfer */
+ usb_needs_explore(xfer->xroot->bus, 0);
+ break;
+ }
+ goto tr_restart;
+ }
+ usbd_transfer_submit(xfer);
+ break;
+
+ default:
+ /* check if a control transfer is active */
+ if (xfer->flags_int.control_rem != 0xFFFF) {
+ /* handle the request */
+ err = usb_handle_request(xfer);
+ }
+ if (xfer->error != USB_ERR_CANCELLED) {
+ /* should not happen - try stalling */
+ goto tr_restart;
+ }
+ break;
+ }
+ return;
+
+tr_restart:
+ /*
+ * If a control transfer is active, stall it, and wait for the
+ * next control transfer.
+ */
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(struct usb_device_request));
+ xfer->nframes = 1;
+ xfer->flags.manual_status = 1;
+ xfer->flags.force_short_xfer = 0;
+ usbd_xfer_set_stall(xfer); /* cancel previous transfer, if any */
+ usbd_transfer_submit(xfer);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_handle_set_config
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static usb_error_t
+usb_handle_set_config(struct usb_xfer *xfer, uint8_t conf_no)
+{
+ struct usb_device *udev = xfer->xroot->udev;
+ usb_error_t err = 0;
+ uint8_t do_unlock;
+
+ /*
+ * We need to protect against other threads doing probe and
+ * attach:
+ */
+ USB_XFER_UNLOCK(xfer);
+
+ /* Prevent re-enumeration */
+ do_unlock = usbd_enum_lock(udev);
+
+ if (conf_no == USB_UNCONFIG_NO) {
+ conf_no = USB_UNCONFIG_INDEX;
+ } else {
+ /*
+ * The relationship between config number and config index
+ * is very simple in our case:
+ */
+ conf_no--;
+ }
+
+ if (usbd_set_config_index(udev, conf_no)) {
+ DPRINTF("set config %d failed\n", conf_no);
+ err = USB_ERR_STALLED;
+ goto done;
+ }
+ if (usb_probe_and_attach(udev, USB_IFACE_INDEX_ANY)) {
+ DPRINTF("probe and attach failed\n");
+ err = USB_ERR_STALLED;
+ goto done;
+ }
+done:
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+ USB_XFER_LOCK(xfer);
+ return (err);
+}
+
+static usb_error_t
+usb_check_alt_setting(struct usb_device *udev,
+ struct usb_interface *iface, uint8_t alt_index)
+{
+ uint8_t do_unlock;
+ usb_error_t err = 0;
+
+ /* Prevent re-enumeration */
+ do_unlock = usbd_enum_lock(udev);
+
+ if (alt_index >= usbd_get_no_alts(udev->cdesc, iface->idesc))
+ err = USB_ERR_INVAL;
+
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_handle_iface_request
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static usb_error_t
+usb_handle_iface_request(struct usb_xfer *xfer,
+ void **ppdata, uint16_t *plen,
+ struct usb_device_request req, uint16_t off, uint8_t state)
+{
+ struct usb_interface *iface;
+ struct usb_interface *iface_parent; /* parent interface */
+ struct usb_device *udev = xfer->xroot->udev;
+ int error;
+ uint8_t iface_index;
+ uint8_t temp_state;
+ uint8_t do_unlock;
+
+ if ((req.bmRequestType & 0x1F) == UT_INTERFACE) {
+ iface_index = req.wIndex[0]; /* unicast */
+ } else {
+ iface_index = 0; /* broadcast */
+ }
+
+ /*
+ * We need to protect against other threads doing probe and
+ * attach:
+ */
+ USB_XFER_UNLOCK(xfer);
+
+ /* Prevent re-enumeration */
+ do_unlock = usbd_enum_lock(udev);
+
+ error = ENXIO;
+
+tr_repeat:
+ iface = usbd_get_iface(udev, iface_index);
+ if ((iface == NULL) ||
+ (iface->idesc == NULL)) {
+ /* end of interfaces non-existing interface */
+ goto tr_stalled;
+ }
+ /* set initial state */
+
+ temp_state = state;
+
+ /* forward request to interface, if any */
+
+ if ((error != 0) &&
+ (error != ENOTTY) &&
+ (iface->subdev != NULL) &&
+ device_is_attached(iface->subdev)) {
+#if 0
+ DEVMETHOD(usb_handle_request, NULL); /* dummy */
+#endif
+ error = USB_HANDLE_REQUEST(iface->subdev,
+ &req, ppdata, plen,
+ off, &temp_state);
+ }
+ iface_parent = usbd_get_iface(udev, iface->parent_iface_index);
+
+ if ((iface_parent == NULL) ||
+ (iface_parent->idesc == NULL)) {
+ /* non-existing interface */
+ iface_parent = NULL;
+ }
+ /* forward request to parent interface, if any */
+
+ if ((error != 0) &&
+ (error != ENOTTY) &&
+ (iface_parent != NULL) &&
+ (iface_parent->subdev != NULL) &&
+ ((req.bmRequestType & 0x1F) == UT_INTERFACE) &&
+ (iface_parent->subdev != iface->subdev) &&
+ device_is_attached(iface_parent->subdev)) {
+ error = USB_HANDLE_REQUEST(iface_parent->subdev,
+ &req, ppdata, plen, off, &temp_state);
+ }
+ if (error == 0) {
+ /* negativly adjust pointer and length */
+ *ppdata = ((uint8_t *)(*ppdata)) - off;
+ *plen += off;
+
+ if ((state == USB_HR_NOT_COMPLETE) &&
+ (temp_state == USB_HR_COMPLETE_OK))
+ goto tr_short;
+ else
+ goto tr_valid;
+ } else if (error == ENOTTY) {
+ goto tr_stalled;
+ }
+ if ((req.bmRequestType & 0x1F) != UT_INTERFACE) {
+ iface_index++; /* iterate */
+ goto tr_repeat;
+ }
+ if (state != USB_HR_NOT_COMPLETE) {
+ /* we are complete */
+ goto tr_valid;
+ }
+ switch (req.bmRequestType) {
+ case UT_WRITE_INTERFACE:
+ switch (req.bRequest) {
+ case UR_SET_INTERFACE:
+ /*
+ * We assume that the endpoints are the same
+ * across the alternate settings.
+ *
+ * Reset the endpoints, because re-attaching
+ * only a part of the device is not possible.
+ */
+ error = usb_check_alt_setting(udev,
+ iface, req.wValue[0]);
+ if (error) {
+ DPRINTF("alt setting does not exist %s\n",
+ usbd_errstr(error));
+ goto tr_stalled;
+ }
+ error = usb_reset_iface_endpoints(udev, iface_index);
+ if (error) {
+ DPRINTF("alt setting failed %s\n",
+ usbd_errstr(error));
+ goto tr_stalled;
+ }
+ /* update the current alternate setting */
+ iface->alt_index = req.wValue[0];
+ break;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_INTERFACE:
+ switch (req.bRequest) {
+ case UR_GET_INTERFACE:
+ *ppdata = &iface->alt_index;
+ *plen = 1;
+ break;
+
+ default:
+ goto tr_stalled;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+tr_valid:
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+ USB_XFER_LOCK(xfer);
+ return (0);
+
+tr_short:
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+ USB_XFER_LOCK(xfer);
+ return (USB_ERR_SHORT_XFER);
+
+tr_stalled:
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+ USB_XFER_LOCK(xfer);
+ return (USB_ERR_STALLED);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_handle_stall
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static usb_error_t
+usb_handle_set_stall(struct usb_xfer *xfer, uint8_t ep, uint8_t do_stall)
+{
+ struct usb_device *udev = xfer->xroot->udev;
+ usb_error_t err;
+
+ USB_XFER_UNLOCK(xfer);
+ err = usbd_set_endpoint_stall(udev,
+ usbd_get_ep_by_addr(udev, ep), do_stall);
+ USB_XFER_LOCK(xfer);
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_handle_get_stall
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static uint8_t
+usb_handle_get_stall(struct usb_device *udev, uint8_t ea_val)
+{
+ struct usb_endpoint *ep;
+ uint8_t halted;
+
+ ep = usbd_get_ep_by_addr(udev, ea_val);
+ if (ep == NULL) {
+ /* nothing to do */
+ return (0);
+ }
+ USB_BUS_LOCK(udev->bus);
+ halted = ep->is_stalled;
+ USB_BUS_UNLOCK(udev->bus);
+
+ return (halted);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_handle_remote_wakeup
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static usb_error_t
+usb_handle_remote_wakeup(struct usb_xfer *xfer, uint8_t is_on)
+{
+ struct usb_device *udev;
+ struct usb_bus *bus;
+
+ udev = xfer->xroot->udev;
+ bus = udev->bus;
+
+ USB_BUS_LOCK(bus);
+
+ if (is_on) {
+ udev->flags.remote_wakeup = 1;
+ } else {
+ udev->flags.remote_wakeup = 0;
+ }
+
+ USB_BUS_UNLOCK(bus);
+
+#if USB_HAVE_POWERD
+ /* In case we are out of sync, update the power state. */
+ usb_bus_power_update(udev->bus);
+#endif
+ return (0); /* success */
+}
+
+/*------------------------------------------------------------------------*
+ * usb_handle_request
+ *
+ * Internal state sequence:
+ *
+ * USB_HR_NOT_COMPLETE -> USB_HR_COMPLETE_OK v USB_HR_COMPLETE_ERR
+ *
+ * Returns:
+ * 0: Ready to start hardware
+ * Else: Stall current transfer, if any
+ *------------------------------------------------------------------------*/
+static usb_error_t
+usb_handle_request(struct usb_xfer *xfer)
+{
+ struct usb_device_request req;
+ struct usb_device *udev;
+ const void *src_zcopy; /* zero-copy source pointer */
+ const void *src_mcopy; /* non zero-copy source pointer */
+ uint16_t off; /* data offset */
+ uint16_t rem; /* data remainder */
+ uint16_t max_len; /* max fragment length */
+ uint16_t wValue;
+ uint8_t state;
+ uint8_t is_complete = 1;
+ usb_error_t err;
+ union {
+ uWord wStatus;
+ uint8_t buf[2];
+ } temp;
+
+ /*
+ * Filter the USB transfer state into
+ * something which we understand:
+ */
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ state = USB_HR_NOT_COMPLETE;
+
+ if (!xfer->flags_int.control_act) {
+ /* nothing to do */
+ goto tr_stalled;
+ }
+ break;
+ case USB_ST_TRANSFERRED:
+ if (!xfer->flags_int.control_act) {
+ state = USB_HR_COMPLETE_OK;
+ } else {
+ state = USB_HR_NOT_COMPLETE;
+ }
+ break;
+ default:
+ state = USB_HR_COMPLETE_ERR;
+ break;
+ }
+
+ /* reset frame stuff */
+
+ usbd_xfer_set_frame_len(xfer, 0, 0);
+
+ usbd_xfer_set_frame_offset(xfer, 0, 0);
+ usbd_xfer_set_frame_offset(xfer, sizeof(req), 1);
+
+ /* get the current request, if any */
+
+ usbd_copy_out(xfer->frbuffers, 0, &req, sizeof(req));
+
+ if (xfer->flags_int.control_rem == 0xFFFF) {
+ /* first time - not initialised */
+ rem = UGETW(req.wLength);
+ off = 0;
+ } else {
+ /* not first time - initialised */
+ rem = xfer->flags_int.control_rem;
+ off = UGETW(req.wLength) - rem;
+ }
+
+ /* set some defaults */
+
+ max_len = 0;
+ src_zcopy = NULL;
+ src_mcopy = NULL;
+ udev = xfer->xroot->udev;
+
+ /* get some request fields decoded */
+
+ wValue = UGETW(req.wValue);
+
+ DPRINTF("req 0x%02x 0x%02x 0x%04x 0x%04x "
+ "off=0x%x rem=0x%x, state=%d\n", req.bmRequestType,
+ req.bRequest, wValue, UGETW(req.wIndex), off, rem, state);
+
+ /* demultiplex the control request */
+
+ switch (req.bmRequestType) {
+ case UT_READ_DEVICE:
+ if (state != USB_HR_NOT_COMPLETE) {
+ break;
+ }
+ switch (req.bRequest) {
+ case UR_GET_DESCRIPTOR:
+ goto tr_handle_get_descriptor;
+ case UR_GET_CONFIG:
+ goto tr_handle_get_config;
+ case UR_GET_STATUS:
+ goto tr_handle_get_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_DEVICE:
+ switch (req.bRequest) {
+ case UR_SET_ADDRESS:
+ goto tr_handle_set_address;
+ case UR_SET_CONFIG:
+ goto tr_handle_set_config;
+ case UR_CLEAR_FEATURE:
+ switch (wValue) {
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_clear_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SET_FEATURE:
+ switch (wValue) {
+ case UF_DEVICE_REMOTE_WAKEUP:
+ goto tr_handle_set_wakeup;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_WRITE_ENDPOINT:
+ switch (req.bRequest) {
+ case UR_CLEAR_FEATURE:
+ switch (wValue) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_clear_halt;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ case UR_SET_FEATURE:
+ switch (wValue) {
+ case UF_ENDPOINT_HALT:
+ goto tr_handle_set_halt;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ default:
+ goto tr_stalled;
+ }
+ break;
+
+ case UT_READ_ENDPOINT:
+ switch (req.bRequest) {
+ case UR_GET_STATUS:
+ goto tr_handle_get_ep_status;
+ default:
+ goto tr_stalled;
+ }
+ break;
+ default:
+ /* we use "USB_ADD_BYTES" to de-const the src_zcopy */
+ err = usb_handle_iface_request(xfer,
+ USB_ADD_BYTES(&src_zcopy, 0),
+ &max_len, req, off, state);
+ if (err == 0) {
+ is_complete = 0;
+ goto tr_valid;
+ } else if (err == USB_ERR_SHORT_XFER) {
+ goto tr_valid;
+ }
+ /*
+ * Reset zero-copy pointer and max length
+ * variable in case they were unintentionally
+ * set:
+ */
+ src_zcopy = NULL;
+ max_len = 0;
+
+ /*
+ * Check if we have a vendor specific
+ * descriptor:
+ */
+ goto tr_handle_get_descriptor;
+ }
+ goto tr_valid;
+
+tr_handle_get_descriptor:
+ err = (usb_temp_get_desc_p) (udev, &req, &src_zcopy, &max_len);
+ if (err)
+ goto tr_stalled;
+ if (src_zcopy == NULL)
+ goto tr_stalled;
+ goto tr_valid;
+
+tr_handle_get_config:
+ temp.buf[0] = udev->curr_config_no;
+ src_mcopy = temp.buf;
+ max_len = 1;
+ goto tr_valid;
+
+tr_handle_get_status:
+
+ wValue = 0;
+
+ USB_BUS_LOCK(udev->bus);
+ if (udev->flags.remote_wakeup) {
+ wValue |= UDS_REMOTE_WAKEUP;
+ }
+ if (udev->flags.self_powered) {
+ wValue |= UDS_SELF_POWERED;
+ }
+ USB_BUS_UNLOCK(udev->bus);
+
+ USETW(temp.wStatus, wValue);
+ src_mcopy = temp.wStatus;
+ max_len = sizeof(temp.wStatus);
+ goto tr_valid;
+
+tr_handle_set_address:
+ if (state == USB_HR_NOT_COMPLETE) {
+ if (wValue >= 0x80) {
+ /* invalid value */
+ goto tr_stalled;
+ } else if (udev->curr_config_no != 0) {
+ /* we are configured ! */
+ goto tr_stalled;
+ }
+ } else if (state != USB_HR_NOT_COMPLETE) {
+ udev->address = (wValue & 0x7F);
+ goto tr_bad_context;
+ }
+ goto tr_valid;
+
+tr_handle_set_config:
+ if (state == USB_HR_NOT_COMPLETE) {
+ if (usb_handle_set_config(xfer, req.wValue[0])) {
+ goto tr_stalled;
+ }
+ }
+ goto tr_valid;
+
+tr_handle_clear_halt:
+ if (state == USB_HR_NOT_COMPLETE) {
+ if (usb_handle_set_stall(xfer, req.wIndex[0], 0)) {
+ goto tr_stalled;
+ }
+ }
+ goto tr_valid;
+
+tr_handle_clear_wakeup:
+ if (state == USB_HR_NOT_COMPLETE) {
+ if (usb_handle_remote_wakeup(xfer, 0)) {
+ goto tr_stalled;
+ }
+ }
+ goto tr_valid;
+
+tr_handle_set_halt:
+ if (state == USB_HR_NOT_COMPLETE) {
+ if (usb_handle_set_stall(xfer, req.wIndex[0], 1)) {
+ goto tr_stalled;
+ }
+ }
+ goto tr_valid;
+
+tr_handle_set_wakeup:
+ if (state == USB_HR_NOT_COMPLETE) {
+ if (usb_handle_remote_wakeup(xfer, 1)) {
+ goto tr_stalled;
+ }
+ }
+ goto tr_valid;
+
+tr_handle_get_ep_status:
+ if (state == USB_HR_NOT_COMPLETE) {
+ temp.wStatus[0] =
+ usb_handle_get_stall(udev, req.wIndex[0]);
+ temp.wStatus[1] = 0;
+ src_mcopy = temp.wStatus;
+ max_len = sizeof(temp.wStatus);
+ }
+ goto tr_valid;
+
+tr_valid:
+ if (state != USB_HR_NOT_COMPLETE) {
+ goto tr_stalled;
+ }
+ /* subtract offset from length */
+
+ max_len -= off;
+
+ /* Compute the real maximum data length */
+
+ if (max_len > xfer->max_data_length) {
+ max_len = usbd_xfer_max_len(xfer);
+ }
+ if (max_len > rem) {
+ max_len = rem;
+ }
+ /*
+ * If the remainder is greater than the maximum data length,
+ * we need to truncate the value for the sake of the
+ * comparison below:
+ */
+ if (rem > xfer->max_data_length) {
+ rem = usbd_xfer_max_len(xfer);
+ }
+ if ((rem != max_len) && (is_complete != 0)) {
+ /*
+ * If we don't transfer the data we can transfer, then
+ * the transfer is short !
+ */
+ xfer->flags.force_short_xfer = 1;
+ xfer->nframes = 2;
+ } else {
+ /*
+ * Default case
+ */
+ xfer->flags.force_short_xfer = 0;
+ xfer->nframes = max_len ? 2 : 1;
+ }
+ if (max_len > 0) {
+ if (src_mcopy) {
+ src_mcopy = USB_ADD_BYTES(src_mcopy, off);
+ usbd_copy_in(xfer->frbuffers + 1, 0,
+ src_mcopy, max_len);
+ usbd_xfer_set_frame_len(xfer, 1, max_len);
+ } else {
+ usbd_xfer_set_frame_data(xfer, 1,
+ USB_ADD_BYTES(src_zcopy, off), max_len);
+ }
+ } else {
+ /* the end is reached, send status */
+ xfer->flags.manual_status = 0;
+ usbd_xfer_set_frame_len(xfer, 1, 0);
+ }
+ DPRINTF("success\n");
+ return (0); /* success */
+
+tr_stalled:
+ DPRINTF("%s\n", (state != USB_HR_NOT_COMPLETE) ?
+ "complete" : "stalled");
+ return (USB_ERR_STALLED);
+
+tr_bad_context:
+ DPRINTF("bad context\n");
+ return (USB_ERR_BAD_CONTEXT);
+}
diff --git a/sys/dev/usb/usb_hid.c b/sys/dev/usb/usb_hid.c
new file mode 100644
index 000000000000..9cf180c985bb
--- /dev/null
+++ b/sys/dev/usb/usb_hid.c
@@ -0,0 +1,153 @@
+/* $NetBSD: hid.c,v 1.17 2001/11/13 06:24:53 lukem Exp $ */
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+
+#define USB_DEBUG_VAR usb_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_request.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+/*------------------------------------------------------------------------*
+ * hid_get_descriptor_from_usb
+ *
+ * This function will search for a HID descriptor between two USB
+ * interface descriptors.
+ *
+ * Return values:
+ * NULL: No more HID descriptors.
+ * Else: Pointer to HID descriptor.
+ *------------------------------------------------------------------------*/
+struct usb_hid_descriptor *
+hid_get_descriptor_from_usb(struct usb_config_descriptor *cd,
+ struct usb_interface_descriptor *id)
+{
+ struct usb_descriptor *desc = (void *)id;
+
+ if (desc == NULL) {
+ return (NULL);
+ }
+ while ((desc = usb_desc_foreach(cd, desc))) {
+ if ((desc->bDescriptorType == UDESC_HID) &&
+ (desc->bLength >= USB_HID_DESCRIPTOR_SIZE(0))) {
+ return (void *)desc;
+ }
+ if (desc->bDescriptorType == UDESC_INTERFACE) {
+ break;
+ }
+ }
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_hid_desc
+ *
+ * This function will read out an USB report descriptor from the USB
+ * device.
+ *
+ * Return values:
+ * NULL: Failure.
+ * Else: Success. The pointer should eventually be passed to free().
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_hid_desc(struct usb_device *udev, struct mtx *mtx,
+ void **descp, uint16_t *sizep,
+ struct malloc_type *mem, uint8_t iface_index)
+{
+ struct usb_interface *iface = usbd_get_iface(udev, iface_index);
+ struct usb_hid_descriptor *hid;
+ usb_error_t err;
+
+ if ((iface == NULL) || (iface->idesc == NULL)) {
+ return (USB_ERR_INVAL);
+ }
+ hid = hid_get_descriptor_from_usb
+ (usbd_get_config_descriptor(udev), iface->idesc);
+
+ if (hid == NULL) {
+ return (USB_ERR_IOERROR);
+ }
+ *sizep = UGETW(hid->descrs[0].wDescriptorLength);
+ if (*sizep == 0) {
+ return (USB_ERR_IOERROR);
+ }
+ if (mtx)
+ mtx_unlock(mtx);
+
+ *descp = malloc(*sizep, mem, M_ZERO | M_WAITOK);
+
+ if (mtx)
+ mtx_lock(mtx);
+
+ if (*descp == NULL) {
+ return (USB_ERR_NOMEM);
+ }
+ err = usbd_req_get_report_descriptor
+ (udev, mtx, *descp, *sizep, iface_index);
+
+ if (err) {
+ free(*descp, mem);
+ *descp = NULL;
+ return (err);
+ }
+ return (USB_ERR_NORMAL_COMPLETION);
+}
diff --git a/sys/dev/usb/usb_hub.c b/sys/dev/usb/usb_hub.c
new file mode 100644
index 000000000000..e3509862ef54
--- /dev/null
+++ b/sys/dev/usb/usb_hub.c
@@ -0,0 +1,2993 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
+ * Copyright (c) 2008-2022 Hans Petter Selasky
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * USB spec: http://www.usb.org/developers/docs/usbspec.zip
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sbuf.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+
+#define USB_DEBUG_VAR uhub_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_request.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_dynamic.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+#include <dev/usb/usb_hub_private.h>
+
+#ifdef USB_DEBUG
+static int uhub_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uhub, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB HUB");
+SYSCTL_INT(_hw_usb_uhub, OID_AUTO, debug, CTLFLAG_RWTUN, &uhub_debug, 0,
+ "Debug level");
+#endif
+
+#if USB_HAVE_POWERD
+static int usb_power_timeout = 30; /* seconds */
+
+SYSCTL_INT(_hw_usb, OID_AUTO, power_timeout, CTLFLAG_RWTUN,
+ &usb_power_timeout, 0, "USB power timeout");
+#endif
+
+#if USB_HAVE_DISABLE_ENUM
+static int usb_disable_enumeration = 0;
+SYSCTL_INT(_hw_usb, OID_AUTO, disable_enumeration, CTLFLAG_RWTUN,
+ &usb_disable_enumeration, 0, "Set to disable all USB device enumeration. "
+ "This can secure against USB devices turning evil, "
+ "for example a USB memory stick becoming a USB keyboard.");
+
+static int usb_disable_port_power = 0;
+SYSCTL_INT(_hw_usb, OID_AUTO, disable_port_power, CTLFLAG_RWTUN,
+ &usb_disable_port_power, 0, "Set to disable all USB port power.");
+#endif
+
+#define UHUB_PROTO(sc) ((sc)->sc_udev->ddesc.bDeviceProtocol)
+#define UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB)
+#define UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT)
+#define UHUB_IS_MULTI_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBMTT)
+#define UHUB_IS_SUPER_SPEED(sc) (UHUB_PROTO(sc) == UDPROTO_SSHUB)
+
+/* prototypes for type checking: */
+
+static device_suspend_t uhub_suspend;
+static device_resume_t uhub_resume;
+
+static bus_driver_added_t uhub_driver_added;
+static bus_child_pnpinfo_t uhub_child_pnpinfo;
+
+static usb_callback_t uhub_intr_callback;
+#if USB_HAVE_TT_SUPPORT
+static usb_callback_t uhub_reset_tt_callback;
+#endif
+
+static void usb_dev_resume_peer(struct usb_device *udev);
+static void usb_dev_suspend_peer(struct usb_device *udev);
+static uint8_t usb_peer_should_wakeup(struct usb_device *udev);
+
+static const struct usb_config uhub_config[UHUB_N_TRANSFER] = {
+ [UHUB_INTR_TRANSFER] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_ANY,
+ .timeout = 0,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .bufsize = 0, /* use wMaxPacketSize */
+ .callback = &uhub_intr_callback,
+ .interval = UHUB_INTR_INTERVAL,
+ },
+#if USB_HAVE_TT_SUPPORT
+ [UHUB_RESET_TT_TRANSFER] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &uhub_reset_tt_callback,
+ .timeout = 1000, /* 1 second */
+ .usb_mode = USB_MODE_HOST,
+ },
+#endif
+};
+
+/*
+ * driver instance for "hub" connected to "usb"
+ * and "hub" connected to "hub"
+ */
+static device_method_t uhub_methods[] = {
+ DEVMETHOD(device_probe, uhub_probe),
+ DEVMETHOD(device_attach, uhub_attach),
+ DEVMETHOD(device_detach, uhub_detach),
+
+ DEVMETHOD(device_suspend, uhub_suspend),
+ DEVMETHOD(device_resume, uhub_resume),
+
+ DEVMETHOD(bus_child_location, uhub_child_location),
+ DEVMETHOD(bus_child_pnpinfo, uhub_child_pnpinfo),
+ DEVMETHOD(bus_get_device_path, uhub_get_device_path),
+ DEVMETHOD(bus_driver_added, uhub_driver_added),
+ DEVMETHOD_END
+};
+
+driver_t uhub_driver = {
+ .name = "uhub",
+ .methods = uhub_methods,
+ .size = sizeof(struct uhub_softc)
+};
+
+DRIVER_MODULE(uhub, usbus, uhub_driver, 0, 0);
+DRIVER_MODULE(uhub, uhub, uhub_driver, 0, 0);
+MODULE_VERSION(uhub, 1);
+
+static void
+uhub_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhub_softc *sc = usbd_xfer_softc(xfer);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(2, "\n");
+ /*
+ * This is an indication that some port
+ * has changed status. Notify the bus
+ * event handler thread that we need
+ * to be explored again:
+ */
+ usb_needs_explore(sc->sc_udev->bus, 0);
+
+ case USB_ST_SETUP:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (xfer->error != USB_ERR_CANCELLED) {
+ /*
+ * Do a clear-stall. The "stall_pipe" flag
+ * will get cleared before next callback by
+ * the USB stack.
+ */
+ usbd_xfer_set_stall(xfer);
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ }
+ break;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * uhub_reset_tt_proc
+ *
+ * This function starts the TT reset USB request
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_TT_SUPPORT
+static void
+uhub_reset_tt_proc(struct usb_proc_msg *_pm)
+{
+ struct usb_udev_msg *pm = (void *)_pm;
+ struct usb_device *udev = pm->udev;
+ struct usb_hub *hub;
+ struct uhub_softc *sc;
+
+ hub = udev->hub;
+ if (hub == NULL)
+ return;
+ sc = hub->hubsoftc;
+ if (sc == NULL)
+ return;
+
+ /* Change lock */
+ USB_BUS_UNLOCK(udev->bus);
+ USB_MTX_LOCK(&sc->sc_mtx);
+ /* Start transfer */
+ usbd_transfer_start(sc->sc_xfer[UHUB_RESET_TT_TRANSFER]);
+ /* Change lock */
+ USB_MTX_UNLOCK(&sc->sc_mtx);
+ USB_BUS_LOCK(udev->bus);
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * uhub_tt_buffer_reset_async_locked
+ *
+ * This function queues a TT reset for the given USB device and endpoint.
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_TT_SUPPORT
+void
+uhub_tt_buffer_reset_async_locked(struct usb_device *child, struct usb_endpoint *ep)
+{
+ struct usb_device_request req;
+ struct usb_device *udev;
+ struct usb_hub *hub;
+ struct usb_port *up;
+ uint16_t wValue;
+ uint8_t port;
+
+ if (child == NULL || ep == NULL)
+ return;
+
+ udev = child->parent_hs_hub;
+ port = child->hs_port_no;
+
+ if (udev == NULL)
+ return;
+
+ hub = udev->hub;
+ if ((hub == NULL) ||
+ (udev->speed != USB_SPEED_HIGH) ||
+ (child->speed != USB_SPEED_LOW &&
+ child->speed != USB_SPEED_FULL) ||
+ (child->flags.usb_mode != USB_MODE_HOST) ||
+ (port == 0) || (ep->edesc == NULL)) {
+ /* not applicable */
+ return;
+ }
+
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ up = hub->ports + port - 1;
+
+ if (udev->ddesc.bDeviceClass == UDCLASS_HUB &&
+ udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBSTT)
+ port = 1;
+
+ /* if we already received a clear buffer request, reset the whole TT */
+ if (up->req_reset_tt.bRequest != 0) {
+ req.bmRequestType = UT_WRITE_CLASS_OTHER;
+ req.bRequest = UR_RESET_TT;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = port;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+ } else {
+ wValue = (ep->edesc->bEndpointAddress & 0xF) |
+ ((child->address & 0x7F) << 4) |
+ ((ep->edesc->bEndpointAddress & 0x80) << 8) |
+ ((ep->edesc->bmAttributes & 3) << 12);
+
+ req.bmRequestType = UT_WRITE_CLASS_OTHER;
+ req.bRequest = UR_CLEAR_TT_BUFFER;
+ USETW(req.wValue, wValue);
+ req.wIndex[0] = port;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+ }
+ up->req_reset_tt = req;
+ /* get reset transfer started */
+ usb_proc_msignal(USB_BUS_TT_PROC(udev->bus),
+ &hub->tt_msg[0], &hub->tt_msg[1]);
+}
+#endif
+
+#if USB_HAVE_TT_SUPPORT
+static void
+uhub_reset_tt_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uhub_softc *sc;
+ struct usb_device *udev;
+ struct usb_port *up;
+ uint8_t x;
+
+ DPRINTF("TT buffer reset\n");
+
+ sc = usbd_xfer_softc(xfer);
+ udev = sc->sc_udev;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+tr_setup:
+ USB_BUS_LOCK(udev->bus);
+ /* find first port which needs a TT reset */
+ for (x = 0; x != udev->hub->nports; x++) {
+ up = udev->hub->ports + x;
+
+ if (up->req_reset_tt.bRequest == 0)
+ continue;
+
+ /* copy in the transfer */
+ usbd_copy_in(xfer->frbuffers, 0, &up->req_reset_tt,
+ sizeof(up->req_reset_tt));
+ /* reset buffer */
+ memset(&up->req_reset_tt, 0, sizeof(up->req_reset_tt));
+
+ /* set length */
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(up->req_reset_tt));
+ xfer->nframes = 1;
+ USB_BUS_UNLOCK(udev->bus);
+
+ usbd_transfer_submit(xfer);
+ return;
+ }
+ USB_BUS_UNLOCK(udev->bus);
+ break;
+
+ default:
+ if (error == USB_ERR_CANCELLED)
+ break;
+
+ DPRINTF("TT buffer reset failed (%s)\n", usbd_errstr(error));
+ goto tr_setup;
+ }
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * uhub_count_active_host_ports
+ *
+ * This function counts the number of active ports at the given speed.
+ *------------------------------------------------------------------------*/
+uint8_t
+uhub_count_active_host_ports(struct usb_device *udev, enum usb_dev_speed speed)
+{
+ struct uhub_softc *sc;
+ struct usb_device *child;
+ struct usb_hub *hub;
+ struct usb_port *up;
+ uint8_t retval = 0;
+ uint8_t x;
+
+ if (udev == NULL)
+ goto done;
+ hub = udev->hub;
+ if (hub == NULL)
+ goto done;
+ sc = hub->hubsoftc;
+ if (sc == NULL)
+ goto done;
+
+ for (x = 0; x != hub->nports; x++) {
+ up = hub->ports + x;
+ child = usb_bus_port_get_device(udev->bus, up);
+ if (child != NULL &&
+ child->flags.usb_mode == USB_MODE_HOST &&
+ child->speed == speed)
+ retval++;
+ }
+done:
+ return (retval);
+}
+
+void
+uhub_explore_handle_re_enumerate(struct usb_device *child)
+{
+ uint8_t do_unlock;
+ usb_error_t err;
+
+ /* check if device should be re-enumerated */
+ if (child->flags.usb_mode != USB_MODE_HOST)
+ return;
+
+ do_unlock = usbd_enum_lock(child);
+ switch (child->re_enumerate_wait) {
+ case USB_RE_ENUM_START:
+ err = usbd_set_config_index(child,
+ USB_UNCONFIG_INDEX);
+ if (err != 0) {
+ DPRINTF("Unconfigure failed: %s: Ignored.\n",
+ usbd_errstr(err));
+ }
+ if (child->parent_hub == NULL) {
+ /* the root HUB cannot be re-enumerated */
+ DPRINTFN(6, "cannot reset root HUB\n");
+ err = 0;
+ } else {
+ err = usbd_req_re_enumerate(child, NULL);
+ }
+ if (err == 0) {
+ /* refresh device strings */
+ usb_get_langid(child);
+ usb_set_device_strings(child);
+
+ /* set default configuration */
+ err = usbd_set_config_index(child, 0);
+ }
+ if (err == 0) {
+ err = usb_probe_and_attach(child,
+ USB_IFACE_INDEX_ANY);
+ }
+ child->re_enumerate_wait = USB_RE_ENUM_DONE;
+ break;
+
+ case USB_RE_ENUM_PWR_OFF:
+ /* get the device unconfigured */
+ err = usbd_set_config_index(child,
+ USB_UNCONFIG_INDEX);
+ if (err) {
+ DPRINTFN(0, "Could not unconfigure "
+ "device (ignored)\n");
+ }
+ if (child->parent_hub == NULL) {
+ /* the root HUB cannot be re-enumerated */
+ DPRINTFN(6, "cannot set port feature\n");
+ err = 0;
+ } else {
+ /* clear port enable */
+ err = usbd_req_clear_port_feature(child->parent_hub,
+ NULL, child->port_no, UHF_PORT_ENABLE);
+ if (err) {
+ DPRINTFN(0, "Could not disable port "
+ "(ignored)\n");
+ }
+ }
+ child->re_enumerate_wait = USB_RE_ENUM_DONE;
+ break;
+
+ case USB_RE_ENUM_SET_CONFIG:
+ err = usbd_set_config_index(child,
+ child->next_config_index);
+ if (err != 0) {
+ DPRINTF("Configure failed: %s: Ignored.\n",
+ usbd_errstr(err));
+ } else {
+ err = usb_probe_and_attach(child,
+ USB_IFACE_INDEX_ANY);
+ }
+ child->re_enumerate_wait = USB_RE_ENUM_DONE;
+ break;
+
+ default:
+ child->re_enumerate_wait = USB_RE_ENUM_DONE;
+ break;
+ }
+ if (do_unlock)
+ usbd_enum_unlock(child);
+}
+
+/*------------------------------------------------------------------------*
+ * uhub_explore_sub - subroutine
+ *
+ * Return values:
+ * 0: Success
+ * Else: A control transaction failed
+ *------------------------------------------------------------------------*/
+static usb_error_t
+uhub_explore_sub(struct uhub_softc *sc, struct usb_port *up)
+{
+ struct usb_bus *bus;
+ struct usb_device *child;
+ uint8_t refcount;
+ usb_error_t err;
+
+ bus = sc->sc_udev->bus;
+ err = USB_ERR_NORMAL_COMPLETION;
+
+ /* get driver added refcount from USB bus */
+ refcount = bus->driver_added_refcount;
+
+ /* get device assosiated with the given port */
+ child = usb_bus_port_get_device(bus, up);
+ if (child == NULL) {
+ /* nothing to do */
+ goto done;
+ }
+
+ uhub_explore_handle_re_enumerate(child);
+
+ /* check if probe and attach should be done */
+
+ if (child->driver_added_refcount != refcount) {
+ child->driver_added_refcount = refcount;
+ err = usb_probe_and_attach(child,
+ USB_IFACE_INDEX_ANY);
+ if (err) {
+ goto done;
+ }
+ }
+ /* start control transfer, if device mode */
+
+ if (child->flags.usb_mode == USB_MODE_DEVICE)
+ usbd_ctrl_transfer_setup(child);
+
+ /* if a HUB becomes present, do a recursive HUB explore */
+
+ if (child->hub)
+ err = (child->hub->explore) (child);
+
+done:
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * uhub_read_port_status - factored out code
+ *------------------------------------------------------------------------*/
+static usb_error_t
+uhub_read_port_status(struct uhub_softc *sc, uint8_t portno)
+{
+ struct usb_port_status ps;
+ usb_error_t err;
+
+ if (sc->sc_usb_port_errors >= UHUB_USB_PORT_ERRORS_MAX) {
+ DPRINTFN(4, "port %d, HUB looks dead, too many errors\n", portno);
+ sc->sc_st.port_status = 0;
+ sc->sc_st.port_change = 0;
+ return (USB_ERR_TIMEOUT);
+ }
+
+ err = usbd_req_get_port_status(
+ sc->sc_udev, NULL, &ps, portno);
+
+ if (err == 0) {
+ sc->sc_st.port_status = UGETW(ps.wPortStatus);
+ sc->sc_st.port_change = UGETW(ps.wPortChange);
+ sc->sc_usb_port_errors = 0;
+ } else {
+ sc->sc_st.port_status = 0;
+ sc->sc_st.port_change = 0;
+ sc->sc_usb_port_errors++;
+ }
+
+ /* debugging print */
+
+ DPRINTFN(4, "port %d, wPortStatus=0x%04x, "
+ "wPortChange=0x%04x, err=%s\n",
+ portno, sc->sc_st.port_status,
+ sc->sc_st.port_change, usbd_errstr(err));
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * uhub_reattach_port
+ *
+ * Returns:
+ * 0: Success
+ * Else: A control transaction failed
+ *------------------------------------------------------------------------*/
+static usb_error_t
+uhub_reattach_port(struct uhub_softc *sc, uint8_t portno)
+{
+ struct usb_device *child;
+ struct usb_device *udev;
+ enum usb_dev_speed speed;
+ enum usb_hc_mode mode;
+ usb_error_t err;
+ uint16_t power_mask;
+ uint8_t timeout;
+
+ DPRINTF("reattaching port %d\n", portno);
+
+ timeout = 0;
+ udev = sc->sc_udev;
+ child = usb_bus_port_get_device(udev->bus,
+ udev->hub->ports + portno - 1);
+
+repeat:
+
+ /* first clear the port connection change bit */
+
+ err = usbd_req_clear_port_feature(udev, NULL,
+ portno, UHF_C_PORT_CONNECTION);
+
+ if (err)
+ goto error;
+
+ /* check if there is a child */
+
+ if (child != NULL) {
+ /*
+ * Free USB device and all subdevices, if any.
+ */
+ usb_free_device(child, 0);
+ child = NULL;
+ }
+ /* get fresh status */
+
+ err = uhub_read_port_status(sc, portno);
+ if (err)
+ goto error;
+
+#if USB_HAVE_DISABLE_ENUM
+ /* check if we should skip enumeration from this USB HUB */
+ if (usb_disable_enumeration != 0 ||
+ sc->sc_disable_enumeration != 0) {
+ DPRINTF("Enumeration is disabled!\n");
+ goto error;
+ }
+#endif
+ /* check if nothing is connected to the port */
+
+ if (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS))
+ goto error;
+
+ /* check if there is no power on the port and print a warning */
+
+ switch (udev->speed) {
+ case USB_SPEED_HIGH:
+ case USB_SPEED_FULL:
+ case USB_SPEED_LOW:
+ power_mask = UPS_PORT_POWER;
+ break;
+ case USB_SPEED_SUPER:
+ if (udev->parent_hub == NULL)
+ power_mask = 0; /* XXX undefined */
+ else
+ power_mask = UPS_PORT_POWER_SS;
+ break;
+ default:
+ power_mask = 0;
+ break;
+ }
+ if ((sc->sc_st.port_status & power_mask) != power_mask) {
+ DPRINTF("WARNING: strange, connected port %d "
+ "has no power\n", portno);
+ }
+
+ /* check if the device is in Host Mode */
+
+ if (!(sc->sc_st.port_status & UPS_PORT_MODE_DEVICE)) {
+ DPRINTF("Port %d is in Host Mode\n", portno);
+
+ if (sc->sc_st.port_status & UPS_SUSPEND) {
+ /*
+ * NOTE: Should not get here in SuperSpeed
+ * mode, because the HUB should report this
+ * bit as zero.
+ */
+ DPRINTF("Port %d was still "
+ "suspended, clearing.\n", portno);
+ err = usbd_req_clear_port_feature(udev,
+ NULL, portno, UHF_PORT_SUSPEND);
+ }
+
+ /* USB Host Mode */
+
+ /* wait for maximum device power up time */
+
+ usb_pause_mtx(NULL,
+ USB_MS_TO_TICKS(usb_port_powerup_delay));
+
+ /* reset port, which implies enabling it */
+
+ err = usbd_req_reset_port(udev, NULL, portno);
+
+ if (err) {
+ DPRINTFN(0, "port %d reset "
+ "failed, error=%s\n",
+ portno, usbd_errstr(err));
+ goto error;
+ }
+ /* get port status again, it might have changed during reset */
+
+ err = uhub_read_port_status(sc, portno);
+ if (err) {
+ goto error;
+ }
+ /* check if something changed during port reset */
+
+ if ((sc->sc_st.port_change & UPS_C_CONNECT_STATUS) ||
+ (!(sc->sc_st.port_status & UPS_CURRENT_CONNECT_STATUS))) {
+ if (timeout) {
+ DPRINTFN(1, "giving up port %d reset - "
+ "device vanished: change %#x status %#x\n",
+ portno, sc->sc_st.port_change,
+ sc->sc_st.port_status);
+ goto error;
+ }
+ timeout = 1;
+ goto repeat;
+ }
+ } else {
+ DPRINTF("Port %d is in Device Mode\n", portno);
+ }
+
+ /*
+ * Figure out the device speed
+ */
+ switch (udev->speed) {
+ case USB_SPEED_HIGH:
+ if (sc->sc_st.port_status & UPS_HIGH_SPEED)
+ speed = USB_SPEED_HIGH;
+ else if (sc->sc_st.port_status & UPS_LOW_SPEED)
+ speed = USB_SPEED_LOW;
+ else
+ speed = USB_SPEED_FULL;
+ break;
+ case USB_SPEED_FULL:
+ if (sc->sc_st.port_status & UPS_LOW_SPEED)
+ speed = USB_SPEED_LOW;
+ else
+ speed = USB_SPEED_FULL;
+ break;
+ case USB_SPEED_LOW:
+ speed = USB_SPEED_LOW;
+ break;
+ case USB_SPEED_SUPER:
+ if (udev->parent_hub == NULL) {
+ /* Root HUB - special case */
+ switch (sc->sc_st.port_status & UPS_OTHER_SPEED) {
+ case 0:
+ speed = USB_SPEED_FULL;
+ break;
+ case UPS_LOW_SPEED:
+ speed = USB_SPEED_LOW;
+ break;
+ case UPS_HIGH_SPEED:
+ speed = USB_SPEED_HIGH;
+ break;
+ default:
+ speed = USB_SPEED_SUPER;
+ break;
+ }
+ } else {
+ speed = USB_SPEED_SUPER;
+ }
+ break;
+ default:
+ /* same speed like parent */
+ speed = udev->speed;
+ break;
+ }
+ if (speed == USB_SPEED_SUPER) {
+ err = usbd_req_set_hub_u1_timeout(udev, NULL,
+ portno, 128 - (2 * udev->depth));
+ if (err) {
+ DPRINTFN(0, "port %d U1 timeout "
+ "failed, error=%s\n",
+ portno, usbd_errstr(err));
+ }
+ err = usbd_req_set_hub_u2_timeout(udev, NULL,
+ portno, 128 - (2 * udev->depth));
+ if (err) {
+ DPRINTFN(0, "port %d U2 timeout "
+ "failed, error=%s\n",
+ portno, usbd_errstr(err));
+ }
+ }
+
+ /*
+ * Figure out the device mode
+ *
+ * NOTE: This part is currently FreeBSD specific.
+ */
+ if (udev->parent_hub != NULL) {
+ /* inherit mode from the parent HUB */
+ mode = udev->parent_hub->flags.usb_mode;
+ } else if (sc->sc_st.port_status & UPS_PORT_MODE_DEVICE)
+ mode = USB_MODE_DEVICE;
+ else
+ mode = USB_MODE_HOST;
+
+ /* need to create a new child */
+ child = usb_alloc_device(sc->sc_dev, udev->bus, udev,
+ udev->depth + 1, portno - 1, portno, speed, mode);
+ if (child == NULL) {
+ DPRINTFN(0, "could not allocate new device\n");
+ goto error;
+ }
+ return (0); /* success */
+
+error:
+ if (child != NULL) {
+ /*
+ * Free USB device and all subdevices, if any.
+ */
+ usb_free_device(child, 0);
+ child = NULL;
+ }
+ if (err == 0) {
+ if (sc->sc_st.port_status & UPS_PORT_ENABLED) {
+ err = usbd_req_clear_port_feature(
+ sc->sc_udev, NULL,
+ portno, UHF_PORT_ENABLE);
+ }
+ }
+ if (err) {
+ DPRINTFN(0, "device problem (%s), "
+ "disabling port %d\n", usbd_errstr(err), portno);
+ }
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_device_20_compatible
+ *
+ * Returns:
+ * 0: HUB does not support suspend and resume
+ * Else: HUB supports suspend and resume
+ *------------------------------------------------------------------------*/
+static uint8_t
+usb_device_20_compatible(struct usb_device *udev)
+{
+ if (udev == NULL)
+ return (0);
+ switch (udev->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ case USB_SPEED_HIGH:
+ return (1);
+ default:
+ return (0);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * uhub_suspend_resume_port
+ *
+ * Returns:
+ * 0: Success
+ * Else: A control transaction failed
+ *------------------------------------------------------------------------*/
+static usb_error_t
+uhub_suspend_resume_port(struct uhub_softc *sc, uint8_t portno)
+{
+ struct usb_device *child;
+ struct usb_device *udev;
+ uint8_t is_suspend;
+ usb_error_t err;
+
+ DPRINTF("port %d\n", portno);
+
+ udev = sc->sc_udev;
+ child = usb_bus_port_get_device(udev->bus,
+ udev->hub->ports + portno - 1);
+
+ /* first clear the port suspend change bit */
+
+ if (usb_device_20_compatible(udev)) {
+ err = usbd_req_clear_port_feature(udev, NULL,
+ portno, UHF_C_PORT_SUSPEND);
+ } else {
+ err = usbd_req_clear_port_feature(udev, NULL,
+ portno, UHF_C_PORT_LINK_STATE);
+ }
+
+ if (err) {
+ DPRINTF("clearing suspend failed.\n");
+ goto done;
+ }
+ /* get fresh status */
+
+ err = uhub_read_port_status(sc, portno);
+ if (err) {
+ DPRINTF("reading port status failed.\n");
+ goto done;
+ }
+ /* convert current state */
+
+ if (usb_device_20_compatible(udev)) {
+ if (sc->sc_st.port_status & UPS_SUSPEND) {
+ is_suspend = 1;
+ } else {
+ is_suspend = 0;
+ }
+ } else {
+ switch (UPS_PORT_LINK_STATE_GET(sc->sc_st.port_status)) {
+ case UPS_PORT_LS_U3:
+ is_suspend = 1;
+ break;
+ case UPS_PORT_LS_SS_INA:
+ usbd_req_warm_reset_port(udev, NULL, portno);
+ is_suspend = 0;
+ break;
+ default:
+ is_suspend = 0;
+ break;
+ }
+ }
+
+ DPRINTF("suspended=%u\n", is_suspend);
+
+ /* do the suspend or resume */
+
+ if (child) {
+ /*
+ * This code handle two cases: 1) Host Mode - we can only
+ * receive resume here 2) Device Mode - we can receive
+ * suspend and resume here
+ */
+ if (is_suspend == 0)
+ usb_dev_resume_peer(child);
+ else if (child->flags.usb_mode == USB_MODE_DEVICE)
+ usb_dev_suspend_peer(child);
+ }
+done:
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * uhub_root_interrupt
+ *
+ * This function is called when a Root HUB interrupt has
+ * happened. "ptr" and "len" makes up the Root HUB interrupt
+ * packet. This function is called having the "bus_mtx" locked.
+ *------------------------------------------------------------------------*/
+void
+uhub_root_intr(struct usb_bus *bus, const uint8_t *ptr, uint8_t len)
+{
+ USB_BUS_LOCK_ASSERT(bus, MA_OWNED);
+
+ usb_needs_explore(bus, 0);
+}
+
+static uint8_t
+uhub_is_too_deep(struct usb_device *udev)
+{
+ switch (udev->speed) {
+ case USB_SPEED_FULL:
+ case USB_SPEED_LOW:
+ case USB_SPEED_HIGH:
+ if (udev->depth > USB_HUB_MAX_DEPTH)
+ return (1);
+ break;
+ case USB_SPEED_SUPER:
+ if (udev->depth > USB_SS_HUB_DEPTH_MAX)
+ return (1);
+ break;
+ default:
+ break;
+ }
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * uhub_explore
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static usb_error_t
+uhub_explore(struct usb_device *udev)
+{
+ struct usb_hub *hub;
+ struct uhub_softc *sc;
+ struct usb_port *up;
+ usb_error_t retval;
+ usb_error_t err;
+ uint8_t portno;
+ uint8_t x;
+ uint8_t do_unlock;
+
+ hub = udev->hub;
+ sc = hub->hubsoftc;
+
+ DPRINTFN(11, "udev=%p addr=%d\n", udev, udev->address);
+
+ /* ignore devices that are too deep */
+ if (uhub_is_too_deep(udev))
+ return (USB_ERR_TOO_DEEP);
+
+ /* check if device is suspended */
+ if (udev->flags.self_suspended) {
+ /* need to wait until the child signals resume */
+ DPRINTF("Device is suspended!\n");
+ return (USB_ERR_NORMAL_COMPLETION);
+ }
+
+ /*
+ * Make sure we don't race against user-space applications
+ * like LibUSB:
+ */
+ do_unlock = usbd_enum_lock(udev);
+
+ /*
+ * Set default error code to avoid compiler warnings.
+ * Note that hub->nports cannot be zero.
+ */
+ retval = USB_ERR_NORMAL_COMPLETION;
+
+ for (x = 0; x != hub->nports; x++) {
+ up = hub->ports + x;
+ portno = x + 1;
+
+ err = uhub_read_port_status(sc, portno);
+ if (err != USB_ERR_NORMAL_COMPLETION)
+ retval = err;
+
+ if (sc->sc_st.port_change & UPS_C_OVERCURRENT_INDICATOR) {
+ DPRINTF("Overcurrent on port %u.\n", portno);
+ err = usbd_req_clear_port_feature(
+ udev, NULL, portno, UHF_C_PORT_OVER_CURRENT);
+ if (err != USB_ERR_NORMAL_COMPLETION)
+ retval = err;
+ }
+ if (!(sc->sc_flags & UHUB_FLAG_DID_EXPLORE)) {
+ /*
+ * Fake a connect status change so that the
+ * status gets checked initially!
+ */
+ sc->sc_st.port_change |=
+ UPS_C_CONNECT_STATUS;
+ }
+ if (sc->sc_st.port_change & UPS_C_PORT_ENABLED) {
+ err = usbd_req_clear_port_feature(
+ udev, NULL, portno, UHF_C_PORT_ENABLE);
+ if (err != USB_ERR_NORMAL_COMPLETION)
+ retval = err;
+ if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) {
+ /*
+ * Ignore the port error if the device
+ * has vanished !
+ */
+ } else if (sc->sc_st.port_status & UPS_PORT_ENABLED) {
+ DPRINTFN(0, "illegal enable change, "
+ "port %d\n", portno);
+ } else {
+ if (up->restartcnt == USB_RESTART_MAX) {
+ /* XXX could try another speed ? */
+ DPRINTFN(0, "port error, giving up "
+ "port %d\n", portno);
+ } else {
+ sc->sc_st.port_change |=
+ UPS_C_CONNECT_STATUS;
+ up->restartcnt++;
+ }
+ }
+ }
+ if (sc->sc_st.port_change & UPS_C_CONNECT_STATUS) {
+ err = uhub_reattach_port(sc, portno);
+ if (err != USB_ERR_NORMAL_COMPLETION)
+ retval = err;
+ }
+ if (sc->sc_st.port_change & (UPS_C_SUSPEND |
+ UPS_C_PORT_LINK_STATE)) {
+ err = uhub_suspend_resume_port(sc, portno);
+ if (err != USB_ERR_NORMAL_COMPLETION)
+ retval = err;
+ }
+ if (udev->speed == USB_SPEED_SUPER &&
+ (sc->sc_st.port_change & UPS_C_BH_PORT_RESET) != 0) {
+ DPRINTF("Warm reset finished on port %u.\n", portno);
+ err = usbd_req_clear_port_feature(
+ udev, NULL, portno, UHF_C_BH_PORT_RESET);
+ if (err != USB_ERR_NORMAL_COMPLETION)
+ retval = err;
+ }
+ if (sc->sc_st.port_change & UPS_C_PORT_RESET) {
+ DPRINTF("Port reset finished on port %u.\n", portno);
+ err = usbd_req_clear_port_feature(
+ udev, NULL, portno, UHF_C_PORT_RESET);
+ if (err != USB_ERR_NORMAL_COMPLETION)
+ retval = err;
+ }
+
+ if (uhub_explore_sub(sc, up) == USB_ERR_NORMAL_COMPLETION) {
+ /* explore succeeded - reset restart counter */
+ up->restartcnt = 0;
+ }
+ }
+
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+
+ /* initial status checked */
+ sc->sc_flags |= UHUB_FLAG_DID_EXPLORE;
+
+ return (retval);
+}
+
+int
+uhub_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+
+ /*
+ * The subclass for USB HUBs is currently ignored because it
+ * is 0 for some and 1 for others.
+ */
+ if (uaa->info.bConfigIndex == 0 &&
+ uaa->info.bDeviceClass == UDCLASS_HUB)
+ return (BUS_PROBE_DEFAULT);
+
+ return (ENXIO);
+}
+
+/* NOTE: The information returned by this function can be wrong. */
+usb_error_t
+uhub_query_info(struct usb_device *udev, uint8_t *pnports, uint8_t *ptt)
+{
+ struct usb_hub_descriptor hubdesc20;
+ struct usb_hub_ss_descriptor hubdesc30;
+ usb_error_t err;
+ uint8_t nports;
+ uint8_t tt;
+
+ if (udev->ddesc.bDeviceClass != UDCLASS_HUB)
+ return (USB_ERR_INVAL);
+
+ nports = 0;
+ tt = 0;
+
+ switch (udev->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ case USB_SPEED_HIGH:
+ /* assuming that there is one port */
+ err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1);
+ if (err) {
+ DPRINTFN(0, "getting USB 2.0 HUB descriptor failed,"
+ "error=%s\n", usbd_errstr(err));
+ break;
+ }
+ nports = hubdesc20.bNbrPorts;
+ if (nports > 127)
+ nports = 127;
+
+ if (udev->speed == USB_SPEED_HIGH)
+ tt = (UGETW(hubdesc20.wHubCharacteristics) >> 5) & 3;
+ break;
+
+ case USB_SPEED_SUPER:
+ err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1);
+ if (err) {
+ DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed,"
+ "error=%s\n", usbd_errstr(err));
+ break;
+ }
+ nports = hubdesc30.bNbrPorts;
+ if (nports > 16)
+ nports = 16;
+ break;
+
+ default:
+ err = USB_ERR_INVAL;
+ break;
+ }
+
+ if (pnports != NULL)
+ *pnports = nports;
+
+ if (ptt != NULL)
+ *ptt = tt;
+
+ return (err);
+}
+
+int
+uhub_attach(device_t dev)
+{
+ struct uhub_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct usb_device *udev = uaa->device;
+ struct usb_device *parent_hub = udev->parent_hub;
+ struct usb_hub *hub;
+ struct usb_hub_descriptor hubdesc20;
+ struct usb_hub_ss_descriptor hubdesc30;
+#if USB_HAVE_DISABLE_ENUM
+ struct sysctl_ctx_list *sysctl_ctx;
+ struct sysctl_oid *sysctl_tree;
+#endif
+ uint16_t pwrdly;
+ uint16_t nports;
+ uint8_t x;
+ uint8_t portno;
+ uint8_t removable;
+ uint8_t iface_index;
+ usb_error_t err;
+
+ sc->sc_udev = udev;
+ sc->sc_dev = dev;
+
+ mtx_init(&sc->sc_mtx, "USB HUB mutex", NULL, MTX_DEF);
+
+ device_set_usb_desc(dev);
+
+ DPRINTFN(2, "depth=%d selfpowered=%d, parent=%p, "
+ "parent->selfpowered=%d\n",
+ udev->depth,
+ udev->flags.self_powered,
+ parent_hub,
+ parent_hub ?
+ parent_hub->flags.self_powered : 0);
+
+ if (uhub_is_too_deep(udev)) {
+ DPRINTFN(0, "HUB at depth %d, "
+ "exceeds maximum. HUB ignored\n", (int)udev->depth);
+ goto error;
+ }
+
+ if (!udev->flags.self_powered && parent_hub &&
+ !parent_hub->flags.self_powered) {
+ DPRINTFN(0, "Bus powered HUB connected to "
+ "bus powered HUB. HUB ignored\n");
+ goto error;
+ }
+
+ if (UHUB_IS_MULTI_TT(sc)) {
+ err = usbd_set_alt_interface_index(udev, 0, 1);
+ if (err) {
+ device_printf(dev, "MTT could not be enabled\n");
+ goto error;
+ }
+ device_printf(dev, "MTT enabled\n");
+ }
+
+ /* get HUB descriptor */
+
+ DPRINTFN(2, "Getting HUB descriptor\n");
+
+ switch (udev->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ case USB_SPEED_HIGH:
+ /* assuming that there is one port */
+ err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, 1);
+ if (err) {
+ DPRINTFN(0, "getting USB 2.0 HUB descriptor failed,"
+ "error=%s\n", usbd_errstr(err));
+ goto error;
+ }
+ /* get number of ports */
+ nports = hubdesc20.bNbrPorts;
+
+ /* get power delay */
+ pwrdly = ((hubdesc20.bPwrOn2PwrGood * UHD_PWRON_FACTOR) +
+ usb_extra_power_up_time);
+
+ /* get complete HUB descriptor */
+ if (nports >= 8) {
+ /* check number of ports */
+ if (nports > 127) {
+ DPRINTFN(0, "Invalid number of USB 2.0 ports,"
+ "error=%s\n", usbd_errstr(err));
+ goto error;
+ }
+ /* get complete HUB descriptor */
+ err = usbd_req_get_hub_descriptor(udev, NULL, &hubdesc20, nports);
+
+ if (err) {
+ DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed,"
+ "error=%s\n", usbd_errstr(err));
+ goto error;
+ }
+ if (hubdesc20.bNbrPorts != nports) {
+ DPRINTFN(0, "Number of ports changed\n");
+ goto error;
+ }
+ }
+ break;
+ case USB_SPEED_SUPER:
+ if (udev->parent_hub != NULL) {
+ err = usbd_req_set_hub_depth(udev, NULL,
+ udev->depth - 1);
+ if (err) {
+ DPRINTFN(0, "Setting USB 3.0 HUB depth failed,"
+ "error=%s\n", usbd_errstr(err));
+ goto error;
+ }
+ }
+ err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, 1);
+ if (err) {
+ DPRINTFN(0, "Getting USB 3.0 HUB descriptor failed,"
+ "error=%s\n", usbd_errstr(err));
+ goto error;
+ }
+ /* get number of ports */
+ nports = hubdesc30.bNbrPorts;
+
+ /* get power delay */
+ pwrdly = ((hubdesc30.bPwrOn2PwrGood * UHD_PWRON_FACTOR) +
+ usb_extra_power_up_time);
+
+ /* get complete HUB descriptor */
+ if (nports >= 8) {
+ /* check number of ports */
+ if (nports > ((udev->parent_hub != NULL) ? 15 : 127)) {
+ DPRINTFN(0, "Invalid number of USB 3.0 ports,"
+ "error=%s\n", usbd_errstr(err));
+ goto error;
+ }
+ /* get complete HUB descriptor */
+ err = usbd_req_get_ss_hub_descriptor(udev, NULL, &hubdesc30, nports);
+
+ if (err) {
+ DPRINTFN(0, "Getting USB 2.0 HUB descriptor failed,"
+ "error=%s\n", usbd_errstr(err));
+ goto error;
+ }
+ if (hubdesc30.bNbrPorts != nports) {
+ DPRINTFN(0, "Number of ports changed\n");
+ goto error;
+ }
+ }
+ break;
+ default:
+ DPRINTF("Assuming HUB has only one port\n");
+ /* default number of ports */
+ nports = 1;
+ /* default power delay */
+ pwrdly = ((10 * UHD_PWRON_FACTOR) + usb_extra_power_up_time);
+ break;
+ }
+ if (nports == 0) {
+ DPRINTFN(0, "portless HUB\n");
+ goto error;
+ }
+ if (nports > USB_MAX_PORTS) {
+ DPRINTF("Port limit exceeded\n");
+ goto error;
+ }
+#if (USB_HAVE_FIXED_PORT == 0)
+ hub = malloc(sizeof(hub[0]) + (sizeof(hub->ports[0]) * nports),
+ M_USBDEV, M_WAITOK | M_ZERO);
+
+ if (hub == NULL)
+ goto error;
+#else
+ hub = &sc->sc_hub;
+#endif
+ udev->hub = hub;
+
+ /* initialize HUB structure */
+ hub->hubsoftc = sc;
+ hub->explore = &uhub_explore;
+ hub->nports = nports;
+ hub->hubudev = udev;
+#if USB_HAVE_TT_SUPPORT
+ hub->tt_msg[0].hdr.pm_callback = &uhub_reset_tt_proc;
+ hub->tt_msg[0].udev = udev;
+ hub->tt_msg[1].hdr.pm_callback = &uhub_reset_tt_proc;
+ hub->tt_msg[1].udev = udev;
+#endif
+ /* if self powered hub, give ports maximum current */
+ if (udev->flags.self_powered) {
+ hub->portpower = USB_MAX_POWER;
+ } else {
+ hub->portpower = USB_MIN_POWER;
+ }
+
+ /* set up interrupt pipe */
+ iface_index = 0;
+ if (udev->parent_hub == NULL) {
+ /* root HUB is special */
+ err = 0;
+ } else {
+ /* normal HUB */
+ err = usbd_transfer_setup(udev, &iface_index, sc->sc_xfer,
+ uhub_config, UHUB_N_TRANSFER, sc, &sc->sc_mtx);
+ }
+ if (err) {
+ DPRINTFN(0, "cannot setup interrupt transfer, "
+ "errstr=%s\n", usbd_errstr(err));
+ goto error;
+ }
+ /* wait with power off for a while */
+ usb_pause_mtx(NULL, USB_MS_TO_TICKS(USB_POWER_DOWN_TIME));
+
+#if USB_HAVE_DISABLE_ENUM
+ /* Add device sysctls */
+
+ sysctl_ctx = device_get_sysctl_ctx(dev);
+ sysctl_tree = device_get_sysctl_tree(dev);
+
+ if (sysctl_ctx != NULL && sysctl_tree != NULL) {
+ (void) SYSCTL_ADD_INT(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree),
+ OID_AUTO, "disable_enumeration", CTLFLAG_RWTUN,
+ &sc->sc_disable_enumeration, 0,
+ "Set to disable enumeration on this USB HUB.");
+
+ (void) SYSCTL_ADD_INT(sysctl_ctx, SYSCTL_CHILDREN(sysctl_tree),
+ OID_AUTO, "disable_port_power", CTLFLAG_RWTUN,
+ &sc->sc_disable_port_power, 0,
+ "Set to disable USB port power on this USB HUB.");
+ }
+#endif
+ /*
+ * To have the best chance of success we do things in the exact same
+ * order as Windoze98. This should not be necessary, but some
+ * devices do not follow the USB specs to the letter.
+ *
+ * These are the events on the bus when a hub is attached:
+ * Get device and config descriptors (see attach code)
+ * Get hub descriptor (see above)
+ * For all ports
+ * turn on power
+ * wait for power to become stable
+ * (all below happens in explore code)
+ * For all ports
+ * clear C_PORT_CONNECTION
+ * For all ports
+ * get port status
+ * if device connected
+ * wait 100 ms
+ * turn on reset
+ * wait
+ * clear C_PORT_RESET
+ * get port status
+ * proceed with device attachment
+ */
+
+ /* XXX should check for none, individual, or ganged power? */
+
+ removable = 0;
+
+ for (x = 0; x != nports; x++) {
+ /* set up data structures */
+ struct usb_port *up = hub->ports + x;
+
+ up->device_index = 0;
+ up->restartcnt = 0;
+ portno = x + 1;
+
+ /* check if port is removable */
+ switch (udev->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ case USB_SPEED_HIGH:
+ if (!UHD_NOT_REMOV(&hubdesc20, portno))
+ removable++;
+ break;
+ case USB_SPEED_SUPER:
+ if (!UHD_NOT_REMOV(&hubdesc30, portno))
+ removable++;
+ break;
+ default:
+ DPRINTF("Assuming removable port\n");
+ removable++;
+ break;
+ }
+ if (err == 0) {
+#if USB_HAVE_DISABLE_ENUM
+ /* check if we should disable USB port power or not */
+ if (usb_disable_port_power != 0 ||
+ sc->sc_disable_port_power != 0) {
+ /* turn the power off */
+ DPRINTFN(2, "Turning port %d power off\n", portno);
+ err = usbd_req_clear_port_feature(udev, NULL,
+ portno, UHF_PORT_POWER);
+ } else {
+#endif
+ /* turn the power on */
+ DPRINTFN(2, "Turning port %d power on\n", portno);
+ err = usbd_req_set_port_feature(udev, NULL,
+ portno, UHF_PORT_POWER);
+#if USB_HAVE_DISABLE_ENUM
+ }
+#endif
+ }
+ if (err != 0) {
+ DPRINTFN(0, "port %d power on or off failed, %s\n",
+ portno, usbd_errstr(err));
+ }
+ DPRINTF("turn on port %d power\n",
+ portno);
+
+ /* wait for stable power */
+ usb_pause_mtx(NULL, USB_MS_TO_TICKS(pwrdly));
+ }
+
+ device_printf(dev, "%d port%s with %d "
+ "removable, %s powered\n", nports, (nports != 1) ? "s" : "",
+ removable, udev->flags.self_powered ? "self" : "bus");
+
+ /* Start the interrupt endpoint, if any */
+
+ USB_MTX_LOCK(&sc->sc_mtx);
+ usbd_transfer_start(sc->sc_xfer[UHUB_INTR_TRANSFER]);
+ USB_MTX_UNLOCK(&sc->sc_mtx);
+
+ /* Enable automatic power save on all USB HUBs */
+
+ usbd_set_power_mode(udev, USB_POWER_MODE_SAVE);
+
+ return (0);
+
+error:
+ usbd_transfer_unsetup(sc->sc_xfer, UHUB_N_TRANSFER);
+
+#if (USB_HAVE_FIXED_PORT == 0)
+ free(udev->hub, M_USBDEV);
+#endif
+ udev->hub = NULL;
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (ENXIO);
+}
+
+/*
+ * Called from process context when the hub is gone.
+ * Detach all devices on active ports.
+ */
+int
+uhub_detach(device_t dev)
+{
+ struct uhub_softc *sc = device_get_softc(dev);
+ struct usb_hub *hub = sc->sc_udev->hub;
+ struct usb_bus *bus = sc->sc_udev->bus;
+ struct usb_device *child;
+ uint8_t x;
+
+ if (hub == NULL) /* must be partially working */
+ return (0);
+
+ /* Make sure interrupt transfer is gone. */
+ usbd_transfer_unsetup(sc->sc_xfer, UHUB_N_TRANSFER);
+
+ /* Detach all ports */
+ for (x = 0; x != hub->nports; x++) {
+ child = usb_bus_port_get_device(bus, hub->ports + x);
+
+ if (child == NULL) {
+ continue;
+ }
+
+ /*
+ * Free USB device and all subdevices, if any.
+ */
+ usb_free_device(child, 0);
+ }
+
+#if USB_HAVE_TT_SUPPORT
+ /* Make sure our TT messages are not queued anywhere */
+ USB_BUS_LOCK(bus);
+ usb_proc_mwait(USB_BUS_TT_PROC(bus),
+ &hub->tt_msg[0], &hub->tt_msg[1]);
+ USB_BUS_UNLOCK(bus);
+#endif
+
+#if (USB_HAVE_FIXED_PORT == 0)
+ free(hub, M_USBDEV);
+#endif
+ sc->sc_udev->hub = NULL;
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static int
+uhub_suspend(device_t dev)
+{
+ DPRINTF("\n");
+ /* Sub-devices are not suspended here! */
+ return (0);
+}
+
+static int
+uhub_resume(device_t dev)
+{
+ DPRINTF("\n");
+ /* Sub-devices are not resumed here! */
+ return (0);
+}
+
+static void
+uhub_driver_added(device_t dev, driver_t *driver)
+{
+ usb_needs_explore_all();
+}
+
+void
+uhub_find_iface_index(struct usb_hub *hub, device_t child,
+ struct hub_result *res)
+{
+ struct usb_interface *iface;
+ struct usb_device *udev;
+ uint8_t nports;
+ uint8_t x;
+ uint8_t i;
+
+ nports = hub->nports;
+ for (x = 0; x != nports; x++) {
+ udev = usb_bus_port_get_device(hub->hubudev->bus,
+ hub->ports + x);
+ if (!udev) {
+ continue;
+ }
+ for (i = 0; i != USB_IFACE_MAX; i++) {
+ iface = usbd_get_iface(udev, i);
+ if (iface &&
+ (iface->subdev == child)) {
+ res->iface_index = i;
+ res->udev = udev;
+ res->portno = x + 1;
+ return;
+ }
+ }
+ }
+ res->iface_index = 0;
+ res->udev = NULL;
+ res->portno = 0;
+}
+
+int
+uhub_child_location(device_t parent, device_t child, struct sbuf *sb)
+{
+ struct uhub_softc *sc;
+ struct usb_hub *hub;
+ struct hub_result res;
+
+ if (!device_is_attached(parent))
+ return (0);
+
+ sc = device_get_softc(parent);
+ hub = sc->sc_udev->hub;
+
+ bus_topo_lock();
+ uhub_find_iface_index(hub, child, &res);
+ if (!res.udev) {
+ DPRINTF("device not on hub\n");
+ goto done;
+ }
+ sbuf_printf(sb, "bus=%u hubaddr=%u port=%u devaddr=%u"
+ " interface=%u"
+#if USB_HAVE_UGEN
+ " ugen=%s"
+#endif
+ , device_get_unit(res.udev->bus->bdev)
+ , (res.udev->parent_hub != NULL) ?
+ res.udev->parent_hub->device_index : 0
+ , res.portno, res.udev->device_index, res.iface_index
+#if USB_HAVE_UGEN
+ , res.udev->ugen_name
+#endif
+ );
+done:
+ bus_topo_unlock();
+
+ return (0);
+}
+
+int
+uhub_get_device_path(device_t bus, device_t child, const char *locator,
+ struct sbuf *sb)
+{
+ struct uhub_softc *sc;
+ struct usb_hub *hub;
+ struct hub_result res;
+ int rv;
+
+ if (strcmp(locator, BUS_LOCATOR_UEFI) == 0) {
+ rv = bus_generic_get_device_path(device_get_parent(bus), bus, locator, sb);
+ if (rv != 0)
+ return (rv);
+
+ sc = device_get_softc(bus);
+ hub = sc->sc_udev->hub;
+
+ bus_topo_lock();
+ uhub_find_iface_index(hub, child, &res);
+ if (!res.udev) {
+ printf("device not on hub\n");
+ goto done;
+ }
+ sbuf_printf(sb, "/USB(0x%x,0x%x)", res.portno - 1, res.iface_index);
+ done:
+ bus_topo_unlock();
+ return (0);
+ }
+
+ /* For the rest, punt to the default handler */
+ return (bus_generic_get_device_path(bus, child, locator, sb));
+}
+
+static int
+uhub_child_pnpinfo(device_t parent, device_t child, struct sbuf*sb)
+{
+ struct uhub_softc *sc;
+ struct usb_hub *hub;
+ struct usb_interface *iface;
+ struct hub_result res;
+ uint8_t do_unlock;
+
+ if (!device_is_attached(parent))
+ return (0);
+
+ sc = device_get_softc(parent);
+ hub = sc->sc_udev->hub;
+
+ bus_topo_lock();
+ uhub_find_iface_index(hub, child, &res);
+ if (!res.udev) {
+ DPRINTF("device not on hub\n");
+ goto done;
+ }
+ iface = usbd_get_iface(res.udev, res.iface_index);
+ if (iface && iface->idesc) {
+ /* Make sure device information is not changed during the print. */
+ do_unlock = usbd_ctrl_lock(res.udev);
+
+ sbuf_printf(sb, "vendor=0x%04x product=0x%04x "
+ "devclass=0x%02x devsubclass=0x%02x "
+ "devproto=0x%02x "
+ "sernum=\"%s\" "
+ "release=0x%04x "
+ "mode=%s "
+ "intclass=0x%02x intsubclass=0x%02x "
+ "intprotocol=0x%02x" "%s%s",
+ UGETW(res.udev->ddesc.idVendor),
+ UGETW(res.udev->ddesc.idProduct),
+ res.udev->ddesc.bDeviceClass,
+ res.udev->ddesc.bDeviceSubClass,
+ res.udev->ddesc.bDeviceProtocol,
+ usb_get_serial(res.udev),
+ UGETW(res.udev->ddesc.bcdDevice),
+ (res.udev->flags.usb_mode == USB_MODE_HOST) ? "host" : "device",
+ iface->idesc->bInterfaceClass,
+ iface->idesc->bInterfaceSubClass,
+ iface->idesc->bInterfaceProtocol,
+ iface->pnpinfo ? " " : "",
+ iface->pnpinfo ? iface->pnpinfo : "");
+
+ if (do_unlock)
+ usbd_ctrl_unlock(res.udev);
+ }
+done:
+ bus_topo_unlock();
+
+ return (0);
+}
+
+/*
+ * The USB Transaction Translator:
+ * ===============================
+ *
+ * When doing LOW- and FULL-speed USB transfers across a HIGH-speed
+ * USB HUB, bandwidth must be allocated for ISOCHRONOUS and INTERRUPT
+ * USB transfers. To utilize bandwidth dynamically the "scatter and
+ * gather" principle must be applied. This means that bandwidth must
+ * be divided into equal parts of bandwidth. With regard to USB all
+ * data is transferred in smaller packets with length
+ * "wMaxPacketSize". The problem however is that "wMaxPacketSize" is
+ * not a constant!
+ *
+ * The bandwidth scheduler which I have implemented will simply pack
+ * the USB transfers back to back until there is no more space in the
+ * schedule. Out of the 8 microframes which the USB 2.0 standard
+ * provides, only 6 are available for non-HIGH-speed devices. I have
+ * reserved the first 4 microframes for ISOCHRONOUS transfers. The
+ * last 2 microframes I have reserved for INTERRUPT transfers. Without
+ * this division, it is very difficult to allocate and free bandwidth
+ * dynamically.
+ *
+ * NOTE about the Transaction Translator in USB HUBs:
+ *
+ * USB HUBs have a very simple Transaction Translator, that will
+ * simply pipeline all the SPLIT transactions. That means that the
+ * transactions will be executed in the order they are queued!
+ *
+ */
+
+/*------------------------------------------------------------------------*
+ * usb_intr_find_best_slot
+ *
+ * Return value:
+ * The best Transaction Translation slot for an interrupt endpoint.
+ *------------------------------------------------------------------------*/
+static uint8_t
+usb_intr_find_best_slot(usb_size_t *ptr, uint8_t start,
+ uint8_t end, uint8_t mask)
+{
+ usb_size_t min = (usb_size_t)-1;
+ usb_size_t sum;
+ uint8_t x;
+ uint8_t y;
+ uint8_t z;
+
+ y = 0;
+
+ /* find the last slot with lesser used bandwidth */
+
+ for (x = start; x < end; x++) {
+ sum = 0;
+
+ /* compute sum of bandwidth */
+ for (z = x; z < end; z++) {
+ if (mask & (1U << (z - x)))
+ sum += ptr[z];
+ }
+
+ /* check if the current multi-slot is more optimal */
+ if (min >= sum) {
+ min = sum;
+ y = x;
+ }
+
+ /* check if the mask is about to be shifted out */
+ if (mask & (1U << (end - 1 - x)))
+ break;
+ }
+ return (y);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_hs_bandwidth_adjust
+ *
+ * This function will update the bandwidth usage for the microframe
+ * having index "slot" by "len" bytes. "len" can be negative. If the
+ * "slot" argument is greater or equal to "USB_HS_MICRO_FRAMES_MAX"
+ * the "slot" argument will be replaced by the slot having least used
+ * bandwidth. The "mask" argument is used for multi-slot allocations.
+ *
+ * Returns:
+ * The slot in which the bandwidth update was done: 0..7
+ *------------------------------------------------------------------------*/
+static uint8_t
+usb_hs_bandwidth_adjust(struct usb_device *udev, int16_t len,
+ uint8_t slot, uint8_t mask)
+{
+ struct usb_bus *bus = udev->bus;
+ struct usb_hub *hub;
+ enum usb_dev_speed speed;
+ uint8_t x;
+
+ USB_BUS_LOCK_ASSERT(bus, MA_OWNED);
+
+ speed = usbd_get_speed(udev);
+
+ switch (speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ if (speed == USB_SPEED_LOW) {
+ len *= 8;
+ }
+ /*
+ * The Host Controller Driver should have
+ * performed checks so that the lookup
+ * below does not result in a NULL pointer
+ * access.
+ */
+
+ hub = udev->parent_hs_hub->hub;
+ if (slot >= USB_HS_MICRO_FRAMES_MAX) {
+ slot = usb_intr_find_best_slot(hub->uframe_usage,
+ USB_FS_ISOC_UFRAME_MAX, 6, mask);
+ }
+ for (x = slot; x < 8; x++) {
+ if (mask & (1U << (x - slot))) {
+ hub->uframe_usage[x] += len;
+ bus->uframe_usage[x] += len;
+ }
+ }
+ break;
+ default:
+ if (slot >= USB_HS_MICRO_FRAMES_MAX) {
+ slot = usb_intr_find_best_slot(bus->uframe_usage, 0,
+ USB_HS_MICRO_FRAMES_MAX, mask);
+ }
+ for (x = slot; x < 8; x++) {
+ if (mask & (1U << (x - slot))) {
+ bus->uframe_usage[x] += len;
+ }
+ }
+ break;
+ }
+ return (slot);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_hs_bandwidth_alloc
+ *
+ * This function is a wrapper function for "usb_hs_bandwidth_adjust()".
+ *------------------------------------------------------------------------*/
+void
+usb_hs_bandwidth_alloc(struct usb_xfer *xfer)
+{
+ struct usb_device *udev;
+ uint8_t slot;
+ uint8_t mask;
+ uint8_t speed;
+
+ udev = xfer->xroot->udev;
+
+ if (udev->flags.usb_mode != USB_MODE_HOST)
+ return; /* not supported */
+
+ xfer->endpoint->refcount_bw++;
+ if (xfer->endpoint->refcount_bw != 1)
+ return; /* already allocated */
+
+ speed = usbd_get_speed(udev);
+
+ switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_INTERRUPT:
+ /* allocate a microframe slot */
+
+ mask = 0x01;
+ slot = usb_hs_bandwidth_adjust(udev,
+ xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX, mask);
+
+ xfer->endpoint->usb_uframe = slot;
+ xfer->endpoint->usb_smask = mask << slot;
+
+ if ((speed != USB_SPEED_FULL) &&
+ (speed != USB_SPEED_LOW)) {
+ xfer->endpoint->usb_cmask = 0x00 ;
+ } else {
+ xfer->endpoint->usb_cmask = (-(0x04 << slot)) & 0xFE;
+ }
+ break;
+
+ case UE_ISOCHRONOUS:
+ switch (usbd_xfer_get_fps_shift(xfer)) {
+ case 0:
+ mask = 0xFF;
+ break;
+ case 1:
+ mask = 0x55;
+ break;
+ case 2:
+ mask = 0x11;
+ break;
+ default:
+ mask = 0x01;
+ break;
+ }
+
+ /* allocate a microframe multi-slot */
+
+ slot = usb_hs_bandwidth_adjust(udev,
+ xfer->max_frame_size, USB_HS_MICRO_FRAMES_MAX, mask);
+
+ xfer->endpoint->usb_uframe = slot;
+ xfer->endpoint->usb_cmask = 0;
+ xfer->endpoint->usb_smask = mask << slot;
+ break;
+
+ default:
+ xfer->endpoint->usb_uframe = 0;
+ xfer->endpoint->usb_cmask = 0;
+ xfer->endpoint->usb_smask = 0;
+ break;
+ }
+
+ DPRINTFN(11, "slot=%d, mask=0x%02x\n",
+ xfer->endpoint->usb_uframe,
+ xfer->endpoint->usb_smask >> xfer->endpoint->usb_uframe);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_hs_bandwidth_free
+ *
+ * This function is a wrapper function for "usb_hs_bandwidth_adjust()".
+ *------------------------------------------------------------------------*/
+void
+usb_hs_bandwidth_free(struct usb_xfer *xfer)
+{
+ struct usb_device *udev;
+ uint8_t slot;
+ uint8_t mask;
+
+ udev = xfer->xroot->udev;
+
+ if (udev->flags.usb_mode != USB_MODE_HOST)
+ return; /* not supported */
+
+ xfer->endpoint->refcount_bw--;
+ if (xfer->endpoint->refcount_bw != 0)
+ return; /* still allocated */
+
+ switch (xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE) {
+ case UE_INTERRUPT:
+ case UE_ISOCHRONOUS:
+
+ slot = xfer->endpoint->usb_uframe;
+ mask = xfer->endpoint->usb_smask;
+
+ /* free microframe slot(s): */
+ usb_hs_bandwidth_adjust(udev,
+ -xfer->max_frame_size, slot, mask >> slot);
+
+ DPRINTFN(11, "slot=%d, mask=0x%02x\n",
+ slot, mask >> slot);
+
+ xfer->endpoint->usb_uframe = 0;
+ xfer->endpoint->usb_cmask = 0;
+ xfer->endpoint->usb_smask = 0;
+ break;
+
+ default:
+ break;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_isoc_time_expand
+ *
+ * This function will expand the time counter from 7-bit to 16-bit.
+ *
+ * Returns:
+ * 16-bit isochronous time counter.
+ *------------------------------------------------------------------------*/
+uint16_t
+usb_isoc_time_expand(struct usb_bus *bus, uint16_t isoc_time_curr)
+{
+ uint16_t rem;
+
+ USB_BUS_LOCK_ASSERT(bus, MA_OWNED);
+
+ rem = bus->isoc_time_last & (USB_ISOC_TIME_MAX - 1);
+
+ isoc_time_curr &= (USB_ISOC_TIME_MAX - 1);
+
+ if (isoc_time_curr < rem) {
+ /* the time counter wrapped around */
+ bus->isoc_time_last += USB_ISOC_TIME_MAX;
+ }
+ /* update the remainder */
+
+ bus->isoc_time_last &= ~(USB_ISOC_TIME_MAX - 1);
+ bus->isoc_time_last |= isoc_time_curr;
+
+ return (bus->isoc_time_last);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_fs_isoc_schedule_alloc_slot
+ *
+ * This function will allocate bandwidth for an isochronous FULL speed
+ * transaction in the FULL speed schedule.
+ *
+ * Returns:
+ * <8: Success
+ * Else: Error
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_TT_SUPPORT
+uint8_t
+usbd_fs_isoc_schedule_alloc_slot(struct usb_xfer *isoc_xfer, uint16_t isoc_time)
+{
+ struct usb_xfer *xfer;
+ struct usb_xfer *pipe_xfer;
+ struct usb_bus *bus;
+ usb_frlength_t len;
+ usb_frlength_t data_len;
+ uint16_t delta;
+ uint16_t slot;
+ uint8_t retval;
+
+ data_len = 0;
+ slot = 0;
+
+ bus = isoc_xfer->xroot->bus;
+
+ TAILQ_FOREACH(xfer, &bus->intr_q.head, wait_entry) {
+ /* skip self, if any */
+
+ if (xfer == isoc_xfer)
+ continue;
+
+ /* check if this USB transfer is going through the same TT */
+
+ if (xfer->xroot->udev->parent_hs_hub !=
+ isoc_xfer->xroot->udev->parent_hs_hub) {
+ continue;
+ }
+ if ((isoc_xfer->xroot->udev->parent_hs_hub->
+ ddesc.bDeviceProtocol == UDPROTO_HSHUBMTT) &&
+ (xfer->xroot->udev->hs_port_no !=
+ isoc_xfer->xroot->udev->hs_port_no)) {
+ continue;
+ }
+ if (xfer->endpoint->methods != isoc_xfer->endpoint->methods)
+ continue;
+
+ /* check if isoc_time is part of this transfer */
+
+ delta = xfer->isoc_time_complete - isoc_time;
+ if (delta > 0 && delta <= xfer->nframes) {
+ delta = xfer->nframes - delta;
+
+ len = xfer->frlengths[delta];
+ len += 8;
+ len *= 7;
+ len /= 6;
+
+ data_len += len;
+ }
+
+ /*
+ * Check double buffered transfers. Only stream ID
+ * equal to zero is valid here!
+ */
+ TAILQ_FOREACH(pipe_xfer, &xfer->endpoint->endpoint_q[0].head,
+ wait_entry) {
+ /* skip self, if any */
+
+ if (pipe_xfer == isoc_xfer)
+ continue;
+
+ /* check if isoc_time is part of this transfer */
+
+ delta = pipe_xfer->isoc_time_complete - isoc_time;
+ if (delta > 0 && delta <= pipe_xfer->nframes) {
+ delta = pipe_xfer->nframes - delta;
+
+ len = pipe_xfer->frlengths[delta];
+ len += 8;
+ len *= 7;
+ len /= 6;
+
+ data_len += len;
+ }
+ }
+ }
+
+ while (data_len >= USB_FS_BYTES_PER_HS_UFRAME) {
+ data_len -= USB_FS_BYTES_PER_HS_UFRAME;
+ slot++;
+ }
+
+ /* check for overflow */
+
+ if (slot >= USB_FS_ISOC_UFRAME_MAX)
+ return (255);
+
+ retval = slot;
+
+ delta = isoc_xfer->isoc_time_complete - isoc_time;
+ if (delta > 0 && delta <= isoc_xfer->nframes) {
+ delta = isoc_xfer->nframes - delta;
+
+ len = isoc_xfer->frlengths[delta];
+ len += 8;
+ len *= 7;
+ len /= 6;
+
+ data_len += len;
+ }
+
+ while (data_len >= USB_FS_BYTES_PER_HS_UFRAME) {
+ data_len -= USB_FS_BYTES_PER_HS_UFRAME;
+ slot++;
+ }
+
+ /* check for overflow */
+
+ if (slot >= USB_FS_ISOC_UFRAME_MAX)
+ return (255);
+
+ return (retval);
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usb_bus_port_get_device
+ *
+ * This function is NULL safe.
+ *------------------------------------------------------------------------*/
+struct usb_device *
+usb_bus_port_get_device(struct usb_bus *bus, struct usb_port *up)
+{
+ if ((bus == NULL) || (up == NULL)) {
+ /* be NULL safe */
+ return (NULL);
+ }
+ if (up->device_index == 0) {
+ /* nothing to do */
+ return (NULL);
+ }
+ return (bus->devices[up->device_index]);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bus_port_set_device
+ *
+ * This function is NULL safe.
+ *------------------------------------------------------------------------*/
+void
+usb_bus_port_set_device(struct usb_bus *bus, struct usb_port *up,
+ struct usb_device *udev, uint8_t device_index)
+{
+ if (bus == NULL) {
+ /* be NULL safe */
+ return;
+ }
+ /*
+ * There is only one case where we don't
+ * have an USB port, and that is the Root Hub!
+ */
+ if (up) {
+ if (udev) {
+ up->device_index = device_index;
+ } else {
+ device_index = up->device_index;
+ up->device_index = 0;
+ }
+ }
+ /*
+ * Make relationships to our new device
+ */
+ if (device_index != 0) {
+#if USB_HAVE_UGEN
+ mtx_lock(&usb_ref_lock);
+#endif
+ bus->devices[device_index] = udev;
+#if USB_HAVE_UGEN
+ mtx_unlock(&usb_ref_lock);
+#endif
+ }
+ /*
+ * Debug print
+ */
+ DPRINTFN(2, "bus %p devices[%u] = %p\n", bus, device_index, udev);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_needs_explore
+ *
+ * This functions is called when the USB event thread needs to run.
+ *------------------------------------------------------------------------*/
+void
+usb_needs_explore(struct usb_bus *bus, uint8_t do_probe)
+{
+ uint8_t do_unlock;
+
+ DPRINTF("\n");
+
+ if (cold != 0) {
+ DPRINTF("Cold\n");
+ return;
+ }
+
+ if (bus == NULL) {
+ DPRINTF("No bus pointer!\n");
+ return;
+ }
+ if ((bus->devices == NULL) ||
+ (bus->devices[USB_ROOT_HUB_ADDR] == NULL)) {
+ DPRINTF("No root HUB\n");
+ return;
+ }
+ if (mtx_owned(&bus->bus_mtx)) {
+ do_unlock = 0;
+ } else {
+ USB_BUS_LOCK(bus);
+ do_unlock = 1;
+ }
+ if (do_probe) {
+ bus->do_probe = 1;
+ }
+ if (usb_proc_msignal(USB_BUS_EXPLORE_PROC(bus),
+ &bus->explore_msg[0], &bus->explore_msg[1])) {
+ /* ignore */
+ }
+ if (do_unlock) {
+ USB_BUS_UNLOCK(bus);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_needs_explore_all
+ *
+ * This function is called whenever a new driver is loaded and will
+ * cause that all USB buses are re-explored.
+ *------------------------------------------------------------------------*/
+void
+usb_needs_explore_all(void)
+{
+ struct usb_bus *bus;
+ devclass_t dc;
+ device_t dev;
+ int max;
+
+ DPRINTFN(3, "\n");
+
+ dc = usb_devclass_ptr;
+ if (dc == NULL) {
+ DPRINTFN(0, "no devclass\n");
+ return;
+ }
+ /*
+ * Explore all USB buses in parallel.
+ */
+ max = devclass_get_maxunit(dc);
+ while (max >= 0) {
+ dev = devclass_get_device(dc, max);
+ if (dev) {
+ bus = device_get_softc(dev);
+ if (bus) {
+ usb_needs_explore(bus, 1);
+ }
+ }
+ max--;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_needs_explore_init
+ *
+ * This function will ensure that the USB controllers are not enumerated
+ * until the "cold" variable is cleared.
+ *------------------------------------------------------------------------*/
+static void
+usb_needs_explore_init(void *arg)
+{
+ /*
+ * The cold variable should be cleared prior to this function
+ * being called:
+ */
+ if (cold == 0)
+ usb_needs_explore_all();
+ else
+ DPRINTFN(-1, "Cold variable is still set!\n");
+}
+SYSINIT(usb_needs_explore_init, SI_SUB_KICK_SCHEDULER, SI_ORDER_SECOND, usb_needs_explore_init, NULL);
+
+/*------------------------------------------------------------------------*
+ * usb_bus_power_update
+ *
+ * This function will ensure that all USB devices on the given bus are
+ * properly suspended or resumed according to the device transfer
+ * state.
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_POWERD
+void
+usb_bus_power_update(struct usb_bus *bus)
+{
+ usb_needs_explore(bus, 0 /* no probe */ );
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_power_ref
+ *
+ * This function will modify the power save reference counts and
+ * wakeup the USB device associated with the given USB transfer, if
+ * needed.
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_POWERD
+void
+usbd_transfer_power_ref(struct usb_xfer *xfer, int val)
+{
+ static const usb_power_mask_t power_mask[4] = {
+ [UE_CONTROL] = USB_HW_POWER_CONTROL,
+ [UE_BULK] = USB_HW_POWER_BULK,
+ [UE_INTERRUPT] = USB_HW_POWER_INTERRUPT,
+ [UE_ISOCHRONOUS] = USB_HW_POWER_ISOC,
+ };
+ struct usb_device *udev;
+ uint8_t needs_explore;
+ uint8_t needs_hw_power;
+ uint8_t xfer_type;
+
+ udev = xfer->xroot->udev;
+
+ if (udev->device_index == USB_ROOT_HUB_ADDR) {
+ /* no power save for root HUB */
+ return;
+ }
+ USB_BUS_LOCK(udev->bus);
+
+ xfer_type = xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE;
+
+ udev->pwr_save.last_xfer_time = ticks;
+ udev->pwr_save.type_refs[xfer_type] += val;
+
+ if (xfer->flags_int.control_xfr) {
+ udev->pwr_save.read_refs += val;
+ if (xfer->flags_int.usb_mode == USB_MODE_HOST) {
+ /*
+ * It is not allowed to suspend during a
+ * control transfer:
+ */
+ udev->pwr_save.write_refs += val;
+ }
+ } else if (USB_GET_DATA_ISREAD(xfer)) {
+ udev->pwr_save.read_refs += val;
+ } else {
+ udev->pwr_save.write_refs += val;
+ }
+
+ if (val > 0) {
+ if (udev->flags.self_suspended)
+ needs_explore = usb_peer_should_wakeup(udev);
+ else
+ needs_explore = 0;
+
+ if (!(udev->bus->hw_power_state & power_mask[xfer_type])) {
+ DPRINTF("Adding type %u to power state\n", xfer_type);
+ udev->bus->hw_power_state |= power_mask[xfer_type];
+ needs_hw_power = 1;
+ } else {
+ needs_hw_power = 0;
+ }
+ } else {
+ needs_explore = 0;
+ needs_hw_power = 0;
+ }
+
+ USB_BUS_UNLOCK(udev->bus);
+
+ if (needs_explore) {
+ DPRINTF("update\n");
+ usb_bus_power_update(udev->bus);
+ } else if (needs_hw_power) {
+ DPRINTF("needs power\n");
+ if (udev->bus->methods->set_hw_power != NULL) {
+ (udev->bus->methods->set_hw_power) (udev->bus);
+ }
+ }
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usb_peer_should_wakeup
+ *
+ * This function returns non-zero if the current device should wake up.
+ *------------------------------------------------------------------------*/
+static uint8_t
+usb_peer_should_wakeup(struct usb_device *udev)
+{
+ return (((udev->power_mode == USB_POWER_MODE_ON) &&
+ (udev->flags.usb_mode == USB_MODE_HOST)) ||
+ (udev->driver_added_refcount != udev->bus->driver_added_refcount) ||
+ (udev->re_enumerate_wait != USB_RE_ENUM_DONE) ||
+ (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0) ||
+ (udev->pwr_save.write_refs != 0) ||
+ ((udev->pwr_save.read_refs != 0) &&
+ (udev->flags.usb_mode == USB_MODE_HOST) &&
+ (usb_peer_can_wakeup(udev) == 0)));
+}
+
+/*------------------------------------------------------------------------*
+ * usb_bus_powerd
+ *
+ * This function implements the USB power daemon and is called
+ * regularly from the USB explore thread.
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_POWERD
+void
+usb_bus_powerd(struct usb_bus *bus)
+{
+ struct usb_device *udev;
+ usb_ticks_t temp;
+ usb_ticks_t limit;
+ usb_ticks_t mintime;
+ usb_size_t type_refs[5];
+ uint8_t x;
+
+ limit = usb_power_timeout;
+ if (limit == 0)
+ limit = hz;
+ else if (limit > 255)
+ limit = 255 * hz;
+ else
+ limit = limit * hz;
+
+ DPRINTF("bus=%p\n", bus);
+
+ USB_BUS_LOCK(bus);
+
+ /*
+ * The root HUB device is never suspended
+ * and we simply skip it.
+ */
+ for (x = USB_ROOT_HUB_ADDR + 1;
+ x != bus->devices_max; x++) {
+ udev = bus->devices[x];
+ if (udev == NULL)
+ continue;
+
+ temp = ticks - udev->pwr_save.last_xfer_time;
+
+ if (usb_peer_should_wakeup(udev)) {
+ /* check if we are suspended */
+ if (udev->flags.self_suspended != 0) {
+ USB_BUS_UNLOCK(bus);
+ usb_dev_resume_peer(udev);
+ USB_BUS_LOCK(bus);
+ }
+ } else if ((temp >= limit) &&
+ (udev->flags.usb_mode == USB_MODE_HOST) &&
+ (udev->flags.self_suspended == 0)) {
+ /* try to do suspend */
+
+ USB_BUS_UNLOCK(bus);
+ usb_dev_suspend_peer(udev);
+ USB_BUS_LOCK(bus);
+ }
+ }
+
+ /* reset counters */
+
+ mintime = (usb_ticks_t)-1;
+ type_refs[0] = 0;
+ type_refs[1] = 0;
+ type_refs[2] = 0;
+ type_refs[3] = 0;
+ type_refs[4] = 0;
+
+ /* Re-loop all the devices to get the actual state */
+
+ for (x = USB_ROOT_HUB_ADDR + 1;
+ x != bus->devices_max; x++) {
+ udev = bus->devices[x];
+ if (udev == NULL)
+ continue;
+
+ /* we found a non-Root-Hub USB device */
+ type_refs[4] += 1;
+
+ /* "last_xfer_time" can be updated by a resume */
+ temp = ticks - udev->pwr_save.last_xfer_time;
+
+ /*
+ * Compute minimum time since last transfer for the complete
+ * bus:
+ */
+ if (temp < mintime)
+ mintime = temp;
+
+ if (udev->flags.self_suspended == 0) {
+ type_refs[0] += udev->pwr_save.type_refs[0];
+ type_refs[1] += udev->pwr_save.type_refs[1];
+ type_refs[2] += udev->pwr_save.type_refs[2];
+ type_refs[3] += udev->pwr_save.type_refs[3];
+ }
+ }
+
+ if (mintime >= (usb_ticks_t)(1 * hz)) {
+ /* recompute power masks */
+ DPRINTF("Recomputing power masks\n");
+ bus->hw_power_state = 0;
+ if (type_refs[UE_CONTROL] != 0)
+ bus->hw_power_state |= USB_HW_POWER_CONTROL;
+ if (type_refs[UE_BULK] != 0)
+ bus->hw_power_state |= USB_HW_POWER_BULK;
+ if (type_refs[UE_INTERRUPT] != 0)
+ bus->hw_power_state |= USB_HW_POWER_INTERRUPT;
+ if (type_refs[UE_ISOCHRONOUS] != 0)
+ bus->hw_power_state |= USB_HW_POWER_ISOC;
+ if (type_refs[4] != 0)
+ bus->hw_power_state |= USB_HW_POWER_NON_ROOT_HUB;
+ }
+ USB_BUS_UNLOCK(bus);
+
+ if (bus->methods->set_hw_power != NULL) {
+ /* always update hardware power! */
+ (bus->methods->set_hw_power) (bus);
+ }
+ return;
+}
+#endif
+
+static usb_error_t
+usbd_device_30_remote_wakeup(struct usb_device *udev, uint8_t bRequest)
+{
+ struct usb_device_request req = {};
+
+ req.bmRequestType = UT_WRITE_INTERFACE;
+ req.bRequest = bRequest;
+ USETW(req.wValue, USB_INTERFACE_FUNC_SUSPEND);
+ USETW(req.wIndex, USB_INTERFACE_FUNC_SUSPEND_LP |
+ USB_INTERFACE_FUNC_SUSPEND_RW);
+
+ return (usbd_do_request(udev, NULL, &req, 0));
+}
+
+static usb_error_t
+usbd_clear_dev_wakeup(struct usb_device *udev)
+{
+ usb_error_t err;
+
+ if (usb_device_20_compatible(udev)) {
+ err = usbd_req_clear_device_feature(udev,
+ NULL, UF_DEVICE_REMOTE_WAKEUP);
+ } else {
+ err = usbd_device_30_remote_wakeup(udev,
+ UR_CLEAR_FEATURE);
+ }
+ return (err);
+}
+
+static usb_error_t
+usbd_set_dev_wakeup(struct usb_device *udev)
+{
+ usb_error_t err;
+
+ if (usb_device_20_compatible(udev)) {
+ err = usbd_req_set_device_feature(udev,
+ NULL, UF_DEVICE_REMOTE_WAKEUP);
+ } else {
+ err = usbd_device_30_remote_wakeup(udev,
+ UR_SET_FEATURE);
+ }
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_dev_resume_peer
+ *
+ * This function will resume an USB peer and do the required USB
+ * signalling to get an USB device out of the suspended state.
+ *------------------------------------------------------------------------*/
+static void
+usb_dev_resume_peer(struct usb_device *udev)
+{
+ struct usb_bus *bus;
+ int err;
+
+ /* be NULL safe */
+ if (udev == NULL)
+ return;
+
+ /* check if already resumed */
+ if (udev->flags.self_suspended == 0)
+ return;
+
+ /* we need a parent HUB to do resume */
+ if (udev->parent_hub == NULL)
+ return;
+
+ DPRINTF("udev=%p\n", udev);
+
+ if ((udev->flags.usb_mode == USB_MODE_DEVICE) &&
+ (udev->flags.remote_wakeup == 0)) {
+ /*
+ * If the host did not set the remote wakeup feature, we can
+ * not wake it up either!
+ */
+ DPRINTF("remote wakeup is not set!\n");
+ return;
+ }
+ /* get bus pointer */
+ bus = udev->bus;
+
+ /* resume parent hub first */
+ usb_dev_resume_peer(udev->parent_hub);
+
+ /* reduce chance of instant resume failure by waiting a little bit */
+ usb_pause_mtx(NULL, USB_MS_TO_TICKS(20));
+
+ if (usb_device_20_compatible(udev)) {
+ /* resume current port (Valid in Host and Device Mode) */
+ err = usbd_req_clear_port_feature(udev->parent_hub,
+ NULL, udev->port_no, UHF_PORT_SUSPEND);
+ } else {
+ /* resume current port (Valid in Host and Device Mode) */
+ err = usbd_req_set_port_link_state(udev->parent_hub,
+ NULL, udev->port_no, UPS_PORT_LS_U0);
+ }
+
+ if (err != 0) {
+ DPRINTFN(0, "Resuming port failed: %s (ignored)\n",
+ usbd_errstr(err));
+ }
+
+ /* resume settle time */
+ usb_pause_mtx(NULL, USB_MS_TO_TICKS(usb_port_resume_delay));
+
+ if (bus->methods->device_resume != NULL) {
+ /* resume USB device on the USB controller */
+ (bus->methods->device_resume) (udev);
+ }
+ USB_BUS_LOCK(bus);
+ /* set that this device is now resumed */
+ udev->flags.self_suspended = 0;
+#if USB_HAVE_POWERD
+ /* make sure that we don't go into suspend right away */
+ udev->pwr_save.last_xfer_time = ticks;
+
+ /* make sure the needed power masks are on */
+ if (udev->pwr_save.type_refs[UE_CONTROL] != 0)
+ bus->hw_power_state |= USB_HW_POWER_CONTROL;
+ if (udev->pwr_save.type_refs[UE_BULK] != 0)
+ bus->hw_power_state |= USB_HW_POWER_BULK;
+ if (udev->pwr_save.type_refs[UE_INTERRUPT] != 0)
+ bus->hw_power_state |= USB_HW_POWER_INTERRUPT;
+ if (udev->pwr_save.type_refs[UE_ISOCHRONOUS] != 0)
+ bus->hw_power_state |= USB_HW_POWER_ISOC;
+#endif
+ USB_BUS_UNLOCK(bus);
+
+ if (bus->methods->set_hw_power != NULL) {
+ /* always update hardware power! */
+ (bus->methods->set_hw_power) (bus);
+ }
+
+ usbd_sr_lock(udev);
+
+ /* notify all sub-devices about resume */
+ err = usb_suspend_resume(udev, 0);
+
+ usbd_sr_unlock(udev);
+
+ /* check if peer has wakeup capability */
+ if (usb_peer_can_wakeup(udev)) {
+ /* clear remote wakeup */
+ err = usbd_clear_dev_wakeup(udev);
+ if (err) {
+ DPRINTFN(0, "Clearing device "
+ "remote wakeup failed: %s\n",
+ usbd_errstr(err));
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_dev_suspend_peer
+ *
+ * This function will suspend an USB peer and do the required USB
+ * signalling to get an USB device into the suspended state.
+ *------------------------------------------------------------------------*/
+static void
+usb_dev_suspend_peer(struct usb_device *udev)
+{
+ struct usb_device *child;
+ int err;
+ uint8_t x;
+ uint8_t nports;
+
+repeat:
+ /* be NULL safe */
+ if (udev == NULL)
+ return;
+
+ /* check if already suspended */
+ if (udev->flags.self_suspended)
+ return;
+
+ /* we need a parent HUB to do suspend */
+ if (udev->parent_hub == NULL)
+ return;
+
+ DPRINTF("udev=%p\n", udev);
+
+ /* check if the current device is a HUB */
+ if (udev->hub != NULL) {
+ nports = udev->hub->nports;
+
+ /* check if all devices on the HUB are suspended */
+ for (x = 0; x != nports; x++) {
+ child = usb_bus_port_get_device(udev->bus,
+ udev->hub->ports + x);
+
+ if (child == NULL)
+ continue;
+
+ if (child->flags.self_suspended)
+ continue;
+
+ DPRINTFN(1, "Port %u is busy on the HUB!\n", x + 1);
+ return;
+ }
+ }
+
+ if (usb_peer_can_wakeup(udev)) {
+ /*
+ * This request needs to be done before we set
+ * "udev->flags.self_suspended":
+ */
+
+ /* allow device to do remote wakeup */
+ err = usbd_set_dev_wakeup(udev);
+ if (err) {
+ DPRINTFN(0, "Setting device "
+ "remote wakeup failed\n");
+ }
+ }
+
+ USB_BUS_LOCK(udev->bus);
+ /*
+ * Checking for suspend condition and setting suspended bit
+ * must be atomic!
+ */
+ err = usb_peer_should_wakeup(udev);
+ if (err == 0) {
+ /*
+ * Set that this device is suspended. This variable
+ * must be set before calling USB controller suspend
+ * callbacks.
+ */
+ udev->flags.self_suspended = 1;
+ }
+ USB_BUS_UNLOCK(udev->bus);
+
+ if (err != 0) {
+ if (usb_peer_can_wakeup(udev)) {
+ /* allow device to do remote wakeup */
+ err = usbd_clear_dev_wakeup(udev);
+ if (err) {
+ DPRINTFN(0, "Setting device "
+ "remote wakeup failed\n");
+ }
+ }
+
+ if (udev->flags.usb_mode == USB_MODE_DEVICE) {
+ /* resume parent HUB first */
+ usb_dev_resume_peer(udev->parent_hub);
+
+ /* reduce chance of instant resume failure by waiting a little bit */
+ usb_pause_mtx(NULL, USB_MS_TO_TICKS(20));
+
+ /* resume current port (Valid in Host and Device Mode) */
+ err = usbd_req_clear_port_feature(udev->parent_hub,
+ NULL, udev->port_no, UHF_PORT_SUSPEND);
+
+ /* resume settle time */
+ usb_pause_mtx(NULL, USB_MS_TO_TICKS(usb_port_resume_delay));
+ }
+ DPRINTF("Suspend was cancelled!\n");
+ return;
+ }
+
+ usbd_sr_lock(udev);
+
+ /* notify all sub-devices about suspend */
+ err = usb_suspend_resume(udev, 1);
+
+ usbd_sr_unlock(udev);
+
+ if (udev->bus->methods->device_suspend != NULL) {
+ usb_timeout_t temp;
+
+ /* suspend device on the USB controller */
+ (udev->bus->methods->device_suspend) (udev);
+
+ /* do DMA delay */
+ temp = usbd_get_dma_delay(udev);
+ if (temp != 0)
+ usb_pause_mtx(NULL, USB_MS_TO_TICKS(temp));
+ }
+
+ if (usb_device_20_compatible(udev)) {
+ /* suspend current port */
+ err = usbd_req_set_port_feature(udev->parent_hub,
+ NULL, udev->port_no, UHF_PORT_SUSPEND);
+ if (err) {
+ DPRINTFN(0, "Suspending port failed\n");
+ return;
+ }
+ } else {
+ /* suspend current port */
+ err = usbd_req_set_port_link_state(udev->parent_hub,
+ NULL, udev->port_no, UPS_PORT_LS_U3);
+ if (err) {
+ DPRINTFN(0, "Suspending port failed\n");
+ return;
+ }
+ }
+
+ udev = udev->parent_hub;
+ goto repeat;
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_set_power_mode
+ *
+ * This function will set the power mode, see USB_POWER_MODE_XXX for a
+ * USB device.
+ *------------------------------------------------------------------------*/
+void
+usbd_set_power_mode(struct usb_device *udev, uint8_t power_mode)
+{
+ /* filter input argument */
+ if ((power_mode != USB_POWER_MODE_ON) &&
+ (power_mode != USB_POWER_MODE_OFF))
+ power_mode = USB_POWER_MODE_SAVE;
+
+ power_mode = usbd_filter_power_mode(udev, power_mode);
+
+ udev->power_mode = power_mode; /* update copy of power mode */
+
+#if USB_HAVE_POWERD
+ usb_bus_power_update(udev->bus);
+#else
+ usb_needs_explore(udev->bus, 0 /* no probe */ );
+#endif
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_filter_power_mode
+ *
+ * This function filters the power mode based on hardware requirements.
+ *------------------------------------------------------------------------*/
+uint8_t
+usbd_filter_power_mode(struct usb_device *udev, uint8_t power_mode)
+{
+ const struct usb_bus_methods *mtod;
+ int8_t temp;
+
+ mtod = udev->bus->methods;
+ temp = -1;
+
+ if (mtod->get_power_mode != NULL)
+ (mtod->get_power_mode) (udev, &temp);
+
+ /* check if we should not filter */
+ if (temp < 0)
+ return (power_mode);
+
+ /* use fixed power mode given by hardware driver */
+ return (temp);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_start_re_enumerate
+ *
+ * This function starts re-enumeration of the given USB device. This
+ * function does not need to be called BUS-locked. This function does
+ * not wait until the re-enumeration is completed.
+ *------------------------------------------------------------------------*/
+void
+usbd_start_re_enumerate(struct usb_device *udev)
+{
+ if (udev->re_enumerate_wait == USB_RE_ENUM_DONE) {
+ udev->re_enumerate_wait = USB_RE_ENUM_START;
+ usb_needs_explore(udev->bus, 0);
+ }
+}
+
+/*-----------------------------------------------------------------------*
+ * usbd_start_set_config
+ *
+ * This function starts setting a USB configuration. This function
+ * does not need to be called BUS-locked. This function does not wait
+ * until the set USB configuratino is completed.
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_start_set_config(struct usb_device *udev, uint8_t index)
+{
+ if (udev->re_enumerate_wait == USB_RE_ENUM_DONE) {
+ if (udev->curr_config_index == index) {
+ /* no change needed */
+ return (0);
+ }
+ udev->next_config_index = index;
+ udev->re_enumerate_wait = USB_RE_ENUM_SET_CONFIG;
+ usb_needs_explore(udev->bus, 0);
+ return (0);
+ } else if (udev->re_enumerate_wait == USB_RE_ENUM_SET_CONFIG) {
+ if (udev->next_config_index == index) {
+ /* no change needed */
+ return (0);
+ }
+ }
+ return (USB_ERR_PENDING_REQUESTS);
+}
diff --git a/sys/dev/usb/usb_hub.h b/sys/dev/usb/usb_hub.h
new file mode 100644
index 000000000000..ffeabc7c0f05
--- /dev/null
+++ b/sys/dev/usb/usb_hub.h
@@ -0,0 +1,81 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_HUB_H_
+#define _USB_HUB_H_
+
+/*
+ * The following structure defines an USB port.
+ */
+struct usb_port {
+ uint8_t restartcnt;
+#define USB_RESTART_MAX 5
+ uint8_t device_index; /* zero means not valid */
+ enum usb_hc_mode usb_mode; /* host or device mode */
+#if USB_HAVE_TT_SUPPORT
+ struct usb_device_request req_reset_tt __aligned(4);
+#endif
+};
+
+/*
+ * The following structure defines an USB HUB.
+ */
+struct usb_hub {
+ struct usb_device *hubudev; /* the HUB device */
+ usb_error_t (*explore) (struct usb_device *hub);
+ void *hubsoftc;
+#if USB_HAVE_TT_SUPPORT
+ struct usb_udev_msg tt_msg[2];
+#endif
+ usb_size_t uframe_usage[USB_HS_MICRO_FRAMES_MAX];
+ uint16_t portpower; /* mA per USB port */
+ uint8_t isoc_last_time;
+ uint8_t nports;
+#if (USB_HAVE_FIXED_PORT == 0)
+ struct usb_port ports[0];
+#else
+ struct usb_port ports[USB_MAX_PORTS];
+#endif
+};
+
+/* function prototypes */
+
+void usb_hs_bandwidth_alloc(struct usb_xfer *xfer);
+void usb_hs_bandwidth_free(struct usb_xfer *xfer);
+void usb_bus_port_set_device(struct usb_bus *bus, struct usb_port *up,
+ struct usb_device *udev, uint8_t device_index);
+struct usb_device *usb_bus_port_get_device(struct usb_bus *bus,
+ struct usb_port *up);
+void usb_needs_explore(struct usb_bus *bus, uint8_t do_probe);
+void usb_needs_explore_all(void);
+void usb_bus_power_update(struct usb_bus *bus);
+void usb_bus_powerd(struct usb_bus *bus);
+void uhub_root_intr(struct usb_bus *, const uint8_t *, uint8_t);
+usb_error_t uhub_query_info(struct usb_device *, uint8_t *, uint8_t *);
+void uhub_explore_handle_re_enumerate(struct usb_device *);
+
+#endif /* _USB_HUB_H_ */
diff --git a/sys/dev/usb/usb_hub_acpi.c b/sys/dev/usb/usb_hub_acpi.c
new file mode 100644
index 000000000000..a89f48fda630
--- /dev/null
+++ b/sys/dev/usb/usb_hub_acpi.c
@@ -0,0 +1,609 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
+ * Copyright (c) 2008-2010 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 2019 Takanori Watanabe.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * USB spec: http://www.usb.org/developers/docs/usbspec.zip
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+
+#define USB_DEBUG_VAR uhub_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_request.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_hub.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_dynamic.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+#include <dev/usb/usb_hub_private.h>
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <dev/acpica/acpivar.h>
+#include <sys/sbuf.h>
+
+#define ACPI_PLD_SIZE 20
+struct acpi_uhub_port {
+ ACPI_HANDLE handle;
+#define ACPI_UPC_CONNECTABLE 0x80000000
+#define ACPI_UPC_PORTTYPE(x) ((x)&0xff)
+ uint32_t upc;
+ uint8_t pld[ACPI_PLD_SIZE];
+};
+
+struct acpi_uhub_softc {
+ struct uhub_softc usc;
+ uint8_t nports;
+ ACPI_HANDLE ah;
+ struct acpi_uhub_port *port;
+};
+
+static UINT32
+acpi_uhub_find_rh_cb(ACPI_HANDLE ah, UINT32 nl, void *ctx, void **status)
+{
+ ACPI_DEVICE_INFO *devinfo;
+ UINT32 ret;
+
+ *status = NULL;
+ devinfo = NULL;
+
+ ret = AcpiGetObjectInfo(ah, &devinfo);
+ if (ACPI_SUCCESS(ret)) {
+ if ((devinfo->Valid & ACPI_VALID_ADR) &&
+ (devinfo->Address == 0)) {
+ ret = AE_CTRL_TERMINATE;
+ *status = ah;
+ }
+ AcpiOsFree(devinfo);
+ }
+ return (ret);
+}
+
+static const char *
+acpi_uhub_upc_type(uint8_t type)
+{
+ const char *typelist[] = {"TypeA", "MiniAB", "Express",
+ "USB3-A", "USB3-B", "USB-MicroB",
+ "USB3-MicroAB", "USB3-PowerB",
+ "TypeC-USB2", "TypeC-Switch",
+ "TypeC-nonSwitch"};
+ const int last = sizeof(typelist) / sizeof(typelist[0]);
+
+ if (type == 0xff) {
+ return "Proprietary";
+ }
+
+ return (type < last) ? typelist[type] : "Unknown";
+}
+
+static int
+acpi_uhub_parse_upc(device_t dev, unsigned p, ACPI_HANDLE ah, struct sysctl_oid_list *poid)
+{
+ ACPI_BUFFER buf;
+ struct acpi_uhub_softc *sc = device_get_softc(dev);
+ struct acpi_uhub_port *port = &sc->port[p - 1];
+
+ buf.Pointer = NULL;
+ buf.Length = ACPI_ALLOCATE_BUFFER;
+
+ if (AcpiEvaluateObject(ah, "_UPC", NULL, &buf) == AE_OK) {
+ ACPI_OBJECT *obj = buf.Pointer;
+ UINT64 porttypenum, conn;
+ uint8_t *connectable;
+
+ acpi_PkgInt(obj, 0, &conn);
+ acpi_PkgInt(obj, 1, &porttypenum);
+ connectable = conn ? "" : "non";
+
+ port->upc = porttypenum;
+ port->upc |= (conn) ? (ACPI_UPC_CONNECTABLE) : 0;
+
+ if (usb_debug)
+ device_printf(dev, "Port %u %sconnectable %s\n",
+ p, connectable,
+ acpi_uhub_upc_type(porttypenum));
+
+ SYSCTL_ADD_U32(
+ device_get_sysctl_ctx(dev),
+ poid, OID_AUTO,
+ "upc",
+ CTLFLAG_RD | CTLFLAG_MPSAFE,
+ SYSCTL_NULL_U32_PTR, port->upc,
+ "UPC value. MSB is visible flag");
+ }
+ AcpiOsFree(buf.Pointer);
+
+ return (0);
+}
+static int
+acpi_uhub_port_sysctl(SYSCTL_HANDLER_ARGS)
+{
+ struct acpi_uhub_port *port = oidp->oid_arg1;
+ struct sbuf sb;
+ int error;
+
+ sbuf_new_for_sysctl(&sb, NULL, 256, req);
+ sbuf_printf(&sb, "Handle %s\n", acpi_name(port->handle));
+ if (port->upc == 0xffffffff) {
+ sbuf_printf(&sb, "\tNo information\n");
+ goto end;
+ }
+ sbuf_printf(&sb, "\t");
+ if (port->upc & ACPI_UPC_CONNECTABLE) {
+ sbuf_printf(&sb, "Connectable ");
+ }
+ sbuf_printf(&sb, "%s port\n", acpi_uhub_upc_type(port->upc & 0xff));
+
+ if ((port->pld[0] & 0x80) == 0) {
+ sbuf_printf(&sb,
+ "\tColor:#%02x%02x%02x\n",
+ port->pld[1], port->pld[2],
+ port->pld[3]);
+ }
+ sbuf_printf(&sb, "\tWidth %d mm Height %d mm\n",
+ port->pld[4] | (port->pld[5] << 8),
+ port->pld[6] | (port->pld[7] << 8));
+ if (port->pld[8] & 1) {
+ sbuf_printf(&sb, "\tVisible\n");
+ }
+ if (port->pld[8] & 2) {
+ sbuf_printf(&sb, "\tDock\n");
+ }
+ if (port->pld[8] & 4) {
+ sbuf_printf(&sb, "\tLid\n");
+ } {
+ int panelpos = (port->pld[8] >> 3) & 7;
+ const char *panposstr[] = {"Top", "Bottom", "Left",
+ "Right", "Front", "Back",
+ "Unknown", "Invalid"};
+ const char *shapestr[] = {
+ "Round", "Oval", "Square", "VRect", "HRect",
+ "VTrape", "HTrape", "Unknown", "Chamferd",
+ "Rsvd", "Rsvd", "Rsvd", "Rsvd",
+ "Rsvd", "Rsvd", "Rsvd", "Rsvd"};
+
+ sbuf_printf(&sb, "\tPanelPosition: %s\n", panposstr[panelpos]);
+ if (panelpos < 6) {
+ const char *posstr[] = {"Upper", "Center",
+ "Lower", "Invalid"};
+
+ sbuf_printf(&sb, "\tVertPosition: %s\n",
+ posstr[(port->pld[8] >> 6) & 3]);
+ sbuf_printf(&sb, "\tHorizPosition: %s\n",
+ posstr[(port->pld[9]) & 3]);
+ }
+ sbuf_printf(&sb, "\tShape: %s\n",
+ shapestr[(port->pld[9] >> 2) & 0xf]);
+ sbuf_printf(&sb, "\tGroup Orientation %s\n",
+ ((port->pld[9] >> 6) & 1) ? "Vertical" :
+ "Horizontal");
+ sbuf_printf(&sb, "\tGroupToken %x\n",
+ ((port->pld[9] >> 7)
+ | (port->pld[10] << 1)) & 0xff);
+ sbuf_printf(&sb, "\tGroupPosition %x\n",
+ ((port->pld[10] >> 7)
+ | (port->pld[11] << 1)) & 0xff);
+ sbuf_printf(&sb, "\t%s %s %s\n",
+ (port->pld[11] & 0x80) ?
+ "Bay" : "",
+ (port->pld[12] & 1) ? "Eject" : "",
+ (port->pld[12] & 2) ? "OSPM" : ""
+ );
+ }
+ if ((port->pld[0] & 0x7f) >= 2) {
+ sbuf_printf(&sb, "\tVOFF%d mm HOFF %dmm",
+ port->pld[16] | (port->pld[17] << 8),
+ port->pld[18] | (port->pld[19] << 8));
+ }
+
+end:
+ error = sbuf_finish(&sb);
+ sbuf_delete(&sb);
+ return (error);
+}
+
+static int
+acpi_uhub_parse_pld(device_t dev, unsigned p, ACPI_HANDLE ah, struct sysctl_oid_list *tree)
+{
+ ACPI_BUFFER buf;
+ struct acpi_uhub_softc *sc = device_get_softc(dev);
+ struct acpi_uhub_port *port = &sc->port[p - 1];
+
+ buf.Pointer = NULL;
+ buf.Length = ACPI_ALLOCATE_BUFFER;
+
+ if (AcpiEvaluateObject(ah, "_PLD", NULL, &buf) == AE_OK) {
+ ACPI_OBJECT *obj;
+ unsigned char *resbuf;
+ int len;
+
+ obj = buf.Pointer;
+
+ if (obj->Type == ACPI_TYPE_PACKAGE
+ && obj->Package.Elements[0].Type == ACPI_TYPE_BUFFER) {
+ ACPI_OBJECT *obj1;
+
+ obj1 = &obj->Package.Elements[0];
+ len = obj1->Buffer.Length;
+ resbuf = obj1->Buffer.Pointer;
+ } else if (obj->Type == ACPI_TYPE_BUFFER) {
+ len = obj->Buffer.Length;
+ resbuf = obj->Buffer.Pointer;
+ } else {
+ goto skip;
+ }
+ len = (len < ACPI_PLD_SIZE) ? len : ACPI_PLD_SIZE;
+ memcpy(port->pld, resbuf, len);
+ SYSCTL_ADD_OPAQUE(
+ device_get_sysctl_ctx(dev), tree, OID_AUTO,
+ "pldraw", CTLFLAG_RD | CTLFLAG_MPSAFE,
+ port->pld, len, "A", "Raw PLD value");
+
+ if (usb_debug) {
+ device_printf(dev, "Revision:%d\n",
+ resbuf[0] & 0x7f);
+ if ((resbuf[0] & 0x80) == 0) {
+ device_printf(dev,
+ "Color:#%02x%02x%02x\n",
+ resbuf[1], resbuf[2],
+ resbuf[3]);
+ }
+ device_printf(dev, "Width %d mm Height %d mm\n",
+ resbuf[4] | (resbuf[5] << 8),
+ resbuf[6] | (resbuf[7] << 8));
+ if (resbuf[8] & 1) {
+ device_printf(dev, "Visible\n");
+ }
+ if (resbuf[8] & 2) {
+ device_printf(dev, "Dock\n");
+ }
+ if (resbuf[8] & 4) {
+ device_printf(dev, "Lid\n");
+ }
+ device_printf(dev, "PanelPosition: %d\n",
+ (resbuf[8] >> 3) & 7);
+ device_printf(dev, "VertPosition: %d\n",
+ (resbuf[8] >> 6) & 3);
+ device_printf(dev, "HorizPosition: %d\n",
+ (resbuf[9]) & 3);
+ device_printf(dev, "Shape: %d\n",
+ (resbuf[9] >> 2) & 0xf);
+ device_printf(dev, "80: %02x, %02x, %02x\n",
+ resbuf[9], resbuf[10], resbuf[11]);
+ device_printf(dev, "96: %02x, %02x, %02x, %02x\n",
+ resbuf[12], resbuf[13],
+ resbuf[14], resbuf[15]);
+
+ if ((resbuf[0] & 0x7f) >= 2) {
+ device_printf(dev, "VOFF%d mm HOFF %dmm",
+ resbuf[16] | (resbuf[17] << 8),
+ resbuf[18] | (resbuf[19] << 8));
+ }
+ }
+ skip:
+ AcpiOsFree(buf.Pointer);
+ }
+ return (0);
+}
+
+static ACPI_STATUS
+acpi_uhub_find_rh(device_t dev, ACPI_HANDLE *ah)
+{
+ device_t grand;
+ ACPI_HANDLE gah;
+
+ *ah = NULL;
+ grand = device_get_parent(device_get_parent(dev));
+
+ if ((gah = acpi_get_handle(grand)) == NULL)
+ return (AE_ERROR);
+
+ return (AcpiWalkNamespace(ACPI_TYPE_DEVICE, gah, 1,
+ acpi_uhub_find_rh_cb, NULL, dev, ah));
+}
+
+static ACPI_STATUS
+acpi_usb_hub_port_probe_cb(ACPI_HANDLE ah, UINT32 lv, void *ctx, void **rv)
+{
+ ACPI_DEVICE_INFO *devinfo;
+ device_t dev = ctx;
+ struct acpi_uhub_softc *sc = device_get_softc(dev);
+ UINT32 ret;
+
+ ret = AcpiGetObjectInfo(ah, &devinfo);
+ if (ACPI_SUCCESS(ret)) {
+ if ((devinfo->Valid & ACPI_VALID_ADR) &&
+ (devinfo->Address > 0) &&
+ (devinfo->Address <= (uint64_t)sc->nports)) {
+ char buf[] = "portXXX";
+ struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev);
+ struct sysctl_oid *oid;
+ struct sysctl_oid_list *tree;
+
+ snprintf(buf, sizeof(buf), "port%ju",
+ (uintmax_t)devinfo->Address);
+ oid = SYSCTL_ADD_NODE(ctx,
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
+ OID_AUTO, buf, CTLFLAG_RD | CTLFLAG_MPSAFE,
+ NULL, "port nodes");
+ tree = SYSCTL_CHILDREN(oid);
+ sc->port[devinfo->Address - 1].handle = ah;
+ sc->port[devinfo->Address - 1].upc = 0xffffffff;
+ acpi_uhub_parse_upc(dev, devinfo->Address, ah, tree);
+ acpi_uhub_parse_pld(dev, devinfo->Address, ah, tree);
+ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), tree,
+ OID_AUTO, "info",
+ CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
+ &sc->port[devinfo->Address - 1], 0,
+ acpi_uhub_port_sysctl, "A", "Port information");
+ }
+ AcpiOsFree(devinfo);
+ }
+ return (AE_OK);
+}
+
+static ACPI_STATUS
+acpi_usb_hub_port_probe(device_t dev, ACPI_HANDLE ah)
+{
+ return (AcpiWalkNamespace(ACPI_TYPE_DEVICE,
+ ah, 1,
+ acpi_usb_hub_port_probe_cb,
+ NULL, dev, NULL));
+}
+
+static int
+acpi_uhub_root_probe(device_t dev)
+{
+ ACPI_STATUS status;
+ ACPI_HANDLE ah;
+
+ if (acpi_disabled("usb"))
+ return (ENXIO);
+
+ status = acpi_uhub_find_rh(dev, &ah);
+ if (ACPI_SUCCESS(status) && ah != NULL &&
+ uhub_probe(dev) <= 0) {
+ /* success prior than non-ACPI USB HUB */
+ return (BUS_PROBE_DEFAULT + 1);
+ }
+ return (ENXIO);
+}
+
+static int
+acpi_uhub_probe(device_t dev)
+{
+ ACPI_HANDLE ah;
+
+ if (acpi_disabled("usb"))
+ return (ENXIO);
+
+ ah = acpi_get_handle(dev);
+ if (ah == NULL)
+ return (ENXIO);
+
+ if (uhub_probe(dev) <= 0) {
+ /* success prior than non-ACPI USB HUB */
+ return (BUS_PROBE_DEFAULT + 1);
+ }
+ return (ENXIO);
+}
+static int
+acpi_uhub_attach_common(device_t dev)
+{
+ struct usb_hub *uh;
+ struct acpi_uhub_softc *sc = device_get_softc(dev);
+ ACPI_STATUS status;
+ int ret = ENXIO;
+
+ uh = sc->usc.sc_udev->hub;
+ sc->nports = uh->nports;
+ sc->port = malloc(sizeof(struct acpi_uhub_port) * uh->nports,
+ M_USBDEV, M_WAITOK | M_ZERO);
+ status = acpi_usb_hub_port_probe(dev, sc->ah);
+
+ if (ACPI_SUCCESS(status)){
+ ret = 0;
+ }
+
+ return (ret);
+}
+
+static int
+acpi_uhub_detach(device_t dev)
+{
+ struct acpi_uhub_softc *sc = device_get_softc(dev);
+
+ free(sc->port, M_USBDEV);
+
+ return (uhub_detach(dev));
+}
+
+static int
+acpi_uhub_root_attach(device_t dev)
+{
+ int ret;
+ struct acpi_uhub_softc *sc = device_get_softc(dev);
+
+ if (ACPI_FAILURE(acpi_uhub_find_rh(dev, &sc->ah)) ||
+ (sc->ah == NULL)) {
+ return (ENXIO);
+ }
+ if ((ret = uhub_attach(dev)) != 0) {
+ return (ret);
+ }
+
+ if ((ret = acpi_uhub_attach_common(dev)) != 0) {
+ acpi_uhub_detach(dev);
+ }
+ return ret;
+}
+
+static int
+acpi_uhub_attach(device_t dev)
+{
+ int ret;
+ struct acpi_uhub_softc *sc = device_get_softc(dev);
+
+ sc->ah = acpi_get_handle(dev);
+
+ if (sc->ah == NULL) {
+ return (ENXIO);
+ }
+ if ((ret = uhub_attach(dev)) != 0) {
+ return (ret);
+ }
+
+ if ((ret = acpi_uhub_attach_common(dev)) != 0) {
+ acpi_uhub_detach(dev);
+ }
+
+ return (ret);
+}
+
+static int
+acpi_uhub_read_ivar(device_t dev, device_t child, int idx, uintptr_t *res)
+{
+ struct hub_result hres;
+ struct acpi_uhub_softc *sc = device_get_softc(dev);
+ ACPI_HANDLE ah;
+
+ bus_topo_lock();
+ uhub_find_iface_index(sc->usc.sc_udev->hub, child, &hres);
+ bus_topo_unlock();
+
+ if ((idx == ACPI_IVAR_HANDLE) &&
+ (hres.portno > 0) &&
+ (hres.portno <= sc->nports) &&
+ (ah = sc->port[hres.portno - 1].handle)) {
+ *res = (uintptr_t)ah;
+ return (0);
+ }
+ return (ENXIO);
+}
+
+static int
+acpi_uhub_child_location(device_t parent, device_t child, struct sbuf *sb)
+{
+ ACPI_HANDLE ah;
+
+ uhub_child_location(parent, child, sb);
+
+ ah = acpi_get_handle(child);
+ if (ah != NULL)
+ sbuf_printf(sb, " handle=%s", acpi_name(ah));
+ return (0);
+}
+
+static int
+acpi_uhub_get_device_path(device_t bus, device_t child, const char *locator, struct sbuf *sb)
+{
+ if (strcmp(locator, BUS_LOCATOR_ACPI) == 0)
+ return (acpi_get_acpi_device_path(bus, child, locator, sb));
+
+ /* Otherwise call the parent class' method. */
+ return (uhub_get_device_path(bus, child, locator, sb));
+}
+
+static device_method_t acpi_uhub_methods[] = {
+ DEVMETHOD(device_probe, acpi_uhub_probe),
+ DEVMETHOD(device_attach, acpi_uhub_attach),
+ DEVMETHOD(device_detach, acpi_uhub_detach),
+ DEVMETHOD(bus_child_location, acpi_uhub_child_location),
+ DEVMETHOD(bus_get_device_path, acpi_uhub_get_device_path),
+ DEVMETHOD(bus_read_ivar, acpi_uhub_read_ivar),
+ DEVMETHOD_END
+
+};
+
+static device_method_t acpi_uhub_root_methods[] = {
+ DEVMETHOD(device_probe, acpi_uhub_root_probe),
+ DEVMETHOD(device_attach, acpi_uhub_root_attach),
+ DEVMETHOD(device_detach, acpi_uhub_detach),
+ DEVMETHOD(bus_read_ivar, acpi_uhub_read_ivar),
+ DEVMETHOD(bus_child_location, acpi_uhub_child_location),
+ DEVMETHOD(bus_get_device_path, acpi_uhub_get_device_path),
+ DEVMETHOD_END
+};
+
+extern driver_t uhub_driver;
+static kobj_class_t uhub_baseclasses[] = {&uhub_driver, NULL};
+
+static driver_t acpi_uhub_driver = {
+ .name = "uhub",
+ .methods = acpi_uhub_methods,
+ .size = sizeof(struct acpi_uhub_softc),
+ .baseclasses = uhub_baseclasses,
+};
+
+static driver_t acpi_uhub_root_driver = {
+ .name = "uhub",
+ .methods = acpi_uhub_root_methods,
+ .size = sizeof(struct acpi_uhub_softc),
+ .baseclasses = uhub_baseclasses,
+};
+
+DRIVER_MODULE(uacpi, uhub, acpi_uhub_driver, 0, 0);
+DRIVER_MODULE(uacpi, usbus, acpi_uhub_root_driver, 0, 0);
+
+MODULE_DEPEND(uacpi, acpi, 1, 1, 1);
+MODULE_DEPEND(uacpi, usb, 1, 1, 1);
+
+MODULE_VERSION(uacpi, 1);
diff --git a/sys/dev/usb/usb_hub_private.h b/sys/dev/usb/usb_hub_private.h
new file mode 100644
index 000000000000..5ca4d1ca0a2a
--- /dev/null
+++ b/sys/dev/usb/usb_hub_private.h
@@ -0,0 +1,85 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
+ * Copyright (c) 2008-2010 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * USB spec: http://www.usb.org/developers/docs/usbspec.zip
+ */
+
+#ifndef USB_HUB_PRIVATE_H_
+#define USB_HUB_PRIVATE_H_
+#define UHUB_INTR_INTERVAL 250 /* ms */
+
+enum {
+ UHUB_INTR_TRANSFER,
+#if USB_HAVE_TT_SUPPORT
+ UHUB_RESET_TT_TRANSFER,
+#endif
+ UHUB_N_TRANSFER,
+};
+
+struct uhub_current_state {
+ uint16_t port_change;
+ uint16_t port_status;
+};
+
+struct uhub_softc {
+ struct uhub_current_state sc_st; /* current state */
+#if (USB_HAVE_FIXED_PORT != 0)
+ struct usb_hub sc_hub;
+#endif
+ device_t sc_dev; /* base device */
+ struct mtx sc_mtx; /* our mutex */
+ struct usb_device *sc_udev; /* USB device */
+ struct usb_xfer *sc_xfer[UHUB_N_TRANSFER]; /* interrupt xfer */
+#if USB_HAVE_DISABLE_ENUM
+ int sc_disable_enumeration;
+ int sc_disable_port_power;
+#endif
+ uint8_t sc_usb_port_errors; /* error counter */
+#define UHUB_USB_PORT_ERRORS_MAX 4
+ uint8_t sc_flags;
+#define UHUB_FLAG_DID_EXPLORE 0x01
+};
+struct hub_result {
+ struct usb_device *udev;
+ uint8_t portno;
+ uint8_t iface_index;
+};
+
+void
+uhub_find_iface_index(struct usb_hub *hub, device_t child,
+ struct hub_result *res);
+
+device_probe_t uhub_probe;
+device_attach_t uhub_attach;
+device_detach_t uhub_detach;
+bus_child_location_t uhub_child_location;
+bus_get_device_path_t uhub_get_device_path;
+
+#endif
diff --git a/sys/dev/usb/usb_if.m b/sys/dev/usb/usb_if.m
new file mode 100644
index 000000000000..edacf6ec89cd
--- /dev/null
+++ b/sys/dev/usb/usb_if.m
@@ -0,0 +1,65 @@
+#-
+# Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer,
+# 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.
+#
+#
+
+# USB interface description
+#
+
+#include <sys/bus.h>
+
+INTERFACE usb;
+
+# The device received a control request
+#
+# The value pointed to by "pstate" can be updated to
+# "USB_HR_COMPLETE_OK" to indicate that the control
+# read transfer is complete, in case of short USB
+# control transfers.
+#
+# Return values:
+# 0: Success
+# ENOTTY: Transaction stalled
+# Else: Use builtin request handler
+#
+METHOD int handle_request {
+ device_t dev;
+ const void *req; /* pointer to the device request */
+ void **pptr; /* data pointer */
+ uint16_t *plen; /* maximum transfer length */
+ uint16_t offset; /* data offset */
+ uint8_t *pstate; /* set if transfer is complete, see USB_HR_XXX */
+};
+
+# Take controller from BIOS
+#
+# Return values:
+# 0: Success
+# Else: Failure
+#
+METHOD int take_controller {
+ device_t dev;
+};
diff --git a/sys/dev/usb/usb_ioctl.h b/sys/dev/usb/usb_ioctl.h
new file mode 100644
index 000000000000..85979b9cf778
--- /dev/null
+++ b/sys/dev/usb/usb_ioctl.h
@@ -0,0 +1,393 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_IOCTL_H_
+#define _USB_IOCTL_H_
+
+#ifndef USB_GLOBAL_INCLUDE_FILE
+#include <sys/ioccom.h>
+#include <sys/cdefs.h>
+
+/* Building "kdump" depends on these includes */
+
+#include <dev/usb/usb_endian.h>
+#include <dev/usb/usb.h>
+#endif
+
+#define USB_DEVICE_NAME "usbctl"
+#define USB_DEVICE_DIR "usb"
+#define USB_GENERIC_NAME "ugen"
+#define USB_TEMPLATE_SYSCTL "hw.usb.template" /* integer type */
+
+/* Definition of valid template sysctl values */
+
+enum {
+ USB_TEMP_MSC, /* USB Mass Storage */
+ USB_TEMP_CDCE, /* USB CDC Ethernet */
+ USB_TEMP_MTP, /* Message Transfer Protocol */
+ USB_TEMP_MODEM, /* USB CDC Modem */
+ USB_TEMP_AUDIO, /* USB Audio */
+ USB_TEMP_KBD, /* USB Keyboard */
+ USB_TEMP_MOUSE, /* USB Mouse */
+ USB_TEMP_PHONE, /* USB Phone */
+ USB_TEMP_SERIALNET, /* USB CDC Ethernet and Modem */
+ USB_TEMP_MIDI, /* USB MIDI */
+ USB_TEMP_MULTI, /* USB Ethernet, serial, and storage */
+ USB_TEMP_CDCEEM, /* USB Ethernet Emulation Model */
+ USB_TEMP_MAX,
+};
+
+struct usb_read_dir {
+ void *urd_data;
+ uint32_t urd_startentry;
+ uint32_t urd_maxlen;
+};
+
+struct usb_ctl_request {
+ void *ucr_data;
+ uint16_t ucr_flags;
+ uint16_t ucr_actlen; /* actual length transferred */
+ uint8_t ucr_addr; /* zero - currently not used */
+ struct usb_device_request ucr_request;
+};
+
+struct usb_alt_interface {
+ uint8_t uai_interface_index;
+ uint8_t uai_alt_index;
+};
+
+struct usb_gen_descriptor {
+ void *ugd_data;
+ uint16_t ugd_lang_id;
+ uint16_t ugd_maxlen;
+ uint16_t ugd_actlen;
+ uint16_t ugd_offset;
+ uint8_t ugd_config_index;
+ uint8_t ugd_string_index;
+ uint8_t ugd_iface_index;
+ uint8_t ugd_altif_index;
+ uint8_t ugd_endpt_index;
+ uint8_t ugd_report_type;
+ uint8_t reserved[8];
+};
+
+struct usb_device_info {
+ uint16_t udi_productNo;
+ uint16_t udi_vendorNo;
+ uint16_t udi_releaseNo;
+ uint16_t udi_power; /* power consumption in mA, 0 if
+ * selfpowered */
+ uint8_t udi_bus;
+ uint8_t udi_addr; /* device address */
+ uint8_t udi_index; /* device index */
+ uint8_t udi_class;
+ uint8_t udi_subclass;
+ uint8_t udi_protocol;
+ uint8_t udi_config_no; /* current config number */
+ uint8_t udi_config_index; /* current config index */
+ uint8_t udi_speed; /* see "USB_SPEED_XXX" */
+ uint8_t udi_mode; /* see "USB_MODE_XXX" */
+ uint8_t udi_nports;
+ uint8_t udi_hubaddr; /* parent HUB address */
+ uint8_t udi_hubindex; /* parent HUB device index */
+ uint8_t udi_hubport; /* parent HUB port */
+ uint8_t udi_power_mode; /* see "USB_POWER_MODE_XXX" */
+ uint8_t udi_suspended; /* set if device is suspended */
+ uint16_t udi_bustypeNo;
+ uint8_t udi_reserved[14]; /* leave space for the future */
+ char udi_product[128];
+ char udi_vendor[128];
+ char udi_serial[64];
+ char udi_release[8];
+};
+
+#define USB_DEVICE_PORT_PATH_MAX 32
+
+struct usb_device_port_path {
+ uint8_t udp_bus; /* which bus we are on */
+ uint8_t udp_index; /* which device index */
+ uint8_t udp_port_level; /* how many levels: 0, 1, 2 ... */
+ uint8_t udp_port_no[USB_DEVICE_PORT_PATH_MAX];
+};
+
+struct usb_device_stats {
+ uint32_t uds_requests_ok[4]; /* Indexed by transfer type UE_XXX */
+ uint32_t uds_requests_fail[4]; /* Indexed by transfer type UE_XXX */
+};
+
+struct usb_fs_start {
+ uint8_t ep_index;
+};
+
+struct usb_fs_stop {
+ uint8_t ep_index;
+};
+
+struct usb_fs_complete {
+ uint8_t ep_index;
+};
+
+/* This structure is used for all endpoint types */
+struct usb_fs_endpoint {
+ /*
+ * NOTE: isochronous USB transfer only use one buffer, but can have
+ * multiple frame lengths !
+ */
+ void **ppBuffer; /* pointer to userland buffers */
+ uint32_t *pLength; /* pointer to frame lengths, updated
+ * to actual length */
+ uint32_t nFrames; /* number of frames */
+ uint32_t aFrames; /* actual number of frames */
+ uint16_t flags;
+ /* a single short frame will terminate */
+#define USB_FS_FLAG_SINGLE_SHORT_OK 0x0001
+ /* multiple short frames are allowed */
+#define USB_FS_FLAG_MULTI_SHORT_OK 0x0002
+ /* all frame(s) transmitted are short terminated */
+#define USB_FS_FLAG_FORCE_SHORT 0x0004
+ /* will do a clear-stall before xfer */
+#define USB_FS_FLAG_CLEAR_STALL 0x0008
+ uint16_t timeout; /* in milliseconds */
+ /* isocronous completion time in milliseconds - used for echo cancel */
+ uint16_t isoc_time_complete;
+ /* timeout value for no timeout */
+#define USB_FS_TIMEOUT_NONE 0
+ int status; /* see USB_ERR_XXX */
+};
+
+struct usb_fs_init {
+ /* userland pointer to endpoints structure */
+ struct usb_fs_endpoint *pEndpoints;
+ /* maximum number of endpoints */
+ uint8_t ep_index_max;
+};
+
+struct usb_fs_uninit {
+ uint8_t dummy; /* zero */
+};
+
+struct usb_fs_open {
+#define USB_FS_MAX_BUFSIZE (1 << 25) /* 32 MBytes */
+ uint32_t max_bufsize;
+#define USB_FS_MAX_FRAMES (1U << 12)
+#define USB_FS_MAX_FRAMES_PRE_SCALE (1U << 31) /* for ISOCHRONOUS transfers */
+ uint32_t max_frames; /* read and write */
+ uint16_t max_packet_length; /* read only */
+ uint8_t dev_index; /* currently unused */
+ uint8_t ep_index;
+ uint8_t ep_no; /* bEndpointNumber */
+};
+
+struct usb_fs_open_stream {
+ struct usb_fs_open fs_open;
+ uint16_t stream_id; /* stream ID */
+};
+
+struct usb_fs_close {
+ uint8_t ep_index;
+};
+
+struct usb_fs_clear_stall_sync {
+ uint8_t ep_index;
+};
+
+struct usb_gen_quirk {
+ uint16_t index; /* Quirk Index */
+ uint16_t vid; /* Vendor ID */
+ uint16_t pid; /* Product ID */
+ uint16_t bcdDeviceLow; /* Low Device Revision */
+ uint16_t bcdDeviceHigh; /* High Device Revision */
+ uint16_t reserved[2];
+ /*
+ * String version of quirk including terminating zero. See
+ * UQ_XXX in "usb_quirk.h".
+ */
+ char quirkname[64 - 14];
+};
+
+/* USB controller */
+#define USB_REQUEST _IOWR('U', 1, struct usb_ctl_request)
+#define USB_SETDEBUG _IOW ('U', 2, int)
+#define USB_DISCOVER _IO ('U', 3)
+#define USB_DEVICEINFO _IOWR('U', 4, struct usb_device_info)
+#define USB_DEVICESTATS _IOR ('U', 5, struct usb_device_stats)
+#define USB_DEVICEENUMERATE _IOW ('U', 6, int)
+
+/* Generic HID device. Numbers 26 and 30-49 are occupied by hidraw. */
+#define USB_GET_REPORT_DESC _IOWR('U', 21, struct usb_gen_descriptor)
+#define USB_SET_IMMED _IOW ('U', 22, int)
+#define USB_GET_REPORT _IOWR('U', 23, struct usb_gen_descriptor)
+#define USB_SET_REPORT _IOW ('U', 24, struct usb_gen_descriptor)
+#define USB_GET_REPORT_ID _IOR ('U', 25, int)
+
+/* Generic USB device */
+#define USB_GET_CONFIG _IOR ('U', 100, int)
+#define USB_SET_CONFIG _IOW ('U', 101, int)
+#define USB_GET_ALTINTERFACE _IOWR('U', 102, struct usb_alt_interface)
+#define USB_SET_ALTINTERFACE _IOWR('U', 103, struct usb_alt_interface)
+#define USB_GET_DEVICE_DESC _IOR ('U', 105, struct usb_device_descriptor)
+#define USB_GET_CONFIG_DESC _IOR ('U', 106, struct usb_config_descriptor)
+#define USB_GET_RX_INTERFACE_DESC _IOR ('U', 107, struct usb_interface_descriptor)
+#define USB_GET_RX_ENDPOINT_DESC _IOR ('U', 108, struct usb_endpoint_descriptor)
+#define USB_GET_FULL_DESC _IOWR('U', 109, struct usb_gen_descriptor)
+#define USB_GET_STRING_DESC _IOWR('U', 110, struct usb_gen_descriptor)
+#define USB_DO_REQUEST _IOWR('U', 111, struct usb_ctl_request)
+#define USB_GET_DEVICEINFO _IOR ('U', 112, struct usb_device_info)
+#define USB_SET_RX_SHORT_XFER _IOW ('U', 113, int)
+#define USB_SET_RX_TIMEOUT _IOW ('U', 114, int)
+#define USB_GET_RX_FRAME_SIZE _IOR ('U', 115, int)
+#define USB_GET_RX_BUFFER_SIZE _IOR ('U', 117, int)
+#define USB_SET_RX_BUFFER_SIZE _IOW ('U', 118, int)
+#define USB_SET_RX_STALL_FLAG _IOW ('U', 119, int)
+#define USB_SET_TX_STALL_FLAG _IOW ('U', 120, int)
+#define USB_GET_IFACE_DRIVER _IOWR('U', 121, struct usb_gen_descriptor)
+#define USB_CLAIM_INTERFACE _IOW ('U', 122, int)
+#define USB_RELEASE_INTERFACE _IOW ('U', 123, int)
+#define USB_IFACE_DRIVER_ACTIVE _IOW ('U', 124, int)
+#define USB_IFACE_DRIVER_DETACH _IOW ('U', 125, int)
+#define USB_GET_PLUGTIME _IOR ('U', 126, uint32_t)
+#define USB_READ_DIR _IOW ('U', 127, struct usb_read_dir)
+/* 128 - 133 unused */
+#define USB_GET_DEV_PORT_PATH _IOR ('U', 134, struct usb_device_port_path)
+#define USB_GET_POWER_USAGE _IOR ('U', 135, int)
+#define USB_SET_TX_FORCE_SHORT _IOW ('U', 136, int)
+#define USB_SET_TX_TIMEOUT _IOW ('U', 137, int)
+#define USB_GET_TX_FRAME_SIZE _IOR ('U', 138, int)
+#define USB_GET_TX_BUFFER_SIZE _IOR ('U', 139, int)
+#define USB_SET_TX_BUFFER_SIZE _IOW ('U', 140, int)
+#define USB_GET_TX_INTERFACE_DESC _IOR ('U', 141, struct usb_interface_descriptor)
+#define USB_GET_TX_ENDPOINT_DESC _IOR ('U', 142, struct usb_endpoint_descriptor)
+#define USB_SET_PORT_ENABLE _IOW ('U', 143, int)
+#define USB_SET_PORT_DISABLE _IOW ('U', 144, int)
+#define USB_SET_POWER_MODE _IOW ('U', 145, int)
+#define USB_GET_POWER_MODE _IOR ('U', 146, int)
+#define USB_SET_TEMPLATE _IOW ('U', 147, int)
+#define USB_GET_TEMPLATE _IOR ('U', 148, int)
+
+/* Modem device */
+#define USB_GET_CM_OVER_DATA _IOR ('U', 180, int)
+#define USB_SET_CM_OVER_DATA _IOW ('U', 181, int)
+
+/* GPIO control */
+#define USB_GET_GPIO _IOR ('U', 182, int)
+#define USB_SET_GPIO _IOW ('U', 183, int)
+
+/* USB file system interface */
+#define USB_FS_START _IOW ('U', 192, struct usb_fs_start)
+#define USB_FS_STOP _IOW ('U', 193, struct usb_fs_stop)
+#define USB_FS_COMPLETE _IOR ('U', 194, struct usb_fs_complete)
+#define USB_FS_INIT _IOW ('U', 195, struct usb_fs_init)
+#define USB_FS_UNINIT _IOW ('U', 196, struct usb_fs_uninit)
+#define USB_FS_OPEN _IOWR('U', 197, struct usb_fs_open)
+#define USB_FS_CLOSE _IOW ('U', 198, struct usb_fs_close)
+#define USB_FS_CLEAR_STALL_SYNC _IOW ('U', 199, struct usb_fs_clear_stall_sync)
+#define USB_FS_OPEN_STREAM _IOWR('U', 200, struct usb_fs_open_stream)
+
+/* USB quirk system interface */
+#define USB_DEV_QUIRK_GET _IOWR('Q', 0, struct usb_gen_quirk)
+#define USB_QUIRK_NAME_GET _IOWR('Q', 1, struct usb_gen_quirk)
+#define USB_DEV_QUIRK_ADD _IOW ('Q', 2, struct usb_gen_quirk)
+#define USB_DEV_QUIRK_REMOVE _IOW ('Q', 3, struct usb_gen_quirk)
+
+#ifdef _KERNEL
+#ifdef COMPAT_FREEBSD32
+
+struct usb_read_dir32 {
+ uint32_t urd_data;
+ uint32_t urd_startentry;
+ uint32_t urd_maxlen;
+};
+#define USB_READ_DIR32 \
+ _IOC_NEWTYPE(USB_READ_DIR, struct usb_read_dir32)
+
+struct usb_ctl_request32 {
+ uint32_t ucr_data;
+ uint16_t ucr_flags;
+ uint16_t ucr_actlen;
+ uint8_t ucr_addr;
+ struct usb_device_request ucr_request;
+};
+#define USB_REQUEST32 _IOC_NEWTYPE(USB_REQUEST, struct usb_ctl_request32)
+#define USB_DO_REQUEST32 _IOC_NEWTYPE(USB_DO_REQUEST, struct usb_ctl_request32)
+
+struct usb_gen_descriptor32 {
+ uint32_t ugd_data; /* void * */
+ uint16_t ugd_lang_id;
+ uint16_t ugd_maxlen;
+ uint16_t ugd_actlen;
+ uint16_t ugd_offset;
+ uint8_t ugd_config_index;
+ uint8_t ugd_string_index;
+ uint8_t ugd_iface_index;
+ uint8_t ugd_altif_index;
+ uint8_t ugd_endpt_index;
+ uint8_t ugd_report_type;
+ uint8_t reserved[8];
+};
+
+#define USB_GET_REPORT_DESC32 \
+ _IOC_NEWTYPE(USB_GET_REPORT_DESC, struct usb_gen_descriptor32)
+#define USB_GET_REPORT32 \
+ _IOC_NEWTYPE(USB_GET_REPORT, struct usb_gen_descriptor32)
+#define USB_SET_REPORT32 \
+ _IOC_NEWTYPE(USB_SET_REPORT, struct usb_gen_descriptor32)
+#define USB_GET_FULL_DESC32 \
+ _IOC_NEWTYPE(USB_GET_FULL_DESC, struct usb_gen_descriptor32)
+#define USB_GET_STRING_DESC32 \
+ _IOC_NEWTYPE(USB_GET_STRING_DESC, struct usb_gen_descriptor32)
+#define USB_GET_IFACE_DRIVER32 \
+ _IOC_NEWTYPE(USB_GET_IFACE_DRIVER, struct usb_gen_descriptor32)
+
+void usb_gen_descriptor_from32(struct usb_gen_descriptor *ugd,
+ const struct usb_gen_descriptor32 *ugd32);
+void update_usb_gen_descriptor32(struct usb_gen_descriptor32 *ugd32,
+ struct usb_gen_descriptor *ugd);
+
+struct usb_fs_endpoint32 {
+ uint32_t ppBuffer; /* void ** */
+ uint32_t pLength; /* uint32_t * */
+ uint32_t nFrames;
+ uint32_t aFrames;
+ uint16_t flags;
+ uint16_t timeout;
+ uint16_t isoc_time_complete;
+ int status;
+};
+
+struct usb_fs_init32 {
+ uint32_t pEndpoints; /* struct usb_fs_endpoint32 * */
+ uint8_t ep_index_max;
+};
+
+#define USB_FS_INIT32 _IOC_NEWTYPE(USB_FS_INIT, struct usb_fs_init32)
+
+#endif /* COMPAT_FREEBSD32 */
+#endif /* _KERNEL */
+
+#endif /* _USB_IOCTL_H_ */
diff --git a/sys/dev/usb/usb_lookup.c b/sys/dev/usb/usb_lookup.c
new file mode 100644
index 000000000000..2db1abe0eb52
--- /dev/null
+++ b/sys/dev/usb/usb_lookup.c
@@ -0,0 +1,152 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/limits.h>
+#include <sys/endian.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+/*------------------------------------------------------------------------*
+ * usbd_lookup_id_by_info
+ *
+ * This functions takes an array of "struct usb_device_id" and tries
+ * to match the entries with the information in "struct usbd_lookup_info".
+ *
+ * NOTE: The "sizeof_id" parameter must be a multiple of the
+ * usb_device_id structure size. Else the behaviour of this function
+ * is undefined.
+ *
+ * Return values:
+ * NULL: No match found.
+ * Else: Pointer to matching entry.
+ *------------------------------------------------------------------------*/
+const struct usb_device_id *
+usbd_lookup_id_by_info(const struct usb_device_id *id, usb_size_t sizeof_id,
+ const struct usbd_lookup_info *info)
+{
+ const struct usb_device_id *id_end;
+
+ if (id == NULL) {
+ goto done;
+ }
+ id_end = (const void *)(((const uint8_t *)id) + sizeof_id);
+
+ /*
+ * Keep on matching array entries until we find a match or
+ * until we reach the end of the matching array:
+ */
+ for (; id != id_end; id++) {
+ if ((id->match_flag_vendor) &&
+ (id->idVendor != info->idVendor)) {
+ continue;
+ }
+ if ((id->match_flag_product) &&
+ (id->idProduct != info->idProduct)) {
+ continue;
+ }
+ if ((id->match_flag_dev_lo) &&
+ (id->bcdDevice_lo > info->bcdDevice)) {
+ continue;
+ }
+ if ((id->match_flag_dev_hi) &&
+ (id->bcdDevice_hi < info->bcdDevice)) {
+ continue;
+ }
+ if ((id->match_flag_dev_class) &&
+ (id->bDeviceClass != info->bDeviceClass)) {
+ continue;
+ }
+ if ((id->match_flag_dev_subclass) &&
+ (id->bDeviceSubClass != info->bDeviceSubClass)) {
+ continue;
+ }
+ if ((id->match_flag_dev_protocol) &&
+ (id->bDeviceProtocol != info->bDeviceProtocol)) {
+ continue;
+ }
+ if ((id->match_flag_int_class) &&
+ (id->bInterfaceClass != info->bInterfaceClass)) {
+ continue;
+ }
+ if ((id->match_flag_int_subclass) &&
+ (id->bInterfaceSubClass != info->bInterfaceSubClass)) {
+ continue;
+ }
+ if ((id->match_flag_int_protocol) &&
+ (id->bInterfaceProtocol != info->bInterfaceProtocol)) {
+ continue;
+ }
+ /* We found a match! */
+ return (id);
+ }
+
+done:
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_lookup_id_by_uaa - factored out code
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+int
+usbd_lookup_id_by_uaa(const struct usb_device_id *id, usb_size_t sizeof_id,
+ struct usb_attach_arg *uaa)
+{
+ id = usbd_lookup_id_by_info(id, sizeof_id, &uaa->info);
+ if (id) {
+ /* copy driver info */
+ uaa->driver_info = id->driver_info;
+ return (0);
+ }
+ return (ENXIO);
+}
diff --git a/sys/dev/usb/usb_mbuf.c b/sys/dev/usb/usb_mbuf.c
new file mode 100644
index 000000000000..6e94bcc65aed
--- /dev/null
+++ b/sys/dev/usb/usb_mbuf.c
@@ -0,0 +1,96 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_dev.h>
+#include <dev/usb/usb_mbuf.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+/*------------------------------------------------------------------------*
+ * usb_alloc_mbufs - allocate mbufs to an usbd interface queue
+ *
+ * Returns:
+ * A pointer that should be passed to "free()" when the buffer(s)
+ * should be released.
+ *------------------------------------------------------------------------*/
+void *
+usb_alloc_mbufs(struct malloc_type *type, struct usb_ifqueue *ifq,
+ usb_size_t block_size, uint16_t nblocks)
+{
+ struct usb_mbuf *m_ptr;
+ uint8_t *data_ptr;
+ void *free_ptr = NULL;
+ usb_size_t alloc_size;
+
+ /* align data */
+ block_size += ((-block_size) & (USB_HOST_ALIGN - 1));
+
+ if (nblocks && block_size) {
+ alloc_size = (block_size + sizeof(struct usb_mbuf)) * nblocks;
+
+ free_ptr = malloc(alloc_size, type, M_WAITOK | M_ZERO);
+ m_ptr = free_ptr;
+ data_ptr = (void *)(m_ptr + nblocks);
+
+ while (nblocks--) {
+ m_ptr->cur_data_ptr =
+ m_ptr->min_data_ptr = data_ptr;
+
+ m_ptr->cur_data_len =
+ m_ptr->max_data_len = block_size;
+
+ USB_IF_ENQUEUE(ifq, m_ptr);
+
+ m_ptr++;
+ data_ptr += block_size;
+ }
+ }
+ return (free_ptr);
+}
diff --git a/sys/dev/usb/usb_mbuf.h b/sys/dev/usb/usb_mbuf.h
new file mode 100644
index 000000000000..16fbe87ce50b
--- /dev/null
+++ b/sys/dev/usb/usb_mbuf.h
@@ -0,0 +1,91 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_MBUF_H_
+#define _USB_MBUF_H_
+
+/*
+ * The following structure defines a minimum re-implementation of the
+ * mbuf system in the kernel.
+ */
+struct usb_mbuf {
+ uint8_t *cur_data_ptr;
+ uint8_t *min_data_ptr;
+ struct usb_mbuf *usb_nextpkt;
+ struct usb_mbuf *usb_next;
+
+ usb_size_t cur_data_len;
+ usb_size_t max_data_len;
+ uint8_t last_packet:1;
+ uint8_t unused:7;
+};
+
+#define USB_IF_ENQUEUE(ifq, m) do { \
+ (m)->usb_nextpkt = NULL; \
+ if ((ifq)->ifq_tail == NULL) \
+ (ifq)->ifq_head = (m); \
+ else \
+ (ifq)->ifq_tail->usb_nextpkt = (m); \
+ (ifq)->ifq_tail = (m); \
+ (ifq)->ifq_len++; \
+ } while (0)
+
+#define USB_IF_DEQUEUE(ifq, m) do { \
+ (m) = (ifq)->ifq_head; \
+ if (m) { \
+ if (((ifq)->ifq_head = (m)->usb_nextpkt) == NULL) { \
+ (ifq)->ifq_tail = NULL; \
+ } \
+ (m)->usb_nextpkt = NULL; \
+ (ifq)->ifq_len--; \
+ } \
+ } while (0)
+
+#define USB_IF_PREPEND(ifq, m) do { \
+ (m)->usb_nextpkt = (ifq)->ifq_head; \
+ if ((ifq)->ifq_tail == NULL) { \
+ (ifq)->ifq_tail = (m); \
+ } \
+ (ifq)->ifq_head = (m); \
+ (ifq)->ifq_len++; \
+ } while (0)
+
+#define USB_IF_QFULL(ifq) ((ifq)->ifq_len >= (ifq)->ifq_maxlen)
+#define USB_IF_QLEN(ifq) ((ifq)->ifq_len)
+#define USB_IF_POLL(ifq, m) ((m) = (ifq)->ifq_head)
+
+#define USB_MBUF_RESET(m) do { \
+ (m)->cur_data_ptr = (m)->min_data_ptr; \
+ (m)->cur_data_len = (m)->max_data_len; \
+ (m)->last_packet = 0; \
+ } while (0)
+
+/* prototypes */
+void *usb_alloc_mbufs(struct malloc_type *type, struct usb_ifqueue *ifq,
+ usb_size_t block_size, uint16_t nblocks);
+
+#endif /* _USB_MBUF_H_ */
diff --git a/sys/dev/usb/usb_msctest.c b/sys/dev/usb/usb_msctest.c
new file mode 100644
index 000000000000..7b31d9dadfab
--- /dev/null
+++ b/sys/dev/usb/usb_msctest.c
@@ -0,0 +1,1150 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008-2022 Hans Petter Selasky.
+ * Copyright (c) 2021-2022 Idwer Vollering.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * The following file contains code that will detect USB autoinstall
+ * disks.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+
+#define USB_DEBUG_VAR usb_debug
+
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_msctest.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_request.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/quirk/usb_quirk.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+enum {
+ ST_COMMAND,
+ ST_DATA_RD,
+ ST_DATA_RD_CS,
+ ST_DATA_WR,
+ ST_DATA_WR_CS,
+ ST_STATUS,
+ ST_MAX,
+};
+
+enum {
+ DIR_IN,
+ DIR_OUT,
+ DIR_NONE,
+};
+
+#define SCSI_MAX_LEN MAX(SCSI_FIXED_BLOCK_SIZE, USB_MSCTEST_BULK_SIZE)
+#define SCSI_INQ_LEN 0x24
+#define SCSI_SENSE_LEN 0xFF
+#define SCSI_FIXED_BLOCK_SIZE 512 /* bytes */
+
+static uint8_t scsi_test_unit_ready[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+static uint8_t scsi_inquiry[] = { 0x12, 0x00, 0x00, 0x00, SCSI_INQ_LEN, 0x00 };
+static uint8_t scsi_rezero_init[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
+static uint8_t scsi_start_unit[] = { 0x1b, 0x00, 0x00, 0x00, 0x01, 0x00 };
+static uint8_t scsi_stop_unit[] = { 0x1b, 0x00, 0x00, 0x00, 0x02, 0x00 };
+static uint8_t scsi_ztestor_eject[] = { 0x85, 0x01, 0x01, 0x01, 0x18, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x00, 0x00 };
+static uint8_t scsi_cmotech_eject[] = { 0xff, 0x52, 0x44, 0x45, 0x56, 0x43,
+ 0x48, 0x47 };
+static uint8_t scsi_huawei_eject[] = { 0x11, 0x06, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00 };
+static uint8_t scsi_huawei_eject2[] = { 0x11, 0x06, 0x20, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00 };
+static uint8_t scsi_huawei_eject3[] = { 0x11, 0x06, 0x20, 0x00, 0x00, 0x01,
+ 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00 };
+static uint8_t scsi_huawei_eject4[] = { 0x11, 0x06, 0x30, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00 };
+static uint8_t scsi_tct_eject[] = { 0x06, 0xf5, 0x04, 0x02, 0x52, 0x70 };
+static uint8_t scsi_sync_cache[] = { 0x35, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00 };
+static uint8_t scsi_request_sense[] = { 0x03, 0x00, 0x00, 0x00, 0x12, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+static uint8_t scsi_read_capacity[] = { 0x25, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00 };
+static uint8_t scsi_prevent_removal[] = { 0x1e, 0, 0, 0, 1, 0 };
+static uint8_t scsi_allow_removal[] = { 0x1e, 0, 0, 0, 0, 0 };
+
+#ifndef USB_MSCTEST_BULK_SIZE
+#define USB_MSCTEST_BULK_SIZE 64 /* dummy */
+#endif
+
+#define ERR_CSW_FAILED -1
+
+/* Command Block Wrapper */
+struct bbb_cbw {
+ uDWord dCBWSignature;
+#define CBWSIGNATURE 0x43425355
+ uDWord dCBWTag;
+ uDWord dCBWDataTransferLength;
+ uByte bCBWFlags;
+#define CBWFLAGS_OUT 0x00
+#define CBWFLAGS_IN 0x80
+ uByte bCBWLUN;
+ uByte bCDBLength;
+#define CBWCDBLENGTH 16
+ uByte CBWCDB[CBWCDBLENGTH];
+} __packed;
+
+/* Command Status Wrapper */
+struct bbb_csw {
+ uDWord dCSWSignature;
+#define CSWSIGNATURE 0x53425355
+ uDWord dCSWTag;
+ uDWord dCSWDataResidue;
+ uByte bCSWStatus;
+#define CSWSTATUS_GOOD 0x0
+#define CSWSTATUS_FAILED 0x1
+#define CSWSTATUS_PHASE 0x2
+} __packed;
+
+struct bbb_transfer {
+ struct mtx mtx;
+ struct cv cv;
+ struct bbb_cbw *cbw;
+ struct bbb_csw *csw;
+
+ struct usb_xfer *xfer[ST_MAX];
+
+ uint8_t *data_ptr;
+
+ usb_size_t data_len; /* bytes */
+ usb_size_t data_rem; /* bytes */
+ usb_timeout_t data_timeout; /* ms */
+ usb_frlength_t actlen; /* bytes */
+ usb_frlength_t buffer_size; /* bytes */
+
+ uint8_t cmd_len; /* bytes */
+ uint8_t dir;
+ uint8_t lun;
+ uint8_t state;
+ uint8_t status_try;
+ int error;
+
+ uint8_t *buffer;
+};
+
+static usb_callback_t bbb_command_callback;
+static usb_callback_t bbb_data_read_callback;
+static usb_callback_t bbb_data_rd_cs_callback;
+static usb_callback_t bbb_data_write_callback;
+static usb_callback_t bbb_data_wr_cs_callback;
+static usb_callback_t bbb_status_callback;
+static usb_callback_t bbb_raw_write_callback;
+
+static void bbb_done(struct bbb_transfer *, int);
+static void bbb_transfer_start(struct bbb_transfer *, uint8_t);
+static void bbb_data_clear_stall_callback(struct usb_xfer *, uint8_t,
+ uint8_t);
+static int bbb_command_start(struct bbb_transfer *, uint8_t, uint8_t,
+ void *, size_t, void *, size_t, usb_timeout_t);
+static struct bbb_transfer *bbb_attach(struct usb_device *, uint8_t, uint8_t);
+static void bbb_detach(struct bbb_transfer *);
+
+static const struct usb_config bbb_config[ST_MAX] = {
+ [ST_COMMAND] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = sizeof(struct bbb_cbw),
+ .callback = &bbb_command_callback,
+ .timeout = 4 * USB_MS_HZ, /* 4 seconds */
+ },
+
+ [ST_DATA_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = SCSI_MAX_LEN,
+ .flags = {.proxy_buffer = 1,.short_xfer_ok = 1,},
+ .callback = &bbb_data_read_callback,
+ .timeout = 4 * USB_MS_HZ, /* 4 seconds */
+ },
+
+ [ST_DATA_RD_CS] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &bbb_data_rd_cs_callback,
+ .timeout = 1 * USB_MS_HZ, /* 1 second */
+ },
+
+ [ST_DATA_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = SCSI_MAX_LEN,
+ .flags = {.ext_buffer = 1,.proxy_buffer = 1,},
+ .callback = &bbb_data_write_callback,
+ .timeout = 4 * USB_MS_HZ, /* 4 seconds */
+ },
+
+ [ST_DATA_WR_CS] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &bbb_data_wr_cs_callback,
+ .timeout = 1 * USB_MS_HZ, /* 1 second */
+ },
+
+ [ST_STATUS] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = sizeof(struct bbb_csw),
+ .flags = {.short_xfer_ok = 1,},
+ .callback = &bbb_status_callback,
+ .timeout = 1 * USB_MS_HZ, /* 1 second */
+ },
+};
+
+static const struct usb_config bbb_raw_config[1] = {
+ [0] = {
+ .type = UE_BULK_INTR,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = SCSI_MAX_LEN,
+ .flags = {.ext_buffer = 1,.proxy_buffer = 1,},
+ .callback = &bbb_raw_write_callback,
+ .timeout = 1 * USB_MS_HZ, /* 1 second */
+ },
+};
+
+static void
+bbb_done(struct bbb_transfer *sc, int error)
+{
+ sc->error = error;
+ sc->state = ST_COMMAND;
+ sc->status_try = 1;
+ cv_signal(&sc->cv);
+}
+
+static void
+bbb_transfer_start(struct bbb_transfer *sc, uint8_t xfer_index)
+{
+ sc->state = xfer_index;
+ usbd_transfer_start(sc->xfer[xfer_index]);
+}
+
+static void
+bbb_data_clear_stall_callback(struct usb_xfer *xfer,
+ uint8_t next_xfer, uint8_t stall_xfer)
+{
+ struct bbb_transfer *sc = usbd_xfer_softc(xfer);
+
+ if (usbd_clear_stall_callback(xfer, sc->xfer[stall_xfer])) {
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ case USB_ST_TRANSFERRED:
+ bbb_transfer_start(sc, next_xfer);
+ break;
+ default:
+ bbb_done(sc, USB_ERR_STALLED);
+ break;
+ }
+ }
+}
+
+static void
+bbb_command_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct bbb_transfer *sc = usbd_xfer_softc(xfer);
+ uint32_t tag;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ bbb_transfer_start
+ (sc, ((sc->dir == DIR_IN) ? ST_DATA_RD :
+ (sc->dir == DIR_OUT) ? ST_DATA_WR :
+ ST_STATUS));
+ break;
+
+ case USB_ST_SETUP:
+ sc->status_try = 0;
+ tag = UGETDW(sc->cbw->dCBWTag) + 1;
+ USETDW(sc->cbw->dCBWSignature, CBWSIGNATURE);
+ USETDW(sc->cbw->dCBWTag, tag);
+ USETDW(sc->cbw->dCBWDataTransferLength, (uint32_t)sc->data_len);
+ sc->cbw->bCBWFlags = ((sc->dir == DIR_IN) ? CBWFLAGS_IN : CBWFLAGS_OUT);
+ sc->cbw->bCBWLUN = sc->lun;
+ sc->cbw->bCDBLength = sc->cmd_len;
+ if (sc->cbw->bCDBLength > sizeof(sc->cbw->CBWCDB)) {
+ sc->cbw->bCDBLength = sizeof(sc->cbw->CBWCDB);
+ DPRINTFN(0, "Truncating long command\n");
+ }
+ usbd_xfer_set_frame_len(xfer, 0,
+ sizeof(struct bbb_cbw));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ bbb_done(sc, error);
+ break;
+ }
+}
+
+static void
+bbb_data_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct bbb_transfer *sc = usbd_xfer_softc(xfer);
+ usb_frlength_t max_bulk = usbd_xfer_max_len(xfer);
+ int actlen, sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ sc->data_rem -= actlen;
+ sc->data_ptr += actlen;
+ sc->actlen += actlen;
+
+ if (actlen < sumlen) {
+ /* short transfer */
+ sc->data_rem = 0;
+ }
+ case USB_ST_SETUP:
+ DPRINTF("max_bulk=%d, data_rem=%d\n",
+ max_bulk, sc->data_rem);
+
+ if (sc->data_rem == 0) {
+ bbb_transfer_start(sc, ST_STATUS);
+ break;
+ }
+ if (max_bulk > sc->data_rem) {
+ max_bulk = sc->data_rem;
+ }
+ usbd_xfer_set_timeout(xfer, sc->data_timeout);
+ usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk);
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error == USB_ERR_CANCELLED) {
+ bbb_done(sc, error);
+ } else {
+ bbb_transfer_start(sc, ST_DATA_RD_CS);
+ }
+ break;
+ }
+}
+
+static void
+bbb_data_rd_cs_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ bbb_data_clear_stall_callback(xfer, ST_STATUS,
+ ST_DATA_RD);
+}
+
+static void
+bbb_data_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct bbb_transfer *sc = usbd_xfer_softc(xfer);
+ usb_frlength_t max_bulk = usbd_xfer_max_len(xfer);
+ int actlen, sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ sc->data_rem -= actlen;
+ sc->data_ptr += actlen;
+ sc->actlen += actlen;
+
+ if (actlen < sumlen) {
+ /* short transfer */
+ sc->data_rem = 0;
+ }
+ case USB_ST_SETUP:
+ DPRINTF("max_bulk=%d, data_rem=%d\n",
+ max_bulk, sc->data_rem);
+
+ if (sc->data_rem == 0) {
+ bbb_transfer_start(sc, ST_STATUS);
+ break;
+ }
+ if (max_bulk > sc->data_rem) {
+ max_bulk = sc->data_rem;
+ }
+ usbd_xfer_set_timeout(xfer, sc->data_timeout);
+ usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk);
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error == USB_ERR_CANCELLED) {
+ bbb_done(sc, error);
+ } else {
+ bbb_transfer_start(sc, ST_DATA_WR_CS);
+ }
+ break;
+ }
+}
+
+static void
+bbb_data_wr_cs_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ bbb_data_clear_stall_callback(xfer, ST_STATUS,
+ ST_DATA_WR);
+}
+
+static void
+bbb_status_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct bbb_transfer *sc = usbd_xfer_softc(xfer);
+ int actlen;
+ int sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ /* very simple status check */
+
+ if (actlen < (int)sizeof(struct bbb_csw)) {
+ bbb_done(sc, USB_ERR_SHORT_XFER);
+ } else if (sc->csw->bCSWStatus == CSWSTATUS_GOOD) {
+ bbb_done(sc, 0); /* success */
+ } else {
+ bbb_done(sc, ERR_CSW_FAILED); /* error */
+ }
+ break;
+
+ case USB_ST_SETUP:
+ usbd_xfer_set_frame_len(xfer, 0,
+ sizeof(struct bbb_csw));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default:
+ DPRINTF("Failed to read CSW: %s, try %d\n",
+ usbd_errstr(error), sc->status_try);
+
+ if (error == USB_ERR_CANCELLED || sc->status_try) {
+ bbb_done(sc, error);
+ } else {
+ sc->status_try = 1;
+ bbb_transfer_start(sc, ST_DATA_RD_CS);
+ }
+ break;
+ }
+}
+
+static void
+bbb_raw_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct bbb_transfer *sc = usbd_xfer_softc(xfer);
+ usb_frlength_t max_bulk = usbd_xfer_max_len(xfer);
+ int actlen, sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ sc->data_rem -= actlen;
+ sc->data_ptr += actlen;
+ sc->actlen += actlen;
+
+ if (actlen < sumlen) {
+ /* short transfer */
+ sc->data_rem = 0;
+ }
+ case USB_ST_SETUP:
+ DPRINTF("max_bulk=%d, data_rem=%d\n",
+ max_bulk, sc->data_rem);
+
+ if (sc->data_rem == 0) {
+ bbb_done(sc, 0);
+ break;
+ }
+ if (max_bulk > sc->data_rem) {
+ max_bulk = sc->data_rem;
+ }
+ usbd_xfer_set_timeout(xfer, sc->data_timeout);
+ usbd_xfer_set_frame_data(xfer, 0, sc->data_ptr, max_bulk);
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ bbb_done(sc, error);
+ break;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * bbb_command_start - execute a SCSI command synchronously
+ *
+ * Return values
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static int
+bbb_command_start(struct bbb_transfer *sc, uint8_t dir, uint8_t lun,
+ void *data_ptr, size_t data_len, void *cmd_ptr, size_t cmd_len,
+ usb_timeout_t data_timeout)
+{
+ sc->lun = lun;
+ sc->dir = data_len ? dir : DIR_NONE;
+ sc->data_ptr = data_ptr;
+ sc->data_len = data_len;
+ sc->data_rem = data_len;
+ sc->data_timeout = (data_timeout + USB_MS_HZ);
+ sc->actlen = 0;
+ sc->error = 0;
+ sc->cmd_len = cmd_len;
+ memset(&sc->cbw->CBWCDB, 0, sizeof(sc->cbw->CBWCDB));
+ memcpy(&sc->cbw->CBWCDB, cmd_ptr, cmd_len);
+ DPRINTFN(1, "SCSI cmd = %*D\n", (int)cmd_len, (char *)sc->cbw->CBWCDB, ":");
+
+ USB_MTX_LOCK(&sc->mtx);
+ usbd_transfer_start(sc->xfer[sc->state]);
+
+ while (usbd_transfer_pending(sc->xfer[sc->state])) {
+ cv_wait(&sc->cv, &sc->mtx);
+ }
+ USB_MTX_UNLOCK(&sc->mtx);
+ return (sc->error);
+}
+
+/*------------------------------------------------------------------------*
+ * bbb_raw_write - write a raw BULK message synchronously
+ *
+ * Return values
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static int
+bbb_raw_write(struct bbb_transfer *sc, const void *data_ptr, size_t data_len,
+ usb_timeout_t data_timeout)
+{
+ sc->data_ptr = __DECONST(void *, data_ptr);
+ sc->data_len = data_len;
+ sc->data_rem = data_len;
+ sc->data_timeout = (data_timeout + USB_MS_HZ);
+ sc->actlen = 0;
+ sc->error = 0;
+
+ DPRINTFN(1, "BULK DATA = %*D\n", (int)data_len,
+ (const char *)data_ptr, ":");
+
+ USB_MTX_LOCK(&sc->mtx);
+ usbd_transfer_start(sc->xfer[0]);
+ while (usbd_transfer_pending(sc->xfer[0]))
+ cv_wait(&sc->cv, &sc->mtx);
+ USB_MTX_UNLOCK(&sc->mtx);
+ return (sc->error);
+}
+
+static struct bbb_transfer *
+bbb_attach(struct usb_device *udev, uint8_t iface_index,
+ uint8_t bInterfaceClass)
+{
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *id;
+ const struct usb_config *pconfig;
+ struct bbb_transfer *sc;
+ usb_error_t err;
+ int nconfig;
+
+#if USB_HAVE_MSCTEST_DETACH
+ uint8_t do_unlock;
+
+ /* Prevent re-enumeration */
+ do_unlock = usbd_enum_lock(udev);
+
+ /*
+ * Make sure any driver which is hooked up to this interface,
+ * like umass is gone:
+ */
+ usb_detach_device(udev, iface_index, 0);
+
+ if (do_unlock)
+ usbd_enum_unlock(udev);
+#endif
+
+ iface = usbd_get_iface(udev, iface_index);
+ if (iface == NULL)
+ return (NULL);
+
+ id = iface->idesc;
+ if (id == NULL || id->bInterfaceClass != bInterfaceClass)
+ return (NULL);
+
+ switch (id->bInterfaceClass) {
+ case UICLASS_MASS:
+ switch (id->bInterfaceSubClass) {
+ case UISUBCLASS_SCSI:
+ case UISUBCLASS_UFI:
+ case UISUBCLASS_SFF8020I:
+ case UISUBCLASS_SFF8070I:
+ break;
+ default:
+ return (NULL);
+ }
+ switch (id->bInterfaceProtocol) {
+ case UIPROTO_MASS_BBB_OLD:
+ case UIPROTO_MASS_BBB:
+ break;
+ default:
+ return (NULL);
+ }
+ pconfig = bbb_config;
+ nconfig = ST_MAX;
+ break;
+ case UICLASS_HID:
+ switch (id->bInterfaceSubClass) {
+ case 0:
+ break;
+ default:
+ return (NULL);
+ }
+ pconfig = bbb_raw_config;
+ nconfig = 1;
+ break;
+ default:
+ return (NULL);
+ }
+
+ sc = malloc(sizeof(*sc), M_USB, M_WAITOK | M_ZERO);
+ mtx_init(&sc->mtx, "USB autoinstall", NULL, MTX_DEF);
+ cv_init(&sc->cv, "WBBB");
+
+ err = usbd_transfer_setup(udev, &iface_index, sc->xfer, pconfig,
+ nconfig, sc, &sc->mtx);
+ if (err) {
+ bbb_detach(sc);
+ return (NULL);
+ }
+ switch (id->bInterfaceClass) {
+ case UICLASS_MASS:
+ /* store pointer to DMA buffers */
+ sc->buffer = usbd_xfer_get_frame_buffer(
+ sc->xfer[ST_DATA_RD], 0);
+ sc->buffer_size =
+ usbd_xfer_max_len(sc->xfer[ST_DATA_RD]);
+ sc->cbw = usbd_xfer_get_frame_buffer(
+ sc->xfer[ST_COMMAND], 0);
+ sc->csw = usbd_xfer_get_frame_buffer(
+ sc->xfer[ST_STATUS], 0);
+ break;
+ default:
+ break;
+ }
+ return (sc);
+}
+
+static void
+bbb_detach(struct bbb_transfer *sc)
+{
+ usbd_transfer_unsetup(sc->xfer, ST_MAX);
+ mtx_destroy(&sc->mtx);
+ cv_destroy(&sc->cv);
+ free(sc, M_USB);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_iface_is_cdrom
+ *
+ * Return values:
+ * 1: This interface is an auto install disk (CD-ROM)
+ * 0: Not an auto install disk.
+ *------------------------------------------------------------------------*/
+int
+usb_iface_is_cdrom(struct usb_device *udev, uint8_t iface_index)
+{
+ struct bbb_transfer *sc;
+ uint8_t timeout;
+ uint8_t is_cdrom;
+ uint8_t sid_type;
+ int err;
+
+ sc = bbb_attach(udev, iface_index, UICLASS_MASS);
+ if (sc == NULL)
+ return (0);
+
+ is_cdrom = 0;
+ timeout = 4; /* tries */
+ while (--timeout) {
+ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
+ SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry),
+ USB_MS_HZ);
+
+ if (err == 0 && sc->actlen > 0) {
+ sid_type = sc->buffer[0] & 0x1F;
+ if (sid_type == 0x05)
+ is_cdrom = 1;
+ break;
+ } else if (err != ERR_CSW_FAILED)
+ break; /* non retryable error */
+ usb_pause_mtx(NULL, hz);
+ }
+ bbb_detach(sc);
+ return (is_cdrom);
+}
+
+static int
+usb_msc_get_max_lun(struct usb_device *udev, uint8_t iface_index)
+{
+ struct usb_device_request req;
+ uint8_t buf = 0;
+
+ /* The Get Max Lun command is a class-specific request. */
+ req.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.bRequest = 0xFE; /* GET_MAX_LUN */
+ USETW(req.wValue, 0);
+ req.wIndex[0] = iface_index;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 1);
+
+ return usbd_do_request(udev, NULL, &req, &buf);
+}
+
+#define USB_ADD_QUIRK(udev, any, which) do { \
+ if (usb_get_manufacturer(udev) != NULL && usb_get_product(udev) != NULL) { \
+ DPRINTFN(0, #which " set for USB mass storage device %s %s (0x%04x:0x%04x)\n", \
+ usb_get_manufacturer(udev), \
+ usb_get_product(udev), \
+ UGETW(udev->ddesc.idVendor), \
+ UGETW(udev->ddesc.idProduct)); \
+ } else { \
+ DPRINTFN(0, #which " set for USB mass storage device, 0x%04x:0x%04x\n", \
+ UGETW(udev->ddesc.idVendor), \
+ UGETW(udev->ddesc.idProduct)); \
+ } \
+ usbd_add_dynamic_quirk(udev, which); \
+ any = 1; \
+} while (0)
+
+usb_error_t
+usb_msc_auto_quirk(struct usb_device *udev, uint8_t iface_index,
+ const struct usb_attach_arg *uaa)
+{
+ struct bbb_transfer *sc;
+ uint8_t timeout;
+ uint8_t is_no_direct;
+ uint8_t sid_type;
+ uint8_t any_quirk;
+ int err;
+
+ sc = bbb_attach(udev, iface_index, UICLASS_MASS);
+ if (sc == NULL)
+ return (0);
+
+ any_quirk = 0;
+
+ /*
+ * Some devices need a delay after that the configuration
+ * value is set to function properly:
+ */
+ usb_pause_mtx(NULL, hz);
+
+ if (usb_test_quirk(uaa, UQ_MSC_NO_GETMAXLUN) == 0 &&
+ usb_msc_get_max_lun(udev, iface_index) != 0) {
+ DPRINTF("Device can't handle GETMAXLUN\n");
+ USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_GETMAXLUN);
+ }
+
+ is_no_direct = 1;
+ for (timeout = 4; timeout != 0; timeout--) {
+ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
+ SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry),
+ USB_MS_HZ);
+
+ if (err == 0 && sc->actlen > 0) {
+ sid_type = sc->buffer[0] & 0x1F;
+ if (sid_type == 0x00)
+ is_no_direct = 0;
+ break;
+ } else if (err != ERR_CSW_FAILED) {
+ DPRINTF("Device is not responding "
+ "properly to SCSI INQUIRY command.\n");
+ goto error; /* non retryable error */
+ }
+ usb_pause_mtx(NULL, hz);
+ }
+
+ if (is_no_direct) {
+ DPRINTF("Device is not direct access.\n");
+ goto done;
+ }
+
+ if (usb_test_quirk(uaa, UQ_MSC_NO_TEST_UNIT_READY) == 0) {
+ err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0,
+ &scsi_test_unit_ready, sizeof(scsi_test_unit_ready),
+ USB_MS_HZ);
+
+ if (err != 0) {
+ if (err != ERR_CSW_FAILED)
+ goto error;
+ USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_TEST_UNIT_READY);
+ }
+ }
+
+ if (usb_test_quirk(uaa, UQ_MSC_NO_PREVENT_ALLOW) == 0) {
+ err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0,
+ &scsi_prevent_removal, sizeof(scsi_prevent_removal),
+ USB_MS_HZ);
+
+ if (err == 0) {
+ err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0,
+ &scsi_allow_removal, sizeof(scsi_allow_removal),
+ USB_MS_HZ);
+ }
+
+ if (err != 0) {
+ if (err != ERR_CSW_FAILED)
+ goto error;
+ USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_PREVENT_ALLOW);
+ }
+ }
+
+ timeout = 1;
+
+retry_sync_cache:
+ err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0,
+ &scsi_sync_cache, sizeof(scsi_sync_cache),
+ USB_MS_HZ);
+
+ if (err != 0) {
+ if (err != ERR_CSW_FAILED)
+ goto error;
+
+ USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_SYNC_CACHE);
+ } else {
+ /*
+ * Certain Kingston memory sticks fail the first
+ * read capacity after a synchronize cache command
+ * has been issued. Disable the synchronize cache
+ * command for such devices.
+ */
+
+ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 8,
+ &scsi_read_capacity, sizeof(scsi_read_capacity),
+ USB_MS_HZ);
+
+ if (err != 0) {
+ if (err != ERR_CSW_FAILED)
+ goto error;
+
+ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 8,
+ &scsi_read_capacity, sizeof(scsi_read_capacity),
+ USB_MS_HZ);
+
+ if (err == 0) {
+ if (timeout--)
+ goto retry_sync_cache;
+
+ USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_SYNC_CACHE);
+ } else {
+ if (err != ERR_CSW_FAILED)
+ goto error;
+ }
+ }
+ }
+
+ if (usb_test_quirk(uaa, UQ_MSC_NO_START_STOP) == 0) {
+ err = bbb_command_start(sc, DIR_NONE, 0, NULL, 0,
+ &scsi_start_unit, sizeof(scsi_start_unit),
+ USB_MS_HZ);
+
+ if (err != 0) {
+ if (err != ERR_CSW_FAILED)
+ goto error;
+ USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_START_STOP);
+ }
+ }
+
+ /* clear sense status of any failed commands on the device */
+
+ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
+ SCSI_INQ_LEN, &scsi_inquiry, sizeof(scsi_inquiry),
+ USB_MS_HZ);
+
+ DPRINTF("Inquiry = %d\n", err);
+
+ if (err != 0) {
+ if (err != ERR_CSW_FAILED)
+ goto error;
+ }
+
+ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
+ SCSI_SENSE_LEN, &scsi_request_sense,
+ sizeof(scsi_request_sense), USB_MS_HZ);
+
+ DPRINTF("Request sense = %d\n", err);
+
+ if (err != 0) {
+ if (err != ERR_CSW_FAILED)
+ goto error;
+ }
+ goto done;
+
+error:
+ /* Apply most quirks */
+ USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_SYNC_CACHE);
+ USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_PREVENT_ALLOW);
+ USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_TEST_UNIT_READY);
+ USB_ADD_QUIRK(udev, any_quirk, UQ_MSC_NO_START_STOP);
+done:
+ bbb_detach(sc);
+
+ if (any_quirk) {
+ /* Unconfigure device, to clear software data toggle. */
+ usbd_set_config_index(udev, USB_UNCONFIG_INDEX);
+
+ /* Need to re-enumerate the device to clear its state. */
+ usbd_req_re_enumerate(udev, NULL);
+ return (USB_ERR_STALLED);
+ }
+
+ /* No quirks were added, continue as usual. */
+ return (0);
+}
+
+usb_error_t
+usb_msc_eject(struct usb_device *udev, uint8_t iface_index, int method)
+{
+ struct bbb_transfer *sc;
+ usb_error_t err __usbdebug_used;
+
+ sc = bbb_attach(udev, iface_index, UICLASS_MASS);
+ if (sc == NULL)
+ return (USB_ERR_INVAL);
+
+ switch (method) {
+ case MSC_EJECT_STOPUNIT:
+ err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
+ &scsi_test_unit_ready, sizeof(scsi_test_unit_ready),
+ USB_MS_HZ);
+ DPRINTF("Test unit ready status: %s\n", usbd_errstr(err));
+ err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
+ &scsi_stop_unit, sizeof(scsi_stop_unit),
+ USB_MS_HZ);
+ break;
+ case MSC_EJECT_REZERO:
+ err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
+ &scsi_rezero_init, sizeof(scsi_rezero_init),
+ USB_MS_HZ);
+ break;
+ case MSC_EJECT_ZTESTOR:
+ err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
+ &scsi_ztestor_eject, sizeof(scsi_ztestor_eject),
+ USB_MS_HZ);
+ break;
+ case MSC_EJECT_CMOTECH:
+ err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
+ &scsi_cmotech_eject, sizeof(scsi_cmotech_eject),
+ USB_MS_HZ);
+ break;
+ case MSC_EJECT_HUAWEI:
+ err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
+ &scsi_huawei_eject, sizeof(scsi_huawei_eject),
+ USB_MS_HZ);
+ break;
+ case MSC_EJECT_HUAWEI2:
+ err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
+ &scsi_huawei_eject2, sizeof(scsi_huawei_eject2),
+ USB_MS_HZ);
+ break;
+ case MSC_EJECT_HUAWEI3:
+ err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
+ &scsi_huawei_eject3, sizeof(scsi_huawei_eject3),
+ USB_MS_HZ);
+ break;
+ case MSC_EJECT_HUAWEI4:
+ err = bbb_command_start(sc, DIR_IN, 0, NULL, 0,
+ &scsi_huawei_eject4, sizeof(scsi_huawei_eject4),
+ USB_MS_HZ);
+ break;
+ case MSC_EJECT_TCT:
+ /*
+ * TCTMobile needs DIR_IN flag. To get it, we
+ * supply a dummy data with the command.
+ */
+ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer,
+ sc->buffer_size, &scsi_tct_eject,
+ sizeof(scsi_tct_eject), USB_MS_HZ);
+ break;
+ default:
+ DPRINTF("Unknown eject method (%d)\n", method);
+ bbb_detach(sc);
+ return (USB_ERR_INVAL);
+ }
+
+ DPRINTF("Eject CD command status: %s\n", usbd_errstr(err));
+
+ bbb_detach(sc);
+ return (0);
+}
+
+usb_error_t
+usb_dymo_eject(struct usb_device *udev, uint8_t iface_index)
+{
+ static const uint8_t data[3] = { 0x1b, 0x5a, 0x01 };
+ struct bbb_transfer *sc;
+ usb_error_t err;
+
+ sc = bbb_attach(udev, iface_index, UICLASS_HID);
+ if (sc == NULL)
+ return (USB_ERR_INVAL);
+ err = bbb_raw_write(sc, data, sizeof(data), USB_MS_HZ);
+ bbb_detach(sc);
+ return (err);
+}
+
+usb_error_t
+usb_msc_read_10(struct usb_device *udev, uint8_t iface_index,
+ uint32_t lba, uint32_t blocks, void *buffer)
+{
+ struct bbb_transfer *sc;
+ uint8_t cmd[10];
+ usb_error_t err;
+
+ cmd[0] = 0x28; /* READ_10 */
+ cmd[1] = 0;
+ cmd[2] = lba >> 24;
+ cmd[3] = lba >> 16;
+ cmd[4] = lba >> 8;
+ cmd[5] = lba >> 0;
+ cmd[6] = 0;
+ cmd[7] = blocks >> 8;
+ cmd[8] = blocks;
+ cmd[9] = 0;
+
+ sc = bbb_attach(udev, iface_index, UICLASS_MASS);
+ if (sc == NULL)
+ return (USB_ERR_INVAL);
+
+ err = bbb_command_start(sc, DIR_IN, 0, buffer,
+ blocks * SCSI_FIXED_BLOCK_SIZE, cmd, 10, USB_MS_HZ);
+
+ bbb_detach(sc);
+
+ return (err);
+}
+
+usb_error_t
+usb_msc_write_10(struct usb_device *udev, uint8_t iface_index,
+ uint32_t lba, uint32_t blocks, void *buffer)
+{
+ struct bbb_transfer *sc;
+ uint8_t cmd[10];
+ usb_error_t err;
+
+ cmd[0] = 0x2a; /* WRITE_10 */
+ cmd[1] = 0;
+ cmd[2] = lba >> 24;
+ cmd[3] = lba >> 16;
+ cmd[4] = lba >> 8;
+ cmd[5] = lba >> 0;
+ cmd[6] = 0;
+ cmd[7] = blocks >> 8;
+ cmd[8] = blocks;
+ cmd[9] = 0;
+
+ sc = bbb_attach(udev, iface_index, UICLASS_MASS);
+ if (sc == NULL)
+ return (USB_ERR_INVAL);
+
+ err = bbb_command_start(sc, DIR_OUT, 0, buffer,
+ blocks * SCSI_FIXED_BLOCK_SIZE, cmd, 10, USB_MS_HZ);
+
+ bbb_detach(sc);
+
+ return (err);
+}
+
+usb_error_t
+usb_msc_read_capacity(struct usb_device *udev, uint8_t iface_index,
+ uint32_t *lba_last, uint32_t *block_size)
+{
+ struct bbb_transfer *sc;
+ usb_error_t err;
+
+ sc = bbb_attach(udev, iface_index, UICLASS_MASS);
+ if (sc == NULL)
+ return (USB_ERR_INVAL);
+
+ err = bbb_command_start(sc, DIR_IN, 0, sc->buffer, 8,
+ &scsi_read_capacity, sizeof(scsi_read_capacity),
+ USB_MS_HZ);
+
+ *lba_last =
+ (sc->buffer[0] << 24) |
+ (sc->buffer[1] << 16) |
+ (sc->buffer[2] << 8) |
+ (sc->buffer[3]);
+
+ *block_size =
+ (sc->buffer[4] << 24) |
+ (sc->buffer[5] << 16) |
+ (sc->buffer[6] << 8) |
+ (sc->buffer[7]);
+
+ /* we currently only support one block size */
+ if (*block_size != SCSI_FIXED_BLOCK_SIZE)
+ err = USB_ERR_INVAL;
+
+ bbb_detach(sc);
+
+ return (err);
+}
diff --git a/sys/dev/usb/usb_msctest.h b/sys/dev/usb/usb_msctest.h
new file mode 100644
index 000000000000..5a52b6f4bec6
--- /dev/null
+++ b/sys/dev/usb/usb_msctest.h
@@ -0,0 +1,61 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008-2022 Hans Petter Selasky.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_MSCTEST_H_
+#define _USB_MSCTEST_H_
+
+enum {
+ MSC_EJECT_STOPUNIT,
+ MSC_EJECT_REZERO,
+ MSC_EJECT_ZTESTOR,
+ MSC_EJECT_CMOTECH,
+ MSC_EJECT_HUAWEI,
+ MSC_EJECT_HUAWEI2,
+ MSC_EJECT_HUAWEI3,
+ MSC_EJECT_HUAWEI4,
+ MSC_EJECT_TCT,
+};
+
+int usb_iface_is_cdrom(struct usb_device *udev,
+ uint8_t iface_index);
+usb_error_t usb_msc_eject(struct usb_device *udev,
+ uint8_t iface_index, int method);
+usb_error_t usb_msc_auto_quirk(struct usb_device *udev,
+ uint8_t iface_index, const struct usb_attach_arg *uaa);
+usb_error_t usb_msc_read_10(struct usb_device *udev,
+ uint8_t iface_index, uint32_t lba, uint32_t blocks,
+ void *buffer);
+usb_error_t usb_msc_write_10(struct usb_device *udev,
+ uint8_t iface_index, uint32_t lba, uint32_t blocks,
+ void *buffer);
+usb_error_t usb_msc_read_capacity(struct usb_device *udev,
+ uint8_t iface_index, uint32_t *lba_last,
+ uint32_t *block_size);
+usb_error_t usb_dymo_eject(struct usb_device *udev,
+ uint8_t iface_index);
+
+#endif /* _USB_MSCTEST_H_ */
diff --git a/sys/dev/usb/usb_parse.c b/sys/dev/usb/usb_parse.c
new file mode 100644
index 000000000000..bb592f1a4c03
--- /dev/null
+++ b/sys/dev/usb/usb_parse.c
@@ -0,0 +1,317 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008-2020 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+
+#define USB_DEBUG_VAR usb_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_debug.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+/*------------------------------------------------------------------------*
+ * usb_desc_foreach
+ *
+ * This function is the safe way to iterate across the USB config
+ * descriptor. It contains several checks against invalid
+ * descriptors. If the "desc" argument passed to this function is
+ * "NULL" the first descriptor, if any, will be returned.
+ *
+ * Return values:
+ * NULL: End of descriptors
+ * Else: Next descriptor after "desc"
+ *------------------------------------------------------------------------*/
+struct usb_descriptor *
+usb_desc_foreach(struct usb_config_descriptor *cd,
+ struct usb_descriptor *_desc)
+{
+ uint8_t *desc_next;
+ uint8_t *start;
+ uint8_t *end;
+ uint8_t *desc;
+
+ /* be NULL safe */
+ if (cd == NULL)
+ return (NULL);
+
+ /* We assume that the "wTotalLength" has been checked. */
+ start = (uint8_t *)cd;
+ end = start + UGETW(cd->wTotalLength);
+ desc = (uint8_t *)_desc;
+
+ /* Get start of next USB descriptor. */
+ if (desc == NULL)
+ desc = start;
+ else
+ desc = desc + desc[0];
+
+ /* Check that the next USB descriptor is within the range. */
+ if ((desc < start) || (desc >= end))
+ return (NULL); /* out of range, or EOD */
+
+ /* Check that the second next USB descriptor is within range. */
+ desc_next = desc + desc[0];
+ if ((desc_next < start) || (desc_next > end))
+ return (NULL); /* out of range */
+
+ /* Check minimum descriptor length. */
+ if (desc[0] < 3)
+ return (NULL); /* too short descriptor */
+
+ /* Return start of next descriptor. */
+ return ((struct usb_descriptor *)desc);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_idesc_foreach
+ *
+ * This function will iterate the interface descriptors in the config
+ * descriptor. The parse state structure should be zeroed before
+ * calling this function the first time.
+ *
+ * Return values:
+ * NULL: End of descriptors
+ * Else: A valid interface descriptor
+ *------------------------------------------------------------------------*/
+struct usb_interface_descriptor *
+usb_idesc_foreach(struct usb_config_descriptor *cd,
+ struct usb_idesc_parse_state *ps)
+{
+ struct usb_interface_descriptor *id;
+ uint8_t new_iface;
+
+ /* retrieve current descriptor */
+ id = (struct usb_interface_descriptor *)ps->desc;
+ /* default is to start a new interface */
+ new_iface = 1;
+
+ while (1) {
+ id = (struct usb_interface_descriptor *)
+ usb_desc_foreach(cd, (struct usb_descriptor *)id);
+ if (id == NULL)
+ break;
+ if ((id->bDescriptorType == UDESC_INTERFACE) &&
+ (id->bLength >= sizeof(*id))) {
+ if (ps->iface_no_last == id->bInterfaceNumber) {
+ /*
+ * Don't allow more than 256 alternate
+ * settings to avoid overflowing the
+ * alternate index which is a 8-bit
+ * variable.
+ */
+ if (ps->iface_index_alt == 255) {
+ DPRINTF("Interface(%u) has more than 256 alternate settings\n",
+ id->bInterfaceNumber);
+ continue;
+ }
+ new_iface = 0;
+ }
+ ps->iface_no_last = id->bInterfaceNumber;
+ break;
+ }
+ }
+
+ if (ps->desc == NULL) {
+ /* first time or zero descriptors */
+ } else if (new_iface) {
+ /* new interface */
+ ps->iface_index ++;
+ ps->iface_index_alt = 0;
+ } else {
+ /* new alternate interface */
+ ps->iface_index_alt ++;
+ }
+#if (USB_IFACE_MAX <= 0)
+#error "USB_IFACE_MAX must be defined greater than zero"
+#endif
+ /* check for too many interfaces */
+ if (ps->iface_index >= USB_IFACE_MAX) {
+ DPRINTF("Interface limit reached\n");
+ id = NULL;
+ }
+
+ /* store and return current descriptor */
+ ps->desc = (struct usb_descriptor *)id;
+ return (id);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_edesc_foreach
+ *
+ * This function will iterate all the endpoint descriptors within an
+ * interface descriptor. Starting value for the "ped" argument should
+ * be a valid interface descriptor.
+ *
+ * Return values:
+ * NULL: End of descriptors
+ * Else: A valid endpoint descriptor
+ *------------------------------------------------------------------------*/
+struct usb_endpoint_descriptor *
+usb_edesc_foreach(struct usb_config_descriptor *cd,
+ struct usb_endpoint_descriptor *ped)
+{
+ struct usb_descriptor *desc;
+
+ desc = ((struct usb_descriptor *)ped);
+
+ while ((desc = usb_desc_foreach(cd, desc))) {
+ if (desc->bDescriptorType == UDESC_INTERFACE) {
+ break;
+ }
+ if (desc->bDescriptorType == UDESC_ENDPOINT) {
+ if (desc->bLength < sizeof(*ped)) {
+ /* endpoint descriptor is invalid */
+ break;
+ }
+ return ((struct usb_endpoint_descriptor *)desc);
+ }
+ }
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_ed_comp_foreach
+ *
+ * This function will iterate all the endpoint companion descriptors
+ * within an endpoint descriptor in an interface descriptor. Starting
+ * value for the "ped" argument should be a valid endpoint companion
+ * descriptor.
+ *
+ * Return values:
+ * NULL: End of descriptors
+ * Else: A valid endpoint companion descriptor
+ *------------------------------------------------------------------------*/
+struct usb_endpoint_ss_comp_descriptor *
+usb_ed_comp_foreach(struct usb_config_descriptor *cd,
+ struct usb_endpoint_ss_comp_descriptor *ped)
+{
+ struct usb_descriptor *desc;
+
+ desc = ((struct usb_descriptor *)ped);
+
+ while ((desc = usb_desc_foreach(cd, desc))) {
+ if (desc->bDescriptorType == UDESC_INTERFACE)
+ break;
+ if (desc->bDescriptorType == UDESC_ENDPOINT)
+ break;
+ if (desc->bDescriptorType == UDESC_ENDPOINT_SS_COMP) {
+ if (desc->bLength < sizeof(*ped)) {
+ /* endpoint companion descriptor is invalid */
+ break;
+ }
+ return ((struct usb_endpoint_ss_comp_descriptor *)desc);
+ }
+ }
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_get_no_descriptors
+ *
+ * This function will count the total number of descriptors in the
+ * configuration descriptor of type "type".
+ *------------------------------------------------------------------------*/
+uint8_t
+usbd_get_no_descriptors(struct usb_config_descriptor *cd, uint8_t type)
+{
+ struct usb_descriptor *desc = NULL;
+ uint8_t count = 0;
+
+ while ((desc = usb_desc_foreach(cd, desc))) {
+ if (desc->bDescriptorType == type) {
+ count++;
+ if (count == 0xFF)
+ break; /* crazy */
+ }
+ }
+ return (count);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_get_no_alts
+ *
+ * Return value:
+ * Number of alternate settings for the given interface descriptor
+ * pointer. If the USB descriptor is corrupt, the returned value can
+ * be greater than the actual number of alternate settings.
+ *------------------------------------------------------------------------*/
+uint8_t
+usbd_get_no_alts(struct usb_config_descriptor *cd,
+ struct usb_interface_descriptor *id)
+{
+ struct usb_descriptor *desc;
+ uint8_t n;
+ uint8_t ifaceno;
+
+ /* Reset interface count */
+
+ n = 0;
+
+ /* Get the interface number */
+
+ ifaceno = id->bInterfaceNumber;
+
+ /* Iterate all the USB descriptors */
+
+ desc = NULL;
+ while ((desc = usb_desc_foreach(cd, desc))) {
+ if ((desc->bDescriptorType == UDESC_INTERFACE) &&
+ (desc->bLength >= sizeof(*id))) {
+ id = (struct usb_interface_descriptor *)desc;
+ if (id->bInterfaceNumber == ifaceno) {
+ n++;
+ if (n == 0xFF)
+ break; /* crazy */
+ }
+ }
+ }
+ return (n);
+}
diff --git a/sys/dev/usb/usb_pci.h b/sys/dev/usb/usb_pci.h
new file mode 100644
index 000000000000..ab293eefb31b
--- /dev/null
+++ b/sys/dev/usb/usb_pci.h
@@ -0,0 +1,42 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_PCI_H_
+#define _USB_PCI_H_
+
+/*
+ * We don't want the following files included everywhere, that's why
+ * they are in a separate file.
+ */
+#ifndef USB_GLOBAL_INCLUDE_FILE
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+
+#include <sys/rman.h>
+#endif
+
+#endif /* _USB_PCI_H_ */
diff --git a/sys/dev/usb/usb_pf.c b/sys/dev/usb/usb_pf.c
new file mode 100644
index 000000000000..0e7a75d04d6a
--- /dev/null
+++ b/sys/dev/usb/usb_pf.c
@@ -0,0 +1,531 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from the Stanford/CMU enet packet filter,
+ * (net/enet.c) distributed as part of 4.3BSD, and code contributed
+ * to Berkeley by Steven McCanne and Van Jacobson both of Lawrence
+ * Berkeley Laboratory.
+ *
+ * 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 University 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 REGENTS 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 REGENTS 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.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/fcntl.h>
+#include <sys/malloc.h>
+#include <sys/proc.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_types.h>
+#include <net/if_clone.h>
+#include <net/bpf.h>
+#include <sys/sysctl.h>
+#include <net/route.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/usb_pf.h>
+#include <dev/usb/usb_transfer.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+static void usbpf_init(void *);
+static void usbpf_uninit(void *);
+static int usbpf_ioctl(if_t, u_long, caddr_t);
+static int usbpf_clone_match(struct if_clone *, const char *);
+static int usbpf_clone_create(struct if_clone *, char *, size_t,
+ struct ifc_data *, if_t *);
+static int usbpf_clone_destroy(struct if_clone *, if_t, uint32_t);
+static struct usb_bus *usbpf_ifname2ubus(const char *);
+static uint32_t usbpf_aggregate_xferflags(struct usb_xfer_flags *);
+static uint32_t usbpf_aggregate_status(struct usb_xfer_flags_int *);
+static int usbpf_xfer_frame_is_read(struct usb_xfer *, uint32_t);
+static uint32_t usbpf_xfer_precompute_size(struct usb_xfer *, int);
+
+static struct if_clone *usbpf_cloner;
+static const char usbusname[] = "usbus";
+
+SYSINIT(usbpf_init, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, usbpf_init, NULL);
+SYSUNINIT(usbpf_uninit, SI_SUB_PSEUDO, SI_ORDER_MIDDLE, usbpf_uninit, NULL);
+
+static void
+usbpf_init(void *arg)
+{
+ struct if_clone_addreq req = {
+ .match_f = usbpf_clone_match,
+ .create_f = usbpf_clone_create,
+ .destroy_f = usbpf_clone_destroy,
+ };
+
+ usbpf_cloner = ifc_attach_cloner(usbusname, &req);
+}
+
+static void
+usbpf_uninit(void *arg)
+{
+ int devlcnt;
+ device_t *devlp;
+ devclass_t dc;
+ struct usb_bus *ubus;
+ int error;
+ int i;
+
+ if_clone_detach(usbpf_cloner);
+
+ dc = devclass_find(usbusname);
+ if (dc == NULL)
+ return;
+ error = devclass_get_devices(dc, &devlp, &devlcnt);
+ if (error)
+ return;
+ for (i = 0; i < devlcnt; i++) {
+ ubus = device_get_softc(devlp[i]);
+ if (ubus != NULL && ubus->ifp != NULL)
+ usbpf_clone_destroy(usbpf_cloner, ubus->ifp, 0);
+ }
+ free(devlp, M_TEMP);
+}
+
+static int
+usbpf_ioctl(if_t ifp, u_long cmd, caddr_t data)
+{
+
+ /* No configuration allowed. */
+ return (EINVAL);
+}
+
+static struct usb_bus *
+usbpf_ifname2ubus(const char *ifname)
+{
+ device_t dev;
+ devclass_t dc;
+ int unit;
+ int error;
+
+ if (strncmp(ifname, usbusname, sizeof(usbusname) - 1) != 0)
+ return (NULL);
+ error = ifc_name2unit(ifname, &unit);
+ if (error || unit < 0)
+ return (NULL);
+ dc = devclass_find(usbusname);
+ if (dc == NULL)
+ return (NULL);
+ dev = devclass_get_device(dc, unit);
+ if (dev == NULL)
+ return (NULL);
+
+ return (device_get_softc(dev));
+}
+
+static int
+usbpf_clone_match(struct if_clone *ifc, const char *name)
+{
+ struct usb_bus *ubus;
+
+ ubus = usbpf_ifname2ubus(name);
+ if (ubus == NULL)
+ return (0);
+ if (ubus->ifp != NULL)
+ return (0);
+
+ return (1);
+}
+
+static int
+usbpf_clone_create(struct if_clone *ifc, char *name, size_t len,
+ struct ifc_data *ifd, if_t *ifpp)
+{
+ int error;
+ int unit;
+ if_t ifp;
+ struct usb_bus *ubus;
+
+ error = ifc_name2unit(name, &unit);
+ if (error)
+ return (error);
+ if (unit < 0)
+ return (EINVAL);
+
+ ubus = usbpf_ifname2ubus(name);
+ if (ubus == NULL)
+ return (1);
+ if (ubus->ifp != NULL)
+ return (1);
+
+ error = ifc_alloc_unit(ifc, &unit);
+ if (error) {
+ device_printf(ubus->parent, "usbpf: Could not allocate "
+ "instance\n");
+ return (error);
+ }
+ ifp = ubus->ifp = if_alloc(IFT_USB);
+ if_setsoftc(ifp, ubus);
+ if_initname(ifp, usbusname, unit);
+ if_setname(ifp, name);
+ if_setioctlfn(ifp, usbpf_ioctl);
+ if_attach(ifp);
+ if_setflagbits(ifp, IFF_UP, 0);
+ rt_ifmsg(ifp, IFF_UP);
+ /*
+ * XXX According to the specification of DLT_USB, it indicates
+ * packets beginning with USB setup header. But not sure all
+ * packets would be.
+ */
+ bpfattach(ifp, DLT_USB, USBPF_HDR_LEN);
+ *ifpp = ifp;
+
+ return (0);
+}
+
+static int
+usbpf_clone_destroy(struct if_clone *ifc, if_t ifp, uint32_t flags)
+{
+ struct usb_bus *ubus;
+ int unit;
+
+ ubus = if_getsoftc(ifp);
+ unit = if_getdunit(ifp);
+
+ /*
+ * Lock USB before clearing the "ifp" pointer, to avoid
+ * clearing the pointer in the middle of a TAP operation:
+ */
+ USB_BUS_LOCK(ubus);
+ ubus->ifp = NULL;
+ USB_BUS_UNLOCK(ubus);
+ bpfdetach(ifp);
+ if_detach(ifp);
+ if_free(ifp);
+ ifc_free_unit(ifc, unit);
+
+ return (0);
+}
+
+void
+usbpf_attach(struct usb_bus *ubus)
+{
+
+ if (bootverbose)
+ device_printf(ubus->parent, "usbpf: Attached\n");
+}
+
+void
+usbpf_detach(struct usb_bus *ubus)
+{
+
+ if (ubus->ifp != NULL)
+ usbpf_clone_destroy(usbpf_cloner, ubus->ifp, 0);
+ if (bootverbose)
+ device_printf(ubus->parent, "usbpf: Detached\n");
+}
+
+static uint32_t
+usbpf_aggregate_xferflags(struct usb_xfer_flags *flags)
+{
+ uint32_t val = 0;
+
+ if (flags->force_short_xfer == 1)
+ val |= USBPF_FLAG_FORCE_SHORT_XFER;
+ if (flags->short_xfer_ok == 1)
+ val |= USBPF_FLAG_SHORT_XFER_OK;
+ if (flags->short_frames_ok == 1)
+ val |= USBPF_FLAG_SHORT_FRAMES_OK;
+ if (flags->pipe_bof == 1)
+ val |= USBPF_FLAG_PIPE_BOF;
+ if (flags->proxy_buffer == 1)
+ val |= USBPF_FLAG_PROXY_BUFFER;
+ if (flags->ext_buffer == 1)
+ val |= USBPF_FLAG_EXT_BUFFER;
+ if (flags->manual_status == 1)
+ val |= USBPF_FLAG_MANUAL_STATUS;
+ if (flags->no_pipe_ok == 1)
+ val |= USBPF_FLAG_NO_PIPE_OK;
+ if (flags->stall_pipe == 1)
+ val |= USBPF_FLAG_STALL_PIPE;
+ return (val);
+}
+
+static uint32_t
+usbpf_aggregate_status(struct usb_xfer_flags_int *flags)
+{
+ uint32_t val = 0;
+
+ if (flags->open == 1)
+ val |= USBPF_STATUS_OPEN;
+ if (flags->transferring == 1)
+ val |= USBPF_STATUS_TRANSFERRING;
+ if (flags->did_dma_delay == 1)
+ val |= USBPF_STATUS_DID_DMA_DELAY;
+ if (flags->did_close == 1)
+ val |= USBPF_STATUS_DID_CLOSE;
+ if (flags->draining == 1)
+ val |= USBPF_STATUS_DRAINING;
+ if (flags->started == 1)
+ val |= USBPF_STATUS_STARTED;
+ if (flags->bandwidth_reclaimed == 1)
+ val |= USBPF_STATUS_BW_RECLAIMED;
+ if (flags->control_xfr == 1)
+ val |= USBPF_STATUS_CONTROL_XFR;
+ if (flags->control_hdr == 1)
+ val |= USBPF_STATUS_CONTROL_HDR;
+ if (flags->control_act == 1)
+ val |= USBPF_STATUS_CONTROL_ACT;
+ if (flags->control_stall == 1)
+ val |= USBPF_STATUS_CONTROL_STALL;
+ if (flags->short_frames_ok == 1)
+ val |= USBPF_STATUS_SHORT_FRAMES_OK;
+ if (flags->short_xfer_ok == 1)
+ val |= USBPF_STATUS_SHORT_XFER_OK;
+#if USB_HAVE_BUSDMA
+ if (flags->bdma_enable == 1)
+ val |= USBPF_STATUS_BDMA_ENABLE;
+ if (flags->bdma_no_post_sync == 1)
+ val |= USBPF_STATUS_BDMA_NO_POST_SYNC;
+ if (flags->bdma_setup == 1)
+ val |= USBPF_STATUS_BDMA_SETUP;
+#endif
+ if (flags->isochronous_xfr == 1)
+ val |= USBPF_STATUS_ISOCHRONOUS_XFR;
+ if (flags->curr_dma_set == 1)
+ val |= USBPF_STATUS_CURR_DMA_SET;
+ if (flags->can_cancel_immed == 1)
+ val |= USBPF_STATUS_CAN_CANCEL_IMMED;
+ if (flags->doing_callback == 1)
+ val |= USBPF_STATUS_DOING_CALLBACK;
+
+ return (val);
+}
+
+static int
+usbpf_xfer_frame_is_read(struct usb_xfer *xfer, uint32_t frame)
+{
+ int isread;
+
+ if ((frame == 0) && (xfer->flags_int.control_xfr != 0) &&
+ (xfer->flags_int.control_hdr != 0)) {
+ /* special case */
+ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
+ /* The device controller writes to memory */
+ isread = 1;
+ } else {
+ /* The host controller reads from memory */
+ isread = 0;
+ }
+ } else {
+ isread = USB_GET_DATA_ISREAD(xfer);
+ }
+ return (isread);
+}
+
+static uint32_t
+usbpf_xfer_precompute_size(struct usb_xfer *xfer, int type)
+{
+ uint32_t totlen;
+ uint32_t x;
+ uint32_t nframes;
+
+ if (type == USBPF_XFERTAP_SUBMIT)
+ nframes = xfer->nframes;
+ else
+ nframes = xfer->aframes;
+
+ totlen = USBPF_HDR_LEN + (USBPF_FRAME_HDR_LEN * nframes);
+
+ /* precompute all trace lengths */
+ for (x = 0; x != nframes; x++) {
+ if (usbpf_xfer_frame_is_read(xfer, x)) {
+ if (type != USBPF_XFERTAP_SUBMIT) {
+ totlen += USBPF_FRAME_ALIGN(
+ xfer->frlengths[x]);
+ }
+ } else {
+ if (type == USBPF_XFERTAP_SUBMIT) {
+ totlen += USBPF_FRAME_ALIGN(
+ xfer->frlengths[x]);
+ }
+ }
+ }
+ return (totlen);
+}
+
+void
+usbpf_xfertap(struct usb_xfer *xfer, int type)
+{
+ struct usb_bus *bus;
+ struct usbpf_pkthdr *up;
+ struct usbpf_framehdr *uf;
+ usb_frlength_t offset;
+ uint32_t totlen;
+ uint32_t frame;
+ uint32_t temp;
+ uint32_t nframes;
+ uint32_t x;
+ uint8_t *buf;
+ uint8_t *ptr;
+
+ bus = xfer->xroot->bus;
+
+ /* sanity checks */
+ if (bus->ifp == NULL || !bpf_peers_present_if(bus->ifp))
+ return;
+
+ totlen = usbpf_xfer_precompute_size(xfer, type);
+
+ if (type == USBPF_XFERTAP_SUBMIT)
+ nframes = xfer->nframes;
+ else
+ nframes = xfer->aframes;
+
+ /*
+ * XXX TODO XXX
+ *
+ * When BPF supports it we could pass a fragmented array of
+ * buffers avoiding the data copy operation here.
+ */
+ buf = ptr = malloc(totlen, M_TEMP, M_NOWAIT);
+ if (buf == NULL) {
+ device_printf(bus->parent, "usbpf: Out of memory\n");
+ return;
+ }
+
+ up = (struct usbpf_pkthdr *)ptr;
+ ptr += USBPF_HDR_LEN;
+
+ /* fill out header */
+ temp = device_get_unit(bus->bdev);
+ up->up_totlen = htole32(totlen);
+ up->up_busunit = htole32(temp);
+ up->up_address = xfer->xroot->udev->device_index;
+ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE)
+ up->up_mode = USBPF_MODE_DEVICE;
+ else
+ up->up_mode = USBPF_MODE_HOST;
+ up->up_type = type;
+ up->up_xfertype = xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE;
+ temp = usbpf_aggregate_xferflags(&xfer->flags);
+ up->up_flags = htole32(temp);
+ temp = usbpf_aggregate_status(&xfer->flags_int);
+ up->up_status = htole32(temp);
+ temp = xfer->error;
+ up->up_error = htole32(temp);
+ temp = xfer->interval;
+ up->up_interval = htole32(temp);
+ up->up_frames = htole32(nframes);
+ temp = xfer->max_packet_size;
+ up->up_packet_size = htole32(temp);
+ temp = xfer->max_packet_count;
+ up->up_packet_count = htole32(temp);
+ temp = xfer->endpointno;
+ up->up_endpoint = htole32(temp);
+ up->up_speed = xfer->xroot->udev->speed;
+
+ /* clear reserved area */
+ memset(up->up_reserved, 0, sizeof(up->up_reserved));
+
+ /* init offset and frame */
+ offset = 0;
+ frame = 0;
+
+ /* iterate all the USB frames and copy data, if any */
+ for (x = 0; x != nframes; x++) {
+ uint32_t length;
+ int isread;
+
+ /* get length */
+ length = xfer->frlengths[x];
+
+ /* get frame header pointer */
+ uf = (struct usbpf_framehdr *)ptr;
+ ptr += USBPF_FRAME_HDR_LEN;
+
+ /* fill out packet header */
+ uf->length = htole32(length);
+ uf->flags = 0;
+
+ /* get information about data read/write */
+ isread = usbpf_xfer_frame_is_read(xfer, x);
+
+ /* check if we need to copy any data */
+ if (isread) {
+ if (type == USBPF_XFERTAP_SUBMIT)
+ length = 0;
+ else {
+ uf->flags |= htole32(
+ USBPF_FRAMEFLAG_DATA_FOLLOWS);
+ }
+ } else {
+ if (type != USBPF_XFERTAP_SUBMIT)
+ length = 0;
+ else {
+ uf->flags |= htole32(
+ USBPF_FRAMEFLAG_DATA_FOLLOWS);
+ }
+ }
+
+ /* check if data is read direction */
+ if (isread)
+ uf->flags |= htole32(USBPF_FRAMEFLAG_READ);
+
+ /* copy USB data, if any */
+ if (length != 0) {
+ /* copy data */
+ usbd_copy_out(&xfer->frbuffers[frame],
+ offset, ptr, length);
+
+ /* align length */
+ temp = USBPF_FRAME_ALIGN(length);
+
+ /* zero pad */
+ if (temp != length)
+ memset(ptr + length, 0, temp - length);
+
+ ptr += temp;
+ }
+
+ if (xfer->flags_int.isochronous_xfr) {
+ offset += usbd_xfer_old_frame_length(xfer, x);
+ } else {
+ frame ++;
+ }
+ }
+
+ bpf_tap_if(bus->ifp, buf, totlen);
+
+ free(buf, M_TEMP);
+}
diff --git a/sys/dev/usb/usb_pf.h b/sys/dev/usb/usb_pf.h
new file mode 100644
index 000000000000..08d8854e247a
--- /dev/null
+++ b/sys/dev/usb/usb_pf.h
@@ -0,0 +1,120 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1990, 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from the Stanford/CMU enet packet filter,
+ * (net/enet.c) distributed as part of 4.3BSD, and code contributed
+ * to Berkeley by Steven McCanne and Van Jacobson both of Lawrence
+ * Berkeley Laboratory.
+ *
+ * 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 University 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 REGENTS 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 REGENTS 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_USB_PF_H
+#define _DEV_USB_PF_H
+
+struct usbpf_pkthdr {
+ uint32_t up_totlen; /* Total length including all headers */
+ uint32_t up_busunit; /* Host controller unit number */
+ uint8_t up_address; /* USB device index */
+ uint8_t up_mode; /* Mode of transfer */
+#define USBPF_MODE_HOST 0
+#define USBPF_MODE_DEVICE 1
+ uint8_t up_type; /* points SUBMIT / DONE */
+ uint8_t up_xfertype; /* Transfer type, see USB2.0 spec. */
+ uint32_t up_flags; /* Transfer flags */
+#define USBPF_FLAG_FORCE_SHORT_XFER (1 << 0)
+#define USBPF_FLAG_SHORT_XFER_OK (1 << 1)
+#define USBPF_FLAG_SHORT_FRAMES_OK (1 << 2)
+#define USBPF_FLAG_PIPE_BOF (1 << 3)
+#define USBPF_FLAG_PROXY_BUFFER (1 << 4)
+#define USBPF_FLAG_EXT_BUFFER (1 << 5)
+#define USBPF_FLAG_MANUAL_STATUS (1 << 6)
+#define USBPF_FLAG_NO_PIPE_OK (1 << 7)
+#define USBPF_FLAG_STALL_PIPE (1 << 8)
+ uint32_t up_status; /* Transfer status */
+#define USBPF_STATUS_OPEN (1 << 0)
+#define USBPF_STATUS_TRANSFERRING (1 << 1)
+#define USBPF_STATUS_DID_DMA_DELAY (1 << 2)
+#define USBPF_STATUS_DID_CLOSE (1 << 3)
+#define USBPF_STATUS_DRAINING (1 << 4)
+#define USBPF_STATUS_STARTED (1 << 5)
+#define USBPF_STATUS_BW_RECLAIMED (1 << 6)
+#define USBPF_STATUS_CONTROL_XFR (1 << 7)
+#define USBPF_STATUS_CONTROL_HDR (1 << 8)
+#define USBPF_STATUS_CONTROL_ACT (1 << 9)
+#define USBPF_STATUS_CONTROL_STALL (1 << 10)
+#define USBPF_STATUS_SHORT_FRAMES_OK (1 << 11)
+#define USBPF_STATUS_SHORT_XFER_OK (1 << 12)
+#define USBPF_STATUS_BDMA_ENABLE (1 << 13)
+#define USBPF_STATUS_BDMA_NO_POST_SYNC (1 << 14)
+#define USBPF_STATUS_BDMA_SETUP (1 << 15)
+#define USBPF_STATUS_ISOCHRONOUS_XFR (1 << 16)
+#define USBPF_STATUS_CURR_DMA_SET (1 << 17)
+#define USBPF_STATUS_CAN_CANCEL_IMMED (1 << 18)
+#define USBPF_STATUS_DOING_CALLBACK (1 << 19)
+ uint32_t up_error; /* USB error, see USB_ERR_XXX */
+ uint32_t up_interval; /* For interrupt and isoc (ms) */
+ uint32_t up_frames; /* Number of following frames */
+ uint32_t up_packet_size; /* Packet size used */
+ uint32_t up_packet_count; /* Packet count used */
+ uint32_t up_endpoint; /* USB endpoint / stream ID */
+ uint8_t up_speed; /* USB speed, see USB_SPEED_XXX */
+ /* sizeof(struct usbpf_pkthdr) == 128 bytes */
+ uint8_t up_reserved[83];
+};
+
+struct usbpf_framehdr {
+ /*
+ * The frame length field excludes length of frame header and
+ * any alignment.
+ */
+ uint32_t length;
+#define USBPF_FRAME_ALIGN(x) (((x) + 3) & ~3)
+ uint32_t flags;
+#define USBPF_FRAMEFLAG_READ (1 << 0)
+#define USBPF_FRAMEFLAG_DATA_FOLLOWS (1 << 1)
+};
+
+#define USBPF_HDR_LEN 128 /* bytes */
+#define USBPF_FRAME_HDR_LEN 8 /* bytes */
+
+extern uint8_t usbpf_pkthdr_size_ok[
+ (sizeof(struct usbpf_pkthdr) == USBPF_HDR_LEN) ? 1 : -1];
+extern uint8_t usbpf_framehdr_size_ok[
+ (sizeof(struct usbpf_framehdr) == USBPF_FRAME_HDR_LEN) ? 1 : -1];
+
+#define USBPF_XFERTAP_SUBMIT 0
+#define USBPF_XFERTAP_DONE 1
+
+#if defined(_KERNEL) || defined(_STANDALONE)
+void usbpf_attach(struct usb_bus *);
+void usbpf_detach(struct usb_bus *);
+void usbpf_xfertap(struct usb_xfer *, int);
+#endif /* _KERNEL || _STANDALONE */
+#endif /* _DEV_USB_PF_H */
+
diff --git a/sys/dev/usb/usb_process.c b/sys/dev/usb/usb_process.c
new file mode 100644
index 000000000000..4507c999f50a
--- /dev/null
+++ b/sys/dev/usb/usb_process.c
@@ -0,0 +1,536 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usb_process.h>
+
+#define USB_DEBUG_VAR usb_proc_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_util.h>
+
+#include <sys/proc.h>
+#include <sys/kthread.h>
+#include <sys/sched.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+static struct proc *usbproc;
+static int usb_pcount;
+#define USB_THREAD_CREATE(f, s, p, ...) \
+ kproc_kthread_add((f), (s), &usbproc, (p), RFHIGHPID, \
+ 0, "usb", __VA_ARGS__)
+#define USB_THREAD_SUSPEND_CHECK() kthread_suspend_check()
+#define USB_THREAD_SUSPEND(p) kthread_suspend(p,0)
+#define USB_THREAD_EXIT(err) kthread_exit()
+
+#ifdef USB_DEBUG
+static int usb_proc_debug;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, proc, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB process");
+SYSCTL_INT(_hw_usb_proc, OID_AUTO, debug, CTLFLAG_RWTUN, &usb_proc_debug, 0,
+ "Debug level");
+#endif
+
+/*------------------------------------------------------------------------*
+ * usb_process
+ *
+ * This function is the USB process dispatcher.
+ *------------------------------------------------------------------------*/
+static void
+usb_process(void *arg)
+{
+ struct usb_process *up = arg;
+ struct usb_proc_msg *pm;
+ struct thread *td;
+
+ /* in case of attach error, check for suspended */
+ USB_THREAD_SUSPEND_CHECK();
+
+ /* adjust priority */
+ td = curthread;
+ thread_lock(td);
+ sched_prio(td, up->up_prio);
+ thread_unlock(td);
+
+ USB_MTX_LOCK(up->up_mtx);
+
+ up->up_curtd = td;
+
+ while (1) {
+ if (up->up_gone)
+ break;
+
+ /*
+ * NOTE to reimplementors: dequeueing a command from the
+ * "used" queue and executing it must be atomic, with regard
+ * to the "up_mtx" mutex. That means any attempt to queue a
+ * command by another thread must be blocked until either:
+ *
+ * 1) the command sleeps
+ *
+ * 2) the command returns
+ *
+ * Here is a practical example that shows how this helps
+ * solving a problem:
+ *
+ * Assume that you want to set the baud rate on a USB serial
+ * device. During the programming of the device you don't
+ * want to receive nor transmit any data, because it will be
+ * garbage most likely anyway. The programming of our USB
+ * device takes 20 milliseconds and it needs to call
+ * functions that sleep.
+ *
+ * Non-working solution: Before we queue the programming
+ * command, we stop transmission and reception of data. Then
+ * we queue a programming command. At the end of the
+ * programming command we enable transmission and reception
+ * of data.
+ *
+ * Problem: If a second programming command is queued while the
+ * first one is sleeping, we end up enabling transmission
+ * and reception of data too early.
+ *
+ * Working solution: Before we queue the programming command,
+ * we stop transmission and reception of data. Then we queue
+ * a programming command. Then we queue a second command
+ * that only enables transmission and reception of data.
+ *
+ * Why it works: If a second programming command is queued
+ * while the first one is sleeping, then the queueing of a
+ * second command to enable the data transfers, will cause
+ * the previous one, which is still on the queue, to be
+ * removed from the queue, and re-inserted after the last
+ * baud rate programming command, which then gives the
+ * desired result.
+ */
+ pm = TAILQ_FIRST(&up->up_qhead);
+
+ if (pm) {
+ DPRINTF("Message pm=%p, cb=%p (enter)\n",
+ pm, pm->pm_callback);
+
+ (pm->pm_callback) (pm);
+
+ if (pm == TAILQ_FIRST(&up->up_qhead)) {
+ /* nothing changed */
+ TAILQ_REMOVE(&up->up_qhead, pm, pm_qentry);
+ pm->pm_qentry.tqe_prev = NULL;
+ }
+ DPRINTF("Message pm=%p (leave)\n", pm);
+
+ continue;
+ }
+ /* end of messages - check if anyone is waiting for sync */
+ if (up->up_dsleep) {
+ up->up_dsleep = 0;
+ cv_broadcast(&up->up_drain);
+ }
+ up->up_msleep = 1;
+ cv_wait(&up->up_cv, up->up_mtx);
+ }
+
+ up->up_ptr = NULL;
+ cv_signal(&up->up_cv);
+ USB_MTX_UNLOCK(up->up_mtx);
+ /* Clear the proc pointer if this is the last thread. */
+ if (--usb_pcount == 0)
+ usbproc = NULL;
+
+ USB_THREAD_EXIT(0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_proc_create
+ *
+ * This function will create a process using the given "prio" that can
+ * execute callbacks. The mutex pointed to by "p_mtx" will be applied
+ * before calling the callbacks and released after that the callback
+ * has returned. The structure pointed to by "up" is assumed to be
+ * zeroed before this function is called.
+ *
+ * Return values:
+ * 0: success
+ * Else: failure
+ *------------------------------------------------------------------------*/
+int
+usb_proc_create(struct usb_process *up, struct mtx *p_mtx,
+ const char *pmesg, uint8_t prio)
+{
+ up->up_mtx = p_mtx;
+ up->up_prio = prio;
+
+ TAILQ_INIT(&up->up_qhead);
+
+ cv_init(&up->up_cv, "-");
+ cv_init(&up->up_drain, "usbdrain");
+
+ if (USB_THREAD_CREATE(&usb_process, up,
+ &up->up_ptr, "%s", pmesg)) {
+ DPRINTFN(0, "Unable to create USB process.");
+ up->up_ptr = NULL;
+ goto error;
+ }
+ usb_pcount++;
+ return (0);
+
+error:
+ usb_proc_free(up);
+ return (ENOMEM);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_proc_free
+ *
+ * NOTE: If the structure pointed to by "up" is all zero, this
+ * function does nothing.
+ *
+ * NOTE: Messages that are pending on the process queue will not be
+ * removed nor called.
+ *------------------------------------------------------------------------*/
+void
+usb_proc_free(struct usb_process *up)
+{
+ /* check if not initialised */
+ if (up->up_mtx == NULL)
+ return;
+
+ usb_proc_drain(up);
+
+ cv_destroy(&up->up_cv);
+ cv_destroy(&up->up_drain);
+
+ /* make sure that we do not enter here again */
+ up->up_mtx = NULL;
+}
+
+/*------------------------------------------------------------------------*
+ * usb_proc_msignal
+ *
+ * This function will queue one of the passed USB process messages on
+ * the USB process queue. The first message that is not already queued
+ * will get queued. If both messages are already queued the one queued
+ * last will be removed from the queue and queued in the end. The USB
+ * process mutex must be locked when calling this function. This
+ * function exploits the fact that a process can only do one callback
+ * at a time. The message that was queued is returned.
+ *------------------------------------------------------------------------*/
+void *
+usb_proc_msignal(struct usb_process *up, void *_pm0, void *_pm1)
+{
+ struct usb_proc_msg *pm0 = _pm0;
+ struct usb_proc_msg *pm1 = _pm1;
+ struct usb_proc_msg *pm2;
+ usb_size_t d;
+ uint8_t t;
+
+ /* check if gone or in polling mode, return dummy value */
+ if (up->up_gone != 0 ||
+ USB_IN_POLLING_MODE_FUNC() != 0)
+ return (_pm0);
+
+ USB_MTX_ASSERT(up->up_mtx, MA_OWNED);
+
+ t = 0;
+
+ if (pm0->pm_qentry.tqe_prev) {
+ t |= 1;
+ }
+ if (pm1->pm_qentry.tqe_prev) {
+ t |= 2;
+ }
+ if (t == 0) {
+ /*
+ * No entries are queued. Queue "pm0" and use the existing
+ * message number.
+ */
+ pm2 = pm0;
+ } else if (t == 1) {
+ /* Check if we need to increment the message number. */
+ if (pm0->pm_num == up->up_msg_num) {
+ up->up_msg_num++;
+ }
+ pm2 = pm1;
+ } else if (t == 2) {
+ /* Check if we need to increment the message number. */
+ if (pm1->pm_num == up->up_msg_num) {
+ up->up_msg_num++;
+ }
+ pm2 = pm0;
+ } else if (t == 3) {
+ /*
+ * Both entries are queued. Re-queue the entry closest to
+ * the end.
+ */
+ d = (pm1->pm_num - pm0->pm_num);
+
+ /* Check sign after subtraction */
+ if (d & 0x80000000) {
+ pm2 = pm0;
+ } else {
+ pm2 = pm1;
+ }
+
+ TAILQ_REMOVE(&up->up_qhead, pm2, pm_qentry);
+ } else {
+ pm2 = NULL; /* panic - should not happen */
+ }
+
+ DPRINTF(" t=%u, num=%u\n", t, up->up_msg_num);
+
+ /* Put message last on queue */
+
+ pm2->pm_num = up->up_msg_num;
+ TAILQ_INSERT_TAIL(&up->up_qhead, pm2, pm_qentry);
+
+ /* Check if we need to wakeup the USB process. */
+
+ if (up->up_msleep) {
+ up->up_msleep = 0; /* save "cv_signal()" calls */
+ cv_signal(&up->up_cv);
+ }
+ return (pm2);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_proc_is_gone
+ *
+ * Return values:
+ * 0: USB process is running
+ * Else: USB process is tearing down
+ *------------------------------------------------------------------------*/
+uint8_t
+usb_proc_is_gone(struct usb_process *up)
+{
+ if (up->up_gone)
+ return (1);
+
+ /*
+ * Allow calls when up_mtx is NULL, before the USB process
+ * structure is initialised.
+ */
+ if (up->up_mtx != NULL)
+ USB_MTX_ASSERT(up->up_mtx, MA_OWNED);
+ return (0);
+}
+
+static int
+usb_proc_mwait_impl(struct usb_process *up, void *_pm0, void *_pm1,
+ bool interruptible)
+{
+ struct usb_proc_msg *pm0 = _pm0;
+ struct usb_proc_msg *pm1 = _pm1;
+ int error;
+
+ /* check if gone */
+ if (up->up_gone)
+ return (ENXIO);
+
+ USB_MTX_ASSERT(up->up_mtx, MA_OWNED);
+
+ error = 0;
+ if (up->up_curtd == curthread) {
+ /* Just remove the messages from the queue. */
+ if (pm0->pm_qentry.tqe_prev) {
+ TAILQ_REMOVE(&up->up_qhead, pm0, pm_qentry);
+ pm0->pm_qentry.tqe_prev = NULL;
+ }
+ if (pm1->pm_qentry.tqe_prev) {
+ TAILQ_REMOVE(&up->up_qhead, pm1, pm_qentry);
+ pm1->pm_qentry.tqe_prev = NULL;
+ }
+ } else
+ while (error == 0 && (pm0->pm_qentry.tqe_prev ||
+ pm1->pm_qentry.tqe_prev)) {
+ /* check if config thread is gone */
+ if (up->up_gone)
+ return (ENXIO);
+ up->up_dsleep = 1;
+ if (interruptible) {
+ error = cv_wait_sig(&up->up_drain, up->up_mtx);
+
+ /*
+ * The fact that we were interrupted doesn't
+ * matter if our goal was accomplished anyways.
+ */
+ if (error != 0 && !USB_PROC_MSG_ENQUEUED(pm0) &&
+ !USB_PROC_MSG_ENQUEUED(pm1))
+ error = 0;
+ } else {
+ cv_wait(&up->up_drain, up->up_mtx);
+ }
+ }
+
+ if (error == ERESTART)
+ error = EINTR;
+ return (error);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_proc_mwait
+ *
+ * This function will return when the USB process message pointed to
+ * by "pm" is no longer on a queue. This function must be called
+ * having "up->up_mtx" locked.
+ *------------------------------------------------------------------------*/
+void
+usb_proc_mwait(struct usb_process *up, void *_pm0, void *_pm1)
+{
+
+ (void)usb_proc_mwait_impl(up, _pm0, _pm1, false);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_proc_mwait_sig
+ *
+ * This function will return when the USB process message pointed to
+ * by "pm" is no longer on a queue. This function must be called
+ * having "up->up_mtx" locked. This version of usb_proc_mwait is
+ * interruptible.
+ *------------------------------------------------------------------------*/
+int
+usb_proc_mwait_sig(struct usb_process *up, void *_pm0, void *_pm1)
+{
+
+ return (usb_proc_mwait_impl(up, _pm0, _pm1, true));
+}
+
+/*------------------------------------------------------------------------*
+ * usb_proc_drain
+ *
+ * This function will tear down an USB process, waiting for the
+ * currently executing command to return.
+ *
+ * NOTE: If the structure pointed to by "up" is all zero,
+ * this function does nothing.
+ *------------------------------------------------------------------------*/
+void
+usb_proc_drain(struct usb_process *up)
+{
+ /* check if not initialised */
+ if (up->up_mtx == NULL)
+ return;
+ /* handle special case with Giant */
+ if (up->up_mtx != &Giant)
+ USB_MTX_ASSERT(up->up_mtx, MA_NOTOWNED);
+
+ USB_MTX_LOCK(up->up_mtx);
+
+ /* Set the gone flag */
+
+ up->up_gone = 1;
+
+ while (up->up_ptr) {
+ /* Check if we need to wakeup the USB process */
+
+ if (up->up_msleep || up->up_csleep) {
+ up->up_msleep = 0;
+ up->up_csleep = 0;
+ cv_signal(&up->up_cv);
+ }
+#ifndef EARLY_AP_STARTUP
+ /* Check if we are still cold booted */
+ if (cold) {
+ USB_THREAD_SUSPEND(up->up_ptr);
+ printf("WARNING: A USB process has "
+ "been left suspended\n");
+ break;
+ }
+#endif
+ cv_wait(&up->up_cv, up->up_mtx);
+ }
+ /* Check if someone is waiting - should not happen */
+
+ if (up->up_dsleep) {
+ up->up_dsleep = 0;
+ cv_broadcast(&up->up_drain);
+ DPRINTF("WARNING: Someone is waiting "
+ "for USB process drain!\n");
+ }
+ USB_MTX_UNLOCK(up->up_mtx);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_proc_rewakeup
+ *
+ * This function is called to re-wakeup the given USB
+ * process. This usually happens after that the USB system has been in
+ * polling mode, like during a panic. This function must be called
+ * having "up->up_mtx" locked.
+ *------------------------------------------------------------------------*/
+void
+usb_proc_rewakeup(struct usb_process *up)
+{
+ /* check if not initialised */
+ if (up->up_mtx == NULL)
+ return;
+ /* check if gone */
+ if (up->up_gone)
+ return;
+
+ USB_MTX_ASSERT(up->up_mtx, MA_OWNED);
+
+ if (up->up_msleep == 0) {
+ /* re-wakeup */
+ cv_signal(&up->up_cv);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_proc_is_called_from
+ *
+ * This function will return non-zero if called from inside the USB
+ * process passed as first argument. Else this function returns zero.
+ *------------------------------------------------------------------------*/
+int
+usb_proc_is_called_from(struct usb_process *up)
+{
+ return (up->up_curtd == curthread);
+}
diff --git a/sys/dev/usb/usb_process.h b/sys/dev/usb/usb_process.h
new file mode 100644
index 000000000000..1962bdb8b607
--- /dev/null
+++ b/sys/dev/usb/usb_process.h
@@ -0,0 +1,89 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_PROCESS_H_
+#define _USB_PROCESS_H_
+
+#ifndef USB_GLOBAL_INCLUDE_FILE
+#include <sys/interrupt.h>
+#include <sys/priority.h>
+#endif
+
+/* defines */
+#define USB_PRI_HIGHEST PI_SWI(SWI_TTY)
+#define USB_PRI_HIGH PI_SWI(SWI_NET)
+#define USB_PRI_MED PI_SWI(SWI_CAMBIO)
+
+#define USB_PROC_WAIT_TIMEOUT 2
+#define USB_PROC_WAIT_DRAIN 1
+#define USB_PROC_WAIT_NORMAL 0
+
+/* structure prototypes */
+
+struct usb_proc_msg;
+struct usb_device;
+
+/*
+ * The following structure defines the USB process.
+ */
+struct usb_process {
+ TAILQ_HEAD(, usb_proc_msg) up_qhead;
+ struct cv up_cv;
+ struct cv up_drain;
+
+ struct thread *up_ptr;
+ struct thread *up_curtd;
+ struct mtx *up_mtx;
+
+ usb_size_t up_msg_num;
+
+ uint8_t up_prio;
+ uint8_t up_gone;
+ uint8_t up_msleep;
+ uint8_t up_csleep;
+ uint8_t up_dsleep;
+};
+
+/* prototypes */
+
+uint8_t usb_proc_is_gone(struct usb_process *up);
+int usb_proc_create(struct usb_process *up, struct mtx *p_mtx,
+ const char *pmesg, uint8_t prio);
+void usb_proc_drain(struct usb_process *up);
+void usb_proc_mwait(struct usb_process *up, void *pm0, void *pm1);
+int usb_proc_mwait_sig(struct usb_process *up, void *pm0, void *pm1);
+void usb_proc_free(struct usb_process *up);
+void *usb_proc_msignal(struct usb_process *up, void *pm0, void *pm1);
+void usb_proc_rewakeup(struct usb_process *up);
+int usb_proc_is_called_from(struct usb_process *up);
+
+void usb_proc_explore_mwait(struct usb_device *, void *, void *);
+void *usb_proc_explore_msignal(struct usb_device *, void *, void *);
+void usb_proc_explore_lock(struct usb_device *);
+void usb_proc_explore_unlock(struct usb_device *);
+
+#endif /* _USB_PROCESS_H_ */
diff --git a/sys/dev/usb/usb_request.c b/sys/dev/usb/usb_request.c
new file mode 100644
index 000000000000..55e5f04194d9
--- /dev/null
+++ b/sys/dev/usb/usb_request.c
@@ -0,0 +1,2345 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
+ * Copyright (c) 2008-2020 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+
+#define USB_DEBUG_VAR usb_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_request.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/usb_dynamic.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <sys/ctype.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+static int usb_no_cs_fail;
+
+SYSCTL_INT(_hw_usb, OID_AUTO, no_cs_fail, CTLFLAG_RWTUN,
+ &usb_no_cs_fail, 0, "USB clear stall failures are ignored, if set");
+
+static int usb_full_ddesc;
+
+SYSCTL_INT(_hw_usb, OID_AUTO, full_ddesc, CTLFLAG_RWTUN,
+ &usb_full_ddesc, 0, "USB always read complete device descriptor, if set");
+
+#ifdef USB_DEBUG
+#ifdef USB_REQ_DEBUG
+/* The following structures are used in connection to fault injection. */
+struct usb_ctrl_debug {
+ int bus_index; /* target bus */
+ int dev_index; /* target address */
+ int ds_fail; /* fail data stage */
+ int ss_fail; /* fail status stage */
+ int ds_delay; /* data stage delay in ms */
+ int ss_delay; /* status stage delay in ms */
+ int bmRequestType_value;
+ int bRequest_value;
+};
+
+struct usb_ctrl_debug_bits {
+ uint16_t ds_delay;
+ uint16_t ss_delay;
+ uint8_t ds_fail:1;
+ uint8_t ss_fail:1;
+ uint8_t enabled:1;
+};
+
+/* The default is to disable fault injection. */
+
+static struct usb_ctrl_debug usb_ctrl_debug = {
+ .bus_index = -1,
+ .dev_index = -1,
+ .bmRequestType_value = -1,
+ .bRequest_value = -1,
+};
+
+SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_bus_fail, CTLFLAG_RWTUN,
+ &usb_ctrl_debug.bus_index, 0, "USB controller index to fail");
+SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_dev_fail, CTLFLAG_RWTUN,
+ &usb_ctrl_debug.dev_index, 0, "USB device address to fail");
+SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_ds_fail, CTLFLAG_RWTUN,
+ &usb_ctrl_debug.ds_fail, 0, "USB fail data stage");
+SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_ss_fail, CTLFLAG_RWTUN,
+ &usb_ctrl_debug.ss_fail, 0, "USB fail status stage");
+SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_ds_delay, CTLFLAG_RWTUN,
+ &usb_ctrl_debug.ds_delay, 0, "USB data stage delay in ms");
+SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_ss_delay, CTLFLAG_RWTUN,
+ &usb_ctrl_debug.ss_delay, 0, "USB status stage delay in ms");
+SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_rt_fail, CTLFLAG_RWTUN,
+ &usb_ctrl_debug.bmRequestType_value, 0, "USB bmRequestType to fail");
+SYSCTL_INT(_hw_usb, OID_AUTO, ctrl_rv_fail, CTLFLAG_RWTUN,
+ &usb_ctrl_debug.bRequest_value, 0, "USB bRequest to fail");
+
+/*------------------------------------------------------------------------*
+ * usbd_get_debug_bits
+ *
+ * This function is only useful in USB host mode.
+ *------------------------------------------------------------------------*/
+static void
+usbd_get_debug_bits(struct usb_device *udev, struct usb_device_request *req,
+ struct usb_ctrl_debug_bits *dbg)
+{
+ int temp;
+
+ memset(dbg, 0, sizeof(*dbg));
+
+ /* Compute data stage delay */
+
+ temp = usb_ctrl_debug.ds_delay;
+ if (temp < 0)
+ temp = 0;
+ else if (temp > (16*1024))
+ temp = (16*1024);
+
+ dbg->ds_delay = temp;
+
+ /* Compute status stage delay */
+
+ temp = usb_ctrl_debug.ss_delay;
+ if (temp < 0)
+ temp = 0;
+ else if (temp > (16*1024))
+ temp = (16*1024);
+
+ dbg->ss_delay = temp;
+
+ /* Check if this control request should be failed */
+
+ if (usbd_get_bus_index(udev) != usb_ctrl_debug.bus_index)
+ return;
+
+ if (usbd_get_device_index(udev) != usb_ctrl_debug.dev_index)
+ return;
+
+ temp = usb_ctrl_debug.bmRequestType_value;
+
+ if ((temp != req->bmRequestType) && (temp >= 0) && (temp <= 255))
+ return;
+
+ temp = usb_ctrl_debug.bRequest_value;
+
+ if ((temp != req->bRequest) && (temp >= 0) && (temp <= 255))
+ return;
+
+ temp = usb_ctrl_debug.ds_fail;
+ if (temp)
+ dbg->ds_fail = 1;
+
+ temp = usb_ctrl_debug.ss_fail;
+ if (temp)
+ dbg->ss_fail = 1;
+
+ dbg->enabled = 1;
+}
+#endif /* USB_REQ_DEBUG */
+#endif /* USB_DEBUG */
+
+/*------------------------------------------------------------------------*
+ * usbd_do_request_callback
+ *
+ * This function is the USB callback for generic USB Host control
+ * transfers.
+ *------------------------------------------------------------------------*/
+void
+usbd_do_request_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ ; /* workaround for a bug in "indent" */
+
+ DPRINTF("st=%u\n", USB_GET_STATE(xfer));
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ cv_signal(&xfer->xroot->udev->ctrlreq_cv);
+ break;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_do_clear_stall_callback
+ *
+ * This function is the USB callback for generic clear stall requests.
+ *------------------------------------------------------------------------*/
+void
+usb_do_clear_stall_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usb_device_request req;
+ struct usb_device *udev;
+ struct usb_endpoint *ep;
+ struct usb_endpoint *ep_end;
+ struct usb_endpoint *ep_first;
+ usb_stream_t x;
+ uint8_t to;
+
+ udev = xfer->xroot->udev;
+
+ USB_BUS_LOCK(udev->bus);
+
+ /* round robin endpoint clear stall */
+
+ ep = udev->ep_curr;
+ ep_end = udev->endpoints + udev->endpoints_max;
+ ep_first = udev->endpoints;
+ to = udev->endpoints_max;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+tr_transferred:
+ /* reset error counter */
+ udev->clear_stall_errors = 0;
+
+ if (ep == NULL)
+ goto tr_setup; /* device was unconfigured */
+ if (ep->edesc &&
+ ep->is_stalled) {
+ ep->toggle_next = 0;
+ ep->is_stalled = 0;
+ /* some hardware needs a callback to clear the data toggle */
+ usbd_clear_stall_locked(udev, ep);
+ for (x = 0; x != USB_MAX_EP_STREAMS; x++) {
+ /* start the current or next transfer, if any */
+ usb_command_wrapper(&ep->endpoint_q[x],
+ ep->endpoint_q[x].curr);
+ }
+ }
+ ep++;
+
+ case USB_ST_SETUP:
+tr_setup:
+ if (to == 0)
+ break; /* no endpoints - nothing to do */
+ if ((ep < ep_first) || (ep >= ep_end))
+ ep = ep_first; /* endpoint wrapped around */
+ if (ep->edesc &&
+ ep->is_stalled) {
+ /* setup a clear-stall packet */
+
+ req.bmRequestType = UT_WRITE_ENDPOINT;
+ req.bRequest = UR_CLEAR_FEATURE;
+ USETW(req.wValue, UF_ENDPOINT_HALT);
+ req.wIndex[0] = ep->edesc->bEndpointAddress;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ /* copy in the transfer */
+
+ usbd_copy_in(xfer->frbuffers, 0, &req, sizeof(req));
+
+ /* set length */
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+ xfer->nframes = 1;
+ USB_BUS_UNLOCK(udev->bus);
+
+ usbd_transfer_submit(xfer);
+
+ USB_BUS_LOCK(udev->bus);
+ break;
+ }
+ ep++;
+ to--;
+ goto tr_setup;
+
+ default:
+ if (error == USB_ERR_CANCELLED)
+ break;
+
+ DPRINTF("Clear stall failed.\n");
+
+ /*
+ * Some VMs like VirtualBox always return failure on
+ * clear-stall which we sometimes should just ignore.
+ */
+ if (usb_no_cs_fail)
+ goto tr_transferred;
+
+ /*
+ * Some non-compliant USB devices do not implement the
+ * clear endpoint halt feature. Silently ignore such
+ * devices, when they at least respond correctly
+ * passing up a valid STALL PID packet.
+ */
+ if (error == USB_ERR_STALLED)
+ goto tr_transferred;
+
+ if (udev->clear_stall_errors == USB_CS_RESET_LIMIT)
+ goto tr_setup;
+
+ if (error == USB_ERR_TIMEOUT) {
+ udev->clear_stall_errors = USB_CS_RESET_LIMIT;
+ DPRINTF("Trying to re-enumerate.\n");
+ usbd_start_re_enumerate(udev);
+ } else {
+ udev->clear_stall_errors++;
+ if (udev->clear_stall_errors == USB_CS_RESET_LIMIT) {
+ DPRINTF("Trying to re-enumerate.\n");
+ usbd_start_re_enumerate(udev);
+ }
+ }
+ goto tr_setup;
+ }
+
+ /* store current endpoint */
+ udev->ep_curr = ep;
+ USB_BUS_UNLOCK(udev->bus);
+}
+
+static usb_handle_req_t *
+usbd_get_hr_func(struct usb_device *udev)
+{
+ /* figure out if there is a Handle Request function */
+ if (udev->flags.usb_mode == USB_MODE_DEVICE)
+ return (usb_temp_get_desc_p);
+ else if (udev->parent_hub == NULL)
+ return (udev->bus->methods->roothub_exec);
+ else
+ return (NULL);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_do_request_flags and usbd_do_request
+ *
+ * Description of arguments passed to these functions:
+ *
+ * "udev" - this is the "usb_device" structure pointer on which the
+ * request should be performed. It is possible to call this function
+ * in both Host Side mode and Device Side mode.
+ *
+ * "mtx" - if this argument is non-NULL the mutex pointed to by it
+ * will get dropped and picked up during the execution of this
+ * function, hence this function sometimes needs to sleep. If this
+ * argument is NULL it has no effect.
+ *
+ * "req" - this argument must always be non-NULL and points to an
+ * 8-byte structure holding the USB request to be done. The USB
+ * request structure has a bit telling the direction of the USB
+ * request, if it is a read or a write.
+ *
+ * "data" - if the "wLength" part of the structure pointed to by "req"
+ * is non-zero this argument must point to a valid kernel buffer which
+ * can hold at least "wLength" bytes. If "wLength" is zero "data" can
+ * be NULL.
+ *
+ * "flags" - here is a list of valid flags:
+ *
+ * o USB_SHORT_XFER_OK: allows the data transfer to be shorter than
+ * specified
+ *
+ * o USB_DELAY_STATUS_STAGE: allows the status stage to be performed
+ * at a later point in time. This is tunable by the "hw.usb.ss_delay"
+ * sysctl. This flag is mostly useful for debugging.
+ *
+ * o USB_USER_DATA_PTR: treat the "data" pointer like a userland
+ * pointer.
+ *
+ * "actlen" - if non-NULL the actual transfer length will be stored in
+ * the 16-bit unsigned integer pointed to by "actlen". This
+ * information is mostly useful when the "USB_SHORT_XFER_OK" flag is
+ * used.
+ *
+ * "timeout" - gives the timeout for the control transfer in
+ * milliseconds. A "timeout" value less than 50 milliseconds is
+ * treated like a 50 millisecond timeout. A "timeout" value greater
+ * than 30 seconds is treated like a 30 second timeout. This USB stack
+ * does not allow control requests without a timeout.
+ *
+ * NOTE: This function is thread safe. All calls to "usbd_do_request_flags"
+ * will be serialized by the use of the USB device enumeration lock.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_do_request_flags(struct usb_device *udev, struct mtx *mtx,
+ struct usb_device_request *req, void *data, uint16_t flags,
+ uint16_t *actlen, usb_timeout_t timeout)
+{
+#ifdef USB_REQ_DEBUG
+ struct usb_ctrl_debug_bits dbg;
+#endif
+ usb_handle_req_t *hr_func;
+ struct usb_xfer *xfer;
+ const void *desc;
+ int err = 0;
+ usb_ticks_t start_ticks;
+ usb_ticks_t delta_ticks;
+ usb_ticks_t max_ticks;
+ uint16_t length;
+ uint16_t temp;
+ uint16_t acttemp;
+ uint8_t do_unlock;
+
+ if (timeout < 50) {
+ /* timeout is too small */
+ timeout = 50;
+ }
+ if (timeout > 30000) {
+ /* timeout is too big */
+ timeout = 30000;
+ }
+ length = UGETW(req->wLength);
+
+ DPRINTFN(5, "udev=%p bmRequestType=0x%02x bRequest=0x%02x "
+ "wValue=0x%02x%02x wIndex=0x%02x%02x wLength=0x%02x%02x\n",
+ udev, req->bmRequestType, req->bRequest,
+ req->wValue[1], req->wValue[0],
+ req->wIndex[1], req->wIndex[0],
+ req->wLength[1], req->wLength[0]);
+
+ /* Check if the device is still alive */
+ if (udev->state < USB_STATE_POWERED) {
+ DPRINTF("usb device has gone\n");
+ return (USB_ERR_NOT_CONFIGURED);
+ }
+
+ /*
+ * Set "actlen" to a known value in case the caller does not
+ * check the return value:
+ */
+ if (actlen)
+ *actlen = 0;
+
+#if (USB_HAVE_USER_IO == 0)
+ if (flags & USB_USER_DATA_PTR)
+ return (USB_ERR_INVAL);
+#endif
+ if ((mtx != NULL) && (mtx != &Giant)) {
+ USB_MTX_UNLOCK(mtx);
+ USB_MTX_ASSERT(mtx, MA_NOTOWNED);
+ }
+
+ /*
+ * Serialize access to this function:
+ */
+ do_unlock = usbd_ctrl_lock(udev);
+
+ hr_func = usbd_get_hr_func(udev);
+
+ if (hr_func != NULL) {
+ DPRINTF("Handle Request function is set\n");
+
+ desc = NULL;
+ temp = 0;
+
+ if (!(req->bmRequestType & UT_READ)) {
+ if (length != 0) {
+ DPRINTFN(1, "The handle request function "
+ "does not support writing data!\n");
+ err = USB_ERR_INVAL;
+ goto done;
+ }
+ }
+
+ /* The root HUB code needs the BUS lock locked */
+
+ USB_BUS_LOCK(udev->bus);
+ err = (hr_func) (udev, req, &desc, &temp);
+ USB_BUS_UNLOCK(udev->bus);
+
+ if (err)
+ goto done;
+
+ if (length > temp) {
+ if (!(flags & USB_SHORT_XFER_OK)) {
+ err = USB_ERR_SHORT_XFER;
+ goto done;
+ }
+ length = temp;
+ }
+ if (actlen)
+ *actlen = length;
+
+ if (length > 0) {
+#if USB_HAVE_USER_IO
+ if (flags & USB_USER_DATA_PTR) {
+ if (copyout(desc, data, length)) {
+ err = USB_ERR_INVAL;
+ goto done;
+ }
+ } else
+#endif
+ memcpy(data, desc, length);
+ }
+ goto done; /* success */
+ }
+
+ /*
+ * Setup a new USB transfer or use the existing one, if any:
+ */
+ usbd_ctrl_transfer_setup(udev);
+
+ xfer = udev->ctrl_xfer[0];
+ if (xfer == NULL) {
+ /* most likely out of memory */
+ err = USB_ERR_NOMEM;
+ goto done;
+ }
+
+#ifdef USB_REQ_DEBUG
+ /* Get debug bits */
+ usbd_get_debug_bits(udev, req, &dbg);
+
+ /* Check for fault injection */
+ if (dbg.enabled)
+ flags |= USB_DELAY_STATUS_STAGE;
+#endif
+ USB_XFER_LOCK(xfer);
+
+ if (flags & USB_DELAY_STATUS_STAGE)
+ xfer->flags.manual_status = 1;
+ else
+ xfer->flags.manual_status = 0;
+
+ if (flags & USB_SHORT_XFER_OK)
+ xfer->flags.short_xfer_ok = 1;
+ else
+ xfer->flags.short_xfer_ok = 0;
+
+ xfer->timeout = timeout;
+
+ start_ticks = ticks;
+
+ max_ticks = USB_MS_TO_TICKS(timeout);
+
+ usbd_copy_in(xfer->frbuffers, 0, req, sizeof(*req));
+
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(*req));
+
+ while (1) {
+ temp = length;
+ if (temp > usbd_xfer_max_len(xfer)) {
+ temp = usbd_xfer_max_len(xfer);
+ }
+#ifdef USB_REQ_DEBUG
+ if (xfer->flags.manual_status) {
+ if (usbd_xfer_frame_len(xfer, 0) != 0) {
+ /* Execute data stage separately */
+ temp = 0;
+ } else if (temp > 0) {
+ if (dbg.ds_fail) {
+ err = USB_ERR_INVAL;
+ break;
+ }
+ if (dbg.ds_delay > 0) {
+ usb_pause_mtx(
+ xfer->xroot->xfer_mtx,
+ USB_MS_TO_TICKS(dbg.ds_delay));
+ /* make sure we don't time out */
+ start_ticks = ticks;
+ }
+ }
+ }
+#endif
+ usbd_xfer_set_frame_len(xfer, 1, temp);
+
+ if (temp > 0) {
+ if (!(req->bmRequestType & UT_READ)) {
+#if USB_HAVE_USER_IO
+ if (flags & USB_USER_DATA_PTR) {
+ USB_XFER_UNLOCK(xfer);
+ err = usbd_copy_in_user(xfer->frbuffers + 1,
+ 0, data, temp);
+ USB_XFER_LOCK(xfer);
+ if (err) {
+ err = USB_ERR_INVAL;
+ break;
+ }
+ } else
+#endif
+ usbd_copy_in(xfer->frbuffers + 1,
+ 0, data, temp);
+ }
+ usbd_xfer_set_frames(xfer, 2);
+ } else {
+ if (usbd_xfer_frame_len(xfer, 0) == 0) {
+ if (xfer->flags.manual_status) {
+#ifdef USB_REQ_DEBUG
+ if (dbg.ss_fail) {
+ err = USB_ERR_INVAL;
+ break;
+ }
+ if (dbg.ss_delay > 0) {
+ usb_pause_mtx(
+ xfer->xroot->xfer_mtx,
+ USB_MS_TO_TICKS(dbg.ss_delay));
+ /* make sure we don't time out */
+ start_ticks = ticks;
+ }
+#endif
+ xfer->flags.manual_status = 0;
+ } else {
+ break;
+ }
+ }
+ usbd_xfer_set_frames(xfer, 1);
+ }
+
+ usbd_transfer_start(xfer);
+
+ while (usbd_transfer_pending(xfer)) {
+ cv_wait(&udev->ctrlreq_cv,
+ xfer->xroot->xfer_mtx);
+ }
+
+ err = xfer->error;
+
+ if (err) {
+ break;
+ }
+
+ /* get actual length of DATA stage */
+
+ if (xfer->aframes < 2) {
+ acttemp = 0;
+ } else {
+ acttemp = usbd_xfer_frame_len(xfer, 1);
+ }
+
+ /* check for short packet */
+
+ if (temp > acttemp) {
+ temp = acttemp;
+ length = temp;
+ }
+ if (temp > 0) {
+ if (req->bmRequestType & UT_READ) {
+#if USB_HAVE_USER_IO
+ if (flags & USB_USER_DATA_PTR) {
+ USB_XFER_UNLOCK(xfer);
+ err = usbd_copy_out_user(xfer->frbuffers + 1,
+ 0, data, temp);
+ USB_XFER_LOCK(xfer);
+ if (err) {
+ err = USB_ERR_INVAL;
+ break;
+ }
+ } else
+#endif
+ usbd_copy_out(xfer->frbuffers + 1,
+ 0, data, temp);
+ }
+ }
+ /*
+ * Clear "frlengths[0]" so that we don't send the setup
+ * packet again:
+ */
+ usbd_xfer_set_frame_len(xfer, 0, 0);
+
+ /* update length and data pointer */
+ length -= temp;
+ data = USB_ADD_BYTES(data, temp);
+
+ if (actlen) {
+ (*actlen) += temp;
+ }
+ /* check for timeout */
+
+ delta_ticks = ticks - start_ticks;
+ if (delta_ticks > max_ticks) {
+ if (!err) {
+ err = USB_ERR_TIMEOUT;
+ }
+ }
+ if (err) {
+ break;
+ }
+ }
+
+ if (err) {
+ /*
+ * Make sure that the control endpoint is no longer
+ * blocked in case of a non-transfer related error:
+ */
+ usbd_transfer_stop(xfer);
+ }
+ USB_XFER_UNLOCK(xfer);
+
+done:
+ if (do_unlock)
+ usbd_ctrl_unlock(udev);
+
+ if ((mtx != NULL) && (mtx != &Giant))
+ USB_MTX_LOCK(mtx);
+
+ switch (err) {
+ case USB_ERR_NORMAL_COMPLETION:
+ case USB_ERR_SHORT_XFER:
+ case USB_ERR_STALLED:
+ case USB_ERR_CANCELLED:
+ break;
+ default:
+ DPRINTF("error=%s - waiting a bit for TT cleanup\n",
+ usbd_errstr(err));
+ usb_pause_mtx(mtx, hz / 16);
+ break;
+ }
+ return ((usb_error_t)err);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_do_request_proc - factored out code
+ *
+ * This function is factored out code. It does basically the same like
+ * usbd_do_request_flags, except it will check the status of the
+ * passed process argument before doing the USB request. If the
+ * process is draining the USB_ERR_IOERROR code will be returned. It
+ * is assumed that the mutex associated with the process is locked
+ * when calling this function.
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_do_request_proc(struct usb_device *udev, struct usb_process *pproc,
+ struct usb_device_request *req, void *data, uint16_t flags,
+ uint16_t *actlen, usb_timeout_t timeout)
+{
+ usb_error_t err;
+ uint16_t len;
+
+ /* get request data length */
+ len = UGETW(req->wLength);
+
+ /* check if the device is being detached */
+ if (usb_proc_is_gone(pproc)) {
+ err = USB_ERR_IOERROR;
+ goto done;
+ }
+
+ /* forward the USB request */
+ err = usbd_do_request_flags(udev, pproc->up_mtx,
+ req, data, flags, actlen, timeout);
+
+done:
+ /* on failure we zero the data */
+ /* on short packet we zero the unused data */
+ if ((len != 0) && (req->bmRequestType & UE_DIR_IN)) {
+ if (err)
+ memset(data, 0, len);
+ else if (actlen && *actlen != len)
+ memset(((uint8_t *)data) + *actlen, 0, len - *actlen);
+ }
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_reset_port
+ *
+ * This function will instruct a USB HUB to perform a reset sequence
+ * on the specified port number.
+ *
+ * Returns:
+ * 0: Success. The USB device should now be at address zero.
+ * Else: Failure. No USB device is present and the USB port should be
+ * disabled.
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_reset_port(struct usb_device *udev, struct mtx *mtx, uint8_t port)
+{
+ struct usb_port_status ps;
+ usb_error_t err;
+ uint16_t n;
+ uint16_t status;
+ uint16_t change;
+
+ DPRINTF("\n");
+
+ /* clear any leftover port reset changes first */
+ usbd_req_clear_port_feature(
+ udev, mtx, port, UHF_C_PORT_RESET);
+
+ /* assert port reset on the given port */
+ err = usbd_req_set_port_feature(
+ udev, mtx, port, UHF_PORT_RESET);
+
+ /* check for errors */
+ if (err)
+ goto done;
+ n = 0;
+ while (1) {
+ /* wait for the device to recover from reset */
+ usb_pause_mtx(mtx, USB_MS_TO_TICKS(usb_port_reset_delay));
+ n += usb_port_reset_delay;
+ err = usbd_req_get_port_status(udev, mtx, &ps, port);
+ if (err)
+ goto done;
+
+ status = UGETW(ps.wPortStatus);
+ change = UGETW(ps.wPortChange);
+
+ /* if the device disappeared, just give up */
+ if (!(status & UPS_CURRENT_CONNECT_STATUS))
+ goto done;
+
+ /* check if reset is complete */
+ if (change & UPS_C_PORT_RESET)
+ break;
+
+ /*
+ * Some Virtual Machines like VirtualBox 4.x fail to
+ * generate a port reset change event. Check if reset
+ * is no longer asserted.
+ */
+ if (!(status & UPS_RESET))
+ break;
+
+ /* check for timeout */
+ if (n > 1000) {
+ n = 0;
+ break;
+ }
+ }
+
+ /* clear port reset first */
+ err = usbd_req_clear_port_feature(
+ udev, mtx, port, UHF_C_PORT_RESET);
+ if (err)
+ goto done;
+
+ /* check for timeout */
+ if (n == 0) {
+ err = USB_ERR_TIMEOUT;
+ goto done;
+ }
+ /* wait for the device to recover from reset */
+ usb_pause_mtx(mtx, USB_MS_TO_TICKS(usb_port_reset_recovery));
+
+done:
+ DPRINTFN(2, "port %d reset returning error=%s\n",
+ port, usbd_errstr(err));
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_warm_reset_port
+ *
+ * This function will instruct an USB HUB to perform a warm reset
+ * sequence on the specified port number. This kind of reset is not
+ * mandatory for LOW-, FULL- and HIGH-speed USB HUBs and is targeted
+ * for SUPER-speed USB HUBs.
+ *
+ * Returns:
+ * 0: Success. The USB device should now be available again.
+ * Else: Failure. No USB device is present and the USB port should be
+ * disabled.
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_warm_reset_port(struct usb_device *udev, struct mtx *mtx,
+ uint8_t port)
+{
+ struct usb_port_status ps;
+ usb_error_t err;
+ uint16_t n;
+ uint16_t status;
+ uint16_t change;
+
+ DPRINTF("\n");
+
+ err = usbd_req_get_port_status(udev, mtx, &ps, port);
+ if (err)
+ goto done;
+
+ status = UGETW(ps.wPortStatus);
+
+ switch (UPS_PORT_LINK_STATE_GET(status)) {
+ case UPS_PORT_LS_U3:
+ case UPS_PORT_LS_COMP_MODE:
+ case UPS_PORT_LS_LOOPBACK:
+ case UPS_PORT_LS_SS_INA:
+ break;
+ default:
+ DPRINTF("Wrong state for warm reset\n");
+ return (0);
+ }
+
+ /* clear any leftover warm port reset changes first */
+ usbd_req_clear_port_feature(udev, mtx,
+ port, UHF_C_BH_PORT_RESET);
+
+ /* set warm port reset */
+ err = usbd_req_set_port_feature(udev, mtx,
+ port, UHF_BH_PORT_RESET);
+ if (err)
+ goto done;
+
+ n = 0;
+ while (1) {
+ /* wait for the device to recover from reset */
+ usb_pause_mtx(mtx, USB_MS_TO_TICKS(usb_port_reset_delay));
+ n += usb_port_reset_delay;
+ err = usbd_req_get_port_status(udev, mtx, &ps, port);
+ if (err)
+ goto done;
+
+ status = UGETW(ps.wPortStatus);
+ change = UGETW(ps.wPortChange);
+
+ /* if the device disappeared, just give up */
+ if (!(status & UPS_CURRENT_CONNECT_STATUS))
+ goto done;
+
+ /* check if reset is complete */
+ if (change & UPS_C_BH_PORT_RESET)
+ break;
+
+ /* check for timeout */
+ if (n > 1000) {
+ n = 0;
+ break;
+ }
+ }
+
+ /* clear port reset first */
+ err = usbd_req_clear_port_feature(
+ udev, mtx, port, UHF_C_BH_PORT_RESET);
+ if (err)
+ goto done;
+
+ /* check for timeout */
+ if (n == 0) {
+ err = USB_ERR_TIMEOUT;
+ goto done;
+ }
+ /* wait for the device to recover from reset */
+ usb_pause_mtx(mtx, USB_MS_TO_TICKS(usb_port_reset_recovery));
+
+done:
+ DPRINTFN(2, "port %d warm reset returning error=%s\n",
+ port, usbd_errstr(err));
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_desc
+ *
+ * This function can be used to retrieve USB descriptors. It contains
+ * some additional logic like zeroing of missing descriptor bytes and
+ * retrying an USB descriptor in case of failure. The "min_len"
+ * argument specifies the minimum descriptor length. The "max_len"
+ * argument specifies the maximum descriptor length. If the real
+ * descriptor length is less than the minimum length the missing
+ * byte(s) will be zeroed. The type field, the second byte of the USB
+ * descriptor, will get forced to the correct type. If the "actlen"
+ * pointer is non-NULL, the actual length of the transfer will get
+ * stored in the 16-bit unsigned integer which it is pointing to. The
+ * first byte of the descriptor will not get updated. If the "actlen"
+ * pointer is NULL the first byte of the descriptor will get updated
+ * to reflect the actual length instead. If "min_len" is not equal to
+ * "max_len" then this function will try to retrive the beginning of
+ * the descriptor and base the maximum length on the first byte of the
+ * descriptor.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_desc(struct usb_device *udev,
+ struct mtx *mtx, uint16_t *actlen, void *desc,
+ uint16_t min_len, uint16_t max_len,
+ uint16_t id, uint8_t type, uint8_t index,
+ uint8_t retries)
+{
+ struct usb_device_request req;
+ uint8_t *buf = desc;
+ usb_error_t err;
+
+ DPRINTFN(4, "id=%d, type=%d, index=%d, max_len=%d\n",
+ id, type, index, max_len);
+
+ req.bmRequestType = UT_READ_DEVICE;
+ req.bRequest = UR_GET_DESCRIPTOR;
+ USETW2(req.wValue, type, index);
+ USETW(req.wIndex, id);
+
+ while (1) {
+ if ((min_len < 2) || (max_len < 2)) {
+ err = USB_ERR_INVAL;
+ goto done;
+ }
+ USETW(req.wLength, min_len);
+
+ err = usbd_do_request_flags(udev, mtx, &req,
+ desc, 0, NULL, 1000 /* ms */);
+
+ if (err != 0 && err != USB_ERR_TIMEOUT &&
+ min_len != max_len) {
+ /* clear descriptor data */
+ memset(desc, 0, max_len);
+
+ /* try to read full descriptor length */
+ USETW(req.wLength, max_len);
+
+ err = usbd_do_request_flags(udev, mtx, &req,
+ desc, USB_SHORT_XFER_OK, NULL, 1000 /* ms */);
+
+ if (err == 0) {
+ /* verify length */
+ if (buf[0] > max_len)
+ buf[0] = max_len;
+ else if (buf[0] < 2)
+ err = USB_ERR_INVAL;
+
+ min_len = buf[0];
+
+ /* enforce descriptor type */
+ buf[1] = type;
+ goto done;
+ }
+ }
+
+ if (err) {
+ if (!retries) {
+ goto done;
+ }
+ retries--;
+
+ usb_pause_mtx(mtx, hz / 5);
+
+ continue;
+ }
+
+ if (min_len == max_len) {
+ /* enforce correct length */
+ if ((buf[0] > min_len) && (actlen == NULL))
+ buf[0] = min_len;
+
+ /* enforce correct type */
+ buf[1] = type;
+
+ goto done;
+ }
+ /* range check */
+
+ if (max_len > buf[0]) {
+ max_len = buf[0];
+ }
+ /* zero minimum data */
+
+ while (min_len > max_len) {
+ min_len--;
+ buf[min_len] = 0;
+ }
+
+ /* set new minimum length */
+
+ min_len = max_len;
+ }
+done:
+ if (actlen != NULL) {
+ if (err)
+ *actlen = 0;
+ else
+ *actlen = min_len;
+ }
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_string_any
+ *
+ * This function will return the string given by "string_index"
+ * using the first language ID. The maximum length "len" includes
+ * the terminating zero. The "len" argument should be twice as
+ * big pluss 2 bytes, compared with the actual maximum string length !
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_string_any(struct usb_device *udev, struct mtx *mtx, char *buf,
+ uint16_t len, uint8_t string_index)
+{
+ char *s;
+ uint8_t *temp;
+ uint16_t i;
+ uint16_t n;
+ uint16_t c;
+ uint8_t swap;
+ usb_error_t err;
+
+ if (len == 0) {
+ /* should not happen */
+ return (USB_ERR_NORMAL_COMPLETION);
+ }
+ if (string_index == 0) {
+ /* this is the language table */
+ buf[0] = 0;
+ return (USB_ERR_INVAL);
+ }
+ if (udev->flags.no_strings) {
+ buf[0] = 0;
+ return (USB_ERR_STALLED);
+ }
+ err = usbd_req_get_string_desc
+ (udev, mtx, buf, len, udev->langid, string_index);
+ if (err) {
+ buf[0] = 0;
+ return (err);
+ }
+ temp = (uint8_t *)buf;
+
+ if (temp[0] < 2) {
+ /* string length is too short */
+ buf[0] = 0;
+ return (USB_ERR_INVAL);
+ }
+ /* reserve one byte for terminating zero */
+ len--;
+
+ /* find maximum length */
+ s = buf;
+ n = (temp[0] / 2) - 1;
+ if (n > len) {
+ n = len;
+ }
+ /* skip descriptor header */
+ temp += 2;
+
+ /* reset swap state */
+ swap = 3;
+
+ /* convert and filter */
+ for (i = 0; (i != n); i++) {
+ c = UGETW(temp + (2 * i));
+
+ /* convert from Unicode, handle buggy strings */
+ if (((c & 0xff00) == 0) && (swap & 1)) {
+ /* Little Endian, default */
+ *s = c;
+ swap = 1;
+ } else if (((c & 0x00ff) == 0) && (swap & 2)) {
+ /* Big Endian */
+ *s = c >> 8;
+ swap = 2;
+ } else {
+ /* silently skip bad character */
+ continue;
+ }
+
+ /*
+ * Filter by default - We only allow alphanumerical
+ * and a few more to avoid any problems with scripts
+ * and daemons.
+ */
+ if (isalpha(*s) ||
+ isdigit(*s) ||
+ *s == '-' ||
+ *s == '+' ||
+ *s == ' ' ||
+ *s == '.' ||
+ *s == ',' ||
+ *s == ':' ||
+ *s == '/' ||
+ *s == '(' ||
+ *s == ')') {
+ /* allowed */
+ s++;
+ }
+ /* silently skip bad character */
+ }
+ *s = 0; /* zero terminate resulting string */
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_string_desc
+ *
+ * If you don't know the language ID, consider using
+ * "usbd_req_get_string_any()".
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_string_desc(struct usb_device *udev, struct mtx *mtx, void *sdesc,
+ uint16_t max_len, uint16_t lang_id,
+ uint8_t string_index)
+{
+ return (usbd_req_get_desc(udev, mtx, NULL, sdesc, 2, max_len, lang_id,
+ UDESC_STRING, string_index, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_config_desc_ptr
+ *
+ * This function is used in device side mode to retrieve the pointer
+ * to the generated config descriptor. This saves allocating space for
+ * an additional config descriptor when setting the configuration.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_descriptor_ptr(struct usb_device *udev,
+ struct usb_config_descriptor **ppcd, uint16_t wValue)
+{
+ struct usb_device_request req;
+ usb_handle_req_t *hr_func;
+ const void *ptr;
+ uint16_t len;
+ usb_error_t err;
+
+ req.bmRequestType = UT_READ_DEVICE;
+ req.bRequest = UR_GET_DESCRIPTOR;
+ USETW(req.wValue, wValue);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+
+ ptr = NULL;
+ len = 0;
+
+ hr_func = usbd_get_hr_func(udev);
+
+ if (hr_func == NULL)
+ err = USB_ERR_INVAL;
+ else {
+ USB_BUS_LOCK(udev->bus);
+ err = (hr_func) (udev, &req, &ptr, &len);
+ USB_BUS_UNLOCK(udev->bus);
+ }
+
+ if (err)
+ ptr = NULL;
+ else if (ptr == NULL)
+ err = USB_ERR_INVAL;
+
+ *ppcd = __DECONST(struct usb_config_descriptor *, ptr);
+
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_config_desc
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_config_desc(struct usb_device *udev, struct mtx *mtx,
+ struct usb_config_descriptor *d, uint8_t conf_index)
+{
+ usb_error_t err;
+
+ DPRINTFN(4, "confidx=%d\n", conf_index);
+
+ err = usbd_req_get_desc(udev, mtx, NULL, d, sizeof(*d),
+ sizeof(*d), 0, UDESC_CONFIG, conf_index, 0);
+ if (err) {
+ goto done;
+ }
+ /* Extra sanity checking */
+ if (UGETW(d->wTotalLength) < (uint16_t)sizeof(*d)) {
+ err = USB_ERR_INVAL;
+ }
+done:
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_alloc_config_desc
+ *
+ * This function is used to allocate a zeroed configuration
+ * descriptor.
+ *
+ * Returns:
+ * NULL: Failure
+ * Else: Success
+ *------------------------------------------------------------------------*/
+void *
+usbd_alloc_config_desc(struct usb_device *udev, uint32_t size)
+{
+ if (size > USB_CONFIG_MAX) {
+ DPRINTF("Configuration descriptor too big\n");
+ return (NULL);
+ }
+#if (USB_HAVE_FIXED_CONFIG == 0)
+ return (malloc(size, M_USBDEV, M_ZERO | M_WAITOK));
+#else
+ memset(udev->config_data, 0, sizeof(udev->config_data));
+ return (udev->config_data);
+#endif
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_alloc_config_desc
+ *
+ * This function is used to free a configuration descriptor.
+ *------------------------------------------------------------------------*/
+void
+usbd_free_config_desc(struct usb_device *udev, void *ptr)
+{
+#if (USB_HAVE_FIXED_CONFIG == 0)
+ free(ptr, M_USBDEV);
+#endif
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_config_desc_full
+ *
+ * This function gets the complete USB configuration descriptor and
+ * ensures that "wTotalLength" is correct. The returned configuration
+ * descriptor is freed by calling "usbd_free_config_desc()".
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_config_desc_full(struct usb_device *udev, struct mtx *mtx,
+ struct usb_config_descriptor **ppcd, uint8_t index)
+{
+ struct usb_config_descriptor cd;
+ struct usb_config_descriptor *cdesc;
+ uint32_t len;
+ usb_error_t err;
+
+ DPRINTFN(4, "index=%d\n", index);
+
+ *ppcd = NULL;
+
+ err = usbd_req_get_config_desc(udev, mtx, &cd, index);
+ if (err)
+ return (err);
+
+ /* get full descriptor */
+ len = UGETW(cd.wTotalLength);
+ if (len < (uint32_t)sizeof(*cdesc)) {
+ /* corrupt descriptor */
+ return (USB_ERR_INVAL);
+ } else if (len > USB_CONFIG_MAX) {
+ DPRINTF("Configuration descriptor was truncated\n");
+ len = USB_CONFIG_MAX;
+ }
+ cdesc = usbd_alloc_config_desc(udev, len);
+ if (cdesc == NULL)
+ return (USB_ERR_NOMEM);
+ err = usbd_req_get_desc(udev, mtx, NULL, cdesc, len, len, 0,
+ UDESC_CONFIG, index, 3);
+ if (err) {
+ usbd_free_config_desc(udev, cdesc);
+ return (err);
+ }
+ /* make sure that the device is not fooling us: */
+ USETW(cdesc->wTotalLength, len);
+
+ *ppcd = cdesc;
+
+ return (0); /* success */
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_device_desc
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_device_desc(struct usb_device *udev, struct mtx *mtx,
+ struct usb_device_descriptor *d)
+{
+ DPRINTFN(4, "\n");
+ return (usbd_req_get_desc(udev, mtx, NULL, d, sizeof(*d),
+ sizeof(*d), 0, UDESC_DEVICE, 0, 3));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_alt_interface_no
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_alt_interface_no(struct usb_device *udev, struct mtx *mtx,
+ uint8_t *alt_iface_no, uint8_t iface_index)
+{
+ struct usb_interface *iface = usbd_get_iface(udev, iface_index);
+ struct usb_device_request req;
+
+ if ((iface == NULL) || (iface->idesc == NULL))
+ return (USB_ERR_INVAL);
+
+ req.bmRequestType = UT_READ_INTERFACE;
+ req.bRequest = UR_GET_INTERFACE;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = iface->idesc->bInterfaceNumber;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 1);
+ return (usbd_do_request(udev, mtx, &req, alt_iface_no));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_alt_interface_no
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_alt_interface_no(struct usb_device *udev, struct mtx *mtx,
+ uint8_t iface_index, uint8_t alt_no)
+{
+ struct usb_interface *iface = usbd_get_iface(udev, iface_index);
+ struct usb_device_request req;
+ usb_error_t err;
+
+ if ((iface == NULL) || (iface->idesc == NULL))
+ return (USB_ERR_INVAL);
+
+ req.bmRequestType = UT_WRITE_INTERFACE;
+ req.bRequest = UR_SET_INTERFACE;
+ req.wValue[0] = alt_no;
+ req.wValue[1] = 0;
+ req.wIndex[0] = iface->idesc->bInterfaceNumber;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+ err = usbd_do_request(udev, mtx, &req, 0);
+ if (err == USB_ERR_STALLED && iface->num_altsetting == 1) {
+ /*
+ * The USB specification chapter 9.4.10 says that USB
+ * devices having only one alternate setting are
+ * allowed to STALL this request. Ignore this failure.
+ */
+ err = 0;
+ DPRINTF("Setting default alternate number failed. (ignored)\n");
+ }
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_device_status
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_device_status(struct usb_device *udev, struct mtx *mtx,
+ struct usb_status *st)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_READ_DEVICE;
+ req.bRequest = UR_GET_STATUS;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, sizeof(*st));
+ return (usbd_do_request(udev, mtx, &req, st));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_hub_descriptor
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_hub_descriptor(struct usb_device *udev, struct mtx *mtx,
+ struct usb_hub_descriptor *hd, uint8_t nports)
+{
+ struct usb_device_request req;
+ uint16_t len = (nports + 7 + (8 * 8)) / 8;
+
+ req.bmRequestType = UT_READ_CLASS_DEVICE;
+ req.bRequest = UR_GET_DESCRIPTOR;
+ USETW2(req.wValue, UDESC_HUB, 0);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, len);
+ return (usbd_do_request(udev, mtx, &req, hd));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_ss_hub_descriptor
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_ss_hub_descriptor(struct usb_device *udev, struct mtx *mtx,
+ struct usb_hub_ss_descriptor *hd, uint8_t nports)
+{
+ struct usb_device_request req;
+ uint16_t len = sizeof(*hd) - 32 + 1 + ((nports + 7) / 8);
+
+ req.bmRequestType = UT_READ_CLASS_DEVICE;
+ req.bRequest = UR_GET_DESCRIPTOR;
+ USETW2(req.wValue, UDESC_SS_HUB, 0);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, len);
+ return (usbd_do_request(udev, mtx, &req, hd));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_hub_status
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_hub_status(struct usb_device *udev, struct mtx *mtx,
+ struct usb_hub_status *st)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_READ_CLASS_DEVICE;
+ req.bRequest = UR_GET_STATUS;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, sizeof(struct usb_hub_status));
+ return (usbd_do_request(udev, mtx, &req, st));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_address
+ *
+ * This function is used to set the address for an USB device. After
+ * port reset the USB device will respond at address zero.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_address(struct usb_device *udev, struct mtx *mtx, uint16_t addr)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+
+ DPRINTFN(6, "setting device address=%d\n", addr);
+
+ req.bmRequestType = UT_WRITE_DEVICE;
+ req.bRequest = UR_SET_ADDRESS;
+ USETW(req.wValue, addr);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+
+ err = USB_ERR_INVAL;
+
+ /* check if USB controller handles set address */
+ if (udev->bus->methods->set_address != NULL)
+ err = (udev->bus->methods->set_address) (udev, mtx, addr);
+
+ if (err != USB_ERR_INVAL)
+ goto done;
+
+ /* Setting the address should not take more than 1 second ! */
+ err = usbd_do_request_flags(udev, mtx, &req, NULL,
+ USB_DELAY_STATUS_STAGE, NULL, 1000);
+
+done:
+ /* allow device time to set new address */
+ usb_pause_mtx(mtx,
+ USB_MS_TO_TICKS(usb_set_address_settle));
+
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_port_status
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_port_status(struct usb_device *udev, struct mtx *mtx,
+ struct usb_port_status *ps, uint8_t port)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_READ_CLASS_OTHER;
+ req.bRequest = UR_GET_STATUS;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = port;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, sizeof(*ps));
+
+ return (usbd_do_request_flags(udev, mtx, &req, ps, 0, NULL, 1000));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_clear_hub_feature
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_clear_hub_feature(struct usb_device *udev, struct mtx *mtx,
+ uint16_t sel)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_CLASS_DEVICE;
+ req.bRequest = UR_CLEAR_FEATURE;
+ USETW(req.wValue, sel);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_hub_feature
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_hub_feature(struct usb_device *udev, struct mtx *mtx,
+ uint16_t sel)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_CLASS_DEVICE;
+ req.bRequest = UR_SET_FEATURE;
+ USETW(req.wValue, sel);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_hub_u1_timeout
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_hub_u1_timeout(struct usb_device *udev, struct mtx *mtx,
+ uint8_t port, uint8_t timeout)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_CLASS_OTHER;
+ req.bRequest = UR_SET_FEATURE;
+ USETW(req.wValue, UHF_PORT_U1_TIMEOUT);
+ req.wIndex[0] = port;
+ req.wIndex[1] = timeout;
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_hub_u2_timeout
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_hub_u2_timeout(struct usb_device *udev, struct mtx *mtx,
+ uint8_t port, uint8_t timeout)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_CLASS_OTHER;
+ req.bRequest = UR_SET_FEATURE;
+ USETW(req.wValue, UHF_PORT_U2_TIMEOUT);
+ req.wIndex[0] = port;
+ req.wIndex[1] = timeout;
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_hub_depth
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_hub_depth(struct usb_device *udev, struct mtx *mtx,
+ uint16_t depth)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_CLASS_DEVICE;
+ req.bRequest = UR_SET_HUB_DEPTH;
+ USETW(req.wValue, depth);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_clear_port_feature
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_clear_port_feature(struct usb_device *udev, struct mtx *mtx,
+ uint8_t port, uint16_t sel)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_CLASS_OTHER;
+ req.bRequest = UR_CLEAR_FEATURE;
+ USETW(req.wValue, sel);
+ req.wIndex[0] = port;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_port_feature
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_port_feature(struct usb_device *udev, struct mtx *mtx,
+ uint8_t port, uint16_t sel)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_CLASS_OTHER;
+ req.bRequest = UR_SET_FEATURE;
+ USETW(req.wValue, sel);
+ req.wIndex[0] = port;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_protocol
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_protocol(struct usb_device *udev, struct mtx *mtx,
+ uint8_t iface_index, uint16_t report)
+{
+ struct usb_interface *iface = usbd_get_iface(udev, iface_index);
+ struct usb_device_request req;
+
+ if ((iface == NULL) || (iface->idesc == NULL)) {
+ return (USB_ERR_INVAL);
+ }
+ DPRINTFN(5, "iface=%p, report=%d, endpt=%d\n",
+ iface, report, iface->idesc->bInterfaceNumber);
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UR_SET_PROTOCOL;
+ USETW(req.wValue, report);
+ req.wIndex[0] = iface->idesc->bInterfaceNumber;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_report
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_report(struct usb_device *udev, struct mtx *mtx, void *data, uint16_t len,
+ uint8_t iface_index, uint8_t type, uint8_t id)
+{
+ struct usb_interface *iface = usbd_get_iface(udev, iface_index);
+ struct usb_device_request req;
+
+ if ((iface == NULL) || (iface->idesc == NULL)) {
+ return (USB_ERR_INVAL);
+ }
+ DPRINTFN(5, "len=%d\n", len);
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UR_SET_REPORT;
+ USETW2(req.wValue, type, id);
+ req.wIndex[0] = iface->idesc->bInterfaceNumber;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, len);
+ return (usbd_do_request(udev, mtx, &req, data));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_report
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_report(struct usb_device *udev, struct mtx *mtx, void *data,
+ uint16_t len, uint8_t iface_index, uint8_t type, uint8_t id)
+{
+ struct usb_interface *iface = usbd_get_iface(udev, iface_index);
+ struct usb_device_request req;
+
+ if ((iface == NULL) || (iface->idesc == NULL)) {
+ return (USB_ERR_INVAL);
+ }
+ DPRINTFN(5, "len=%d\n", len);
+
+ req.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.bRequest = UR_GET_REPORT;
+ USETW2(req.wValue, type, id);
+ req.wIndex[0] = iface->idesc->bInterfaceNumber;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, len);
+ return (usbd_do_request(udev, mtx, &req, data));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_idle
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_idle(struct usb_device *udev, struct mtx *mtx,
+ uint8_t iface_index, uint8_t duration, uint8_t id)
+{
+ struct usb_interface *iface = usbd_get_iface(udev, iface_index);
+ struct usb_device_request req;
+
+ if ((iface == NULL) || (iface->idesc == NULL)) {
+ return (USB_ERR_INVAL);
+ }
+ DPRINTFN(5, "%d %d\n", duration, id);
+
+ req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+ req.bRequest = UR_SET_IDLE;
+ USETW2(req.wValue, duration, id);
+ req.wIndex[0] = iface->idesc->bInterfaceNumber;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_report_descriptor
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_report_descriptor(struct usb_device *udev, struct mtx *mtx,
+ void *d, uint16_t size, uint8_t iface_index)
+{
+ struct usb_interface *iface = usbd_get_iface(udev, iface_index);
+ struct usb_device_request req;
+
+ if ((iface == NULL) || (iface->idesc == NULL)) {
+ return (USB_ERR_INVAL);
+ }
+ req.bmRequestType = UT_READ_INTERFACE;
+ req.bRequest = UR_GET_DESCRIPTOR;
+ USETW2(req.wValue, UDESC_REPORT, 0); /* report id should be 0 */
+ req.wIndex[0] = iface->idesc->bInterfaceNumber;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, size);
+ return (usbd_do_request(udev, mtx, &req, d));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_config
+ *
+ * This function is used to select the current configuration number in
+ * both USB device side mode and USB host side mode. When setting the
+ * configuration the function of the interfaces can change.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_config(struct usb_device *udev, struct mtx *mtx, uint8_t conf)
+{
+ struct usb_device_request req;
+
+ DPRINTF("setting config %d\n", conf);
+
+ /* do "set configuration" request */
+
+ req.bmRequestType = UT_WRITE_DEVICE;
+ req.bRequest = UR_SET_CONFIG;
+ req.wValue[0] = conf;
+ req.wValue[1] = 0;
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_get_config
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_get_config(struct usb_device *udev, struct mtx *mtx, uint8_t *pconf)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_READ_DEVICE;
+ req.bRequest = UR_GET_CONFIG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 1);
+ return (usbd_do_request(udev, mtx, &req, pconf));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_setup_device_desc
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_setup_device_desc(struct usb_device *udev, struct mtx *mtx)
+{
+ usb_error_t err;
+
+ /*
+ * Get the first 8 bytes of the device descriptor !
+ *
+ * NOTE: "usbd_do_request()" will check the device descriptor
+ * next time we do a request to see if the maximum packet size
+ * changed! The 8 first bytes of the device descriptor
+ * contains the maximum packet size to use on control endpoint
+ * 0. If this value is different from "USB_MAX_IPACKET" a new
+ * USB control request will be setup!
+ */
+ switch (udev->speed) {
+ case USB_SPEED_FULL:
+ if (usb_full_ddesc != 0) {
+ /* get full device descriptor */
+ err = usbd_req_get_device_desc(udev, mtx, &udev->ddesc);
+ if (err == 0)
+ break;
+ }
+
+ /* get partial device descriptor, some devices crash on this */
+ err = usbd_req_get_desc(udev, mtx, NULL, &udev->ddesc,
+ USB_MAX_IPACKET, USB_MAX_IPACKET, 0, UDESC_DEVICE, 0, 0);
+ if (err != 0) {
+ DPRINTF("Trying fallback for getting the USB device descriptor\n");
+ /* try 8 bytes bMaxPacketSize */
+ udev->ddesc.bMaxPacketSize = 8;
+ /* get full device descriptor */
+ err = usbd_req_get_device_desc(udev, mtx, &udev->ddesc);
+ if (err == 0)
+ break;
+ /* try 16 bytes bMaxPacketSize */
+ udev->ddesc.bMaxPacketSize = 16;
+ /* get full device descriptor */
+ err = usbd_req_get_device_desc(udev, mtx, &udev->ddesc);
+ if (err == 0)
+ break;
+ /* try 32/64 bytes bMaxPacketSize */
+ udev->ddesc.bMaxPacketSize = 32;
+ }
+ /* get the full device descriptor */
+ err = usbd_req_get_device_desc(udev, mtx, &udev->ddesc);
+ break;
+
+ default:
+ DPRINTF("Minimum bMaxPacketSize is large enough "
+ "to hold the complete device descriptor or "
+ "only one bMaxPacketSize choice\n");
+
+ /* get the full device descriptor */
+ err = usbd_req_get_device_desc(udev, mtx, &udev->ddesc);
+
+ /* try one more time, if error */
+ if (err != 0)
+ err = usbd_req_get_device_desc(udev, mtx, &udev->ddesc);
+ break;
+ }
+
+ if (err != 0) {
+ DPRINTFN(0, "getting device descriptor "
+ "at addr %d failed, %s\n", udev->address,
+ usbd_errstr(err));
+ return (err);
+ }
+
+ DPRINTF("adding unit addr=%d, rev=%02x, class=%d, "
+ "subclass=%d, protocol=%d, maxpacket=%d, len=%d, speed=%d\n",
+ udev->address, UGETW(udev->ddesc.bcdUSB),
+ udev->ddesc.bDeviceClass,
+ udev->ddesc.bDeviceSubClass,
+ udev->ddesc.bDeviceProtocol,
+ udev->ddesc.bMaxPacketSize,
+ udev->ddesc.bLength,
+ udev->speed);
+
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_re_enumerate
+ *
+ * NOTE: After this function returns the hardware is in the
+ * unconfigured state! The application is responsible for setting a
+ * new configuration.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_re_enumerate(struct usb_device *udev, struct mtx *mtx)
+{
+ struct usb_device *parent_hub;
+ usb_error_t err;
+ uint8_t old_addr;
+ uint8_t do_retry = 1;
+
+ if (udev->flags.usb_mode != USB_MODE_HOST) {
+ return (USB_ERR_INVAL);
+ }
+ old_addr = udev->address;
+ parent_hub = udev->parent_hub;
+ if (parent_hub == NULL) {
+ return (USB_ERR_INVAL);
+ }
+retry:
+#if USB_HAVE_TT_SUPPORT
+ /*
+ * Try to reset the High Speed parent HUB of a LOW- or FULL-
+ * speed device, if any.
+ */
+ if (udev->parent_hs_hub != NULL &&
+ udev->speed != USB_SPEED_HIGH) {
+ DPRINTF("Trying to reset parent High Speed TT.\n");
+ if (udev->parent_hs_hub == parent_hub &&
+ (uhub_count_active_host_ports(parent_hub, USB_SPEED_LOW) +
+ uhub_count_active_host_ports(parent_hub, USB_SPEED_FULL)) == 1) {
+ /* we can reset the whole TT */
+ err = usbd_req_reset_tt(parent_hub, NULL,
+ udev->hs_port_no);
+ } else {
+ /* only reset a particular device and endpoint */
+ err = usbd_req_clear_tt_buffer(udev->parent_hs_hub, NULL,
+ udev->hs_port_no, old_addr, UE_CONTROL, 0);
+ }
+ if (err) {
+ DPRINTF("Resetting parent High "
+ "Speed TT failed (%s).\n",
+ usbd_errstr(err));
+ }
+ }
+#endif
+ /* Try to warm reset first */
+ if (parent_hub->speed == USB_SPEED_SUPER)
+ usbd_req_warm_reset_port(parent_hub, mtx, udev->port_no);
+
+ /* Try to reset the parent HUB port. */
+ err = usbd_req_reset_port(parent_hub, mtx, udev->port_no);
+ if (err) {
+ DPRINTFN(0, "addr=%d, port reset failed, %s\n",
+ old_addr, usbd_errstr(err));
+ goto done;
+ }
+
+ /*
+ * After that the port has been reset our device should be at
+ * address zero:
+ */
+ udev->address = USB_START_ADDR;
+
+ /* reset "bMaxPacketSize" */
+ udev->ddesc.bMaxPacketSize = USB_MAX_IPACKET;
+
+ /* reset USB state */
+ usb_set_device_state(udev, USB_STATE_POWERED);
+
+ /*
+ * Restore device address:
+ */
+ err = usbd_req_set_address(udev, mtx, old_addr);
+ if (err) {
+ /* XXX ignore any errors! */
+ DPRINTFN(0, "addr=%d, set address failed! (%s, ignored)\n",
+ old_addr, usbd_errstr(err));
+ }
+ /*
+ * Restore device address, if the controller driver did not
+ * set a new one:
+ */
+ if (udev->address == USB_START_ADDR)
+ udev->address = old_addr;
+
+ /* setup the device descriptor and the initial "wMaxPacketSize" */
+ err = usbd_setup_device_desc(udev, mtx);
+
+done:
+ if (err && do_retry) {
+ /* give the USB firmware some time to load */
+ usb_pause_mtx(mtx, hz / 2);
+ /* no more retries after this retry */
+ do_retry = 0;
+ /* try again */
+ goto retry;
+ }
+ /* restore address */
+ if (udev->address == USB_START_ADDR)
+ udev->address = old_addr;
+ /* update state, if successful */
+ if (err == 0)
+ usb_set_device_state(udev, USB_STATE_ADDRESSED);
+ return (err);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_clear_device_feature
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_clear_device_feature(struct usb_device *udev, struct mtx *mtx,
+ uint16_t sel)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_DEVICE;
+ req.bRequest = UR_CLEAR_FEATURE;
+ USETW(req.wValue, sel);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_device_feature
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_device_feature(struct usb_device *udev, struct mtx *mtx,
+ uint16_t sel)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_DEVICE;
+ req.bRequest = UR_SET_FEATURE;
+ USETW(req.wValue, sel);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_reset_tt
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_reset_tt(struct usb_device *udev, struct mtx *mtx,
+ uint8_t port)
+{
+ struct usb_device_request req;
+
+ /* For single TT HUBs the port should be 1 */
+
+ if (udev->ddesc.bDeviceClass == UDCLASS_HUB &&
+ udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBSTT)
+ port = 1;
+
+ req.bmRequestType = UT_WRITE_CLASS_OTHER;
+ req.bRequest = UR_RESET_TT;
+ USETW(req.wValue, 0);
+ req.wIndex[0] = port;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_clear_tt_buffer
+ *
+ * For single TT HUBs the port should be 1.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_clear_tt_buffer(struct usb_device *udev, struct mtx *mtx,
+ uint8_t port, uint8_t addr, uint8_t type, uint8_t endpoint)
+{
+ struct usb_device_request req;
+ uint16_t wValue;
+
+ /* For single TT HUBs the port should be 1 */
+
+ if (udev->ddesc.bDeviceClass == UDCLASS_HUB &&
+ udev->ddesc.bDeviceProtocol == UDPROTO_HSHUBSTT)
+ port = 1;
+
+ wValue = (endpoint & 0xF) | ((addr & 0x7F) << 4) |
+ ((endpoint & 0x80) << 8) | ((type & 3) << 12);
+
+ req.bmRequestType = UT_WRITE_CLASS_OTHER;
+ req.bRequest = UR_CLEAR_TT_BUFFER;
+ USETW(req.wValue, wValue);
+ req.wIndex[0] = port;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_port_link_state
+ *
+ * USB 3.0 specific request
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_port_link_state(struct usb_device *udev, struct mtx *mtx,
+ uint8_t port, uint8_t link_state)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_CLASS_OTHER;
+ req.bRequest = UR_SET_FEATURE;
+ USETW(req.wValue, UHF_PORT_LINK_STATE);
+ req.wIndex[0] = port;
+ req.wIndex[1] = link_state;
+ USETW(req.wLength, 0);
+ return (usbd_do_request(udev, mtx, &req, 0));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_req_set_lpm_info
+ *
+ * USB 2.0 specific request for Link Power Management.
+ *
+ * Returns:
+ * 0: Success
+ * USB_ERR_PENDING_REQUESTS: NYET
+ * USB_ERR_TIMEOUT: TIMEOUT
+ * USB_ERR_STALL: STALL
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_req_set_lpm_info(struct usb_device *udev, struct mtx *mtx,
+ uint8_t port, uint8_t besl, uint8_t addr, uint8_t rwe)
+{
+ struct usb_device_request req;
+ usb_error_t err;
+ uint8_t buf[1];
+
+ req.bmRequestType = UT_WRITE_CLASS_OTHER;
+ req.bRequest = UR_SET_AND_TEST;
+ USETW(req.wValue, UHF_PORT_L1);
+ req.wIndex[0] = (port & 0xF) | ((besl & 0xF) << 4);
+ req.wIndex[1] = (addr & 0x7F) | (rwe ? 0x80 : 0x00);
+ USETW(req.wLength, sizeof(buf));
+
+ /* set default value in case of short transfer */
+ buf[0] = 0x00;
+
+ err = usbd_do_request(udev, mtx, &req, buf);
+ if (err)
+ return (err);
+
+ switch (buf[0]) {
+ case 0x00: /* SUCCESS */
+ break;
+ case 0x10: /* NYET */
+ err = USB_ERR_PENDING_REQUESTS;
+ break;
+ case 0x11: /* TIMEOUT */
+ err = USB_ERR_TIMEOUT;
+ break;
+ case 0x30: /* STALL */
+ err = USB_ERR_STALLED;
+ break;
+ default: /* reserved */
+ err = USB_ERR_IOERROR;
+ break;
+ }
+ return (err);
+}
diff --git a/sys/dev/usb/usb_request.h b/sys/dev/usb/usb_request.h
new file mode 100644
index 000000000000..56a4003e8bae
--- /dev/null
+++ b/sys/dev/usb/usb_request.h
@@ -0,0 +1,101 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_REQUEST_H_
+#define _USB_REQUEST_H_
+
+struct usb_process;
+
+usb_error_t usbd_req_clear_hub_feature(struct usb_device *udev,
+ struct mtx *mtx, uint16_t sel);
+usb_error_t usbd_req_clear_port_feature(struct usb_device *udev,
+ struct mtx *mtx, uint8_t port, uint16_t sel);
+usb_error_t usbd_req_get_alt_interface_no(struct usb_device *udev,
+ struct mtx *mtx, uint8_t *alt_iface_no,
+ uint8_t iface_index);
+usb_error_t usbd_req_get_config(struct usb_device *udev, struct mtx *mtx,
+ uint8_t *pconf);
+usb_error_t usbd_req_get_descriptor_ptr(struct usb_device *udev,
+ struct usb_config_descriptor **ppcd, uint16_t wValue);
+usb_error_t usbd_req_get_config_desc(struct usb_device *udev, struct mtx *mtx,
+ struct usb_config_descriptor *d, uint8_t conf_index);
+usb_error_t usbd_req_get_config_desc_full(struct usb_device *udev,
+ struct mtx *mtx, struct usb_config_descriptor **ppcd,
+ uint8_t conf_index);
+usb_error_t usbd_req_get_desc(struct usb_device *udev, struct mtx *mtx,
+ uint16_t *actlen, void *desc, uint16_t min_len,
+ uint16_t max_len, uint16_t id, uint8_t type,
+ uint8_t index, uint8_t retries);
+usb_error_t usbd_req_get_device_desc(struct usb_device *udev, struct mtx *mtx,
+ struct usb_device_descriptor *d);
+usb_error_t usbd_req_get_device_status(struct usb_device *udev,
+ struct mtx *mtx, struct usb_status *st);
+usb_error_t usbd_req_get_hub_descriptor(struct usb_device *udev,
+ struct mtx *mtx, struct usb_hub_descriptor *hd,
+ uint8_t nports);
+usb_error_t usbd_req_get_ss_hub_descriptor(struct usb_device *udev,
+ struct mtx *mtx, struct usb_hub_ss_descriptor *hd,
+ uint8_t nports);
+usb_error_t usbd_req_get_hub_status(struct usb_device *udev, struct mtx *mtx,
+ struct usb_hub_status *st);
+usb_error_t usbd_req_get_port_status(struct usb_device *udev, struct mtx *mtx,
+ struct usb_port_status *ps, uint8_t port);
+usb_error_t usbd_req_reset_port(struct usb_device *udev, struct mtx *mtx,
+ uint8_t port);
+usb_error_t usbd_req_warm_reset_port(struct usb_device *udev,
+ struct mtx *mtx, uint8_t port);
+usb_error_t usbd_req_set_address(struct usb_device *udev, struct mtx *mtx,
+ uint16_t addr);
+usb_error_t usbd_req_set_hub_feature(struct usb_device *udev, struct mtx *mtx,
+ uint16_t sel);
+usb_error_t usbd_req_set_port_feature(struct usb_device *udev,
+ struct mtx *mtx, uint8_t port, uint16_t sel);
+usb_error_t usbd_setup_device_desc(struct usb_device *udev, struct mtx *mtx);
+usb_error_t usbd_req_re_enumerate(struct usb_device *udev, struct mtx *mtx);
+usb_error_t usbd_req_clear_device_feature(struct usb_device *udev,
+ struct mtx *mtx, uint16_t sel);
+usb_error_t usbd_req_set_device_feature(struct usb_device *udev,
+ struct mtx *mtx, uint16_t sel);
+usb_error_t usbd_req_set_hub_u1_timeout(struct usb_device *udev,
+ struct mtx *mtx, uint8_t port, uint8_t timeout);
+usb_error_t usbd_req_set_hub_u2_timeout(struct usb_device *udev,
+ struct mtx *mtx, uint8_t port, uint8_t timeout);
+usb_error_t usbd_req_set_hub_depth(struct usb_device *udev,
+ struct mtx *mtx, uint16_t depth);
+usb_error_t usbd_req_reset_tt(struct usb_device *udev, struct mtx *mtx,
+ uint8_t port);
+usb_error_t usbd_req_clear_tt_buffer(struct usb_device *udev, struct mtx *mtx,
+ uint8_t port, uint8_t addr, uint8_t type, uint8_t endpoint);
+usb_error_t usbd_req_set_port_link_state(struct usb_device *udev,
+ struct mtx *mtx, uint8_t port, uint8_t link_state);
+usb_error_t usbd_req_set_lpm_info(struct usb_device *udev, struct mtx *mtx,
+ uint8_t port, uint8_t besl, uint8_t addr, uint8_t rwe);
+
+void * usbd_alloc_config_desc(struct usb_device *, uint32_t);
+void usbd_free_config_desc(struct usb_device *, void *);
+
+#endif /* _USB_REQUEST_H_ */
diff --git a/sys/dev/usb/usb_transfer.c b/sys/dev/usb/usb_transfer.c
new file mode 100644
index 000000000000..67745cf49397
--- /dev/null
+++ b/sys/dev/usb/usb_transfer.c
@@ -0,0 +1,3750 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008-2021 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+
+#define USB_DEBUG_VAR usb_debug
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_busdma.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_transfer.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_util.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#include <dev/usb/usb_pf.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+struct usb_std_packet_size {
+ struct {
+ uint16_t min; /* inclusive */
+ uint16_t max; /* inclusive */
+ } range;
+
+ uint16_t fixed[4];
+};
+
+static usb_callback_t usb_request_callback;
+
+static const struct usb_config usb_control_ep_cfg[USB_CTRL_XFER_MAX] = {
+ /* This transfer is used for generic control endpoint transfers */
+
+ [0] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control endpoint */
+ .direction = UE_DIR_ANY,
+ .bufsize = USB_EP0_BUFSIZE, /* bytes */
+ .flags = {.proxy_buffer = 1,},
+ .callback = &usb_request_callback,
+ .usb_mode = USB_MODE_DUAL, /* both modes */
+ },
+
+ /* This transfer is used for generic clear stall only */
+
+ [1] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &usb_do_clear_stall_callback,
+ .timeout = 1000, /* 1 second */
+ .interval = 50, /* 50ms */
+ .usb_mode = USB_MODE_HOST,
+ },
+};
+
+static const struct usb_config usb_control_ep_quirk_cfg[USB_CTRL_XFER_MAX] = {
+ /* This transfer is used for generic control endpoint transfers */
+
+ [0] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control endpoint */
+ .direction = UE_DIR_ANY,
+ .bufsize = 65535, /* bytes */
+ .callback = &usb_request_callback,
+ .usb_mode = USB_MODE_DUAL, /* both modes */
+ },
+
+ /* This transfer is used for generic clear stall only */
+
+ [1] = {
+ .type = UE_CONTROL,
+ .endpoint = 0x00, /* Control pipe */
+ .direction = UE_DIR_ANY,
+ .bufsize = sizeof(struct usb_device_request),
+ .callback = &usb_do_clear_stall_callback,
+ .timeout = 1000, /* 1 second */
+ .interval = 50, /* 50ms */
+ .usb_mode = USB_MODE_HOST,
+ },
+};
+
+/* function prototypes */
+
+static void usbd_update_max_frame_size(struct usb_xfer *);
+static void usbd_transfer_unsetup_sub(struct usb_xfer_root *, uint8_t);
+static void usbd_control_transfer_init(struct usb_xfer *);
+static int usbd_setup_ctrl_transfer(struct usb_xfer *);
+static void usb_callback_proc(struct usb_proc_msg *);
+static void usbd_callback_ss_done_defer(struct usb_xfer *);
+static void usbd_callback_wrapper(struct usb_xfer_queue *);
+static void usbd_transfer_start_cb(void *);
+static uint8_t usbd_callback_wrapper_sub(struct usb_xfer *);
+static void usbd_get_std_packet_size(struct usb_std_packet_size *ptr,
+ uint8_t type, enum usb_dev_speed speed);
+
+/*------------------------------------------------------------------------*
+ * usb_request_callback
+ *------------------------------------------------------------------------*/
+static void
+usb_request_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE)
+ usb_handle_request_callback(xfer, error);
+ else
+ usbd_do_request_callback(xfer, error);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_update_max_frame_size
+ *
+ * This function updates the maximum frame size, hence high speed USB
+ * can transfer multiple consecutive packets.
+ *------------------------------------------------------------------------*/
+static void
+usbd_update_max_frame_size(struct usb_xfer *xfer)
+{
+ /* compute maximum frame size */
+ /* this computation should not overflow 16-bit */
+ /* max = 15 * 1024 */
+
+ xfer->max_frame_size = xfer->max_packet_size * xfer->max_packet_count;
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_get_dma_delay
+ *
+ * The following function is called when we need to
+ * synchronize with DMA hardware.
+ *
+ * Returns:
+ * 0: no DMA delay required
+ * Else: milliseconds of DMA delay
+ *------------------------------------------------------------------------*/
+usb_timeout_t
+usbd_get_dma_delay(struct usb_device *udev)
+{
+ const struct usb_bus_methods *mtod;
+ uint32_t temp;
+
+ mtod = udev->bus->methods;
+ temp = 0;
+
+ if (mtod->get_dma_delay) {
+ (mtod->get_dma_delay) (udev, &temp);
+ /*
+ * Round up and convert to milliseconds. Note that we use
+ * 1024 milliseconds per second. to save a division.
+ */
+ temp += 0x3FF;
+ temp /= 0x400;
+ }
+ return (temp);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_setup_sub_malloc
+ *
+ * This function will allocate one or more DMA'able memory chunks
+ * according to "size", "align" and "count" arguments. "ppc" is
+ * pointed to a linear array of USB page caches afterwards.
+ *
+ * If the "align" argument is equal to "1" a non-contiguous allocation
+ * can happen. Else if the "align" argument is greater than "1", the
+ * allocation will always be contiguous in memory.
+ *
+ * Returns:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_BUSDMA
+uint8_t
+usbd_transfer_setup_sub_malloc(struct usb_setup_params *parm,
+ struct usb_page_cache **ppc, usb_size_t size, usb_size_t align,
+ usb_size_t count)
+{
+ struct usb_page_cache *pc;
+ struct usb_page *pg;
+ void *buf;
+ usb_size_t n_dma_pc;
+ usb_size_t n_dma_pg;
+ usb_size_t n_obj;
+ usb_size_t x;
+ usb_size_t y;
+ usb_size_t r;
+ usb_size_t z;
+
+ USB_ASSERT(align > 0, ("Invalid alignment, 0x%08x\n",
+ align));
+ USB_ASSERT(size > 0, ("Invalid size = 0\n"));
+
+ if (count == 0) {
+ return (0); /* nothing to allocate */
+ }
+ /*
+ * Make sure that the size is aligned properly.
+ */
+ size = -((-size) & (-align));
+
+ /*
+ * Try multi-allocation chunks to reduce the number of DMA
+ * allocations, hence DMA allocations are slow.
+ */
+ if (align == 1) {
+ /* special case - non-cached multi page DMA memory */
+ n_dma_pc = count;
+ n_dma_pg = (2 + (size / USB_PAGE_SIZE));
+ n_obj = 1;
+ } else if (size >= USB_PAGE_SIZE) {
+ n_dma_pc = count;
+ n_dma_pg = 1;
+ n_obj = 1;
+ } else {
+ /* compute number of objects per page */
+#ifdef USB_DMA_SINGLE_ALLOC
+ n_obj = 1;
+#else
+ n_obj = (USB_PAGE_SIZE / size);
+#endif
+ /*
+ * Compute number of DMA chunks, rounded up
+ * to nearest one:
+ */
+ n_dma_pc = howmany(count, n_obj);
+ n_dma_pg = 1;
+ }
+
+ /*
+ * DMA memory is allocated once, but mapped twice. That's why
+ * there is one list for auto-free and another list for
+ * non-auto-free which only holds the mapping and not the
+ * allocation.
+ */
+ if (parm->buf == NULL) {
+ /* reserve memory (auto-free) */
+ parm->dma_page_ptr += n_dma_pc * n_dma_pg;
+ parm->dma_page_cache_ptr += n_dma_pc;
+
+ /* reserve memory (no-auto-free) */
+ parm->dma_page_ptr += count * n_dma_pg;
+ parm->xfer_page_cache_ptr += count;
+ return (0);
+ }
+ for (x = 0; x != n_dma_pc; x++) {
+ /* need to initialize the page cache */
+ parm->dma_page_cache_ptr[x].tag_parent =
+ &parm->curr_xfer->xroot->dma_parent_tag;
+ }
+ for (x = 0; x != count; x++) {
+ /* need to initialize the page cache */
+ parm->xfer_page_cache_ptr[x].tag_parent =
+ &parm->curr_xfer->xroot->dma_parent_tag;
+ }
+
+ if (ppc != NULL) {
+ if (n_obj != 1)
+ *ppc = parm->xfer_page_cache_ptr;
+ else
+ *ppc = parm->dma_page_cache_ptr;
+ }
+ r = count; /* set remainder count */
+ z = n_obj * size; /* set allocation size */
+ pc = parm->xfer_page_cache_ptr;
+ pg = parm->dma_page_ptr;
+
+ if (n_obj == 1) {
+ /*
+ * Avoid mapping memory twice if only a single object
+ * should be allocated per page cache:
+ */
+ for (x = 0; x != n_dma_pc; x++) {
+ if (usb_pc_alloc_mem(parm->dma_page_cache_ptr,
+ pg, z, align)) {
+ return (1); /* failure */
+ }
+ /* Make room for one DMA page cache and "n_dma_pg" pages */
+ parm->dma_page_cache_ptr++;
+ pg += n_dma_pg;
+ }
+ } else {
+ for (x = 0; x != n_dma_pc; x++) {
+ if (r < n_obj) {
+ /* compute last remainder */
+ z = r * size;
+ n_obj = r;
+ }
+ if (usb_pc_alloc_mem(parm->dma_page_cache_ptr,
+ pg, z, align)) {
+ return (1); /* failure */
+ }
+ /* Set beginning of current buffer */
+ buf = parm->dma_page_cache_ptr->buffer;
+ /* Make room for one DMA page cache and "n_dma_pg" pages */
+ parm->dma_page_cache_ptr++;
+ pg += n_dma_pg;
+
+ for (y = 0; (y != n_obj); y++, r--, pc++, pg += n_dma_pg) {
+ /* Load sub-chunk into DMA */
+ if (usb_pc_dmamap_create(pc, size)) {
+ return (1); /* failure */
+ }
+ pc->buffer = USB_ADD_BYTES(buf, y * size);
+ pc->page_start = pg;
+
+ USB_MTX_LOCK(pc->tag_parent->mtx);
+ if (usb_pc_load_mem(pc, size, 1 /* synchronous */ )) {
+ USB_MTX_UNLOCK(pc->tag_parent->mtx);
+ return (1); /* failure */
+ }
+ USB_MTX_UNLOCK(pc->tag_parent->mtx);
+ }
+ }
+ }
+
+ parm->xfer_page_cache_ptr = pc;
+ parm->dma_page_ptr = pg;
+ return (0);
+}
+#endif
+
+/*------------------------------------------------------------------------*
+ * usbd_get_max_frame_length
+ *
+ * This function returns the maximum single frame length as computed by
+ * usbd_transfer_setup(). It is useful when computing buffer sizes for
+ * devices having multiple alternate settings. The SuperSpeed endpoint
+ * companion pointer is allowed to be NULL.
+ *------------------------------------------------------------------------*/
+uint32_t
+usbd_get_max_frame_length(const struct usb_endpoint_descriptor *edesc,
+ const struct usb_endpoint_ss_comp_descriptor *ecomp,
+ enum usb_dev_speed speed)
+{
+ uint32_t max_packet_size;
+ uint32_t max_packet_count;
+ uint8_t type;
+
+ max_packet_size = UGETW(edesc->wMaxPacketSize);
+ max_packet_count = 1;
+ type = (edesc->bmAttributes & UE_XFERTYPE);
+
+ switch (speed) {
+ case USB_SPEED_HIGH:
+ switch (type) {
+ case UE_ISOCHRONOUS:
+ case UE_INTERRUPT:
+ max_packet_count +=
+ (max_packet_size >> 11) & 3;
+
+ /* check for invalid max packet count */
+ if (max_packet_count > 3)
+ max_packet_count = 3;
+ break;
+ default:
+ break;
+ }
+ max_packet_size &= 0x7FF;
+ break;
+ case USB_SPEED_SUPER:
+ max_packet_count += (max_packet_size >> 11) & 3;
+
+ if (ecomp != NULL)
+ max_packet_count += ecomp->bMaxBurst;
+
+ if ((max_packet_count == 0) ||
+ (max_packet_count > 16))
+ max_packet_count = 16;
+
+ switch (type) {
+ case UE_CONTROL:
+ max_packet_count = 1;
+ break;
+ case UE_ISOCHRONOUS:
+ if (ecomp != NULL) {
+ uint8_t mult;
+
+ mult = UE_GET_SS_ISO_MULT(
+ ecomp->bmAttributes) + 1;
+ if (mult > 3)
+ mult = 3;
+
+ max_packet_count *= mult;
+ }
+ break;
+ default:
+ break;
+ }
+ max_packet_size &= 0x7FF;
+ break;
+ default:
+ break;
+ }
+ return (max_packet_size * max_packet_count);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_setup_sub - transfer setup subroutine
+ *
+ * This function must be called from the "xfer_setup" callback of the
+ * USB Host or Device controller driver when setting up an USB
+ * transfer. This function will setup correct packet sizes, buffer
+ * sizes, flags and more, that are stored in the "usb_xfer"
+ * structure.
+ *------------------------------------------------------------------------*/
+void
+usbd_transfer_setup_sub(struct usb_setup_params *parm)
+{
+ enum {
+ REQ_SIZE = 8,
+ MIN_PKT = 8,
+ };
+ struct usb_xfer *xfer = parm->curr_xfer;
+ const struct usb_config *setup = parm->curr_setup;
+ struct usb_endpoint_ss_comp_descriptor *ecomp;
+ struct usb_endpoint_descriptor *edesc;
+ struct usb_std_packet_size std_size;
+ usb_frcount_t n_frlengths;
+ usb_frcount_t n_frbuffers;
+ usb_frcount_t x;
+ uint16_t maxp_old;
+ uint8_t type;
+ uint8_t zmps;
+
+ /*
+ * Sanity check. The following parameters must be initialized before
+ * calling this function.
+ */
+ if ((parm->hc_max_packet_size == 0) ||
+ (parm->hc_max_packet_count == 0) ||
+ (parm->hc_max_frame_size == 0)) {
+ parm->err = USB_ERR_INVAL;
+ goto done;
+ }
+ edesc = xfer->endpoint->edesc;
+ ecomp = xfer->endpoint->ecomp;
+
+ type = (edesc->bmAttributes & UE_XFERTYPE);
+
+ xfer->flags = setup->flags;
+ xfer->nframes = setup->frames;
+ xfer->timeout = setup->timeout;
+ xfer->callback = setup->callback;
+ xfer->interval = setup->interval;
+ xfer->endpointno = edesc->bEndpointAddress;
+ xfer->max_packet_size = UGETW(edesc->wMaxPacketSize);
+ xfer->max_packet_count = 1;
+ /* make a shadow copy: */
+ xfer->flags_int.usb_mode = parm->udev->flags.usb_mode;
+
+ parm->bufsize = setup->bufsize;
+
+ switch (parm->speed) {
+ case USB_SPEED_HIGH:
+ switch (type) {
+ case UE_ISOCHRONOUS:
+ case UE_INTERRUPT:
+ xfer->max_packet_count +=
+ (xfer->max_packet_size >> 11) & 3;
+
+ /* check for invalid max packet count */
+ if (xfer->max_packet_count > 3)
+ xfer->max_packet_count = 3;
+ break;
+ default:
+ break;
+ }
+ xfer->max_packet_size &= 0x7FF;
+ break;
+ case USB_SPEED_SUPER:
+ xfer->max_packet_count += (xfer->max_packet_size >> 11) & 3;
+
+ if (ecomp != NULL)
+ xfer->max_packet_count += ecomp->bMaxBurst;
+
+ if ((xfer->max_packet_count == 0) ||
+ (xfer->max_packet_count > 16))
+ xfer->max_packet_count = 16;
+
+ switch (type) {
+ case UE_CONTROL:
+ xfer->max_packet_count = 1;
+ break;
+ case UE_ISOCHRONOUS:
+ if (ecomp != NULL) {
+ uint8_t mult;
+
+ mult = UE_GET_SS_ISO_MULT(
+ ecomp->bmAttributes) + 1;
+ if (mult > 3)
+ mult = 3;
+
+ xfer->max_packet_count *= mult;
+ }
+ break;
+ default:
+ break;
+ }
+ xfer->max_packet_size &= 0x7FF;
+ break;
+ default:
+ break;
+ }
+ /* range check "max_packet_count" */
+
+ if (xfer->max_packet_count > parm->hc_max_packet_count) {
+ xfer->max_packet_count = parm->hc_max_packet_count;
+ }
+
+ /* store max packet size value before filtering */
+
+ maxp_old = xfer->max_packet_size;
+
+ /* filter "wMaxPacketSize" according to HC capabilities */
+
+ if ((xfer->max_packet_size > parm->hc_max_packet_size) ||
+ (xfer->max_packet_size == 0)) {
+ xfer->max_packet_size = parm->hc_max_packet_size;
+ }
+ /* filter "wMaxPacketSize" according to standard sizes */
+
+ usbd_get_std_packet_size(&std_size, type, parm->speed);
+
+ if (std_size.range.min || std_size.range.max) {
+ if (xfer->max_packet_size < std_size.range.min) {
+ xfer->max_packet_size = std_size.range.min;
+ }
+ if (xfer->max_packet_size > std_size.range.max) {
+ xfer->max_packet_size = std_size.range.max;
+ }
+ } else {
+ if (xfer->max_packet_size >= std_size.fixed[3]) {
+ xfer->max_packet_size = std_size.fixed[3];
+ } else if (xfer->max_packet_size >= std_size.fixed[2]) {
+ xfer->max_packet_size = std_size.fixed[2];
+ } else if (xfer->max_packet_size >= std_size.fixed[1]) {
+ xfer->max_packet_size = std_size.fixed[1];
+ } else {
+ /* only one possibility left */
+ xfer->max_packet_size = std_size.fixed[0];
+ }
+ }
+
+ /*
+ * Check if the max packet size was outside its allowed range
+ * and clamped to a valid value:
+ */
+ if (maxp_old != xfer->max_packet_size)
+ xfer->flags_int.maxp_was_clamped = 1;
+
+ /* compute "max_frame_size" */
+
+ usbd_update_max_frame_size(xfer);
+
+ /* check interrupt interval and transfer pre-delay */
+
+ if (type == UE_ISOCHRONOUS) {
+ uint16_t frame_limit;
+
+ xfer->interval = 0; /* not used, must be zero */
+ xfer->flags_int.isochronous_xfr = 1; /* set flag */
+
+ if (xfer->timeout == 0) {
+ /*
+ * set a default timeout in
+ * case something goes wrong!
+ */
+ xfer->timeout = 1000 / 4;
+ }
+ switch (parm->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ frame_limit = USB_MAX_FS_ISOC_FRAMES_PER_XFER;
+ xfer->fps_shift = 0;
+ break;
+ default:
+ frame_limit = USB_MAX_HS_ISOC_FRAMES_PER_XFER;
+ xfer->fps_shift = edesc->bInterval;
+ if (xfer->fps_shift > 0)
+ xfer->fps_shift--;
+ if (xfer->fps_shift > 3)
+ xfer->fps_shift = 3;
+ if (xfer->flags.pre_scale_frames != 0)
+ xfer->nframes <<= (3 - xfer->fps_shift);
+ break;
+ }
+
+ if (xfer->nframes > frame_limit) {
+ /*
+ * this is not going to work
+ * cross hardware
+ */
+ parm->err = USB_ERR_INVAL;
+ goto done;
+ }
+ if (xfer->nframes == 0) {
+ /*
+ * this is not a valid value
+ */
+ parm->err = USB_ERR_ZERO_NFRAMES;
+ goto done;
+ }
+ } else {
+ /*
+ * If a value is specified use that else check the
+ * endpoint descriptor!
+ */
+ if (type == UE_INTERRUPT) {
+ uint32_t temp;
+
+ if (xfer->interval == 0) {
+ xfer->interval = edesc->bInterval;
+
+ switch (parm->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ break;
+ default:
+ /* 125us -> 1ms */
+ if (xfer->interval < 4)
+ xfer->interval = 1;
+ else if (xfer->interval > 16)
+ xfer->interval = (1 << (16 - 4));
+ else
+ xfer->interval =
+ (1 << (xfer->interval - 4));
+ break;
+ }
+ }
+
+ if (xfer->interval == 0) {
+ /*
+ * One millisecond is the smallest
+ * interval we support:
+ */
+ xfer->interval = 1;
+ }
+
+ xfer->fps_shift = 0;
+ temp = 1;
+
+ while ((temp != 0) && (temp < xfer->interval)) {
+ xfer->fps_shift++;
+ temp *= 2;
+ }
+
+ switch (parm->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ break;
+ default:
+ xfer->fps_shift += 3;
+ break;
+ }
+ }
+ }
+
+ /*
+ * NOTE: we do not allow "max_packet_size" or "max_frame_size"
+ * to be equal to zero when setting up USB transfers, hence
+ * this leads to a lot of extra code in the USB kernel.
+ */
+
+ if ((xfer->max_frame_size == 0) ||
+ (xfer->max_packet_size == 0)) {
+ zmps = 1;
+
+ if ((parm->bufsize <= MIN_PKT) &&
+ (type != UE_CONTROL) &&
+ (type != UE_BULK)) {
+ /* workaround */
+ xfer->max_packet_size = MIN_PKT;
+ xfer->max_packet_count = 1;
+ parm->bufsize = 0; /* automatic setup length */
+ usbd_update_max_frame_size(xfer);
+
+ } else {
+ parm->err = USB_ERR_ZERO_MAXP;
+ goto done;
+ }
+
+ } else {
+ zmps = 0;
+ }
+
+ /*
+ * check if we should setup a default
+ * length:
+ */
+
+ if (parm->bufsize == 0) {
+ parm->bufsize = xfer->max_frame_size;
+
+ if (type == UE_ISOCHRONOUS) {
+ parm->bufsize *= xfer->nframes;
+ }
+ }
+ /*
+ * check if we are about to setup a proxy
+ * type of buffer:
+ */
+
+ if (xfer->flags.proxy_buffer) {
+ /* round bufsize up */
+
+ parm->bufsize += (xfer->max_frame_size - 1);
+
+ if (parm->bufsize < xfer->max_frame_size) {
+ /* length wrapped around */
+ parm->err = USB_ERR_INVAL;
+ goto done;
+ }
+ /* subtract remainder */
+
+ parm->bufsize -= (parm->bufsize % xfer->max_frame_size);
+
+ /* add length of USB device request structure, if any */
+
+ if (type == UE_CONTROL) {
+ parm->bufsize += REQ_SIZE; /* SETUP message */
+ }
+ }
+ xfer->max_data_length = parm->bufsize;
+
+ /* Setup "n_frlengths" and "n_frbuffers" */
+
+ if (type == UE_ISOCHRONOUS) {
+ n_frlengths = xfer->nframes;
+ n_frbuffers = 1;
+ } else {
+ if (type == UE_CONTROL) {
+ xfer->flags_int.control_xfr = 1;
+ if (xfer->nframes == 0) {
+ if (parm->bufsize <= REQ_SIZE) {
+ /*
+ * there will never be any data
+ * stage
+ */
+ xfer->nframes = 1;
+ } else {
+ xfer->nframes = 2;
+ }
+ }
+ } else {
+ if (xfer->nframes == 0) {
+ xfer->nframes = 1;
+ }
+ }
+
+ n_frlengths = xfer->nframes;
+ n_frbuffers = xfer->nframes;
+ }
+
+ /*
+ * check if we have room for the
+ * USB device request structure:
+ */
+
+ if (type == UE_CONTROL) {
+ if (xfer->max_data_length < REQ_SIZE) {
+ /* length wrapped around or too small bufsize */
+ parm->err = USB_ERR_INVAL;
+ goto done;
+ }
+ xfer->max_data_length -= REQ_SIZE;
+ }
+ /*
+ * Setup "frlengths" and shadow "frlengths" for keeping the
+ * initial frame lengths when a USB transfer is complete. This
+ * information is useful when computing isochronous offsets.
+ */
+ xfer->frlengths = parm->xfer_length_ptr;
+ parm->xfer_length_ptr += 2 * n_frlengths;
+
+ /* setup "frbuffers" */
+ xfer->frbuffers = parm->xfer_page_cache_ptr;
+ parm->xfer_page_cache_ptr += n_frbuffers;
+
+ /* initialize max frame count */
+ xfer->max_frame_count = xfer->nframes;
+
+ /*
+ * check if we need to setup
+ * a local buffer:
+ */
+
+ if (!xfer->flags.ext_buffer) {
+#if USB_HAVE_BUSDMA
+ struct usb_page_search page_info;
+ struct usb_page_cache *pc;
+
+ if (usbd_transfer_setup_sub_malloc(parm,
+ &pc, parm->bufsize, 1, 1)) {
+ parm->err = USB_ERR_NOMEM;
+ } else if (parm->buf != NULL) {
+ usbd_get_page(pc, 0, &page_info);
+
+ xfer->local_buffer = page_info.buffer;
+
+ usbd_xfer_set_frame_offset(xfer, 0, 0);
+
+ if ((type == UE_CONTROL) && (n_frbuffers > 1)) {
+ usbd_xfer_set_frame_offset(xfer, REQ_SIZE, 1);
+ }
+ }
+#else
+ /* align data */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ if (parm->buf != NULL) {
+ xfer->local_buffer =
+ USB_ADD_BYTES(parm->buf, parm->size[0]);
+
+ usbd_xfer_set_frame_offset(xfer, 0, 0);
+
+ if ((type == UE_CONTROL) && (n_frbuffers > 1)) {
+ usbd_xfer_set_frame_offset(xfer, REQ_SIZE, 1);
+ }
+ }
+ parm->size[0] += parm->bufsize;
+
+ /* align data again */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+#endif
+ }
+ /*
+ * Compute maximum buffer size
+ */
+
+ if (parm->bufsize_max < parm->bufsize) {
+ parm->bufsize_max = parm->bufsize;
+ }
+#if USB_HAVE_BUSDMA
+ if (xfer->flags_int.bdma_enable) {
+ /*
+ * Setup "dma_page_ptr".
+ *
+ * Proof for formula below:
+ *
+ * Assume there are three USB frames having length "a", "b" and
+ * "c". These USB frames will at maximum need "z"
+ * "usb_page" structures. "z" is given by:
+ *
+ * z = ((a / USB_PAGE_SIZE) + 2) + ((b / USB_PAGE_SIZE) + 2) +
+ * ((c / USB_PAGE_SIZE) + 2);
+ *
+ * Constraining "a", "b" and "c" like this:
+ *
+ * (a + b + c) <= parm->bufsize
+ *
+ * We know that:
+ *
+ * z <= ((parm->bufsize / USB_PAGE_SIZE) + (3*2));
+ *
+ * Here is the general formula:
+ */
+ xfer->dma_page_ptr = parm->dma_page_ptr;
+ parm->dma_page_ptr += (2 * n_frbuffers);
+ parm->dma_page_ptr += (parm->bufsize / USB_PAGE_SIZE);
+ }
+#endif
+ if (zmps) {
+ /* correct maximum data length */
+ xfer->max_data_length = 0;
+ }
+ /* subtract USB frame remainder from "hc_max_frame_size" */
+
+ xfer->max_hc_frame_size =
+ (parm->hc_max_frame_size -
+ (parm->hc_max_frame_size % xfer->max_frame_size));
+
+ if (xfer->max_hc_frame_size == 0) {
+ parm->err = USB_ERR_INVAL;
+ goto done;
+ }
+
+ /* initialize frame buffers */
+
+ if (parm->buf) {
+ for (x = 0; x != n_frbuffers; x++) {
+ xfer->frbuffers[x].tag_parent =
+ &xfer->xroot->dma_parent_tag;
+#if USB_HAVE_BUSDMA
+ if (xfer->flags_int.bdma_enable &&
+ (parm->bufsize_max > 0)) {
+ if (usb_pc_dmamap_create(
+ xfer->frbuffers + x,
+ parm->bufsize_max)) {
+ parm->err = USB_ERR_NOMEM;
+ goto done;
+ }
+ }
+#endif
+ }
+ }
+done:
+ if (parm->err) {
+ /*
+ * Set some dummy values so that we avoid division by zero:
+ */
+ xfer->max_hc_frame_size = 1;
+ xfer->max_frame_size = 1;
+ xfer->max_packet_size = 1;
+ xfer->max_data_length = 0;
+ xfer->nframes = 0;
+ xfer->max_frame_count = 0;
+ }
+}
+
+static uint8_t
+usbd_transfer_setup_has_bulk(const struct usb_config *setup_start,
+ uint16_t n_setup)
+{
+ while (n_setup--) {
+ uint8_t type = setup_start[n_setup].type;
+ if (type == UE_BULK || type == UE_BULK_INTR ||
+ type == UE_TYPE_ANY)
+ return (1);
+ }
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_setup - setup an array of USB transfers
+ *
+ * NOTE: You must always call "usbd_transfer_unsetup" after calling
+ * "usbd_transfer_setup" if success was returned.
+ *
+ * The idea is that the USB device driver should pre-allocate all its
+ * transfers by one call to this function.
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+usb_error_t
+usbd_transfer_setup(struct usb_device *udev,
+ const uint8_t *ifaces, struct usb_xfer **ppxfer,
+ const struct usb_config *setup_start, uint16_t n_setup,
+ void *priv_sc, struct mtx *xfer_mtx)
+{
+ const struct usb_config *setup_end = setup_start + n_setup;
+ const struct usb_config *setup;
+ struct usb_setup_params *parm;
+ struct usb_endpoint *ep;
+ struct usb_xfer_root *info;
+ struct usb_xfer *xfer;
+ void *buf = NULL;
+ usb_error_t error = 0;
+ uint16_t n;
+ uint16_t refcount;
+ uint8_t do_unlock;
+
+ WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL,
+ "usbd_transfer_setup can sleep!");
+
+ /* do some checking first */
+
+ if (n_setup == 0) {
+ DPRINTFN(6, "setup array has zero length!\n");
+ return (USB_ERR_INVAL);
+ }
+ if (ifaces == NULL) {
+ DPRINTFN(6, "ifaces array is NULL!\n");
+ return (USB_ERR_INVAL);
+ }
+ if (xfer_mtx == NULL) {
+ DPRINTFN(6, "using global lock\n");
+ xfer_mtx = &Giant;
+ }
+
+ /* more sanity checks */
+
+ for (setup = setup_start, n = 0;
+ setup != setup_end; setup++, n++) {
+ if (setup->bufsize == (usb_frlength_t)-1) {
+ error = USB_ERR_BAD_BUFSIZE;
+ DPRINTF("invalid bufsize\n");
+ }
+ if (setup->callback == NULL) {
+ error = USB_ERR_NO_CALLBACK;
+ DPRINTF("no callback\n");
+ }
+ ppxfer[n] = NULL;
+ }
+
+ if (error)
+ return (error);
+
+ /* Protect scratch area */
+ do_unlock = usbd_ctrl_lock(udev);
+
+ refcount = 0;
+ info = NULL;
+
+ parm = &udev->scratch.xfer_setup[0].parm;
+ memset(parm, 0, sizeof(*parm));
+
+ parm->udev = udev;
+ parm->speed = usbd_get_speed(udev);
+ parm->hc_max_packet_count = 1;
+
+ if (parm->speed >= USB_SPEED_MAX) {
+ parm->err = USB_ERR_INVAL;
+ goto done;
+ }
+ /* setup all transfers */
+
+ while (1) {
+ if (buf) {
+ /*
+ * Initialize the "usb_xfer_root" structure,
+ * which is common for all our USB transfers.
+ */
+ info = USB_ADD_BYTES(buf, 0);
+
+ info->memory_base = buf;
+ info->memory_size = parm->size[0];
+
+#if USB_HAVE_BUSDMA
+ info->dma_page_cache_start = USB_ADD_BYTES(buf, parm->size[4]);
+ info->dma_page_cache_end = USB_ADD_BYTES(buf, parm->size[5]);
+#endif
+ info->xfer_page_cache_start = USB_ADD_BYTES(buf, parm->size[5]);
+ info->xfer_page_cache_end = USB_ADD_BYTES(buf, parm->size[2]);
+
+ cv_init(&info->cv_drain, "WDRAIN");
+
+ info->xfer_mtx = xfer_mtx;
+#if USB_HAVE_BUSDMA
+ usb_dma_tag_setup(&info->dma_parent_tag,
+ parm->dma_tag_p, udev->bus->dma_parent_tag[0].tag,
+ xfer_mtx, &usb_bdma_done_event, udev->bus->dma_bits,
+ parm->dma_tag_max);
+#endif
+
+ info->bus = udev->bus;
+ info->udev = udev;
+
+ TAILQ_INIT(&info->done_q.head);
+ info->done_q.command = &usbd_callback_wrapper;
+#if USB_HAVE_BUSDMA
+ TAILQ_INIT(&info->dma_q.head);
+ info->dma_q.command = &usb_bdma_work_loop;
+#endif
+ info->done_m[0].hdr.pm_callback = &usb_callback_proc;
+ info->done_m[0].xroot = info;
+ info->done_m[1].hdr.pm_callback = &usb_callback_proc;
+ info->done_m[1].xroot = info;
+
+ /*
+ * In device side mode control endpoint
+ * requests need to run from a separate
+ * context, else there is a chance of
+ * deadlock!
+ */
+ if (setup_start == usb_control_ep_cfg ||
+ setup_start == usb_control_ep_quirk_cfg)
+ info->done_p =
+ USB_BUS_CONTROL_XFER_PROC(udev->bus);
+ else if (xfer_mtx == &Giant)
+ info->done_p =
+ USB_BUS_GIANT_PROC(udev->bus);
+ else if (usbd_transfer_setup_has_bulk(setup_start, n_setup))
+ info->done_p =
+ USB_BUS_NON_GIANT_BULK_PROC(udev->bus);
+ else
+ info->done_p =
+ USB_BUS_NON_GIANT_ISOC_PROC(udev->bus);
+ }
+ /* reset sizes */
+
+ parm->size[0] = 0;
+ parm->buf = buf;
+ parm->size[0] += sizeof(info[0]);
+
+ for (setup = setup_start, n = 0;
+ setup != setup_end; setup++, n++) {
+ /* skip USB transfers without callbacks: */
+ if (setup->callback == NULL) {
+ continue;
+ }
+ /* see if there is a matching endpoint */
+ ep = usbd_get_endpoint(udev,
+ ifaces[setup->if_index], setup);
+
+ /*
+ * Check that the USB PIPE is valid and that
+ * the endpoint mode is proper.
+ *
+ * Make sure we don't allocate a streams
+ * transfer when such a combination is not
+ * valid.
+ */
+ if ((ep == NULL) || (ep->methods == NULL) ||
+ ((ep->ep_mode != USB_EP_MODE_STREAMS) &&
+ (ep->ep_mode != USB_EP_MODE_DEFAULT)) ||
+ (setup->stream_id != 0 &&
+ (setup->stream_id >= USB_MAX_EP_STREAMS ||
+ (ep->ep_mode != USB_EP_MODE_STREAMS)))) {
+ if (setup->flags.no_pipe_ok)
+ continue;
+ if ((setup->usb_mode != USB_MODE_DUAL) &&
+ (setup->usb_mode != udev->flags.usb_mode))
+ continue;
+ parm->err = USB_ERR_NO_PIPE;
+ goto done;
+ }
+
+ /* align data properly */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ /* store current setup pointer */
+ parm->curr_setup = setup;
+
+ if (buf) {
+ /*
+ * Common initialization of the
+ * "usb_xfer" structure.
+ */
+ xfer = USB_ADD_BYTES(buf, parm->size[0]);
+ xfer->address = udev->address;
+ xfer->priv_sc = priv_sc;
+ xfer->xroot = info;
+
+ usb_callout_init_mtx(&xfer->timeout_handle,
+ &udev->bus->bus_mtx, 0);
+ } else {
+ /*
+ * Setup a dummy xfer, hence we are
+ * writing to the "usb_xfer"
+ * structure pointed to by "xfer"
+ * before we have allocated any
+ * memory:
+ */
+ xfer = &udev->scratch.xfer_setup[0].dummy;
+ memset(xfer, 0, sizeof(*xfer));
+ refcount++;
+ }
+
+ /* set transfer endpoint pointer */
+ xfer->endpoint = ep;
+
+ /* set transfer stream ID */
+ xfer->stream_id = setup->stream_id;
+
+ parm->size[0] += sizeof(xfer[0]);
+ parm->methods = xfer->endpoint->methods;
+ parm->curr_xfer = xfer;
+
+ /*
+ * Call the Host or Device controller transfer
+ * setup routine:
+ */
+ (udev->bus->methods->xfer_setup) (parm);
+
+ /* check for error */
+ if (parm->err)
+ goto done;
+
+ if (buf) {
+ /*
+ * Increment the endpoint refcount. This
+ * basically prevents setting a new
+ * configuration and alternate setting
+ * when USB transfers are in use on
+ * the given interface. Search the USB
+ * code for "endpoint->refcount_alloc" if you
+ * want more information.
+ */
+ USB_BUS_LOCK(info->bus);
+ if (xfer->endpoint->refcount_alloc >= USB_EP_REF_MAX)
+ parm->err = USB_ERR_INVAL;
+
+ xfer->endpoint->refcount_alloc++;
+
+ if (xfer->endpoint->refcount_alloc == 0)
+ panic("usbd_transfer_setup(): Refcount wrapped to zero\n");
+ USB_BUS_UNLOCK(info->bus);
+
+ /*
+ * Whenever we set ppxfer[] then we
+ * also need to increment the
+ * "setup_refcount":
+ */
+ info->setup_refcount++;
+
+ /*
+ * Transfer is successfully setup and
+ * can be used:
+ */
+ ppxfer[n] = xfer;
+ }
+
+ /* check for error */
+ if (parm->err)
+ goto done;
+ }
+
+ if (buf != NULL || parm->err != 0)
+ goto done;
+
+ /* if no transfers, nothing to do */
+ if (refcount == 0)
+ goto done;
+
+ /* align data properly */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ /* store offset temporarily */
+ parm->size[1] = parm->size[0];
+
+ /*
+ * The number of DMA tags required depends on
+ * the number of endpoints. The current estimate
+ * for maximum number of DMA tags per endpoint
+ * is three:
+ * 1) for loading memory
+ * 2) for allocating memory
+ * 3) for fixing memory [UHCI]
+ */
+ parm->dma_tag_max += 3 * MIN(n_setup, USB_EP_MAX);
+
+ /*
+ * DMA tags for QH, TD, Data and more.
+ */
+ parm->dma_tag_max += 8;
+
+ parm->dma_tag_p += parm->dma_tag_max;
+
+ parm->size[0] += ((uint8_t *)parm->dma_tag_p) -
+ ((uint8_t *)0);
+
+ /* align data properly */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ /* store offset temporarily */
+ parm->size[3] = parm->size[0];
+
+ parm->size[0] += ((uint8_t *)parm->dma_page_ptr) -
+ ((uint8_t *)0);
+
+ /* align data properly */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ /* store offset temporarily */
+ parm->size[4] = parm->size[0];
+
+ parm->size[0] += ((uint8_t *)parm->dma_page_cache_ptr) -
+ ((uint8_t *)0);
+
+ /* store end offset temporarily */
+ parm->size[5] = parm->size[0];
+
+ parm->size[0] += ((uint8_t *)parm->xfer_page_cache_ptr) -
+ ((uint8_t *)0);
+
+ /* store end offset temporarily */
+
+ parm->size[2] = parm->size[0];
+
+ /* align data properly */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ parm->size[6] = parm->size[0];
+
+ parm->size[0] += ((uint8_t *)parm->xfer_length_ptr) -
+ ((uint8_t *)0);
+
+ /* align data properly */
+ parm->size[0] += ((-parm->size[0]) & (USB_HOST_ALIGN - 1));
+
+ /* allocate zeroed memory */
+ buf = malloc(parm->size[0], M_USB, M_WAITOK | M_ZERO);
+#if (USB_HAVE_MALLOC_WAITOK == 0)
+ if (buf == NULL) {
+ parm->err = USB_ERR_NOMEM;
+ DPRINTFN(0, "cannot allocate memory block for "
+ "configuration (%d bytes)\n",
+ parm->size[0]);
+ goto done;
+ }
+#endif
+ parm->dma_tag_p = USB_ADD_BYTES(buf, parm->size[1]);
+ parm->dma_page_ptr = USB_ADD_BYTES(buf, parm->size[3]);
+ parm->dma_page_cache_ptr = USB_ADD_BYTES(buf, parm->size[4]);
+ parm->xfer_page_cache_ptr = USB_ADD_BYTES(buf, parm->size[5]);
+ parm->xfer_length_ptr = USB_ADD_BYTES(buf, parm->size[6]);
+ }
+
+done:
+ if (buf) {
+ if (info->setup_refcount == 0) {
+ /*
+ * "usbd_transfer_unsetup_sub" will unlock
+ * the bus mutex before returning !
+ */
+ USB_BUS_LOCK(info->bus);
+
+ /* something went wrong */
+ usbd_transfer_unsetup_sub(info, 0);
+ }
+ }
+
+ /* check if any errors happened */
+ if (parm->err)
+ usbd_transfer_unsetup(ppxfer, n_setup);
+
+ error = parm->err;
+
+ if (do_unlock)
+ usbd_ctrl_unlock(udev);
+
+ return (error);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_unsetup_sub - factored out code
+ *------------------------------------------------------------------------*/
+static void
+usbd_transfer_unsetup_sub(struct usb_xfer_root *info, uint8_t needs_delay)
+{
+#if USB_HAVE_BUSDMA
+ struct usb_page_cache *pc;
+#endif
+
+ USB_BUS_LOCK_ASSERT(info->bus, MA_OWNED);
+
+ /* wait for any outstanding DMA operations */
+
+ if (needs_delay) {
+ usb_timeout_t temp;
+ temp = usbd_get_dma_delay(info->udev);
+ if (temp != 0) {
+ usb_pause_mtx(&info->bus->bus_mtx,
+ USB_MS_TO_TICKS(temp));
+ }
+ }
+
+ /* make sure that our done messages are not queued anywhere */
+ usb_proc_mwait(info->done_p, &info->done_m[0], &info->done_m[1]);
+
+ USB_BUS_UNLOCK(info->bus);
+
+#if USB_HAVE_BUSDMA
+ /* free DMA'able memory, if any */
+ pc = info->dma_page_cache_start;
+ while (pc != info->dma_page_cache_end) {
+ usb_pc_free_mem(pc);
+ pc++;
+ }
+
+ /* free DMA maps in all "xfer->frbuffers" */
+ pc = info->xfer_page_cache_start;
+ while (pc != info->xfer_page_cache_end) {
+ usb_pc_dmamap_destroy(pc);
+ pc++;
+ }
+
+ /* free all DMA tags */
+ usb_dma_tag_unsetup(&info->dma_parent_tag);
+#endif
+
+ cv_destroy(&info->cv_drain);
+
+ /*
+ * free the "memory_base" last, hence the "info" structure is
+ * contained within the "memory_base"!
+ */
+ free(info->memory_base, M_USB);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_unsetup - unsetup/free an array of USB transfers
+ *
+ * NOTE: All USB transfers in progress will get called back passing
+ * the error code "USB_ERR_CANCELLED" before this function
+ * returns.
+ *------------------------------------------------------------------------*/
+void
+usbd_transfer_unsetup(struct usb_xfer **pxfer, uint16_t n_setup)
+{
+ struct usb_xfer *xfer;
+ struct usb_xfer_root *info;
+ uint8_t needs_delay = 0;
+
+ WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL,
+ "usbd_transfer_unsetup can sleep!");
+
+ while (n_setup--) {
+ xfer = pxfer[n_setup];
+
+ if (xfer == NULL)
+ continue;
+
+ info = xfer->xroot;
+
+ USB_XFER_LOCK(xfer);
+ USB_BUS_LOCK(info->bus);
+
+ /*
+ * HINT: when you start/stop a transfer, it might be a
+ * good idea to directly use the "pxfer[]" structure:
+ *
+ * usbd_transfer_start(sc->pxfer[0]);
+ * usbd_transfer_stop(sc->pxfer[0]);
+ *
+ * That way, if your code has many parts that will not
+ * stop running under the same lock, in other words
+ * "xfer_mtx", the usbd_transfer_start and
+ * usbd_transfer_stop functions will simply return
+ * when they detect a NULL pointer argument.
+ *
+ * To avoid any races we clear the "pxfer[]" pointer
+ * while holding the private mutex of the driver:
+ */
+ pxfer[n_setup] = NULL;
+
+ USB_BUS_UNLOCK(info->bus);
+ USB_XFER_UNLOCK(xfer);
+
+ usbd_transfer_drain(xfer);
+
+#if USB_HAVE_BUSDMA
+ if (xfer->flags_int.bdma_enable)
+ needs_delay = 1;
+#endif
+ /*
+ * NOTE: default endpoint does not have an
+ * interface, even if endpoint->iface_index == 0
+ */
+ USB_BUS_LOCK(info->bus);
+ xfer->endpoint->refcount_alloc--;
+ USB_BUS_UNLOCK(info->bus);
+
+ usb_callout_drain(&xfer->timeout_handle);
+
+ USB_BUS_LOCK(info->bus);
+
+ USB_ASSERT(info->setup_refcount != 0, ("Invalid setup "
+ "reference count\n"));
+
+ info->setup_refcount--;
+
+ if (info->setup_refcount == 0) {
+ usbd_transfer_unsetup_sub(info,
+ needs_delay);
+ } else {
+ USB_BUS_UNLOCK(info->bus);
+ }
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_control_transfer_init - factored out code
+ *
+ * In USB Device Mode we have to wait for the SETUP packet which
+ * containst the "struct usb_device_request" structure, before we can
+ * transfer any data. In USB Host Mode we already have the SETUP
+ * packet at the moment the USB transfer is started. This leads us to
+ * having to setup the USB transfer at two different places in
+ * time. This function just contains factored out control transfer
+ * initialisation code, so that we don't duplicate the code.
+ *------------------------------------------------------------------------*/
+static void
+usbd_control_transfer_init(struct usb_xfer *xfer)
+{
+ struct usb_device_request req;
+
+ /* copy out the USB request header */
+
+ usbd_copy_out(xfer->frbuffers, 0, &req, sizeof(req));
+
+ /* setup remainder */
+
+ xfer->flags_int.control_rem = UGETW(req.wLength);
+
+ /* copy direction to endpoint variable */
+
+ xfer->endpointno &= ~(UE_DIR_IN | UE_DIR_OUT);
+ xfer->endpointno |=
+ (req.bmRequestType & UT_READ) ? UE_DIR_IN : UE_DIR_OUT;
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_control_transfer_did_data
+ *
+ * This function returns non-zero if a control endpoint has
+ * transferred the first DATA packet after the SETUP packet.
+ * Else it returns zero.
+ *------------------------------------------------------------------------*/
+static uint8_t
+usbd_control_transfer_did_data(struct usb_xfer *xfer)
+{
+ struct usb_device_request req;
+
+ /* SETUP packet is not yet sent */
+ if (xfer->flags_int.control_hdr != 0)
+ return (0);
+
+ /* copy out the USB request header */
+ usbd_copy_out(xfer->frbuffers, 0, &req, sizeof(req));
+
+ /* compare remainder to the initial value */
+ return (xfer->flags_int.control_rem != UGETW(req.wLength));
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_setup_ctrl_transfer
+ *
+ * This function handles initialisation of control transfers. Control
+ * transfers are special in that regard that they can both transmit
+ * and receive data.
+ *
+ * Return values:
+ * 0: Success
+ * Else: Failure
+ *------------------------------------------------------------------------*/
+static int
+usbd_setup_ctrl_transfer(struct usb_xfer *xfer)
+{
+ usb_frlength_t len;
+
+ /* Check for control endpoint stall */
+ if (xfer->flags.stall_pipe && xfer->flags_int.control_act) {
+ /* the control transfer is no longer active */
+ xfer->flags_int.control_stall = 1;
+ xfer->flags_int.control_act = 0;
+ } else {
+ /* don't stall control transfer by default */
+ xfer->flags_int.control_stall = 0;
+ }
+
+ /* Check for invalid number of frames */
+ if (xfer->nframes > 2) {
+ /*
+ * If you need to split a control transfer, you
+ * have to do one part at a time. Only with
+ * non-control transfers you can do multiple
+ * parts a time.
+ */
+ DPRINTFN(0, "Too many frames: %u\n",
+ (unsigned)xfer->nframes);
+ goto error;
+ }
+
+ /*
+ * Check if there is a control
+ * transfer in progress:
+ */
+ if (xfer->flags_int.control_act) {
+ if (xfer->flags_int.control_hdr) {
+ /* clear send header flag */
+
+ xfer->flags_int.control_hdr = 0;
+
+ /* setup control transfer */
+ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
+ usbd_control_transfer_init(xfer);
+ }
+ }
+ /* get data length */
+
+ len = xfer->sumlen;
+
+ } else {
+ /* the size of the SETUP structure is hardcoded ! */
+
+ if (xfer->frlengths[0] != sizeof(struct usb_device_request)) {
+ DPRINTFN(0, "Wrong framelength %u != %zu\n",
+ xfer->frlengths[0], sizeof(struct
+ usb_device_request));
+ goto error;
+ }
+ /* check USB mode */
+ if (xfer->flags_int.usb_mode == USB_MODE_DEVICE) {
+ /* check number of frames */
+ if (xfer->nframes != 1) {
+ /*
+ * We need to receive the setup
+ * message first so that we know the
+ * data direction!
+ */
+ DPRINTF("Misconfigured transfer\n");
+ goto error;
+ }
+ /*
+ * Set a dummy "control_rem" value. This
+ * variable will be overwritten later by a
+ * call to "usbd_control_transfer_init()" !
+ */
+ xfer->flags_int.control_rem = 0xFFFF;
+ } else {
+ /* setup "endpoint" and "control_rem" */
+
+ usbd_control_transfer_init(xfer);
+ }
+
+ /* set transfer-header flag */
+
+ xfer->flags_int.control_hdr = 1;
+
+ /* get data length */
+
+ len = (xfer->sumlen - sizeof(struct usb_device_request));
+ }
+
+ /* update did data flag */
+
+ xfer->flags_int.control_did_data =
+ usbd_control_transfer_did_data(xfer);
+
+ /* check if there is a length mismatch */
+
+ if (len > xfer->flags_int.control_rem) {
+ DPRINTFN(0, "Length (%d) greater than "
+ "remaining length (%d)\n", len,
+ xfer->flags_int.control_rem);
+ goto error;
+ }
+ /* check if we are doing a short transfer */
+
+ if (xfer->flags.force_short_xfer) {
+ xfer->flags_int.control_rem = 0;
+ } else {
+ if ((len != xfer->max_data_length) &&
+ (len != xfer->flags_int.control_rem) &&
+ (xfer->nframes != 1)) {
+ DPRINTFN(0, "Short control transfer without "
+ "force_short_xfer set\n");
+ goto error;
+ }
+ xfer->flags_int.control_rem -= len;
+ }
+
+ /* the status part is executed when "control_act" is 0 */
+
+ if ((xfer->flags_int.control_rem > 0) ||
+ (xfer->flags.manual_status)) {
+ /* don't execute the STATUS stage yet */
+ xfer->flags_int.control_act = 1;
+
+ /* sanity check */
+ if ((!xfer->flags_int.control_hdr) &&
+ (xfer->nframes == 1)) {
+ /*
+ * This is not a valid operation!
+ */
+ DPRINTFN(0, "Invalid parameter "
+ "combination\n");
+ goto error;
+ }
+ } else {
+ /* time to execute the STATUS stage */
+ xfer->flags_int.control_act = 0;
+ }
+ return (0); /* success */
+
+error:
+ return (1); /* failure */
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_submit - start USB hardware for the given transfer
+ *
+ * This function should only be called from the USB callback.
+ *------------------------------------------------------------------------*/
+void
+usbd_transfer_submit(struct usb_xfer *xfer)
+{
+ struct usb_xfer_root *info;
+ struct usb_bus *bus;
+ usb_frcount_t x;
+
+ info = xfer->xroot;
+ bus = info->bus;
+
+ DPRINTF("xfer=%p, endpoint=%p, nframes=%d, dir=%s\n",
+ xfer, xfer->endpoint, xfer->nframes, USB_GET_DATA_ISREAD(xfer) ?
+ "read" : "write");
+
+#ifdef USB_DEBUG
+ if (USB_DEBUG_VAR > 0) {
+ USB_BUS_LOCK(bus);
+
+ usb_dump_endpoint(xfer->endpoint);
+
+ USB_BUS_UNLOCK(bus);
+ }
+#endif
+
+ USB_XFER_LOCK_ASSERT(xfer, MA_OWNED);
+ USB_BUS_LOCK_ASSERT(bus, MA_NOTOWNED);
+
+ /* Only open the USB transfer once! */
+ if (!xfer->flags_int.open) {
+ xfer->flags_int.open = 1;
+
+ DPRINTF("open\n");
+
+ USB_BUS_LOCK(bus);
+ (xfer->endpoint->methods->open) (xfer);
+ USB_BUS_UNLOCK(bus);
+ }
+ /* set "transferring" flag */
+ xfer->flags_int.transferring = 1;
+
+#if USB_HAVE_POWERD
+ /* increment power reference */
+ usbd_transfer_power_ref(xfer, 1);
+#endif
+ /*
+ * Check if the transfer is waiting on a queue, most
+ * frequently the "done_q":
+ */
+ if (xfer->wait_queue) {
+ USB_BUS_LOCK(bus);
+ usbd_transfer_dequeue(xfer);
+ USB_BUS_UNLOCK(bus);
+ }
+ /* clear "did_dma_delay" flag */
+ xfer->flags_int.did_dma_delay = 0;
+
+ /* clear "did_close" flag */
+ xfer->flags_int.did_close = 0;
+
+#if USB_HAVE_BUSDMA
+ /* clear "bdma_setup" flag */
+ xfer->flags_int.bdma_setup = 0;
+#endif
+ /* by default we cannot cancel any USB transfer immediately */
+ xfer->flags_int.can_cancel_immed = 0;
+
+ /* clear lengths and frame counts by default */
+ xfer->sumlen = 0;
+ xfer->actlen = 0;
+ xfer->aframes = 0;
+
+ /* clear any previous errors */
+ xfer->error = 0;
+
+ /* Check if the device is still alive */
+ if (info->udev->state < USB_STATE_POWERED) {
+ USB_BUS_LOCK(bus);
+ /*
+ * Must return cancelled error code else
+ * device drivers can hang.
+ */
+ usbd_transfer_done(xfer, USB_ERR_CANCELLED);
+ USB_BUS_UNLOCK(bus);
+ return;
+ }
+
+ /* sanity check */
+ if (xfer->nframes == 0) {
+ if (xfer->flags.stall_pipe) {
+ /*
+ * Special case - want to stall without transferring
+ * any data:
+ */
+ DPRINTF("xfer=%p nframes=0: stall "
+ "or clear stall!\n", xfer);
+ USB_BUS_LOCK(bus);
+ xfer->flags_int.can_cancel_immed = 1;
+ /* start the transfer */
+ usb_command_wrapper(&xfer->endpoint->
+ endpoint_q[xfer->stream_id], xfer);
+ USB_BUS_UNLOCK(bus);
+ return;
+ }
+ USB_BUS_LOCK(bus);
+ usbd_transfer_done(xfer, USB_ERR_INVAL);
+ USB_BUS_UNLOCK(bus);
+ return;
+ }
+ /* compute some variables */
+
+ for (x = 0; x != xfer->nframes; x++) {
+ /* make a copy of the frlenghts[] */
+ xfer->frlengths[x + xfer->max_frame_count] = xfer->frlengths[x];
+ /* compute total transfer length */
+ xfer->sumlen += xfer->frlengths[x];
+ if (xfer->sumlen < xfer->frlengths[x]) {
+ /* length wrapped around */
+ USB_BUS_LOCK(bus);
+ usbd_transfer_done(xfer, USB_ERR_INVAL);
+ USB_BUS_UNLOCK(bus);
+ return;
+ }
+ }
+
+ /* clear some internal flags */
+
+ xfer->flags_int.short_xfer_ok = 0;
+ xfer->flags_int.short_frames_ok = 0;
+
+ /* check if this is a control transfer */
+
+ if (xfer->flags_int.control_xfr) {
+ if (usbd_setup_ctrl_transfer(xfer)) {
+ USB_BUS_LOCK(bus);
+ usbd_transfer_done(xfer, USB_ERR_STALLED);
+ USB_BUS_UNLOCK(bus);
+ return;
+ }
+ }
+ /*
+ * Setup filtered version of some transfer flags,
+ * in case of data read direction
+ */
+ if (USB_GET_DATA_ISREAD(xfer)) {
+ if (xfer->flags.short_frames_ok) {
+ xfer->flags_int.short_xfer_ok = 1;
+ xfer->flags_int.short_frames_ok = 1;
+ } else if (xfer->flags.short_xfer_ok) {
+ xfer->flags_int.short_xfer_ok = 1;
+
+ /* check for control transfer */
+ if (xfer->flags_int.control_xfr) {
+ /*
+ * 1) Control transfers do not support
+ * reception of multiple short USB
+ * frames in host mode and device side
+ * mode, with exception of:
+ *
+ * 2) Due to sometimes buggy device
+ * side firmware we need to do a
+ * STATUS stage in case of short
+ * control transfers in USB host mode.
+ * The STATUS stage then becomes the
+ * "alt_next" to the DATA stage.
+ */
+ xfer->flags_int.short_frames_ok = 1;
+ }
+ }
+ }
+ /*
+ * Check if BUS-DMA support is enabled and try to load virtual
+ * buffers into DMA, if any:
+ */
+#if USB_HAVE_BUSDMA
+ if (xfer->flags_int.bdma_enable) {
+ /* insert the USB transfer last in the BUS-DMA queue */
+ usb_command_wrapper(&xfer->xroot->dma_q, xfer);
+ return;
+ }
+#endif
+ /*
+ * Enter the USB transfer into the Host Controller or
+ * Device Controller schedule:
+ */
+ usbd_pipe_enter(xfer);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_pipe_enter - factored out code
+ *------------------------------------------------------------------------*/
+void
+usbd_pipe_enter(struct usb_xfer *xfer)
+{
+ struct usb_endpoint *ep;
+
+ USB_XFER_LOCK_ASSERT(xfer, MA_OWNED);
+
+ USB_BUS_LOCK(xfer->xroot->bus);
+
+ ep = xfer->endpoint;
+
+ DPRINTF("enter\n");
+
+ /* the transfer can now be cancelled */
+ xfer->flags_int.can_cancel_immed = 1;
+
+ /* enter the transfer */
+ (ep->methods->enter) (xfer);
+
+ /* check for transfer error */
+ if (xfer->error) {
+ /* some error has happened */
+ usbd_transfer_done(xfer, 0);
+ USB_BUS_UNLOCK(xfer->xroot->bus);
+ return;
+ }
+
+ /* start the transfer */
+ usb_command_wrapper(&ep->endpoint_q[xfer->stream_id], xfer);
+ USB_BUS_UNLOCK(xfer->xroot->bus);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_start - start an USB transfer
+ *
+ * NOTE: Calling this function more than one time will only
+ * result in a single transfer start, until the USB transfer
+ * completes.
+ *------------------------------------------------------------------------*/
+void
+usbd_transfer_start(struct usb_xfer *xfer)
+{
+ if (xfer == NULL) {
+ /* transfer is gone */
+ return;
+ }
+ USB_XFER_LOCK_ASSERT(xfer, MA_OWNED);
+
+ /* mark the USB transfer started */
+
+ if (!xfer->flags_int.started) {
+ /* lock the BUS lock to avoid races updating flags_int */
+ USB_BUS_LOCK(xfer->xroot->bus);
+ xfer->flags_int.started = 1;
+ USB_BUS_UNLOCK(xfer->xroot->bus);
+ }
+ /* check if the USB transfer callback is already transferring */
+
+ if (xfer->flags_int.transferring) {
+ return;
+ }
+ USB_BUS_LOCK(xfer->xroot->bus);
+ /* call the USB transfer callback */
+ usbd_callback_ss_done_defer(xfer);
+ USB_BUS_UNLOCK(xfer->xroot->bus);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_stop - stop an USB transfer
+ *
+ * NOTE: Calling this function more than one time will only
+ * result in a single transfer stop.
+ * NOTE: When this function returns it is not safe to free nor
+ * reuse any DMA buffers. See "usbd_transfer_drain()".
+ *------------------------------------------------------------------------*/
+void
+usbd_transfer_stop(struct usb_xfer *xfer)
+{
+ struct usb_endpoint *ep;
+
+ if (xfer == NULL) {
+ /* transfer is gone */
+ return;
+ }
+ USB_XFER_LOCK_ASSERT(xfer, MA_OWNED);
+
+ /* check if the USB transfer was ever opened */
+
+ if (!xfer->flags_int.open) {
+ if (xfer->flags_int.started) {
+ /* nothing to do except clearing the "started" flag */
+ /* lock the BUS lock to avoid races updating flags_int */
+ USB_BUS_LOCK(xfer->xroot->bus);
+ xfer->flags_int.started = 0;
+ USB_BUS_UNLOCK(xfer->xroot->bus);
+ }
+ return;
+ }
+ /* try to stop the current USB transfer */
+
+ USB_BUS_LOCK(xfer->xroot->bus);
+ /* override any previous error */
+ xfer->error = USB_ERR_CANCELLED;
+
+ /*
+ * Clear "open" and "started" when both private and USB lock
+ * is locked so that we don't get a race updating "flags_int"
+ */
+ xfer->flags_int.open = 0;
+ xfer->flags_int.started = 0;
+
+ /*
+ * Check if we can cancel the USB transfer immediately.
+ */
+ if (xfer->flags_int.transferring) {
+ if (xfer->flags_int.can_cancel_immed &&
+ (!xfer->flags_int.did_close)) {
+ DPRINTF("close\n");
+ /*
+ * The following will lead to an USB_ERR_CANCELLED
+ * error code being passed to the USB callback.
+ */
+ (xfer->endpoint->methods->close) (xfer);
+ /* only close once */
+ xfer->flags_int.did_close = 1;
+ } else {
+ /* need to wait for the next done callback */
+ }
+ } else {
+ DPRINTF("close\n");
+
+ /* close here and now */
+ (xfer->endpoint->methods->close) (xfer);
+
+ /*
+ * Any additional DMA delay is done by
+ * "usbd_transfer_unsetup()".
+ */
+
+ /*
+ * Special case. Check if we need to restart a blocked
+ * endpoint.
+ */
+ ep = xfer->endpoint;
+
+ /*
+ * If the current USB transfer is completing we need
+ * to start the next one:
+ */
+ if (ep->endpoint_q[xfer->stream_id].curr == xfer) {
+ usb_command_wrapper(
+ &ep->endpoint_q[xfer->stream_id], NULL);
+ }
+ }
+
+ USB_BUS_UNLOCK(xfer->xroot->bus);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_pending
+ *
+ * This function will check if an USB transfer is pending which is a
+ * little bit complicated!
+ * Return values:
+ * 0: Not pending
+ * 1: Pending: The USB transfer will receive a callback in the future.
+ *------------------------------------------------------------------------*/
+uint8_t
+usbd_transfer_pending(struct usb_xfer *xfer)
+{
+ struct usb_xfer_root *info;
+ struct usb_xfer_queue *pq;
+
+ if (xfer == NULL) {
+ /* transfer is gone */
+ return (0);
+ }
+ USB_XFER_LOCK_ASSERT(xfer, MA_OWNED);
+
+ if (xfer->flags_int.transferring) {
+ /* trivial case */
+ return (1);
+ }
+ USB_BUS_LOCK(xfer->xroot->bus);
+ if (xfer->wait_queue) {
+ /* we are waiting on a queue somewhere */
+ USB_BUS_UNLOCK(xfer->xroot->bus);
+ return (1);
+ }
+ info = xfer->xroot;
+ pq = &info->done_q;
+
+ if (pq->curr == xfer) {
+ /* we are currently scheduled for callback */
+ USB_BUS_UNLOCK(xfer->xroot->bus);
+ return (1);
+ }
+ /* we are not pending */
+ USB_BUS_UNLOCK(xfer->xroot->bus);
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_drain
+ *
+ * This function will stop the USB transfer and wait for any
+ * additional BUS-DMA and HW-DMA operations to complete. Buffers that
+ * are loaded into DMA can safely be freed or reused after that this
+ * function has returned.
+ *------------------------------------------------------------------------*/
+void
+usbd_transfer_drain(struct usb_xfer *xfer)
+{
+ WITNESS_WARN(WARN_GIANTOK | WARN_SLEEPOK, NULL,
+ "usbd_transfer_drain can sleep!");
+
+ if (xfer == NULL) {
+ /* transfer is gone */
+ return;
+ }
+ if (xfer->xroot->xfer_mtx != &Giant) {
+ USB_XFER_LOCK_ASSERT(xfer, MA_NOTOWNED);
+ }
+ USB_XFER_LOCK(xfer);
+
+ usbd_transfer_stop(xfer);
+
+ while (usbd_transfer_pending(xfer) ||
+ xfer->flags_int.doing_callback) {
+ /*
+ * It is allowed that the callback can drop its
+ * transfer mutex. In that case checking only
+ * "usbd_transfer_pending()" is not enough to tell if
+ * the USB transfer is fully drained. We also need to
+ * check the internal "doing_callback" flag.
+ */
+ xfer->flags_int.draining = 1;
+
+ /*
+ * Wait until the current outstanding USB
+ * transfer is complete !
+ */
+ cv_wait(&xfer->xroot->cv_drain, xfer->xroot->xfer_mtx);
+ }
+ USB_XFER_UNLOCK(xfer);
+}
+
+struct usb_page_cache *
+usbd_xfer_get_frame(struct usb_xfer *xfer, usb_frcount_t frindex)
+{
+ KASSERT(frindex < xfer->max_frame_count, ("frame index overflow"));
+
+ return (&xfer->frbuffers[frindex]);
+}
+
+void *
+usbd_xfer_get_frame_buffer(struct usb_xfer *xfer, usb_frcount_t frindex)
+{
+ struct usb_page_search page_info;
+
+ KASSERT(frindex < xfer->max_frame_count, ("frame index overflow"));
+
+ usbd_get_page(&xfer->frbuffers[frindex], 0, &page_info);
+ return (page_info.buffer);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_xfer_get_fps_shift
+ *
+ * The following function is only useful for isochronous transfers. It
+ * returns how many times the frame execution rate has been shifted
+ * down.
+ *
+ * Return value:
+ * Success: 0..3
+ * Failure: 0
+ *------------------------------------------------------------------------*/
+uint8_t
+usbd_xfer_get_fps_shift(struct usb_xfer *xfer)
+{
+ return (xfer->fps_shift);
+}
+
+usb_frlength_t
+usbd_xfer_frame_len(struct usb_xfer *xfer, usb_frcount_t frindex)
+{
+ KASSERT(frindex < xfer->max_frame_count, ("frame index overflow"));
+
+ return (xfer->frlengths[frindex]);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_xfer_set_frame_data
+ *
+ * This function sets the pointer of the buffer that should
+ * loaded directly into DMA for the given USB frame. Passing "ptr"
+ * equal to NULL while the corresponding "frlength" is greater
+ * than zero gives undefined results!
+ *------------------------------------------------------------------------*/
+void
+usbd_xfer_set_frame_data(struct usb_xfer *xfer, usb_frcount_t frindex,
+ void *ptr, usb_frlength_t len)
+{
+ KASSERT(frindex < xfer->max_frame_count, ("frame index overflow"));
+
+ /* set virtual address to load and length */
+ xfer->frbuffers[frindex].buffer = ptr;
+ usbd_xfer_set_frame_len(xfer, frindex, len);
+}
+
+void
+usbd_xfer_frame_data(struct usb_xfer *xfer, usb_frcount_t frindex,
+ void **ptr, int *len)
+{
+ KASSERT(frindex < xfer->max_frame_count, ("frame index overflow"));
+
+ if (ptr != NULL)
+ *ptr = xfer->frbuffers[frindex].buffer;
+ if (len != NULL)
+ *len = xfer->frlengths[frindex];
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_xfer_old_frame_length
+ *
+ * This function returns the framelength of the given frame at the
+ * time the transfer was submitted. This function can be used to
+ * compute the starting data pointer of the next isochronous frame
+ * when an isochronous transfer has completed.
+ *------------------------------------------------------------------------*/
+usb_frlength_t
+usbd_xfer_old_frame_length(struct usb_xfer *xfer, usb_frcount_t frindex)
+{
+ KASSERT(frindex < xfer->max_frame_count, ("frame index overflow"));
+
+ return (xfer->frlengths[frindex + xfer->max_frame_count]);
+}
+
+void
+usbd_xfer_status(struct usb_xfer *xfer, int *actlen, int *sumlen, int *aframes,
+ int *nframes)
+{
+ if (actlen != NULL)
+ *actlen = xfer->actlen;
+ if (sumlen != NULL)
+ *sumlen = xfer->sumlen;
+ if (aframes != NULL)
+ *aframes = xfer->aframes;
+ if (nframes != NULL)
+ *nframes = xfer->nframes;
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_xfer_set_frame_offset
+ *
+ * This function sets the frame data buffer offset relative to the beginning
+ * of the USB DMA buffer allocated for this USB transfer.
+ *------------------------------------------------------------------------*/
+void
+usbd_xfer_set_frame_offset(struct usb_xfer *xfer, usb_frlength_t offset,
+ usb_frcount_t frindex)
+{
+ KASSERT(!xfer->flags.ext_buffer, ("Cannot offset data frame "
+ "when the USB buffer is external\n"));
+ KASSERT(frindex < xfer->max_frame_count, ("frame index overflow"));
+
+ /* set virtual address to load */
+ xfer->frbuffers[frindex].buffer =
+ USB_ADD_BYTES(xfer->local_buffer, offset);
+}
+
+void
+usbd_xfer_set_interval(struct usb_xfer *xfer, int i)
+{
+ xfer->interval = i;
+}
+
+void
+usbd_xfer_set_timeout(struct usb_xfer *xfer, int t)
+{
+ xfer->timeout = t;
+}
+
+void
+usbd_xfer_set_frames(struct usb_xfer *xfer, usb_frcount_t n)
+{
+ xfer->nframes = n;
+}
+
+usb_frcount_t
+usbd_xfer_max_frames(struct usb_xfer *xfer)
+{
+ return (xfer->max_frame_count);
+}
+
+usb_frlength_t
+usbd_xfer_max_len(struct usb_xfer *xfer)
+{
+ return (xfer->max_data_length);
+}
+
+usb_frlength_t
+usbd_xfer_max_framelen(struct usb_xfer *xfer)
+{
+ return (xfer->max_frame_size);
+}
+
+void
+usbd_xfer_set_frame_len(struct usb_xfer *xfer, usb_frcount_t frindex,
+ usb_frlength_t len)
+{
+ KASSERT(frindex < xfer->max_frame_count, ("frame index overflow"));
+
+ xfer->frlengths[frindex] = len;
+}
+
+/*------------------------------------------------------------------------*
+ * usb_callback_proc - factored out code
+ *
+ * This function performs USB callbacks.
+ *------------------------------------------------------------------------*/
+static void
+usb_callback_proc(struct usb_proc_msg *_pm)
+{
+ struct usb_done_msg *pm = (void *)_pm;
+ struct usb_xfer_root *info = pm->xroot;
+
+ /* Change locking order */
+ USB_BUS_UNLOCK(info->bus);
+
+ /*
+ * We exploit the fact that the mutex is the same for all
+ * callbacks that will be called from this thread:
+ */
+ USB_MTX_LOCK(info->xfer_mtx);
+ USB_BUS_LOCK(info->bus);
+
+ /* Continue where we lost track */
+ usb_command_wrapper(&info->done_q,
+ info->done_q.curr);
+
+ USB_MTX_UNLOCK(info->xfer_mtx);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_callback_ss_done_defer
+ *
+ * This function will defer the start, stop and done callback to the
+ * correct thread.
+ *------------------------------------------------------------------------*/
+static void
+usbd_callback_ss_done_defer(struct usb_xfer *xfer)
+{
+ struct usb_xfer_root *info = xfer->xroot;
+ struct usb_xfer_queue *pq = &info->done_q;
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ if (pq->curr != xfer) {
+ usbd_transfer_enqueue(pq, xfer);
+ }
+ if (!pq->recurse_1) {
+ /*
+ * We have to postpone the callback due to the fact we
+ * will have a Lock Order Reversal, LOR, if we try to
+ * proceed !
+ */
+ (void) usb_proc_msignal(info->done_p,
+ &info->done_m[0], &info->done_m[1]);
+ } else {
+ /* clear second recurse flag */
+ pq->recurse_2 = 0;
+ }
+ return;
+
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_callback_wrapper
+ *
+ * This is a wrapper for USB callbacks. This wrapper does some
+ * auto-magic things like figuring out if we can call the callback
+ * directly from the current context or if we need to wakeup the
+ * interrupt process.
+ *------------------------------------------------------------------------*/
+static void
+usbd_callback_wrapper(struct usb_xfer_queue *pq)
+{
+ struct usb_xfer *xfer = pq->curr;
+ struct usb_xfer_root *info = xfer->xroot;
+
+ USB_BUS_LOCK_ASSERT(info->bus, MA_OWNED);
+ if ((pq->recurse_3 != 0 || mtx_owned(info->xfer_mtx) == 0) &&
+ USB_IN_POLLING_MODE_FUNC() == 0) {
+ /*
+ * Cases that end up here:
+ *
+ * 5) HW interrupt done callback or other source.
+ * 6) HW completed transfer during callback
+ */
+ DPRINTFN(3, "case 5 and 6\n");
+
+ /*
+ * We have to postpone the callback due to the fact we
+ * will have a Lock Order Reversal, LOR, if we try to
+ * proceed!
+ *
+ * Postponing the callback also ensures that other USB
+ * transfer queues get a chance.
+ */
+ (void) usb_proc_msignal(info->done_p,
+ &info->done_m[0], &info->done_m[1]);
+ return;
+ }
+ /*
+ * Cases that end up here:
+ *
+ * 1) We are starting a transfer
+ * 2) We are prematurely calling back a transfer
+ * 3) We are stopping a transfer
+ * 4) We are doing an ordinary callback
+ */
+ DPRINTFN(3, "case 1-4\n");
+ /* get next USB transfer in the queue */
+ info->done_q.curr = NULL;
+
+ /* set flag in case of drain */
+ xfer->flags_int.doing_callback = 1;
+
+ USB_BUS_UNLOCK(info->bus);
+ USB_BUS_LOCK_ASSERT(info->bus, MA_NOTOWNED);
+
+ /* set correct USB state for callback */
+ if (!xfer->flags_int.transferring) {
+ xfer->usb_state = USB_ST_SETUP;
+ if (!xfer->flags_int.started) {
+ /* we got stopped before we even got started */
+ USB_BUS_LOCK(info->bus);
+ goto done;
+ }
+ } else {
+ if (usbd_callback_wrapper_sub(xfer)) {
+ /* the callback has been deferred */
+ USB_BUS_LOCK(info->bus);
+ goto done;
+ }
+#if USB_HAVE_POWERD
+ /* decrement power reference */
+ usbd_transfer_power_ref(xfer, -1);
+#endif
+ xfer->flags_int.transferring = 0;
+
+ if (xfer->error) {
+ xfer->usb_state = USB_ST_ERROR;
+ } else {
+ /* set transferred state */
+ xfer->usb_state = USB_ST_TRANSFERRED;
+#if USB_HAVE_BUSDMA
+ /* sync DMA memory, if any */
+ if (xfer->flags_int.bdma_enable &&
+ (!xfer->flags_int.bdma_no_post_sync)) {
+ usb_bdma_post_sync(xfer);
+ }
+#endif
+ }
+ }
+
+#if USB_HAVE_PF
+ if (xfer->usb_state != USB_ST_SETUP) {
+ USB_BUS_LOCK(info->bus);
+ usbpf_xfertap(xfer, USBPF_XFERTAP_DONE);
+ USB_BUS_UNLOCK(info->bus);
+ }
+#endif
+ /* call processing routine */
+ (xfer->callback) (xfer, xfer->error);
+
+ /* pickup the USB mutex again */
+ USB_BUS_LOCK(info->bus);
+
+ /*
+ * Check if we got started after that we got cancelled, but
+ * before we managed to do the callback.
+ */
+ if ((!xfer->flags_int.open) &&
+ (xfer->flags_int.started) &&
+ (xfer->usb_state == USB_ST_ERROR)) {
+ /* clear flag in case of drain */
+ xfer->flags_int.doing_callback = 0;
+ /* try to loop, but not recursivly */
+ usb_command_wrapper(&info->done_q, xfer);
+ return;
+ }
+
+done:
+ /* clear flag in case of drain */
+ xfer->flags_int.doing_callback = 0;
+
+ /*
+ * Check if we are draining.
+ */
+ if (xfer->flags_int.draining &&
+ (!xfer->flags_int.transferring)) {
+ /* "usbd_transfer_drain()" is waiting for end of transfer */
+ xfer->flags_int.draining = 0;
+ cv_broadcast(&info->cv_drain);
+ }
+
+ /* do the next callback, if any */
+ usb_command_wrapper(&info->done_q,
+ info->done_q.curr);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_dma_delay_done_cb
+ *
+ * This function is called when the DMA delay has been exectuded, and
+ * will make sure that the callback is called to complete the USB
+ * transfer. This code path is usually only used when there is an USB
+ * error like USB_ERR_CANCELLED.
+ *------------------------------------------------------------------------*/
+void
+usb_dma_delay_done_cb(struct usb_xfer *xfer)
+{
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ DPRINTFN(3, "Completed %p\n", xfer);
+
+ /* queue callback for execution, again */
+ usbd_transfer_done(xfer, 0);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_dequeue
+ *
+ * - This function is used to remove an USB transfer from a USB
+ * transfer queue.
+ *
+ * - This function can be called multiple times in a row.
+ *------------------------------------------------------------------------*/
+void
+usbd_transfer_dequeue(struct usb_xfer *xfer)
+{
+ struct usb_xfer_queue *pq;
+
+ pq = xfer->wait_queue;
+ if (pq) {
+ TAILQ_REMOVE(&pq->head, xfer, wait_entry);
+ xfer->wait_queue = NULL;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_enqueue
+ *
+ * - This function is used to insert an USB transfer into a USB *
+ * transfer queue.
+ *
+ * - This function can be called multiple times in a row.
+ *------------------------------------------------------------------------*/
+void
+usbd_transfer_enqueue(struct usb_xfer_queue *pq, struct usb_xfer *xfer)
+{
+ /*
+ * Insert the USB transfer into the queue, if it is not
+ * already on a USB transfer queue:
+ */
+ if (xfer->wait_queue == NULL) {
+ xfer->wait_queue = pq;
+ TAILQ_INSERT_TAIL(&pq->head, xfer, wait_entry);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_done
+ *
+ * - This function is used to remove an USB transfer from the busdma,
+ * pipe or interrupt queue.
+ *
+ * - This function is used to queue the USB transfer on the done
+ * queue.
+ *
+ * - This function is used to stop any USB transfer timeouts.
+ *------------------------------------------------------------------------*/
+void
+usbd_transfer_done(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct usb_xfer_root *info = xfer->xroot;
+
+ USB_BUS_LOCK_ASSERT(info->bus, MA_OWNED);
+
+ DPRINTF("err=%s\n", usbd_errstr(error));
+
+ /*
+ * If we are not transferring then just return.
+ * This can happen during transfer cancel.
+ */
+ if (!xfer->flags_int.transferring) {
+ DPRINTF("not transferring\n");
+ /* end of control transfer, if any */
+ xfer->flags_int.control_act = 0;
+ return;
+ }
+ /* only set transfer error, if not already set */
+ if (xfer->error == USB_ERR_NORMAL_COMPLETION)
+ xfer->error = error;
+
+ /* stop any callouts */
+ usb_callout_stop(&xfer->timeout_handle);
+
+ /*
+ * If we are waiting on a queue, just remove the USB transfer
+ * from the queue, if any. We should have the required locks
+ * locked to do the remove when this function is called.
+ */
+ usbd_transfer_dequeue(xfer);
+
+#if USB_HAVE_BUSDMA
+ if (mtx_owned(info->xfer_mtx)) {
+ struct usb_xfer_queue *pq;
+
+ /*
+ * If the private USB lock is not locked, then we assume
+ * that the BUS-DMA load stage has been passed:
+ */
+ pq = &info->dma_q;
+
+ if (pq->curr == xfer) {
+ /* start the next BUS-DMA load, if any */
+ usb_command_wrapper(pq, NULL);
+ }
+ }
+#endif
+ /* keep some statistics */
+ if (xfer->error == USB_ERR_CANCELLED) {
+ info->udev->stats_cancelled.uds_requests
+ [xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE]++;
+ } else if (xfer->error != USB_ERR_NORMAL_COMPLETION) {
+ info->udev->stats_err.uds_requests
+ [xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE]++;
+ } else {
+ info->udev->stats_ok.uds_requests
+ [xfer->endpoint->edesc->bmAttributes & UE_XFERTYPE]++;
+ }
+
+ /* call the USB transfer callback */
+ usbd_callback_ss_done_defer(xfer);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_start_cb
+ *
+ * This function is called to start the USB transfer when
+ * "xfer->interval" is greater than zero, and and the endpoint type is
+ * BULK or CONTROL.
+ *------------------------------------------------------------------------*/
+static void
+usbd_transfer_start_cb(void *arg)
+{
+ struct usb_xfer *xfer = arg;
+ struct usb_endpoint *ep = xfer->endpoint;
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ DPRINTF("start\n");
+
+#if USB_HAVE_PF
+ usbpf_xfertap(xfer, USBPF_XFERTAP_SUBMIT);
+#endif
+
+ /* the transfer can now be cancelled */
+ xfer->flags_int.can_cancel_immed = 1;
+
+ /* start USB transfer, if no error */
+ if (xfer->error == 0)
+ (ep->methods->start) (xfer);
+
+ /* check for transfer error */
+ if (xfer->error) {
+ /* some error has happened */
+ usbd_transfer_done(xfer, 0);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_xfer_set_zlp
+ *
+ * This function sets the USB transfers ZLP flag.
+ *------------------------------------------------------------------------*/
+void
+usbd_xfer_set_zlp(struct usb_xfer *xfer)
+{
+ if (xfer == NULL) {
+ /* tearing down */
+ return;
+ }
+ USB_XFER_LOCK_ASSERT(xfer, MA_OWNED);
+
+ /* avoid any races by locking the USB mutex */
+ USB_BUS_LOCK(xfer->xroot->bus);
+ xfer->flags.send_zlp = 1;
+ USB_BUS_UNLOCK(xfer->xroot->bus);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_xfer_get_and_clr_zlp
+ *
+ * This function gets and clears the USB transfers ZLP flag and
+ * queues a zero-length USB transfer if the flag was set.
+ *------------------------------------------------------------------------*/
+uint8_t
+usbd_xfer_get_and_clr_zlp(struct usb_xfer *xfer)
+{
+ uint8_t retval;
+
+ if (xfer == NULL) {
+ /* tearing down */
+ return (0);
+ }
+ USB_XFER_LOCK_ASSERT(xfer, MA_OWNED);
+
+ retval = xfer->flags.send_zlp;
+
+ if (retval != 0) {
+ DPRINTFN(1, "Sending zero-length packet.\n");
+
+ /* avoid any races by locking the USB mutex */
+ USB_BUS_LOCK(xfer->xroot->bus);
+ xfer->flags.send_zlp = 0;
+ USB_BUS_UNLOCK(xfer->xroot->bus);
+
+ /* queue up a zero-length packet */
+ usbd_xfer_set_frame_len(xfer, 0, 0);
+ usbd_xfer_set_frames(xfer, 1);
+ usbd_transfer_submit(xfer);
+ }
+ return (retval);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_xfer_set_stall
+ *
+ * This function is used to set the stall flag outside the
+ * callback. This function is NULL safe.
+ *------------------------------------------------------------------------*/
+void
+usbd_xfer_set_stall(struct usb_xfer *xfer)
+{
+ if (xfer == NULL) {
+ /* tearing down */
+ return;
+ }
+ USB_XFER_LOCK_ASSERT(xfer, MA_OWNED);
+
+ /* avoid any races by locking the USB mutex */
+ USB_BUS_LOCK(xfer->xroot->bus);
+ xfer->flags.stall_pipe = 1;
+ USB_BUS_UNLOCK(xfer->xroot->bus);
+}
+
+int
+usbd_xfer_is_stalled(struct usb_xfer *xfer)
+{
+ return (xfer->endpoint->is_stalled);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_clear_stall
+ *
+ * This function is used to clear the stall flag outside the
+ * callback. This function is NULL safe.
+ *------------------------------------------------------------------------*/
+void
+usbd_transfer_clear_stall(struct usb_xfer *xfer)
+{
+ if (xfer == NULL) {
+ /* tearing down */
+ return;
+ }
+ USB_XFER_LOCK_ASSERT(xfer, MA_OWNED);
+
+ /* avoid any races by locking the USB mutex */
+ USB_BUS_LOCK(xfer->xroot->bus);
+ xfer->flags.stall_pipe = 0;
+ USB_BUS_UNLOCK(xfer->xroot->bus);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_pipe_start
+ *
+ * This function is used to add an USB transfer to the pipe transfer list.
+ *------------------------------------------------------------------------*/
+void
+usbd_pipe_start(struct usb_xfer_queue *pq)
+{
+ struct usb_endpoint *ep;
+ struct usb_xfer *xfer;
+ uint8_t type;
+
+ xfer = pq->curr;
+ ep = xfer->endpoint;
+
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /*
+ * If the endpoint is already stalled we do nothing !
+ */
+ if (ep->is_stalled) {
+ return;
+ }
+ /*
+ * Check if we are supposed to stall the endpoint:
+ */
+ if (xfer->flags.stall_pipe) {
+ struct usb_device *udev;
+ struct usb_xfer_root *info;
+
+ /* clear stall command */
+ xfer->flags.stall_pipe = 0;
+
+ /* get pointer to USB device */
+ info = xfer->xroot;
+ udev = info->udev;
+
+ /*
+ * Only stall BULK and INTERRUPT endpoints.
+ */
+ type = (ep->edesc->bmAttributes & UE_XFERTYPE);
+ if ((type == UE_BULK) ||
+ (type == UE_INTERRUPT)) {
+ uint8_t did_stall;
+
+ did_stall = 1;
+
+ if (udev->flags.usb_mode == USB_MODE_DEVICE) {
+ (udev->bus->methods->set_stall) (
+ udev, ep, &did_stall);
+ } else if (udev->ctrl_xfer[1]) {
+ info = udev->ctrl_xfer[1]->xroot;
+ usb_proc_msignal(
+ USB_BUS_CS_PROC(info->bus),
+ &udev->cs_msg[0], &udev->cs_msg[1]);
+ } else {
+ /* should not happen */
+ DPRINTFN(0, "No stall handler\n");
+ }
+ /*
+ * Check if we should stall. Some USB hardware
+ * handles set- and clear-stall in hardware.
+ */
+ if (did_stall) {
+ /*
+ * The transfer will be continued when
+ * the clear-stall control endpoint
+ * message is received.
+ */
+ ep->is_stalled = 1;
+ return;
+ }
+ } else if (type == UE_ISOCHRONOUS) {
+ /*
+ * Make sure any FIFO overflow or other FIFO
+ * error conditions go away by resetting the
+ * endpoint FIFO through the clear stall
+ * method.
+ */
+ if (udev->flags.usb_mode == USB_MODE_DEVICE) {
+ (udev->bus->methods->clear_stall) (udev, ep);
+ }
+ }
+ }
+ /* Set or clear stall complete - special case */
+ if (xfer->nframes == 0) {
+ /* we are complete */
+ xfer->aframes = 0;
+ usbd_transfer_done(xfer, 0);
+ return;
+ }
+ /*
+ * Handled cases:
+ *
+ * 1) Start the first transfer queued.
+ *
+ * 2) Re-start the current USB transfer.
+ */
+ /*
+ * Check if there should be any
+ * pre transfer start delay:
+ */
+ if (xfer->interval > 0) {
+ type = (ep->edesc->bmAttributes & UE_XFERTYPE);
+ if ((type == UE_BULK) ||
+ (type == UE_CONTROL)) {
+ usbd_transfer_timeout_ms(xfer,
+ &usbd_transfer_start_cb,
+ xfer->interval);
+ return;
+ }
+ }
+ DPRINTF("start\n");
+
+#if USB_HAVE_PF
+ usbpf_xfertap(xfer, USBPF_XFERTAP_SUBMIT);
+#endif
+ /* the transfer can now be cancelled */
+ xfer->flags_int.can_cancel_immed = 1;
+
+ /* start USB transfer, if no error */
+ if (xfer->error == 0)
+ (ep->methods->start) (xfer);
+
+ /* check for transfer error */
+ if (xfer->error) {
+ /* some error has happened */
+ usbd_transfer_done(xfer, 0);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_timeout_ms
+ *
+ * This function is used to setup a timeout on the given USB
+ * transfer. If the timeout has been deferred the callback given by
+ * "cb" will get called after "ms" milliseconds.
+ *------------------------------------------------------------------------*/
+void
+usbd_transfer_timeout_ms(struct usb_xfer *xfer,
+ void (*cb) (void *arg), usb_timeout_t ms)
+{
+ USB_BUS_LOCK_ASSERT(xfer->xroot->bus, MA_OWNED);
+
+ /* defer delay */
+ usb_callout_reset(&xfer->timeout_handle,
+ USB_MS_TO_TICKS(ms) + USB_CALLOUT_ZERO_TICKS, cb, xfer);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_callback_wrapper_sub
+ *
+ * - This function will update variables in an USB transfer after
+ * that the USB transfer is complete.
+ *
+ * - This function is used to start the next USB transfer on the
+ * ep transfer queue, if any.
+ *
+ * NOTE: In some special cases the USB transfer will not be removed from
+ * the pipe queue, but remain first. To enforce USB transfer removal call
+ * this function passing the error code "USB_ERR_CANCELLED".
+ *
+ * Return values:
+ * 0: Success.
+ * Else: The callback has been deferred.
+ *------------------------------------------------------------------------*/
+static uint8_t
+usbd_callback_wrapper_sub(struct usb_xfer *xfer)
+{
+ struct usb_endpoint *ep;
+ struct usb_bus *bus;
+ usb_frcount_t x;
+
+ bus = xfer->xroot->bus;
+
+ if ((!xfer->flags_int.open) &&
+ (!xfer->flags_int.did_close)) {
+ DPRINTF("close\n");
+ USB_BUS_LOCK(bus);
+ (xfer->endpoint->methods->close) (xfer);
+ USB_BUS_UNLOCK(bus);
+ /* only close once */
+ xfer->flags_int.did_close = 1;
+ return (1); /* wait for new callback */
+ }
+ /*
+ * If we have a non-hardware induced error we
+ * need to do the DMA delay!
+ */
+ if (xfer->error != 0 && !xfer->flags_int.did_dma_delay &&
+ (xfer->error == USB_ERR_CANCELLED ||
+ xfer->error == USB_ERR_TIMEOUT ||
+ bus->methods->start_dma_delay != NULL)) {
+ usb_timeout_t temp;
+
+ /* only delay once */
+ xfer->flags_int.did_dma_delay = 1;
+
+ /* we can not cancel this delay */
+ xfer->flags_int.can_cancel_immed = 0;
+
+ temp = usbd_get_dma_delay(xfer->xroot->udev);
+
+ DPRINTFN(3, "DMA delay, %u ms, "
+ "on %p\n", temp, xfer);
+
+ if (temp != 0) {
+ USB_BUS_LOCK(bus);
+ /*
+ * Some hardware solutions have dedicated
+ * events when it is safe to free DMA'ed
+ * memory. For the other hardware platforms we
+ * use a static delay.
+ */
+ if (bus->methods->start_dma_delay != NULL) {
+ (bus->methods->start_dma_delay) (xfer);
+ } else {
+ usbd_transfer_timeout_ms(xfer,
+ (void (*)(void *))&usb_dma_delay_done_cb,
+ temp);
+ }
+ USB_BUS_UNLOCK(bus);
+ return (1); /* wait for new callback */
+ }
+ }
+ /* check actual number of frames */
+ if (xfer->aframes > xfer->nframes) {
+ if (xfer->error == 0) {
+ panic("%s: actual number of frames, %d, is "
+ "greater than initial number of frames, %d\n",
+ __FUNCTION__, xfer->aframes, xfer->nframes);
+ } else {
+ /* just set some valid value */
+ xfer->aframes = xfer->nframes;
+ }
+ }
+ /* compute actual length */
+ xfer->actlen = 0;
+
+ for (x = 0; x != xfer->aframes; x++) {
+ xfer->actlen += xfer->frlengths[x];
+ }
+
+ /*
+ * Frames that were not transferred get zero actual length in
+ * case the USB device driver does not check the actual number
+ * of frames transferred, "xfer->aframes":
+ */
+ for (; x < xfer->nframes; x++) {
+ usbd_xfer_set_frame_len(xfer, x, 0);
+ }
+
+ /* check actual length */
+ if (xfer->actlen > xfer->sumlen) {
+ if (xfer->error == 0) {
+ panic("%s: actual length, %d, is greater than "
+ "initial length, %d\n",
+ __FUNCTION__, xfer->actlen, xfer->sumlen);
+ } else {
+ /* just set some valid value */
+ xfer->actlen = xfer->sumlen;
+ }
+ }
+ DPRINTFN(1, "xfer=%p endpoint=%p sts=%d alen=%d, slen=%d, afrm=%d, nfrm=%d\n",
+ xfer, xfer->endpoint, xfer->error, xfer->actlen, xfer->sumlen,
+ xfer->aframes, xfer->nframes);
+
+ if (xfer->error) {
+ /* end of control transfer, if any */
+ xfer->flags_int.control_act = 0;
+
+#if USB_HAVE_TT_SUPPORT
+ switch (xfer->error) {
+ case USB_ERR_NORMAL_COMPLETION:
+ case USB_ERR_SHORT_XFER:
+ case USB_ERR_STALLED:
+ case USB_ERR_CANCELLED:
+ /* nothing to do */
+ break;
+ default:
+ /* try to reset the TT, if any */
+ USB_BUS_LOCK(bus);
+ uhub_tt_buffer_reset_async_locked(xfer->xroot->udev, xfer->endpoint);
+ USB_BUS_UNLOCK(bus);
+ break;
+ }
+#endif
+ /* check if we should block the execution queue */
+ if ((xfer->error != USB_ERR_CANCELLED) &&
+ (xfer->flags.pipe_bof)) {
+ DPRINTFN(2, "xfer=%p: Block On Failure "
+ "on endpoint=%p\n", xfer, xfer->endpoint);
+ goto done;
+ }
+ } else {
+ /* check for short transfers */
+ if (xfer->actlen < xfer->sumlen) {
+ /* end of control transfer, if any */
+ xfer->flags_int.control_act = 0;
+
+ if (!xfer->flags_int.short_xfer_ok) {
+ xfer->error = USB_ERR_SHORT_XFER;
+ if (xfer->flags.pipe_bof) {
+ DPRINTFN(2, "xfer=%p: Block On Failure on "
+ "Short Transfer on endpoint %p.\n",
+ xfer, xfer->endpoint);
+ goto done;
+ }
+ }
+ } else {
+ /*
+ * Check if we are in the middle of a
+ * control transfer:
+ */
+ if (xfer->flags_int.control_act) {
+ DPRINTFN(5, "xfer=%p: Control transfer "
+ "active on endpoint=%p\n", xfer, xfer->endpoint);
+ goto done;
+ }
+ }
+ }
+
+ ep = xfer->endpoint;
+
+ /*
+ * If the current USB transfer is completing we need to start the
+ * next one:
+ */
+ USB_BUS_LOCK(bus);
+ if (ep->endpoint_q[xfer->stream_id].curr == xfer) {
+ usb_command_wrapper(&ep->endpoint_q[xfer->stream_id], NULL);
+
+ if (ep->endpoint_q[xfer->stream_id].curr != NULL ||
+ TAILQ_FIRST(&ep->endpoint_q[xfer->stream_id].head) != NULL) {
+ /* there is another USB transfer waiting */
+ } else {
+ /* this is the last USB transfer */
+ /* clear isochronous sync flag */
+ xfer->endpoint->is_synced = 0;
+ }
+ }
+ USB_BUS_UNLOCK(bus);
+done:
+ return (0);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_command_wrapper
+ *
+ * This function is used to execute commands non-recursivly on an USB
+ * transfer.
+ *------------------------------------------------------------------------*/
+void
+usb_command_wrapper(struct usb_xfer_queue *pq, struct usb_xfer *xfer)
+{
+ if (xfer) {
+ /*
+ * If the transfer is not already processing,
+ * queue it!
+ */
+ if (pq->curr != xfer) {
+ usbd_transfer_enqueue(pq, xfer);
+ if (pq->curr != NULL) {
+ /* something is already processing */
+ DPRINTFN(6, "busy %p\n", pq->curr);
+ return;
+ }
+ }
+ } else {
+ /* Get next element in queue */
+ pq->curr = NULL;
+ }
+
+ if (!pq->recurse_1) {
+ /* clear third recurse flag */
+ pq->recurse_3 = 0;
+
+ do {
+ /* set two first recurse flags */
+ pq->recurse_1 = 1;
+ pq->recurse_2 = 1;
+
+ if (pq->curr == NULL) {
+ xfer = TAILQ_FIRST(&pq->head);
+ if (xfer) {
+ TAILQ_REMOVE(&pq->head, xfer,
+ wait_entry);
+ xfer->wait_queue = NULL;
+ pq->curr = xfer;
+ } else {
+ break;
+ }
+ }
+ DPRINTFN(6, "cb %p (enter)\n", pq->curr);
+ (pq->command) (pq);
+ DPRINTFN(6, "cb %p (leave)\n", pq->curr);
+
+ /*
+ * Set third recurse flag to indicate
+ * recursion happened:
+ */
+ pq->recurse_3 = 1;
+
+ } while (!pq->recurse_2);
+
+ /* clear first recurse flag */
+ pq->recurse_1 = 0;
+
+ } else {
+ /* clear second recurse flag */
+ pq->recurse_2 = 0;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_ctrl_transfer_setup
+ *
+ * This function is used to setup the default USB control endpoint
+ * transfer.
+ *------------------------------------------------------------------------*/
+void
+usbd_ctrl_transfer_setup(struct usb_device *udev)
+{
+ struct usb_xfer *xfer;
+ uint8_t no_resetup;
+ uint8_t iface_index;
+
+ /* check for root HUB */
+ if (udev->parent_hub == NULL)
+ return;
+repeat:
+
+ xfer = udev->ctrl_xfer[0];
+ if (xfer) {
+ USB_XFER_LOCK(xfer);
+ no_resetup =
+ ((xfer->address == udev->address) &&
+ (udev->ctrl_ep_desc.wMaxPacketSize[0] ==
+ udev->ddesc.bMaxPacketSize));
+ if (udev->flags.usb_mode == USB_MODE_DEVICE) {
+ if (no_resetup) {
+ /*
+ * NOTE: checking "xfer->address" and
+ * starting the USB transfer must be
+ * atomic!
+ */
+ usbd_transfer_start(xfer);
+ }
+ }
+ USB_XFER_UNLOCK(xfer);
+ } else {
+ no_resetup = 0;
+ }
+
+ if (no_resetup) {
+ /*
+ * All parameters are exactly the same like before.
+ * Just return.
+ */
+ return;
+ }
+ /*
+ * Update wMaxPacketSize for the default control endpoint:
+ */
+ udev->ctrl_ep_desc.wMaxPacketSize[0] =
+ udev->ddesc.bMaxPacketSize;
+
+ /*
+ * Unsetup any existing USB transfer:
+ */
+ usbd_transfer_unsetup(udev->ctrl_xfer, USB_CTRL_XFER_MAX);
+
+ /*
+ * Reset clear stall error counter.
+ */
+ udev->clear_stall_errors = 0;
+
+ /*
+ * Try to setup a new USB transfer for the
+ * default control endpoint:
+ */
+ iface_index = 0;
+ if (usbd_transfer_setup(udev, &iface_index,
+ udev->ctrl_xfer, udev->bus->control_ep_quirk ?
+ usb_control_ep_quirk_cfg : usb_control_ep_cfg, USB_CTRL_XFER_MAX, NULL,
+ &udev->device_mtx)) {
+ DPRINTFN(0, "could not setup default "
+ "USB transfer\n");
+ } else {
+ goto repeat;
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_clear_data_toggle - factored out code
+ *
+ * NOTE: the intention of this function is not to reset the hardware
+ * data toggle.
+ *------------------------------------------------------------------------*/
+void
+usbd_clear_stall_locked(struct usb_device *udev, struct usb_endpoint *ep)
+{
+ USB_BUS_LOCK_ASSERT(udev->bus, MA_OWNED);
+
+ /* check that we have a valid case */
+ if (udev->flags.usb_mode == USB_MODE_HOST &&
+ udev->parent_hub != NULL &&
+ udev->bus->methods->clear_stall != NULL &&
+ ep->methods != NULL) {
+ (udev->bus->methods->clear_stall) (udev, ep);
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_clear_data_toggle - factored out code
+ *
+ * NOTE: the intention of this function is not to reset the hardware
+ * data toggle on the USB device side.
+ *------------------------------------------------------------------------*/
+void
+usbd_clear_data_toggle(struct usb_device *udev, struct usb_endpoint *ep)
+{
+ DPRINTFN(5, "udev=%p endpoint=%p\n", udev, ep);
+
+ USB_BUS_LOCK(udev->bus);
+ ep->toggle_next = 0;
+ /* some hardware needs a callback to clear the data toggle */
+ usbd_clear_stall_locked(udev, ep);
+ USB_BUS_UNLOCK(udev->bus);
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_clear_stall_callback - factored out clear stall callback
+ *
+ * Input parameters:
+ * xfer1: Clear Stall Control Transfer
+ * xfer2: Stalled USB Transfer
+ *
+ * This function is NULL safe.
+ *
+ * Return values:
+ * 0: In progress
+ * Else: Finished
+ *
+ * Clear stall config example:
+ *
+ * static const struct usb_config my_clearstall = {
+ * .type = UE_CONTROL,
+ * .endpoint = 0,
+ * .direction = UE_DIR_ANY,
+ * .interval = 50, //50 milliseconds
+ * .bufsize = sizeof(struct usb_device_request),
+ * .timeout = 1000, //1.000 seconds
+ * .callback = &my_clear_stall_callback, // **
+ * .usb_mode = USB_MODE_HOST,
+ * };
+ *
+ * ** "my_clear_stall_callback" calls "usbd_clear_stall_callback"
+ * passing the correct parameters.
+ *------------------------------------------------------------------------*/
+uint8_t
+usbd_clear_stall_callback(struct usb_xfer *xfer1,
+ struct usb_xfer *xfer2)
+{
+ struct usb_device_request req;
+
+ if (xfer2 == NULL) {
+ /* looks like we are tearing down */
+ DPRINTF("NULL input parameter\n");
+ return (0);
+ }
+ USB_XFER_LOCK_ASSERT(xfer1, MA_OWNED);
+ USB_XFER_LOCK_ASSERT(xfer2, MA_OWNED);
+
+ switch (USB_GET_STATE(xfer1)) {
+ case USB_ST_SETUP:
+
+ /*
+ * pre-clear the data toggle to DATA0 ("umass.c" and
+ * "ata-usb.c" depends on this)
+ */
+
+ usbd_clear_data_toggle(xfer2->xroot->udev, xfer2->endpoint);
+
+ /* setup a clear-stall packet */
+
+ req.bmRequestType = UT_WRITE_ENDPOINT;
+ req.bRequest = UR_CLEAR_FEATURE;
+ USETW(req.wValue, UF_ENDPOINT_HALT);
+ req.wIndex[0] = xfer2->endpoint->edesc->bEndpointAddress;
+ req.wIndex[1] = 0;
+ USETW(req.wLength, 0);
+
+ /*
+ * "usbd_transfer_setup_sub()" will ensure that
+ * we have sufficient room in the buffer for
+ * the request structure!
+ */
+
+ /* copy in the transfer */
+
+ usbd_copy_in(xfer1->frbuffers, 0, &req, sizeof(req));
+
+ /* set length */
+ xfer1->frlengths[0] = sizeof(req);
+ xfer1->nframes = 1;
+
+ usbd_transfer_submit(xfer1);
+ return (0);
+
+ case USB_ST_TRANSFERRED:
+ break;
+
+ default: /* Error */
+ if (xfer1->error == USB_ERR_CANCELLED) {
+ return (0);
+ }
+ break;
+ }
+ return (1); /* Clear Stall Finished */
+}
+
+/*------------------------------------------------------------------------*
+ * usbd_transfer_poll
+ *
+ * The following function gets called from the USB keyboard driver and
+ * UMASS when the system has panicked.
+ *
+ * NOTE: It is currently not possible to resume normal operation on
+ * the USB controller which has been polled, due to clearing of the
+ * "up_dsleep" and "up_msleep" flags.
+ *------------------------------------------------------------------------*/
+void
+usbd_transfer_poll(struct usb_xfer **ppxfer, uint16_t max)
+{
+ struct usb_xfer *xfer;
+ struct usb_xfer_root *xroot;
+ struct usb_device *udev;
+ struct usb_proc_msg *pm;
+ struct usb_bus *bus;
+ uint16_t n;
+ uint16_t drop_bus_spin;
+ uint16_t drop_bus;
+ uint16_t drop_xfer;
+
+ for (n = 0; n != max; n++) {
+ /* Extra checks to avoid panic */
+ xfer = ppxfer[n];
+ if (xfer == NULL)
+ continue; /* no USB transfer */
+ xroot = xfer->xroot;
+ if (xroot == NULL)
+ continue; /* no USB root */
+ udev = xroot->udev;
+ if (udev == NULL)
+ continue; /* no USB device */
+ bus = udev->bus;
+ if (bus == NULL)
+ continue; /* no BUS structure */
+ if (bus->methods == NULL)
+ continue; /* no BUS methods */
+ if (bus->methods->xfer_poll == NULL)
+ continue; /* no poll method */
+
+ drop_bus_spin = 0;
+ drop_bus = 0;
+ drop_xfer = 0;
+
+ if (USB_IN_POLLING_MODE_FUNC() == 0) {
+ /* make sure that the BUS spin mutex is not locked */
+ while (mtx_owned(&bus->bus_spin_lock)) {
+ mtx_unlock_spin(&bus->bus_spin_lock);
+ drop_bus_spin++;
+ }
+
+ /* make sure that the BUS mutex is not locked */
+ while (mtx_owned(&bus->bus_mtx)) {
+ mtx_unlock(&bus->bus_mtx);
+ drop_bus++;
+ }
+
+ /* make sure that the transfer mutex is not locked */
+ while (mtx_owned(xroot->xfer_mtx)) {
+ mtx_unlock(xroot->xfer_mtx);
+ drop_xfer++;
+ }
+ }
+
+ /* Make sure cv_signal() and cv_broadcast() is not called */
+ USB_BUS_CONTROL_XFER_PROC(bus)->up_msleep = 0;
+ USB_BUS_EXPLORE_PROC(bus)->up_msleep = 0;
+ USB_BUS_GIANT_PROC(bus)->up_msleep = 0;
+ USB_BUS_NON_GIANT_ISOC_PROC(bus)->up_msleep = 0;
+ USB_BUS_NON_GIANT_BULK_PROC(bus)->up_msleep = 0;
+
+ /* poll USB hardware */
+ (bus->methods->xfer_poll) (bus);
+
+ USB_BUS_LOCK(xroot->bus);
+
+ /* check for clear stall */
+ if (udev->ctrl_xfer[1] != NULL) {
+ /* poll clear stall start */
+ pm = &udev->cs_msg[0].hdr;
+ (pm->pm_callback) (pm);
+ /* poll clear stall done thread */
+ pm = &udev->ctrl_xfer[1]->
+ xroot->done_m[0].hdr;
+ (pm->pm_callback) (pm);
+ }
+
+ /* poll done thread */
+ pm = &xroot->done_m[0].hdr;
+ (pm->pm_callback) (pm);
+
+ USB_BUS_UNLOCK(xroot->bus);
+
+ /* restore transfer mutex */
+ while (drop_xfer--)
+ mtx_lock(xroot->xfer_mtx);
+
+ /* restore BUS mutex */
+ while (drop_bus--)
+ mtx_lock(&bus->bus_mtx);
+
+ /* restore BUS spin mutex */
+ while (drop_bus_spin--)
+ mtx_lock_spin(&bus->bus_spin_lock);
+ }
+}
+
+static void
+usbd_get_std_packet_size(struct usb_std_packet_size *ptr,
+ uint8_t type, enum usb_dev_speed speed)
+{
+ static const uint16_t intr_range_max[USB_SPEED_MAX] = {
+ [USB_SPEED_LOW] = 8,
+ [USB_SPEED_FULL] = 64,
+ [USB_SPEED_HIGH] = 1024,
+ [USB_SPEED_VARIABLE] = 1024,
+ [USB_SPEED_SUPER] = 1024,
+ };
+
+ static const uint16_t isoc_range_max[USB_SPEED_MAX] = {
+ [USB_SPEED_LOW] = 0, /* invalid */
+ [USB_SPEED_FULL] = 1023,
+ [USB_SPEED_HIGH] = 1024,
+ [USB_SPEED_VARIABLE] = 3584,
+ [USB_SPEED_SUPER] = 1024,
+ };
+
+ static const uint16_t control_min[USB_SPEED_MAX] = {
+ [USB_SPEED_LOW] = 8,
+ [USB_SPEED_FULL] = 8,
+ [USB_SPEED_HIGH] = 64,
+ [USB_SPEED_VARIABLE] = 512,
+ [USB_SPEED_SUPER] = 512,
+ };
+
+ static const uint16_t bulk_min[USB_SPEED_MAX] = {
+ [USB_SPEED_LOW] = 8,
+ [USB_SPEED_FULL] = 8,
+ [USB_SPEED_HIGH] = 512,
+ [USB_SPEED_VARIABLE] = 512,
+ [USB_SPEED_SUPER] = 1024,
+ };
+
+ uint16_t temp;
+
+ memset(ptr, 0, sizeof(*ptr));
+
+ switch (type) {
+ case UE_INTERRUPT:
+ ptr->range.max = intr_range_max[speed];
+ break;
+ case UE_ISOCHRONOUS:
+ ptr->range.max = isoc_range_max[speed];
+ break;
+ default:
+ if (type == UE_BULK)
+ temp = bulk_min[speed];
+ else /* UE_CONTROL */
+ temp = control_min[speed];
+
+ /* default is fixed */
+ ptr->fixed[0] = temp;
+ ptr->fixed[1] = temp;
+ ptr->fixed[2] = temp;
+ ptr->fixed[3] = temp;
+
+ if (speed == USB_SPEED_FULL) {
+ /* multiple sizes */
+ ptr->fixed[1] = 16;
+ ptr->fixed[2] = 32;
+ ptr->fixed[3] = 64;
+ }
+ if ((speed == USB_SPEED_VARIABLE) &&
+ (type == UE_BULK)) {
+ /* multiple sizes */
+ ptr->fixed[2] = 1024;
+ ptr->fixed[3] = 1536;
+ }
+ break;
+ }
+}
+
+void *
+usbd_xfer_softc(struct usb_xfer *xfer)
+{
+ return (xfer->priv_sc);
+}
+
+void *
+usbd_xfer_get_priv(struct usb_xfer *xfer)
+{
+ return (xfer->priv_fifo);
+}
+
+void
+usbd_xfer_set_priv(struct usb_xfer *xfer, void *ptr)
+{
+ xfer->priv_fifo = ptr;
+}
+
+uint8_t
+usbd_xfer_state(struct usb_xfer *xfer)
+{
+ return (xfer->usb_state);
+}
+
+void
+usbd_xfer_set_flag(struct usb_xfer *xfer, int flag)
+{
+ switch (flag) {
+ case USB_FORCE_SHORT_XFER:
+ xfer->flags.force_short_xfer = 1;
+ break;
+ case USB_SHORT_XFER_OK:
+ xfer->flags.short_xfer_ok = 1;
+ break;
+ case USB_MULTI_SHORT_OK:
+ xfer->flags.short_frames_ok = 1;
+ break;
+ case USB_MANUAL_STATUS:
+ xfer->flags.manual_status = 1;
+ break;
+ }
+}
+
+void
+usbd_xfer_clr_flag(struct usb_xfer *xfer, int flag)
+{
+ switch (flag) {
+ case USB_FORCE_SHORT_XFER:
+ xfer->flags.force_short_xfer = 0;
+ break;
+ case USB_SHORT_XFER_OK:
+ xfer->flags.short_xfer_ok = 0;
+ break;
+ case USB_MULTI_SHORT_OK:
+ xfer->flags.short_frames_ok = 0;
+ break;
+ case USB_MANUAL_STATUS:
+ xfer->flags.manual_status = 0;
+ break;
+ }
+}
+
+/*
+ * The following function returns in milliseconds when the isochronous
+ * transfer was completed by the hardware. The returned value wraps
+ * around 65536 milliseconds.
+ */
+uint16_t
+usbd_xfer_get_timestamp(struct usb_xfer *xfer)
+{
+ return (xfer->isoc_time_complete);
+}
+
+/*
+ * The following function returns non-zero if the max packet size
+ * field was clamped to a valid value. Else it returns zero.
+ */
+uint8_t
+usbd_xfer_maxp_was_clamped(struct usb_xfer *xfer)
+{
+ return (xfer->flags_int.maxp_was_clamped);
+}
+
+/*
+ * The following function computes the next isochronous frame number
+ * where the first isochronous packet should be queued.
+ *
+ * The function returns non-zero if there was a discontinuity.
+ * Else zero is returned for normal operation.
+ */
+uint8_t
+usbd_xfer_get_isochronous_start_frame(struct usb_xfer *xfer, uint32_t frame_curr,
+ uint32_t frame_min, uint32_t frame_ms, uint32_t frame_mask, uint32_t *p_frame_start)
+{
+ uint32_t duration;
+ uint32_t delta;
+ uint8_t retval;
+ uint8_t shift;
+
+ /* Compute time ahead of current schedule. */
+ delta = (xfer->endpoint->isoc_next - frame_curr) & frame_mask;
+
+ /*
+ * Check if it is the first transfer or if the future frame
+ * delta is less than one millisecond or if the frame delta is
+ * negative:
+ */
+ if (xfer->endpoint->is_synced == 0 ||
+ delta < (frame_ms + frame_min) ||
+ delta > (frame_mask / 2)) {
+ /* Schedule transfer 2 milliseconds into the future. */
+ xfer->endpoint->isoc_next = (frame_curr + 2 * frame_ms + frame_min) & frame_mask;
+ xfer->endpoint->is_synced = 1;
+
+ retval = 1;
+ } else {
+ retval = 0;
+ }
+
+ /* Store start time, if any. */
+ if (p_frame_start != NULL)
+ *p_frame_start = xfer->endpoint->isoc_next & frame_mask;
+
+ /* Get relative completion time, in milliseconds. */
+ delta = xfer->endpoint->isoc_next - frame_curr + (frame_curr % frame_ms);
+ delta &= frame_mask;
+ delta /= frame_ms;
+
+ switch (usbd_get_speed(xfer->xroot->udev)) {
+ case USB_SPEED_FULL:
+ shift = 3;
+ break;
+ default:
+ shift = usbd_xfer_get_fps_shift(xfer);
+ break;
+ }
+
+ /* Get duration in milliseconds, rounded up. */
+ duration = ((xfer->nframes << shift) + 7) / 8;
+
+ /* Compute full 32-bit completion time, in milliseconds. */
+ xfer->isoc_time_complete =
+ usb_isoc_time_expand(xfer->xroot->bus, frame_curr / frame_ms) +
+ delta + duration;
+
+ /* Compute next isochronous frame. */
+ xfer->endpoint->isoc_next += duration * frame_ms;
+ xfer->endpoint->isoc_next &= frame_mask;
+
+ return (retval);
+}
diff --git a/sys/dev/usb/usb_transfer.h b/sys/dev/usb/usb_transfer.h
new file mode 100644
index 000000000000..4b075a107c37
--- /dev/null
+++ b/sys/dev/usb/usb_transfer.h
@@ -0,0 +1,151 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_TRANSFER_H_
+#define _USB_TRANSFER_H_
+
+/*
+ * A few words about USB transfer states:
+ * ======================================
+ *
+ * USB transfers can have multiple states, because they can be
+ * cancelled and started again and this cannot always be done
+ * atomically under a mutex. One reason for this is that the USB
+ * hardware sometimes needs to wait for DMA controllers to finish
+ * which is done asynchronously and grows the statemachine.
+ */
+
+/*
+ * The following structure defines the messages that is used to signal
+ * the "done_p" USB process.
+ */
+struct usb_done_msg {
+ struct usb_proc_msg hdr;
+ struct usb_xfer_root *xroot;
+};
+
+#define USB_DMATAG_TO_XROOT(dpt) \
+ __containerof(dpt, struct usb_xfer_root, dma_parent_tag)
+
+/*
+ * The following structure is used to keep information about memory
+ * that should be automatically freed at the moment all USB transfers
+ * have been freed.
+ */
+struct usb_xfer_root {
+ struct usb_dma_parent_tag dma_parent_tag;
+#if USB_HAVE_BUSDMA
+ struct usb_xfer_queue dma_q;
+#endif
+ struct usb_xfer_queue done_q;
+ struct usb_done_msg done_m[2];
+ struct cv cv_drain;
+
+ struct usb_process *done_p; /* pointer to callback process */
+ void *memory_base;
+ struct mtx *xfer_mtx; /* cannot be changed during operation */
+#if USB_HAVE_BUSDMA
+ struct usb_page_cache *dma_page_cache_start;
+ struct usb_page_cache *dma_page_cache_end;
+#endif
+ struct usb_page_cache *xfer_page_cache_start;
+ struct usb_page_cache *xfer_page_cache_end;
+ struct usb_bus *bus; /* pointer to USB bus (cached) */
+ struct usb_device *udev; /* pointer to USB device */
+
+ usb_size_t memory_size;
+ usb_size_t setup_refcount;
+#if USB_HAVE_BUSDMA
+ usb_frcount_t dma_nframes; /* number of page caches to load */
+ usb_frcount_t dma_currframe; /* currect page cache number */
+ usb_frlength_t dma_frlength_0; /* length of page cache zero */
+ uint8_t dma_error; /* set if virtual memory could not be
+ * loaded */
+#endif
+ uint8_t done_sleep; /* set if done thread is sleeping */
+};
+
+/*
+ * The following structure is used when setting up an array of USB
+ * transfers.
+ */
+struct usb_setup_params {
+ struct usb_dma_tag *dma_tag_p;
+ struct usb_page *dma_page_ptr;
+ struct usb_page_cache *dma_page_cache_ptr; /* these will be
+ * auto-freed */
+ struct usb_page_cache *xfer_page_cache_ptr; /* these will not be
+ * auto-freed */
+ struct usb_device *udev;
+ struct usb_xfer *curr_xfer;
+ const struct usb_config *curr_setup;
+ const struct usb_pipe_methods *methods;
+ void *buf;
+ usb_frlength_t *xfer_length_ptr;
+
+ usb_size_t size[7];
+ usb_frlength_t bufsize;
+ usb_frlength_t bufsize_max;
+
+ uint32_t hc_max_frame_size;
+ uint16_t hc_max_packet_size;
+ uint8_t hc_max_packet_count;
+ enum usb_dev_speed speed;
+ uint8_t dma_tag_max;
+ usb_error_t err;
+};
+
+/* function prototypes */
+
+uint8_t usbd_transfer_setup_sub_malloc(struct usb_setup_params *parm,
+ struct usb_page_cache **ppc, usb_size_t size, usb_size_t align,
+ usb_size_t count);
+void usb_dma_delay_done_cb(struct usb_xfer *);
+void usb_command_wrapper(struct usb_xfer_queue *pq,
+ struct usb_xfer *xfer);
+void usbd_pipe_enter(struct usb_xfer *xfer);
+void usbd_pipe_start(struct usb_xfer_queue *pq);
+void usbd_transfer_dequeue(struct usb_xfer *xfer);
+void usbd_transfer_done(struct usb_xfer *xfer, usb_error_t error);
+void usbd_transfer_enqueue(struct usb_xfer_queue *pq,
+ struct usb_xfer *xfer);
+void usbd_transfer_setup_sub(struct usb_setup_params *parm);
+void usbd_ctrl_transfer_setup(struct usb_device *udev);
+void usbd_clear_stall_locked(struct usb_device *udev,
+ struct usb_endpoint *ep);
+void usbd_clear_data_toggle(struct usb_device *udev,
+ struct usb_endpoint *ep);
+usb_callback_t usbd_do_request_callback;
+usb_callback_t usb_handle_request_callback;
+usb_callback_t usb_do_clear_stall_callback;
+void usbd_transfer_timeout_ms(struct usb_xfer *xfer,
+ void (*cb) (void *arg), usb_timeout_t ms);
+usb_timeout_t usbd_get_dma_delay(struct usb_device *udev);
+void usbd_transfer_power_ref(struct usb_xfer *xfer, int val);
+uint8_t usbd_xfer_get_isochronous_start_frame(struct usb_xfer *, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t *);
+
+#endif /* _USB_TRANSFER_H_ */
diff --git a/sys/dev/usb/usb_util.c b/sys/dev/usb/usb_util.c
new file mode 100644
index 000000000000..cd2dd7d6039e
--- /dev/null
+++ b/sys/dev/usb/usb_util.c
@@ -0,0 +1,249 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008-2022 Hans Petter Selasky
+ *
+ * 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.
+ */
+
+#ifdef USB_GLOBAL_INCLUDE_FILE
+#include USB_GLOBAL_INCLUDE_FILE
+#else
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+
+#include <dev/usb/usb_core.h>
+#include <dev/usb/usb_util.h>
+#include <dev/usb/usb_process.h>
+#include <dev/usb/usb_device.h>
+#include <dev/usb/usb_request.h>
+#include <dev/usb/usb_busdma.h>
+
+#include <dev/usb/usb_controller.h>
+#include <dev/usb/usb_bus.h>
+#endif /* USB_GLOBAL_INCLUDE_FILE */
+
+/*------------------------------------------------------------------------*
+ * device_set_usb_desc
+ *
+ * This function can be called at probe or attach to set the USB
+ * device supplied textual description for the given device.
+ *------------------------------------------------------------------------*/
+void
+device_set_usb_desc(device_t dev)
+{
+ struct usb_attach_arg *uaa;
+ struct usb_device *udev;
+ char *temp_p;
+ uint8_t do_unlock;
+
+ if (dev == NULL) {
+ /* should not happen */
+ return;
+ }
+ uaa = device_get_ivars(dev);
+ if (uaa == NULL) {
+ /* can happen if called at the wrong time */
+ return;
+ }
+ udev = uaa->device;
+
+ /* Protect scratch area */
+ do_unlock = usbd_ctrl_lock(udev);
+ temp_p = (char *)udev->scratch.data;
+ usb_devinfo(udev, temp_p, sizeof(udev->scratch.data));
+ if (do_unlock)
+ usbd_ctrl_unlock(udev);
+
+ device_set_desc_copy(dev, temp_p);
+ device_printf(dev, "<%s> on %s\n", temp_p,
+ device_get_nameunit(udev->bus->bdev));
+}
+
+/*------------------------------------------------------------------------*
+ * usb_pause_mtx - factored out code
+ *
+ * This function will delay the code by the passed number of system
+ * ticks. The passed mutex "mtx" will be dropped while waiting, if
+ * "mtx" is different from NULL.
+ *------------------------------------------------------------------------*/
+void
+usb_pause_mtx(struct mtx *mtx, int timo)
+{
+ if (mtx != NULL)
+ mtx_unlock(mtx);
+
+ /*
+ * Add one tick to the timeout so that we don't return too
+ * early! Note that pause() will assert that the passed
+ * timeout is positive and non-zero!
+ */
+ pause("USBWAIT", timo + 1);
+
+ if (mtx != NULL)
+ mtx_lock(mtx);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_printbcd
+ *
+ * This function will print the version number "bcd" to the string
+ * pointed to by "p" having a maximum length of "p_len" bytes
+ * including the terminating zero.
+ *------------------------------------------------------------------------*/
+void
+usb_printbcd(char *p, uint16_t p_len, uint16_t bcd)
+{
+ if (snprintf(p, p_len, "%x.%02x", bcd >> 8, bcd & 0xff)) {
+ /* ignore any errors */
+ }
+}
+
+/*------------------------------------------------------------------------*
+ * usb_trim_spaces
+ *
+ * This function removes spaces at the beginning and the end of the string
+ * pointed to by the "p" argument.
+ *------------------------------------------------------------------------*/
+void
+usb_trim_spaces(char *p)
+{
+ char *q;
+ char *e;
+
+ if (p == NULL)
+ return;
+ q = e = p;
+ while (*q == ' ') /* skip leading spaces */
+ q++;
+ while ((*p = *q++)) /* copy string */
+ if (*p++ != ' ') /* remember last non-space */
+ e = p;
+ *e = 0; /* kill trailing spaces */
+}
+
+/*------------------------------------------------------------------------*
+ * usb_make_str_desc - convert an ASCII string into a UNICODE string
+ *------------------------------------------------------------------------*/
+uint8_t
+usb_make_str_desc(void *ptr, uint16_t max_len, const char *s)
+{
+ struct usb_string_descriptor *p = ptr;
+ uint8_t totlen;
+ int j;
+
+ if (max_len < 2) {
+ /* invalid length */
+ return (0);
+ }
+ max_len = ((max_len / 2) - 1);
+
+ j = strlen(s);
+
+ if (j < 0) {
+ j = 0;
+ }
+ if (j > 126) {
+ j = 126;
+ }
+ if (max_len > j) {
+ max_len = j;
+ }
+ totlen = (max_len + 1) * 2;
+
+ p->bLength = totlen;
+ p->bDescriptorType = UDESC_STRING;
+
+ while (max_len--) {
+ USETW2(p->bString[max_len], 0, s[max_len]);
+ }
+ return (totlen);
+}
+
+/*------------------------------------------------------------------------*
+ * usb_check_request - prevent damaging USB requests
+ *
+ * Return values:
+ * 0: Access allowed
+ * Else: No access
+ *------------------------------------------------------------------------*/
+#if USB_HAVE_UGEN
+int
+usb_check_request(struct usb_device *udev, struct usb_device_request *req)
+{
+ struct usb_endpoint *ep;
+ int error;
+
+ /*
+ * Avoid requests that would damage the bus integrity:
+ */
+ if (((req->bmRequestType == UT_WRITE_DEVICE) &&
+ (req->bRequest == UR_SET_ADDRESS)) ||
+ ((req->bmRequestType == UT_WRITE_DEVICE) &&
+ (req->bRequest == UR_SET_CONFIG)) ||
+ ((req->bmRequestType == UT_WRITE_INTERFACE) &&
+ (req->bRequest == UR_SET_INTERFACE))) {
+ /*
+ * These requests can be useful for testing USB drivers.
+ */
+ error = priv_check(curthread, PRIV_DRIVER);
+ if (error)
+ return (error);
+ }
+
+ /*
+ * Special case - handle clearing of stall
+ */
+ if (req->bmRequestType == UT_WRITE_ENDPOINT) {
+ ep = usbd_get_ep_by_addr(udev, req->wIndex[0]);
+ if (ep == NULL)
+ return (EINVAL);
+ if ((req->bRequest == UR_CLEAR_FEATURE) &&
+ (UGETW(req->wValue) == UF_ENDPOINT_HALT))
+ usbd_clear_data_toggle(udev, ep);
+ }
+
+ /* TODO: add more checks to verify the interface index */
+
+ return (0);
+}
+#endif
diff --git a/sys/dev/usb/usb_util.h b/sys/dev/usb/usb_util.h
new file mode 100644
index 000000000000..711a6b8824cb
--- /dev/null
+++ b/sys/dev/usb/usb_util.h
@@ -0,0 +1,39 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_UTIL_H_
+#define _USB_UTIL_H_
+
+struct usb_device;
+struct usb_device_request;
+
+uint8_t usb_make_str_desc(void *ptr, uint16_t max_len, const char *s);
+void usb_printbcd(char *p, uint16_t p_len, uint16_t bcd);
+void usb_trim_spaces(char *p);
+int usb_check_request(struct usb_device *, struct usb_device_request *);
+
+#endif /* _USB_UTIL_H_ */
diff --git a/sys/dev/usb/usbdevs b/sys/dev/usb/usbdevs
new file mode 100644
index 000000000000..2318e6bd0017
--- /dev/null
+++ b/sys/dev/usb/usbdevs
@@ -0,0 +1,5156 @@
+/* $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.
+ *
+ * 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 CHIPSBANK 0x0204 Chipsbank Microelectronics Co.
+vendor HUMAX 0x02ad HUMAX
+vendor QUAN 0x01e1 Quan
+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 Semiconductor
+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 Microchip Technology
+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 (official)
+vendor SANYO 0x0474 Sanyo Electric
+vendor SEAGATE 0x0477 Seagate
+vendor CONNECTIX 0x0478 Connectix
+vendor SEMTECH 0x047a Semtech
+vendor DELL2 0x047c Dell
+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 / Hon Hai
+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 MICROCHIP 0x04d8 Microchip Technology, Inc.
+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 Microelectronics
+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 NINTENDO 0x057e Nintendo
+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 CISCOLINKSYS3 0x05a6 Cisco-Linksys
+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 SMK 0x0609 SMK
+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 MICRON 0x0634 Micron Technology
+vendor VIVITAR 0x0636 Vivitar
+vendor GUNZE 0x0637 Gunze Electronics USA
+vendor AVISION 0x0638 Avision
+vendor TEAC 0x0644 TEAC
+vendor ACTON 0x0647 Acton Research Corp.
+vendor OPTO 0x065a Optoelectronics Co., Ltd
+vendor SGI 0x065e Silicon Graphics
+vendor SANWASUPPLY 0x0663 Sanwa Supply
+vendor MEGATEC 0x0665 Megatec
+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 SYNAPTICS 0x06cb Synaptics, Inc.
+vendor KEYSPAN 0x06cd Keyspan / InnoSys Inc.
+vendor CONTEC 0x06ce Contec products
+vendor AASHIMA 0x06d6 Aashima Technology
+vendor LIEBERT 0x06da Liebert
+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 TECLAST 0x071b Teclast
+vendor SONYERICSSON 0x0731 Sony Ericsson
+vendor EICON 0x0734 Eicon Networks
+vendor MADCATZ 0x0738 Mad Catz, Inc.
+vendor SYNTECH 0x0745 Syntech Information
+vendor DIGITALSTREAM 0x074e Digital Stream
+vendor AUREAL 0x0755 Aureal Semiconductor
+vendor MAUDIO 0x0763 M-Audio
+vendor CYBERPOWER 0x0764 Cyber Power Systems, Inc.
+vendor SURECOM 0x0769 Surecom Technology
+vendor HIDGLOBAL 0x076b HID Global
+vendor LINKSYS2 0x077b Linksys
+vendor GRIFFIN 0x077d Griffin Technology
+vendor SANDISK 0x0781 SanDisk
+vendor JENOPTIK 0x0784 Jenoptik
+vendor LOGITEC 0x0789 Logitec
+vendor NOKIA2 0x078b Nokia
+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 KINGSUN 0x07c0 KingSun
+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 BEWAN 0x07fa Bewan
+vendor CENTURY 0x07f7 Century Corp
+vendor NEWLINK 0x07ff NEWlink
+vendor MAGTEK 0x0801 Mag-Tek
+vendor ZOOM 0x0803 Zoom Telephonics
+vendor PCS 0x0810 Personal Communication Systems
+vendor SYNET 0x0812 Synet Electronics
+vendor ALPHASMART 0x081e AlphaSmart, Inc.
+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 FUJITSUSIEMENS 0x08d4 Fujitsu-Siemens
+vendor BILLIONTON 0x08dd Billionton Systems
+vendor GEMALTO 0x08e6 Gemalto SA
+vendor EXTENDED 0x08e9 Extended Systems
+vendor MSYSTEMS 0x08ec M-Systems
+vendor DIGIANSWER 0x08fd Digianswer
+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 DYMO 0x0922 DYMO
+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 NVIDIA 0x0955 NVIDIA Corporation
+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 ZIPPY 0x099a Zippy Technology Corporation
+vendor POINTCHIPS 0x09a6 PointChips
+vendor INTERSIL 0x09aa Intersil
+vendor TRIPPLITE2 0x09ae Tripp Lite
+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 PLOYTEC 0x0a4a Ploytec GmbH
+vendor BROADCOM 0x0a5c Broadcom
+vendor GREENHOUSE 0x0a6b GREENHOUSE
+vendor MEDELI 0x0a67 Medeli
+vendor GEOCAST 0x0a79 Geocast Network Systems
+vendor EGO 0x0a92 EGO systems
+vendor IDQUANTIQUE 0x0aba ID Quantique
+vendor IDTECH 0x0acd ID TECH
+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 ADLINK 0x0b63 ADLINK Technoligy, Inc.
+vendor ATI2 0x0b6f ATI Technologies
+vendor ZEEVO 0x0b7a Zeevo, Inc.
+vendor KURUSUGAWA 0x0b7e Kurusugawa Electronics, Inc.
+vendor SMART 0x0b8c Smart Technologies
+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 ERICSSON2 0x0bdb Ericsson
+vendor MEI 0x0bed MEI
+vendor ADDONICS2 0x0bf6 Addonics Technology
+vendor FSC 0x0bf8 Fujitsu Siemens Computers
+vendor AGATE 0x0c08 Agate Technologies
+vendor DMI 0x0c0b DMI
+vendor CANYON 0x0c10 Canyon
+vendor ICOM 0x0c26 Icom Inc.
+vendor GNOTOMETRICS 0x0c33 GN Otometrics
+vendor CHICONY2 0x0c45 Chicony / Microdia / Sonix Technology Co., Ltd.
+vendor REINERSCT 0x0c4b Reiner-SCT
+vendor SEALEVEL 0x0c52 Sealevel System
+vendor JETI 0x0c6c Jeti
+vendor LUWEN 0x0c76 Luwen
+vendor ELEKTOR 0x0c7d ELEKTOR Electronics
+vendor KYOCERA2 0x0c88 Kyocera Wireless Corp.
+vendor ZCOM 0x0cde Z-Com
+vendor ATHEROS2 0x0cf3 Atheros Communications
+vendor POSIFLEX 0x0d3a POSIFLEX
+vendor TANGTOP 0x0d3d Tangtop
+vendor KOBIL 0x0d46 KOBIL
+vendor SMC3 0x0d5c Standard Microsystems
+vendor ADDON 0x0d7d Add-on Technology
+vendor ACDC 0x0d7e American Computer & Digital Components
+vendor CMEDIA 0x0d8c CMEDIA
+vendor CONCEPTRONIC 0x0d8e Conceptronic
+vendor SKANHEX 0x0d96 Skanhex Technology, Inc.
+vendor POWERCOM 0x0d9f PowerCOM
+vendor MSI 0x0db0 Micro Star International
+vendor ELCON 0x0db7 ELCON Systemtechnik
+vendor UNKNOWN4 0x0dcd Unknown vendor
+vendor NETAC 0x0dd8 Netac
+vendor SITECOMEU 0x0df6 Sitecom Europe
+vendor MOBILEACTION 0x0df7 Mobile Action
+vendor AMIGO 0x0e0b Amigo Technology
+vendor SMART2 0x0e39 Smart Modular Technologies
+vendor SPEEDDRAGON 0x0e55 Speed Dragon Multimedia
+vendor HAWKING 0x0e66 Hawking
+vendor FOSSIL 0x0e67 Fossil, Inc
+vendor GMATE 0x0e7e G.Mate, Inc
+vendor MEDIATEK 0x0e8d MediaTek, 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 LARSENBRUSGAARD 0x0fd8 Larsen and Brusgaard
+vendor OWL 0x0fde OWL
+vendor KONTRON 0x0fe6 Kontron AG
+vendor DVICO 0x0fe9 DViCO
+vendor QUALCOMM 0x1004 Qualcomm
+vendor APACER 0x1005 Apacer
+vendor MOTOROLA4 0x100d Motorola
+vendor HP3 0x103c Hewlett Packard
+vendor AIRPLUS 0x1011 Airplus
+vendor DESKNOTE 0x1019 Desknote
+vendor AMD2 0x1022 Advanced Micro Devices
+vendor NEC3 0x1033 NEC
+vendor TTI 0x103e Thurlby Thandar Instruments
+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 HONEYWELL 0x10ac Honeywell
+vendor LIEBERT2 0x10af Liebert
+vendor PLX 0x10b5 PLX
+vendor ASANTE 0x10bd Asante
+vendor SILABS 0x10c4 Silicon Labs
+vendor SILABS3 0x10c5 Silicon Labs
+vendor SILABS4 0x10ce Silicon Labs
+vendor ACTIONS 0x10d6 Actions
+vendor MOXA 0x110a Moxa
+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 DLINK3 0x1186 Dlink
+vendor ACERCM 0x1189 Acer Communications & Multimedia
+vendor SIERRA 0x1199 Sierra Wireless
+vendor SANWA 0x11ad Sanwa Electric Instrument Co., Ltd.
+vendor TOPFIELD 0x11db Topfield Co., Ltd
+vendor SIEMENS3 0x11f5 Siemens
+vendor NETINDEX 0x11f6 NetIndex
+vendor ALCATEL 0x11f7 Alcatel
+vendor INTERBIOMETRICS 0x1209 Interbiometrics
+vendor FUJITSU3 0x1221 Fujitsu Ltd.
+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 TESTO 0x128d Testo products
+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 VALIDITY 0x138a Validity Sensors, Inc.
+vendor BALTECH 0x13ad Baltech
+vendor CISCOLINKSYS 0x13b1 Cisco-Linksys
+vendor SHARK 0x13d2 Shark
+vendor AZUREWAVE 0x13d3 AsureWave
+vendor INITIO 0x13fd Initio Corporation
+vendor EMTEC 0x13fe Emtec
+vendor NOVATEL 0x1410 Novatel Wireless
+vendor OMNIVISION2 0x1415 OmniVision Technologies, Inc.
+vendor MERLIN 0x1416 Merlin
+vendor REDOCTANE 0x1430 RedOctane
+vendor WISTRONNEWEB 0x1435 Wistron NeWeb
+vendor RADIOSHACK 0x1453 Radio Shack
+vendor FIC 0x1457 FIC / OpenMoko
+vendor HUAWEI3COM 0x1472 Huawei-3Com
+vendor ABOCOM2 0x1482 AboCom Systems
+vendor SILICOM 0x1485 Silicom
+vendor RALINK 0x148f Ralink Technology
+vendor IMAGINATION 0x149a Imagination Technologies
+vendor ATP 0x14af ATP Electronics
+vendor CONCEPTRONIC2 0x14b2 Conceptronic
+vendor SUPERTOP 0x14cd Super Top
+vendor PLANEX3 0x14ea Planex Communications
+vendor SILICONPORTALS 0x1527 Silicon Portals
+vendor UBIQUAM 0x1529 UBIQUAM Co., Ltd.
+vendor JMICRON 0x152d JMicron
+vendor UBLOX 0x1546 U-blox
+vendor PNY 0x154b PNY
+vendor OWEN 0x1555 Owen
+vendor OQO 0x1557 OQO
+vendor UMEDIA 0x157e U-MEDIA Communications
+vendor FIBERLINE 0x1582 Fiberline
+vendor FREESCALE 0x15a2 Freescale Semiconductor, Inc.
+vendor AFATECH 0x15a4 Afatech Technologies, Inc.
+vendor SPARKLAN 0x15a9 SparkLAN
+vendor OLIMEX 0x15ba Olimex
+vendor SOUNDGRAPH 0x15c2 Soundgraph, Inc.
+vendor AMIT2 0x15c5 AMIT
+vendor TEXTECH 0x15ca Textech International Ltd.
+vendor SOHOWARE 0x15e8 SOHOware
+vendor ABIT 0x15eb ABIT Corporation
+vendor UMAX 0x1606 UMAX Data Systems
+vendor INSIDEOUT 0x1608 Inside Out Networks
+vendor AMOI 0x1614 Amoi Electronics
+vendor GOODWAY 0x1631 Good Way Technology
+vendor ENTREGA 0x1645 Entrega
+vendor ACTIONTEC 0x1668 Actiontec Electronics
+vendor CLIPSAL 0x166a Clipsal
+vendor CISCOLINKSYS2 0x167b Cisco-Linksys
+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 WIENERPLEINBAUS 0x16dc WIENER Plein & Baus GmbH.
+vendor AXESSTEL 0x1726 Axesstel Co., Ltd.
+vendor LINKSYS4 0x1737 Linksys
+vendor SENAO 0x1740 Senao
+vendor ASUS2 0x1761 ASUS
+vendor SWEEX2 0x177f Sweex
+vendor METAGEEK 0x1781 MetaGeek
+vendor KAMSTRUP 0x17a8 Kamstrup A/S
+vendor MISC 0x1781 Misc Vendors
+vendor DISPLAYLINK 0x17e9 DisplayLink
+vendor LENOVO 0x17ef Lenovo
+vendor WAVESENSE 0x17f4 WaveSense
+vendor VAISALA 0x1843 Vaisala
+vendor E3C 0x18b4 E3C Technologies
+vendor AMIT 0x18c5 AMIT
+vendor GOOGLE 0x18d1 Google
+vendor QCOM 0x18e8 Qcom
+vendor ELV 0x18ef ELV
+vendor LINKSYS3 0x1915 Linksys
+vendor MEINBERG 0x1938 Meinberg Funkuhren
+vendor BECEEM 0x198f Beceem Communications
+vendor ZTE 0x19d2 ZTE
+vendor QUALCOMMINC 0x19d2 Qualcomm, Incorporated
+vendor QUALCOMM3 0x19f5 Qualcomm, Inc.
+vendor QUANTA2 0x1a32 Quanta
+vendor TERMINUS 0x1a40 Terminus Technology
+vendor ABBOTT 0x1a61 Abbott Diabetics
+vendor BAYER 0x1a79 Bayer
+vendor WCH2 0x1a86 QinHeng Electronics
+vendor STELERA 0x1a8d Stelera Wireless
+vendor SEL 0x1adb Schweitzer Engineering Laboratories
+vendor CORSAIR 0x1b1c Corsair
+vendor ASM 0x1b21 ASMedia Technology
+vendor MATRIXORBITAL 0x1b3d Matrix Orbital
+vendor OVISLINK 0x1b75 OvisLink
+vendor TML 0x1b91 The Mobility Lab
+vendor TCTMOBILE 0x1bbb TCT Mobile
+vendor ALTI2 0x1bc9 Alti-2 products
+vendor SUNPLUS 0x1bcf Sunplus Innovation Technology Inc.
+vendor WAGO 0x1be3 WAGO Kontakttechnik GmbH.
+vendor TELIT 0x1bc7 Telit
+vendor IONICS 0x1c0c Ionics PlugComputer
+vendor LONGCHEER 0x1c9e Longcheer Holdings, Ltd.
+vendor MPMAN 0x1cae MpMan
+vendor DRESDENELEKTRONIK 0x1cf1 dresden elektronik
+vendor NEOTEL 0x1d09 Neotel
+vendor DREAMLINK 0x1d34 Dream Link
+vendor PEGATRON 0x1d4d Pegatron
+vendor QISDA 0x1da5 Qisda
+vendor METAGEEK2 0x1dd5 MetaGeek
+vendor ALINK 0x1e0e Alink
+vendor AIRTIES 0x1eda AirTies
+vendor FESTO 0x1e29 Festo
+vendor LAKESHORE 0x1fb9 Lake Shore Cryotronics, Inc.
+vendor VERTEX 0x1fe7 Vertex Wireless Co., Ltd.
+vendor DLINK 0x2001 D-Link
+vendor PLANEX2 0x2019 Planex Communications
+vendor HAUPPAUGE2 0x2040 Hauppauge Computer Works
+vendor TLAYTECH 0x20b9 Tlay Tech
+vendor ENCORE 0x203d Encore
+vendor QIHARDWARE 0x20b7 QI-hardware
+vendor PARA 0x20b8 PARA Industrial
+vendor SIMTEC 0x20df Simtec Electronics
+vendor TRENDNET 0x20f4 TRENDnet
+vendor RTSYSTEMS 0x2100 RT Systems
+vendor DLINK4 0x2101 D-Link
+vendor VIALABS 0x2109 VIA Labs
+vendor ERICSSON 0x2282 Ericsson
+vendor MOTOROLA2 0x22b8 Motorola
+vendor WETELECOM 0x22de WeTelecom
+vendor PINNACLE 0x2304 Pinnacle Systems
+vendor ARDUINO 0x2341 Arduino SA
+vendor TPLINK 0x2357 TP-Link
+vendor WESTMOUNTAIN 0x2405 West Mountain Radio
+vendor TRIPPLITE 0x2478 Tripp-Lite
+vendor NORELSYS 0x2537 NOREL Systems Ltd.
+vendor TENDA2 0x2604 Tenda
+vendor HIROSE 0x2631 Hirose Electric
+vendor XIAOMI 0x2717 Xiaomi
+vendor NHJ 0x2770 NHJ
+vendor THINGM 0x27b8 ThingM
+vendor PERASO 0x2932 Peraso Technologies, Inc.
+vendor PLANEX 0x2c02 Planex Communications
+vendor MERCUSYS 0x2c4e Mercusys, Inc.
+vendor QUECTEL 0x2c7c Quectel Wireless Solutions
+vendor VIDZMEDIA 0x3275 VidzMedia Pte Ltd
+vendor LINKINSTRUMENTS 0x3195 Link Instruments Inc.
+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 FEIXUN 0x4855 FeiXun Communication
+vendor PAPOUCH 0x5050 Papouch products
+vendor AVERATEC 0x50c2 Averatec
+vendor SWEEX 0x5173 Sweex
+vendor PROLIFIC2 0x5372 Prolific Technologies
+vendor ONSPEC2 0x55aa OnSpec Electronic Inc.
+vendor ZINWELL 0x5a57 Zinwell
+vendor INGENIC 0x601a Ingenic Semiconductor Ltd.
+vendor SITECOM 0x6189 Sitecom
+vendor SPRINGERDESIGN 0x6400 Springer Design, Inc.
+vendor ARKMICRO 0x6547 Arkmicro Technologies Inc.
+vendor 3COM2 0x6891 3Com
+vendor EDIMAX 0x7392 Edimax
+vendor INTEL 0x8086 Intel
+vendor INTEL2 0x8087 Intel
+vendor ALLWIN 0x8516 ALLWIN Tech
+vendor SITECOM2 0x9016 Sitecom
+vendor MOSCHIP 0x9710 MosChip Semiconductor
+vendor NETGEAR4 0x9846 Netgear
+vendor MARVELL 0x9e88 Marvell Technology Group Ltd.
+vendor 3COM3 0xa727 3Com
+vendor CACE 0xcace CACE Technologies
+vendor COMPARE 0xcdab Compare
+vendor DATAAPEX 0xdaae DataApex
+vendor EVOLUTION 0xdeee Evolution Robotics
+vendor EMPIA 0xeb1a eMPIA Technology
+vendor HP2 0xf003 Hewlett Packard
+vendor LOGILINK 0xfc08 LogiLink
+vendor USRP 0xfffe GNU Radio USRP
+
+/*
+ * List of known products. Grouped by vendor.
+ */
+
+/* 3Com products */
+product 3COM HOMECONN 0x009d HomeConnect USB 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
+
+/* Abbott Diabetics */
+product ABBOTT STEREO_PLUG 0x3410 Abbott Diabetics Stereo Plug
+product ABBOTT STRIP_PORT 0x3420 Abbott Diabetics Strip Port
+
+/* ABIT products */
+product ABIT AK_020 0x7d0e 3G modem
+
+product ACDC HUB 0x2315 USB Pen Drive HUB
+product ACDC SECWRITE 0x2316 USB Pen Drive Secure Write
+product ACDC PEN 0x2317 USB Pen Drive with Secure Write
+
+/* AboCom products */
+product ABOCOM XX1 0x110c XX1
+product ABOCOM XX2 0x200c XX2
+product ABOCOM RT2770 0x2770 RT2770
+product ABOCOM RT2870 0x2870 RT2870
+product ABOCOM RT3070 0x3070 RT3070
+product ABOCOM RT3071 0x3071 RT3071
+product ABOCOM RT3072 0x3072 RT3072
+product ABOCOM2 RT2870_1 0x3c09 RT2870
+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 RTL8188CU_1 0x8188 RTL8188CU
+product ABOCOM RTL8188CU_2 0x8189 RTL8188CU
+product ABOCOM RTL8192CU 0x8178 RTL8192CU
+product ABOCOM RTL8188EU 0x8179 RTL8188EU
+product ABOCOM WUG2700 0xb21f WUG2700
+
+/* Acton Research Corp. */
+product ACTON SPECTRAPRO 0x0100 FTDI compatible adapter
+
+/* 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_NF 0x4505 SMCWUSB-G (no firmware)
+product ACCTON SMCWUSBG 0x4506 SMCWUSB-G
+product ACCTON SMCWUSBTG2_NF 0x4507 SMCWUSBT-G2 (no firmware)
+product ACCTON SMCWUSBTG2 0x4508 SMCWUSBT-G2
+product ACCTON PRISM_GT 0x4521 PrismGT USB 2.0 WLAN
+product ACCTON SS1001 0x5046 SpeedStream Ethernet Adapter
+product ACCTON RT2870_2 0x6618 RT2870
+product ACCTON RT3070 0x7511 RT3070
+product ACCTON RT2770 0x7512 RT2770
+product ACCTON RT2870_3 0x7522 RT2870
+product ACCTON RT2870_5 0x8522 RT2870
+product ACCTON RT3070_4 0xa512 RT3070
+product ACCTON RT2870_4 0xa618 RT2870
+product ACCTON RT3070_1 0xa701 RT3070
+product ACCTON RT3070_2 0xa702 RT3070
+product ACCTON RT2870_1 0xb522 RT2870
+product ACCTON RT3070_3 0xc522 RT3070
+product ACCTON RT3070_5 0xd522 RT3070
+product ACCTON RTL8192SU 0xc512 RTL8192SU
+product ACCTON ZD1211B 0xe501 ZD1211B
+product ACCTON WN7512 0xf522 WN7512
+
+/* 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 S81 0x4027 BenQ S81 phone
+product ACERP H10 0x4068 AWL400 Wireless Adapter
+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
+
+/* Actions products */
+product ACTIONS MP4 0x1101 Actions MP4 Player
+
+/* 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
+
+/* Addonics products */
+product ADDONICS2 205 0xa001 Cable 205
+
+/* Addtron products */
+product ADDTRON AWU120 0xff31 AWU-120
+
+/* ADLINK Texhnology products */
+product ADLINK ND6530 0x6530 ND-6530 USB-Serial
+
+/* 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
+
+/* Afatech Technologies, Inc. */
+product AFATECH AFATECH1336 0x1336 Flash Card Reader
+
+/* 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
+product AIPTEK2 SUNPLUS_TECH 0x0c15 Sunplus Technology Inc.
+
+/* AirPlis products */
+product AIRPLUS MCD650 0x3198 MCD650 modem
+
+/* AirPrime products */
+product AIRPRIME PC5220 0x0112 CDMA Wireless PC Card
+product AIRPRIME USB308 0x68A3 USB308 HSPA+ USB Modem
+product AIRPRIME AC313U 0x68aa Sierra Wireless AirCard 313U
+
+/* AirTies products */
+product AIRTIES RT3070 0x2310 RT3070
+
+/* AKS products */
+product AKS USBHASP 0x0001 USB-HASP 0.06
+
+/* Alcatel products */
+product ALCATEL OT535 0x02df One Touch 535/735
+
+/* Alcor Micro, Inc. products */
+product ALCOR2 KBD_HUB 0x2802 Kbd Hub
+
+product ALCOR SDCR_6335 0x6335 SD/MMC Card Reader
+product ALCOR SDCR_6362 0x6362 SD/MMC Card Reader
+product ALCOR SDCR_6366 0x6366 SD/MMC Card Reader
+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
+product ALCOR AU9720 0x9720 USB2 - RS-232
+product ALCOR AU6390 0x6390 AU6390 USB-IDE converter
+
+/* Alink products */
+product ALINK DWM652U5 0xce16 DWM-652
+product ALINK 3G 0x9000 3G modem
+product ALINK SIM7600E 0x9001 LTE modem
+product ALINK SIM7600G 0x9011 LTE modem
+product ALINK 3GU 0x9200 3G modem
+
+/* Altec Lansing products */
+product ALTEC ADA70 0x0070 ADA70 Speakers
+product ALTEC ASC495 0xff05 ASC495 Speakers
+
+/* Alti-2 products */
+product ALTI2 N3 0x6001 FTDI compatible adapter
+
+/* Allied Telesyn International products */
+product ALLIEDTELESYN ATUSB100 0xb100 AT-USB100
+
+/* ALLWIN Tech products */
+product ALLWIN RT2070 0x2070 RT2070
+product ALLWIN RT2770 0x2770 RT2770
+product ALLWIN RT2870 0x2870 RT2870
+product ALLWIN RT3070 0x3070 RT3070
+product ALLWIN RT3071 0x3071 RT3071
+product ALLWIN RT3072 0x3072 RT3072
+product ALLWIN RT3572 0x3572 RT3572
+
+/* AlphaSmart, Inc. products */
+product ALPHASMART DANA_KB 0xdbac AlphaSmart Dana Keyboard
+product ALPHASMART DANA_SYNC 0xdf00 AlphaSmart Dana HotSync
+
+/* Amoi products */
+product AMOI H01 0x0800 H01 3G modem
+product AMOI H01A 0x7002 H01A 3G modem
+product AMOI H02 0x0802 H02 3G modem
+
+/* American Power Conversion products */
+product APC UPS 0x0002 Uninterruptible Power Supply
+
+/* American Power Conversion products */
+product APC UPS1000 0x0003 Uninterruptible Power Supply
+
+/* Ambit Microsystems products */
+product AMBIT WLAN 0x0302 WLAN
+product AMBIT NTL_250 0x6098 NTL 250 cable modem
+
+/* Apacer products */
+product APACER HT202 0xb113 USB 2.0 Flash Drive
+
+/* Amigo Technology products */
+product AMIGO RT2870_1 0x9031 RT2870
+product AMIGO RT2870_2 0x9041 RT2870
+
+/* AMIT products */
+product AMIT CGWLUSB2GO 0x0002 CG-WLUSB2GO
+product AMIT CGWLUSB2GNR 0x0008 CG-WLUSB2GNR
+product AMIT RT2870_1 0x0012 RT2870
+
+/* AMIT(2) products */
+product AMIT2 RT2870 0x0008 RT2870
+
+/* Analog Devices products */
+product ANALOGDEVICES GNICE 0xf000 FTDI compatible adapter
+product ANALOGDEVICES GNICEPLUS 0xf001 FTDI compatible adapter
+
+/* Anchor products */
+product ANCHOR SERIAL 0x2008 Serial
+product ANCHOR EZUSB 0x2131 EZUSB
+product ANCHOR EZLINK 0x2720 EZLINK
+
+/* AnyData products */
+product ANYDATA ADU_620UW 0x6202 CDMA 2000 EV-DO USB Modem
+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
+
+/* Apple Computer products */
+product APPLE IMAC_KBD 0x0201 USB iMac Keyboard
+product APPLE KBD 0x0202 USB Keyboard M2452
+product APPLE EXT_KBD 0x020c Apple Extended USB Keyboard
+/* PowerBooks Feb 2005, iBooks G4 */
+product APPLE FOUNTAIN_ANSI 0x020e Apple Internal Keyboard/Trackpad
+product APPLE FOUNTAIN_ISO 0x020f Apple Internal Keyboard/Trackpad
+/* 17 inch PowerBook */
+product APPLE GEYSER_17 0x020d Apple Internal Keyboard/Trackpad
+/* PowerBooks Oct 2005 */
+product APPLE GEYSER_ANSI 0x0214 Apple Internal Keyboard/Trackpad
+product APPLE GEYSER_ISO 0x0215 Apple Internal Keyboard/Trackpad
+product APPLE GEYSER_JIS 0x0216 Apple Internal Keyboard/Trackpad
+/* Core Duo MacBook & MacBook Pro */
+product APPLE GEYSER3_ANSI 0x0217 Apple Internal Keyboard/Trackpad
+product APPLE GEYSER3_ISO 0x0218 Apple Internal Keyboard/Trackpad
+product APPLE GEYSER3_JIS 0x0219 Apple Internal Keyboard/Trackpad
+/* Core2 Duo MacBook & MacBook Pro */
+product APPLE GEYSER4_ANSI 0x021a Apple Internal Keyboard/Trackpad
+product APPLE GEYSER4_ISO 0x021b Apple Internal Keyboard/Trackpad
+product APPLE GEYSER4_JIS 0x021c Apple Internal Keyboard/Trackpad
+/* External */
+product APPLE ALU_MINI_ANSI 0x021d Apple Keyboard/Trackpad
+product APPLE ALU_MINI_ISO 0x021e Apple Keyboard/Trackpad
+product APPLE ALU_MINI_JIS 0x021f Apple Keyboard/Trackpad
+product APPLE ALU_ANSI 0x0220 Apple Keyboard/Trackpad
+product APPLE ALU_ISO 0x0221 Apple Keyboard/Trackpad
+product APPLE ALU_JIS 0x0222 Apple Keyboard/Trackpad
+/* MacbookAir, aka wellspring */
+product APPLE WELLSPRING_ANSI 0x0223 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING_ISO 0x0224 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING_JIS 0x0225 Apple Internal Keyboard/Trackpad
+/* Core2 Duo MacBook3,1 */
+product APPLE GEYSER4_HF_ANSI 0x0229 Apple Internal Keyboard/Trackpad
+product APPLE GEYSER4_HF_ISO 0x022a Apple Internal Keyboard/Trackpad
+product APPLE GEYSER4_HF_JIS 0x022b Apple Internal Keyboard/Trackpad
+/* MacbookProPenryn, aka wellspring2 */
+product APPLE WELLSPRING2_ANSI 0x0230 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING2_ISO 0x0231 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING2_JIS 0x0232 Apple Internal Keyboard/Trackpad
+/* Macbook5,1 (unibody), aka wellspring3 */
+product APPLE WELLSPRING3_ANSI 0x0236 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING3_ISO 0x0237 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING3_JIS 0x0238 Apple Internal Keyboard/Trackpad
+/* MacbookAir3,2 (unibody), aka wellspring4 */
+product APPLE WELLSPRING4_ANSI 0x023f Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING4_ISO 0x0240 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING4_JIS 0x0241 Apple Internal Keyboard/Trackpad
+/* MacbookAir3,1 (unibody), aka wellspring4 */
+product APPLE WELLSPRING4A_ANSI 0x0242 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING4A_ISO 0x0243 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING4A_JIS 0x0244 Apple Internal Keyboard/Trackpad
+/* Macbook8 (unibody, March 2011) */
+product APPLE WELLSPRING5_ANSI 0x0245 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING5_ISO 0x0246 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING5_JIS 0x0247 Apple Internal Keyboard/Trackpad
+/* MacbookAir4,1 (unibody, July 2011) */
+product APPLE WELLSPRING6A_ANSI 0x0249 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING6A_ISO 0x024a Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING6A_JIS 0x024b Apple Internal Keyboard/Trackpad
+/* MacbookAir4,2 (unibody, July 2011) */
+product APPLE WELLSPRING6_ANSI 0x024c Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING6_ISO 0x024d Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING6_JIS 0x024e Apple Internal Keyboard/Trackpad
+/* External */
+product APPLE ALU_REVB_ANSI 0x024f Apple Keyboard/Trackpad
+product APPLE ALU_REVB_ISO 0x0250 Apple Keyboard/Trackpad
+product APPLE ALU_REVB_JIS 0x0251 Apple Keyboard/Trackpad
+/* Macbook8,2 (unibody) */
+product APPLE WELLSPRING5A_ANSI 0x0252 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING5A_ISO 0x0253 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING5A_JIS 0x0254 Apple Internal Keyboard/Trackpad
+/* MacbookPro10,1 (unibody, June 2012) */
+product APPLE WELLSPRING7_ANSI 0x0262 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING7_ISO 0x0263 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING7_JIS 0x0264 Apple Internal Keyboard/Trackpad
+/* MacbookPro10,2 (unibody, October 2012) */
+product APPLE WELLSPRING7A_ANSI 0x0259 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING7A_ISO 0x025a Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING7A_JIS 0x025b Apple Internal Keyboard/Trackpad
+/* MacbookAir6,2 (unibody, June 2013) */
+product APPLE WELLSPRING8_ANSI 0x0290 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING8_ISO 0x0291 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING8_JIS 0x0292 Apple Internal Keyboard/Trackpad
+/* MacbookPro12,1 */
+product APPLE WELLSPRING9_ANSI 0x0272 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING9_ISO 0x0273 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRING9_JIS 0x0274 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRINGT2_J140K 0x027a Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRINGT2_J132 0x027b Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRINGT2_J680 0x027c Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRINGT2_J213 0x027d Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRINGT2_J214K 0x027e Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRINGT2_J223 0x027f Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRINGT2_J230K 0x0280 Apple Internal Keyboard/Trackpad
+product APPLE WELLSPRINGT2_J152F 0x0340 Apple Internal Keyboard/Trackpad
+product APPLE MAGIC_KEYBOARD_2021 0x029c Apple Internal Keyboard/Trackpad
+product APPLE MAGIC_KEYBOARD_FINGERPRINT_2021 0x029a Apple Keyboard/Trackpad
+product APPLE MAGIC_TRACKPAD2 0x0265 Apple Magic Trackpad 2
+product APPLE MOUSE 0x0301 Mouse M4848
+product APPLE OPTMOUSE 0x0302 Optical mouse
+product APPLE MIGHTYMOUSE 0x0304 Mighty Mouse
+product APPLE KBD_HUB 0x1001 Hub in Apple USB Keyboard
+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 IPOD_TOUCH 0x1291 iPod Touch
+product APPLE IPHONE_3G 0x1292 iPhone 3G
+product APPLE IPHONE_3GS 0x1294 iPhone 3GS
+product APPLE IPHONE_4 0x1297 iPhone 4
+product APPLE IPHONE_4S 0x12a0 iPhone 4S
+product APPLE IPHONE_5 0x12a8 iPhone 5
+product APPLE IPAD 0x129a iPad
+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 AX88178A 0x178a AX88178A USB 2.0 10/100/1000 Ethernet
+product ASIX AX88179 0x1790 AX88179 USB 3.0 10/100/1000 Ethernet
+product ASIX AX88772 0x7720 AX88772
+product ASIX AX88772A 0x772a AX88772A USB 2.0 10/100 Ethernet
+product ASIX AX88772B 0x772b AX88772B USB 2.0 10/100 Ethernet
+product ASIX AX88772B_1 0x7e2b AX88772B USB 2.0 10/100 Ethernet
+
+/* ASUS products */
+product ASUS2 USBN11 0x0b05 USB-N11
+product ASUS RT2570 0x1706 RT2500USB Wireless Adapter
+product ASUS WL167G 0x1707 WL-167g Wireless Adapter
+product ASUS WL159G 0x170c WL-159g
+product ASUS A9T_WIFI 0x171b A9T wireless
+product ASUS P5B_WIFI 0x171d P5B wireless
+product ASUS RT2573_1 0x1723 RT2573
+product ASUS RT2573_2 0x1724 RT2573
+product ASUS LCM 0x1726 LCM display
+product ASUS RT2870_1 0x1731 RT2870
+product ASUS RT2870_2 0x1732 RT2870
+product ASUS RT2870_3 0x1742 RT2870
+product ASUS RT2870_4 0x1760 RT2870
+product ASUS RT2870_5 0x1761 RT2870
+product ASUS USBN13 0x1784 USB-N13
+product ASUS USBN10 0x1786 USB-N10
+product ASUS RT3070_1 0x1790 RT3070
+product ASUS RTL8192SU 0x1791 RTL8192SU
+product ASUS USB_N53 0x179d ASUS Black Diamond Dual Band USB-N53
+product ASUS RTL8192CU 0x17ab RTL8192CU
+product ASUS USBN66 0x17ad USB-N66
+product ASUS USBN10NANO 0x17ba USB-N10 Nano
+product ASUS USBAC51 0x17d1 USB-AC51
+product ASUS USBAC56 0x17d2 USB-AC56
+product ASUS USBN14 0x17e8 USB-N14
+product ASUS USBN10NANOB1 0x18f0 USB-N10 Nano rev B1
+product ASUS A730W 0x4202 ASUS MyPal A730W
+product ASUS P535 0x420f ASUS P535 PDA
+product ASUS GMSC 0x422f ASUS Generic Mass Storage
+
+/* 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 UC232B 0x2022 Serial
+product ATEN UC210T 0x2009 UC-210T Ethernet
+product ATEN DSB650C 0x4000 DSB-650C
+
+/* ATP Electronics products */
+product ATP EUSB 0xaf01 ATP IG eUSB SSD
+
+/* 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)
+product ATHEROS2 TG121N 0x1001 TG121N
+product ATHEROS2 WN821NV2 0x1002 WN821NV2
+product ATHEROS2 3CRUSBN275 0x1010 3CRUSBN275
+product ATHEROS2 WN612 0x1011 WN612
+product ATHEROS2 AR9170 0x9170 AR9170
+
+/* Atmel Comp. products */
+product ATMEL STK541 0x2109 Zigbee Controller
+product ATMEL UHB124 0x3301 AT43301 USB 1.1 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
+
+/* AuthenTec products */
+product AUTHENTEC AES1610 0x1600 AES1610 Fingerprint Sensor
+
+/* Avision products */
+product AVISION 1200U 0x0268 1200U scanner
+
+/* AVM products */
+product AVM FRITZWLAN 0x8401 FRITZ!WLAN N
+
+/* Axesstel products */
+product AXESSTEL DATAMODEM 0x1000 Data Modem
+
+/* AsureWave products */
+product AZUREWAVE RT2870_1 0x3247 RT2870
+product AZUREWAVE RT2870_2 0x3262 RT2870
+product AZUREWAVE RT3070_1 0x3273 RT3070
+product AZUREWAVE RT3070_2 0x3284 RT3070
+product AZUREWAVE RT3070_3 0x3305 RT3070
+product AZUREWAVE RTL8188CU 0x3357 RTL8188CU
+product AZUREWAVE RTL8188CE_1 0x3358 RTL8188CE
+product AZUREWAVE RTL8188CE_2 0x3359 RTL8188CE
+product AZUREWAVE RTL8192SU_1 0x3306 RTL8192SU
+product AZUREWAVE RTL8192SU_2 0x3309 RTL8192SU
+product AZUREWAVE RTL8192SU_3 0x3310 RTL8192SU
+product AZUREWAVE RTL8192SU_4 0x3311 RTL8192SU
+product AZUREWAVE RTL8192SU_5 0x3325 RTL8192SU
+
+/* Baltech products */
+product BALTECH CARDREADER 0x9999 Card reader
+product BALTECH SMARTCARDREADER 0xf019 SmartCard reader
+
+/* Bayer products */
+product BAYER CONTOUR_CABLE 0x6001 FTDI compatible adapter
+
+/* B&B Electronics products */
+product BBELECTRONICS USOTL4 0xAC01 RS-422/485
+product BBELECTRONICS 232USB9M 0xac27 FTDI compatible adapter
+product BBELECTRONICS 485USB9F_2W 0xac25 FTDI compatible adapter
+product BBELECTRONICS 485USB9F_4W 0xac26 FTDI compatible adapter
+product BBELECTRONICS 485USBTB_2W 0xac33 FTDI compatible adapter
+product BBELECTRONICS 485USBTB_4W 0xac34 FTDI compatible adapter
+product BBELECTRONICS TTL3USB9M 0xac50 FTDI compatible adapter
+product BBELECTRONICS TTL5USB9M 0xac49 FTDI compatible adapter
+product BBELECTRONICS USO9ML2 0xac03 FTDI compatible adapter
+product BBELECTRONICS USO9ML2DR 0xac17 FTDI compatible adapter
+product BBELECTRONICS USO9ML2DR_2 0xac16 FTDI compatible adapter
+product BBELECTRONICS USOPTL4 0xac11 FTDI compatible adapter
+product BBELECTRONICS USOPTL4DR 0xac19 FTDI compatible adapter
+product BBELECTRONICS USOPTL4DR2 0xac18 FTDI compatible adapter
+product BBELECTRONICS USPTL4 0xac12 FTDI compatible adapter
+product BBELECTRONICS USTL4 0xac02 FTDI compatible adapter
+product BBELECTRONICS ZZ_PROG1_USB 0xba02 FTDI compatible adapter
+
+/* 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 B2B128 0x0128 USB3 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 F6H375USB 0x0375 F6H375-USB
+product BELKIN F5U409 0x0409 F5U409 Serial
+product BELKIN F6C550AVR 0x0551 F6C550-AVR UPS
+product BELKIN F6C1250TWRK 0x0750 F6C1250-TW-RK
+product BELKIN F6C1500TWRK 0x0751 F6C1500-TW-RK
+product BELKIN F6C900UNV 0x0900 F6C900-UNV
+product BELKIN F6C100UNV 0x0910 F6C100-UNV
+product BELKIN F6C120UNV 0x0912 F6C120-UNV UPS
+product BELKIN F6C800UNV 0x0980 F6C800-UNV
+product BELKIN F9L1004V1 0x1004 N300 Wireless Adapter
+product BELKIN F6C1100UNV 0x1100 F6C1100-UNV, F6C1200-UNV
+product BELKIN F5U120 0x1203 F5U120-PC Hub
+product BELKIN RTL8188CU 0x1102 RTL8188CU Wireless Adapter
+product BELKIN F9L1103 0x1103 F9L1103 Wireless Adapter
+product BELKIN RTL8192CU 0x2102 RTL8192CU Wireless Adapter
+product BELKIN F7D2102 0x2103 F7D2102 Wireless Adapter
+product BELKIN F5U258 0x258A F5U258 Host to Host cable
+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 F5D7050E 0x705e F5D7050E Wireless Adapter
+product BELKIN RT2870_1 0x8053 RT2870
+product BELKIN RT2870_2 0x805c RT2870
+product BELKIN F5D8053V3 0x815c F5D8053 v3
+product BELKIN RTL8192SU_1 0x815f RTL8192SU
+product BELKIN RTL8192SU_2 0x845a RTL8192SU
+product BELKIN RTL8192SU_3 0x945a RTL8192SU
+product BELKIN F5D8055 0x825a F5D8055
+product BELKIN F5D8055V2 0x825b F5D8055 v2
+product BELKIN F5D9050V3 0x905b F5D9050 ver 3 Wireless Adapter
+product BELKIN2 F5U002 0x0002 F5U002 Parallel printer
+product BELKIN F6D4050V1 0x935a F6D4050 v1
+product BELKIN F6D4050V2 0x935b F6D4050 v2
+
+/* 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
+
+/* Brainboxes Limited products */
+product BRAINBOXES US101 0x1011 US-101 USB2Serial 1xRS232
+product BRAINBOXES US159 0x1021 US-159 USB2Serial 1xRS232
+product BRAINBOXES US235 0x1017 US-235 USB2Serial 1xRS232
+product BRAINBOXES US257 0x5001 US-257 USB2Serial 2xRS232
+product BRAINBOXES US25701 0x5002 US-25701 USB2Serial 2xRS232
+product BRAINBOXES US279_12 0x2021 US-279 USB2Serial 8xRS232 (Port 1 and 2)
+product BRAINBOXES US279_34 0x2022 US-279 USB2Serial 8xRS232 (Port 3 and 4)
+product BRAINBOXES US279_56 0x2023 US-279 USB2Serial 8xRS232 (Port 5 and 6)
+product BRAINBOXES US279_78 0x2024 US-279 USB2Serial 8xRS232 (Port 7 and 8)
+product BRAINBOXES US313 0x6001 US-313 USB2Serial 2xRS422/485
+product BRAINBOXES US320 0x1019 US-320 USB2Serial 1xRS422/485
+product BRAINBOXES US324 0x1013 US-324 USB2Serial 1xRS422/485
+product BRAINBOXES US346_12 0x3011 US-346 USB2Serial 4xRS422/485 (Port 1 and 2)
+product BRAINBOXES US346_34 0x3012 US-346 USB2Serial 4xRS422/485 (Port 3 and 4)
+product BRAINBOXES US701_12 0x2011 US-701 USB2Serial 4xRS232 (Port 1 and 2)
+product BRAINBOXES US701_34 0x2012 US-701 USB2Serial 4xRS232 (Port 3 and 4)
+product BRAINBOXES US842_12 0x8001 US-842 USB2Serial 8xRS-422/485 (Port 1 and 2)
+product BRAINBOXES US842_34 0x8002 US-842 USB2Serial 8xRS-422/485 (Port 3 and 4)
+product BRAINBOXES US842_56 0x8003 US-842 USB2Serial 8xRS-422/485 (Port 5 and 6)
+product BRAINBOXES US842_78 0x8004 US-842 USB2Serial 8xRS-422/485 (Port 7 and 8)
+
+/* Broadcom products */
+product BROADCOM BCM2033 0x2033 BCM2033 Bluetooth USB dongle
+
+/* Brother Industries products */
+product BROTHER HL1050 0x0002 HL-1050 laser printer
+product BROTHER MFC8600_9650 0x0100 MFC8600/9650 multifunction device
+
+/* Behavior Technology Computer products */
+product BTC BTC6100 0x5550 6100C Keyboard
+product BTC BTC7932 0x6782 Keyboard with mouse port
+
+/* CACE Technologies products */
+product CACE AIRPCAPNX 0x0300 AirPcap NX
+
+/* 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
+product CENTURY EX35SW4_SB4 0x011f 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 CHICONY KU0325 0x0116 KU-0325 keyboard
+product CHICONY CNF7129 0xb071 Notebook Web Camera
+product CHICONY HDUVCCAM 0xb40a HD UVC WebCam
+product CHICONY RTL8188CUS_1 0xaff7 RTL8188CUS
+product CHICONY RTL8188CUS_2 0xaff8 RTL8188CUS
+product CHICONY RTL8188CUS_3 0xaff9 RTL8188CUS
+product CHICONY RTL8188CUS_4 0xaffa RTL8188CUS
+product CHICONY RTL8188CUS_5 0xaffa RTL8188CUS
+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 WUSB54AG 0x000c WUSB54AG Wireless Adapter
+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
+product CISCOLINKSYS AE1000 0x002f AE1000
+product CISCOLINKSYS WUSB6300 0x003f WUSB6300
+product CISCOLINKSYS USB3GIGV1 0x0041 USB3GIGV1 USB Ethernet Adapter
+product CISCOLINKSYS2 RT3070 0x4001 RT3070
+product CISCOLINKSYS3 RT3070 0x0101 RT3070
+
+/* Clipsal products */
+product CLIPSAL 560884 0x0101 560884 C-Bus Audio Matrix Switch
+product CLIPSAL 5500PACA 0x0201 5500PACA C-Bus Pascal Automation Controller
+product CLIPSAL 5800PC 0x0301 5800PC C-Bus Wireless Interface
+product CLIPSAL 5500PCU 0x0303 5500PCU C-Bus Interface
+product CLIPSAL 5000CT2 0x0304 5000CT2 C-Bus Touch Screen
+product CLIPSAL C5000CT2 0x0305 C5000CT2 C-Bus Touch Screen
+product CLIPSAL L51xx 0x0401 L51xx C-Bus Dimmer
+
+/* C-Media products */
+product CMEDIA CM6206 0x0102 CM106 compatible sound device
+
+/* 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 RTL8192SU_1 0x3300 RTL8192SU
+product CONCEPTRONIC2 RTL8192SU_2 0x3301 RTL8192SU
+product CONCEPTRONIC2 RTL8192SU_3 0x3302 RTL8192SU
+product CONCEPTRONIC2 C54RU 0x3c02 C54RU WLAN
+product CONCEPTRONIC2 C54RU2 0x3c22 C54RU
+product CONCEPTRONIC2 RT3070_1 0x3c08 RT3070
+product CONCEPTRONIC2 RT3070_2 0x3c11 RT3070
+product CONCEPTRONIC2 VIGORN61 0x3c25 VIGORN61
+product CONCEPTRONIC2 RT2870_1 0x3c06 RT2870
+product CONCEPTRONIC2 RT2870_2 0x3c07 RT2870
+product CONCEPTRONIC2 RT2870_7 0x3c09 RT2870
+product CONCEPTRONIC2 RT2870_8 0x3c12 RT2870
+product CONCEPTRONIC2 RT2870_3 0x3c23 RT2870
+product CONCEPTRONIC2 RT2870_4 0x3c25 RT2870
+product CONCEPTRONIC2 RT2870_5 0x3c27 RT2870
+product CONCEPTRONIC2 RT2870_6 0x3c28 RT2870
+
+/* Connectix products */
+product CONNECTIX QUICKCAM 0x0001 QuickCam
+
+/* Conect products */
+product CONTEC COM1USBH 0x8311 FTDI compatible adapter
+
+/* 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 CGUSBRS232R 0x002a CG-USBRS232R
+product COREGA CGWLUSB2GL 0x002d CG-WLUSB2GL
+product COREGA CGWLUSB2GPX 0x002e CG-WLUSB2GPX
+product COREGA RT2870_1 0x002f RT2870
+product COREGA RT2870_2 0x003c RT2870
+product COREGA RT2870_3 0x003f RT2870
+product COREGA RT3070 0x0041 RT3070
+product COREGA CGWLUSB300GNM 0x0042 CG-WLUSB300GNM
+product COREGA RTL8192SU 0x0047 RTL8192SU
+product COREGA RTL8192CU 0x0056 RTL8192CU
+
+product COREGA WLUSB_11_STICK 0x7613 WLAN USB Stick 11
+product COREGA FETHER_USB_TXC 0x9601 FEther USB-TXC
+
+/* Corsair products */
+product CORSAIR K60 0x0a60 Corsair Vengeance K60 keyboard
+product CORSAIR K68 0x1b3f Corsair Gaming K68 keyboard
+product CORSAIR K70 0x1b09 Corsair Vengeance K70 keyboard
+product CORSAIR K70_RGB 0x1b13 Corsair K70 RGB Keyboard
+product CORSAIR STRAFE 0x1b15 Corsair STRAFE Gaming keyboard
+product CORSAIR STRAFE2 0x1b44 Corsair STRAFE Gaming keyboard
+
+/* 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 CREATIVE STAGE_SE_MINI 0x3295 Stage SE mini
+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
+
+/* Chipsbank Microelectronics Co., Ltd */
+product CHIPSBANK USBMEMSTICK 0x6025 CBM2080 Flash drive controller
+product CHIPSBANK USBMEMSTICK1 0x6026 CBM1180 Flash drive controller
+
+/* 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
+product CURITEL UM150 0x3711 EVDO modem
+product CURITEL UM175 0x3714 EVDO modem
+
+/* CyberPower products */
+product CYBERPOWER BC900D 0x0005 900AVR/BC900D, CP1200AVR/BC1200D
+product CYBERPOWER 1500CAVRLCD 0x0501 1500CAVRLCD
+product CYBERPOWER OR2200LCDRM2U 0x0601 OR2200LCDRM2U
+
+/* CyberTAN Technology products */
+product CYBERTAN TG54USB 0x1666 TG54USB
+product CYBERTAN RT2870 0x1828 RT2870
+
+/* 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 IKARILASER 0x121f Ikari Laser SteelSeries ApS
+
+product CYPRESS USBRS232 0x5500 USB-RS232 Interface
+product CYPRESS CYUSB330x 0x6500 HX3 USB 3.0 Hub
+product CYPRESS CYUSB330x_2 0x6502 HX3 USB 3.0 Hub (USB 2.0)
+product CYPRESS SLIM_HUB 0x6560 Slim Hub
+product CYPRESS XX6830XX 0x6830 PATA Storage Device
+product CYPRESS SILVERSHIELD 0xfd13 Gembird Silver Shield PM
+
+/* Daisy Technology products */
+product DAISY DMC 0x6901 USB MultiMedia Reader
+
+/* Dallas Semiconductor products */
+product DALLAS J6502 0x4201 J-6502 speakers
+
+/* DataApex products */
+product DATAAPEX MULTICOM 0xead6 MultiCom
+
+/* 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 U5700 0x8114 Dell 5700 3G
+product DELL U5500 0x8115 Dell 5500 3G
+product DELL U5505 0x8116 Dell 5505 3G
+product DELL U5700_2 0x8117 Dell 5700 3G
+product DELL U5510 0x8118 Dell 5510 3G
+product DELL U5700_3 0x8128 Dell 5700 3G
+product DELL U5700_4 0x8129 Dell 5700 3G
+product DELL U5720 0x8133 Dell 5720 3G
+product DELL U5720_2 0x8134 Dell 5720 3G
+product DELL U740 0x8135 Dell U740 CDMA
+product DELL U5520 0x8136 Dell 5520 3G
+product DELL U5520_2 0x8137 Dell 5520 3G
+product DELL U5520_3 0x8138 Dell 5520 3G
+product DELL U5730 0x8180 Dell 5730 3G
+product DELL U5730_2 0x8181 Dell 5730 3G
+product DELL U5730_3 0x8182 Dell 5730 3G
+product DELL DW5809 0x81b1 Dell DW5809e Snapdragon X7 LTE/GPS
+product DELL DW5809_2 0x81b3 Dell DW5809e Snapdragon X7 LTE/GPS
+product DELL DW5811 0x81b5 Dell DW5811e Snapdragon X7 LTE/GPS
+product DELL DW5811_2 0x81b6 Dell DW5811e Snapdragon X7 LTE/GPS
+product DELL DW5816 0x81cb Dell DW5816e Snapdragon X7 LTE/GPS
+product DELL DW5816_2 0x81cc Dell DW5816e Snapdragon X7 LTE/GPS
+product DELL DW5818 0x81d0 Dell DW5818 Snapdragon X7 LTE
+product DELL DW5818_2 0x81d2 Dell DW5819 Snapdragon X7 LTE
+product DELL DW700 0x9500 Dell DW700 GPS
+product DELL2 VARIOUS_UPS 0xffff Various UPS Models
+
+/* 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
+
+/* Digianswer A/S products */
+product DIGIANSWER ZIGBEE802154 0x000a ZigBee/802.15.4 MAC
+
+/* D-Link products */
+/*product DLINK DSBS25 0x0100 DSB-S25 serial*/
+product DLINK DUBE100 0x1a00 10/100 Ethernet
+product DLINK DUBE100C1 0x1a02 DUB-E100 rev C1
+product DLINK DSB650TX4 0x200c 10/100 Ethernet
+product DLINK DWL120E 0x3200 DWL-120 rev E
+product DLINK RTL8192CU_1 0x3307 RTL8192CU
+product DLINK RTL8188CU 0x3308 RTL8188CU
+product DLINK RTL8192CU_2 0x3309 RTL8192CU
+product DLINK RTL8192CU_3 0x330a RTL8192CU
+product DLINK DWA131B 0x330d DWA-131 rev B
+product DLINK DWA125D1 0x330f DWA-125 rev D1
+product DLINK DWA123D1 0x3310 DWA-123 rev D1
+product DLINK DWA171A1 0x3314 DWA-171 rev A1
+product DLINK DWA182C1 0x3315 DWA-182 rev C1
+product DLINK DWA180A1 0x3316 DWA-180 rev A1
+product DLINK DWA172A1 0x3318 DWA-172 rev A1
+product DLINK DWA131E1 0x3319 DWA-131 rev E1
+product DLINK DWA182D1 0x331c DWA-182 rev D1
+product DLINK DWA181A1 0x331e DWA-181 rev A1
+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 RT2870 0x3c09 RT2870
+product DLINK RT3072 0x3c0a RT3072
+product DLINK DWA140B3 0x3c15 DWA-140 rev B3
+product DLINK DWA125A3 0x3c19 DWA-125 rev A3
+product DLINK DWA160B2 0x3c1a DWA-160 rev B2
+product DLINK DWA127 0x3c1b DWA-127 Wireless Adapter
+product DLINK DWA162 0x3c1f DWA-162 Wireless Adapter
+product DLINK DWA140D1 0x3c20 DWA-140 rev D1
+product DLINK DWA130F1 0x3c25 DWA-130 rev F1
+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 DUB1312 0x4a00 10/100/1000 Ethernet
+product DLINK DWM157 0x7d02 DWM-157
+product DLINK DWM157_2 0x7d0e DWM-157
+product DLINK DWR510 0x7e12 DWR-510
+product DLINK DWM222 0x7e35 DWM-222
+product DLINK DWM222_2 0x7e3d DWM-222
+product DLINK DWM157_CD_2 0xa407 DWM-157 CD-ROM Mode
+product DLINK DWM157_CD 0xa707 DWM-157 CD-ROM Mode
+product DLINK DWR510_CD 0xa805 DWR-510 CD-ROM Mode
+product DLINK DWM222_CD 0xab00 DWM-222 CD-ROM Mode
+product DLINK DWM222_CD_2 0xac01 DWM-222 CD-ROM Mode
+product DLINK DSB650 0xabc1 10/100 Ethernet
+product DLINK DUBH7 0xf103 DUB-H7 USB 2.0 7-Port Hub
+product DLINK2 RTL8192SU_1 0x3300 RTL8192SU
+product DLINK2 RTL8192SU_2 0x3302 RTL8192SU
+product DLINK2 DWA131A1 0x3303 DWA-131 A1
+product DLINK2 DWA160A2 0x3a09 DWA-160 A2
+product DLINK2 DWA120 0x3a0c DWA-120
+product DLINK2 DWA120_NF 0x3a0d DWA-120 (no firmware)
+product DLINK2 DWA130D1 0x3a0f DWA-130 D1
+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
+product DLINK2 RT2870_1 0x3c09 RT2870
+product DLINK2 RT3072 0x3c0a RT3072
+product DLINK2 RT3072_1 0x3c0b RT3072
+product DLINK2 RT3070_1 0x3c0d RT3070
+product DLINK2 RT3070_2 0x3c0e RT3070
+product DLINK2 RT3070_3 0x3c0f RT3070
+product DLINK2 DWA160A1 0x3c10 DWA-160 A1
+product DLINK2 RT2870_2 0x3c11 RT2870
+product DLINK2 DWA130 0x3c13 DWA-130
+product DLINK2 RT3070_4 0x3c15 RT3070
+product DLINK2 RT3070_5 0x3c16 RT3070
+product DLINK3 DWM652 0x3e04 DWM-652
+
+/* DisplayLink products */
+product DISPLAYLINK LCD4300U 0x01ba LCD-4300U
+product DISPLAYLINK LCD8000U 0x01bb LCD-8000U
+product DISPLAYLINK LD220 0x0100 Samsung LD220
+product DISPLAYLINK GUC2020 0x0059 IOGEAR DVI GUC2020
+product DISPLAYLINK VCUD60 0x0136 Rextron DVI
+product DISPLAYLINK CONV 0x0138 StarTech CONV-USB2DVI
+product DISPLAYLINK DLDVI 0x0141 DisplayLink DVI
+product DISPLAYLINK VGA10 0x015a CMP-USBVGA10
+product DISPLAYLINK WSDVI 0x0198 WS Tech DVI
+product DISPLAYLINK EC008 0x019b EasyCAP008 DVI
+product DISPLAYLINK HPDOCK 0x01d4 HP USB Docking
+product DISPLAYLINK NL571 0x01d7 HP USB DVI
+product DISPLAYLINK M01061 0x01e2 Lenovo DVI
+product DISPLAYLINK SWDVI 0x024c SUNWEIT DVI
+product DISPLAYLINK NBDOCK 0x0215 VideoHome NBdock1920
+product DISPLAYLINK LUM70 0x02a9 Lilliput UM-70
+product DISPLAYLINK DVI_19 0x0360 USB to DVI-19
+product DISPLAYLINK UM7X0 0x401a nanovision MiMo
+product DISPLAYLINK LT1421 0x03e0 Lenovo ThinkVision LT1421
+product DISPLAYLINK POLARIS2 0x0117 Polaris2 USB dock
+product DISPLAYLINK PLUGABLE 0x0377 Plugable docking station
+product DISPLAYLINK ITEC 0x02e9 i-tec USB 2.0 Docking Station
+
+/* DMI products */
+product DMI CFSM_RW 0xa109 CF/SM Reader/Writer
+product DMI DISK 0x2bcf Generic Disk
+
+/* DrayTek products */
+product DRAYTEK VIGOR550 0x0550 Vigor550
+
+/* Dream Link products */
+product DREAMLINK DL100B 0x0004 USB Webmail Notifier
+
+/* dresden elektronik products */
+product DRESDENELEKTRONIK SENSORTERMINALBOARD 0x0001 SensorTerminalBoard
+product DRESDENELEKTRONIK WIRELESSHANDHELDTERMINAL 0x0004 Wireless Handheld Terminal
+product DRESDENELEKTRONIK DE_RFNODE 0x001c deRFnode
+product DRESDENELEKTRONIK LEVELSHIFTERSTICKLOWCOST 0x0022 Levelshifter Stick Low Cost
+
+/* DYMO */
+product DYMO LABELMANAGERPNP 0x1001 DYMO LabelManager PnP
+
+/* Dynastream Innovations */
+product DYNASTREAM ANTDEVBOARD 0x1003 ANT dev board
+product DYNASTREAM ANT2USB 0x1004 ANT2USB
+product DYNASTREAM ANTDEVBOARD2 0x1006 ANT dev board
+
+/* Edimax products */
+product EDIMAX EW7318USG 0x7318 USB Wireless dongle
+product EDIMAX RTL8192SU_1 0x7611 RTL8192SU
+product EDIMAX RTL8192SU_2 0x7612 RTL8192SU
+product EDIMAX EW7622UMN 0x7622 EW-7622UMn
+product EDIMAX MT7601U 0x7710 MT7601U
+product EDIMAX RT2870_1 0x7711 RT2870
+product EDIMAX EW7717 0x7717 EW-7717
+product EDIMAX EW7718 0x7718 EW-7718
+product EDIMAX EW7733UND 0x7733 EW-7733UnD
+product EDIMAX EW7811UN 0x7811 EW-7811Un
+product EDIMAX RTL8192CU 0x7822 RTL8192CU
+product EDIMAX EW7811UTC_1 0xa811 EW-7811UTC
+product EDIMAX EW7811UTC_2 0xa812 EW-7811UTC
+product EDIMAX EW7822UAC 0xa822 EW-7822UAC
+product EDIMAX EW7811UN_V2 0xb811 EW-7811UN V2
+
+/* eGalax Products */
+product EGALAX TPANEL 0x0001 Touch Panel
+product EGALAX TPANEL2 0x0002 Touch Panel
+product EGALAX2 TPANEL 0x0001 Touch Panel
+
+/* EGO Products */
+product EGO M4U 0x1020 ESI M4U
+
+/* Eicon Networks */
+product EICON DIVA852 0x4905 Diva 852 ISDN TA
+
+/* 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 WDC150SU2M 0x4008 WDC-150SU2M
+product ELECOM LDUSBTX2 0x400b LD-USB/TX
+product ELECOM WDB433SU2M2 0x400f WDB-433SU2M2
+product ELECOM LDUSB20 0x4010 LD-USB20
+product ELECOM EDCQUA3C 0x4017 EDC-QUA3C
+product ELECOM UCSGT 0x5003 UC-SGT
+product ELECOM UCSGT0 0x5004 UC-SGT
+product ELECOM LDUSBTX3 0xabc1 LD-USB/TX
+
+/* Elektor products */
+product ELEKTOR FT323R 0x0005 FTDI compatible adapter
+
+/* Elsa products */
+product ELSA MODEM1 0x2265 ELSA Modem Board
+product ELSA USB2ETHERNET 0x3000 Microlink USB2Ethernet
+
+/* ELV products */
+product ELV USBI2C 0xe00f USB-I2C interface
+
+/* EMS products */
+product EMS DUAL_SHOOTER 0x0003 PSX gun controller converter
+
+/* Emtec products */
+product EMTEC DANEELEC4GB 0x1e20 USB DISK Pro PMAP
+product EMTEC RUF2PS 0x2240 Flash Drive
+
+/* Encore products */
+product ENCORE RT3070_1 0x1480 RT3070
+product ENCORE RT3070_2 0x14a1 RT3070
+product ENCORE RT3070_3 0x14a9 RT3070
+
+/* 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 TMU220B 0x0202 TM-U220B
+product EPSON CRESSI_EDY 0x0521 Cressi Edy diving computer
+product EPSON N2ITION3 0x0522 Zeagle N2iTion3 diving computer
+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
+
+/* Evolution products */
+product EVOLUTION ER1 0x0300 FTDI compatible adapter
+product EVOLUTION HYBRID 0x0302 FTDI compatible adapter
+product EVOLUTION RCM4 0x0303 FTDI compatible adapter
+
+/* Extended Systems products */
+product EXTENDED XTNDACCESS 0x0100 XTNDAccess IrDA
+
+/* Falcom products */
+product FALCOM TWIST 0x0001 USB GSM/GPRS Modem
+product FALCOM SAMBA 0x0005 FTDI compatible adapter
+
+/* FEIYA products */
+product FEIYA 5IN1 0x1132 5-in-1 Card Reader
+product FEIYA ELANGO 0x6200 MicroSDHC Card Reader
+product FEIYA AC110 0x6300 AC-110 Card Reader
+
+/* FeiXun Communication products */
+product FEIXUN RTL8188CU 0x0090 RTL8188CU
+product FEIXUN RTL8192CU 0x0091 RTL8192CU
+
+/* Festo */
+product FESTO CPX_USB 0x0102 CPX-USB
+product FESTO CMSP 0x0501 CMSP
+
+/* Fiberline */
+product FIBERLINE WL430U 0x6003 WL-430U
+
+/* FIC / OpenMoko */
+product FIC NEO1973_DEBUG 0x5118 FTDI compatible adapter
+
+/* Fossil, Inc products */
+product FOSSIL WRISTPDA 0x0002 Wrist PDA
+
+/* Foxconn products */
+product FOXCONN TCOM_TC_300 0xe000 T-Com TC 300
+product FOXCONN PIRELLI_DP_L10 0xe003 Pirelli DP-L10
+
+/* Freecom products */
+product FREECOM DVD 0xfc01 DVD drive
+product FREECOM HDD 0xfc05 Classic SL Hard Drive
+
+/* Fujitsu Siemens Computers products */
+product FSC E5400 0x1009 PrismGT USB 2.0 WLAN
+
+/* Future Technology Devices products */
+product FTDI SCX8_USB_PHOENIX 0x5259 SCx8 USB Phoenix interface
+product FTDI SERIAL_8U100AX 0x8372 8U100AX Serial
+product FTDI SERIAL_8U232AM 0x6001 8U232AM Serial
+product FTDI SERIAL_8U232AM4 0x6004 8U232AM Serial
+product FTDI SERIAL_232RL 0x6006 FT232RL Serial
+product FTDI SERIAL_2232C 0x6010 FT2232C Dual port Serial
+product FTDI 232H 0x6014 FTDI compatible adapter
+product FTDI 232EX 0x6015 FTDI compatible adapter
+product FTDI SERIAL_2232D 0x9e90 FT2232D Dual port Serial
+product FTDI SERIAL_4232H 0x6011 FT4232H Quad port Serial
+product FTDI XDS100V2 0xa6d0 TI XDS100V1/V2 and early Beaglebones
+product FTDI XDS100V3 0xa6d1 TI XDS100V3
+product FTDI KTLINK 0xbbe2 KT-LINK Embedded Hackers Multitool
+product FTDI TURTELIZER2 0xbdc8 egnite Turtelizer 2 JTAG/RS232 Adapter
+/* 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 GAMMASCOUT 0xd678 Gamma-Scout
+product FTDI KBS 0xe6c8 Pyramid KBS USB LCD
+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 USB_UIRT 0xf850 USB-UIRT
+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
+/* Commerzielle und Technische Informationssysteme GmbH products */
+product FTDI CTI_USB_NANO_485 0xf60b CTI USB-Nano 485
+product FTDI CTI_USB_MINI_485 0xf608 CTI USB-Mini 485
+/* Other products */
+product FTDI 232RL 0xfbfa FTDI compatible adapter
+product FTDI 4N_GALAXY_DE_1 0xf3c0 FTDI compatible adapter
+product FTDI 4N_GALAXY_DE_2 0xf3c1 FTDI compatible adapter
+product FTDI 4N_GALAXY_DE_3 0xf3c2 FTDI compatible adapter
+product FTDI 8U232AM_ALT 0x6006 FTDI compatible adapter
+product FTDI ACCESSO 0xfad0 FTDI compatible adapter
+product FTDI ACG_HFDUAL 0xdd20 FTDI compatible adapter
+product FTDI ACTIVE_ROBOTS 0xe548 FTDI compatible adapter
+product FTDI ACTZWAVE 0xf2d0 FTDI compatible adapter
+product FTDI AMC232 0xff00 FTDI compatible adapter
+product FTDI ARTEMIS 0xdf28 FTDI compatible adapter
+product FTDI ASK_RDR400 0xc991 FTDI compatible adapter
+product FTDI ATIK_ATK16 0xdf30 FTDI compatible adapter
+product FTDI ATIK_ATK16C 0xdf32 FTDI compatible adapter
+product FTDI ATIK_ATK16HR 0xdf31 FTDI compatible adapter
+product FTDI ATIK_ATK16HRC 0xdf33 FTDI compatible adapter
+product FTDI ATIK_ATK16IC 0xdf35 FTDI compatible adapter
+product FTDI BCS_SE923 0xfb99 FTDI compatible adapter
+product FTDI CANDAPTER 0x9f80 FTDI compatible adapter
+product FTDI CANUSB 0xffa8 FTDI compatible adapter
+product FTDI CCSICDU20_0 0xf9d0 FTDI compatible adapter
+product FTDI CCSICDU40_1 0xf9d1 FTDI compatible adapter
+product FTDI CCSICDU64_4 0xf9d4 FTDI compatible adapter
+product FTDI CCSLOAD_N_GO_3 0xf9d3 FTDI compatible adapter
+product FTDI CCSMACHX_2 0xf9d2 FTDI compatible adapter
+product FTDI CCSPRIME8_5 0xf9d5 FTDI compatible adapter
+product FTDI CHAMSYS_24_MASTER_WING 0xdaf8 FTDI compatible adapter
+product FTDI CHAMSYS_MAXI_WING 0xdafd FTDI compatible adapter
+product FTDI CHAMSYS_MEDIA_WING 0xdafe FTDI compatible adapter
+product FTDI CHAMSYS_MIDI_TIMECODE 0xdafb FTDI compatible adapter
+product FTDI CHAMSYS_MINI_WING 0xdafc FTDI compatible adapter
+product FTDI CHAMSYS_PC_WING 0xdaf9 FTDI compatible adapter
+product FTDI CHAMSYS_USB_DMX 0xdafa FTDI compatible adapter
+product FTDI CHAMSYS_WING 0xdaff FTDI compatible adapter
+product FTDI COM4SM 0xd578 FTDI compatible adapter
+product FTDI CONVERTER_0 0xd388 FTDI compatible adapter
+product FTDI CONVERTER_1 0xd389 FTDI compatible adapter
+product FTDI CONVERTER_2 0xd38a FTDI compatible adapter
+product FTDI CONVERTER_3 0xd38b FTDI compatible adapter
+product FTDI CONVERTER_4 0xd38c FTDI compatible adapter
+product FTDI CONVERTER_5 0xd38d FTDI compatible adapter
+product FTDI CONVERTER_6 0xd38e FTDI compatible adapter
+product FTDI CONVERTER_7 0xd38f FTDI compatible adapter
+product FTDI DMX4ALL 0xc850 FTDI compatible adapter
+product FTDI DOMINTELL_DGQG 0xef50 FTDI compatible adapter
+product FTDI DOMINTELL_DUSB 0xef51 FTDI compatible adapter
+product FTDI DOTEC 0x9868 FTDI compatible adapter
+product FTDI ECLO_COM_1WIRE 0xea90 FTDI compatible adapter
+product FTDI ECO_PRO_CDS 0xe520 FTDI compatible adapter
+product FTDI ELSTER_UNICOM 0xe700 FTDI compatible adapter
+product FTDI ELV_ALC8500 0xf06e FTDI compatible adapter
+product FTDI ELV_CLI7000 0xfb59 FTDI compatible adapter
+product FTDI ELV_CSI8 0xe0f0 FTDI compatible adapter
+product FTDI ELV_EC3000 0xe006 FTDI compatible adapter
+product FTDI ELV_EM1000DL 0xe0f1 FTDI compatible adapter
+product FTDI ELV_EM1010PC 0xe0ef FTDI compatible adapter
+product FTDI ELV_FEM 0xe00a FTDI compatible adapter
+product FTDI ELV_FHZ1000PC 0xf06f FTDI compatible adapter
+product FTDI ELV_FHZ1300PC 0xe0e8 FTDI compatible adapter
+product FTDI ELV_FM3RX 0xe0ed FTDI compatible adapter
+product FTDI ELV_FS20SIG 0xe0f4 FTDI compatible adapter
+product FTDI ELV_HS485 0xe0ea FTDI compatible adapter
+product FTDI ELV_KL100 0xe002 FTDI compatible adapter
+product FTDI ELV_MSM1 0xe001 FTDI compatible adapter
+product FTDI ELV_PCD200 0xf06c FTDI compatible adapter
+product FTDI ELV_PCK100 0xe0f2 FTDI compatible adapter
+product FTDI ELV_PPS7330 0xfb5c FTDI compatible adapter
+product FTDI ELV_RFP500 0xe0f3 FTDI compatible adapter
+product FTDI ELV_T1100 0xf06b FTDI compatible adapter
+product FTDI ELV_TFD128 0xe0ec FTDI compatible adapter
+product FTDI ELV_TFM100 0xfb5d FTDI compatible adapter
+product FTDI ELV_TWS550 0xe009 FTDI compatible adapter
+product FTDI ELV_UAD8 0xf068 FTDI compatible adapter
+product FTDI ELV_UDA7 0xf069 FTDI compatible adapter
+product FTDI ELV_UDF77 0xfb5e FTDI compatible adapter
+product FTDI ELV_UIO88 0xfb5f FTDI compatible adapter
+product FTDI ELV_ULA200 0xf06d FTDI compatible adapter
+product FTDI ELV_UM100 0xfb5a FTDI compatible adapter
+product FTDI ELV_UMS100 0xe0eb FTDI compatible adapter
+product FTDI ELV_UO100 0xfb5b FTDI compatible adapter
+product FTDI ELV_UR100 0xfb58 FTDI compatible adapter
+product FTDI ELV_USI2 0xf06a FTDI compatible adapter
+product FTDI ELV_USR 0xe000 FTDI compatible adapter
+product FTDI ELV_UTP8 0xe0f5 FTDI compatible adapter
+product FTDI ELV_WS300PC 0xe0f6 FTDI compatible adapter
+product FTDI ELV_WS444PC 0xe0f7 FTDI compatible adapter
+product FTDI ELV_WS500 0xe0e9 FTDI compatible adapter
+product FTDI ELV_WS550 0xe004 FTDI compatible adapter
+product FTDI ELV_WS777 0xe0ee FTDI compatible adapter
+product FTDI ELV_WS888 0xe008 FTDI compatible adapter
+product FTDI FUTURE_0 0xf44a FTDI compatible adapter
+product FTDI FUTURE_1 0xf44b FTDI compatible adapter
+product FTDI FUTURE_2 0xf44c FTDI compatible adapter
+product FTDI GENERIC 0x9378 FTDI compatible adapter
+product FTDI GUDEADS_E808 0xe808 FTDI compatible adapter
+product FTDI GUDEADS_E809 0xe809 FTDI compatible adapter
+product FTDI GUDEADS_E80A 0xe80a FTDI compatible adapter
+product FTDI GUDEADS_E80B 0xe80b FTDI compatible adapter
+product FTDI GUDEADS_E80C 0xe80c FTDI compatible adapter
+product FTDI GUDEADS_E80D 0xe80d FTDI compatible adapter
+product FTDI GUDEADS_E80E 0xe80e FTDI compatible adapter
+product FTDI GUDEADS_E80F 0xe80f FTDI compatible adapter
+product FTDI GUDEADS_E88D 0xe88d FTDI compatible adapter
+product FTDI GUDEADS_E88E 0xe88e FTDI compatible adapter
+product FTDI GUDEADS_E88F 0xe88f FTDI compatible adapter
+product FTDI HD_RADIO 0x937c FTDI compatible adapter
+product FTDI HO720 0xed72 FTDI compatible adapter
+product FTDI HO730 0xed73 FTDI compatible adapter
+product FTDI HO820 0xed74 FTDI compatible adapter
+product FTDI HO870 0xed71 FTDI compatible adapter
+product FTDI IBS_APP70 0xff3d FTDI compatible adapter
+product FTDI IBS_PCMCIA 0xff3a FTDI compatible adapter
+product FTDI IBS_PEDO 0xff3e FTDI compatible adapter
+product FTDI IBS_PICPRO 0xff39 FTDI compatible adapter
+product FTDI IBS_PK1 0xff3b FTDI compatible adapter
+product FTDI IBS_PROD 0xff3f FTDI compatible adapter
+product FTDI IBS_RS232MON 0xff3c FTDI compatible adapter
+product FTDI IBS_US485 0xff38 FTDI compatible adapter
+product FTDI IPLUS 0xd070 FTDI compatible adapter
+product FTDI IPLUS2 0xd071 FTDI compatible adapter
+product FTDI IRTRANS 0xfc60 FTDI compatible adapter
+product FTDI LENZ_LIUSB 0xd780 FTDI compatible adapter
+product FTDI LM3S_DEVEL_BOARD 0xbcd8 FTDI compatible adapter
+product FTDI LM3S_EVAL_BOARD 0xbcd9 FTDI compatible adapter
+product FTDI LM3S_ICDI_B_BOARD 0xbcda FTDI compatible adapter
+product FTDI MASTERDEVEL2 0xf449 FTDI compatible adapter
+product FTDI MHAM_DB9 0xeeed FTDI compatible adapter
+product FTDI MHAM_IC 0xeeec FTDI compatible adapter
+product FTDI MHAM_KW 0xeee8 FTDI compatible adapter
+product FTDI MHAM_RS232 0xeeee FTDI compatible adapter
+product FTDI MHAM_Y6 0xeeea FTDI compatible adapter
+product FTDI MHAM_Y8 0xeeeb FTDI compatible adapter
+product FTDI MHAM_Y9 0xeeef FTDI compatible adapter
+product FTDI MHAM_YS 0xeee9 FTDI compatible adapter
+product FTDI MICRO_CHAMELEON 0xcaa0 FTDI compatible adapter
+product FTDI MTXORB_5 0xfa05 FTDI compatible adapter
+product FTDI MTXORB_6 0xfa06 FTDI compatible adapter
+product FTDI NXTCAM 0xabb8 FTDI compatible adapter
+product FTDI OCEANIC 0xf460 FTDI compatible adapter
+product FTDI OOCDLINK 0xbaf8 FTDI compatible adapter
+product FTDI OPENDCC 0xbfd8 FTDI compatible adapter
+product FTDI OPENDCC_GATEWAY 0xbfdb FTDI compatible adapter
+product FTDI OPENDCC_GBM 0xbfdc FTDI compatible adapter
+product FTDI OPENDCC_SNIFFER 0xbfd9 FTDI compatible adapter
+product FTDI OPENDCC_THROTTLE 0xbfda FTDI compatible adapter
+product FTDI PCDJ_DAC2 0xfa88 FTDI compatible adapter
+product FTDI PERLE_ULTRAPORT 0xf0c0 FTDI compatible adapter
+product FTDI PHI_FISCO 0xe40b FTDI compatible adapter
+product FTDI PIEGROUP 0xf208 FTDI compatible adapter
+product FTDI PROPOX_JTAGCABLEII 0xd738 FTDI compatible adapter
+product FTDI R2000KU_TRUE_RNG 0xfb80 FTDI compatible adapter
+product FTDI R2X0 0xfc71 FTDI compatible adapter
+product FTDI RELAIS 0xfa10 FTDI compatible adapter
+product FTDI REU_TINY 0xed22 FTDI compatible adapter
+product FTDI RMP200 0xe729 FTDI compatible adapter
+product FTDI RM_CANVIEW 0xfd60 FTDI compatible adapter
+product FTDI RRCIRKITS_LOCOBUFFER 0xc7d0 FTDI compatible adapter
+product FTDI SCIENCESCOPE_HS_LOGBOOK 0xff1d FTDI compatible adapter
+product FTDI SCIENCESCOPE_LOGBOOKML 0xff18 FTDI compatible adapter
+product FTDI SCIENCESCOPE_LS_LOGBOOK 0xff1c FTDI compatible adapter
+product FTDI SCS_DEVICE_0 0xd010 FTDI compatible adapter
+product FTDI SCS_DEVICE_1 0xd011 FTDI compatible adapter
+product FTDI SCS_DEVICE_2 0xd012 FTDI compatible adapter
+product FTDI SCS_DEVICE_3 0xd013 FTDI compatible adapter
+product FTDI SCS_DEVICE_4 0xd014 FTDI compatible adapter
+product FTDI SCS_DEVICE_5 0xd015 FTDI compatible adapter
+product FTDI SCS_DEVICE_6 0xd016 FTDI compatible adapter
+product FTDI SCS_DEVICE_7 0xd017 FTDI compatible adapter
+product FTDI SDMUSBQSS 0xf448 FTDI compatible adapter
+product FTDI SIGNALYZER_SH2 0xbca2 FTDI compatible adapter
+product FTDI SIGNALYZER_SH4 0xbca4 FTDI compatible adapter
+product FTDI SIGNALYZER_SLITE 0xbca1 FTDI compatible adapter
+product FTDI SIGNALYZER_ST 0xbca0 FTDI compatible adapter
+product FTDI SITOP_UPS500S 0xe0e4 Siemens UPS500S standby power
+product FTDI SPECIAL_1 0xfc70 FTDI compatible adapter
+product FTDI SPECIAL_3 0xfc72 FTDI compatible adapter
+product FTDI SPECIAL_4 0xfc73 FTDI compatible adapter
+product FTDI SPROG_II 0xf0c8 FTDI compatible adapter
+product FTDI SR_RADIO 0x9379 FTDI compatible adapter
+product FTDI SUUNTO_SPORTS 0xf680 FTDI compatible adapter
+product FTDI TAVIR_STK500 0xfa33 FTDI compatible adapter
+product FTDI TERATRONIK_D2XX 0xec89 FTDI compatible adapter
+product FTDI TERATRONIK_VCP 0xec88 FTDI compatible adapter
+product FTDI THORLABS 0xfaf0 FTDI compatible adapter
+product FTDI TIAO 0x8a98 FTDI compatible adapter
+product FTDI TNC_X 0xebe0 FTDI compatible adapter
+product FTDI TTUSB 0xff20 FTDI compatible adapter
+product FTDI USBX_707 0xf857 FTDI compatible adapter
+product FTDI USINT_CAT 0xb810 FTDI compatible adapter
+product FTDI USINT_RS232 0xb812 FTDI compatible adapter
+product FTDI USINT_WKEY 0xb811 FTDI compatible adapter
+product FTDI VARDAAN 0xf070 FTDI compatible adapter
+product FTDI VNHCPCUSB_D 0xfe38 FTDI compatible adapter
+product FTDI WESTREX_MODEL_777 0xdc00 FTDI compatible adapter
+product FTDI WESTREX_MODEL_8900F 0xdc01 FTDI compatible adapter
+product FTDI XF_547 0xfc0a FTDI compatible adapter
+product FTDI XF_640 0xfc0e FTDI compatible adapter
+product FTDI XF_642 0xfc0f FTDI compatible adapter
+product FTDI XM_RADIO 0x937a FTDI compatible adapter
+product FTDI YEI_SERVOCENTER31 0xe050 FTDI compatible adapter
+
+/* Fuji photo products */
+product FUJIPHOTO MASS0100 0x0100 Mass Storage
+
+/* Fujitsu protducts */
+product FUJITSU AH_F401U 0x105b AH-F401U Air H device
+
+/* Fujitsu-Siemens protducts */
+product FUJITSUSIEMENS SCR 0x0009 Fujitsu-Siemens SCR USB Reader
+
+/* Garmin products */
+product GARMIN IQUE_3600 0x0004 iQue 3600
+product GARMIN FORERUNNER230 0x086d ForeRunner 230
+product GARMIN INSTINCTSOLAR 0x0d8a Instinct Solar
+product GARMIN DAKOTA20 0x23c0 Dakota 20
+product GARMIN GPSMAP62S 0x2459 GPSMAP 62s
+product GARMIN EDGETOURINGPLUS 0x26c8 Edge Touring+
+
+/* Gemalto products */
+product GEMALTO PROXPU 0x5501 Prox-PU/CU RFID Card Reader
+
+/* 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 GL606 0x0606 GL606 USB 2.0 HUB
+product GENESYS GL850G 0x0608 GL850G USB 2.0 HUB
+product GENESYS GL3520_2 0x0610 GL3520 4-Port USB 2.0 DataPath
+product GENESYS GL3520_SS 0x0616 GL3520 4-Port USB 3.0 DataPath
+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 GL3220 0x0732 GL3220 USB 3.1 AiO Card Reader
+product GENESYS GL3233 0x0743 GL3233 USB 3.0 AiO Card Reader
+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
+product GIGASET RT3070_1 0x0740 RT3070
+product GIGASET RT3070_2 0x0744 RT3070
+product GIGABYTE RT2870_1 0x800b RT2870
+product GIGABYTE GNWB31N 0x800c GN-WB31N
+product GIGABYTE GNWB32L 0x800d GN-WB32L
+
+/* 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 MODEM_1 0x1329 USB Modem
+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
+
+/* GN Otometrics */
+product GNOTOMETRICS USB 0x0010 FTDI compatible adapter
+
+/* GoHubs products */
+product GOHUBS GOCOM232 0x1001 GoCOM232 Serial
+
+/* Good Way Technology products */
+product GOODWAY GWUSB2E 0x6200 GWUSB2E
+product GOODWAY RT2573 0xc019 RT2573
+
+/* Google products */
+product GOOGLE NEXUSONE 0x4e11 Nexus One
+
+/* 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
+product GUILLEMOT HWNU300 0xe030 HWNU-300
+product GUILLEMOT HWNUM300 0xe031 HWNUm-300
+product GUILLEMOT HWGUN54 0xe032 HWGUn-54
+product GUILLEMOT HWNUP150 0xe033 HWNUP-150
+
+/* 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
+product HAUPPAUGE2 NOVAT500 0x9580 NovaT 500Stick
+
+/* Hawking Technologies products */
+product HAWKING RT2870_1 0x0001 RT2870
+product HAWKING RT2870_2 0x0003 RT2870
+product HAWKING HWUN2 0x0009 HWUN2
+product HAWKING RT3070 0x000b RT3070
+product HAWKING RTL8192CU 0x0019 RTL8192CU
+product HAWKING UF100 0x400c 10/100 USB Ethernet
+product HAWKING RTL8192SU_1 0x0015 RTL8192SU
+product HAWKING RTL8192SU_2 0x0016 RTL8192SU
+product HAWKING HD65U 0x0023 HD65U
+
+/* HID Global GmbH products */
+product HIDGLOBAL CM2020 0x0596 Omnikey Cardman 2020
+product HIDGLOBAL CM6020 0x1784 Omnikey Cardman 6020
+
+/* Hitachi, Ltd. products */
+product HITACHI DVDCAM_DZ_MV100A 0x0004 DVD-CAM DZ-MV100A Camcorder
+product HITACHI DVDCAM_USB 0x001e DVDCAM USB HS Interface
+
+/* Holtek products */
+product HOLTEK F85 0xa030 Holtek USB gaming keyboard
+
+/* Honeywell */
+product HONEYWELL HGI80 0x0102 Honeywell HGI80 Wireless USB Gateway
+
+/* 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 UN2420_QDL 0x241d UN2420 QDL Firmware Loader
+product HP UN2420 0x251d UN2420 WWAN/GPS Module
+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 HP3 RTL8188CU 0x1629 RTL8188CU
+product HP P2000U 0x1801 Inkjet P-2000U
+product HP HS2300 0x1e1d HS2300 HSDPA (aka MC8775)
+product HP T500 0x1f01 T500
+product HP T750 0x1f02 T750
+product HP 640C 0x2004 DeskJet 640c
+product HP 4670V 0x3005 ScanJet 4670v
+product HP P1100 0x3102 Photosmart P1100
+product HP LD220 0x3524 LD220 POS Display
+product HP OJ4215 0x3d11 OfficeJet 4215
+product HP HN210E 0x811c Ethernet HN210E
+product HP2 C500 0x6002 PhotoSmart C500
+product HP EV2200 0x1b1d ev2200 HSDPA (aka MC5720)
+product HP HS2300 0x1e1d hs2300 HSDPA (aka MC8775)
+product HP LT4132 0xa31d lt4132 LTEHSPA+ (aka ME906s)
+
+/* HTC products */
+product HTC WINMOBILE 0x00ce HTC USB Sync
+product HTC PPC6700MODEM 0x00cf PPC6700 Modem
+product HTC SMARTPHONE 0x0a51 SmartPhone USB Sync
+product HTC WIZARD 0x0bce HTC Wizard USB Sync
+product HTC LEGENDSYNC 0x0c97 HTC Legend USB Sync
+product HTC LEGEND 0x0ff9 HTC Legend
+product HTC LEGENDINTERNET 0x0ffe HTC Legend Internet Sharing
+
+/* HUAWEI products */
+product HUAWEI MOBILE 0x1001 Huawei Mobile
+product HUAWEI E220 0x1003 HSDPA modem
+product HUAWEI E220BIS 0x1004 HSDPA modem
+product HUAWEI E1401 0x1401 3G modem
+product HUAWEI E1402 0x1402 3G modem
+product HUAWEI E1403 0x1403 3G modem
+product HUAWEI E1404 0x1404 3G modem
+product HUAWEI E1405 0x1405 3G modem
+product HUAWEI E1406 0x1406 3G modem
+product HUAWEI E1407 0x1407 3G modem
+product HUAWEI E1408 0x1408 3G modem
+product HUAWEI E1409 0x1409 3G modem
+product HUAWEI E140A 0x140a 3G modem
+product HUAWEI E140B 0x140b 3G modem
+product HUAWEI E180V 0x140c E180V
+product HUAWEI E140D 0x140d 3G modem
+product HUAWEI E140E 0x140e 3G modem
+product HUAWEI E140F 0x140f 3G modem
+product HUAWEI E1410 0x1410 3G modem
+product HUAWEI E1411 0x1411 3G modem
+product HUAWEI E1412 0x1412 3G modem
+product HUAWEI E1413 0x1413 3G modem
+product HUAWEI E1414 0x1414 3G modem
+product HUAWEI E1415 0x1415 3G modem
+product HUAWEI E1416 0x1416 3G modem
+product HUAWEI E1417 0x1417 3G modem
+product HUAWEI E1418 0x1418 3G modem
+product HUAWEI E1419 0x1419 3G modem
+product HUAWEI E141A 0x141a 3G modem
+product HUAWEI E141B 0x141b 3G modem
+product HUAWEI E141C 0x141c 3G modem
+product HUAWEI E141D 0x141d 3G modem
+product HUAWEI E141E 0x141e 3G modem
+product HUAWEI E141F 0x141f 3G modem
+product HUAWEI E1420 0x1420 3G modem
+product HUAWEI E1421 0x1421 3G modem
+product HUAWEI E1422 0x1422 3G modem
+product HUAWEI E1423 0x1423 3G modem
+product HUAWEI E1424 0x1424 3G modem
+product HUAWEI E1425 0x1425 3G modem
+product HUAWEI E1426 0x1426 3G modem
+product HUAWEI E1427 0x1427 3G modem
+product HUAWEI E1428 0x1428 3G modem
+product HUAWEI E1429 0x1429 3G modem
+product HUAWEI E142A 0x142a 3G modem
+product HUAWEI E142B 0x142b 3G modem
+product HUAWEI E142C 0x142c 3G modem
+product HUAWEI E142D 0x142d 3G modem
+product HUAWEI E142E 0x142e 3G modem
+product HUAWEI E142F 0x142f 3G modem
+product HUAWEI E1430 0x1430 3G modem
+product HUAWEI E1431 0x1431 3G modem
+product HUAWEI E1432 0x1432 3G modem
+product HUAWEI E1433 0x1433 3G modem
+product HUAWEI E1434 0x1434 3G modem
+product HUAWEI E1435 0x1435 3G modem
+product HUAWEI E1436 0x1436 3G modem
+product HUAWEI E1437 0x1437 3G modem
+product HUAWEI E1438 0x1438 3G modem
+product HUAWEI E1439 0x1439 3G modem
+product HUAWEI E143A 0x143a 3G modem
+product HUAWEI E143B 0x143b 3G modem
+product HUAWEI E143C 0x143c 3G modem
+product HUAWEI E143D 0x143d 3G modem
+product HUAWEI E143E 0x143e 3G modem
+product HUAWEI E143F 0x143f 3G modem
+product HUAWEI E1752 0x1446 3G modem
+product HUAWEI K4505 0x1464 3G modem
+product HUAWEI K3765 0x1465 3G modem
+product HUAWEI E1820 0x14ac E1820 HSPA+ USB Slider
+product HUAWEI K3771_INIT 0x14c4 K3771 Initial
+product HUAWEI K3770 0x14c9 3G modem
+product HUAWEI K3771 0x14ca K3771
+product HUAWEI K3772 0x14cf K3772
+product HUAWEI K3770_INIT 0x14d1 K3770 Initial
+product HUAWEI E392 0x1505 LTE modem
+product HUAWEI K3765_INIT 0x1520 K3765 Initial
+product HUAWEI K4505_INIT 0x1521 K4505 Initial
+product HUAWEI K3772_INIT 0x1526 K3772 Initial
+product HUAWEI E3272_INIT 0x155b LTE modem initial
+product HUAWEI ME909U 0x1573 LTE modem
+product HUAWEI R215_INIT 0x1582 LTE modem initial
+product HUAWEI R215 0x1588 LTE modem
+product HUAWEI ME909S 0x15c1 LTE modem
+product HUAWEI ETS2055 0x1803 CDMA modem
+product HUAWEI E173 0x1c05 3G modem
+product HUAWEI E173_INIT 0x1c0b 3G modem initial
+product HUAWEI E3272 0x1c1e LTE modem
+product HUAWEI E3372_INIT 0x1f01 LTE modem initial
+product HUAWEI E3372_NCM 0x1506 LTE modem in NCM mode
+product HUAWEI E3372v153_INIT 0x14fe LTE modem initial
+product HUAWEI E3372v153_NCM 0x155f LTE modem in NCM mode
+product HUAWEI E5573Cs322_NCM 0x155e LTE AP modem NCM mode
+product HUAWEI E5573Cs322_ECM 0x14db LTE AP modem ECM mode
+product HUAWEI E5573Cs322_ACM 0x1442 LTE AP modem ACM mode
+
+/* HUAWEI 3com products */
+product HUAWEI3COM WUB320G 0x0009 Aolynk WUB320g
+
+/* IBM Corporation */
+product IBM USBCDROMDRIVE 0x4427 USB CD-ROM Drive
+product IBM USB4543 0x4543 TI IBM USB 4543 Modem
+product IBM USB454B 0x454b TI IBM USB 454B Modem
+product IBM USB454C 0x454c TI IBM USB 454C Modem
+
+/* Icom products */
+product ICOM SP1 0x0004 FTDI compatible adapter
+product ICOM OPC_U_UC 0x0018 FTDI compatible adapter
+product ICOM RP2C1 0x0009 FTDI compatible adapter
+product ICOM RP2C2 0x000a FTDI compatible adapter
+product ICOM RP2D 0x000b FTDI compatible adapter
+product ICOM RP2KVR 0x0013 FTDI compatible adapter
+product ICOM RP2KVT 0x0012 FTDI compatible adapter
+product ICOM RP2VR 0x000d FTDI compatible adapter
+product ICOM RP2VT 0x000c FTDI compatible adapter
+product ICOM RP4KVR 0x0011 FTDI compatible adapter
+product ICOM RP4KVT 0x0010 FTDI compatible adapter
+
+/* ID-tech products */
+product IDTECH IDT1221U 0x0300 FTDI compatible adapter
+
+/* Imagination Technologies products */
+product IMAGINATION DBX1 0x2107 DBX1 DSP core
+
+/* Initio Corporation products */
+product INITIO INIC_1610P 0x1e40 USB to SATA Bridge
+
+/* 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
+product INTEL2 IRMH 0x0020 Integrated Rate Matching Hub
+product INTEL2 IRMH2 0x0024 Integrated Rate Matching Hub
+product INTEL2 IRMH3 0x8000 Integrated Rate Matching Hub
+product INTEL2 IRMH4 0x8008 Integrated Rate Matching Hub
+product INTEL2 SNP 0x0a2b Stone Peak (7265) Bluetooth Module
+product INTEL2 SFP 0x0aa7 Sandy Peak (3168) Bluetooth Module
+product INTEL2 JFP 0x0aaa Jefferson Peak (9460/9560) Bluetooth Module
+product INTEL2 THP 0x0025 Thunder Peak (9160/9260) Bluetooth Module
+product INTEL2 HSP 0x0026 Harrison Peak (22560) Bluetooth Module
+
+/* Interbiometric products */
+product INTERBIOMETRICS IOBOARD 0x1002 FTDI compatible adapter
+product INTERBIOMETRICS MINI_IOBOARD 0x1006 FTDI compatible adapter
+
+/* 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 WNGDNUS2 0x093f WN-GDN/US2
+product IODATA RT3072_1 0x0944 RT3072
+product IODATA RT3072_2 0x0945 RT3072
+product IODATA RT3072_3 0x0947 RT3072
+product IODATA RT3072_4 0x0948 RT3072
+product IODATA WNAC867U 0x0952 WN-AC867U
+product IODATA USBRSAQ 0x0a03 Serial USB-RSAQ1
+product IODATA USBRSAQ5 0x0a0e Serial USB-RSAQ5
+product IODATA2 USB2SC 0x0a09 USB2.0-SCSI Bridge USB2-SC
+
+/* Iomega products */
+product IOMEGA ZIP100 0x0001 Zip 100
+product IOMEGA ZIP250 0x0030 Zip 250
+
+/* Ionic products */
+product IONICS PLUGCOMPUTER 0x0102 FTDI compatible adapter
+
+/* Integrated System Solution Corp. products */
+product ISSC ISSCBTA 0x1001 Bluetooth USB Adapter
+
+/* iTegno products */
+product ITEGNO WM1080A 0x1080 WM1080A GSM/GPRS modem
+product ITEGNO WM2080A 0x2080 WM2080A CDMA modem
+
+/* Microchip Technology, Inc. products */
+product MICROCHIP PICOLCD20X2 0x0002 Mini-Box picoLCD 20x2
+product MICROCHIP PICOLCD4X20 0xc001 Mini-Box picoLCD 4x20
+
+/* Jablotron products */
+product JABLOTRON PC60B 0x0001 PC-60B
+
+/* Jaton products */
+product JATON EDA 0x5704 Ethernet
+
+/* Jeti products */
+product JETI SPC1201 0x04b2 FTDI compatible adapter
+
+/* JMicron products */
+product JMICRON JMS566 0x3569 USB to SATA 3.0Gb/s bridge
+product JMICRON JMS567 0x0567 USB to SATA 6.0Gb/s bridge
+product JMICRON JM20336 0x2336 USB to SATA Bridge
+product JMICRON JM20337 0x2338 USB to ATA/ATAPI Bridge
+
+/* 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
+
+/* Kamstrrup products */
+product KAMSTRUP OPTICALEYE 0x0001 Optical Eye/3-wire
+product KAMSTRUP MBUS_250D 0x0005 M-Bus Master MultiPort 250D
+
+/* 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
+product KENSINGTON SLIMBLADE 0x2041 Slimblade Trackball
+
+/* Synaptics products */
+product SYNAPTICS FPR9A 0x009a Fingerprint Reader
+
+/* 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
+product KINGSTON HYPERX3_0 0x162b DT HyperX 3.0
+product KINGSTON DATATRAVELER3_0 0x1666 DataTraveler 3.0
+
+/* Kawasaki products */
+product KLSI DUH3E10BT 0x0008 USB Ethernet
+product KLSI DUH3E10BTN 0x0009 USB Ethernet
+
+/* Kobil products */
+product KOBIL CONV_B1 0x2020 FTDI compatible adapter
+product KOBIL CONV_KAAN 0x2021 FTDI compatible adapter
+
+/* 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
+
+/* Kontron AG products */
+product KONTRON DM9601 0x8101 USB Ethernet
+product KONTRON JP1082 0x9700 USB Ethernet
+
+/* 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
+product KYOCERA2 KPC680 0x180a Qualcomm Kyocera CDMA Technologies MSM
+
+/* LaCie products */
+product LACIE HD 0xa601 Hard Disk
+product LACIE CDRW 0xa602 CD R/W
+
+/* Lake Shore Cryotronics products */
+product LAKESHORE 121 0x0100 121 Current Source
+product LAKESHORE 218A 0x0200 218A Temperature Monitor
+product LAKESHORE 219 0x0201 219 Temperature Monitor
+product LAKESHORE 233 0x0202 233 Temperature Transmitter
+product LAKESHORE 235 0x0203 235 Temperature Transmitter
+product LAKESHORE 335 0x0300 335 Temperature Controller
+product LAKESHORE 336 0x0301 336 Temperature Controller
+product LAKESHORE 350 0x0302 350 Temperature Controller
+product LAKESHORE 371 0x0303 371 AC Bridge
+product LAKESHORE 411 0x0400 411 Handheld Gaussmeter
+product LAKESHORE 425 0x0401 425 Gaussmeter
+product LAKESHORE 455A 0x0402 455A DSP Gaussmeter
+product LAKESHORE 475A 0x0403 475A DSP Gaussmeter
+product LAKESHORE 465 0x0404 465 Gaussmeter
+product LAKESHORE 625A 0x0600 625A Magnet PSU
+product LAKESHORE 642A 0x0601 642A Magnet PSU
+product LAKESHORE 648 0x0602 648 Magnet PSU
+product LAKESHORE 737 0x0700 737 VSM Controller
+product LAKESHORE 776 0x0701 776 Matrix Switch
+
+/* Larsen and Brusgaard products */
+product LARSENBRUSGAARD ALTITRACK 0x0001 FTDI compatible adapter
+
+/* Leadtek products */
+product LEADTEK 9531 0x2101 9531 GPS
+
+/* Lenovo products */
+product LENOVO GIGALAN 0x304b USB 3.0 Ethernet
+product LENOVO ETHERNET 0x7203 USB 2.0 Ethernet
+product LENOVO RTL8153 0x7205 USB 3.0 Ethernet
+product LENOVO ONELINK 0x720a USB 3.0 Ethernet
+product LENOVO RTL8153_04 0x720c USB 3.0 Ethernet
+product LENOVO ONELINKPLUS 0x3054 LAN port in Thinkpad OneLink+ dock
+product LENOVO TBT3LAN 0x3069 LAN port in Thinkpad TB3 dock
+product LENOVO USBCLAN 0x3062 LAN port in Thinkpad USB-C dock
+product LENOVO TBT3LANGEN2 0x3082 LAN port in Thinkpad TB3 dock gen2
+product LENOVO USBCLANGEN2 0xa387 LAN port in Thinkpad USB-C dock gen2
+product LENOVO USBCLANHYBRID 0xa359 LAN port in Thinkpad Hybrid USB-C with USB-A dock
+
+/* Lexar products */
+product LEXAR JUMPSHOT 0x0001 jumpSHOT CompactFlash Reader
+product LEXAR CF_READER 0xb002 USB CF Reader
+product LEXAR JUMPDRIVE 0xa833 USB Jumpdrive Flash Drive
+
+/* Lexmark products */
+product LEXMARK S2450 0x0009 Optra S 2450
+
+/* Liebert products */
+product LIEBERT POWERSURE_PXT 0xffff PowerSure Personal XT
+product LIEBERT2 POWERSURE_PSA 0x0001 PowerSure PSA UPS
+product LIEBERT2 PSI1000 0x0004 UPS PSI 1000 FW:08
+
+/* Link Instruments Inc. products */
+product LINKINSTRUMENTS MSO19 0xf190 Link Instruments MSO-19
+product LINKINSTRUMENTS MSO28 0xf280 Link Instruments MSO-28
+product LINKINSTRUMENTS MSO28_2 0xf281 Link Instruments MSO-28
+
+/* 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
+product LINKSYS4 WUSB100 0x0070 WUSB100
+product LINKSYS4 WUSB600N 0x0071 WUSB600N
+product LINKSYS4 WUSB54GCV2 0x0073 WUSB54GC v2
+product LINKSYS4 WUSB54GCV3 0x0077 WUSB54GC v3
+product LINKSYS4 RT3070 0x0078 RT3070
+product LINKSYS4 WUSB600NV2 0x0079 WUSB600N v2
+
+/* Logilink products */
+product LOGILINK U2M 0x0101 LogiLink USB MIDI Cable
+
+/* 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 WEBCAMC100 0X0817 Webcam C100
+product LOGITECH QUICKCAMEXP 0x0840 QuickCam Express
+product LOGITECH QUICKCAM 0x0850 QuickCam
+product LOGITECH QUICKCAMPRO3 0x0990 QuickCam Pro 9000
+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 G510S 0xc22d G510s Keyboard
+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 UNIFYING 0xc52b Logitech Unifying Receiver
+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
+product LOGITEC LAN_GTJU2A 0x0160 LAN-GTJ/U2A Ethernet
+product LOGITEC RT2870_1 0x0162 RT2870
+product LOGITEC RT2870_2 0x0163 RT2870
+product LOGITEC RT2870_3 0x0164 RT2870
+product LOGITEC LANW300NU2 0x0166 LAN-W300N/U2
+product LOGITEC LANW150NU2 0x0168 LAN-W150N/U2
+product LOGITEC LANW300NU2S 0x0169 LAN-W300N/U2S
+
+/* Longcheer Holdings, Ltd. products */
+product LONGCHEER WM66 0x6061 Longcheer WM66 HSDPA
+product LONGCHEER W14 0x9603 Mobilcom W14
+product LONGCHEER DISK 0xf000 Driver disk
+product LONGCHEER XSSTICK 0x9605 4G Systems XSStick P14
+
+
+/* Lucent products */
+product LUCENT EVALKIT 0x1001 USS-720 evaluation kit
+
+/* Luwen products */
+product LUWEN EASYDISK 0x0005 EasyDisc
+
+/* Macally products */
+product MACALLY MOUSE1 0x0101 mouse
+
+/* Mag-Tek products */
+product MAGTEK USBSWIPE 0x0002 USB Mag Stripe Swipe Reader
+
+/* Marvell Technology Group, Ltd. products */
+product MARVELL SHEEVAPLUG 0x9e8f SheevaPlug serial interface
+
+/* Matrix Orbital products */
+product MATRIXORBITAL FTDI_RANGE_0100 0x0100 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0101 0x0101 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0102 0x0102 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0103 0x0103 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0104 0x0104 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0105 0x0105 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0106 0x0106 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0107 0x0107 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0108 0x0108 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0109 0x0109 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_010A 0x010a FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_010B 0x010b FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_010C 0x010c FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_010D 0x010d FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_010E 0x010e FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_010F 0x010f FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0110 0x0110 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0111 0x0111 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0112 0x0112 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0113 0x0113 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0114 0x0114 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0115 0x0115 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0116 0x0116 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0117 0x0117 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0118 0x0118 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0119 0x0119 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_011A 0x011a FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_011B 0x011b FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_011C 0x011c FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_011D 0x011d FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_011E 0x011e FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_011F 0x011f FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0120 0x0120 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0121 0x0121 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0122 0x0122 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0123 0x0123 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0124 0x0124 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0125 0x0125 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0126 0x0126 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0128 0x0128 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0129 0x0129 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_012A 0x012a FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_012B 0x012b FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_012D 0x012d FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_012E 0x012e FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_012F 0x012f FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0130 0x0130 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0131 0x0131 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0132 0x0132 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0133 0x0133 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0134 0x0134 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0135 0x0135 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0136 0x0136 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0137 0x0137 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0138 0x0138 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0139 0x0139 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_013A 0x013a FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_013B 0x013b FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_013C 0x013c FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_013D 0x013d FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_013E 0x013e FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_013F 0x013f FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0140 0x0140 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0141 0x0141 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0142 0x0142 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0143 0x0143 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0144 0x0144 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0145 0x0145 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0146 0x0146 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0147 0x0147 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0148 0x0148 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0149 0x0149 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_014A 0x014a FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_014B 0x014b FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_014C 0x014c FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_014D 0x014d FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_014E 0x014e FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_014F 0x014f FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0150 0x0150 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0151 0x0151 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0152 0x0152 FTDI compatible adapter
+product MATRIXORBITAL MOUA 0x0153 Martrix Orbital MOU-Axxxx LCD displays
+product MATRIXORBITAL FTDI_RANGE_0159 0x0159 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_015A 0x015a FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_015B 0x015b FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_015C 0x015c FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_015D 0x015d FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_015E 0x015e FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_015F 0x015f FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0160 0x0160 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0161 0x0161 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0162 0x0162 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0163 0x0163 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0164 0x0164 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0165 0x0165 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0166 0x0166 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0167 0x0167 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0168 0x0168 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0169 0x0169 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_016A 0x016a FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_016B 0x016b FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_016C 0x016c FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_016D 0x016d FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_016E 0x016e FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_016F 0x016f FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0170 0x0170 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0171 0x0171 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0172 0x0172 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0173 0x0173 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0174 0x0174 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0175 0x0175 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0176 0x0176 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0177 0x0177 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0178 0x0178 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0179 0x0179 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_017A 0x017a FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_017B 0x017b FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_017C 0x017c FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_017D 0x017d FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_017E 0x017e FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_017F 0x017f FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0180 0x0180 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0181 0x0181 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0182 0x0182 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0183 0x0183 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0184 0x0184 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0185 0x0185 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0186 0x0186 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0187 0x0187 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0188 0x0188 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0189 0x0189 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_018A 0x018a FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_018B 0x018b FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_018C 0x018c FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_018D 0x018d FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_018E 0x018e FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_018F 0x018f FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0190 0x0190 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0191 0x0191 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0192 0x0192 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0193 0x0193 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0194 0x0194 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0195 0x0195 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0196 0x0196 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0197 0x0197 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0198 0x0198 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_0199 0x0199 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_019A 0x019a FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_019B 0x019b FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_019C 0x019c FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_019D 0x019d FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_019E 0x019e FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_019F 0x019f FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01A0 0x01a0 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01A1 0x01a1 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01A2 0x01a2 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01A3 0x01a3 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01A4 0x01a4 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01A5 0x01a5 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01A6 0x01a6 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01A7 0x01a7 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01A8 0x01a8 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01A9 0x01a9 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01AA 0x01aa FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01AB 0x01ab FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01AC 0x01ac FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01AD 0x01ad FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01AE 0x01ae FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01AF 0x01af FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01B0 0x01b0 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01B1 0x01b1 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01B2 0x01b2 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01B3 0x01b3 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01B4 0x01b4 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01B5 0x01b5 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01B6 0x01b6 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01B7 0x01b7 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01B8 0x01b8 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01B9 0x01b9 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01BA 0x01ba FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01BB 0x01bb FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01BC 0x01bc FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01BD 0x01bd FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01BE 0x01be FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01BF 0x01bf FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01C0 0x01c0 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01C1 0x01c1 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01C2 0x01c2 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01C3 0x01c3 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01C4 0x01c4 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01C5 0x01c5 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01C6 0x01c6 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01C7 0x01c7 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01C8 0x01c8 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01C9 0x01c9 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01CA 0x01ca FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01CB 0x01cb FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01CC 0x01cc FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01CD 0x01cd FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01CE 0x01ce FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01CF 0x01cf FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01D0 0x01d0 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01D1 0x01d1 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01D2 0x01d2 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01D3 0x01d3 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01D4 0x01d4 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01D5 0x01d5 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01D6 0x01d6 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01D7 0x01d7 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01D8 0x01d8 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01D9 0x01d9 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01DA 0x01da FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01DB 0x01db FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01DC 0x01dc FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01DD 0x01dd FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01DE 0x01de FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01DF 0x01df FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01E0 0x01e0 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01E1 0x01e1 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01E2 0x01e2 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01E3 0x01e3 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01E4 0x01e4 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01E5 0x01e5 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01E6 0x01e6 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01E7 0x01e7 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01E8 0x01e8 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01E9 0x01e9 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01EA 0x01ea FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01EB 0x01eb FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01EC 0x01ec FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01ED 0x01ed FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01EE 0x01ee FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01EF 0x01ef FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01F0 0x01f0 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01F1 0x01f1 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01F2 0x01f2 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01F3 0x01f3 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01F4 0x01f4 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01F5 0x01f5 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01F6 0x01f6 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01F7 0x01f7 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01F8 0x01f8 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01F9 0x01f9 FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01FA 0x01fa FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01FB 0x01fb FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01FC 0x01fc FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01FD 0x01fd FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01FE 0x01fe FTDI compatible adapter
+product MATRIXORBITAL FTDI_RANGE_01FF 0x01ff FTDI compatible adapter
+
+/* 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
+
+/* Medeli */
+product MEDELI DD305 0x5011 DD305 Digital Drum Set
+
+/* MediaTek, Inc. */
+product MEDIATEK MTK3329 0x3329 MTK II GPS Receiver
+
+/* 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 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 LUA3U2AGT 0x006e LUA3-U2-AGT
+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 WLIUCG300N 0x00e8 WLI-UC-G300N
+product MELCO SG54HG 0x00f4 WLI-U2-SG54HG
+product MELCO WLRUCG 0x0116 WLR-UC-G
+product MELCO WLRUCGAOSS 0x0119 WLR-UC-G-AOSS
+product MELCO WLIUCAG300N 0x012e WLI-UC-AG300N
+product MELCO WLIUCG 0x0137 WLI-UC-G
+product MELCO WLIUCG300HP 0x0148 WLI-UC-G300HP
+product MELCO RT2870_2 0x0150 RT2870
+product MELCO WLIUCGN 0x015d WLI-UC-GN
+product MELCO WLIUCG301N 0x016f WLI-UC-G301N
+product MELCO WLIUCGNM 0x01a2 WLI-UC-GNM
+product MELCO WLIUCG300HPV1 0x01a8 WLI-UC-G300HP-V1
+product MELCO WLIUCGNM2 0x01ee WLI-UC-GNM2
+product MELCO WIU2433DM 0x0242 WI-U2-433DM
+product MELCO WIU3866D 0x025d WI-U3-866D
+product MELCO WIU2433DHP 0x029b WI-U2-433DHP
+
+/* Mercusys, Inc. */
+product MERCUSYS MW150US 0x0102 Mercusys MW150US
+
+/* Merlin products */
+product MERLIN V620 0x1110 Merlin V620
+
+/* MetaGeek products */
+product METAGEEK TELLSTICK 0x0c30 FTDI compatible adapter
+product METAGEEK WISPY1B 0x083e MetaGeek Wi-Spy
+product METAGEEK WISPY24X 0x083f MetaGeek Wi-Spy 2.4x
+product METAGEEK2 WISPYDBX 0x5000 MetaGeek Wi-Spy DBx
+
+/* 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
+
+/* MEI products */
+product MEI CASHFLOW_SC 0x1100 Cashflow-SC Cash Acceptor
+product MEI S2000 0x1101 Series 2000 Combo Acceptor
+
+/* Microdia / Sonix Techonology Co., Ltd. products */
+product CHICONY2 YUREX 0x1010 YUREX
+product CHICONY2 CAM_1 0x62c0 CAM_1
+product CHICONY2 TEMPER 0x7401 TEMPer sensor
+
+/* Micro Star International products */
+product MSI BT_DONGLE 0x1967 Bluetooth USB dongle
+product MSI RT3070_1 0x3820 RT3070
+product MSI RT3070_2 0x3821 RT3070
+product MSI RT3070_8 0x3822 RT3070
+product MSI RT3070_3 0x3870 RT3070
+product MSI RT3070_9 0x3871 RT3070
+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 RT3070_4 0x6899 RT3070
+product MSI RT3070_5 0x821a RT3070
+product MSI RT3070_10 0x822a RT3070
+product MSI RT3070_6 0x870a RT3070
+product MSI RT3070_11 0x871a RT3070
+product MSI RT3070_7 0x899a RT3070
+product MSI RT2573_3 0xa861 RT2573
+product MSI RT2573_4 0xa874 RT2573
+
+/* Micron products */
+product MICRON REALSSD 0x0655 Real SSD eUSB
+
+/* 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 INTELLIMOUSE5 0x0039 IntelliMouse 1.1 5-Button Mouse
+product MICROSOFT WHEELMOUSE 0x0040 Wheel Mouse Optical
+product MICROSOFT MN510 0x006e MN510 Wireless
+product MICROSOFT 700WX 0x0079 Palm 700WX
+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 WLNOTEBOOK3 0x00d2 Wireless Optical Mouse 3000 (Model 1049)
+product MICROSOFT NATURAL4000 0x00db Natural Ergonomic Keyboard 4000
+product MICROSOFT WLNOTEBOOK2 0x00e1 Wireless Optical Mouse 3000 (Model 1056)
+product MICROSOFT XBOX360 0x0292 XBOX 360 WLAN
+product MICROSOFT WINDEVETH 0x0c5e Windows Dev Kit 2023 Ethernet
+
+/* 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 MAUDIO MIDISPORT2X2 0x1001 Midisport 2x2
+product MAUDIO FASTTRACKULTRA 0x2080 Fast Track Ultra
+product MAUDIO FASTTRACKULTRA8R 0x2081 Fast Track Ultra 8R
+
+/* 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
+
+/* Mobile Action products */
+product MOBILEACTION MA620 0x0620 MA-620 Infrared Adapter
+
+/* Mobility products */
+product MOBILITY USB_SERIAL 0x0202 FTDI compatible adapter
+product MOBILITY EA 0x0204 Ethernet
+product MOBILITY EASIDOCK 0x0304 EasiDock Ethernet
+
+/* MosChip products */
+product MOSCHIP MCS7703 0x7703 MCS7703 Serial Port Adapter
+product MOSCHIP MCS7730 0x7730 MCS7730 Ethernet
+product MOSCHIP MCS7820 0x7820 MCS7820 Serial Port Adapter
+product MOSCHIP MCS7830 0x7830 MCS7830 Ethernet
+product MOSCHIP MCS7832 0x7832 MCS7832 Ethernet
+product MOSCHIP MCS7840 0x7840 MCS7840 Serial Port Adapter
+
+/* Motorola products */
+product MOTOROLA MC141555 0x1555 MC141555 hub controller
+product MOTOROLA SB4100 0x4100 SB4100 USB Cable Modem
+product MOTOROLA2 T720C 0x2822 T720c
+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
+product MOTOROLA2 MB886 0x710f MB886 Mobile Phone (Atria HD)
+product MOTOROLA4 RT2770 0x9031 RT2770
+product MOTOROLA4 RT3070 0x9032 RT3070
+
+/* Moxa */
+product MOXA MXU1_1110 0x1110 Moxa Uport 1110
+product MOXA MXU1_1130 0x1130 Moxa Uport 1130
+product MOXA MXU1_1131 0x1131 Moxa Uport 1131
+product MOXA MXU1_1150 0x1150 Moxa Uport 1150
+product MOXA MXU1_1151 0x1151 Moxa Uport 1151
+
+/* MpMan products */
+product MPMAN MPF400_2 0x25a8 MPF400 Music Player 2Go
+product MPMAN MPF400_1 0x36d0 MPF400 Music Player 1Go
+
+/* MultiTech products */
+product MULTITECH MT9234ZBA_2 0x0319 MT9234ZBA USB modem (alt)
+product MULTITECH ATLAS 0xf101 MT5634ZBA-USB modem
+product MULTITECH GSM 0xf108 GSM USB Modem
+product MULTITECH CDMA 0xf109 CDMA USB Modem
+product MULTITECH CDMA_FW 0xf110 CDMA USB Modem firmware running
+product MULTITECH GSM_FW 0xf111 GSM USB Modem firmware running
+product MULTITECH EDGE 0xf112 Edge USB Modem
+product MULTITECH MT9234MU 0xf114 MT9234 MU
+product MULTITECH MT9234ZBA 0xf115 MT9234 ZBA
+
+/* 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 BEARPAW2400TA 0x0218 BearPaw 2400TA 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_8813 0x8813 USB-IDE
+product MYSON HEDEN 0x8818 USB-IDE
+product MYSON HUBREADER 0x8819 COMBO Card reader with USB HUB
+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_0050 0x0050 USB 2.0 7-Port Hub
+product NEC HUB_005A 0x005a USB 2.0 4-Port Hub
+product NEC WL300NUG 0x0249 WL300NU-G
+product NEC WL900U 0x0408 Aterm WL900U
+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
+
+/* Neotel products */
+product NEOTEL PRIME 0x4000 Prime USB modem
+
+/* 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 GADGETZERO 0xa4a0 Linux Gadget Zero
+product NETCHIP ETHERNETGADGET 0xa4a2 Linux Ethernet/RNDIS gadget on pxa210/25x/26x
+product NETCHIP POCKETBOOK 0xa4a5 PocketBook
+
+/* 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 M4100 0x1100 M4100/M5300/M7100 series switch
+product NETGEAR WG111V1_2 0x4240 PrismGT USB 2.0 WLAN
+product NETGEAR WG111V3 0x4260 WG111v3
+product NETGEAR WG111U 0x4300 WG111U
+product NETGEAR WG111U_NF 0x4301 WG111U (no firmware)
+product NETGEAR WG111V2 0x6a00 WG111V2
+product NETGEAR WN111V2 0x9001 WN111V2
+product NETGEAR WNDA3100 0x9010 WNDA3100
+product NETGEAR WNDA4100 0x9012 WNDA4100
+product NETGEAR WNDA3200 0x9018 WNDA3200
+product NETGEAR RTL8192CU 0x9021 RTL8192CU
+product NETGEAR WNA1000 0x9040 WNA1000
+product NETGEAR WNA1000M 0x9041 WNA1000M
+product NETGEAR A6100 0x9052 A6100
+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)
+product NETGEAR3 WPN111_2 0x5f02 WPN111
+product NETGEAR4 RTL8188CU 0x9041 RTL8188CU
+
+/* NetIndex products */
+product NETINDEX WS002IN 0x2001 Willcom WS002IN
+
+/* NEWlink */
+product NEWLINK USB2IDEBRIDGE 0x00ff USB 2.0 Hard Drive Enclosure
+
+/* 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
+product NOVATECH RTL8188CU 0x9071 RTL8188CU
+
+/* Nokia products */
+product NOKIA N958GB 0x0070 Nokia N95 8GBc
+product NOKIA2 CA42 0x1234 CA-42 cable
+
+/* NOREL Systems Ltd. products */
+product NORELSYS NS1081 0x1081 NS1081 USB 3.0 Flash Card Reader
+
+/* 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 Expedite ES620
+product NOVATEL E725 0x2120 Expedite E725
+product NOVATEL ES620_2 0x2130 Expedite ES620
+product NOVATEL ES620 0x2100 ES620 CDMA
+product NOVATEL U720 0x2110 Merlin U720
+product NOVATEL EU730 0x2400 Expedite EU730
+product NOVATEL EU740 0x2410 Expedite EU740
+product NOVATEL EU870D 0x2420 Expedite EU870D
+product NOVATEL U727 0x4100 Merlin U727 CDMA
+product NOVATEL MC950D 0x4400 Novatel MC950D HSUPA
+product NOVATEL MC990D 0x7001 Novatel MC990D
+product NOVATEL ZEROCD 0x5010 Novatel ZeroCD
+product NOVATEL MIFI2200V 0x5020 Novatel MiFi 2200 CDMA Virgin Mobile
+product NOVATEL ZEROCD2 0x5030 Novatel ZeroCD
+product NOVATEL MIFI2200 0x5041 Novatel MiFi 2200 CDMA
+product NOVATEL U727_2 0x5100 Merlin U727 CDMA
+product NOVATEL U760 0x6000 Novatel U760
+product NOVATEL MC760 0x6002 Novatel MC760
+product NOVATEL MC547 0x7042 Novatel MC547
+product NOVATEL MC679 0x7031 Novatel MC679
+product NOVATEL2 FLEXPACKGPS 0x0100 NovAtel FlexPack GPS receiver
+
+/* NVIDIA products */
+product NVIDIA RTL8153 0x09ff USB 3.0 Ethernet
+
+/* Merlin products */
+product MERLIN V620 0x1110 Merlin V620
+
+/* O2Micro products */
+product O2MICRO OZ776_HUB 0x7761 OZ776 hub
+product O2MICRO OZ776_CCID_SC 0x7772 OZ776 CCID SC Reader
+
+/* Olimex products */
+product OLIMEX ARM_USB_OCD 0x0003 FTDI compatible adapter
+product OLIMEX ARM_USB_OCD_H 0x002b FTDI compatible adapter
+
+/* Olympus products */
+product OLYMPUS C1 0x0102 C-1 Digital Camera
+product OLYMPUS C700 0x0105 C-700 Ultra Zoom
+/* Current Olympus DSLRs and EVIL cameras reuse the same product code and
+ * bcdDevice for multiple models. This may result in problems with newer
+ * cameras. */
+/* Olympus DSLRs */
+product OLYMPUS E_1 0x0102
+product OLYMPUS E_300 0x0118
+product OLYMPUS E_330 0x0118
+product OLYMPUS E_30 0x0118
+/* Olympus EVIL cameras */
+product OLYMPUS E_PM1 0x012c
+product OLYMPUS E_PM2 0x012e
+product OLYMPUS E_M1 0x012e
+product OLYMPUS E_M1MarkII 0x012e
+product OLYMPUS E_M5MarkIII 0x012e
+
+/* 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 GTMAX72 0x6711 GlobeTrotter Max 7.2 HSDPA
+product OPTION GTHSDPA 0x6971 GlobeTrotter HSDPA
+product OPTION GTMAXHSUPA 0x7001 GlobeTrotter HSUPA
+product OPTION GTMAXHSUPAE 0x6901 GlobeTrotter HSUPA PCIe
+product OPTION GTMAX380HSUPAE 0x7211 GlobeTrotter 380HSUPA PCIe
+product OPTION GT3G_1 0x6050 3G modem
+product OPTION GT3G_2 0x6100 3G modem
+product OPTION GT3G_3 0x6150 3G modem
+product OPTION GT3G_4 0x6200 3G modem
+product OPTION GT3G_5 0x6250 3G modem
+product OPTION GT3G_6 0x6350 3G modem
+product OPTION E6500 0x6500 3G modem
+product OPTION E6501 0x6501 3G modem
+product OPTION E6601 0x6601 3G modem
+product OPTION E6721 0x6721 3G modem
+product OPTION E6741 0x6741 3G modem
+product OPTION E6761 0x6761 3G modem
+product OPTION E6800 0x6800 3G modem
+product OPTION E7021 0x7021 3G modem
+product OPTION E7041 0x7041 3G modem
+product OPTION E7061 0x7061 3G modem
+product OPTION E7100 0x7100 3G modem
+product OPTION GTM380 0x7201 3G modem
+product OPTION GE40X 0x7601 Globetrotter HSUPA
+product OPTION GSICON72 0x6911 GlobeSurfer iCON
+product OPTION GSICONHSUPA 0x7251 Globetrotter HSUPA
+product OPTION ICON401 0x7401 GlobeSurfer iCON 401
+product OPTION GTHSUPA 0x7011 Globetrotter HSUPA
+product OPTION GMT382 0x7501 Globetrotter HSUPA
+product OPTION GE40X_1 0x7301 Globetrotter HSUPA
+product OPTION GE40X_2 0x7361 Globetrotter HSUPA
+product OPTION GE40X_3 0x7381 Globetrotter HSUPA
+product OPTION GTM661W 0x9000 GTM661W
+product OPTION ICONEDGE 0xc031 GlobeSurfer iCON EDGE
+product OPTION MODHSXPA 0xd013 Globetrotter HSUPA
+product OPTION ICON321 0xd031 Globetrotter HSUPA
+product OPTION ICON505 0xd055 Globetrotter iCON 505
+product OPTION ICON452 0x7901 Globetrotter iCON 452
+
+/* Optoelectronics Co., Ltd */
+product OPTO BARCODE 0x0001 Barcode Reader
+product OPTO OPTICONCODE 0x0009 Opticon Code Reader
+product OPTO BARCODE_1 0xa002 Barcode Reader
+product OPTO CRD7734 0xc000 USB Cradle CRD-7734-RU
+product OPTO CRD7734_1 0xc001 USB Cradle CRD-7734-RU
+
+/* OvisLink product */
+product OVISLINK RT3072 0x3072 RT3072
+
+/* 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
+
+/* Ours Technology Inc. */
+product OTI DKU5 0x6858 DKU-5 Serial
+
+/* Owen.ru products */
+product OWEN AC4 0x0004 AC4 USB-RS485 converter
+
+/* OWL producs */
+product OWL CM_160 0xca05 OWL CM-160 power monitor
+
+/* 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
+product PANASONIC CFF9_3G_QDL 0x250e ToughBook CF-F9 GOBI 3G Modem Loader
+product PANASONIC CFF9_3G 0x250f ToughBook CF-F9 GOBI 3G Modem
+product PANASONIC TYTP50P6S 0x3900 TY-TP50P6-S 50in Touch Panel
+
+/* Papouch products */
+product PAPOUCH AD4USB 0x8003 FTDI compatible adapter
+product PAPOUCH AP485 0x0101 FTDI compatible adapter
+product PAPOUCH AP485_2 0x0104 FTDI compatible adapter
+product PAPOUCH DRAK5 0x0700 FTDI compatible adapter
+product PAPOUCH DRAK6 0x1000 FTDI compatible adapter
+product PAPOUCH GMSR 0x8005 FTDI compatible adapter
+product PAPOUCH GMUX 0x8004 FTDI compatible adapter
+product PAPOUCH IRAMP 0x0500 FTDI compatible adapter
+product PAPOUCH LEC 0x0300 FTDI compatible adapter
+product PAPOUCH MU 0x8001 FTDI compatible adapter
+product PAPOUCH QUIDO10X1 0x0b00 FTDI compatible adapter
+product PAPOUCH QUIDO2X16 0x0e00 FTDI compatible adapter
+product PAPOUCH QUIDO2X2 0x0a00 FTDI compatible adapter
+product PAPOUCH QUIDO30X3 0x0c00 FTDI compatible adapter
+product PAPOUCH QUIDO3X32 0x0f00 FTDI compatible adapter
+product PAPOUCH QUIDO4X4 0x0900 FTDI compatible adapter
+product PAPOUCH QUIDO60X3 0x0d00 FTDI compatible adapter
+product PAPOUCH QUIDO8X8 0x0800 FTDI compatible adapter
+product PAPOUCH SB232 0x0301 FTDI compatible adapter
+product PAPOUCH SB422 0x0102 FTDI compatible adapter
+product PAPOUCH SB422_2 0x0105 FTDI compatible adapter
+product PAPOUCH SB485 0x0100 FTDI compatible adapter
+product PAPOUCH SB485C 0x0107 FTDI compatible adapter
+product PAPOUCH SB485S 0x0106 FTDI compatible adapter
+product PAPOUCH SB485_2 0x0103 FTDI compatible adapter
+product PAPOUCH SIMUKEY 0x8002 FTDI compatible adapter
+product PAPOUCH TMU 0x0400 FTDI compatible adapter
+product PAPOUCH UPSUSB 0x8000 FTDI compatible adapter
+
+/* PARA Industrial products */
+product PARA RT3070 0x8888 RT3070
+
+/* Simtec Electronics products */
+product SIMTEC ENTROPYKEY 0x0001 Entropy Key
+
+/* Pegatron products */
+product PEGATRON RT2870 0x0002 RT2870
+product PEGATRON RT3070 0x000c RT3070
+product PEGATRON RT3070_2 0x000e RT3070
+product PEGATRON RT3070_3 0x0010 RT3070
+
+/* Peracom products */
+product PERACOM SERIAL1 0x0001 Serial
+product PERACOM ENET 0x0002 Ethernet
+product PERACOM ENET3 0x0003 At Home Ethernet
+product PERACOM ENET2 0x0005 Ethernet
+
+/* Peraso Technologies, Inc products */
+product PERASO PRS4001 0x4001 PRS4001 WLAN
+
+/* 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 SPC900NC 0x0329 SPC 900NC CCD PC Camera
+product PHILIPS DSS150 0x0471 DSS 150 Digital Speaker System
+product PHILIPS ACE1001 0x066a AKTAKOM ACE-1001 cable
+product PHILIPS SPE3030CC 0x083a USB 2.0 External Disk
+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
+product PHILIPS RT2870 0x200f RT2870
+
+/* Philips Semiconductor products */
+product PHILIPSSEMI HUB1122 0x1122 HUB
+
+/* Megatec */
+product MEGATEC UPS 0x5161 Phoenixtec protocol based UPS
+
+/* 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 RTL8188CUS 0x1201 RTL8188CUS
+product PLANEX2 GW_US11S 0x3220 GW-US11S WLAN
+product PLANEX2 GW_US54GXS 0x5303 GW-US54GXS WLAN
+product PLANEX2 GW_US300 0x5304 GW-US300
+product PLANEX2 RTL8188CU_1 0xab2a RTL8188CU
+product PLANEX2 RTL8188CU_2 0xed17 RTL8188CU
+product PLANEX2 RTL8188CU_3 0x4902 RTL8188CU
+product PLANEX2 RTL8188CU_4 0xab2e RTL8188CU
+product PLANEX2 RTL8192CU 0xab2b RTL8192CU
+product PLANEX2 GWUS54HP 0xab01 GW-US54HP
+product PLANEX2 GWUS300MINIS 0xab24 GW-US300MiniS
+product PLANEX2 RT3070 0xab25 RT3070
+product PLANEX2 MZKUE150N 0xab2f MZK-UE150N
+product PLANEX2 GW900D 0xab30 GW-900D
+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 PLANEX2 RT2870 0xed06 RT2870
+product PLANEX2 GWUSMICRON 0xed14 GW-USMicroN
+product PLANEX2 GWUSVALUEEZ 0xed17 GW-USValue-EZ
+product PLANEX3 GWUS54GZ 0xab10 GW-US54GZ
+product PLANEX3 GU1000T 0xab11 GU-1000T
+product PLANEX3 GWUS54MINI 0xab13 GW-US54Mini
+product PLANEX2 GWUSNANO 0xab28 GW-USNano
+
+/* Plextor Corp. */
+product PLEXTOR 40_12_40U 0x0011 PlexWriter 40/12/40U
+
+/* Ploytec GmbH */
+product PLOYTEC SPL_CRIMSON_1 0xc150 SPL Crimson Revision 1
+
+/* PLX products */
+product PLX TESTBOARD 0x9060 test board
+product PLX CA42 0xac70 CA-42
+
+/* PowerCOM products */
+product POWERCOM IMPERIAL_SERIES 0x00a2 IMPERIAL Series
+product POWERCOM SMART_KING_PRO 0x00a3 Smart KING Pro
+product POWERCOM WOW 0x00a4 WOW
+product POWERCOM VANGUARD 0x00a5 Vanguard
+product POWERCOM BLACK_KNIGHT_PRO 0x00a6 Black Knight Pro
+
+/* 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
+
+/* Posiflex products */
+product POSIFLEX PP7000 0x0300 FTDI compatible adapter
+
+/* 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
+product PRIMAX HP_RH304AA 0x4d17 HP RH304AA mouse
+
+/* Prolific products */
+product PROLIFIC PL2301 0x0000 PL2301 Host-Host interface
+product PROLIFIC PL2302 0x0001 PL2302 Host-Host interface
+product PROLIFIC MOTOROLA 0x0307 Motorola Cable
+product PROLIFIC RSAQ2 0x04bb PL2303 Serial (IODATA USB-RSAQ2)
+product PROLIFIC ALLTRONIX_GPRS 0x0609 Alltronix ACM003U00 modem
+product PROLIFIC ALDIGA_AL11U 0x0611 AlDiga AL-11U modem
+product PROLIFIC MICROMAX_610U 0x0612 Micromax 610U
+product PROLIFIC DCU11 0x1234 DCU-11 Phone Cable
+product PROLIFIC UIC_MSR206 0x206a UIC MSR206 Card Reader
+product PROLIFIC PL2303 0x2303 PL2303 Serial (ATEN/IOGEAR UC232A)
+product PROLIFIC PL2303GC 0x23a3 PL2303HXN Serial, type GC
+product PROLIFIC PL2303GB 0x23b3 PL2303HXN Serial, type GB
+product PROLIFIC PL2303GT 0x23c3 PL2303HXN Serial, type GT
+product PROLIFIC PL2303GL 0x23d3 PL2303HXN Serial, type GL
+product PROLIFIC PL2303GE 0x23e3 PL2303HXN Serial, type GE
+product PROLIFIC PL2303GS 0x23f3 PL2303HXN Serial, type GS
+product PROLIFIC PL2305 0x2305 Parallel printer
+product PROLIFIC ATAPI4 0x2307 ATAPI-4 Controller
+product PROLIFIC PL2501 0x2501 PL2501 Host-Host interface
+product PROLIFIC PL2506 0x2506 PL2506 USB to IDE Bridge
+product PROLIFIC PL27A1 0x27A1 PL27A1 USB 3.0 Host-Host interface
+product PROLIFIC HCR331 0x331a HCR331 Hybrid Card Reader
+product PROLIFIC PHAROS 0xaaa0 Prolific Pharos
+product PROLIFIC RSAQ3 0xaaa2 PL2303 Serial Adapter (IODATA USB-RSAQ3)
+product PROLIFIC2 PL2303 0x2303 PL2303 Serial Adapter
+
+/* Putercom products */
+product PUTERCOM UPA100 0x047e USB-1284 BRIDGE
+
+/* Qcom products */
+product QCOM RT2573 0x6196 RT2573
+product QCOM RT2573_2 0x6229 RT2573
+product QCOM RT2573_3 0x6238 RT2573
+product QCOM RT2870 0x6259 RT2870
+
+/* QI-hardware */
+product QIHARDWARE JTAGSERIAL 0x0713 FTDI compatible adapter
+
+/* Qisda products */
+product QISDA H21_1 0x4512 3G modem
+product QISDA H21_2 0x4523 3G modem
+product QISDA H20_1 0x4515 3G modem
+product QISDA H20_2 0x4519 3G modem
+
+/* Qualcomm products */
+product QUALCOMM CDMA_MSM 0x6000 CDMA Technologies MSM phone
+product QUALCOMM NTT_L02C_MODEM 0x618f NTT DOCOMO L-02C
+product QUALCOMM NTT_L02C_STORAGE 0x61dd NTT DOCOMO L-02C
+product QUALCOMM2 MF330 0x6613 MF330
+product QUALCOMM2 RWT_FCT 0x3100 RWT FCT-CDMA 2000 1xRTT modem
+product QUALCOMM2 CDMA_MSM 0x3196 CDMA Technologies MSM modem
+product QUALCOMM2 AC8700 0x6000 AC8700
+product QUALCOMM2 VW110L 0x1000 Vertex Wireless 110L modem
+product QUALCOMM2 SIM5218 0x9000 SIM5218
+product QUALCOMM2 WM620 0x9002 Neoway WM620
+product QUALCOMM2 GOBI2000_QDL 0x9204 Qualcomm Gobi 2000 QDL
+product QUALCOMM2 GOBI2000 0x9205 Qualcomm Gobi 2000 modem
+product QUALCOMM2 VT80N 0x6500 Venus VT80N
+product QUALCOMM3 VFAST2 0x9909 Venus Fast2 modem
+product QUALCOMMINC CDMA_MSM 0x0001 CDMA Technologies MSM modem
+product QUALCOMMINC E0002 0x0002 3G modem
+product QUALCOMMINC E0003 0x0003 3G modem
+product QUALCOMMINC E0004 0x0004 3G modem
+product QUALCOMMINC E0005 0x0005 3G modem
+product QUALCOMMINC E0006 0x0006 3G modem
+product QUALCOMMINC E0007 0x0007 3G modem
+product QUALCOMMINC E0008 0x0008 3G modem
+product QUALCOMMINC E0009 0x0009 3G modem
+product QUALCOMMINC E000A 0x000a 3G modem
+product QUALCOMMINC E000B 0x000b 3G modem
+product QUALCOMMINC E000C 0x000c 3G modem
+product QUALCOMMINC E000D 0x000d 3G modem
+product QUALCOMMINC E000E 0x000e 3G modem
+product QUALCOMMINC E000F 0x000f 3G modem
+product QUALCOMMINC E0010 0x0010 3G modem
+product QUALCOMMINC E0011 0x0011 3G modem
+product QUALCOMMINC E0012 0x0012 3G modem
+product QUALCOMMINC E0013 0x0013 3G modem
+product QUALCOMMINC E0014 0x0014 3G modem
+product QUALCOMMINC MF628 0x0015 3G modem
+product QUALCOMMINC MF633R 0x0016 ZTE WCDMA modem
+product QUALCOMMINC E0017 0x0017 3G modem
+product QUALCOMMINC E0018 0x0018 3G modem
+product QUALCOMMINC E0019 0x0019 3G modem
+product QUALCOMMINC E0020 0x0020 3G modem
+product QUALCOMMINC E0021 0x0021 3G modem
+product QUALCOMMINC E0022 0x0022 3G modem
+product QUALCOMMINC E0023 0x0023 3G modem
+product QUALCOMMINC E0024 0x0024 3G modem
+product QUALCOMMINC E0025 0x0025 3G modem
+product QUALCOMMINC E0026 0x0026 3G modem
+product QUALCOMMINC E0027 0x0027 3G modem
+product QUALCOMMINC E0028 0x0028 3G modem
+product QUALCOMMINC E0029 0x0029 3G modem
+product QUALCOMMINC E0030 0x0030 3G modem
+product QUALCOMMINC MF626 0x0031 3G modem
+product QUALCOMMINC E0032 0x0032 3G modem
+product QUALCOMMINC E0033 0x0033 3G modem
+product QUALCOMMINC E0037 0x0037 3G modem
+product QUALCOMMINC E0039 0x0039 3G modem
+product QUALCOMMINC E0042 0x0042 3G modem
+product QUALCOMMINC E0043 0x0043 3G modem
+product QUALCOMMINC E0048 0x0048 3G modem
+product QUALCOMMINC E0049 0x0049 3G modem
+product QUALCOMMINC E0051 0x0051 3G modem
+product QUALCOMMINC E0052 0x0052 3G modem
+product QUALCOMMINC ZTE_STOR2 0x0053 USB ZTE Storage
+product QUALCOMMINC E0054 0x0054 3G modem
+product QUALCOMMINC E0055 0x0055 3G modem
+product QUALCOMMINC E0057 0x0057 3G modem
+product QUALCOMMINC E0058 0x0058 3G modem
+product QUALCOMMINC E0059 0x0059 3G modem
+product QUALCOMMINC E0060 0x0060 3G modem
+product QUALCOMMINC E0061 0x0061 3G modem
+product QUALCOMMINC E0062 0x0062 3G modem
+product QUALCOMMINC E0063 0x0063 3G modem
+product QUALCOMMINC E0064 0x0064 3G modem
+product QUALCOMMINC E0066 0x0066 3G modem
+product QUALCOMMINC E0069 0x0069 3G modem
+product QUALCOMMINC E0070 0x0070 3G modem
+product QUALCOMMINC E0073 0x0073 3G modem
+product QUALCOMMINC E0076 0x0076 3G modem
+product QUALCOMMINC E0078 0x0078 3G modem
+product QUALCOMMINC E0082 0x0082 3G modem
+product QUALCOMMINC E0086 0x0086 3G modem
+product QUALCOMMINC MF112 0x0103 3G modem
+product QUALCOMMINC SURFSTICK 0x0117 1&1 Surf Stick
+product QUALCOMMINC K3772_Z_INIT 0x1179 K3772-Z Initial
+product QUALCOMMINC K3772_Z 0x1181 K3772-Z
+product QUALCOMMINC ZTE_MF730M 0x1420 3G modem
+product QUALCOMMINC MF195E_INIT 0x1514 MF195E initial
+product QUALCOMMINC MF195E 0x1516 MF195E
+product QUALCOMMINC ZTE_STOR 0x2000 USB ZTE Storage
+product QUALCOMMINC E2002 0x2002 3G modem
+product QUALCOMMINC E2003 0x2003 3G modem
+product QUALCOMMINC AC682 0xffdd CDMA 1xEVDO USB modem
+product QUALCOMMINC AC682_INIT 0xffde CDMA 1xEVDO USB modem (initial)
+product QUALCOMMINC AC8710 0xfff1 3G modem
+product QUALCOMMINC AC2726 0xfff5 3G modem
+product QUALCOMMINC AC8700 0xfffe CDMA 1xEVDO USB modem
+
+/* Quanta products */
+product QUANTA RW6815_1 0x00ce HP iPAQ rw6815
+product QUANTA RT3070 0x0304 RT3070
+product QUANTA Q101_STOR 0x1000 USB Q101 Storage
+product QUANTA Q101 0xea02 HSDPA modem
+product QUANTA Q111 0xea03 HSDPA modem
+product QUANTA GLX 0xea04 HSDPA modem
+product QUANTA GKE 0xea05 HSDPA modem
+product QUANTA GLE 0xea06 HSDPA modem
+product QUANTA RW6815R 0xf003 HP iPAQ rw6815 RNDIS
+
+/* Quectel products */
+product QUECTEL EC21 0x0121 Quectel EC21
+product QUECTEL EC25 0x0125 Quectel EC20(MDM9x07)/EC25/EG25
+product QUECTEL EM05 0x0127 Quectel EM05
+product QUECTEL EG91 0x0191 Quectel EG91
+product QUECTEL EG95 0x0195 Quectel EG95
+product QUECTEL BG96 0x0296 Quectel BG96
+product QUECTEL EP06 0x0306 Quectel EG06/EP06/EM06
+product QUECTEL EG065K 0x030B Quectel EG065K/EG060K
+product QUECTEL AG15 0x0415 Quectel AG15
+product QUECTEL AG35 0x0435 Quectel AG35
+product QUECTEL AG520 0x0452 Quectel AG520
+product QUECTEL AG550 0x0455 Quectel AG550
+product QUECTEL EM12 0x0512 Quectel EG12/EP12/EM12/EG16/EG18
+product QUECTEL EM160R 0x0620 Quectel EM160R/EG20
+product QUECTEL BG95 0x0700 Quectel BG95/BG77/BG600L-M3/BC69
+product QUECTEL RG500 0x0800 Quectel RG500/RM500/RG510/RM510
+product QUECTEL RG520 0x0801 Quectel RG520/RM520/SG520
+product QUECTEL EC200 0x6000 Quectel EC200/UC200
+product QUECTEL EC200S 0x6002 Quectel EC200S
+product QUECTEL EC200T 0x6026 Quectel EC200T
+product QUECTEL UC200 0x6120 Quectel UC200
+
+/* Quickshot products */
+product QUICKSHOT STRIKEPAD 0x6238 USB StrikePad
+
+/* Qtronix products */
+product QTRONIX 980N 0x2011 Scorpion-980N keyboard
+
+/* 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 RT2070 0x2070 RT2070
+product RALINK RT2570_2 0x2570 RT2500USB Wireless Adapter
+product RALINK RT2573 0x2573 RT2501USB Wireless Adapter
+product RALINK RT2671 0x2671 RT2601USB Wireless Adapter
+product RALINK RT2770 0x2770 RT2770
+product RALINK RT2870 0x2870 RT2870
+product RALINK RT_STOR 0x2878 USB Storage
+product RALINK RT3070 0x3070 RT3070
+product RALINK RT3071 0x3071 RT3071
+product RALINK RT3072 0x3072 RT3072
+product RALINK RT3370 0x3370 RT3370
+product RALINK RT3572 0x3572 RT3572
+product RALINK RT3573 0x3573 RT3573
+product RALINK RT5370 0x5370 RT5370
+product RALINK RT5372 0x5372 RT5372
+product RALINK RT5572 0x5572 RT5572
+product RALINK MT7601U 0x7601 MT7601 Mediatek Wireless Adpater
+product RALINK RT8070 0x8070 RT8070
+product RALINK RT2570_3 0x9020 RT2500USB Wireless Adapter
+product RALINK RT2573_2 0x9021 RT2501USB Wireless Adapter
+
+/* RATOC Systems products */
+product RATOC REXUSB60 0xb000 USB serial adapter REX-USB60
+product RATOC REXUSB60F 0xb020 USB serial adapter REX-USB60F
+
+/* Realtek products */
+/* Green House and CompUSA OEM this part */
+product REALTEK USB20CRW 0x0158 USB20CRW Card Reader
+product REALTEK RTL8188ETV 0x0179 RTL8188ETV
+product REALTEK RTL8188CTV 0x018a RTL8188CTV
+product REALTEK RTL8821AU_2 0x0811 RTL8821AU
+product REALTEK RTW8821CU_CD 0x1a2b RTW8821CU_CD
+product REALTEK RTL8188RU_2 0x317f RTL8188RU
+product REALTEK USBKR100 0x8150 USBKR100 USB Ethernet
+product REALTEK RTL8152 0x8152 RTL8152 USB Ethernet
+product REALTEK RTL8153 0x8153 RTL8153 USB Ethernet
+product REALTEK RTL8156 0x8156 RTL8156 USB Ethernet
+product REALTEK RTL8188CE_0 0x8170 RTL8188CE
+product REALTEK RTL8171 0x8171 RTL8171
+product REALTEK RTL8172 0x8172 RTL8172
+product REALTEK RTL8173 0x8173 RTL8173
+product REALTEK RTL8174 0x8174 RTL8174
+product REALTEK RTL8188CU_0 0x8176 RTL8188CU
+product REALTEK RTL8191CU 0x8177 RTL8191CU
+product REALTEK RTL8192CU 0x8178 RTL8192CU
+product REALTEK RTL8188EU 0x8179 RTL8188EU
+product REALTEK RTL8188CU_1 0x817a RTL8188CU
+product REALTEK RTL8188CU_2 0x817b RTL8188CU
+product REALTEK RTL8192CE 0x817c RTL8192CE
+product REALTEK RTL8188RU_1 0x817d RTL8188RU
+product REALTEK RTL8188CE_1 0x817e RTL8188CE
+product REALTEK RTL8188RU_3 0x817f RTL8188RU
+product REALTEK RTL8187 0x8187 RTL8187 Wireless Adapter
+product REALTEK RTL8187B_0 0x8189 RTL8187B Wireless Adapter
+product REALTEK RTL8188CUS 0x818a RTL8188CUS
+product REALTEK RTL8192EU 0x818b RTL8192EU
+product REALTEK RTL8188CU_3 0x8191 RTL8188CU
+product REALTEK RTL8196EU 0x8196 RTL8196EU
+product REALTEK RTL8187B_1 0x8197 RTL8187B Wireless Adapter
+product REALTEK RTL8187B_2 0x8198 RTL8187B Wireless Adapter
+product REALTEK RTL8712 0x8712 RTL8712
+product REALTEK RTL8713 0x8713 RTL8713
+product REALTEK RTL8188CU_COMBO 0x8754 RTL8188CU
+product REALTEK RTL8812AU_1 0x8812 RTL8812AU Wireless Adapter
+product REALTEK RTL8812AU_2 0x881a RTL8812AU Wireless Adapter
+product REALTEK RTL8821AU_1 0xa811 RTL8821AU
+product REALTEK RTL8723BU 0xb720 RTL8723BU
+product REALTEK RTL8192SU 0xc512 RTL8192SU
+product REALTEK RTW8821CU 0xc811 RTW8821CU
+
+/* RedOctane products */
+product REDOCTANE GHMIDI 0x474b GH MIDI INTERFACE
+
+/* Renesas products */
+product RENESAS RX610 0x0053 RX610 RX-Stick
+
+/* 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
+
+/* Reiner-SCT products */
+product REINERSCT CYBERJACK_ECOM 0x0100 e-com cyberJack
+
+/* Roland products */
+product ROLAND UA100 0x0000 UA-100 Audio I/F
+product ROLAND UM4 0x0002 UM-4 MIDI I/F
+product ROLAND SC8850 0x0003 SC-8850 MIDI Synth
+product ROLAND U8 0x0004 U-8 Audio I/F
+product ROLAND UM2 0x0005 UM-2 MIDI I/F
+product ROLAND SC8820 0x0007 SC-8820 MIDI Synth
+product ROLAND PC300 0x0008 PC-300 MIDI Keyboard
+product ROLAND UM1 0x0009 UM-1 MIDI I/F
+product ROLAND SK500 0x000b SK-500 MIDI Keyboard
+product ROLAND SCD70 0x000c SC-D70 MIDI Synth
+product ROLAND UM880N 0x0014 EDIROL UM-880 MIDI I/F (native)
+product ROLAND UM880G 0x0015 EDIROL UM-880 MIDI I/F (generic)
+product ROLAND SD90 0x0016 SD-90 MIDI Synth
+product ROLAND UM550 0x0023 UM-550 MIDI I/F
+product ROLAND SD20 0x0027 SD-20 MIDI Synth
+product ROLAND SD80 0x0029 SD-80 MIDI Synth
+product ROLAND UA700 0x002b UA-700 Audio I/F
+product ROLAND PCR300 0x0033 EDIROL PCR-300 MIDI I/F
+product ROLAND UA25EX_AD 0x00e6 EDIROL UA-25EX (Advanced Driver)
+product ROLAND UA25EX_CC 0x00e7 EDIROL UA-25EX (Class Compliant)
+
+/* Rockfire products */
+product ROCKFIRE GAMEPAD 0x2033 gamepad 203USB
+
+/* RATOC Systems products */
+product RATOC REXUSB60 0xb000 REX-USB60
+product RATOC REXUSB60F 0xb020 REX-USB60F
+
+/* RT system products */
+product RTSYSTEMS CT29B 0x9e54 FTDI compatible adapter
+product RTSYSTEMS SERIAL_VX7 0x9e52 FTDI compatible adapter
+
+/* 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 WIS09ABGN 0x2018 WIS09ABGN Wireless LAN adapter
+product SAMSUNG ML6060 0x3008 ML-6060 laser printer
+product SAMSUNG YP_U2 0x5050 YP-U2 MP3 Player
+product SAMSUNG YP_U4 0x5092 YP-U4 MP3 Player
+product SAMSUNG I500 0x6601 I500 Palm USB Phone
+product SAMSUNG I330 0x8001 I330 phone cradle
+product SAMSUNG2 RT2870_1 0x2018 RT2870
+
+/* 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_128 0x7100 Cruzer Mini 128MB
+product SANDISK SDCZ2_256 0x7104 Cruzer Mini 256MB
+product SANDISK SDCZ4_128 0x7112 Cruzer Micro 128MB
+product SANDISK SDCZ4_256 0x7113 Cruzer Micro 256MB
+product SANDISK SDCZ48_32 0x5581 Ultra 32GB
+product SANDISK IMAGEMATE_SDDR289 0xb6ba ImageMate SDDR-289
+
+/* Sanwa Electric Instrument Co., Ltd. products */
+product SANWA KB_USB2 0x0701 KB-USB2 multimeter cable
+
+/* 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
+
+/* Schweitzer Engineering Laboratories products */
+product SEL C662 0x0001 C662 Cable
+
+/* Sealevel products */
+product SEALEVEL 2101 0x2101 FTDI compatible adapter
+product SEALEVEL 2102 0x2102 FTDI compatible adapter
+product SEALEVEL 2103 0x2103 FTDI compatible adapter
+product SEALEVEL 2104 0x2104 FTDI compatible adapter
+product SEALEVEL 2106 0x9020 FTDI compatible adapter
+product SEALEVEL 2201_1 0x2211 FTDI compatible adapter
+product SEALEVEL 2201_2 0x2221 FTDI compatible adapter
+product SEALEVEL 2202_1 0x2212 FTDI compatible adapter
+product SEALEVEL 2202_2 0x2222 FTDI compatible adapter
+product SEALEVEL 2203_1 0x2213 FTDI compatible adapter
+product SEALEVEL 2203_2 0x2223 FTDI compatible adapter
+product SEALEVEL 2401_1 0x2411 FTDI compatible adapter
+product SEALEVEL 2401_2 0x2421 FTDI compatible adapter
+product SEALEVEL 2401_3 0x2431 FTDI compatible adapter
+product SEALEVEL 2401_4 0x2441 FTDI compatible adapter
+product SEALEVEL 2402_1 0x2412 FTDI compatible adapter
+product SEALEVEL 2402_2 0x2422 FTDI compatible adapter
+product SEALEVEL 2402_3 0x2432 FTDI compatible adapter
+product SEALEVEL 2402_4 0x2442 FTDI compatible adapter
+product SEALEVEL 2403_1 0x2413 FTDI compatible adapter
+product SEALEVEL 2403_2 0x2423 FTDI compatible adapter
+product SEALEVEL 2403_3 0x2433 FTDI compatible adapter
+product SEALEVEL 2403_4 0x2443 FTDI compatible adapter
+product SEALEVEL 2801_1 0x2811 FTDI compatible adapter
+product SEALEVEL 2801_2 0x2821 FTDI compatible adapter
+product SEALEVEL 2801_3 0x2831 FTDI compatible adapter
+product SEALEVEL 2801_4 0x2841 FTDI compatible adapter
+product SEALEVEL 2801_5 0x2851 FTDI compatible adapter
+product SEALEVEL 2801_6 0x2861 FTDI compatible adapter
+product SEALEVEL 2801_7 0x2871 FTDI compatible adapter
+product SEALEVEL 2801_8 0x2881 FTDI compatible adapter
+product SEALEVEL 2802_1 0x2812 FTDI compatible adapter
+product SEALEVEL 2802_2 0x2822 FTDI compatible adapter
+product SEALEVEL 2802_3 0x2832 FTDI compatible adapter
+product SEALEVEL 2802_4 0x2842 FTDI compatible adapter
+product SEALEVEL 2802_5 0x2852 FTDI compatible adapter
+product SEALEVEL 2802_6 0x2862 FTDI compatible adapter
+product SEALEVEL 2802_7 0x2872 FTDI compatible adapter
+product SEALEVEL 2802_8 0x2882 FTDI compatible adapter
+product SEALEVEL 2803_1 0x2813 FTDI compatible adapter
+product SEALEVEL 2803_2 0x2823 FTDI compatible adapter
+product SEALEVEL 2803_3 0x2833 FTDI compatible adapter
+product SEALEVEL 2803_4 0x2843 FTDI compatible adapter
+product SEALEVEL 2803_5 0x2853 FTDI compatible adapter
+product SEALEVEL 2803_6 0x2863 FTDI compatible adapter
+product SEALEVEL 2803_7 0x2873 FTDI compatible adapter
+product SEALEVEL 2803_8 0x2883 FTDI compatible adapter
+
+/* Senao products */
+product SENAO EUB1200AC 0x0100 EnGenius EUB1200AC
+product SENAO RT2870_3 0x0605 RT2870
+product SENAO RT2870_4 0x0615 RT2870
+product SENAO NUB8301 0x2000 NUB-8301
+product SENAO RT2870_1 0x9701 RT2870
+product SENAO RT2870_2 0x9702 RT2870
+product SENAO RT3070 0x9703 RT3070
+product SENAO RT3071 0x9705 RT3071
+product SENAO RT3072_1 0x9706 RT3072
+product SENAO RT3072_2 0x9707 RT3072
+product SENAO RT3072_3 0x9708 RT3072
+product SENAO RT3072_4 0x9709 RT3072
+product SENAO RT3072_5 0x9801 RT3072
+product SENAO RTL8192SU_1 0x9603 RTL8192SU
+product SENAO RTL8192SU_2 0x9605 RTL8192SU
+
+/* ShanTou products */
+product SHANTOU ST268 0x0268 ST268
+product SHANTOU DM9601 0x9601 DM 9601
+product SHANTOU ADM8515 0x8515 ADM8515
+
+/* 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
+product SHARP WZERO3ADES 0x91ac Advanced W-ZERO3 ES Smartphone
+product SHARP WILLCOM03 0x9242 WILLCOM03
+
+/* 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
+product SIEMENS3 EF81 0x0005 EF81
+
+/* Sierra Wireless products */
+product SIERRA EM5625 0x0017 EM5625
+product SIERRA MC5720_2 0x0018 MC5720
+product SIERRA MC5725 0x0020 MC5725
+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 EM5725 0x0022 EM5725
+product SIERRA C597 0x0023 Sierra Wireless Compass 597
+product SIERRA MC5727 0x0024 MC5727
+product SIERRA T598 0x0025 T598
+product SIERRA T11 0x0026 T11
+product SIERRA AC402 0x0027 AC402
+product SIERRA MC5728 0x0028 MC5728
+product SIERRA E0029 0x0029 E0029
+product SIERRA AIRCARD580 0x0112 Sierra Wireless AirCard 580
+product SIERRA AC595U 0x0120 Sierra Wireless AirCard 595U
+product SIERRA MC5720 0x0218 MC5720 Wireless Modem
+product SIERRA MINI5725 0x0220 Sierra Wireless miniPCI 5275
+product SIERRA MC5727_2 0x0224 MC5727
+product SIERRA MC8755_2 0x6802 MC8755
+product SIERRA MC8765 0x6803 MC8765
+product SIERRA MC8755 0x6804 MC8755
+product SIERRA MC8765_2 0x6805 MC8765
+product SIERRA MC8755_4 0x6808 MC8755
+product SIERRA MC8765_3 0x6809 MC8765
+product SIERRA AC875U 0x6812 AC875U HSDPA USB Modem
+product SIERRA MC8755_3 0x6813 MC8755 HSDPA
+product SIERRA MC8775_2 0x6815 MC8775
+product SIERRA MC8775 0x6816 MC8775
+product SIERRA AC875 0x6820 Sierra Wireless AirCard 875
+product SIERRA AC875U_2 0x6821 AC875U
+product SIERRA AC875E 0x6822 AC875E
+product SIERRA MC8780 0x6832 MC8780
+product SIERRA MC8781 0x6833 MC8781
+product SIERRA MC8780_2 0x6834 MC8780
+product SIERRA MC8781_2 0x6835 MC8781
+product SIERRA MC8780_3 0x6838 MC8780
+product SIERRA MC8781_3 0x6839 MC8781
+product SIERRA MC8785 0x683A MC8785
+product SIERRA MC8785_2 0x683B MC8785
+product SIERRA MC8790 0x683C MC8790
+product SIERRA MC8791 0x683D MC8791
+product SIERRA MC8792 0x683E MC8792
+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 AC885E 0x6859 AC885E
+product SIERRA AC885E_2 0x685A AC885E
+product SIERRA AC885U 0x6880 Sierra Wireless AirCard 885U
+product SIERRA C888 0x6890 C888
+product SIERRA C22 0x6891 C22
+product SIERRA E6892 0x6892 E6892
+product SIERRA E6893 0x6893 E6893
+product SIERRA MC8700 0x68A3 MC8700
+product SIERRA MC7354 0x68C0 MC7354
+product SIERRA MC7355 0x9041 MC7355
+product SIERRA AC340U 0x9051 Sierra Wireless AirCard 340U
+product SIERRA MC7430 0x9071 Sierra Wireless MC7430 Qualcomm Snapdragon X7 LTE-A
+product SIERRA AC313U 0x68aa Sierra Wireless AirCard 313U
+product SIERRA TRUINSTALL 0x0fff Aircard Tru Installer
+product SIERRA EM7430 0x81cb Sierra Wireless EM7430 Qualcomm Snapdragon X7 LTE-A
+product SIERRA EM7430_2 0x81cc Sierra Wireless EM7430 Qualcomm Snapdragon X7 LTE-A
+product SIERRA EM7455 0x9078 Sierra Wireless EM7455 Qualcomm Snapdragon X7 LTE-A
+product SIERRA EM7455_2 0x9079 Sierra Wireless EM7455 Qualcomm Snapdragon X7 LTE-A
+product SIERRA EM7565 0x9090 Sierra Wireless EM7565 Qualcomm Snapdragon X7 LTE-A
+product SIERRA EM7565_2 0x9091 Sierra Wireless EM7565 Qualcomm Snapdragon X7 LTE-A
+
+/* Sigmatel products */
+product SIGMATEL WBT_3052 0x4200 WBT-3052 IrDA/USB Bridge
+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 DK201 0x0103 FTDI compatible adapter
+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 VSTABI 0x0f91 VStabi Controller
+product SILABS ARKHAM_DS101_M 0x1101 Arkham DS101 Monitor
+product SILABS ARKHAM_DS101_A 0x1601 Arkham DS101 Adapter
+product SILABS BSM7DUSB 0x800a SPORTident BSM7-D USB
+product SILABS POLOLU 0x803b Pololu Serial
+product SILABS CYGNAL_DEBUG 0x8044 Cygnal Debug Adapter
+product SILABS SB_PARAMOUNT_ME 0x8043 Software Bisque Paramount ME
+product SILABS SAEL 0x8053 SA-EL USB
+product SILABS GSM2228 0x8054 Enfora GSM2228 USB
+product SILABS ARGUSISP 0x8066 Argussoft ISP
+product SILABS IMS_USB_RS422 0x806f IMS USB-RS422
+product SILABS CRUMB128 0x807a Crumb128 board
+product SILABS OPTRIS_MSPRO 0x80c4 Optris MSpro LT Thermometer
+product SILABS DEGREE 0x80ca Degree Controls Inc
+product SILABS TRACIENT 0x80dd Tracient RFID
+product SILABS TRAQMATE 0x80ed Track Systems Traqmate
+product SILABS SUUNTO 0x80f6 Suunto Sports Instrument
+product SILABS ARYGON_MIFARE 0x8115 Arygon Mifare RFID reader
+product SILABS BURNSIDE 0x813d Burnside Telecon Deskmobile
+product SILABS TAMSMASTER 0x813f Tams Master Easy Control
+product SILABS WMRBATT 0x814a WMR RIGblaster Plug&Play
+product SILABS WMRRIGBLASTER 0x814a WMR RIGblaster Plug&Play
+product SILABS WMRRIGTALK 0x814b WMR RIGtalk RT1
+product SILABS B_G_H3000 0x8156 B&G H3000 Data Cable
+product SILABS HELICOM 0x815e Helicomm IP-Link 1220-DVM
+product SILABS HAMLINKUSB 0x815f Timewave HamLinkUSB
+product SILABS AVIT_USB_TTL 0x818b AVIT Research USB-TTL
+product SILABS MJS_TOSLINK 0x819f MJS USB-TOSLINK
+product SILABS WAVIT 0x81a6 ThinkOptics WavIt
+product SILABS MULTIPLEX_RC 0x81a9 Multiplex RC adapter
+product SILABS MSD_DASHHAWK 0x81ac MSD DashHawk
+product SILABS INSYS_MODEM 0x81ad INSYS Modem
+product SILABS LIPOWSKY_JTAG 0x81c8 Lipowsky Baby-JTAG
+product SILABS LIPOWSKY_LIN 0x81e2 Lipowsky Baby-LIN
+product SILABS AEROCOMM 0x81e7 Aerocomm Radio
+product SILABS ZEPHYR_BIO 0x81e8 Zephyr Bioharness
+product SILABS EMS_C1007 0x81f2 EMS C1007 HF RFID controller
+product SILABS LIPOWSKY_HARP 0x8218 Lipowsky HARP-1
+product SILABS C2_EDGE_MODEM 0x822b Commander 2 EDGE(GSM) Modem
+product SILABS CYGNAL_GPS 0x826b Cygnal Fasttrax GPS
+product SILABS TELEGESIS_ETRX2 0x8293 Telegesis ETRX2USB
+product SILABS PROCYON_AVS 0x82f9 Procyon AVS
+product SILABS MC35PU 0x8341 MC35pu
+product SILABS CYGNAL 0x8382 Cygnal
+product SILABS AMBER_AMB2560 0x83a8 Amber Wireless AMB2560
+product SILABS DEKTEK_DTAPLUS 0x83d8 DekTec DTA Plus VHF/UHF Booster
+product SILABS KYOCERA_GPS 0x8411 Kyocera GPS
+product SILABS IRZ_SG10 0x8418 IRZ SG-10 GSM/GPRS Modem
+product SILABS BEI_VCP 0x846e BEI USB Sensor (VCP)
+product SILABS BALLUFF_RFID 0x8477 Balluff RFID reader
+product SILABS AC_SERV_IBUS 0x85ea AC-Services IBUS Interface
+product SILABS AC_SERV_CIS 0x85eb AC-Services CIS-IBUS
+product SILABS V_PREON32 0x85f8 Virtenio Preon32
+product SILABS AC_SERV_CAN 0x8664 AC-Services CAN Interface
+product SILABS AC_SERV_OBD 0x8665 AC-Services OBD Interface
+product SILABS MMB_ZIGBEE 0x88a4 MMB Networks ZigBee
+product SILABS INGENI_ZIGBEE 0x88a5 Planet Innovation Ingeni ZigBee
+product SILABS HUBZ 0x8a2a HubZ dual ZigBee and Z-Wave
+product SILABS BV_AV2010_10 0x8b34 Bitron Video AV2010/10 ZigBee USB Stick
+product SILABS CP2102 0xea60 SILABS USB UART
+product SILABS CP210X_2 0xea61 CP210x Serial
+product SILABS CP210X_3 0xea70 CP210x Serial
+product SILABS CP210X_4 0xea80 CP210x Serial
+product SILABS INFINITY_MIC 0xea71 Infinity GPS-MIC-1 Radio Monophone
+product SILABS CP2112 0xea90 CP2112 HID USB-to-SMBus Bridge with GPIO
+product SILABS USBSCOPE50 0xf001 USBscope50
+product SILABS USBWAVE12 0xf002 USBwave12
+product SILABS USBPULSE100 0xf003 USBpulse100
+product SILABS USBCOUNT50 0xf004 USBcount50
+product SILABS2 DCU11CLONE 0xaa26 DCU-11 clone
+product SILABS3 GPRS_MODEM 0xea61 GPRS Modem
+product SILABS4 100EU_MODEM 0xea61 GPRS Modem 100EU
+
+/* 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 RT2870_1 0x0017 RT2870
+product SITECOMEU WL168V1 0x000d WL-168 v1
+product SITECOMEU LN030 0x0021 MCS7830
+product SITECOMEU WL168V4 0x0028 WL-168 v4
+product SITECOMEU RT2870_2 0x002b RT2870
+product SITECOMEU RT2870_3 0x002c RT2870
+product SITECOMEU RT2870_4 0x002d RT2870
+product SITECOMEU RT2770 0x0039 RT2770
+product SITECOMEU RT3070_2 0x003b RT3070
+product SITECOMEU RT3070_3 0x003c RT3070
+product SITECOMEU RT3070_4 0x003d RT3070
+product SITECOMEU RT3070 0x003e RT3070
+product SITECOMEU WL608 0x003f WL-608
+product SITECOMEU RT3071 0x0040 RT3071
+product SITECOMEU RT3072_1 0x0041 RT3072
+product SITECOMEU RT3072_2 0x0042 RT3072
+product SITECOMEU WL353 0x0045 WL-353
+product SITECOMEU RT3072_3 0x0047 RT3072
+product SITECOMEU RT3072_4 0x0048 RT3072
+product SITECOMEU RT3072_5 0x004a RT3072
+product SITECOMEU WL349V1 0x004b WL-349 v1
+product SITECOMEU RT3072_6 0x004d RT3072
+product SITECOMEU WLA1000 0x005b WLA-1000
+product SITECOMEU RT3070_1 0x0051 RT3070
+product SITECOMEU RTL8188CU_1 0x0052 RTL8188CU
+product SITECOMEU RTL8188CU_2 0x005c RTL8188CU
+product SITECOMEU RTL8192CU 0x0061 RTL8192CU
+product SITECOMEU RTL8188S 0x006b RTL8188S
+product SITECOMEU LN032 0x0072 LN-032
+product SITECOMEU WLA7100 0x0074 WLA-7100
+product SITECOMEU LN031 0x0056 LN-031
+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
+
+/* Smart Technologies products */
+product SMART PL2303 0x2303 Serial adapter
+
+/* Smart Modular Technologies products */
+product SMART2 G2MEMKEY 0x1700 G2 Memory Key
+
+/* SmartBridges products */
+product SMARTBRIDGES SMARTLINK 0x0001 SmartLink USB Ethernet
+product SMARTBRIDGES SMARTNIC 0x0003 smartNIC 2 PnP Ethernet
+
+/* Microchip Technology (formerly 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 SMC2 2513HUB 0x2513 USB Hub
+product SMC2 2514HUB 0x2514 USB Hub
+product SMC2 2517HUB 0x2517 USB Hub
+product SMC3 2662WUSB 0xa002 2662W-AR Wireless
+product SMC2 LAN7800_ETH 0x7800 USB/Ethernet
+product SMC2 LAN7801_ETH 0x7801 USB/Ethernet
+product SMC2 LAN7850_ETH 0x7850 USB/Ethernet
+product SMC2 LAN9500_ETH 0x9500 USB/Ethernet
+product SMC2 LAN9505_ETH 0x9505 USB/Ethernet
+product SMC2 LAN9530_ETH 0x9530 USB/Ethernet
+product SMC2 LAN9730_ETH 0x9730 USB/Ethernet
+product SMC2 LAN9500_SAL10 0x9900 USB/Ethernet
+product SMC2 LAN9505_SAL10 0x9901 USB/Ethernet
+product SMC2 LAN9500A_SAL10 0x9902 USB/Ethernet
+product SMC2 LAN9505A_SAL10 0x9903 USB/Ethernet
+product SMC2 LAN9514_SAL10 0x9904 USB/Ethernet
+product SMC2 LAN9500A_HAL 0x9905 USB/Ethernet
+product SMC2 LAN9505A_HAL 0x9906 USB/Ethernet
+product SMC2 LAN9500_ETH_2 0x9907 USB/Ethernet
+product SMC2 LAN9500A_ETH_2 0x9908 USB/Ethernet
+product SMC2 LAN9514_ETH_2 0x9909 USB/Ethernet
+product SMC2 LAN9500A_ETH 0x9e00 USB/Ethernet
+product SMC2 LAN9505A_ETH 0x9e01 USB/Ethernet
+product SMC2 LAN89530_ETH 0x9e08 USB/Ethernet
+product SMC2 LAN9514_ETH 0xec00 USB/Ethernet
+
+/* 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
+product SONY QN3 0x0437 Sony QN3 CMD-Jxx phone cable
+
+/* Sony Ericsson products */
+product SONYERICSSON DCU10 0x0528 DCU-10 Phone Data Cable
+product SONYERICSSON DATAPILOT 0x2003 Datapilot Phone 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
+product SPARKLAN RT2870_1 0x0006 RT2870
+product SPARKLAN RT3070 0x0010 RT3070
+
+/* Soundgraph products */
+product SOUNDGRAPH IMON_VFD 0x0044 Antec Veris Elite VFD Panel, Knob, and Remote
+product SOUNDGRAPH SSTONE_LC16 0xffdc Silverstone LC16 VFD Panel, Knob, and Remote
+
+/* Speed Dragon Multimedia products */
+product SPEEDDRAGON MS3303H 0x110b MS3303H Serial
+
+/* 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
+product STELERA E1003 0x1003 3G modem
+product STELERA E1004 0x1004 3G modem
+product STELERA E1005 0x1005 3G modem
+product STELERA E1006 0x1006 3G modem
+product STELERA E1007 0x1007 3G modem
+product STELERA E1008 0x1008 3G modem
+product STELERA E1009 0x1009 3G modem
+product STELERA E100A 0x100a 3G modem
+product STELERA E100B 0x100b 3G modem
+product STELERA E100C 0x100c 3G modem
+product STELERA E100D 0x100d 3G modem
+product STELERA E100E 0x100e 3G modem
+product STELERA E100F 0x100f 3G modem
+product STELERA E1010 0x1010 3G modem
+product STELERA E1011 0x1011 3G modem
+product STELERA E1012 0x1012 3G modem
+
+/* STMicroelectronics products */
+product STMICRO BIOCPU 0x2016 Biometric Coprocessor
+product STMICRO COMMUNICATOR 0x7554 USB Communicator
+product STMICRO ST72682 0xfada USB 2.0 Flash drive controller
+
+/* 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_TYPE_6 0x0005 Type 6 USB keyboard
+product SUN KEYBOARD_TYPE_7 0x00a2 Type 7 USB keyboard
+/* XXX The above is a North American PC style keyboard possibly */
+product SUN MOUSE 0x0100 Type 6 USB mouse
+product SUN KBD_HUB 0x100e Kbd Hub
+
+/* Sunplus Innovation Technology Inc. products */
+product SUNPLUS USBMOUSE 0x0007 USB Optical Mouse
+
+/* Super Top products */
+product SUPERTOP IDE 0x6600 USB-IDE
+product SUPERTOP FLASHDRIVE 0x121c extrememory Snippy
+
+/* Syntech products */
+product SYNTECH CPT8001C 0x0001 CPT-8001C Barcode scanner
+product SYNTECH CYPHERLAB100 0x1000 CipherLab USB Barcode Scanner
+
+/* Teclast products */
+product TECLAST TLC300 0x3203 USB Media Player
+
+/* Testo products */
+product TESTO USB_INTERFACE 0x0001 FTDI compatible adapter
+
+/* TexTech products */
+product TEXTECH U2M_1 0x0101 Textech USB MIDI cable
+product TEXTECH U2M_2 0x1806 Textech USB MIDI cable
+
+/* The Mobility Lab products */
+product TML USB_SERIAL 0x0064 FTDI compatible adapter
+
+/* Thurlby Thandar Instrument products */
+product TTI QL355P 0x03e8 FTDI compatible adapter
+
+/* 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 EP9001G2A 0x11f2 EP-9001-G rev 2A
+product SURECOM RT2570 0x11f3 RT2570
+product SURECOM RT2573 0x31f3 RT2573
+
+/* Sweex products */
+product SWEEX ZD1211 0x1809 ZD1211
+product SWEEX2 LW153 0x0153 LW153
+product SWEEX2 LW154 0x0154 LW154
+product SWEEX2 LW303 0x0302 LW303
+product SWEEX2 LW313 0x0313 LW313
+
+/* 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)
+
+/* TCTMobile products */
+product TCTMOBILE X060S 0x0000 X060S 3G modem
+product TCTMOBILE X080S 0xf000 X080S 3G modem
+
+/* 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
+
+/* Telit products */
+product TELIT UC864E 0x1003 UC864E 3G modem
+product TELIT UC864G 0x1004 UC864G 3G modem
+
+/* Ten X Technology, Inc. */
+product TENX UAUDIO0 0xf211 USB audio headset
+
+/* Tenda products */
+product TENDA2 U12 0x0012 Tenda U12
+
+/* ThingM products */
+product THINGM BLINK1 0x01ed USB notification light
+
+/* Texas Intel products */
+product TI UTUSB41 0x1446 UT-USB41 hub
+product TI TUSB2046 0x2046 TUSB2046 hub
+product TI USB3410 0x3410 TI USB 3410 Modem
+product TI USB5052 0x5052 TI USB 5250 Modem
+product TI FRI2 0x5053 TI Fish River Island II
+product TI USB5052_EEPROM 0x505a TI USB 5250 Modem w/Eeprom
+product TI USB5052_FW 0x505f TI USB 5250 Modme w/Firmware running
+product TI USB5152 0x5152 TI USB 5152 Modem
+product TI EZ430 0xf430 TI ex430 development tool
+
+/* Thrustmaster products */
+product THRUST FUSION_PAD 0xa0a3 Fusion Digital Gamepad
+
+/* TLayTech products */
+product TLAYTECH TEU800 0x1682 TEU800 3G modem
+
+/* Topre Corporation products */
+product TOPRE HHKB 0x0100 HHKB Professional
+
+/* Toshiba Corporation products */
+product TOSHIBA POCKETPC_E740 0x0706 PocketPC e740
+product TOSHIBA RT3070 0x0a07 RT3070
+product TOSHIBA G450 0x0d45 G450 modem
+product TOSHIBA HSDPA 0x1302 G450 modem
+product TOSHIBA TRANSMEMORY 0x6545 USB ThumbDrive
+
+/* TP-Link products */
+product TPLINK RTL8192CU 0x0100 RTL8192CU
+product TPLINK T4U 0x0101 Archer T4U
+product TPLINK WN821NV5 0x0107 TL-WN821N v5
+product TPLINK WN822NV4 0x0108 TL-WN822N v4
+product TPLINK WN823NV2 0x0109 TL-WN823N v2
+product TPLINK WN722NV2 0x010c TL-WN722N v2
+product TPLINK WN727NV5 0x0111 TL-WN727N v5
+product TPLINK T4UV2 0x010d Archer T4U ver 2
+product TPLINK T4UHV1 0x0103 Archer T4UH ver 1
+product TPLINK T4UHV2 0x010e Archer T4UH ver 2
+product TPLINK T2UNANO 0x011e Archer T2U Nano
+product TPLINK T2UV3 0x011f Archer T2U ver 3
+product TPLINK T2UPLUS 0x0120 Archer T2U Plus
+product TPLINK RTL8153 0x0601 RTL8153 USB 10/100/1000 LAN
+
+/* Trek Technology products */
+product TREK THUMBDRIVE 0x1111 ThumbDrive
+product TREK MEMKEY 0x8888 IBM USB Memory Key
+product TREK THUMBDRIVE_8MB 0x9988 ThumbDrive_8MB
+
+/* TRENDnet products */
+product TRENDNET RTL8192CU 0x624d RTL8192CU
+product TRENDNET TEW646UBH 0x646b TEW-646UBH
+product TRENDNET RTL8188CU 0x648b RTL8188CU
+product TRENDNET TEW805UB 0x805b TEW-805UB
+
+/* Tripp-Lite products */
+product TRIPPLITE U209 0x2008 Serial
+product TRIPPLITE2 OMNIVS1000 0x0001 OMNIVS1000, SMART550USB
+product TRIPPLITE2 AVR550U 0x1003 AVR550U
+product TRIPPLITE2 AVR750U 0x1007 AVR750U
+product TRIPPLITE2 ECO550UPS 0x1008 ECO550UPS
+product TRIPPLITE2 T750_INTL 0x1f06 T750 INTL
+product TRIPPLITE2 RT_2200_INTL 0x1f0a R/T 2200 INTL
+product TRIPPLITE2 OMNI1000LCD 0x2005 OMNI1000LCD
+product TRIPPLITE2 OMNI900LCD 0x2007 OMNI900LCD
+product TRIPPLITE2 SMART_2200RMXL2U 0x3012 smart2200RMXL2U
+product TRIPPLITE2 UPS_3014 0x3014 Unknown UPS
+product TRIPPLITE2 SU1500RTXL2UA 0x4001 SmartOnline SU1500RTXL2UA
+product TRIPPLITE2 SU6000RT4U 0x4002 SmartOnline SU6000RT4U
+product TRIPPLITE2 SU1500RTXL2UA_2 0x4003 SmartOnline SU1500RTXL2UA
+
+/* 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)
+
+/* u-blox products */
+product UBLOX XPLR_M9 0x0501 M9 GNSS Explorer Kit
+
+/* 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 RT2870_1 0x300e RT2870
+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
+
+/* Unknown products */
+product UNKNOWN4 NF_RIC 0x0001 FTDI compatible adapter
+
+/* USI products */
+product USI MC60 0x10c5 MC60 Serial
+
+/* U.S. Robotics products */
+product USR USR5422 0x0118 USR5422 WLAN
+product USR USR5423 0x0121 USR5423 WLAN
+
+/* VIA Technologies products */
+product VIA USB2IDEBRIDGE 0x6204 USB 2.0 IDE Bridge
+
+/* VIA Labs */
+product VIALABS USB30SATABRIDGE 0x0700 USB 3.0 SATA Bridge
+product VIALABS VL701 0x0701 VL701 USB 3.0 SATA Bridge
+
+/* Vaisala products */
+product VAISALA CABLE 0x0200 USB Interface cable
+
+/* Vertex products */
+product VERTEX VW110L 0x0100 Vertex VW110L modem
+
+/* 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
+
+/* WAGO Kontakttechnik GmbH products */
+product WAGO SERVICECABLE 0x07a6 USB Service Cable 750-923
+
+/* WaveSense products */
+product WAVESENSE JAZZ 0xaaaa Jazz blood glucose meter
+
+/* WCH products */
+product WCH CH341SER 0x5523 CH341/CH340 USB-Serial Bridge
+product WCH2 CH341SER_2 0x5523 CH341/CH340 USB-Serial Bridge
+product WCH2 CH343SER 0x55d3 CH343 USB Serial
+product WCH2 CH9102SER 0x55d4 CH9102 USB Serial
+product WCH2 CH341SER_3 0x7522 CH341/CH340 USB-Serial Bridge
+product WCH2 CH341SER 0x7523 CH341/CH340 USB-Serial Bridge
+product WCH2 U2M 0x752d CH345 USB2.0-MIDI
+
+/* West Mountain Radio products */
+product WESTMOUNTAIN RIGBLASTER_ADVANTAGE 0x0003 RIGblaster Advantage
+
+/* 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
+product WESTERN MYPASSPORT_00 0x0704 MyPassport External HDD
+product WESTERN MYPASSPORT_11 0x0741 MyPassport External HDD
+product WESTERN MYPASSPORT_01 0x0746 MyPassport External HDD
+product WESTERN MYPASSPORT_02 0x0748 MyPassport External HDD
+product WESTERN MYPASSPORT_03 0x074A MyPassport External HDD
+product WESTERN MYPASSPORT_04 0x074C MyPassport External HDD
+product WESTERN MYPASSPORT_05 0x074E MyPassport External HDD
+product WESTERN MYPASSPORT_06 0x07A6 MyPassport External HDD
+product WESTERN MYPASSPORT_07 0x07A8 MyPassport External HDD
+product WESTERN MYPASSPORT_08 0x07AA MyPassport External HDD
+product WESTERN MYPASSPORT_09 0x07AC MyPassport External HDD
+product WESTERN MYPASSPORT_10 0x07AE MyPassport External HDD
+product WESTERN MYPASSPORTES_00 0x070A MyPassport Essential External HDD
+product WESTERN MYPASSPORTES_01 0x071A MyPassport Essential External HDD
+product WESTERN MYPASSPORTES_02 0x0730 MyPassport Essential External HDD
+product WESTERN MYPASSPORTES_03 0x0732 MyPassport Essential External HDD
+product WESTERN MYPASSPORTES_04 0x0740 MyPassport Essential External HDD
+product WESTERN MYPASSPORTES_05 0x0742 MyPassport Essential External HDD
+product WESTERN MYPASSPORTES_06 0x0750 MyPassport Essential External HDD
+product WESTERN MYPASSPORTES_07 0x0752 MyPassport Essential External HDD
+product WESTERN MYPASSPORTES_08 0x07A0 MyPassport Essential External HDD
+product WESTERN MYPASSPORTES_09 0x07A2 MyPassport Essential External HDD
+product WESTERN MYPASSPORTUL_00 0x0743 MyPassport Ultra External HDD
+
+/* WeTelecom products */
+product WETELECOM WM_D200 0x6801 WM-D200
+
+/* WIENER Plein & Baus GmbH products */
+product WIENERPLEINBAUS PL512 0x0010 PL512 PSU
+product WIENERPLEINBAUS RCM 0x0011 RCM Remote Control
+product WIENERPLEINBAUS MPOD 0x0012 MPOD PSU
+product WIENERPLEINBAUS CML 0x0015 CML Data Logger
+
+/* Windbond Electronics */
+product WINBOND CDC 0x5011 CDC serial device
+product WINBOND UH104 0x5518 4-port USB Hub
+
+/* WinMaxGroup products */
+product WINMAXGROUP FLASH64MC 0x6660 USB Flash Disk 64M-C
+
+/* Wistron NeWeb products */
+product WISTRONNEWEB WNC0600 0x0326 WNC-0600USB
+product WISTRONNEWEB UR045G 0x0427 PrismGT USB 2.0 WLAN
+product WISTRONNEWEB UR055G 0x0711 UR055G
+product WISTRONNEWEB O8494 0x0804 ORiNOCO 802.11n
+product WISTRONNEWEB AR5523_1 0x0826 AR5523
+product WISTRONNEWEB AR5523_1_NF 0x0827 AR5523 (no firmware)
+product WISTRONNEWEB AR5523_2 0x0828 AR5523
+product WISTRONNEWEB AR5523_2_ALT 0x082a AR5523
+product WISTRONNEWEB AR5523_2_NF 0x0829 AR5523 (no firmware)
+
+/* Xerox products */
+product XEROX WCM15 0xffef WorkCenter M15
+
+/* Xiaomi products */
+product XIAOMI MT7601U 0x4106 MT7601U
+
+/* 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
+
+/* Yamaha products */
+product YAMAHA UX256 0x1000 UX256 MIDI I/F
+product YAMAHA MU1000 0x1001 MU1000 MIDI Synth.
+product YAMAHA MU2000 0x1002 MU2000 MIDI Synth.
+product YAMAHA MU500 0x1003 MU500 MIDI Synth.
+product YAMAHA UW500 0x1004 UW500 USB Audio I/F
+product YAMAHA MOTIF6 0x1005 MOTIF6 MIDI Synth. Workstation
+product YAMAHA MOTIF7 0x1006 MOTIF7 MIDI Synth. Workstation
+product YAMAHA MOTIF8 0x1007 MOTIF8 MIDI Synth. Workstation
+product YAMAHA UX96 0x1008 UX96 MIDI I/F
+product YAMAHA UX16 0x1009 UX16 MIDI I/F
+product YAMAHA S08 0x100e S08 MIDI Keyboard
+product YAMAHA CLP150 0x100f CLP-150 digital piano
+product YAMAHA CLP170 0x1010 CLP-170 digital piano
+product YAMAHA RPU200 0x3104 RP-U200
+product YAMAHA RTA54I 0x4000 NetVolante RTA54i Broadband&ISDN Router
+product YAMAHA RTW65B 0x4001 NetVolante RTW65b Broadband Wireless Router
+product YAMAHA RTW65I 0x4002 NetVolante RTW65i Broadband&ISDN Wireless Router
+product YAMAHA RTA55I 0x4004 NetVolante RTA55i Broadband VoIP Router
+
+/* Yano products */
+product YANO U640MO 0x0101 U640MO-03
+product YANO FW800HD 0x05fc METALWEAR-HDD
+
+/* Y.C. Cable products */
+product YCCABLE PL2303 0x0fba PL2303 Serial
+
+/* Y-E Data products */
+product YEDATA FLASHBUSTERU 0x0000 Flashbuster-U
+
+/* 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 XM142 0x0015 XM-142
+product ZCOM ZD1211B 0x001a ZD1211B
+product ZCOM RT2870_1 0x0022 RT2870
+product ZCOM UB81 0x0023 UB81
+product ZCOM RT2870_2 0x0025 RT2870
+product ZCOM UB82 0x0026 UB82
+
+/* Zeevo, Inc. products */
+product ZEEVO BLUETOOTH 0x07d0 BT-500 Bluetooth USB Adapter
+
+/* Zinwell products */
+product ZINWELL RT2570 0x0260 RT2570
+product ZINWELL RT2870_1 0x0280 RT2870
+product ZINWELL RT2870_2 0x0282 RT2870
+product ZINWELL RT3072_1 0x0283 RT3072
+product ZINWELL RT3072_2 0x0284 RT3072
+product ZINWELL RT3070 0x5257 RT3070
+
+/* Zoom Telephonics, Inc. products */
+product ZOOM 2986L 0x9700 2986L Fax modem
+product ZOOM 3095 0x3095 3095 USB Fax modem
+
+/* Zoran Microelectronics products */
+product ZORAN EX20DSC 0x4343 Digital Camera EX-20 DSC
+
+/* ZTE products */
+product ZTE MF622 0x0001 MF622 modem
+product ZTE MF628 0x0015 MF628 modem
+product ZTE MF626 0x0031 MF626 modem
+product ZTE MF820D_INSTALLER 0x0166 MF820D CD
+product ZTE MF820D 0x0167 MF820D modem
+product ZTE INSTALLER 0x2000 UMTS CD
+product ZTE MC2718 0xffe8 MC2718 modem
+product ZTE AC8700 0xfffe CDMA 1xEVDO USB modem
+
+/* Zydas Technology Corporation products */
+product ZYDAS ZD1201 0x1201 ZD1201
+product ZYDAS ZD1211 0x1211 ZD1211 WLAN abg
+product ZYDAS ZD1211B 0x1215 ZD1211B
+product ZYDAS ZD1221 0x1221 ZD1221
+product ZYDAS ALL0298 0xa211 ALL0298
+product ZYDAS ZD1211B_2 0xb215 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 G270S 0x340c G-270S
+product ZYXEL G220V2 0x340f G-220 v2
+product ZYXEL G202 0x3410 G-202
+product ZYXEL RT2573 0x3415 RT2573
+product ZYXEL RT2870_1 0x3416 RT2870
+product ZYXEL NWD271N 0x3417 NWD-271N
+product ZYXEL NWD211AN 0x3418 NWD-211AN
+product ZYXEL RT2870_2 0x341a RT2870
+product ZYXEL RT3070 0x341e NWD2105
+product ZYXEL RTL8192CU 0x341f RTL8192CU
+product ZYXEL NWD2705 0x3421 NWD2705
+product ZYXEL NWD6605 0x3426 ND6605
+product ZYXEL PRESTIGE 0x401a Prestige
diff --git a/sys/dev/usb/usbdi.h b/sys/dev/usb/usbdi.h
new file mode 100644
index 000000000000..08d130aa2868
--- /dev/null
+++ b/sys/dev/usb/usbdi.h
@@ -0,0 +1,718 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2009 Andrew Thompson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _USB_USBDI_H_
+#define _USB_USBDI_H_
+
+struct usb_fifo;
+struct usb_xfer;
+struct usb_device;
+struct usb_attach_arg;
+struct usb_interface;
+struct usb_endpoint;
+struct usb_page_cache;
+struct usb_page_search;
+struct usb_process;
+struct usb_proc_msg;
+struct usb_mbuf;
+struct usb_fs_privdata;
+struct mbuf;
+
+typedef enum { /* keep in sync with usb_errstr_table */
+ USB_ERR_NORMAL_COMPLETION = 0,
+ USB_ERR_PENDING_REQUESTS, /* 1 */
+ USB_ERR_NOT_STARTED, /* 2 */
+ USB_ERR_INVAL, /* 3 */
+ USB_ERR_NOMEM, /* 4 */
+ USB_ERR_CANCELLED, /* 5 */
+ USB_ERR_BAD_ADDRESS, /* 6 */
+ USB_ERR_BAD_BUFSIZE, /* 7 */
+ USB_ERR_BAD_FLAG, /* 8 */
+ USB_ERR_NO_CALLBACK, /* 9 */
+ USB_ERR_IN_USE, /* 10 */
+ USB_ERR_NO_ADDR, /* 11 */
+ USB_ERR_NO_PIPE, /* 12 */
+ USB_ERR_ZERO_NFRAMES, /* 13 */
+ USB_ERR_ZERO_MAXP, /* 14 */
+ USB_ERR_SET_ADDR_FAILED, /* 15 */
+ USB_ERR_NO_POWER, /* 16 */
+ USB_ERR_TOO_DEEP, /* 17 */
+ USB_ERR_IOERROR, /* 18 */
+ USB_ERR_NOT_CONFIGURED, /* 19 */
+ USB_ERR_TIMEOUT, /* 20 */
+ USB_ERR_SHORT_XFER, /* 21 */
+ USB_ERR_STALLED, /* 22 */
+ USB_ERR_INTERRUPTED, /* 23 */
+ USB_ERR_DMA_LOAD_FAILED, /* 24 */
+ USB_ERR_BAD_CONTEXT, /* 25 */
+ USB_ERR_NO_ROOT_HUB, /* 26 */
+ USB_ERR_NO_INTR_THREAD, /* 27 */
+ USB_ERR_NOT_LOCKED, /* 28 */
+ USB_ERR_MAX
+} usb_error_t;
+
+/*
+ * Flags for transfers
+ */
+#define USB_FORCE_SHORT_XFER 0x0001 /* force a short transmit last */
+#define USB_SHORT_XFER_OK 0x0004 /* allow short reads */
+#define USB_DELAY_STATUS_STAGE 0x0010 /* insert delay before STATUS stage */
+#define USB_USER_DATA_PTR 0x0020 /* internal flag */
+#define USB_MULTI_SHORT_OK 0x0040 /* allow multiple short frames */
+#define USB_MANUAL_STATUS 0x0080 /* manual ctrl status */
+
+#define USB_NO_TIMEOUT 0
+#define USB_DEFAULT_TIMEOUT 5000 /* 5000 ms = 5 seconds */
+
+#if defined(_KERNEL) || defined(_STANDALONE)
+/* typedefs */
+
+typedef void (usb_callback_t)(struct usb_xfer *, usb_error_t);
+typedef void (usb_proc_callback_t)(struct usb_proc_msg *);
+typedef usb_error_t (usb_handle_req_t)(struct usb_device *,
+ struct usb_device_request *, const void **, uint16_t *);
+
+typedef int (usb_fifo_open_t)(struct usb_fifo *fifo, int fflags);
+typedef void (usb_fifo_close_t)(struct usb_fifo *fifo, int fflags);
+typedef int (usb_fifo_ioctl_t)(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags);
+typedef void (usb_fifo_cmd_t)(struct usb_fifo *fifo);
+typedef void (usb_fifo_filter_t)(struct usb_fifo *fifo, struct usb_mbuf *m);
+
+/* USB events */
+#ifndef USB_GLOBAL_INCLUDE_FILE
+#include <sys/_eventhandler.h>
+#endif
+typedef void (*usb_dev_configured_t)(void *, struct usb_device *,
+ struct usb_attach_arg *);
+EVENTHANDLER_DECLARE(usb_dev_configured, usb_dev_configured_t);
+
+/*
+ * The following macros are used used to convert milliseconds into
+ * HZ. We use 1024 instead of 1000 milliseconds per second to save a
+ * full division.
+ */
+#define USB_MS_HZ 1024
+
+#define USB_MS_TO_TICKS(ms) \
+ (((uint32_t)((((uint32_t)(ms)) * ((uint32_t)(hz))) + USB_MS_HZ - 1)) / USB_MS_HZ)
+
+/*
+ * Common queue structure for USB transfers.
+ */
+struct usb_xfer_queue {
+ TAILQ_HEAD(, usb_xfer) head;
+ struct usb_xfer *curr; /* current USB transfer processed */
+ void (*command) (struct usb_xfer_queue *pq);
+ uint8_t recurse_1:1;
+ uint8_t recurse_2:1;
+ uint8_t recurse_3:1;
+ uint8_t reserved:5;
+};
+
+/*
+ * The following structure defines an USB endpoint
+ * USB endpoint.
+ */
+struct usb_endpoint {
+ /* queue of USB transfers */
+ struct usb_xfer_queue endpoint_q[USB_MAX_EP_STREAMS];
+
+ struct usb_endpoint_descriptor *edesc;
+ struct usb_endpoint_ss_comp_descriptor *ecomp;
+ const struct usb_pipe_methods *methods; /* set by HC driver */
+
+ uint16_t isoc_next;
+
+ uint8_t toggle_next:1; /* next data toggle value */
+ uint8_t is_stalled:1; /* set if endpoint is stalled */
+ uint8_t is_synced:1; /* set if we a synchronised */
+ uint8_t unused:5;
+ uint8_t iface_index; /* not used by "default endpoint" */
+
+ uint8_t refcount_alloc; /* allocation refcount */
+ uint8_t refcount_bw; /* bandwidth refcount */
+#define USB_EP_REF_MAX 0x3f
+
+ /* High-Speed resource allocation (valid if "refcount_bw" > 0) */
+
+ uint8_t usb_smask; /* USB start mask */
+ uint8_t usb_cmask; /* USB complete mask */
+ uint8_t usb_uframe; /* USB microframe */
+
+ /* USB endpoint mode, see USB_EP_MODE_XXX */
+
+ uint8_t ep_mode;
+};
+
+/*
+ * The following structure defines an USB interface.
+ */
+struct usb_interface {
+ struct usb_interface_descriptor *idesc;
+ device_t subdev;
+ /* Total number of alternate settings, from 1 to 256 */
+ uint16_t num_altsetting;
+ /* Current alternate interface index, from 0 to 255 */
+ uint8_t alt_index;
+ uint8_t parent_iface_index;
+
+ /* Linux compat */
+ struct usb_host_interface *altsetting;
+ struct usb_host_interface *cur_altsetting;
+ struct usb_device *linux_udev;
+ void *bsd_priv_sc; /* device specific information */
+ char *pnpinfo; /* additional PnP-info for this interface */
+ uint8_t bsd_iface_index;
+};
+
+/*
+ * The following structure defines a set of USB transfer flags.
+ */
+struct usb_xfer_flags {
+ uint8_t force_short_xfer:1; /* force a short transmit transfer
+ * last */
+ uint8_t short_xfer_ok:1; /* allow short receive transfers */
+ uint8_t short_frames_ok:1; /* allow short frames */
+ uint8_t pipe_bof:1; /* block pipe on failure */
+ uint8_t proxy_buffer:1; /* makes buffer size a factor of
+ * "max_frame_size" */
+ uint8_t ext_buffer:1; /* uses external DMA buffer */
+ uint8_t manual_status:1; /* non automatic status stage on
+ * control transfers */
+ uint8_t no_pipe_ok:1; /* set if "USB_ERR_NO_PIPE" error can
+ * be ignored */
+ uint8_t stall_pipe:1; /* set if the endpoint belonging to
+ * this USB transfer should be stalled
+ * before starting this transfer! */
+ uint8_t pre_scale_frames:1; /* "usb_config->frames" is
+ * assumed to give the
+ * buffering time in
+ * milliseconds and is
+ * converted into the nearest
+ * number of frames when the
+ * USB transfer is setup. This
+ * option only has effect for
+ * ISOCHRONOUS transfers.
+ */
+ uint8_t send_zlp:1; /* send a zero length packet first */
+};
+
+/*
+ * The following structure define an USB configuration, that basically
+ * is used when setting up an USB transfer.
+ */
+struct usb_config {
+ usb_callback_t *callback; /* USB transfer callback */
+ usb_frlength_t bufsize; /* total pipe buffer size in bytes */
+ usb_frcount_t frames; /* maximum number of USB frames */
+ usb_timeout_t interval; /* interval in milliseconds */
+#define USB_DEFAULT_INTERVAL 0
+ usb_timeout_t timeout; /* transfer timeout in milliseconds */
+ struct usb_xfer_flags flags; /* transfer flags */
+ usb_stream_t stream_id; /* USB3.0 specific */
+ enum usb_hc_mode usb_mode; /* host or device mode */
+ uint8_t type; /* pipe type */
+ uint8_t endpoint; /* pipe number */
+ uint8_t direction; /* pipe direction */
+ uint8_t ep_index; /* pipe index match to use */
+ uint8_t if_index; /* "ifaces" index to use */
+};
+
+/*
+ * Use these macro when defining USB device ID arrays if you want to
+ * have your driver module automatically loaded in host, device or
+ * both modes respectively:
+ */
+#if USB_HAVE_ID_SECTION
+#define STRUCT_USB_HOST_ID \
+ struct usb_device_id __section("usb_host_id")
+#define STRUCT_USB_DEVICE_ID \
+ struct usb_device_id __section("usb_device_id")
+#define STRUCT_USB_DUAL_ID \
+ struct usb_device_id __section("usb_dual_id")
+#else
+#define STRUCT_USB_HOST_ID \
+ struct usb_device_id
+#define STRUCT_USB_DEVICE_ID \
+ struct usb_device_id
+#define STRUCT_USB_DUAL_ID \
+ struct usb_device_id
+#endif /* USB_HAVE_ID_SECTION */
+
+/*
+ * The following structure is used when looking up an USB driver for
+ * an USB device. It is inspired by the Linux structure called
+ * "usb_device_id".
+ */
+struct usb_device_id {
+ /* Select which fields to match against */
+#if BYTE_ORDER == LITTLE_ENDIAN
+ uint16_t
+ match_flag_vendor:1,
+ match_flag_product:1,
+ match_flag_dev_lo:1,
+ match_flag_dev_hi:1,
+
+ match_flag_dev_class:1,
+ match_flag_dev_subclass:1,
+ match_flag_dev_protocol:1,
+ match_flag_int_class:1,
+
+ match_flag_int_subclass:1,
+ match_flag_int_protocol:1,
+ match_flag_unused:6;
+#else
+ uint16_t
+ match_flag_unused:6,
+ match_flag_int_protocol:1,
+ match_flag_int_subclass:1,
+
+ match_flag_int_class:1,
+ match_flag_dev_protocol:1,
+ match_flag_dev_subclass:1,
+ match_flag_dev_class:1,
+
+ match_flag_dev_hi:1,
+ match_flag_dev_lo:1,
+ match_flag_product:1,
+ match_flag_vendor:1;
+#endif
+
+ /* Used for product specific matches; the BCD range is inclusive */
+ uint16_t idVendor;
+ uint16_t idProduct;
+ uint16_t bcdDevice_lo;
+ uint16_t bcdDevice_hi;
+
+ /* Used for device class matches */
+ uint8_t bDeviceClass;
+ uint8_t bDeviceSubClass;
+ uint8_t bDeviceProtocol;
+
+ /* Used for interface class matches */
+ uint8_t bInterfaceClass;
+ uint8_t bInterfaceSubClass;
+ uint8_t bInterfaceProtocol;
+
+#if USB_HAVE_COMPAT_LINUX
+ /* which fields to match against */
+ uint16_t match_flags;
+#define USB_DEVICE_ID_MATCH_VENDOR 0x0001
+#define USB_DEVICE_ID_MATCH_PRODUCT 0x0002
+#define USB_DEVICE_ID_MATCH_DEV_LO 0x0004
+#define USB_DEVICE_ID_MATCH_DEV_HI 0x0008
+#define USB_DEVICE_ID_MATCH_DEV_CLASS 0x0010
+#define USB_DEVICE_ID_MATCH_DEV_SUBCLASS 0x0020
+#define USB_DEVICE_ID_MATCH_DEV_PROTOCOL 0x0040
+#define USB_DEVICE_ID_MATCH_INT_CLASS 0x0080
+#define USB_DEVICE_ID_MATCH_INT_SUBCLASS 0x0100
+#define USB_DEVICE_ID_MATCH_INT_PROTOCOL 0x0200
+#endif
+
+ /* Hook for driver specific information */
+ unsigned long driver_info;
+} __aligned(32);
+
+#define USB_STD_PNP_INFO "M16:mask;U16:vendor;U16:product;L16:release;G16:release;" \
+ "U8:devclass;U8:devsubclass;U8:devproto;" \
+ "U8:intclass;U8:intsubclass;U8:intprotocol;"
+#define USB_STD_PNP_HOST_INFO USB_STD_PNP_INFO "T:mode=host;"
+#define USB_STD_PNP_DEVICE_INFO USB_STD_PNP_INFO "T:mode=device;"
+#define USB_PNP_HOST_INFO(table) \
+ MODULE_PNP_INFO(USB_STD_PNP_HOST_INFO, uhub, table, table, \
+ sizeof(table) / sizeof(table[0]))
+#define USB_PNP_DEVICE_INFO(table) \
+ MODULE_PNP_INFO(USB_STD_PNP_DEVICE_INFO, uhub, table, table, \
+ sizeof(table) / sizeof(table[0]))
+#define USB_PNP_DUAL_INFO(table) \
+ MODULE_PNP_INFO(USB_STD_PNP_INFO, uhub, table, table, \
+ sizeof(table) / sizeof(table[0]))
+
+/* check that the size of the structure above is correct */
+extern char usb_device_id_assert[(sizeof(struct usb_device_id) == 32) ? 1 : -1];
+
+#define USB_VENDOR(vend) \
+ .match_flag_vendor = 1, .idVendor = (vend)
+
+#define USB_PRODUCT(prod) \
+ .match_flag_product = 1, .idProduct = (prod)
+
+#define USB_VP(vend,prod) \
+ USB_VENDOR(vend), USB_PRODUCT(prod)
+
+#define USB_VPI(vend,prod,info) \
+ USB_VENDOR(vend), USB_PRODUCT(prod), USB_DRIVER_INFO(info)
+
+#define USB_DEV_BCD_GTEQ(lo) /* greater than or equal */ \
+ .match_flag_dev_lo = 1, .bcdDevice_lo = (lo)
+
+#define USB_DEV_BCD_LTEQ(hi) /* less than or equal */ \
+ .match_flag_dev_hi = 1, .bcdDevice_hi = (hi)
+
+#define USB_DEV_CLASS(dc) \
+ .match_flag_dev_class = 1, .bDeviceClass = (dc)
+
+#define USB_DEV_SUBCLASS(dsc) \
+ .match_flag_dev_subclass = 1, .bDeviceSubClass = (dsc)
+
+#define USB_DEV_PROTOCOL(dp) \
+ .match_flag_dev_protocol = 1, .bDeviceProtocol = (dp)
+
+#define USB_IFACE_CLASS(ic) \
+ .match_flag_int_class = 1, .bInterfaceClass = (ic)
+
+#define USB_IFACE_SUBCLASS(isc) \
+ .match_flag_int_subclass = 1, .bInterfaceSubClass = (isc)
+
+#define USB_IFACE_PROTOCOL(ip) \
+ .match_flag_int_protocol = 1, .bInterfaceProtocol = (ip)
+
+#define USB_IF_CSI(class,subclass,info) \
+ USB_IFACE_CLASS(class), USB_IFACE_SUBCLASS(subclass), USB_DRIVER_INFO(info)
+
+#define USB_DRIVER_INFO(n) \
+ .driver_info = (n)
+
+#define USB_GET_DRIVER_INFO(did) \
+ (did)->driver_info
+
+/*
+ * The following structure keeps information that is used to match
+ * against an array of "usb_device_id" elements.
+ */
+struct usbd_lookup_info {
+ uint16_t idVendor;
+ uint16_t idProduct;
+ uint16_t bcdDevice;
+ uint8_t bDeviceClass;
+ uint8_t bDeviceSubClass;
+ uint8_t bDeviceProtocol;
+ uint8_t bInterfaceClass;
+ uint8_t bInterfaceSubClass;
+ uint8_t bInterfaceProtocol;
+ uint8_t bIfaceIndex;
+ uint8_t bIfaceNum;
+ uint8_t bConfigIndex;
+ uint8_t bConfigNum;
+};
+
+/* Structure used by probe and attach */
+
+struct usb_attach_arg {
+ struct usbd_lookup_info info;
+ device_t temp_dev; /* for internal use */
+ unsigned long driver_info; /* for internal use */
+ void *driver_ivar;
+ struct usb_device *device; /* current device */
+ struct usb_interface *iface; /* current interface */
+ enum usb_hc_mode usb_mode; /* host or device mode */
+ uint8_t port;
+ uint8_t dev_state;
+#define UAA_DEV_READY 0
+#define UAA_DEV_DISABLED 1
+#define UAA_DEV_EJECTING 2
+};
+
+/*
+ * General purpose locking wrappers to ease supporting
+ * USB polled mode:
+ */
+#ifdef INVARIANTS
+#define USB_MTX_ASSERT(_m, _t) do { \
+ if (!USB_IN_POLLING_MODE_FUNC()) \
+ mtx_assert(_m, _t); \
+} while (0)
+#else
+#define USB_MTX_ASSERT(_m, _t) do { } while (0)
+#endif
+
+#define USB_MTX_LOCK(_m) do { \
+ if (!USB_IN_POLLING_MODE_FUNC()) \
+ mtx_lock(_m); \
+} while (0)
+
+#define USB_MTX_UNLOCK(_m) do { \
+ if (!USB_IN_POLLING_MODE_FUNC()) \
+ mtx_unlock(_m); \
+} while (0)
+
+#define USB_MTX_LOCK_SPIN(_m) do { \
+ if (!USB_IN_POLLING_MODE_FUNC()) \
+ mtx_lock_spin(_m); \
+} while (0)
+
+#define USB_MTX_UNLOCK_SPIN(_m) do { \
+ if (!USB_IN_POLLING_MODE_FUNC()) \
+ mtx_unlock_spin(_m); \
+} while (0)
+
+/*
+ * The following is a wrapper for the callout structure to ease
+ * porting the code to other platforms.
+ */
+struct usb_callout {
+ struct callout co;
+};
+#define usb_callout_init_mtx(c,m,f) callout_init_mtx(&(c)->co,m,f)
+#define usb_callout_reset(c,...) do { \
+ if (!USB_IN_POLLING_MODE_FUNC()) \
+ callout_reset(&(c)->co, __VA_ARGS__); \
+} while (0)
+#define usb_callout_reset_sbt(c,...) do { \
+ if (!USB_IN_POLLING_MODE_FUNC()) \
+ callout_reset_sbt(&(c)->co, __VA_ARGS__); \
+} while (0)
+#define usb_callout_stop(c) do { \
+ if (!USB_IN_POLLING_MODE_FUNC()) { \
+ callout_stop(&(c)->co); \
+ } else { \
+ /* \
+ * Cannot stop callout when \
+ * polling. Set dummy callback \
+ * function instead: \
+ */ \
+ (c)->co.c_func = &usbd_dummy_timeout; \
+ } \
+} while (0)
+#define usb_callout_drain(c) callout_drain(&(c)->co)
+#define usb_callout_pending(c) callout_pending(&(c)->co)
+
+/* USB transfer states */
+
+#define USB_ST_SETUP 0
+#define USB_ST_TRANSFERRED 1
+#define USB_ST_ERROR 2
+
+/* USB handle request states */
+#define USB_HR_NOT_COMPLETE 0
+#define USB_HR_COMPLETE_OK 1
+#define USB_HR_COMPLETE_ERR 2
+
+/*
+ * The following macro will return the current state of an USB
+ * transfer like defined by the "USB_ST_XXX" enums.
+ */
+#define USB_GET_STATE(xfer) (usbd_xfer_state(xfer))
+
+/*
+ * The following structure defines the USB process message header.
+ */
+struct usb_proc_msg {
+ TAILQ_ENTRY(usb_proc_msg) pm_qentry;
+ usb_proc_callback_t *pm_callback;
+ usb_size_t pm_num;
+};
+
+#define USB_PROC_MSG_ENQUEUED(msg) ((msg)->pm_qentry.tqe_prev != NULL)
+
+#define USB_FIFO_TX 0
+#define USB_FIFO_RX 1
+
+/*
+ * Locking note for the following functions. All the
+ * "usb_fifo_cmd_t" and "usb_fifo_filter_t" functions are called
+ * locked. The others are called unlocked.
+ */
+struct usb_fifo_methods {
+ usb_fifo_open_t *f_open;
+ usb_fifo_close_t *f_close;
+ usb_fifo_ioctl_t *f_ioctl;
+ /*
+ * NOTE: The post-ioctl callback is called after the USB reference
+ * gets locked in the IOCTL handler:
+ */
+ usb_fifo_ioctl_t *f_ioctl_post;
+ usb_fifo_cmd_t *f_start_read;
+ usb_fifo_cmd_t *f_stop_read;
+ usb_fifo_cmd_t *f_start_write;
+ usb_fifo_cmd_t *f_stop_write;
+ usb_fifo_filter_t *f_filter_read;
+ usb_fifo_filter_t *f_filter_write;
+ const char *basename[4];
+ const char *postfix[4];
+};
+
+struct usb_fifo_sc {
+ struct usb_fifo *fp[2];
+ struct usb_fs_privdata *dev;
+};
+
+const char *usbd_errstr(usb_error_t error);
+void *usbd_find_descriptor(struct usb_device *udev, void *id,
+ uint8_t iface_index, uint8_t type, uint8_t type_mask,
+ uint8_t subtype, uint8_t subtype_mask);
+struct usb_config_descriptor *usbd_get_config_descriptor(
+ struct usb_device *udev);
+struct usb_device_descriptor *usbd_get_device_descriptor(
+ struct usb_device *udev);
+struct usb_interface *usbd_get_iface(struct usb_device *udev,
+ uint8_t iface_index);
+struct usb_interface_descriptor *usbd_get_interface_descriptor(
+ struct usb_interface *iface);
+struct usb_endpoint *usbd_get_endpoint(struct usb_device *udev, uint8_t iface_index,
+ const struct usb_config *setup);
+struct usb_endpoint *usbd_get_ep_by_addr(struct usb_device *udev, uint8_t ea_val);
+usb_error_t usbd_interface_count(struct usb_device *udev, uint8_t *count);
+enum usb_hc_mode usbd_get_mode(struct usb_device *udev);
+enum usb_dev_speed usbd_get_speed(struct usb_device *udev);
+void device_set_usb_desc(device_t dev);
+void usb_pause_mtx(struct mtx *mtx, int _ticks);
+usb_error_t usbd_set_pnpinfo(struct usb_device *udev,
+ uint8_t iface_index, const char *pnpinfo);
+usb_error_t usbd_add_dynamic_quirk(struct usb_device *udev,
+ uint16_t quirk);
+usb_error_t usbd_set_endpoint_mode(struct usb_device *udev,
+ struct usb_endpoint *ep, uint8_t ep_mode);
+uint8_t usbd_get_endpoint_mode(struct usb_device *udev,
+ struct usb_endpoint *ep);
+
+const struct usb_device_id *usbd_lookup_id_by_info(
+ const struct usb_device_id *id, usb_size_t sizeof_id,
+ const struct usbd_lookup_info *info);
+int usbd_lookup_id_by_uaa(const struct usb_device_id *id,
+ usb_size_t sizeof_id, struct usb_attach_arg *uaa);
+
+usb_error_t usbd_do_request_flags(struct usb_device *udev, struct mtx *mtx,
+ struct usb_device_request *req, void *data, uint16_t flags,
+ uint16_t *actlen, usb_timeout_t timeout);
+#define usbd_do_request(u,m,r,d) \
+ usbd_do_request_flags(u,m,r,d,0,NULL,USB_DEFAULT_TIMEOUT)
+
+uint8_t usbd_clear_stall_callback(struct usb_xfer *xfer1,
+ struct usb_xfer *xfer2);
+uint8_t usbd_get_interface_altindex(struct usb_interface *iface);
+usb_error_t usbd_set_alt_interface_index(struct usb_device *udev,
+ uint8_t iface_index, uint8_t alt_index);
+uint32_t usbd_get_isoc_fps(struct usb_device *udev);
+uint32_t usbd_get_max_frame_length(const struct usb_endpoint_descriptor *,
+ const struct usb_endpoint_ss_comp_descriptor *,
+ enum usb_dev_speed);
+usb_error_t usbd_transfer_setup(struct usb_device *udev,
+ const uint8_t *ifaces, struct usb_xfer **pxfer,
+ const struct usb_config *setup_start, uint16_t n_setup,
+ void *priv_sc, struct mtx *priv_mtx);
+void usbd_transfer_submit(struct usb_xfer *xfer);
+void usbd_transfer_clear_stall(struct usb_xfer *xfer);
+void usbd_transfer_drain(struct usb_xfer *xfer);
+uint8_t usbd_transfer_pending(struct usb_xfer *xfer);
+void usbd_transfer_start(struct usb_xfer *xfer);
+void usbd_transfer_stop(struct usb_xfer *xfer);
+void usbd_transfer_unsetup(struct usb_xfer **pxfer, uint16_t n_setup);
+void usbd_transfer_poll(struct usb_xfer **ppxfer, uint16_t max);
+void usbd_set_parent_iface(struct usb_device *udev, uint8_t iface_index,
+ uint8_t parent_index);
+uint8_t usbd_get_bus_index(struct usb_device *udev);
+uint8_t usbd_get_device_index(struct usb_device *udev);
+void usbd_set_power_mode(struct usb_device *udev, uint8_t power_mode);
+uint8_t usbd_filter_power_mode(struct usb_device *udev, uint8_t power_mode);
+uint8_t usbd_device_attached(struct usb_device *udev);
+
+usb_frlength_t
+ usbd_xfer_old_frame_length(struct usb_xfer *xfer, usb_frcount_t frindex);
+void usbd_xfer_status(struct usb_xfer *xfer, int *actlen, int *sumlen,
+ int *aframes, int *nframes);
+struct usb_page_cache *usbd_xfer_get_frame(struct usb_xfer *, usb_frcount_t);
+void *usbd_xfer_get_frame_buffer(struct usb_xfer *, usb_frcount_t);
+void *usbd_xfer_softc(struct usb_xfer *xfer);
+void *usbd_xfer_get_priv(struct usb_xfer *xfer);
+void usbd_xfer_set_priv(struct usb_xfer *xfer, void *);
+void usbd_xfer_set_interval(struct usb_xfer *xfer, int);
+uint8_t usbd_xfer_state(struct usb_xfer *xfer);
+void usbd_xfer_set_frame_data(struct usb_xfer *xfer, usb_frcount_t frindex,
+ void *ptr, usb_frlength_t len);
+void usbd_xfer_frame_data(struct usb_xfer *xfer, usb_frcount_t frindex,
+ void **ptr, int *len);
+void usbd_xfer_set_frame_offset(struct usb_xfer *xfer, usb_frlength_t offset,
+ usb_frcount_t frindex);
+usb_frlength_t usbd_xfer_max_len(struct usb_xfer *xfer);
+usb_frlength_t usbd_xfer_max_framelen(struct usb_xfer *xfer);
+usb_frcount_t usbd_xfer_max_frames(struct usb_xfer *xfer);
+uint8_t usbd_xfer_get_fps_shift(struct usb_xfer *xfer);
+usb_frlength_t usbd_xfer_frame_len(struct usb_xfer *xfer,
+ usb_frcount_t frindex);
+void usbd_xfer_set_frame_len(struct usb_xfer *xfer, usb_frcount_t frindex,
+ usb_frlength_t len);
+void usbd_xfer_set_timeout(struct usb_xfer *xfer, int timeout);
+void usbd_xfer_set_frames(struct usb_xfer *xfer, usb_frcount_t n);
+void usbd_xfer_set_zlp(struct usb_xfer *xfer);
+uint8_t usbd_xfer_get_and_clr_zlp(struct usb_xfer *xfer);
+void usbd_xfer_set_stall(struct usb_xfer *xfer);
+int usbd_xfer_is_stalled(struct usb_xfer *xfer);
+void usbd_xfer_set_flag(struct usb_xfer *xfer, int flag);
+void usbd_xfer_clr_flag(struct usb_xfer *xfer, int flag);
+uint16_t usbd_xfer_get_timestamp(struct usb_xfer *xfer);
+uint8_t usbd_xfer_maxp_was_clamped(struct usb_xfer *xfer);
+
+void usbd_copy_in(struct usb_page_cache *cache, usb_frlength_t offset,
+ const void *ptr, usb_frlength_t len);
+int usbd_copy_in_user(struct usb_page_cache *cache, usb_frlength_t offset,
+ const void *ptr, usb_frlength_t len);
+void usbd_copy_out(struct usb_page_cache *cache, usb_frlength_t offset,
+ void *ptr, usb_frlength_t len);
+int usbd_copy_out_user(struct usb_page_cache *cache, usb_frlength_t offset,
+ void *ptr, usb_frlength_t len);
+void usbd_get_page(struct usb_page_cache *pc, usb_frlength_t offset,
+ struct usb_page_search *res);
+void usbd_m_copy_in(struct usb_page_cache *cache, usb_frlength_t dst_offset,
+ struct mbuf *m, usb_size_t src_offset, usb_frlength_t src_len);
+void usbd_frame_zero(struct usb_page_cache *cache, usb_frlength_t offset,
+ usb_frlength_t len);
+void usbd_start_re_enumerate(struct usb_device *udev);
+usb_error_t
+ usbd_start_set_config(struct usb_device *, uint8_t);
+int usbd_in_polling_mode(void);
+void usbd_dummy_timeout(void *);
+
+int usb_fifo_attach(struct usb_device *udev, void *priv_sc,
+ struct mtx *priv_mtx, struct usb_fifo_methods *pm,
+ struct usb_fifo_sc *f_sc, uint16_t unit, int16_t subunit,
+ uint8_t iface_index, uid_t uid, gid_t gid, int mode);
+void usb_fifo_detach(struct usb_fifo_sc *f_sc);
+int usb_fifo_alloc_buffer(struct usb_fifo *f, uint32_t bufsize,
+ uint16_t nbuf);
+void usb_fifo_free_buffer(struct usb_fifo *f);
+uint32_t usb_fifo_put_bytes_max(struct usb_fifo *fifo);
+void usb_fifo_put_data(struct usb_fifo *fifo, struct usb_page_cache *pc,
+ usb_frlength_t offset, usb_frlength_t len, uint8_t what);
+void usb_fifo_put_data_linear(struct usb_fifo *fifo, void *ptr,
+ usb_size_t len, uint8_t what);
+uint8_t usb_fifo_put_data_buffer(struct usb_fifo *f, void *ptr, usb_size_t len);
+void usb_fifo_put_data_error(struct usb_fifo *fifo);
+uint8_t usb_fifo_get_data(struct usb_fifo *fifo, struct usb_page_cache *pc,
+ usb_frlength_t offset, usb_frlength_t len, usb_frlength_t *actlen,
+ uint8_t what);
+uint8_t usb_fifo_get_data_linear(struct usb_fifo *fifo, void *ptr,
+ usb_size_t len, usb_size_t *actlen, uint8_t what);
+uint8_t usb_fifo_get_data_buffer(struct usb_fifo *f, void **pptr,
+ usb_size_t *plen);
+void usb_fifo_reset(struct usb_fifo *f);
+void usb_fifo_wakeup(struct usb_fifo *f);
+void usb_fifo_get_data_error(struct usb_fifo *fifo);
+void *usb_fifo_softc(struct usb_fifo *fifo);
+void usb_fifo_set_close_zlp(struct usb_fifo *, uint8_t);
+void usb_fifo_set_write_defrag(struct usb_fifo *, uint8_t);
+void usb_fifo_free(struct usb_fifo *f);
+#endif /* _KERNEL || _STANDALONE */
+#endif /* _USB_USBDI_H_ */
diff --git a/sys/dev/usb/usbdi_util.h b/sys/dev/usb/usbdi_util.h
new file mode 100644
index 000000000000..403327e8070f
--- /dev/null
+++ b/sys/dev/usb/usbdi_util.h
@@ -0,0 +1,91 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2009 Andrew Thompson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _USB_USBDI_UTIL_H_
+#define _USB_USBDI_UTIL_H_
+
+struct cv;
+
+/* structures */
+
+struct usb_idesc_parse_state {
+ struct usb_descriptor *desc;
+ uint8_t iface_index; /* current interface index */
+ uint8_t iface_no_last;
+ uint8_t iface_index_alt; /* current alternate setting */
+};
+
+/* prototypes */
+
+usb_error_t usbd_do_request_proc(struct usb_device *udev, struct usb_process *pproc,
+ struct usb_device_request *req, void *data, uint16_t flags,
+ uint16_t *actlen, usb_timeout_t timeout);
+
+struct usb_descriptor *usb_desc_foreach(struct usb_config_descriptor *cd,
+ struct usb_descriptor *desc);
+struct usb_interface_descriptor *usb_idesc_foreach(
+ struct usb_config_descriptor *cd,
+ struct usb_idesc_parse_state *ps);
+struct usb_endpoint_descriptor *usb_edesc_foreach(
+ struct usb_config_descriptor *cd,
+ struct usb_endpoint_descriptor *ped);
+struct usb_endpoint_ss_comp_descriptor *usb_ed_comp_foreach(
+ struct usb_config_descriptor *cd,
+ struct usb_endpoint_ss_comp_descriptor *ped);
+uint8_t usbd_get_no_descriptors(struct usb_config_descriptor *cd,
+ uint8_t type);
+uint8_t usbd_get_no_alts(struct usb_config_descriptor *cd,
+ struct usb_interface_descriptor *id);
+
+usb_error_t usbd_req_get_report(struct usb_device *udev, struct mtx *mtx,
+ void *data, uint16_t len, uint8_t iface_index, uint8_t type,
+ uint8_t id);
+usb_error_t usbd_req_get_report_descriptor(struct usb_device *udev,
+ struct mtx *mtx, void *d, uint16_t size,
+ uint8_t iface_index);
+usb_error_t usbd_req_get_string_any(struct usb_device *udev, struct mtx *mtx,
+ char *buf, uint16_t len, uint8_t string_index);
+usb_error_t usbd_req_get_string_desc(struct usb_device *udev, struct mtx *mtx,
+ void *sdesc, uint16_t max_len, uint16_t lang_id,
+ uint8_t string_index);
+usb_error_t usbd_req_set_config(struct usb_device *udev, struct mtx *mtx,
+ uint8_t conf);
+usb_error_t usbd_req_set_alt_interface_no(struct usb_device *udev,
+ struct mtx *mtx, uint8_t iface_index, uint8_t alt_no);
+usb_error_t usbd_req_set_idle(struct usb_device *udev, struct mtx *mtx,
+ uint8_t iface_index, uint8_t duration, uint8_t id);
+usb_error_t usbd_req_set_protocol(struct usb_device *udev, struct mtx *mtx,
+ uint8_t iface_index, uint16_t report);
+usb_error_t usbd_req_set_report(struct usb_device *udev, struct mtx *mtx,
+ void *data, uint16_t len, uint8_t iface_index,
+ uint8_t type, uint8_t id);
+
+/* The following functions will not return NULL strings. */
+
+const char *usb_get_manufacturer(struct usb_device *);
+const char *usb_get_product(struct usb_device *);
+const char *usb_get_serial(struct usb_device *);
+
+#endif /* _USB_USBDI_UTIL_H_ */
diff --git a/sys/dev/usb/usbhid.h b/sys/dev/usb/usbhid.h
new file mode 100644
index 000000000000..35c7da8ae789
--- /dev/null
+++ b/sys/dev/usb/usbhid.h
@@ -0,0 +1,97 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
+ * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved.
+ * Copyright (c) 1998 Lennart Augustsson. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _USB_HID_H_
+#define _USB_HID_H_
+
+#include <dev/hid/hid.h>
+
+#ifndef USB_GLOBAL_INCLUDE_FILE
+#include <dev/usb/usb_endian.h>
+#endif
+
+#define UR_GET_HID_DESCRIPTOR 0x06
+#define UDESC_HID 0x21
+#define UDESC_REPORT 0x22
+#define UDESC_PHYSICAL 0x23
+#define UR_SET_HID_DESCRIPTOR 0x07
+#define UR_GET_REPORT 0x01
+#define UR_SET_REPORT 0x09
+#define UR_GET_IDLE 0x02
+#define UR_SET_IDLE 0x0a
+#define UR_GET_PROTOCOL 0x03
+#define UR_SET_PROTOCOL 0x0b
+
+struct usb_hid_descriptor {
+ uByte bLength;
+ uByte bDescriptorType;
+ uWord bcdHID;
+ uByte bCountryCode;
+ uByte bNumDescriptors;
+ struct {
+ uByte bDescriptorType;
+ uWord wDescriptorLength;
+ } descrs[1];
+} __packed;
+
+#define USB_HID_DESCRIPTOR_SIZE(n) (9+((n)*3))
+
+#define UHID_INPUT_REPORT HID_INPUT_REPORT
+#define UHID_OUTPUT_REPORT HID_OUTPUT_REPORT
+#define UHID_FEATURE_REPORT HID_FEATURE_REPORT
+
+#if defined(_KERNEL) || defined(_STANDALONE)
+struct usb_config_descriptor;
+
+#ifdef COMPAT_USBHID12
+/* FreeBSD <= 12 compat shims */
+#define hid_report_size(buf, len, kind, id) \
+ hid_report_size_max(buf, len, kind, id)
+static __inline uint32_t
+hid_get_data_unsigned(const uint8_t *buf, hid_size_t len,
+ struct hid_location *loc)
+{
+ return (hid_get_udata(buf, len, loc));
+}
+static __inline void
+hid_put_data_unsigned(uint8_t *buf, hid_size_t len, struct hid_location *loc,
+ unsigned value)
+{
+ return (hid_put_udata(buf, len, loc, value));
+}
+#endif
+
+struct usb_hid_descriptor *hid_get_descriptor_from_usb(
+ struct usb_config_descriptor *cd,
+ struct usb_interface_descriptor *id);
+usb_error_t usbd_req_get_hid_desc(struct usb_device *udev, struct mtx *mtx,
+ void **descp, uint16_t *sizep, struct malloc_type *mem,
+ uint8_t iface_index);
+#endif /* _KERNEL || _STANDALONE */
+#endif /* _USB_HID_H_ */
diff --git a/sys/dev/usb/video/udl.c b/sys/dev/usb/video/udl.c
new file mode 100644
index 000000000000..213f1f5bb957
--- /dev/null
+++ b/sys/dev/usb/video/udl.c
@@ -0,0 +1,1153 @@
+/* $OpenBSD: udl.c,v 1.81 2014/12/09 07:05:06 doug Exp $ */
+
+/*-
+ * Copyright (c) 2015 Hans Petter Selasky <hselasky@freebsd.org>
+ * Copyright (c) 2009 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.
+ */
+
+/*
+ * Driver for the "DisplayLink DL-120 / DL-160" graphic chips based on
+ * the reversed engineered specifications of Florian Echtler
+ * <floe@butterbrot.org>:
+ *
+ * http://floe.butterbrot.org/displaylink/doku.php
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/callout.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/consio.h>
+#include <sys/fbio.h>
+
+#include <dev/fb/fbreg.h>
+#include <dev/syscons/syscons.h>
+
+#include <dev/videomode/videomode.h>
+#include <dev/videomode/edidvar.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#include <dev/usb/video/udl.h>
+
+#include "fb_if.h"
+
+#undef DPRINTF
+#undef DPRINTFN
+#define USB_DEBUG_VAR udl_debug
+#include <dev/usb/usb_debug.h>
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, udl, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB UDL");
+
+#ifdef USB_DEBUG
+static int udl_debug = 0;
+
+SYSCTL_INT(_hw_usb_udl, OID_AUTO, debug, CTLFLAG_RWTUN,
+ &udl_debug, 0, "Debug level");
+#endif
+
+#define UDL_FPS_MAX 60
+#define UDL_FPS_MIN 1
+
+static int udl_fps = 25;
+SYSCTL_INT(_hw_usb_udl, OID_AUTO, fps, CTLFLAG_RWTUN,
+ &udl_fps, 0, "Frames Per Second, 1-60");
+
+static struct mtx udl_buffer_mtx;
+static struct udl_buffer_head udl_buffer_head;
+
+MALLOC_DEFINE(M_USB_DL, "USB", "USB DisplayLink");
+
+/*
+ * Prototypes.
+ */
+static usb_callback_t udl_bulk_write_callback;
+
+static device_probe_t udl_probe;
+static device_attach_t udl_attach;
+static device_detach_t udl_detach;
+static fb_getinfo_t udl_fb_getinfo;
+static fb_setblankmode_t udl_fb_setblankmode;
+
+static void udl_select_chip(struct udl_softc *, struct usb_attach_arg *);
+static int udl_init_chip(struct udl_softc *);
+static void udl_select_mode(struct udl_softc *);
+static int udl_init_resolution(struct udl_softc *);
+static void udl_fbmem_alloc(struct udl_softc *);
+static int udl_cmd_write_buf_le16(struct udl_softc *, const uint8_t *, uint32_t, uint8_t, int);
+static int udl_cmd_buf_copy_le16(struct udl_softc *, uint32_t, uint32_t, uint8_t, int);
+static void udl_cmd_insert_int_1(struct udl_cmd_buf *, uint8_t);
+static void udl_cmd_insert_int_3(struct udl_cmd_buf *, uint32_t);
+static void udl_cmd_insert_buf_le16(struct udl_cmd_buf *, const uint8_t *, uint32_t);
+static void udl_cmd_write_reg_1(struct udl_cmd_buf *, uint8_t, uint8_t);
+static void udl_cmd_write_reg_3(struct udl_cmd_buf *, uint8_t, uint32_t);
+static int udl_power_save(struct udl_softc *, int, int);
+
+static const struct usb_config udl_config[UDL_N_TRANSFER] = {
+ [UDL_BULK_WRITE_0] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1,},
+ .bufsize = UDL_CMD_MAX_DATA_SIZE * UDL_CMD_MAX_FRAMES,
+ .callback = &udl_bulk_write_callback,
+ .frames = UDL_CMD_MAX_FRAMES,
+ .timeout = 5000, /* 5 seconds */
+ },
+ [UDL_BULK_WRITE_1] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_TX,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,.ext_buffer = 1,},
+ .bufsize = UDL_CMD_MAX_DATA_SIZE * UDL_CMD_MAX_FRAMES,
+ .callback = &udl_bulk_write_callback,
+ .frames = UDL_CMD_MAX_FRAMES,
+ .timeout = 5000, /* 5 seconds */
+ },
+};
+
+/*
+ * Driver glue.
+ */
+static device_method_t udl_methods[] = {
+ DEVMETHOD(device_probe, udl_probe),
+ DEVMETHOD(device_attach, udl_attach),
+ DEVMETHOD(device_detach, udl_detach),
+ DEVMETHOD(fb_getinfo, udl_fb_getinfo),
+ DEVMETHOD_END
+};
+
+static driver_t udl_driver = {
+ .name = "udl",
+ .methods = udl_methods,
+ .size = sizeof(struct udl_softc),
+};
+
+DRIVER_MODULE(udl, uhub, udl_driver, NULL, NULL);
+MODULE_DEPEND(udl, usb, 1, 1, 1);
+MODULE_DEPEND(udl, fbd, 1, 1, 1);
+MODULE_DEPEND(udl, videomode, 1, 1, 1);
+MODULE_VERSION(udl, 1);
+
+/*
+ * Matching devices.
+ */
+static const STRUCT_USB_HOST_ID udl_devs[] = {
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LCD4300U, DL120)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LCD8000U, DL120)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_GUC2020, DL160)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LD220, DL165)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_VCUD60, DL160)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_DLDVI, DL160)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_VGA10, DL120)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_WSDVI, DLUNK)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_EC008, DL160)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_HPDOCK, DL160)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_NL571, DL160)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_M01061, DL195)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_NBDOCK, DL165)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_SWDVI, DLUNK)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_UM7X0, DL120)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_CONV, DL160)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_PLUGABLE, DL160)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LUM70, DL125)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_POLARIS2, DLUNK)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_LT1421, DLUNK)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_ITEC, DL165)},
+ {USB_VPI(USB_VENDOR_DISPLAYLINK, USB_PRODUCT_DISPLAYLINK_DVI_19, DL165)},
+};
+
+static void
+udl_buffer_init(void *arg)
+{
+ mtx_init(&udl_buffer_mtx, "USB", "UDL", MTX_DEF);
+ TAILQ_INIT(&udl_buffer_head);
+}
+SYSINIT(udl_buffer_init, SI_SUB_LOCK, SI_ORDER_FIRST, udl_buffer_init, NULL);
+
+CTASSERT(sizeof(struct udl_buffer) < PAGE_SIZE);
+
+static void *
+udl_buffer_alloc(uint32_t size)
+{
+ struct udl_buffer *buf;
+ mtx_lock(&udl_buffer_mtx);
+ TAILQ_FOREACH(buf, &udl_buffer_head, entry) {
+ if (buf->size == size) {
+ TAILQ_REMOVE(&udl_buffer_head, buf, entry);
+ break;
+ }
+ }
+ mtx_unlock(&udl_buffer_mtx);
+ if (buf != NULL) {
+ uint8_t *ptr = ((uint8_t *)buf) - size;
+ /* wipe and recycle buffer */
+ memset(ptr, 0, size);
+ /* return buffer pointer */
+ return (ptr);
+ }
+ /* allocate new buffer */
+ return (malloc(size + sizeof(*buf), M_USB_DL, M_WAITOK | M_ZERO));
+}
+
+static void
+udl_buffer_free(void *_buf, uint32_t size)
+{
+ struct udl_buffer *buf;
+
+ /* check for NULL pointer */
+ if (_buf == NULL)
+ return;
+ /* compute pointer to recycle list */
+ buf = (struct udl_buffer *)(((uint8_t *)_buf) + size);
+
+ /*
+ * Memory mapped buffers should never be freed.
+ * Put display buffer into a recycle list.
+ */
+ mtx_lock(&udl_buffer_mtx);
+ buf->size = size;
+ TAILQ_INSERT_TAIL(&udl_buffer_head, buf, entry);
+ mtx_unlock(&udl_buffer_mtx);
+}
+
+static uint32_t
+udl_get_fb_size(struct udl_softc *sc)
+{
+ unsigned i = sc->sc_cur_mode;
+
+ return ((uint32_t)udl_modes[i].hdisplay *
+ (uint32_t)udl_modes[i].vdisplay * 2);
+}
+
+static uint32_t
+udl_get_fb_width(struct udl_softc *sc)
+{
+ unsigned i = sc->sc_cur_mode;
+
+ return (udl_modes[i].hdisplay);
+}
+
+static uint32_t
+udl_get_fb_height(struct udl_softc *sc)
+{
+ unsigned i = sc->sc_cur_mode;
+
+ return (udl_modes[i].vdisplay);
+}
+
+static uint32_t
+udl_get_fb_hz(struct udl_softc *sc)
+{
+ unsigned i = sc->sc_cur_mode;
+
+ return (udl_modes[i].hz);
+}
+
+static void
+udl_callout(void *arg)
+{
+ struct udl_softc *sc = arg;
+ const uint32_t max = udl_get_fb_size(sc);
+ int fps;
+
+ if (sc->sc_power_save == 0) {
+ fps = udl_fps;
+
+ /* figure out number of frames per second */
+ if (fps < UDL_FPS_MIN)
+ fps = UDL_FPS_MIN;
+ else if (fps > UDL_FPS_MAX)
+ fps = UDL_FPS_MAX;
+
+ if (sc->sc_sync_off >= max)
+ sc->sc_sync_off = 0;
+ usbd_transfer_start(sc->sc_xfer[UDL_BULK_WRITE_0]);
+ usbd_transfer_start(sc->sc_xfer[UDL_BULK_WRITE_1]);
+ } else {
+ fps = 1;
+ }
+ callout_reset(&sc->sc_callout, hz / fps, &udl_callout, sc);
+}
+
+static int
+udl_probe(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != 0)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != 0)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(udl_devs, sizeof(udl_devs), uaa));
+}
+
+static int
+udl_attach(device_t dev)
+{
+ struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev);
+ struct sysctl_oid *tree = device_get_sysctl_tree(dev);
+ struct udl_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ int error;
+ int i;
+
+ device_set_usb_desc(dev);
+
+ mtx_init(&sc->sc_mtx, "UDL lock", NULL, MTX_DEF);
+ cv_init(&sc->sc_cv, "UDLCV");
+ callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0);
+ sc->sc_udev = uaa->device;
+
+ error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex,
+ sc->sc_xfer, udl_config, UDL_N_TRANSFER, sc, &sc->sc_mtx);
+
+ if (error) {
+ DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+ usbd_xfer_set_priv(sc->sc_xfer[UDL_BULK_WRITE_0], &sc->sc_xfer_head[0]);
+ usbd_xfer_set_priv(sc->sc_xfer[UDL_BULK_WRITE_1], &sc->sc_xfer_head[1]);
+
+ TAILQ_INIT(&sc->sc_xfer_head[0]);
+ TAILQ_INIT(&sc->sc_xfer_head[1]);
+ TAILQ_INIT(&sc->sc_cmd_buf_free);
+ TAILQ_INIT(&sc->sc_cmd_buf_pending);
+
+ sc->sc_def_chip = -1;
+ sc->sc_chip = USB_GET_DRIVER_INFO(uaa);
+ sc->sc_def_mode = -1;
+ sc->sc_cur_mode = UDL_MAX_MODES;
+
+ /* Allow chip ID to be overwritten */
+ SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "chipid_force",
+ CTLFLAG_RWTUN, &sc->sc_def_chip, 0, "chip ID");
+
+ /* Export current chip ID */
+ SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "chipid",
+ CTLFLAG_RD, &sc->sc_chip, 0, "chip ID");
+
+ if (sc->sc_def_chip > -1 && sc->sc_def_chip <= DLMAX) {
+ device_printf(dev, "Forcing chip ID to 0x%04x\n", sc->sc_def_chip);
+ sc->sc_chip = sc->sc_def_chip;
+ }
+ /*
+ * The product might have more than one chip
+ */
+ if (sc->sc_chip == DLUNK)
+ udl_select_chip(sc, uaa);
+
+ for (i = 0; i != UDL_CMD_MAX_BUFFERS; i++) {
+ struct udl_cmd_buf *cb = &sc->sc_cmd_buf_temp[i];
+
+ TAILQ_INSERT_TAIL(&sc->sc_cmd_buf_free, cb, entry);
+ }
+
+ /*
+ * Initialize chip.
+ */
+ error = udl_init_chip(sc);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ goto detach;
+
+ /*
+ * Select edid mode.
+ */
+ udl_select_mode(sc);
+
+ /* Allow default mode to be overwritten */
+ SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "mode_force",
+ CTLFLAG_RWTUN, &sc->sc_def_mode, 0, "mode");
+
+ /* Export current mode */
+ SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "mode",
+ CTLFLAG_RD, &sc->sc_cur_mode, 0, "mode");
+
+ i = sc->sc_def_mode;
+ if (i > -1 && i < UDL_MAX_MODES) {
+ if (udl_modes[i].chip <= sc->sc_chip) {
+ device_printf(dev, "Forcing mode to %d\n", i);
+ sc->sc_cur_mode = i;
+ }
+ }
+ /* Printout current mode */
+ device_printf(dev, "Mode selected %dx%d @ %dHz\n",
+ (int)udl_get_fb_width(sc),
+ (int)udl_get_fb_height(sc),
+ (int)udl_get_fb_hz(sc));
+
+ udl_init_resolution(sc);
+
+ /* Allocate frame buffer */
+ udl_fbmem_alloc(sc);
+
+ UDL_LOCK(sc);
+ udl_callout(sc);
+ UDL_UNLOCK(sc);
+
+ sc->sc_fb_info.fb_name = device_get_nameunit(dev);
+ sc->sc_fb_info.fb_size = sc->sc_fb_size;
+ sc->sc_fb_info.fb_bpp = 16;
+ sc->sc_fb_info.fb_depth = 16;
+ sc->sc_fb_info.fb_width = udl_get_fb_width(sc);
+ sc->sc_fb_info.fb_height = udl_get_fb_height(sc);
+ sc->sc_fb_info.fb_stride = sc->sc_fb_info.fb_width * 2;
+ sc->sc_fb_info.fb_pbase = 0;
+ sc->sc_fb_info.fb_vbase = (uintptr_t)sc->sc_fb_addr;
+ sc->sc_fb_info.fb_priv = sc;
+ sc->sc_fb_info.setblankmode = &udl_fb_setblankmode;
+
+ sc->sc_fbdev = device_add_child(dev, "fbd", DEVICE_UNIT_ANY);
+ if (sc->sc_fbdev == NULL)
+ goto detach;
+ if (device_probe_and_attach(sc->sc_fbdev) != 0)
+ goto detach;
+
+ return (0);
+
+detach:
+ udl_detach(dev);
+
+ return (ENXIO);
+}
+
+static int
+udl_detach(device_t dev)
+{
+ struct udl_softc *sc = device_get_softc(dev);
+ int error;
+
+ /* delete all child devices */
+ error = bus_generic_detach(dev);
+ if (error != 0)
+ return (error);
+
+ UDL_LOCK(sc);
+ sc->sc_gone = 1;
+ callout_stop(&sc->sc_callout);
+ UDL_UNLOCK(sc);
+
+ usbd_transfer_unsetup(sc->sc_xfer, UDL_N_TRANSFER);
+
+ callout_drain(&sc->sc_callout);
+
+ mtx_destroy(&sc->sc_mtx);
+ cv_destroy(&sc->sc_cv);
+
+ /* put main framebuffer into a recycle list, if any */
+ udl_buffer_free(sc->sc_fb_addr, sc->sc_fb_size);
+
+ /* free shadow framebuffer memory, if any */
+ free(sc->sc_fb_copy, M_USB_DL);
+
+ return (0);
+}
+
+static struct fb_info *
+udl_fb_getinfo(device_t dev)
+{
+ struct udl_softc *sc = device_get_softc(dev);
+
+ return (&sc->sc_fb_info);
+}
+
+static int
+udl_fb_setblankmode(void *arg, int mode)
+{
+ struct udl_softc *sc = arg;
+
+ switch (mode) {
+ case V_DISPLAY_ON:
+ udl_power_save(sc, 1, M_WAITOK);
+ break;
+ case V_DISPLAY_BLANK:
+ udl_power_save(sc, 1, M_WAITOK);
+ if (sc->sc_fb_addr != 0) {
+ const uint32_t max = udl_get_fb_size(sc);
+
+ memset((void *)sc->sc_fb_addr, 0, max);
+ }
+ break;
+ case V_DISPLAY_STAND_BY:
+ case V_DISPLAY_SUSPEND:
+ udl_power_save(sc, 0, M_WAITOK);
+ break;
+ }
+ return (0);
+}
+
+static struct udl_cmd_buf *
+udl_cmd_buf_alloc_locked(struct udl_softc *sc, int flags)
+{
+ struct udl_cmd_buf *cb;
+
+ while ((cb = TAILQ_FIRST(&sc->sc_cmd_buf_free)) == NULL) {
+ if (flags != M_WAITOK)
+ break;
+ cv_wait(&sc->sc_cv, &sc->sc_mtx);
+ }
+ if (cb != NULL) {
+ TAILQ_REMOVE(&sc->sc_cmd_buf_free, cb, entry);
+ cb->off = 0;
+ }
+ return (cb);
+}
+
+static struct udl_cmd_buf *
+udl_cmd_buf_alloc(struct udl_softc *sc, int flags)
+{
+ struct udl_cmd_buf *cb;
+
+ UDL_LOCK(sc);
+ cb = udl_cmd_buf_alloc_locked(sc, flags);
+ UDL_UNLOCK(sc);
+ return (cb);
+}
+
+static void
+udl_cmd_buf_send(struct udl_softc *sc, struct udl_cmd_buf *cb)
+{
+ UDL_LOCK(sc);
+ if (sc->sc_gone) {
+ TAILQ_INSERT_TAIL(&sc->sc_cmd_buf_free, cb, entry);
+ } else {
+ /* mark end of command stack */
+ udl_cmd_insert_int_1(cb, UDL_BULK_SOC);
+ udl_cmd_insert_int_1(cb, UDL_BULK_CMD_EOC);
+
+ TAILQ_INSERT_TAIL(&sc->sc_cmd_buf_pending, cb, entry);
+ usbd_transfer_start(sc->sc_xfer[UDL_BULK_WRITE_0]);
+ usbd_transfer_start(sc->sc_xfer[UDL_BULK_WRITE_1]);
+ }
+ UDL_UNLOCK(sc);
+}
+
+static struct udl_cmd_buf *
+udl_fb_synchronize_locked(struct udl_softc *sc)
+{
+ const uint32_t max = udl_get_fb_size(sc);
+
+ /* check if framebuffer is not ready */
+ if (sc->sc_fb_addr == NULL ||
+ sc->sc_fb_copy == NULL)
+ return (NULL);
+
+ while (sc->sc_sync_off < max) {
+ uint32_t delta = max - sc->sc_sync_off;
+
+ if (delta > UDL_CMD_MAX_PIXEL_COUNT * 2)
+ delta = UDL_CMD_MAX_PIXEL_COUNT * 2;
+ if (bcmp(sc->sc_fb_addr + sc->sc_sync_off, sc->sc_fb_copy + sc->sc_sync_off, delta) != 0) {
+ struct udl_cmd_buf *cb;
+
+ cb = udl_cmd_buf_alloc_locked(sc, M_NOWAIT);
+ if (cb == NULL)
+ goto done;
+ memcpy(sc->sc_fb_copy + sc->sc_sync_off,
+ sc->sc_fb_addr + sc->sc_sync_off, delta);
+ udl_cmd_insert_int_1(cb, UDL_BULK_SOC);
+ udl_cmd_insert_int_1(cb, UDL_BULK_CMD_FB_WRITE | UDL_BULK_CMD_FB_WORD);
+ udl_cmd_insert_int_3(cb, sc->sc_sync_off);
+ udl_cmd_insert_int_1(cb, delta / 2);
+ udl_cmd_insert_buf_le16(cb, sc->sc_fb_copy + sc->sc_sync_off, delta);
+ sc->sc_sync_off += delta;
+ return (cb);
+ } else {
+ sc->sc_sync_off += delta;
+ }
+ }
+done:
+ return (NULL);
+}
+
+static void
+udl_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct udl_softc *sc = usbd_xfer_softc(xfer);
+ struct udl_cmd_head *phead = usbd_xfer_get_priv(xfer);
+ struct udl_cmd_buf *cb;
+ unsigned i;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ TAILQ_CONCAT(&sc->sc_cmd_buf_free, phead, entry);
+ case USB_ST_SETUP:
+tr_setup:
+ for (i = 0; i != UDL_CMD_MAX_FRAMES; i++) {
+ cb = TAILQ_FIRST(&sc->sc_cmd_buf_pending);
+ if (cb == NULL) {
+ cb = udl_fb_synchronize_locked(sc);
+ if (cb == NULL)
+ break;
+ } else {
+ TAILQ_REMOVE(&sc->sc_cmd_buf_pending, cb, entry);
+ }
+ TAILQ_INSERT_TAIL(phead, cb, entry);
+ usbd_xfer_set_frame_data(xfer, i, cb->buf, cb->off);
+ }
+ if (i != 0) {
+ usbd_xfer_set_frames(xfer, i);
+ usbd_transfer_submit(xfer);
+ }
+ break;
+ default:
+ TAILQ_CONCAT(&sc->sc_cmd_buf_free, phead, entry);
+ if (error != USB_ERR_CANCELLED) {
+ /* try clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+ /* wakeup any waiters */
+ cv_signal(&sc->sc_cv);
+}
+
+static int
+udl_power_save(struct udl_softc *sc, int on, int flags)
+{
+ struct udl_cmd_buf *cb;
+
+ /* get new buffer */
+ cb = udl_cmd_buf_alloc(sc, flags);
+ if (cb == NULL)
+ return (EAGAIN);
+
+ DPRINTF("screen %s\n", on ? "ON" : "OFF");
+
+ sc->sc_power_save = on ? 0 : 1;
+
+ if (on)
+ udl_cmd_write_reg_1(cb, UDL_REG_SCREEN, UDL_REG_SCREEN_ON);
+ else
+ udl_cmd_write_reg_1(cb, UDL_REG_SCREEN, UDL_REG_SCREEN_OFF);
+
+ udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0xff);
+ udl_cmd_buf_send(sc, cb);
+ return (0);
+}
+
+static int
+udl_ctrl_msg(struct udl_softc *sc, uint8_t rt, uint8_t r,
+ uint16_t index, uint16_t value, uint8_t *buf, size_t len)
+{
+ usb_device_request_t req;
+ int error;
+
+ req.bmRequestType = rt;
+ req.bRequest = r;
+ USETW(req.wIndex, index);
+ USETW(req.wValue, value);
+ USETW(req.wLength, len);
+
+ error = usbd_do_request_flags(sc->sc_udev, NULL,
+ &req, buf, 0, NULL, USB_DEFAULT_TIMEOUT);
+
+ DPRINTF("%s\n", usbd_errstr(error));
+
+ return (error);
+}
+
+static int
+udl_poll(struct udl_softc *sc, uint32_t *buf)
+{
+ uint32_t lbuf;
+ int error;
+
+ error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE,
+ UDL_CTRL_CMD_POLL, 0x0000, 0x0000, (uint8_t *)&lbuf, sizeof(lbuf));
+ if (error == USB_ERR_NORMAL_COMPLETION)
+ *buf = le32toh(lbuf);
+ return (error);
+}
+
+static int
+udl_read_1(struct udl_softc *sc, uint16_t addr, uint8_t *buf)
+{
+ uint8_t lbuf[1];
+ int error;
+
+ error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE,
+ UDL_CTRL_CMD_READ_1, addr, 0x0000, lbuf, 1);
+ if (error == USB_ERR_NORMAL_COMPLETION)
+ *buf = *(uint8_t *)lbuf;
+ return (error);
+}
+
+static int
+udl_write_1(struct udl_softc *sc, uint16_t addr, uint8_t buf)
+{
+ int error;
+
+ error = udl_ctrl_msg(sc, UT_WRITE_VENDOR_DEVICE,
+ UDL_CTRL_CMD_WRITE_1, addr, 0x0000, &buf, 1);
+ return (error);
+}
+
+static int
+udl_read_edid(struct udl_softc *sc, uint8_t *buf)
+{
+ uint8_t lbuf[64];
+ uint16_t offset;
+ int error;
+
+ offset = 0;
+
+ error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE,
+ UDL_CTRL_CMD_READ_EDID, 0x00a1, (offset << 8), lbuf, 64);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ goto fail;
+ bcopy(lbuf + 1, buf + offset, 63);
+ offset += 63;
+
+ error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE,
+ UDL_CTRL_CMD_READ_EDID, 0x00a1, (offset << 8), lbuf, 64);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ goto fail;
+ bcopy(lbuf + 1, buf + offset, 63);
+ offset += 63;
+
+ error = udl_ctrl_msg(sc, UT_READ_VENDOR_DEVICE,
+ UDL_CTRL_CMD_READ_EDID, 0x00a1, (offset << 8), lbuf, 3);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ goto fail;
+ bcopy(lbuf + 1, buf + offset, 2);
+fail:
+ return (error);
+}
+
+static uint8_t
+udl_lookup_mode(uint16_t hdisplay, uint16_t vdisplay, uint8_t hz,
+ uint16_t chip, uint32_t clock)
+{
+ uint8_t idx;
+
+ /*
+ * Check first if we have a matching mode with pixelclock
+ */
+ for (idx = 0; idx != UDL_MAX_MODES; idx++) {
+ if ((udl_modes[idx].hdisplay == hdisplay) &&
+ (udl_modes[idx].vdisplay == vdisplay) &&
+ (udl_modes[idx].clock == clock) &&
+ (udl_modes[idx].chip <= chip)) {
+ return (idx);
+ }
+ }
+
+ /*
+ * If not, check for matching mode with update frequency
+ */
+ for (idx = 0; idx != UDL_MAX_MODES; idx++) {
+ if ((udl_modes[idx].hdisplay == hdisplay) &&
+ (udl_modes[idx].vdisplay == vdisplay) &&
+ (udl_modes[idx].hz == hz) &&
+ (udl_modes[idx].chip <= chip)) {
+ return (idx);
+ }
+ }
+ return (idx);
+}
+
+static void
+udl_select_chip(struct udl_softc *sc, struct usb_attach_arg *uaa)
+{
+ const char *pserial;
+
+ pserial = usb_get_serial(uaa->device);
+
+ sc->sc_chip = DL120;
+
+ if ((uaa->info.idVendor == USB_VENDOR_DISPLAYLINK) &&
+ (uaa->info.idProduct == USB_PRODUCT_DISPLAYLINK_WSDVI)) {
+ /*
+ * WS Tech DVI is DL120 or DL160. All deviced uses the
+ * same revision (0.04) so iSerialNumber must be used
+ * to determine which chip it is.
+ */
+
+ if (strlen(pserial) > 7) {
+ if (strncmp(pserial, "0198-13", 7) == 0)
+ sc->sc_chip = DL160;
+ }
+ DPRINTF("iSerialNumber (%s) used to select chip (%d)\n",
+ pserial, sc->sc_chip);
+ }
+ if ((uaa->info.idVendor == USB_VENDOR_DISPLAYLINK) &&
+ (uaa->info.idProduct == USB_PRODUCT_DISPLAYLINK_SWDVI)) {
+ /*
+ * SUNWEIT DVI is DL160, DL125, DL165 or DL195. Major revision
+ * can be used to differ between DL1x0 and DL1x5. Minor to
+ * differ between DL1x5. iSerialNumber seems not to be uniqe.
+ */
+
+ sc->sc_chip = DL160;
+
+ if (uaa->info.bcdDevice >= 0x100) {
+ sc->sc_chip = DL165;
+ if (uaa->info.bcdDevice == 0x104)
+ sc->sc_chip = DL195;
+ if (uaa->info.bcdDevice == 0x108)
+ sc->sc_chip = DL125;
+ }
+ DPRINTF("bcdDevice (%02x) used to select chip (%d)\n",
+ uaa->info.bcdDevice, sc->sc_chip);
+ }
+}
+
+static int
+udl_set_enc_key(struct udl_softc *sc, uint8_t *buf, uint8_t len)
+{
+ int error;
+
+ error = udl_ctrl_msg(sc, UT_WRITE_VENDOR_DEVICE,
+ UDL_CTRL_CMD_SET_KEY, 0x0000, 0x0000, buf, len);
+ return (error);
+}
+
+static void
+udl_fbmem_alloc(struct udl_softc *sc)
+{
+ uint32_t size;
+
+ size = udl_get_fb_size(sc);
+ size = round_page(size);
+ /* check for zero size */
+ if (size == 0)
+ size = PAGE_SIZE;
+ /*
+ * It is assumed that allocations above PAGE_SIZE bytes will
+ * be PAGE_SIZE aligned for use with mmap()
+ */
+ sc->sc_fb_addr = udl_buffer_alloc(size);
+ sc->sc_fb_copy = malloc(size, M_USB_DL, M_WAITOK | M_ZERO);
+ sc->sc_fb_size = size;
+}
+
+static void
+udl_cmd_insert_int_1(struct udl_cmd_buf *cb, uint8_t value)
+{
+
+ cb->buf[cb->off] = value;
+ cb->off += 1;
+}
+
+#if 0
+static void
+udl_cmd_insert_int_2(struct udl_cmd_buf *cb, uint16_t value)
+{
+ uint16_t lvalue;
+
+ lvalue = htobe16(value);
+ bcopy(&lvalue, cb->buf + cb->off, 2);
+
+ cb->off += 2;
+}
+
+#endif
+
+static void
+udl_cmd_insert_int_3(struct udl_cmd_buf *cb, uint32_t value)
+{
+ uint32_t lvalue;
+
+#if BYTE_ORDER == BIG_ENDIAN
+ lvalue = htobe32(value) << 8;
+#else
+ lvalue = htobe32(value) >> 8;
+#endif
+ bcopy(&lvalue, cb->buf + cb->off, 3);
+
+ cb->off += 3;
+}
+
+#if 0
+static void
+udl_cmd_insert_int_4(struct udl_cmd_buf *cb, uint32_t value)
+{
+ uint32_t lvalue;
+
+ lvalue = htobe32(value);
+ bcopy(&lvalue, cb->buf + cb->off, 4);
+
+ cb->off += 4;
+}
+
+#endif
+
+static void
+udl_cmd_insert_buf_le16(struct udl_cmd_buf *cb, const uint8_t *buf, uint32_t len)
+{
+ uint32_t x;
+
+ for (x = 0; x != len; x += 2) {
+ /* byte swap from little endian to big endian */
+ cb->buf[cb->off + x + 0] = buf[x + 1];
+ cb->buf[cb->off + x + 1] = buf[x + 0];
+ }
+ cb->off += len;
+}
+
+static void
+udl_cmd_write_reg_1(struct udl_cmd_buf *cb, uint8_t reg, uint8_t val)
+{
+
+ udl_cmd_insert_int_1(cb, UDL_BULK_SOC);
+ udl_cmd_insert_int_1(cb, UDL_BULK_CMD_REG_WRITE_1);
+ udl_cmd_insert_int_1(cb, reg);
+ udl_cmd_insert_int_1(cb, val);
+}
+
+static void
+udl_cmd_write_reg_3(struct udl_cmd_buf *cb, uint8_t reg, uint32_t val)
+{
+
+ udl_cmd_write_reg_1(cb, reg + 0, (val >> 16) & 0xff);
+ udl_cmd_write_reg_1(cb, reg + 1, (val >> 8) & 0xff);
+ udl_cmd_write_reg_1(cb, reg + 2, (val >> 0) & 0xff);
+}
+
+static int
+udl_init_chip(struct udl_softc *sc)
+{
+ uint32_t ui32;
+ uint8_t ui8;
+ int error;
+
+ error = udl_poll(sc, &ui32);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+ DPRINTF("poll=0x%08x\n", ui32);
+
+ /* Some products may use later chip too */
+ switch (ui32 & 0xff) {
+ case 0xf1: /* DL1x5 */
+ switch (sc->sc_chip) {
+ case DL120:
+ sc->sc_chip = DL125;
+ break;
+ case DL160:
+ sc->sc_chip = DL165;
+ break;
+ }
+ break;
+ }
+ DPRINTF("chip 0x%04x\n", sc->sc_chip);
+
+ error = udl_read_1(sc, 0xc484, &ui8);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+ DPRINTF("read 0x%02x from 0xc484\n", ui8);
+
+ error = udl_write_1(sc, 0xc41f, 0x01);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+ DPRINTF("write 0x01 to 0xc41f\n");
+
+ error = udl_read_edid(sc, sc->sc_edid);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+ DPRINTF("read EDID\n");
+
+ error = udl_set_enc_key(sc, __DECONST(void *, udl_null_key_1),
+ sizeof(udl_null_key_1));
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+ DPRINTF("set encryption key\n");
+
+ error = udl_write_1(sc, 0xc40b, 0x00);
+ if (error != USB_ERR_NORMAL_COMPLETION)
+ return (error);
+ DPRINTF("write 0x00 to 0xc40b\n");
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static void
+udl_init_fb_offsets(struct udl_cmd_buf *cb, uint32_t start16, uint32_t stride16,
+ uint32_t start8, uint32_t stride8)
+{
+ udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0x00);
+ udl_cmd_write_reg_3(cb, UDL_REG_ADDR_START16, start16);
+ udl_cmd_write_reg_3(cb, UDL_REG_ADDR_STRIDE16, stride16);
+ udl_cmd_write_reg_3(cb, UDL_REG_ADDR_START8, start8);
+ udl_cmd_write_reg_3(cb, UDL_REG_ADDR_STRIDE8, stride8);
+ udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0xff);
+}
+
+static int
+udl_init_resolution(struct udl_softc *sc)
+{
+ const uint32_t max = udl_get_fb_size(sc);
+ const uint8_t *buf = udl_modes[sc->sc_cur_mode].mode;
+ struct udl_cmd_buf *cb;
+ uint32_t delta;
+ uint32_t i;
+ int error;
+
+ /* get new buffer */
+ cb = udl_cmd_buf_alloc(sc, M_WAITOK);
+ if (cb == NULL)
+ return (EAGAIN);
+
+ /* write resolution values and set video memory offsets */
+ udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0x00);
+ for (i = 0; i < UDL_MODE_SIZE; i++)
+ udl_cmd_write_reg_1(cb, i, buf[i]);
+ udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0xff);
+
+ udl_init_fb_offsets(cb, 0x000000, 0x000a00, 0x555555, 0x000500);
+ udl_cmd_buf_send(sc, cb);
+
+ /* fill screen with black color */
+ for (i = 0; i < max; i += delta) {
+ static const uint8_t udl_black[UDL_CMD_MAX_PIXEL_COUNT * 2] __aligned(4);
+
+ delta = max - i;
+ if (delta > UDL_CMD_MAX_PIXEL_COUNT * 2)
+ delta = UDL_CMD_MAX_PIXEL_COUNT * 2;
+ if (i == 0)
+ error = udl_cmd_write_buf_le16(sc, udl_black, i, delta / 2, M_WAITOK);
+ else
+ error = udl_cmd_buf_copy_le16(sc, 0, i, delta / 2, M_WAITOK);
+ if (error)
+ return (error);
+ }
+
+ /* get new buffer */
+ cb = udl_cmd_buf_alloc(sc, M_WAITOK);
+ if (cb == NULL)
+ return (EAGAIN);
+
+ /* show framebuffer content */
+ udl_cmd_write_reg_1(cb, UDL_REG_SCREEN, UDL_REG_SCREEN_ON);
+ udl_cmd_write_reg_1(cb, UDL_REG_SYNC, 0xff);
+ udl_cmd_buf_send(sc, cb);
+ return (0);
+}
+
+static void
+udl_select_mode(struct udl_softc *sc)
+{
+ struct udl_mode mode;
+ int index = UDL_MAX_MODES;
+ int i;
+
+ /* try to get the preferred mode from EDID */
+ edid_parse(sc->sc_edid, &sc->sc_edid_info);
+#ifdef USB_DEBUG
+ edid_print(&sc->sc_edid_info);
+#endif
+ if (sc->sc_edid_info.edid_preferred_mode != NULL) {
+ mode.hz =
+ (sc->sc_edid_info.edid_preferred_mode->dot_clock * 1000) /
+ (sc->sc_edid_info.edid_preferred_mode->htotal *
+ sc->sc_edid_info.edid_preferred_mode->vtotal);
+ mode.clock =
+ sc->sc_edid_info.edid_preferred_mode->dot_clock / 10;
+ mode.hdisplay =
+ sc->sc_edid_info.edid_preferred_mode->hdisplay;
+ mode.vdisplay =
+ sc->sc_edid_info.edid_preferred_mode->vdisplay;
+ index = udl_lookup_mode(mode.hdisplay, mode.vdisplay, mode.hz,
+ sc->sc_chip, mode.clock);
+ sc->sc_cur_mode = index;
+ } else {
+ DPRINTF("no preferred mode found!\n");
+ }
+
+ if (index == UDL_MAX_MODES) {
+ DPRINTF("no mode line found\n");
+
+ i = 0;
+ while (i < sc->sc_edid_info.edid_nmodes) {
+ mode.hz =
+ (sc->sc_edid_info.edid_modes[i].dot_clock * 1000) /
+ (sc->sc_edid_info.edid_modes[i].htotal *
+ sc->sc_edid_info.edid_modes[i].vtotal);
+ mode.clock =
+ sc->sc_edid_info.edid_modes[i].dot_clock / 10;
+ mode.hdisplay =
+ sc->sc_edid_info.edid_modes[i].hdisplay;
+ mode.vdisplay =
+ sc->sc_edid_info.edid_modes[i].vdisplay;
+ index = udl_lookup_mode(mode.hdisplay, mode.vdisplay,
+ mode.hz, sc->sc_chip, mode.clock);
+ if (index < UDL_MAX_MODES)
+ if ((sc->sc_cur_mode == UDL_MAX_MODES) ||
+ (index > sc->sc_cur_mode))
+ sc->sc_cur_mode = index;
+ i++;
+ }
+ }
+ /*
+ * If no mode found use default.
+ */
+ if (sc->sc_cur_mode == UDL_MAX_MODES)
+ sc->sc_cur_mode = udl_lookup_mode(800, 600, 60, sc->sc_chip, 0);
+}
+
+static int
+udl_cmd_write_buf_le16(struct udl_softc *sc, const uint8_t *buf, uint32_t off,
+ uint8_t pixels, int flags)
+{
+ struct udl_cmd_buf *cb;
+
+ cb = udl_cmd_buf_alloc(sc, flags);
+ if (cb == NULL)
+ return (EAGAIN);
+
+ udl_cmd_insert_int_1(cb, UDL_BULK_SOC);
+ udl_cmd_insert_int_1(cb, UDL_BULK_CMD_FB_WRITE | UDL_BULK_CMD_FB_WORD);
+ udl_cmd_insert_int_3(cb, off);
+ udl_cmd_insert_int_1(cb, pixels);
+ udl_cmd_insert_buf_le16(cb, buf, 2 * pixels);
+ udl_cmd_buf_send(sc, cb);
+
+ return (0);
+}
+
+static int
+udl_cmd_buf_copy_le16(struct udl_softc *sc, uint32_t src, uint32_t dst,
+ uint8_t pixels, int flags)
+{
+ struct udl_cmd_buf *cb;
+
+ cb = udl_cmd_buf_alloc(sc, flags);
+ if (cb == NULL)
+ return (EAGAIN);
+
+ udl_cmd_insert_int_1(cb, UDL_BULK_SOC);
+ udl_cmd_insert_int_1(cb, UDL_BULK_CMD_FB_COPY | UDL_BULK_CMD_FB_WORD);
+ udl_cmd_insert_int_3(cb, dst);
+ udl_cmd_insert_int_1(cb, pixels);
+ udl_cmd_insert_int_3(cb, src);
+ udl_cmd_buf_send(sc, cb);
+
+ return (0);
+}
diff --git a/sys/dev/usb/video/udl.h b/sys/dev/usb/video/udl.h
new file mode 100644
index 000000000000..9f859659da55
--- /dev/null
+++ b/sys/dev/usb/video/udl.h
@@ -0,0 +1,319 @@
+/* $OpenBSD: udl.h,v 1.21 2013/04/15 09:23:02 mglocker Exp $ */
+
+/*
+ * Copyright (c) 2009 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.
+ */
+
+#ifndef _UDL_H_
+#define _UDL_H_
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+/*
+ * BULK command transfer structure.
+ */
+#define UDL_CMD_MAX_FRAMES 64 /* units */
+#define UDL_CMD_MAX_DATA_SIZE 512 /* bytes */
+#define UDL_CMD_MAX_HEAD_SIZE 16 /* bytes */
+#define UDL_CMD_MAX_PIXEL_COUNT ((UDL_CMD_MAX_DATA_SIZE - UDL_CMD_MAX_HEAD_SIZE) / 2)
+#define UDL_CMD_MAX_BUFFERS (3 * UDL_CMD_MAX_FRAMES)
+#define UDL_FONT_HEIGHT 16 /* pixels */
+#define UDL_MAX_MODES 25 /* units */
+
+MALLOC_DECLARE(M_USB_DL);
+
+struct udl_buffer {
+ TAILQ_ENTRY(udl_buffer) entry;
+ uint32_t size;
+};
+
+TAILQ_HEAD(udl_buffer_head, udl_buffer);
+
+struct udl_cmd_buf {
+ TAILQ_ENTRY(udl_cmd_buf) entry;
+ uint32_t off;
+ uint8_t buf[UDL_CMD_MAX_DATA_SIZE] __aligned(4);
+};
+
+TAILQ_HEAD(udl_cmd_head, udl_cmd_buf);
+
+enum {
+ UDL_BULK_WRITE_0,
+ UDL_BULK_WRITE_1,
+ UDL_N_TRANSFER,
+};
+
+/*
+ * Our per device structure.
+ */
+struct udl_softc {
+ struct mtx sc_mtx;
+ struct cv sc_cv;
+ struct callout sc_callout;
+ struct usb_xfer *sc_xfer[UDL_N_TRANSFER];
+ struct usb_device *sc_udev;
+ device_t sc_fbdev;
+ struct fb_info sc_fb_info;
+ uint8_t sc_edid[128];
+ struct edid_info sc_edid_info;
+ struct udl_cmd_head sc_xfer_head[2];
+ struct udl_cmd_head sc_cmd_buf_free;
+ struct udl_cmd_head sc_cmd_buf_pending;
+ struct udl_cmd_buf sc_cmd_buf_temp[UDL_CMD_MAX_BUFFERS];
+ uint32_t sc_sync_off;
+ uint32_t sc_fb_size;
+ uint8_t *sc_fb_addr;
+ uint8_t *sc_fb_copy;
+ int sc_def_chip; /* default chip version */
+ int sc_chip;
+#define DLALL 0x0000
+#define DL125 0x0000 /* max 1280x1024, 1440x900 */
+#define DL120 0x0001 /* max 1280x1024, 1440x1050 */
+#define DL160 0x0002 /* max 1600x1200, 1680x1050 */
+#define DL165 0x0003 /* max 1600x1200, 1920x1080 */
+#define DL195 0x0004 /* max 1920x1200, 2048x1152 */
+#define DLMAX 0x0004
+#define DLUNK 0x00ff /* unknown */
+ int sc_def_mode; /* default mode */
+ int sc_cur_mode;
+ uint8_t sc_power_save; /* set if power save is enabled */
+ uint8_t sc_gone;
+};
+
+#define UDL_LOCK(sc) mtx_lock(&(sc)->sc_mtx)
+#define UDL_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx)
+
+/*
+ * Chip commands.
+ */
+#define UDL_CTRL_CMD_READ_EDID 0x02
+#define UDL_CTRL_CMD_WRITE_1 0x03
+#define UDL_CTRL_CMD_READ_1 0x04
+#define UDL_CTRL_CMD_POLL 0x06
+#define UDL_CTRL_CMD_SET_KEY 0x12
+
+#define UDL_BULK_SOC 0xaf /* start of command token */
+
+#define UDL_BULK_CMD_REG_WRITE_1 0x20 /* write 1 byte to register */
+#define UDL_BULK_CMD_EOC 0xa0 /* end of command stack */
+#define UDL_BULK_CMD_DECOMP 0xe0 /* send decompression table */
+
+#define UDL_BULK_CMD_FB_BASE 0x60
+#define UDL_BULK_CMD_FB_WORD 0x08
+#define UDL_BULK_CMD_FB_COMP 0x10
+#define UDL_BULK_CMD_FB_WRITE (UDL_BULK_CMD_FB_BASE | 0x00)
+#define UDL_BULK_CMD_FB_COPY (UDL_BULK_CMD_FB_BASE | 0x02)
+
+/*
+ * Chip registers.
+ */
+#define UDL_REG_ADDR_START16 0x20
+#define UDL_REG_ADDR_STRIDE16 0x23
+#define UDL_REG_ADDR_START8 0x26
+#define UDL_REG_ADDR_STRIDE8 0x29
+
+#define UDL_REG_SCREEN 0x1f
+#define UDL_REG_SCREEN_ON 0x00
+#define UDL_REG_SCREEN_OFF 0x01
+#define UDL_REG_SYNC 0xff
+
+#define UDL_MODE_SIZE 29
+
+/*
+ * Register values for screen resolution initialization.
+ */
+static const uint8_t udl_reg_vals_640x480_60[UDL_MODE_SIZE] = { /* 25.17 Mhz 59.9 Hz
+ * VESA std */
+ 0x00, 0x99, 0x30, 0x26, 0x94, 0x60, 0xa9, 0xce, 0x60, 0x07, 0xb3, 0x0f,
+ 0x79, 0xff, 0xff, 0x02, 0x80, 0x83, 0xbc, 0xff, 0xfc, 0xff, 0xff, 0x01,
+ 0xe0, 0x01, 0x02, 0xab, 0x13
+};
+static const uint8_t udl_reg_vals_640x480_67[UDL_MODE_SIZE] = { /* 30.25 MHz 66.6 Hz MAC
+ * std */
+ 0x00, 0x1d, 0x33, 0x07, 0xb3, 0x60, 0xa9, 0xce, 0x60, 0xb6, 0xa8, 0xff,
+ 0xff, 0xbf, 0x70, 0x02, 0x80, 0x83, 0xbc, 0xff, 0xff, 0xff, 0xf9, 0x01,
+ 0xe0, 0x01, 0x02, 0xa2, 0x17
+};
+static const uint8_t udl_reg_vals_640x480_72[UDL_MODE_SIZE] = { /* 31.50 Mhz 72.8 Hz
+ * VESA std */
+ 0x00, 0x2b, 0xeb, 0x35, 0xd3, 0x0a, 0x95, 0xe6, 0x0e, 0x0f, 0xb5, 0x15,
+ 0x2a, 0xff, 0xff, 0x02, 0x80, 0xcc, 0x1d, 0xff, 0xf9, 0xff, 0xff, 0x01,
+ 0xe0, 0x01, 0x02, 0x9c, 0x18
+};
+static const uint8_t udl_reg_vals_640x480_75[UDL_MODE_SIZE] = { /* 31.50 Mhz 75.7 Hz
+ * VESA std */
+ 0x00, 0xeb, 0xf7, 0xd3, 0x0f, 0x4f, 0x93, 0xfa, 0x47, 0xb5, 0x58, 0xff,
+ 0xff, 0xbf, 0x70, 0x02, 0x80, 0xf4, 0x8f, 0xff, 0xff, 0xff, 0xf9, 0x01,
+ 0xe0, 0x01, 0x02, 0x9c, 0x18
+};
+static const uint8_t udl_reg_vals_800x480_61[UDL_MODE_SIZE] = { /* 33.00 MHz 61.9 Hz */
+ 0x00, 0x20, 0x3c, 0x7a, 0xc9, 0xf2, 0x6c, 0x48, 0xf9, 0x70, 0x53, 0xff,
+ 0xff, 0x21, 0x27, 0x03, 0x20, 0x91, 0xf3, 0xff, 0xff, 0xff, 0xf9, 0x01,
+ 0xe0, 0x01, 0x02, 0xc8, 0x19
+};
+static const uint8_t udl_reg_vals_800x600_56[UDL_MODE_SIZE] = { /* 36.00 MHz 56.2 Hz
+ * VESA std */
+ 0x00, 0x65, 0x35, 0x48, 0xf4, 0xf2, 0x6c, 0x19, 0x18, 0xc9, 0x4b, 0xff,
+ 0xff, 0x70, 0x35, 0x03, 0x20, 0x32, 0x31, 0xff, 0xff, 0xff, 0xfc, 0x02,
+ 0x58, 0x01, 0x02, 0x20, 0x1c
+};
+static const uint8_t udl_reg_vals_800x600_60[UDL_MODE_SIZE] = { /* 40.00 MHz 60.3 Hz
+ * VESA std */
+ 0x00, 0x20, 0x3c, 0x7a, 0xc9, 0x93, 0x60, 0xc8, 0xc7, 0x70, 0x53, 0xff,
+ 0xff, 0x21, 0x27, 0x03, 0x20, 0x91, 0x8f, 0xff, 0xff, 0xff, 0xf2, 0x02,
+ 0x58, 0x01, 0x02, 0x40, 0x1f
+};
+static const uint8_t udl_reg_vals_800x600_72[UDL_MODE_SIZE] = { /* 50.00 MHz 72.1 Hz
+ * VESA std */
+ 0x00, 0xeb, 0xf7, 0xd1, 0x90, 0x4d, 0x82, 0x23, 0x1f, 0x39, 0xcf, 0xff,
+ 0xff, 0x43, 0x21, 0x03, 0x20, 0x62, 0xc5, 0xff, 0xff, 0xff, 0xca, 0x02,
+ 0x58, 0x01, 0x02, 0x10, 0x27
+};
+static const uint8_t udl_reg_vals_800x600_74[UDL_MODE_SIZE] = { /* 50.00 MHz 74.4 Hz */
+ 0x00, 0xb3, 0x76, 0x39, 0xcf, 0x60, 0xa9, 0xc7, 0xf4, 0x70, 0x53, 0xff,
+ 0xff, 0x35, 0x33, 0x03, 0x20, 0x8f, 0xe9, 0xff, 0xff, 0xff, 0xf9, 0x02,
+ 0x58, 0x01, 0x02, 0x10, 0x27
+};
+static const uint8_t udl_reg_vals_800x600_75[UDL_MODE_SIZE] = { /* 49.50 MHz 75.0 Hz
+ * VESA std */
+ 0x00, 0xb3, 0x76, 0x39, 0xcf, 0xf2, 0x6c, 0x19, 0x18, 0x70, 0x53, 0xff,
+ 0xff, 0x35, 0x33, 0x03, 0x20, 0x32, 0x31, 0xff, 0xff, 0xff, 0xf9, 0x02,
+ 0x58, 0x01, 0x02, 0xac, 0x26
+};
+static const uint8_t udl_reg_vals_1024x768_60[UDL_MODE_SIZE] = { /* 65.00 MHz 60.0 Hz
+ * VESA std */
+ 0x00, 0x36, 0x18, 0xd5, 0x10, 0x60, 0xa9, 0x7b, 0x33, 0xa1, 0x2b, 0x27,
+ 0x32, 0xff, 0xff, 0x04, 0x00, 0xd9, 0x9a, 0xff, 0xca, 0xff, 0xff, 0x03,
+ 0x00, 0x04, 0x03, 0xc8, 0x32
+};
+static const uint8_t udl_reg_vals_1024x768_70[UDL_MODE_SIZE] = { /* 75.00 MHz 70.0 Hz
+ * VESA std */
+ 0x00, 0xb4, 0xed, 0x4c, 0x5e, 0x60, 0xa9, 0x7b, 0x33, 0x10, 0x4d, 0xff,
+ 0xff, 0x27, 0x32, 0x04, 0x00, 0xd9, 0x9a, 0xff, 0xff, 0xff, 0xca, 0x03,
+ 0x00, 0x04, 0x02, 0x98, 0x3a
+};
+static const uint8_t udl_reg_vals_1024x768_75[UDL_MODE_SIZE] = { /* 78.75 MHz 75.0 Hz
+ * VESA std */
+ 0x00, 0xec, 0xb4, 0xa0, 0x4c, 0x36, 0x0a, 0x07, 0xb3, 0x5e, 0xd5, 0xff,
+ 0xff, 0x0f, 0x79, 0x04, 0x00, 0x0f, 0x66, 0xff, 0xff, 0xff, 0xf9, 0x03,
+ 0x00, 0x04, 0x02, 0x86, 0x3d
+};
+static const uint8_t udl_reg_vals_1280x800_60[UDL_MODE_SIZE] = { /* 83.46 MHz 59.9 MHz */
+ 0x00, 0xb2, 0x19, 0x34, 0xdf, 0x93, 0x60, 0x30, 0xfb, 0x9f, 0xca, 0xff,
+ 0xff, 0x27, 0x32, 0x05, 0x00, 0x61, 0xf6, 0xff, 0xff, 0xff, 0xf9, 0x03,
+ 0x20, 0x04, 0x02, 0x34, 0x41
+};
+static const uint8_t udl_reg_vals_1280x960_60[UDL_MODE_SIZE] = { /* 108.00 MHz 60.0 Hz
+ * VESA std */
+ 0x00, 0xa6, 0x03, 0x5c, 0x7e, 0x0a, 0x95, 0x48, 0xf4, 0x61, 0xbd, 0xff,
+ 0xff, 0x94, 0x43, 0x05, 0x00, 0x91, 0xe8, 0xff, 0xff, 0xff, 0xf9, 0x03,
+ 0xc0, 0x04, 0x02, 0x60, 0x54
+};
+static const uint8_t udl_reg_vals_1280x1024_60[UDL_MODE_SIZE] = { /* 108.00 MHz 60.0 Hz
+ * VESA std */
+ 0x00, 0x98, 0xf8, 0x0d, 0x57, 0x2a, 0x55, 0x4d, 0x54, 0xca, 0x0d, 0xff,
+ 0xff, 0x94, 0x43, 0x05, 0x00, 0x9a, 0xa8, 0xff, 0xff, 0xff, 0xf9, 0x04,
+ 0x00, 0x04, 0x02, 0x60, 0x54
+};
+static const uint8_t udl_reg_vals_1280x1024_75[UDL_MODE_SIZE] = { /* 135.00 MHz 75.0 Hz
+ * VESA std */
+ 0x00, 0xce, 0x12, 0x3f, 0x9f, 0x2a, 0x55, 0x4d, 0x54, 0xca, 0x0d, 0xff,
+ 0xff, 0x32, 0x60, 0x05, 0x00, 0x9a, 0xa8, 0xff, 0xff, 0xff, 0xf9, 0x04,
+ 0x00, 0x04, 0x02, 0x78, 0x69
+};
+static const uint8_t udl_reg_vals_1366x768_60[UDL_MODE_SIZE] = { /* 90 MHz 60.0 Hz */
+ 0x01, 0x19, 0x1e, 0x1f, 0xb0, 0x93, 0x60, 0x40, 0x7b, 0x36, 0xe8, 0x27,
+ 0x32, 0xff, 0xff, 0x05, 0x56, 0x03, 0xd9, 0xff, 0xff, 0xfc, 0xa7, 0x03,
+ 0x00, 0x04, 0x02, 0x9a, 0x42
+};
+static const uint8_t udl_reg_vals_1440x900_60[UDL_MODE_SIZE] = { /* 106.47 MHz 59.9 Hz */
+ 0x00, 0x24, 0xce, 0xe7, 0x72, 0x36, 0x0a, 0x86, 0xca, 0x1c, 0x10, 0xff,
+ 0xff, 0x60, 0x3a, 0x05, 0xa0, 0x0d, 0x94, 0xff, 0xff, 0xff, 0xf9, 0x03,
+ 0x84, 0x04, 0x02, 0x2e, 0x53
+};
+static const uint8_t udl_reg_vals_1440x900_59[UDL_MODE_SIZE] = { /* 106.50 MHz 59.8 Hz */
+ 0x00, 0x24, 0xce, 0xe7, 0x72, 0xd8, 0x2a, 0x1b, 0x28, 0x1c, 0x10, 0xff,
+ 0xff, 0x60, 0x3a, 0x05, 0xa0, 0x36, 0x50, 0xff, 0xff, 0xff, 0xf9, 0x03,
+ 0x84, 0x04, 0x02, 0x34, 0x53
+};
+static const uint8_t udl_reg_vals_1440x900_75[UDL_MODE_SIZE] = { /* 136.49 MHz 75.0 Hz */
+ 0x00, 0x73, 0xa6, 0x14, 0xea, 0x0a, 0x95, 0xca, 0x10, 0x7f, 0x46, 0xff,
+ 0xff, 0x60, 0x3a, 0x05, 0xa0, 0x94, 0x20, 0xff, 0xff, 0xff, 0xf9, 0x03,
+ 0x84, 0x04, 0x02, 0xa2, 0x6a
+};
+static const uint8_t udl_reg_vals_1680x1050_60[UDL_MODE_SIZE] = { /* 147.14 MHz 60.0 Hz */
+ 0x00, 0x53, 0x43, 0xa6, 0x71, 0xc1, 0x52, 0xd9, 0x29, 0x69, 0x9f, 0xff,
+ 0xff, 0xd7, 0xee, 0x06, 0x90, 0xb2, 0x53, 0xff, 0xff, 0xff, 0xf9, 0x04,
+ 0x1a, 0x04, 0x02, 0xf4, 0x72
+};
+static const uint8_t udl_reg_vals_1600x1200_60[UDL_MODE_SIZE] = { /* 162.00 MHz 60.0 Hz
+ * VESA std */
+ 0x00, 0xcf, 0xa4, 0x3c, 0x4e, 0x55, 0x73, 0x71, 0x2b, 0x71, 0x52, 0xff,
+ 0xff, 0xee, 0xca, 0x06, 0x40, 0xe2, 0x57, 0xff, 0xff, 0xff, 0xf9, 0x04,
+ 0xb0, 0x04, 0x02, 0x90, 0x7e
+};
+static const uint8_t udl_reg_vals_1920x1080_60[UDL_MODE_SIZE] = { /* 138.50 MHz 59.9 Hz */
+ 0x00, 0x73, 0xa6, 0x28, 0xb3, 0x54, 0xaa, 0x41, 0x5d, 0x0d, 0x9f, 0x32,
+ 0x60, 0xff, 0xff, 0x07, 0x80, 0x0a, 0xea, 0xff, 0xf9, 0xff, 0xff, 0x04,
+ 0x38, 0x04, 0x02, 0xe0, 0x7c
+};
+
+struct udl_mode {
+ uint16_t hdisplay;
+ uint16_t vdisplay;
+ uint8_t hz;
+ uint16_t chip;
+ uint32_t clock;
+ const uint8_t *mode;
+};
+
+static const struct udl_mode udl_modes[UDL_MAX_MODES] = {
+ {640, 480, 60, DLALL, 2520, udl_reg_vals_640x480_60},
+ {640, 480, 67, DLALL, 3025, udl_reg_vals_640x480_67},
+ {640, 480, 72, DLALL, 3150, udl_reg_vals_640x480_72},
+ {640, 480, 75, DLALL, 3150, udl_reg_vals_640x480_75},
+ {800, 480, 59, DLALL, 5000, udl_reg_vals_800x480_61},
+ {800, 480, 61, DLALL, 3300, udl_reg_vals_800x480_61},
+ {800, 600, 56, DLALL, 3600, udl_reg_vals_800x600_56},
+ {800, 600, 60, DLALL, 4000, udl_reg_vals_800x600_60},
+ {800, 600, 72, DLALL, 5000, udl_reg_vals_800x600_72},
+ {800, 600, 74, DLALL, 5000, udl_reg_vals_800x600_74},
+ {800, 600, 75, DLALL, 4950, udl_reg_vals_800x600_75},
+ {1024, 768, 60, DLALL, 6500, udl_reg_vals_1024x768_60},
+ {1024, 768, 70, DLALL, 7500, udl_reg_vals_1024x768_70},
+ {1024, 768, 75, DLALL, 7850, udl_reg_vals_1024x768_75},
+ {1280, 800, 60, DLALL, 8346, udl_reg_vals_1280x800_60},
+ {1280, 960, 60, DLALL, 10800, udl_reg_vals_1280x960_60},
+ {1280, 1024, 60, DLALL, 10800, udl_reg_vals_1280x1024_60},
+ {1280, 1024, 75, DLALL, 13500, udl_reg_vals_1280x1024_75},
+ {1366, 768, 60, DLALL, 9000, udl_reg_vals_1366x768_60},
+ {1440, 900, 59, DL125, 10650, udl_reg_vals_1440x900_59},
+ {1440, 900, 60, DL125, 10647, udl_reg_vals_1440x900_60},
+ {1440, 900, 75, DL125, 13649, udl_reg_vals_1440x900_75},
+ {1680, 1050, 60, DL160, 14714, udl_reg_vals_1680x1050_60},
+ {1600, 1200, 60, DL160, 16200, udl_reg_vals_1600x1200_60},
+ {1920, 1080, 60, DL165, 13850, udl_reg_vals_1920x1080_60}
+};
+
+/*
+ * Encryption.
+ */
+static const uint8_t udl_null_key_1[] = {
+ 0x57, 0xcd, 0xdc, 0xa7, 0x1c, 0x88, 0x5e, 0x15, 0x60, 0xfe, 0xc6, 0x97,
+ 0x16, 0x3d, 0x47, 0xf2
+};
+
+#endif /* _UDL_H_ */
diff --git a/sys/dev/usb/wlan/if_mtw.c b/sys/dev/usb/wlan/if_mtw.c
new file mode 100644
index 000000000000..137590651948
--- /dev/null
+++ b/sys/dev/usb/wlan/if_mtw.c
@@ -0,0 +1,4685 @@
+/*-
+ * Copyright (c) 2008-2010 Damien Bergamini <damien.bergamini@free.fr>
+ * Copyright (c) 2013-2014 Kevin Lo
+ * Copyright (c) 2021 James Hastings
+ * Ported to FreeBSD by Jesper Schmitz Mouridsen jsm@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.
+ */
+
+/*
+ * MediaTek MT7601U 802.11b/g/n WLAN.
+ */
+
+#include "opt_wlan.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/endian.h>
+#include <sys/eventhandler.h>
+#include <sys/firmware.h>
+#include <sys/kdb.h>
+#include <sys/kernel.h>
+#include <sys/linker.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/socket.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+
+#include <net/bpf.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+#include <net/if_var.h>
+#include <net80211/ieee80211_var.h>
+#include <net80211/ieee80211_radiotap.h>
+#include <net80211/ieee80211_ratectl.h>
+#include <net80211/ieee80211_regdomain.h>
+#ifdef IEEE80211_SUPPORT_SUPERG
+#include <net80211/ieee80211_superg.h>
+#endif
+#include <netinet/if_ether.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR mtw_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_msctest.h>
+
+#include "if_mtwreg.h"
+#include "if_mtwvar.h"
+
+#define MTW_DEBUG
+
+#ifdef MTW_DEBUG
+int mtw_debug;
+static SYSCTL_NODE(_hw_usb, OID_AUTO, mtw, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB mtw");
+SYSCTL_INT(_hw_usb_mtw, OID_AUTO, debug, CTLFLAG_RWTUN, &mtw_debug, 0,
+ "mtw debug level");
+
+enum {
+ MTW_DEBUG_XMIT = 0x00000001, /* basic xmit operation */
+ MTW_DEBUG_XMIT_DESC = 0x00000002, /* xmit descriptors */
+ MTW_DEBUG_RECV = 0x00000004, /* basic recv operation */
+ MTW_DEBUG_RECV_DESC = 0x00000008, /* recv descriptors */
+ MTW_DEBUG_STATE = 0x00000010, /* 802.11 state transitions */
+ MTW_DEBUG_RATE = 0x00000020, /* rate adaptation */
+ MTW_DEBUG_USB = 0x00000040, /* usb requests */
+ MTW_DEBUG_FIRMWARE = 0x00000080, /* firmware(9) loading debug */
+ MTW_DEBUG_BEACON = 0x00000100, /* beacon handling */
+ MTW_DEBUG_INTR = 0x00000200, /* ISR */
+ MTW_DEBUG_TEMP = 0x00000400, /* temperature calibration */
+ MTW_DEBUG_ROM = 0x00000800, /* various ROM info */
+ MTW_DEBUG_KEY = 0x00001000, /* crypto keys management */
+ MTW_DEBUG_TXPWR = 0x00002000, /* dump Tx power values */
+ MTW_DEBUG_RSSI = 0x00004000, /* dump RSSI lookups */
+ MTW_DEBUG_RESET = 0x00008000, /* initialization progress */
+ MTW_DEBUG_CALIB = 0x00010000, /* calibration progress */
+ MTW_DEBUG_CMD = 0x00020000, /* command queue */
+ MTW_DEBUG_ANY = 0xffffffff
+};
+
+#define MTW_DPRINTF(_sc, _m, ...) \
+ do { \
+ if (mtw_debug & (_m)) \
+ device_printf((_sc)->sc_dev, __VA_ARGS__); \
+ } while (0)
+
+#else
+#define MTW_DPRINTF(_sc, _m, ...) \
+ do { \
+ (void)_sc; \
+ } while (0)
+#endif
+
+#define IEEE80211_HAS_ADDR4(wh) IEEE80211_IS_DSTODS(wh)
+
+/* NB: "11" is the maximum number of padding bytes needed for Tx */
+#define MTW_MAX_TXSZ \
+ (sizeof(struct mtw_txd) + sizeof(struct mtw_txwi) + MCLBYTES + 11)
+
+/*
+ * Because of LOR in mtw_key_delete(), use atomic instead.
+ * '& MTW_CMDQ_MASQ' is to loop cmdq[].
+ */
+#define MTW_CMDQ_GET(c) (atomic_fetchadd_32((c), 1) & MTW_CMDQ_MASQ)
+
+static const STRUCT_USB_HOST_ID mtw_devs[] = {
+#define MTW_DEV(v, p) \
+ { \
+ USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) \
+ }
+ MTW_DEV(EDIMAX, MT7601U),
+ MTW_DEV(RALINK, MT7601U),
+ MTW_DEV(XIAOMI, MT7601U)
+};
+#undef MTW_DEV
+
+static device_probe_t mtw_match;
+static device_attach_t mtw_attach;
+static device_detach_t mtw_detach;
+
+static usb_callback_t mtw_bulk_rx_callback;
+static usb_callback_t mtw_bulk_tx_callback0;
+static usb_callback_t mtw_bulk_tx_callback1;
+static usb_callback_t mtw_bulk_tx_callback2;
+static usb_callback_t mtw_bulk_tx_callback3;
+static usb_callback_t mtw_bulk_tx_callback4;
+static usb_callback_t mtw_bulk_tx_callback5;
+static usb_callback_t mtw_fw_callback;
+
+static void mtw_autoinst(void *, struct usb_device *, struct usb_attach_arg *);
+static int mtw_driver_loaded(struct module *, int, void *);
+static void mtw_bulk_tx_callbackN(struct usb_xfer *xfer, usb_error_t error,
+ u_int index);
+static struct ieee80211vap *mtw_vap_create(struct ieee80211com *,
+ const char[IFNAMSIZ], int, enum ieee80211_opmode, int,
+ const uint8_t[IEEE80211_ADDR_LEN], const uint8_t[IEEE80211_ADDR_LEN]);
+static void mtw_vap_delete(struct ieee80211vap *);
+static void mtw_cmdq_cb(void *, int);
+static void mtw_setup_tx_list(struct mtw_softc *, struct mtw_endpoint_queue *);
+static void mtw_unsetup_tx_list(struct mtw_softc *,
+ struct mtw_endpoint_queue *);
+static void mtw_load_microcode(void *arg);
+
+static usb_error_t mtw_do_request(struct mtw_softc *,
+ struct usb_device_request *, void *);
+static int mtw_read(struct mtw_softc *, uint16_t, uint32_t *);
+static int mtw_read_region_1(struct mtw_softc *, uint16_t, uint8_t *, int);
+static int mtw_write_2(struct mtw_softc *, uint16_t, uint16_t);
+static int mtw_write(struct mtw_softc *, uint16_t, uint32_t);
+static int mtw_write_region_1(struct mtw_softc *, uint16_t, uint8_t *, int);
+static int mtw_set_region_4(struct mtw_softc *, uint16_t, uint32_t, int);
+static int mtw_efuse_read_2(struct mtw_softc *, uint16_t, uint16_t *);
+static int mtw_bbp_read(struct mtw_softc *, uint8_t, uint8_t *);
+static int mtw_bbp_write(struct mtw_softc *, uint8_t, uint8_t);
+static int mtw_mcu_cmd(struct mtw_softc *sc, uint8_t cmd, void *buf, int len);
+static void mtw_get_txpower(struct mtw_softc *);
+static int mtw_read_eeprom(struct mtw_softc *);
+static struct ieee80211_node *mtw_node_alloc(struct ieee80211vap *,
+ const uint8_t mac[IEEE80211_ADDR_LEN]);
+static int mtw_media_change(if_t);
+static int mtw_newstate(struct ieee80211vap *, enum ieee80211_state, int);
+static int mtw_wme_update(struct ieee80211com *);
+static void mtw_key_set_cb(void *);
+static int mtw_key_set(struct ieee80211vap *, struct ieee80211_key *);
+static void mtw_key_delete_cb(void *);
+static int mtw_key_delete(struct ieee80211vap *, struct ieee80211_key *);
+static void mtw_ratectl_to(void *);
+static void mtw_ratectl_cb(void *, int);
+static void mtw_drain_fifo(void *);
+static void mtw_iter_func(void *, struct ieee80211_node *);
+static void mtw_newassoc_cb(void *);
+static void mtw_newassoc(struct ieee80211_node *, int);
+static int mtw_mcu_radio(struct mtw_softc *sc, int func, uint32_t val);
+static void mtw_recv_mgmt(struct ieee80211_node *, struct mbuf *, int,
+ const struct ieee80211_rx_stats *, int, int);
+static void mtw_rx_frame(struct mtw_softc *, struct mbuf *, uint32_t);
+static void mtw_tx_free(struct mtw_endpoint_queue *pq, struct mtw_tx_data *,
+ int);
+static void mtw_set_tx_desc(struct mtw_softc *, struct mtw_tx_data *);
+static int mtw_tx(struct mtw_softc *, struct mbuf *, struct ieee80211_node *);
+static int mtw_tx_mgt(struct mtw_softc *, struct mbuf *,
+ struct ieee80211_node *);
+static int mtw_sendprot(struct mtw_softc *, const struct mbuf *,
+ struct ieee80211_node *, int, int);
+static int mtw_tx_param(struct mtw_softc *, struct mbuf *,
+ struct ieee80211_node *, const struct ieee80211_bpf_params *);
+static int mtw_raw_xmit(struct ieee80211_node *, struct mbuf *,
+ const struct ieee80211_bpf_params *);
+static int mtw_transmit(struct ieee80211com *, struct mbuf *);
+static void mtw_start(struct mtw_softc *);
+static void mtw_parent(struct ieee80211com *);
+static void mtw_select_chan_group(struct mtw_softc *, int);
+
+static int mtw_set_chan(struct mtw_softc *, struct ieee80211_channel *);
+static void mtw_set_channel(struct ieee80211com *);
+static void mtw_getradiocaps(struct ieee80211com *, int, int *,
+ struct ieee80211_channel[]);
+static void mtw_scan_start(struct ieee80211com *);
+static void mtw_scan_end(struct ieee80211com *);
+static void mtw_update_beacon(struct ieee80211vap *, int);
+static void mtw_update_beacon_cb(void *);
+static void mtw_updateprot(struct ieee80211com *);
+static void mtw_updateprot_cb(void *);
+static void mtw_usb_timeout_cb(void *);
+static int mtw_reset(struct mtw_softc *sc);
+static void mtw_enable_tsf_sync(struct mtw_softc *);
+
+
+static void mtw_enable_mrr(struct mtw_softc *);
+static void mtw_set_txpreamble(struct mtw_softc *);
+static void mtw_set_basicrates(struct mtw_softc *);
+static void mtw_set_leds(struct mtw_softc *, uint16_t);
+static void mtw_set_bssid(struct mtw_softc *, const uint8_t *);
+static void mtw_set_macaddr(struct mtw_softc *, const uint8_t *);
+static void mtw_updateslot(struct ieee80211com *);
+static void mtw_updateslot_cb(void *);
+static void mtw_update_mcast(struct ieee80211com *);
+static int8_t mtw_rssi2dbm(struct mtw_softc *, uint8_t, uint8_t);
+static void mtw_update_promisc_locked(struct mtw_softc *);
+static void mtw_update_promisc(struct ieee80211com *);
+static int mtw_txrx_enable(struct mtw_softc *);
+static void mtw_init_locked(struct mtw_softc *);
+static void mtw_stop(void *);
+static void mtw_delay(struct mtw_softc *, u_int);
+static void mtw_update_chw(struct ieee80211com *ic);
+static int mtw_ampdu_enable(struct ieee80211_node *ni,
+ struct ieee80211_tx_ampdu *tap);
+
+static eventhandler_tag mtw_etag;
+
+static const struct {
+ uint8_t reg;
+ uint8_t val;
+} mt7601_rf_bank0[] = { MT7601_BANK0_RF },
+ mt7601_rf_bank4[] = { MT7601_BANK4_RF },
+ mt7601_rf_bank5[] = { MT7601_BANK5_RF };
+static const struct {
+ uint32_t reg;
+ uint32_t val;
+} mt7601_def_mac[] = { MT7601_DEF_MAC };
+static const struct {
+ uint8_t reg;
+ uint8_t val;
+} mt7601_def_bbp[] = { MT7601_DEF_BBP };
+
+
+static const struct {
+ u_int chan;
+ uint8_t r17, r18, r19, r20;
+} mt7601_rf_chan[] = { MT7601_RF_CHAN };
+
+
+static const struct usb_config mtw_config[MTW_N_XFER] = {
+ [MTW_BULK_RX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = MTW_MAX_RXSZ,
+ .flags = {.pipe_bof = 1,
+ .short_xfer_ok = 1,},
+ .callback = mtw_bulk_rx_callback,
+ },
+ [MTW_BULK_TX_BE] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = MTW_MAX_TXSZ,
+ .flags = {.pipe_bof = 1,
+ .force_short_xfer = 0,},
+ .callback = mtw_bulk_tx_callback0,
+ .timeout = 5000, /* ms */
+ },
+ [MTW_BULK_TX_BK] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = MTW_MAX_TXSZ,
+ .flags = {.pipe_bof = 1,
+ .force_short_xfer = 1,},
+ .callback = mtw_bulk_tx_callback1,
+ .timeout = 5000, /* ms */
+ },
+ [MTW_BULK_TX_VI] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = MTW_MAX_TXSZ,
+ .flags = {.pipe_bof = 1,
+ .force_short_xfer = 1,},
+ .callback = mtw_bulk_tx_callback2,
+ .timeout = 5000, /* ms */
+ },
+ [MTW_BULK_TX_VO] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = MTW_MAX_TXSZ,
+ .flags = {.pipe_bof = 1,
+ .force_short_xfer = 1,},
+ .callback = mtw_bulk_tx_callback3,
+ .timeout = 5000, /* ms */
+ },
+ [MTW_BULK_TX_HCCA] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = MTW_MAX_TXSZ,
+ .flags = {.pipe_bof = 1,
+ .force_short_xfer = 1, .no_pipe_ok = 1,},
+ .callback = mtw_bulk_tx_callback4,
+ .timeout = 5000, /* ms */
+ },
+ [MTW_BULK_TX_PRIO] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = MTW_MAX_TXSZ,
+ .flags = {.pipe_bof = 1,
+ .force_short_xfer = 1, .no_pipe_ok = 1,},
+ .callback = mtw_bulk_tx_callback5,
+ .timeout = 5000, /* ms */
+ },
+
+ [MTW_BULK_FW_CMD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = 0x2c44,
+ .flags = {.pipe_bof = 1,
+ .force_short_xfer = 1, .no_pipe_ok = 1,},
+ .callback = mtw_fw_callback,
+
+ },
+
+ [MTW_BULK_RAW_TX] = {
+ .type = UE_BULK,
+ .ep_index = 0,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = MTW_MAX_TXSZ,
+ .flags = {.pipe_bof = 1,
+ .force_short_xfer = 1, .no_pipe_ok = 1,},
+ .callback = mtw_bulk_tx_callback0,
+ .timeout = 5000, /* ms */
+ },
+
+};
+static uint8_t mtw_wme_ac_xfer_map[4] = {
+ [WME_AC_BE] = MTW_BULK_TX_BE,
+ [WME_AC_BK] = MTW_BULK_TX_BK,
+ [WME_AC_VI] = MTW_BULK_TX_VI,
+ [WME_AC_VO] = MTW_BULK_TX_VO,
+};
+static void
+mtw_autoinst(void *arg, struct usb_device *udev, struct usb_attach_arg *uaa)
+{
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *id;
+
+ if (uaa->dev_state != UAA_DEV_READY)
+ return;
+
+ iface = usbd_get_iface(udev, 0);
+ if (iface == NULL)
+ return;
+ id = iface->idesc;
+ if (id == NULL || id->bInterfaceClass != UICLASS_MASS)
+ return;
+ if (usbd_lookup_id_by_uaa(mtw_devs, sizeof(mtw_devs), uaa))
+ return;
+
+ if (usb_msc_eject(udev, 0, MSC_EJECT_STOPUNIT) == 0)
+ uaa->dev_state = UAA_DEV_EJECTING;
+}
+
+static int
+mtw_driver_loaded(struct module *mod, int what, void *arg)
+{
+ switch (what) {
+ case MOD_LOAD:
+ mtw_etag = EVENTHANDLER_REGISTER(usb_dev_configured,
+ mtw_autoinst, NULL, EVENTHANDLER_PRI_ANY);
+ break;
+ case MOD_UNLOAD:
+ EVENTHANDLER_DEREGISTER(usb_dev_configured, mtw_etag);
+ break;
+ default:
+ return (EOPNOTSUPP);
+ }
+ return (0);
+}
+
+static const char *
+mtw_get_rf(int rev)
+{
+ switch (rev) {
+ case MT7601_RF_7601:
+ return ("MT7601");
+ case MT7610_RF_7610:
+ return ("MT7610");
+ case MT7612_RF_7612:
+ return ("MT7612");
+ }
+ return ("unknown");
+}
+static int
+mtw_wlan_enable(struct mtw_softc *sc, int enable)
+{
+ uint32_t tmp;
+ int error = 0;
+
+ if (enable) {
+ mtw_read(sc, MTW_WLAN_CTRL, &tmp);
+ if (sc->asic_ver == 0x7612)
+ tmp &= ~0xfffff000;
+
+ tmp &= ~MTW_WLAN_CLK_EN;
+ tmp |= MTW_WLAN_EN;
+ mtw_write(sc, MTW_WLAN_CTRL, tmp);
+ mtw_delay(sc, 2);
+
+ tmp |= MTW_WLAN_CLK_EN;
+ if (sc->asic_ver == 0x7612) {
+ tmp |= (MTW_WLAN_RESET | MTW_WLAN_RESET_RF);
+ }
+ mtw_write(sc, MTW_WLAN_CTRL, tmp);
+ mtw_delay(sc, 2);
+
+ mtw_read(sc, MTW_OSC_CTRL, &tmp);
+ tmp |= MTW_OSC_EN;
+ mtw_write(sc, MTW_OSC_CTRL, tmp);
+ tmp |= MTW_OSC_CAL_REQ;
+ mtw_write(sc, MTW_OSC_CTRL, tmp);
+ } else {
+ mtw_read(sc, MTW_WLAN_CTRL, &tmp);
+ tmp &= ~(MTW_WLAN_CLK_EN | MTW_WLAN_EN);
+ mtw_write(sc, MTW_WLAN_CTRL, tmp);
+
+ mtw_read(sc, MTW_OSC_CTRL, &tmp);
+ tmp &= ~MTW_OSC_EN;
+ mtw_write(sc, MTW_OSC_CTRL, tmp);
+ }
+ return (error);
+}
+
+static int
+mtw_read_cfg(struct mtw_softc *sc, uint16_t reg, uint32_t *val)
+{
+ usb_device_request_t req;
+ uint32_t tmp;
+ uint16_t actlen;
+ int error;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = MTW_READ_CFG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 4);
+ error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, &tmp, 0,
+ &actlen, 1000);
+
+ if (error == 0)
+ *val = le32toh(tmp);
+ else
+ *val = 0xffffffff;
+ return (error);
+}
+
+static int
+mtw_match(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != 0)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != 0)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(mtw_devs, sizeof(mtw_devs), uaa));
+}
+
+static int
+mtw_attach(device_t self)
+{
+ struct mtw_softc *sc = device_get_softc(self);
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t ver;
+ int i, ret;
+ uint32_t tmp;
+ uint8_t iface_index;
+ int ntries, error;
+
+ device_set_usb_desc(self);
+ sc->sc_udev = uaa->device;
+ sc->sc_dev = self;
+ sc->sc_sent = 0;
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev),
+ MTX_NETWORK_LOCK, MTX_DEF);
+
+ iface_index = 0;
+
+ error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer,
+ mtw_config, MTW_N_XFER, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(sc->sc_dev,
+ "could not allocate USB transfers, "
+ "err=%s\n",
+ usbd_errstr(error));
+ goto detach;
+ }
+ for (i = 0; i < 4; i++) {
+ sc->txd_fw[i] = (struct mtw_txd_fw *)
+ malloc(sizeof(struct mtw_txd_fw),
+ M_USBDEV, M_NOWAIT | M_ZERO);
+ }
+ MTW_LOCK(sc);
+ sc->sc_idx = 0;
+ mbufq_init(&sc->sc_snd, ifqmaxlen);
+
+ /*enable WLAN core */
+ if ((error = mtw_wlan_enable(sc, 1)) != 0) {
+ device_printf(sc->sc_dev, "could not enable WLAN core\n");
+ return (ENXIO);
+ }
+
+ /* wait for the chip to settle */
+ DELAY(100);
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (mtw_read(sc, MTW_ASIC_VER, &ver) != 0) {
+ goto detach;
+ }
+ if (ver != 0 && ver != 0xffffffff)
+ break;
+ DELAY(10);
+ }
+ if (ntries == 100) {
+ device_printf(sc->sc_dev,
+ "timeout waiting for NIC to initialize\n");
+ goto detach;
+ }
+ sc->asic_ver = ver >> 16;
+ sc->asic_rev = ver & 0xffff;
+ DELAY(100);
+ if (sc->asic_ver != 0x7601) {
+ device_printf(sc->sc_dev,
+ "Your revision 0x04%x is not supported yet\n",
+ sc->asic_rev);
+ goto detach;
+ }
+
+
+ if (mtw_read(sc, MTW_MAC_VER_ID, &tmp) != 0)
+ goto detach;
+ sc->mac_rev = tmp & 0xffff;
+
+ mtw_load_microcode(sc);
+ ret = msleep(&sc->fwloading, &sc->sc_mtx, 0, "fwload", 3 * hz);
+ if (ret == EWOULDBLOCK || sc->fwloading != 1) {
+ device_printf(sc->sc_dev,
+ "timeout waiting for MCU to initialize\n");
+ goto detach;
+ }
+
+ sc->sc_srom_read = mtw_efuse_read_2;
+ /* retrieve RF rev. no and various other things from EEPROM */
+ mtw_read_eeprom(sc);
+
+ device_printf(sc->sc_dev,
+ "MAC/BBP RT%04X (rev 0x%04X), RF %s (MIMO %dT%dR), address %s\n",
+ sc->asic_ver, sc->mac_rev, mtw_get_rf(sc->rf_rev), sc->ntxchains,
+ sc->nrxchains, ether_sprintf(ic->ic_macaddr));
+ DELAY(100);
+
+ //mtw_set_leds(sc,5);
+ // mtw_mcu_radio(sc,0x31,0);
+ MTW_UNLOCK(sc);
+
+
+ ic->ic_softc = sc;
+ ic->ic_name = device_get_nameunit(self);
+ ic->ic_phytype = IEEE80211_T_OFDM; /* not only, but not used */
+ ic->ic_opmode = IEEE80211_M_STA; /* default to BSS mode */
+
+ ic->ic_caps = IEEE80211_C_STA | /* station mode supported */
+ IEEE80211_C_MONITOR | /* monitor mode supported */
+ IEEE80211_C_IBSS |
+ IEEE80211_C_HOSTAP |
+ IEEE80211_C_WDS | /* 4-address traffic works */
+ IEEE80211_C_MBSS |
+ IEEE80211_C_SHPREAMBLE | /* short preamble supported */
+ IEEE80211_C_SHSLOT | /* short slot time supported */
+ IEEE80211_C_WME | /* WME */
+ IEEE80211_C_WPA; /* WPA1|WPA2(RSN) */
+ device_printf(sc->sc_dev, "[HT] Enabling 802.11n\n");
+ ic->ic_htcaps = IEEE80211_HTC_HT
+ | IEEE80211_HTC_AMPDU
+ | IEEE80211_HTC_AMSDU
+ | IEEE80211_HTCAP_MAXAMSDU_3839
+ | IEEE80211_HTCAP_SMPS_OFF;
+
+ ic->ic_rxstream = sc->nrxchains;
+ ic->ic_txstream = sc->ntxchains;
+
+ ic->ic_cryptocaps = IEEE80211_CRYPTO_WEP | IEEE80211_CRYPTO_AES_CCM |
+ IEEE80211_CRYPTO_AES_OCB | IEEE80211_CRYPTO_TKIP |
+ IEEE80211_CRYPTO_TKIPMIC;
+
+ ic->ic_flags |= IEEE80211_F_DATAPAD;
+ ic->ic_flags_ext |= IEEE80211_FEXT_SWBMISS;
+
+ mtw_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans,
+ ic->ic_channels);
+
+ ieee80211_ifattach(ic);
+
+ ic->ic_scan_start = mtw_scan_start;
+ ic->ic_scan_end = mtw_scan_end;
+ ic->ic_set_channel = mtw_set_channel;
+ ic->ic_getradiocaps = mtw_getradiocaps;
+ ic->ic_node_alloc = mtw_node_alloc;
+ ic->ic_newassoc = mtw_newassoc;
+ ic->ic_update_mcast = mtw_update_mcast;
+ ic->ic_updateslot = mtw_updateslot;
+ ic->ic_wme.wme_update = mtw_wme_update;
+ ic->ic_raw_xmit = mtw_raw_xmit;
+ ic->ic_update_promisc = mtw_update_promisc;
+ ic->ic_vap_create = mtw_vap_create;
+ ic->ic_vap_delete = mtw_vap_delete;
+ ic->ic_transmit = mtw_transmit;
+ ic->ic_parent = mtw_parent;
+
+ ic->ic_update_chw = mtw_update_chw;
+ ic->ic_ampdu_enable = mtw_ampdu_enable;
+
+ ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr,
+ sizeof(sc->sc_txtap), MTW_TX_RADIOTAP_PRESENT,
+ &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap),
+ MTW_RX_RADIOTAP_PRESENT);
+ TASK_INIT(&sc->cmdq_task, 0, mtw_cmdq_cb, sc);
+ TASK_INIT(&sc->ratectl_task, 0, mtw_ratectl_cb, sc);
+ usb_callout_init_mtx(&sc->ratectl_ch, &sc->sc_mtx, 0);
+
+ if (bootverbose)
+ ieee80211_announce(ic);
+
+ return (0);
+
+detach:
+ MTW_UNLOCK(sc);
+ mtw_detach(self);
+ return (ENXIO);
+}
+
+static void
+mtw_drain_mbufq(struct mtw_softc *sc)
+{
+ struct mbuf *m;
+ struct ieee80211_node *ni;
+
+ MTW_LOCK_ASSERT(sc, MA_OWNED);
+ while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
+ ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
+ m->m_pkthdr.rcvif = NULL;
+ ieee80211_free_node(ni);
+ m_freem(m);
+ }
+}
+
+static int
+mtw_detach(device_t self)
+{
+ struct mtw_softc *sc = device_get_softc(self);
+ struct ieee80211com *ic = &sc->sc_ic;
+ int i;
+ MTW_LOCK(sc);
+ mtw_reset(sc);
+ DELAY(10000);
+ sc->sc_detached = 1;
+ MTW_UNLOCK(sc);
+
+
+ /* stop all USB transfers */
+ for (i = 0; i < MTW_N_XFER; i++)
+ usbd_transfer_drain(sc->sc_xfer[i]);
+
+ MTW_LOCK(sc);
+ sc->ratectl_run = MTW_RATECTL_OFF;
+ sc->cmdq_run = sc->cmdq_key_set = MTW_CMDQ_ABORT;
+
+ /* free TX list, if any */
+ if (ic->ic_nrunning > 0)
+ for (i = 0; i < MTW_EP_QUEUES; i++)
+ mtw_unsetup_tx_list(sc, &sc->sc_epq[i]);
+
+ /* Free TX queue */
+ mtw_drain_mbufq(sc);
+ MTW_UNLOCK(sc);
+ if (sc->sc_ic.ic_softc == sc) {
+ /* drain tasks */
+ usb_callout_drain(&sc->ratectl_ch);
+ ieee80211_draintask(ic, &sc->cmdq_task);
+ ieee80211_draintask(ic, &sc->ratectl_task);
+ ieee80211_ifdetach(ic);
+ }
+ for (i = 0; i < 4; i++) {
+ free(sc->txd_fw[i], M_USBDEV);
+ }
+ firmware_unregister("/mediatek/mt7601u");
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static struct ieee80211vap *
+mtw_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
+ enum ieee80211_opmode opmode, int flags,
+ const uint8_t bssid[IEEE80211_ADDR_LEN],
+ const uint8_t mac[IEEE80211_ADDR_LEN])
+{
+ struct mtw_softc *sc = ic->ic_softc;
+ struct mtw_vap *rvp;
+ struct ieee80211vap *vap;
+ int i;
+
+ if (sc->rvp_cnt >= MTW_VAP_MAX) {
+ device_printf(sc->sc_dev, "number of VAPs maxed out\n");
+ return (NULL);
+ }
+
+ switch (opmode) {
+ case IEEE80211_M_STA:
+ /* enable s/w bmiss handling for sta mode */
+ flags |= IEEE80211_CLONE_NOBEACONS;
+ /* fall though */
+ case IEEE80211_M_IBSS:
+ case IEEE80211_M_MONITOR:
+ case IEEE80211_M_HOSTAP:
+ case IEEE80211_M_MBSS:
+ /* other than WDS vaps, only one at a time */
+ if (!TAILQ_EMPTY(&ic->ic_vaps))
+ return (NULL);
+ break;
+ case IEEE80211_M_WDS:
+ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
+ if (vap->iv_opmode != IEEE80211_M_HOSTAP)
+ continue;
+ /* WDS vap's always share the local mac address. */
+ flags &= ~IEEE80211_CLONE_BSSID;
+ break;
+ }
+ if (vap == NULL) {
+ device_printf(sc->sc_dev,
+ "wds only supported in ap mode\n");
+ return (NULL);
+ }
+ break;
+ default:
+ device_printf(sc->sc_dev, "unknown opmode %d\n", opmode);
+ return (NULL);
+ }
+
+ rvp = malloc(sizeof(struct mtw_vap), M_80211_VAP, M_WAITOK | M_ZERO);
+ vap = &rvp->vap;
+
+ if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid) !=
+ 0) {
+ /* out of memory */
+ free(rvp, M_80211_VAP);
+ return (NULL);
+ }
+
+ vap->iv_update_beacon = mtw_update_beacon;
+ vap->iv_max_aid = MTW_WCID_MAX;
+
+ /*
+ * The linux rt2800 driver limits 1 stream devices to a 32KB
+ * RX AMPDU.
+ */
+ if (ic->ic_rxstream > 1)
+ vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_64K;
+ else
+ vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_64K;
+ vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_2; /* 2uS */
+
+ /*
+ * To delete the right key from h/w, we need wcid.
+ * Luckily, there is unused space in ieee80211_key{}, wk_pad,
+ * and matching wcid will be written into there. So, cast
+ * some spells to remove 'const' from ieee80211_key{}
+ */
+ vap->iv_key_delete = (void *)mtw_key_delete;
+ vap->iv_key_set = (void *)mtw_key_set;
+
+ // override state transition machine
+ rvp->newstate = vap->iv_newstate;
+ vap->iv_newstate = mtw_newstate;
+ if (opmode == IEEE80211_M_IBSS) {
+ rvp->recv_mgmt = vap->iv_recv_mgmt;
+ vap->iv_recv_mgmt = mtw_recv_mgmt;
+ }
+
+ ieee80211_ratectl_init(vap);
+ ieee80211_ratectl_setinterval(vap, 1000); // 1 second
+
+ /* complete setup */
+ ieee80211_vap_attach(vap, mtw_media_change, ieee80211_media_status,
+ mac);
+
+ /* make sure id is always unique */
+ for (i = 0; i < MTW_VAP_MAX; i++) {
+ if ((sc->rvp_bmap & 1 << i) == 0) {
+ sc->rvp_bmap |= 1 << i;
+ rvp->rvp_id = i;
+ break;
+ }
+ }
+ if (sc->rvp_cnt++ == 0)
+ ic->ic_opmode = opmode;
+
+ if (opmode == IEEE80211_M_HOSTAP)
+ sc->cmdq_run = MTW_CMDQ_GO;
+
+ MTW_DPRINTF(sc, MTW_DEBUG_STATE, "rvp_id=%d bmap=%x rvp_cnt=%d\n",
+ rvp->rvp_id, sc->rvp_bmap, sc->rvp_cnt);
+
+ return (vap);
+}
+
+static void
+mtw_vap_delete(struct ieee80211vap *vap)
+{
+ struct mtw_vap *rvp = MTW_VAP(vap);
+ struct ieee80211com *ic;
+ struct mtw_softc *sc;
+ uint8_t rvp_id;
+
+ if (vap == NULL)
+ return;
+
+ ic = vap->iv_ic;
+ sc = ic->ic_softc;
+
+ MTW_LOCK(sc);
+ m_freem(rvp->beacon_mbuf);
+ rvp->beacon_mbuf = NULL;
+
+ rvp_id = rvp->rvp_id;
+ sc->ratectl_run &= ~(1 << rvp_id);
+ sc->rvp_bmap &= ~(1 << rvp_id);
+ mtw_set_region_4(sc, MTW_SKEY(rvp_id, 0), 0, 256);
+ mtw_set_region_4(sc, (0x7800 + (rvp_id) * 512), 0, 512);
+ --sc->rvp_cnt;
+
+ MTW_DPRINTF(sc, MTW_DEBUG_STATE,
+ "vap=%p rvp_id=%d bmap=%x rvp_cnt=%d\n", vap, rvp_id, sc->rvp_bmap,
+ sc->rvp_cnt);
+
+ MTW_UNLOCK(sc);
+
+ ieee80211_ratectl_deinit(vap);
+ ieee80211_vap_detach(vap);
+ free(rvp, M_80211_VAP);
+}
+
+/*
+ * There are numbers of functions need to be called in context thread.
+ * Rather than creating taskqueue event for each of those functions,
+ * here is all-for-one taskqueue callback function. This function
+ * guarantees deferred functions are executed in the same order they
+ * were enqueued.
+ * '& MTW_CMDQ_MASQ' is to loop cmdq[].
+ */
+static void
+mtw_cmdq_cb(void *arg, int pending)
+{
+ struct mtw_softc *sc = arg;
+ uint8_t i;
+ /* call cmdq[].func locked */
+ MTW_LOCK(sc);
+ for (i = sc->cmdq_exec; sc->cmdq[i].func && pending;
+ i = sc->cmdq_exec, pending--) {
+ MTW_DPRINTF(sc, MTW_DEBUG_CMD, "cmdq_exec=%d pending=%d\n", i,
+ pending);
+ if (sc->cmdq_run == MTW_CMDQ_GO) {
+ /*
+ * If arg0 is NULL, callback func needs more
+ * than one arg. So, pass ptr to cmdq struct.
+ */
+ if (sc->cmdq[i].arg0)
+ sc->cmdq[i].func(sc->cmdq[i].arg0);
+ else
+ sc->cmdq[i].func(&sc->cmdq[i]);
+ }
+ sc->cmdq[i].arg0 = NULL;
+ sc->cmdq[i].func = NULL;
+ sc->cmdq_exec++;
+ sc->cmdq_exec &= MTW_CMDQ_MASQ;
+ }
+ MTW_UNLOCK(sc);
+}
+
+static void
+mtw_setup_tx_list(struct mtw_softc *sc, struct mtw_endpoint_queue *pq)
+{
+ struct mtw_tx_data *data;
+
+ memset(pq, 0, sizeof(*pq));
+
+ STAILQ_INIT(&pq->tx_qh);
+ STAILQ_INIT(&pq->tx_fh);
+
+ for (data = &pq->tx_data[0]; data < &pq->tx_data[MTW_TX_RING_COUNT];
+ data++) {
+ data->sc = sc;
+ STAILQ_INSERT_TAIL(&pq->tx_fh, data, next);
+ }
+ pq->tx_nfree = MTW_TX_RING_COUNT;
+}
+
+static void
+mtw_unsetup_tx_list(struct mtw_softc *sc, struct mtw_endpoint_queue *pq)
+{
+ struct mtw_tx_data *data;
+ /* make sure any subsequent use of the queues will fail */
+ pq->tx_nfree = 0;
+
+ STAILQ_INIT(&pq->tx_fh);
+ STAILQ_INIT(&pq->tx_qh);
+
+ /* free up all node references and mbufs */
+ for (data = &pq->tx_data[0]; data < &pq->tx_data[MTW_TX_RING_COUNT];
+ data++) {
+ if (data->m != NULL) {
+ m_freem(data->m);
+ data->m = NULL;
+ }
+ if (data->ni != NULL) {
+ ieee80211_free_node(data->ni);
+ data->ni = NULL;
+ }
+ }
+}
+
+static int
+mtw_write_ivb(struct mtw_softc *sc, void *buf, uint16_t len)
+{
+ usb_device_request_t req;
+ uint16_t actlen;
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = MTW_RESET;
+ USETW(req.wValue, 0x12);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, len);
+
+ int error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, buf,
+ 0, &actlen, 1000);
+
+ return (error);
+}
+
+static int
+mtw_write_cfg(struct mtw_softc *sc, uint16_t reg, uint32_t val)
+{
+ usb_device_request_t req;
+ int error;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = MTW_WRITE_CFG;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 4);
+ val = htole32(val);
+ error = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, &val);
+ return (error);
+}
+
+static int
+mtw_usb_dma_write(struct mtw_softc *sc, uint32_t val)
+{
+ // if (sc->asic_ver == 0x7612)
+ // return mtw_write_cfg(sc, MTW_USB_U3DMA_CFG, val);
+ // else
+ return (mtw_write(sc, MTW_USB_DMA_CFG, val));
+}
+
+static void
+mtw_ucode_setup(struct mtw_softc *sc)
+{
+
+ mtw_usb_dma_write(sc, (MTW_USB_TX_EN | MTW_USB_RX_EN));
+ mtw_write(sc, MTW_FCE_PSE_CTRL, 1);
+ mtw_write(sc, MTW_TX_CPU_FCE_BASE, 0x400230);
+ mtw_write(sc, MTW_TX_CPU_FCE_MAX_COUNT, 1);
+ mtw_write(sc, MTW_MCU_FW_IDX, 1);
+ mtw_write(sc, MTW_FCE_PDMA, 0x44);
+ mtw_write(sc, MTW_FCE_SKIP_FS, 3);
+}
+static int
+mtw_ucode_write(struct mtw_softc *sc, const uint8_t *fw, const uint8_t *ivb,
+ int32_t len, uint32_t offset)
+{
+
+ // struct usb_attach_arg *uaa = device_get_ivars(sc->sc_dev);
+#if 0 // firmware not tested
+
+ if (sc->asic_ver == 0x7612 && offset >= 0x90000)
+ blksz = 0x800; /* MT7612 ROM Patch */
+
+ xfer = usbd_alloc_xfer(sc->sc_udev);
+ if (xfer == NULL) {
+ error = ENOMEM;
+ goto fail;
+ }
+ buf = usbd_alloc_buffer(xfer, blksz + 12);
+ if (buf == NULL) {
+ error = ENOMEM;
+ goto fail;
+ }
+#endif
+
+
+
+ int mlen;
+ int idx = 0;
+
+ mlen = 0x2c44;
+
+ while (len > 0) {
+
+ if (len < 0x2c44 && len > 0) {
+ mlen = len;
+ }
+
+ sc->txd_fw[idx]->len = htole16(mlen);
+ sc->txd_fw[idx]->flags = htole16(MTW_TXD_DATA | MTW_TXD_MCU);
+
+ memcpy(&sc->txd_fw[idx]->fw, fw, mlen);
+ // memcpy(&txd[1], fw, mlen);
+ // memset(&txd[1] + mlen, 0, MTW_DMA_PAD);
+ // mtw_write_cfg(sc, MTW_MCU_DMA_ADDR, offset
+ //+sent); 1mtw_write_cfg(sc, MTW_MCU_DMA_LEN, (mlen << 16));
+
+ // sc->sc_fw_data[idx]->len=htole16(mlen);
+
+ // memcpy(tmpbuf,fw,mlen);
+ // memset(tmpbuf+mlen,0,MTW_DMA_PAD);
+ // memcpy(sc->sc_fw_data[idx].buf, fw, mlen);
+
+ fw += mlen;
+ len -= mlen;
+ // sent+=mlen;
+ idx++;
+ }
+ sc->sc_sent = 0;
+ memcpy(sc->sc_ivb_1, ivb, MTW_MCU_IVB_LEN);
+
+ usbd_transfer_start(sc->sc_xfer[7]);
+
+ return (0);
+}
+
+static void
+mtw_load_microcode(void *arg)
+{
+
+ struct mtw_softc *sc = (struct mtw_softc *)arg;
+ const struct mtw_ucode_hdr *hdr;
+ // onst struct mtw_ucode *fw = NULL;
+ const char *fwname;
+ size_t size;
+ int error = 0;
+ uint32_t tmp, iofs = 0x40;
+ // int ntries;
+ int dlen, ilen;
+ device_printf(sc->sc_dev, "version:0x%hx\n", sc->asic_ver);
+ /* is firmware already running? */
+ mtw_read_cfg(sc, MTW_MCU_DMA_ADDR, &tmp);
+ if (tmp == MTW_MCU_READY) {
+ return;
+ }
+ if (sc->asic_ver == 0x7612) {
+ fwname = "mtw-mt7662u_rom_patch";
+
+ const struct firmware *firmware = firmware_get_flags(fwname,FIRMWARE_GET_NOWARN);
+ if (firmware == NULL) {
+ device_printf(sc->sc_dev,
+ "failed loadfirmware of file %s (error %d)\n",
+ fwname, error);
+ return;
+ }
+ size = firmware->datasize;
+
+ const struct mtw_ucode *fw = (const struct mtw_ucode *)
+ firmware->data;
+ hdr = (const struct mtw_ucode_hdr *)&fw->hdr;
+ // memcpy(fw,(const unsigned char*)firmware->data +
+ // 0x1e,size-0x1e);
+ ilen = size - 0x1e;
+
+ mtw_ucode_setup(sc);
+
+ if ((error = mtw_ucode_write(sc, firmware->data, fw->ivb, ilen,
+ 0x90000)) != 0) {
+ goto fail;
+ }
+ mtw_usb_dma_write(sc, 0x00e41814);
+ }
+
+ fwname = "/mediatek/mt7601u.bin";
+ iofs = 0x40;
+ // dofs = 0;
+ if (sc->asic_ver == 0x7612) {
+ fwname = "mtw-mt7662u";
+ iofs = 0x80040;
+ // dofs = 0x110800;
+ } else if (sc->asic_ver == 0x7610) {
+ fwname = "mt7610u";
+ // dofs = 0x80000;
+ }
+ MTW_UNLOCK(sc);
+ const struct firmware *firmware = firmware_get_flags(fwname, FIRMWARE_GET_NOWARN);
+
+ if (firmware == NULL) {
+ device_printf(sc->sc_dev,
+ "failed loadfirmware of file %s (error %d)\n", fwname,
+ error);
+ MTW_LOCK(sc);
+ return;
+ }
+ MTW_LOCK(sc);
+ size = firmware->datasize;
+ MTW_DPRINTF(sc, MTW_DEBUG_FIRMWARE, "firmware size:%zu\n", size);
+ const struct mtw_ucode *fw = (const struct mtw_ucode *)firmware->data;
+
+ if (size < sizeof(struct mtw_ucode_hdr)) {
+ device_printf(sc->sc_dev, "firmware header too short\n");
+ goto fail;
+ }
+
+ hdr = (const struct mtw_ucode_hdr *)&fw->hdr;
+
+ if (size < sizeof(struct mtw_ucode_hdr) + le32toh(hdr->ilm_len) +
+ le32toh(hdr->dlm_len)) {
+ device_printf(sc->sc_dev, "firmware payload too short\n");
+ goto fail;
+ }
+
+ ilen = le32toh(hdr->ilm_len) - MTW_MCU_IVB_LEN;
+ dlen = le32toh(hdr->dlm_len);
+
+ if (ilen > size || dlen > size) {
+ device_printf(sc->sc_dev, "firmware payload too large\n");
+ goto fail;
+ }
+
+ mtw_write(sc, MTW_FCE_PDMA, 0);
+ mtw_write(sc, MTW_FCE_PSE_CTRL, 0);
+ mtw_ucode_setup(sc);
+
+ if ((error = mtw_ucode_write(sc, fw->data, fw->ivb, ilen, iofs)) != 0)
+ device_printf(sc->sc_dev, "Could not write ucode errro=%d\n",
+ error);
+
+ device_printf(sc->sc_dev, "loaded firmware ver %.8x %.8x %s\n",
+ le32toh(hdr->fw_ver), le32toh(hdr->build_ver), hdr->build_time);
+
+ return;
+fail:
+ return;
+}
+static usb_error_t
+mtw_do_request(struct mtw_softc *sc, struct usb_device_request *req, void *data)
+{
+ usb_error_t err;
+ int ntries = 5;
+
+ MTW_LOCK_ASSERT(sc, MA_OWNED);
+
+ while (ntries--) {
+ err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, req, data,
+ 0, NULL, 2000); // ms seconds
+ if (err == 0)
+ break;
+ MTW_DPRINTF(sc, MTW_DEBUG_USB,
+ "Control request failed, %s (retrying)\n",
+ usbd_errstr(err));
+ mtw_delay(sc, 10);
+ }
+ return (err);
+}
+
+static int
+mtw_read(struct mtw_softc *sc, uint16_t reg, uint32_t *val)
+{
+ uint32_t tmp;
+ int error;
+
+ error = mtw_read_region_1(sc, reg, (uint8_t *)&tmp, sizeof tmp);
+ if (error == 0)
+ *val = le32toh(tmp);
+ else
+ *val = 0xffffffff;
+ return (error);
+}
+
+static int
+mtw_read_region_1(struct mtw_softc *sc, uint16_t reg, uint8_t *buf, int len)
+{
+ usb_device_request_t req;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = MTW_READ_REGION_1;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, len);
+
+ return (mtw_do_request(sc, &req, buf));
+}
+
+static int
+mtw_write_2(struct mtw_softc *sc, uint16_t reg, uint16_t val)
+{
+
+ usb_device_request_t req;
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = MTW_WRITE_2;
+ USETW(req.wValue, val);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 0);
+ return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL));
+}
+
+static int
+mtw_write(struct mtw_softc *sc, uint16_t reg, uint32_t val)
+{
+
+ int error;
+
+ if ((error = mtw_write_2(sc, reg, val & 0xffff)) == 0) {
+
+ error = mtw_write_2(sc, reg + 2, val >> 16);
+ }
+
+ return (error);
+}
+
+static int
+mtw_write_region_1(struct mtw_softc *sc, uint16_t reg, uint8_t *buf, int len)
+{
+
+ usb_device_request_t req;
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = MTW_WRITE_REGION_1;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, len);
+ return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, buf));
+}
+
+static int
+mtw_set_region_4(struct mtw_softc *sc, uint16_t reg, uint32_t val, int count)
+{
+ int i, error = 0;
+
+ KASSERT((count & 3) == 0, ("mte_set_region_4: Invalid data length.\n"));
+ for (i = 0; i < count && error == 0; i += 4)
+ error = mtw_write(sc, reg + i, val);
+ return (error);
+}
+
+static int
+mtw_efuse_read_2(struct mtw_softc *sc, uint16_t addr, uint16_t *val)
+{
+
+ uint32_t tmp;
+ uint16_t reg;
+ int error, ntries;
+
+ if ((error = mtw_read(sc, MTW_EFUSE_CTRL, &tmp)) != 0)
+ return (error);
+
+ addr *= 2;
+ /*
+ * Read one 16-byte block into registers EFUSE_DATA[0-3]:
+ * DATA0: 3 2 1 0
+ * DATA1: 7 6 5 4
+ * DATA2: B A 9 8
+ * DATA3: F E D C
+ */
+ tmp &= ~(MTW_EFSROM_MODE_MASK | MTW_EFSROM_AIN_MASK);
+ tmp |= (addr & ~0xf) << MTW_EFSROM_AIN_SHIFT | MTW_EFSROM_KICK;
+ mtw_write(sc, MTW_EFUSE_CTRL, tmp);
+ for (ntries = 0; ntries < 100; ntries++) {
+ if ((error = mtw_read(sc, MTW_EFUSE_CTRL, &tmp)) != 0)
+ return (error);
+ if (!(tmp & MTW_EFSROM_KICK))
+ break;
+ DELAY(2);
+ }
+ if (ntries == 100)
+ return (ETIMEDOUT);
+
+ if ((tmp & MTW_EFUSE_AOUT_MASK) == MTW_EFUSE_AOUT_MASK) {
+ *val = 0xffff; // address not found
+ return (0);
+ }
+// determine to which 32-bit register our 16-bit word belongs
+ reg = MTW_EFUSE_DATA0 + (addr & 0xc);
+ if ((error = mtw_read(sc, reg, &tmp)) != 0)
+ return (error);
+
+ *val = (addr & 2) ? tmp >> 16 : tmp & 0xffff;
+ return (0);
+}
+
+static __inline int
+mtw_srom_read(struct mtw_softc *sc, uint16_t addr, uint16_t *val)
+{
+ /* either eFUSE ROM or EEPROM */
+ return (sc->sc_srom_read(sc, addr, val));
+}
+
+static int
+mtw_bbp_read(struct mtw_softc *sc, uint8_t reg, uint8_t *val)
+{
+ uint32_t tmp;
+ int ntries, error;
+
+ for (ntries = 0; ntries < 10; ntries++) {
+ if ((error = mtw_read(sc, MTW_BBP_CSR, &tmp)) != 0)
+ return (error);
+ if (!(tmp & MTW_BBP_CSR_KICK))
+ break;
+ }
+ if (ntries == 10)
+ return (ETIMEDOUT);
+
+ tmp = MTW_BBP_CSR_READ | MTW_BBP_CSR_KICK | reg << 8;
+ if ((error = mtw_write(sc, MTW_BBP_CSR, tmp)) != 0)
+ return (error);
+
+ for (ntries = 0; ntries < 10; ntries++) {
+ if ((error = mtw_read(sc, MTW_BBP_CSR, &tmp)) != 0)
+ return (error);
+ if (!(tmp & MTW_BBP_CSR_KICK))
+ break;
+ }
+ if (ntries == 10)
+ return (ETIMEDOUT);
+
+ *val = tmp & 0xff;
+ return (0);
+}
+
+static int
+mtw_bbp_write(struct mtw_softc *sc, uint8_t reg, uint8_t val)
+{
+ uint32_t tmp;
+ int ntries, error;
+
+ for (ntries = 0; ntries < 10; ntries++) {
+ if ((error = mtw_read(sc, MTW_BBP_CSR, &tmp)) != 0)
+ return (error);
+ if (!(tmp & MTW_BBP_CSR_KICK))
+ break;
+ }
+ if (ntries == 10)
+ return (ETIMEDOUT);
+
+ tmp = MTW_BBP_CSR_KICK | reg << 8 | val;
+ return (mtw_write(sc, MTW_BBP_CSR, tmp));
+}
+
+static int
+mtw_mcu_cmd(struct mtw_softc *sc, u_int8_t cmd, void *buf, int len)
+{
+ sc->sc_idx = 0;
+ sc->txd_fw[sc->sc_idx]->len = htole16(
+ len + 8);
+ sc->txd_fw[sc->sc_idx]->flags = htole16(MTW_TXD_CMD | MTW_TXD_MCU |
+ (cmd & 0x1f) << MTW_TXD_CMD_SHIFT | (0 & 0xf));
+
+ memset(&sc->txd_fw[sc->sc_idx]->fw, 0, 2004);
+ memcpy(&sc->txd_fw[sc->sc_idx]->fw, buf, len);
+ usbd_transfer_start(sc->sc_xfer[7]);
+ return (0);
+}
+
+/*
+ * Add `delta' (signed) to each 4-bit sub-word of a 32-bit word.
+ * Used to adjust per-rate Tx power registers.
+ */
+static __inline uint32_t
+b4inc(uint32_t b32, int8_t delta)
+{
+ int8_t i, b4;
+
+ for (i = 0; i < 8; i++) {
+ b4 = b32 & 0xf;
+ b4 += delta;
+ if (b4 < 0)
+ b4 = 0;
+ else if (b4 > 0xf)
+ b4 = 0xf;
+ b32 = b32 >> 4 | b4 << 28;
+ }
+ return (b32);
+}
+static void
+mtw_get_txpower(struct mtw_softc *sc)
+{
+ uint16_t val;
+ int i;
+
+ /* Read power settings for 2GHz channels. */
+ for (i = 0; i < 14; i += 2) {
+ mtw_srom_read(sc, MTW_EEPROM_PWR2GHZ_BASE1 + i / 2, &val);
+ sc->txpow1[i + 0] = (int8_t)(val & 0xff);
+ sc->txpow1[i + 1] = (int8_t)(val >> 8);
+ mtw_srom_read(sc, MTW_EEPROM_PWR2GHZ_BASE2 + i / 2, &val);
+ sc->txpow2[i + 0] = (int8_t)(val & 0xff);
+ sc->txpow2[i + 1] = (int8_t)(val >> 8);
+ }
+ /* Fix broken Tx power entries. */
+ for (i = 0; i < 14; i++) {
+ if (sc->txpow1[i] < 0 || sc->txpow1[i] > 27)
+ sc->txpow1[i] = 5;
+ if (sc->txpow2[i] < 0 || sc->txpow2[i] > 27)
+ sc->txpow2[i] = 5;
+ MTW_DPRINTF(sc, MTW_DEBUG_TXPWR,
+ "chan %d: power1=%d, power2=%d\n", mt7601_rf_chan[i].chan,
+ sc->txpow1[i], sc->txpow2[i]);
+ }
+}
+
+struct ieee80211_node *
+mtw_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
+{
+ return (malloc(sizeof(struct mtw_node), M_80211_NODE,
+ M_NOWAIT | M_ZERO));
+}
+static int
+mtw_read_eeprom(struct mtw_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ int8_t delta_2ghz, delta_5ghz;
+ uint16_t val;
+ int ridx, ant;
+
+ sc->sc_srom_read = mtw_efuse_read_2;
+
+ /* read RF information */
+ mtw_srom_read(sc, MTW_EEPROM_CHIPID, &val);
+ sc->rf_rev = val;
+ mtw_srom_read(sc, MTW_EEPROM_ANTENNA, &val);
+ sc->ntxchains = (val >> 4) & 0xf;
+ sc->nrxchains = val & 0xf;
+ MTW_DPRINTF(sc, MTW_DEBUG_ROM, "EEPROM RF rev=0x%02x chains=%dT%dR\n",
+ sc->rf_rev, sc->ntxchains, sc->nrxchains);
+
+ /* read ROM version */
+ mtw_srom_read(sc, MTW_EEPROM_VERSION, &val);
+ MTW_DPRINTF(sc, MTW_DEBUG_ROM, "EEPROM rev=%d, FAE=%d\n", val & 0xff,
+ val >> 8);
+
+ /* read MAC address */
+ mtw_srom_read(sc, MTW_EEPROM_MAC01, &val);
+ ic->ic_macaddr[0] = val & 0xff;
+ ic->ic_macaddr[1] = val >> 8;
+ mtw_srom_read(sc, MTW_EEPROM_MAC23, &val);
+ ic->ic_macaddr[2] = val & 0xff;
+ ic->ic_macaddr[3] = val >> 8;
+ mtw_srom_read(sc, MTW_EEPROM_MAC45, &val);
+ ic->ic_macaddr[4] = val & 0xff;
+ ic->ic_macaddr[5] = val >> 8;
+#if 0
+ printf("eFUSE ROM\n00: ");
+ for (int i = 0; i < 256; i++) {
+ if (((i % 8) == 0) && i > 0)
+ printf("\n%02x: ", i);
+ mtw_srom_read(sc, i, &val);
+ printf(" %04x", val);
+ }
+ printf("\n");
+#endif
+ /* check if RF supports automatic Tx access gain control */
+ mtw_srom_read(sc, MTW_EEPROM_CONFIG, &val);
+ device_printf(sc->sc_dev, "EEPROM CFG 0x%04x\n", val);
+ if ((val & 0xff) != 0xff) {
+ sc->ext_5ghz_lna = (val >> 3) & 1;
+ sc->ext_2ghz_lna = (val >> 2) & 1;
+ /* check if RF supports automatic Tx access gain control */
+ sc->calib_2ghz = sc->calib_5ghz = (val >> 1) & 1;
+ /* check if we have a hardware radio switch */
+ sc->rfswitch = val & 1;
+ }
+
+ /* read RF frequency offset from EEPROM */
+ mtw_srom_read(sc, MTW_EEPROM_FREQ_OFFSET, &val);
+ if ((val & 0xff) != 0xff)
+ sc->rf_freq_offset = val;
+ else
+ sc->rf_freq_offset = 0;
+ MTW_DPRINTF(sc, MTW_DEBUG_ROM, "frequency offset 0x%x\n",
+ sc->rf_freq_offset);
+
+ /* Read Tx power settings. */
+ mtw_get_txpower(sc);
+
+ /* read Tx power compensation for each Tx rate */
+ mtw_srom_read(sc, MTW_EEPROM_DELTAPWR, &val);
+ delta_2ghz = delta_5ghz = 0;
+ if ((val & 0xff) != 0xff && (val & 0x80)) {
+ delta_2ghz = val & 0xf;
+ if (!(val & 0x40)) /* negative number */
+ delta_2ghz = -delta_2ghz;
+ }
+ val >>= 8;
+ if ((val & 0xff) != 0xff && (val & 0x80)) {
+ delta_5ghz = val & 0xf;
+ if (!(val & 0x40)) /* negative number */
+ delta_5ghz = -delta_5ghz;
+ }
+ MTW_DPRINTF(sc, MTW_DEBUG_ROM | MTW_DEBUG_TXPWR,
+ "power compensation=%d (2GHz), %d (5GHz)\n", delta_2ghz,
+ delta_5ghz);
+
+ for (ridx = 0; ridx < 5; ridx++) {
+ uint32_t reg;
+
+ mtw_srom_read(sc, MTW_EEPROM_RPWR + ridx * 2, &val);
+ reg = val;
+ mtw_srom_read(sc, MTW_EEPROM_RPWR + ridx * 2 + 1, &val);
+ reg |= (uint32_t)val << 16;
+
+ sc->txpow20mhz[ridx] = reg;
+ sc->txpow40mhz_2ghz[ridx] = b4inc(reg, delta_2ghz);
+ sc->txpow40mhz_5ghz[ridx] = b4inc(reg, delta_5ghz);
+
+ MTW_DPRINTF(sc, MTW_DEBUG_ROM | MTW_DEBUG_TXPWR,
+ "ridx %d: power 20MHz=0x%08x, 40MHz/2GHz=0x%08x, "
+ "40MHz/5GHz=0x%08x\n",
+ ridx, sc->txpow20mhz[ridx], sc->txpow40mhz_2ghz[ridx],
+ sc->txpow40mhz_5ghz[ridx]);
+ }
+
+ /* read RSSI offsets and LNA gains from EEPROM */
+ val = 0;
+ mtw_srom_read(sc, MTW_EEPROM_RSSI1_2GHZ, &val);
+ sc->rssi_2ghz[0] = val & 0xff; /* Ant A */
+ sc->rssi_2ghz[1] = val >> 8; /* Ant B */
+ mtw_srom_read(sc, MTW_EEPROM_RSSI2_2GHZ, &val);
+ /*
+ * On RT3070 chips (limited to 2 Rx chains), this ROM
+ * field contains the Tx mixer gain for the 2GHz band.
+ */
+ if ((val & 0xff) != 0xff)
+ sc->txmixgain_2ghz = val & 0x7;
+ MTW_DPRINTF(sc, MTW_DEBUG_ROM, "tx mixer gain=%u (2GHz)\n",
+ sc->txmixgain_2ghz);
+ sc->lna[2] = val >> 8; /* channel group 2 */
+ mtw_srom_read(sc, MTW_EEPROM_RSSI1_5GHZ, &val);
+ sc->rssi_5ghz[0] = val & 0xff; /* Ant A */
+ sc->rssi_5ghz[1] = val >> 8; /* Ant B */
+ mtw_srom_read(sc, MTW_EEPROM_RSSI2_5GHZ, &val);
+ sc->rssi_5ghz[2] = val & 0xff; /* Ant C */
+
+ sc->lna[3] = val >> 8; /* channel group 3 */
+
+ mtw_srom_read(sc, MTW_EEPROM_LNA, &val);
+ sc->lna[0] = val & 0xff; /* channel group 0 */
+ sc->lna[1] = val >> 8; /* channel group 1 */
+ MTW_DPRINTF(sc, MTW_DEBUG_ROM, "LNA0 0x%x\n", sc->lna[0]);
+
+ /* fix broken 5GHz LNA entries */
+ if (sc->lna[2] == 0 || sc->lna[2] == 0xff) {
+ MTW_DPRINTF(sc, MTW_DEBUG_ROM,
+ "invalid LNA for channel group %d\n", 2);
+ sc->lna[2] = sc->lna[1];
+ }
+ if (sc->lna[3] == 0 || sc->lna[3] == 0xff) {
+ MTW_DPRINTF(sc, MTW_DEBUG_ROM,
+ "invalid LNA for channel group %d\n", 3);
+ sc->lna[3] = sc->lna[1];
+ }
+
+ /* fix broken RSSI offset entries */
+ for (ant = 0; ant < 3; ant++) {
+ if (sc->rssi_2ghz[ant] < -10 || sc->rssi_2ghz[ant] > 10) {
+ MTW_DPRINTF(sc, MTW_DEBUG_ROM,
+ "invalid RSSI%d offset: %d (2GHz)\n", ant + 1,
+ sc->rssi_2ghz[ant]);
+ sc->rssi_2ghz[ant] = 0;
+ }
+ if (sc->rssi_5ghz[ant] < -10 || sc->rssi_5ghz[ant] > 10) {
+ MTW_DPRINTF(sc, MTW_DEBUG_ROM,
+ "invalid RSSI%d offset: %d (5GHz)\n", ant + 1,
+ sc->rssi_5ghz[ant]);
+ sc->rssi_5ghz[ant] = 0;
+ }
+ }
+ return (0);
+}
+static int
+mtw_media_change(if_t ifp)
+{
+ struct ieee80211vap *vap = if_getsoftc(ifp);
+ struct ieee80211com *ic = vap->iv_ic;
+ const struct ieee80211_txparam *tp;
+ struct mtw_softc *sc = ic->ic_softc;
+ uint8_t rate, ridx;
+
+ MTW_LOCK(sc);
+ ieee80211_media_change(ifp);
+ //tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)];
+ tp = &vap->iv_txparms[ic->ic_curmode];
+ if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) {
+ struct ieee80211_node *ni;
+ struct mtw_node *rn;
+ /* XXX TODO: methodize with MCS rates */
+ rate =
+ ic->ic_sup_rates[ic->ic_curmode].rs_rates[tp->ucastrate] &
+ IEEE80211_RATE_VAL;
+ for (ridx = 0; ridx < MTW_RIDX_MAX; ridx++) {
+ if (rt2860_rates[ridx].rate == rate)
+ break;
+ }
+ ni = ieee80211_ref_node(vap->iv_bss);
+ rn = MTW_NODE(ni);
+ rn->fix_ridx = ridx;
+
+ MTW_DPRINTF(sc, MTW_DEBUG_RATE, "rate=%d, fix_ridx=%d\n", rate,
+ rn->fix_ridx);
+ ieee80211_free_node(ni);
+ }
+ MTW_UNLOCK(sc);
+
+ return (0);
+}
+
+void
+mtw_set_leds(struct mtw_softc *sc, uint16_t which)
+{
+ struct mtw_mcu_cmd_8 cmd;
+ cmd.func = htole32(0x1);
+ cmd.val = htole32(which);
+ mtw_mcu_cmd(sc, CMD_LED_MODE, &cmd, sizeof(struct mtw_mcu_cmd_8));
+}
+static void
+mtw_abort_tsf_sync(struct mtw_softc *sc)
+{
+ uint32_t tmp;
+
+ mtw_read(sc, MTW_BCN_TIME_CFG, &tmp);
+ tmp &= ~(MTW_BCN_TX_EN | MTW_TSF_TIMER_EN | MTW_TBTT_TIMER_EN);
+ mtw_write(sc, MTW_BCN_TIME_CFG, tmp);
+}
+static int
+mtw_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
+{
+ const struct ieee80211_txparam *tp;
+ struct ieee80211com *ic = vap->iv_ic;
+ struct mtw_softc *sc = ic->ic_softc;
+ struct mtw_vap *rvp = MTW_VAP(vap);
+ enum ieee80211_state ostate;
+ uint32_t sta[3];
+ uint8_t ratectl = 0;
+ uint8_t restart_ratectl = 0;
+ uint8_t bid = 1 << rvp->rvp_id;
+
+
+ ostate = vap->iv_state;
+ MTW_DPRINTF(sc, MTW_DEBUG_STATE, "%s -> %s\n",
+ ieee80211_state_name[ostate], ieee80211_state_name[nstate]);
+ IEEE80211_UNLOCK(ic);
+ MTW_LOCK(sc);
+ ratectl = sc->ratectl_run; /* remember current state */
+ usb_callout_stop(&sc->ratectl_ch);
+ sc->ratectl_run = MTW_RATECTL_OFF;
+ if (ostate == IEEE80211_S_RUN) {
+ /* turn link LED off */
+ }
+
+ switch (nstate) {
+ case IEEE80211_S_INIT:
+ restart_ratectl = 1;
+ if (ostate != IEEE80211_S_RUN)
+ break;
+
+ ratectl &= ~bid;
+ sc->runbmap &= ~bid;
+
+ /* abort TSF synchronization if there is no vap running */
+ if (--sc->running == 0)
+ mtw_abort_tsf_sync(sc);
+ break;
+
+ case IEEE80211_S_RUN:
+ if (!(sc->runbmap & bid)) {
+ if (sc->running++)
+ restart_ratectl = 1;
+ sc->runbmap |= bid;
+ }
+
+ m_freem(rvp->beacon_mbuf);
+ rvp->beacon_mbuf = NULL;
+
+ switch (vap->iv_opmode) {
+ case IEEE80211_M_HOSTAP:
+ case IEEE80211_M_MBSS:
+ sc->ap_running |= bid;
+ ic->ic_opmode = vap->iv_opmode;
+ mtw_update_beacon_cb(vap);
+ break;
+ case IEEE80211_M_IBSS:
+ sc->adhoc_running |= bid;
+ if (!sc->ap_running)
+ ic->ic_opmode = vap->iv_opmode;
+ mtw_update_beacon_cb(vap);
+ break;
+ case IEEE80211_M_STA:
+ sc->sta_running |= bid;
+ if (!sc->ap_running && !sc->adhoc_running)
+ ic->ic_opmode = vap->iv_opmode;
+
+ /* read statistic counters (clear on read) */
+ mtw_read_region_1(sc, MTW_TX_STA_CNT0, (uint8_t *)sta,
+ sizeof sta);
+
+ break;
+ default:
+ ic->ic_opmode = vap->iv_opmode;
+ break;
+ }
+
+ if (vap->iv_opmode != IEEE80211_M_MONITOR) {
+ struct ieee80211_node *ni;
+
+ if (ic->ic_bsschan == IEEE80211_CHAN_ANYC) {
+ MTW_UNLOCK(sc);
+ IEEE80211_LOCK(ic);
+ return (-1);
+ }
+ mtw_updateslot(ic);
+ mtw_enable_mrr(sc);
+ mtw_set_txpreamble(sc);
+ mtw_set_basicrates(sc);
+ ni = ieee80211_ref_node(vap->iv_bss);
+ IEEE80211_ADDR_COPY(sc->sc_bssid, ni->ni_bssid);
+ mtw_set_bssid(sc, sc->sc_bssid);
+ ieee80211_free_node(ni);
+ mtw_enable_tsf_sync(sc);
+
+ /* enable automatic rate adaptation */
+ tp = &vap->iv_txparms[ieee80211_chan2mode(
+ ic->ic_curchan)];
+ if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE)
+ ratectl |= bid;
+ } else {
+ mtw_enable_tsf_sync(sc);
+ }
+
+ break;
+ default:
+ MTW_DPRINTF(sc, MTW_DEBUG_STATE, "undefined state\n");
+ break;
+ }
+
+ /* restart amrr for running VAPs */
+ if ((sc->ratectl_run = ratectl) && restart_ratectl) {
+ usb_callout_reset(&sc->ratectl_ch, hz, mtw_ratectl_to, sc);
+ }
+ MTW_UNLOCK(sc);
+ IEEE80211_LOCK(ic);
+ return (rvp->newstate(vap, nstate, arg));
+}
+
+static int
+mtw_wme_update(struct ieee80211com *ic)
+{
+ struct chanAccParams chp;
+ struct mtw_softc *sc = ic->ic_softc;
+ const struct wmeParams *ac;
+ int aci, error = 0;
+ ieee80211_wme_ic_getparams(ic, &chp);
+ ac = chp.cap_wmeParams;
+
+ MTW_LOCK(sc);
+ /* update MAC TX configuration registers */
+ for (aci = 0; aci < WME_NUM_AC; aci++) {
+ error = mtw_write(sc, MTW_EDCA_AC_CFG(aci),
+ ac[aci].wmep_logcwmax << 16 | ac[aci].wmep_logcwmin << 12 |
+ ac[aci].wmep_aifsn << 8 | ac[aci].wmep_txopLimit);
+ if (error)
+ goto err;
+ }
+
+ /* update SCH/DMA registers too */
+ error = mtw_write(sc, MTW_WMM_AIFSN_CFG,
+ ac[WME_AC_VO].wmep_aifsn << 12 | ac[WME_AC_VI].wmep_aifsn << 8 |
+ ac[WME_AC_BK].wmep_aifsn << 4 | ac[WME_AC_BE].wmep_aifsn);
+ if (error)
+ goto err;
+ error = mtw_write(sc, MTW_WMM_CWMIN_CFG,
+ ac[WME_AC_VO].wmep_logcwmin << 12 |
+ ac[WME_AC_VI].wmep_logcwmin << 8 |
+ ac[WME_AC_BK].wmep_logcwmin << 4 | ac[WME_AC_BE].wmep_logcwmin);
+ if (error)
+ goto err;
+ error = mtw_write(sc, MTW_WMM_CWMAX_CFG,
+ ac[WME_AC_VO].wmep_logcwmax << 12 |
+ ac[WME_AC_VI].wmep_logcwmax << 8 |
+ ac[WME_AC_BK].wmep_logcwmax << 4 | ac[WME_AC_BE].wmep_logcwmax);
+ if (error)
+ goto err;
+ error = mtw_write(sc, MTW_WMM_TXOP0_CFG,
+ ac[WME_AC_BK].wmep_txopLimit << 16 | ac[WME_AC_BE].wmep_txopLimit);
+ if (error)
+ goto err;
+ error = mtw_write(sc, MTW_WMM_TXOP1_CFG,
+ ac[WME_AC_VO].wmep_txopLimit << 16 | ac[WME_AC_VI].wmep_txopLimit);
+
+err:
+ MTW_UNLOCK(sc);
+ if (error)
+ MTW_DPRINTF(sc, MTW_DEBUG_USB, "WME update failed\n");
+
+ return (error);
+}
+
+static int
+mtw_key_set(struct ieee80211vap *vap, struct ieee80211_key *k)
+{
+ struct ieee80211com *ic = vap->iv_ic;
+ struct mtw_softc *sc = ic->ic_softc;
+ uint32_t i;
+
+ i = MTW_CMDQ_GET(&sc->cmdq_store);
+ MTW_DPRINTF(sc, MTW_DEBUG_KEY, "cmdq_store=%d\n", i);
+ sc->cmdq[i].func = mtw_key_set_cb;
+ sc->cmdq[i].arg0 = NULL;
+ sc->cmdq[i].arg1 = vap;
+ sc->cmdq[i].k = k;
+ IEEE80211_ADDR_COPY(sc->cmdq[i].mac, k->wk_macaddr);
+ ieee80211_runtask(ic, &sc->cmdq_task);
+
+ /*
+ * To make sure key will be set when hostapd
+ * calls iv_key_set() before if_init().
+ */
+ if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
+ MTW_LOCK(sc);
+ sc->cmdq_key_set = MTW_CMDQ_GO;
+ MTW_UNLOCK(sc);
+ }
+
+ return (1);
+}
+static void
+mtw_key_set_cb(void *arg)
+{
+ struct mtw_cmdq *cmdq = arg;
+ struct ieee80211vap *vap = cmdq->arg1;
+ struct ieee80211_key *k = cmdq->k;
+ struct ieee80211com *ic = vap->iv_ic;
+ struct mtw_softc *sc = ic->ic_softc;
+ struct ieee80211_node *ni;
+ u_int cipher = k->wk_cipher->ic_cipher;
+ uint32_t attr;
+ uint16_t base;
+ uint8_t mode, wcid, iv[8];
+ MTW_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (vap->iv_opmode == IEEE80211_M_HOSTAP)
+ ni = ieee80211_find_vap_node(&ic->ic_sta, vap, cmdq->mac);
+ else
+ ni = vap->iv_bss;
+
+ /* map net80211 cipher to RT2860 security mode */
+ switch (cipher) {
+ case IEEE80211_CIPHER_WEP:
+ if (k->wk_keylen < 8)
+ mode = MTW_MODE_WEP40;
+ else
+ mode = MTW_MODE_WEP104;
+ break;
+ case IEEE80211_CIPHER_TKIP:
+ mode = MTW_MODE_TKIP;
+ break;
+ case IEEE80211_CIPHER_AES_CCM:
+ mode = MTW_MODE_AES_CCMP;
+ break;
+ default:
+ MTW_DPRINTF(sc, MTW_DEBUG_KEY, "undefined case\n");
+ return;
+ }
+
+ if (k->wk_flags & IEEE80211_KEY_GROUP) {
+ wcid = 0; /* NB: update WCID0 for group keys */
+ base = MTW_SKEY(0, k->wk_keyix);
+ } else {
+ wcid = (ni != NULL) ? MTW_AID2WCID(ni->ni_associd) : 0;
+ base = MTW_PKEY(wcid);
+ }
+
+ if (cipher == IEEE80211_CIPHER_TKIP) {
+ mtw_write_region_1(sc, base, k->wk_key, 16);
+ mtw_write_region_1(sc, base + 16, &k->wk_key[24], 8);
+ mtw_write_region_1(sc, base + 24, &k->wk_key[16], 8);
+ } else {
+ /* roundup len to 16-bit: XXX fix write_region_1() instead */
+ mtw_write_region_1(sc, base, k->wk_key,
+ (k->wk_keylen + 1) & ~1);
+ }
+
+ if (!(k->wk_flags & IEEE80211_KEY_GROUP) ||
+ (k->wk_flags & (IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV))) {
+ /* set initial packet number in IV+EIV */
+ if (cipher == IEEE80211_CIPHER_WEP) {
+ memset(iv, 0, sizeof iv);
+ iv[3] = vap->iv_def_txkey << 6;
+ } else {
+ if (cipher == IEEE80211_CIPHER_TKIP) {
+ iv[0] = k->wk_keytsc >> 8;
+ iv[1] = (iv[0] | 0x20) & 0x7f;
+ iv[2] = k->wk_keytsc;
+ } else { //CCMP
+ iv[0] = k->wk_keytsc;
+ iv[1] = k->wk_keytsc >> 8;
+ iv[2] = 0;
+ }
+ iv[3] = k->wk_keyix << 6 | IEEE80211_WEP_EXTIV;
+ iv[4] = k->wk_keytsc >> 16;
+ iv[5] = k->wk_keytsc >> 24;
+ iv[6] = k->wk_keytsc >> 32;
+ iv[7] = k->wk_keytsc >> 40;
+ }
+ mtw_write_region_1(sc, MTW_IVEIV(wcid), iv, 8);
+ }
+
+ if (k->wk_flags & IEEE80211_KEY_GROUP) {
+ /* install group key */
+ mtw_read(sc, MTW_SKEY_MODE_0_7, &attr);
+ attr &= ~(0xf << (k->wk_keyix * 4));
+ attr |= mode << (k->wk_keyix * 4);
+ mtw_write(sc, MTW_SKEY_MODE_0_7, attr);
+
+ if (cipher & (IEEE80211_CIPHER_WEP)) {
+ mtw_read(sc, MTW_WCID_ATTR(wcid + 1), &attr);
+ attr = (attr & ~0xf) | (mode << 1);
+ mtw_write(sc, MTW_WCID_ATTR(wcid + 1), attr);
+
+ mtw_set_region_4(sc, MTW_IVEIV(0), 0, 4);
+
+ mtw_read(sc, MTW_WCID_ATTR(wcid), &attr);
+ attr = (attr & ~0xf) | (mode << 1);
+ mtw_write(sc, MTW_WCID_ATTR(wcid), attr);
+ }
+ } else {
+ /* install pairwise key */
+ mtw_read(sc, MTW_WCID_ATTR(wcid), &attr);
+ attr = (attr & ~0xf) | (mode << 1) | MTW_RX_PKEY_EN;
+ mtw_write(sc, MTW_WCID_ATTR(wcid), attr);
+ }
+ k->wk_pad = wcid;
+}
+
+/*
+ * If wlan is destroyed without being brought down i.e. without
+ * wlan down or wpa_cli terminate, this function is called after
+ * vap is gone. Don't refer it.
+ */
+static void
+mtw_key_delete_cb(void *arg)
+{
+ struct mtw_cmdq *cmdq = arg;
+ struct mtw_softc *sc = cmdq->arg1;
+ struct ieee80211_key *k = &cmdq->key;
+ uint32_t attr;
+ uint8_t wcid;
+
+ MTW_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (k->wk_flags & IEEE80211_KEY_GROUP) {
+ /* remove group key */
+ MTW_DPRINTF(sc, MTW_DEBUG_KEY, "removing group key\n");
+ mtw_read(sc, MTW_SKEY_MODE_0_7, &attr);
+ attr &= ~(0xf << (k->wk_keyix * 4));
+ mtw_write(sc, MTW_SKEY_MODE_0_7, attr);
+ } else {
+ /* remove pairwise key */
+ MTW_DPRINTF(sc, MTW_DEBUG_KEY, "removing key for wcid %x\n",
+ k->wk_pad);
+ /* matching wcid was written to wk_pad in mtw_key_set() */
+ wcid = k->wk_pad;
+ mtw_read(sc, MTW_WCID_ATTR(wcid), &attr);
+ attr &= ~0xf;
+ mtw_write(sc, MTW_WCID_ATTR(wcid), attr);
+ }
+
+ k->wk_pad = 0;
+}
+
+/*
+ * return 0 on error
+ */
+static int
+mtw_key_delete(struct ieee80211vap *vap, struct ieee80211_key *k)
+{
+ struct ieee80211com *ic = vap->iv_ic;
+ struct mtw_softc *sc = ic->ic_softc;
+ struct ieee80211_key *k0;
+ uint32_t i;
+ if (sc->sc_flags & MTW_RUNNING)
+ return (1);
+
+ /*
+ * When called back, key might be gone. So, make a copy
+ * of some values need to delete keys before deferring.
+ * But, because of LOR with node lock, cannot use lock here.
+ * So, use atomic instead.
+ */
+ i = MTW_CMDQ_GET(&sc->cmdq_store);
+ MTW_DPRINTF(sc, MTW_DEBUG_KEY, "cmdq_store=%d\n", i);
+ sc->cmdq[i].func = mtw_key_delete_cb;
+ sc->cmdq[i].arg0 = NULL;
+ sc->cmdq[i].arg1 = sc;
+ k0 = &sc->cmdq[i].key;
+ k0->wk_flags = k->wk_flags;
+ k0->wk_keyix = k->wk_keyix;
+ /* matching wcid was written to wk_pad in mtw_key_set() */
+ k0->wk_pad = k->wk_pad;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+ return (1); /* return fake success */
+}
+
+static void
+mtw_ratectl_to(void *arg)
+{
+ struct mtw_softc *sc = arg;
+ /* do it in a process context, so it can go sleep */
+ ieee80211_runtask(&sc->sc_ic, &sc->ratectl_task);
+ /* next timeout will be rescheduled in the callback task */
+}
+
+/* ARGSUSED */
+static void
+mtw_ratectl_cb(void *arg, int pending)
+{
+
+ struct mtw_softc *sc = arg;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+
+ if (vap == NULL)
+ return;
+
+ ieee80211_iterate_nodes(&ic->ic_sta, mtw_iter_func, sc);
+
+ usb_callout_reset(&sc->ratectl_ch, hz, mtw_ratectl_to, sc);
+
+
+}
+
+static void
+mtw_drain_fifo(void *arg)
+{
+ struct mtw_softc *sc = arg;
+ uint32_t stat;
+ uint16_t(*wstat)[3];
+ uint8_t wcid, mcs, pid;
+ int8_t retry;
+
+ MTW_LOCK_ASSERT(sc, MA_OWNED);
+
+ for (;;) {
+ /* drain Tx status FIFO (maxsize = 16) */
+ mtw_read(sc, MTW_TX_STAT_FIFO, &stat);
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT, "tx stat 0x%08x\n", stat);
+ if (!(stat & MTW_TXQ_VLD))
+ break;
+
+ wcid = (stat >> MTW_TXQ_WCID_SHIFT) & 0xff;
+
+ /* if no ACK was requested, no feedback is available */
+ if (!(stat & MTW_TXQ_ACKREQ) || wcid > MTW_WCID_MAX ||
+ wcid == 0)
+ continue;
+
+ /*
+ * Even though each stat is Tx-complete-status like format,
+ * the device can poll stats. Because there is no guarantee
+ * that the referring node is still around when read the stats.
+ * So that, if we use ieee80211_ratectl_tx_update(), we will
+ * have hard time not to refer already freed node.
+ *
+ * To eliminate such page faults, we poll stats in softc.
+ * Then, update the rates later with
+ * ieee80211_ratectl_tx_update().
+ */
+ wstat = &(sc->wcid_stats[wcid]);
+ (*wstat)[MTW_TXCNT]++;
+ if (stat & MTW_TXQ_OK)
+ (*wstat)[MTW_SUCCESS]++;
+ else
+ counter_u64_add(sc->sc_ic.ic_oerrors, 1);
+ /*
+ * Check if there were retries, ie if the Tx success rate is
+ * different from the requested rate. Note that it works only
+ * because we do not allow rate fallback from OFDM to CCK.
+ */
+ mcs = (stat >> MTW_TXQ_MCS_SHIFT) & 0x7f;
+ pid = (stat >> MTW_TXQ_PID_SHIFT) & 0xf;
+ if ((retry = pid - 1 - mcs) > 0) {
+ (*wstat)[MTW_TXCNT] += retry;
+ (*wstat)[MTW_RETRY] += retry;
+ }
+ }
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT, "count=%d\n", sc->fifo_cnt);
+
+ sc->fifo_cnt = 0;
+}
+
+static void
+mtw_iter_func(void *arg, struct ieee80211_node *ni)
+{
+ struct mtw_softc *sc = arg;
+ MTW_LOCK(sc);
+ struct ieee80211_ratectl_tx_stats *txs = &sc->sc_txs;
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct mtw_node *rn = MTW_NODE(ni);
+ uint32_t sta[3];
+ uint16_t(*wstat)[3];
+ int error, ridx;
+ uint8_t txrate = 0;
+
+ /* Check for special case */
+ if (sc->rvp_cnt <= 1 && vap->iv_opmode == IEEE80211_M_STA &&
+ ni != vap->iv_bss)
+ goto fail;
+
+ txs->flags = IEEE80211_RATECTL_TX_STATS_NODE |
+ IEEE80211_RATECTL_TX_STATS_RETRIES;
+ txs->ni = ni;
+ if (sc->rvp_cnt <= 1 &&
+ (vap->iv_opmode == IEEE80211_M_IBSS ||
+ vap->iv_opmode == IEEE80211_M_STA)) {
+ /*
+ * read statistic counters (clear on read) and update AMRR state
+ */
+ error = mtw_read_region_1(sc, MTW_TX_STA_CNT0, (uint8_t *)sta,
+ sizeof sta);
+ MTW_DPRINTF(sc, MTW_DEBUG_RATE, "error:%d\n", error);
+ if (error != 0)
+ goto fail;
+
+ /* count failed TX as errors */
+ if_inc_counter(vap->iv_ifp, IFCOUNTER_OERRORS,
+ le32toh(sta[0]) & 0xffff);
+
+ txs->nretries = (le32toh(sta[1]) >> 16);
+ txs->nsuccess = (le32toh(sta[1]) & 0xffff);
+ /* nretries??? */
+ txs->nframes = txs->nsuccess + (le32toh(sta[0]) & 0xffff);
+
+ MTW_DPRINTF(sc, MTW_DEBUG_RATE,
+ "retrycnt=%d success=%d failcnt=%d\n", txs->nretries,
+ txs->nsuccess, le32toh(sta[0]) & 0xffff);
+ } else {
+ wstat = &(sc->wcid_stats[MTW_AID2WCID(ni->ni_associd)]);
+
+ if (wstat == &(sc->wcid_stats[0]) ||
+ wstat > &(sc->wcid_stats[MTW_WCID_MAX]))
+ goto fail;
+
+ txs->nretries = (*wstat)[MTW_RETRY];
+ txs->nsuccess = (*wstat)[MTW_SUCCESS];
+ txs->nframes = (*wstat)[MTW_TXCNT];
+ MTW_DPRINTF(sc, MTW_DEBUG_RATE,
+ "wstat retrycnt=%d txcnt=%d success=%d\n", txs->nretries,
+ txs->nframes, txs->nsuccess);
+
+ memset(wstat, 0, sizeof(*wstat));
+ }
+
+ ieee80211_ratectl_tx_update(vap, txs);
+ ieee80211_ratectl_rate(ni, NULL, 0);
+ txrate = ieee80211_node_get_txrate_dot11rate(ni);
+
+ /* XXX TODO: methodize with MCS rates */
+ for (ridx = 0; ridx < MTW_RIDX_MAX; ridx++) {
+ MTW_DPRINTF(sc, MTW_DEBUG_RATE, "ni_txrate=0x%x\n",
+ txrate);
+ if (rt2860_rates[ridx].rate == txrate) {
+ break;
+ }
+ }
+ rn->amrr_ridx = ridx;
+fail:
+ MTW_UNLOCK(sc);
+
+ MTW_DPRINTF(sc, MTW_DEBUG_RATE, "rate=%d, ridx=%d\n",
+ txrate, rn->amrr_ridx);
+}
+
+static void
+mtw_newassoc_cb(void *arg)
+{
+ struct mtw_cmdq *cmdq = arg;
+ struct ieee80211_node *ni = cmdq->arg1;
+ struct mtw_softc *sc = ni->ni_vap->iv_ic->ic_softc;
+
+ uint8_t wcid = cmdq->wcid;
+
+ MTW_LOCK_ASSERT(sc, MA_OWNED);
+
+ mtw_write_region_1(sc, MTW_WCID_ENTRY(wcid), ni->ni_macaddr,
+ IEEE80211_ADDR_LEN);
+
+ memset(&(sc->wcid_stats[wcid]), 0, sizeof(sc->wcid_stats[wcid]));
+}
+
+static void
+mtw_newassoc(struct ieee80211_node *ni, int isnew)
+{
+
+ struct mtw_node *mn = MTW_NODE(ni);
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct ieee80211com *ic = vap->iv_ic;
+ struct mtw_softc *sc = ic->ic_softc;
+
+ uint8_t rate;
+ uint8_t ridx;
+ uint8_t wcid;
+ //int i;
+ // int i,j;
+ wcid = MTW_AID2WCID(ni->ni_associd);
+
+ if (wcid > MTW_WCID_MAX) {
+ device_printf(sc->sc_dev, "wcid=%d out of range\n", wcid);
+ return;
+ }
+
+ /* only interested in true associations */
+ if (isnew && ni->ni_associd != 0) {
+ /*
+ * This function could is called though timeout function.
+ * Need to deferggxr.
+ */
+
+ uint32_t cnt = MTW_CMDQ_GET(&sc->cmdq_store);
+ MTW_DPRINTF(sc, MTW_DEBUG_STATE, "cmdq_store=%d\n", cnt);
+ sc->cmdq[cnt].func = mtw_newassoc_cb;
+ sc->cmdq[cnt].arg0 = NULL;
+ sc->cmdq[cnt].arg1 = ni;
+ sc->cmdq[cnt].wcid = wcid;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+ }
+
+ MTW_DPRINTF(sc, MTW_DEBUG_STATE,
+ "new assoc isnew=%d associd=%x addr=%s\n", isnew, ni->ni_associd,
+ ether_sprintf(ni->ni_macaddr));
+ rate = vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)].mgmtrate;
+ /* XXX TODO: methodize with MCS rates */
+ for (ridx = 0; ridx < MTW_RIDX_MAX; ridx++)
+ if (rt2860_rates[ridx].rate == rate)
+ break;
+ mn->mgt_ridx = ridx;
+ MTW_DPRINTF(sc, MTW_DEBUG_STATE | MTW_DEBUG_RATE,
+ "rate=%d, ctl_ridx=%d\n", rate, ridx);
+ MTW_LOCK(sc);
+ if (sc->ratectl_run != MTW_RATECTL_OFF) {
+ usb_callout_reset(&sc->ratectl_ch, hz, &mtw_ratectl_to, sc);
+ }
+ MTW_UNLOCK(sc);
+
+}
+
+/*
+ * Return the Rx chain with the highest RSSI for a given frame.
+ */
+static __inline uint8_t
+mtw_maxrssi_chain(struct mtw_softc *sc, const struct mtw_rxwi *rxwi)
+{
+ uint8_t rxchain = 0;
+
+ if (sc->nrxchains > 1) {
+ if (rxwi->rssi[1] > rxwi->rssi[rxchain])
+ rxchain = 1;
+ if (sc->nrxchains > 2)
+ if (rxwi->rssi[2] > rxwi->rssi[rxchain])
+ rxchain = 2;
+ }
+ return (rxchain);
+}
+static void
+mtw_get_tsf(struct mtw_softc *sc, uint64_t *buf)
+{
+ mtw_read_region_1(sc, MTW_TSF_TIMER_DW0, (uint8_t *)buf, sizeof(*buf));
+}
+
+static void
+mtw_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m, int subtype,
+ const struct ieee80211_rx_stats *rxs, int rssi, int nf)
+{
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct mtw_softc *sc = vap->iv_ic->ic_softc;
+ struct mtw_vap *rvp = MTW_VAP(vap);
+ uint64_t ni_tstamp, rx_tstamp;
+
+ rvp->recv_mgmt(ni, m, subtype, rxs, rssi, nf);
+
+ if (vap->iv_state == IEEE80211_S_RUN &&
+ (subtype == IEEE80211_FC0_SUBTYPE_BEACON ||
+ subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP)) {
+ ni_tstamp = le64toh(ni->ni_tstamp.tsf);
+ MTW_LOCK(sc);
+ mtw_get_tsf(sc, &rx_tstamp);
+ MTW_UNLOCK(sc);
+ rx_tstamp = le64toh(rx_tstamp);
+
+ if (ni_tstamp >= rx_tstamp) {
+ MTW_DPRINTF(sc, MTW_DEBUG_RECV | MTW_DEBUG_BEACON,
+ "ibss merge, tsf %ju tstamp %ju\n",
+ (uintmax_t)rx_tstamp, (uintmax_t)ni_tstamp);
+ (void)ieee80211_ibss_merge(ni);
+ }
+ }
+}
+static void
+mtw_rx_frame(struct mtw_softc *sc, struct mbuf *m, uint32_t dmalen)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_frame *wh;
+ struct ieee80211_node *ni;
+ struct epoch_tracker et;
+
+ struct mtw_rxwi *rxwi;
+ uint32_t flags;
+ uint16_t len, rxwisize;
+ uint8_t ant, rssi;
+ int8_t nf;
+
+ rxwisize = sizeof(struct mtw_rxwi);
+
+ if (__predict_false(
+ dmalen < rxwisize + sizeof(struct ieee80211_frame_ack))) {
+ MTW_DPRINTF(sc, MTW_DEBUG_RECV,
+ "payload is too short: dma length %u < %zu\n", dmalen,
+ rxwisize + sizeof(struct ieee80211_frame_ack));
+ goto fail;
+ }
+
+ rxwi = mtod(m, struct mtw_rxwi *);
+ len = le16toh(rxwi->len) & 0xfff;
+ flags = le32toh(rxwi->flags);
+ if (__predict_false(len > dmalen - rxwisize)) {
+ MTW_DPRINTF(sc, MTW_DEBUG_RECV, "bad RXWI length %u > %u\n",
+ len, dmalen);
+ goto fail;
+ }
+
+ if (__predict_false(flags & (MTW_RX_CRCERR | MTW_RX_ICVERR))) {
+ MTW_DPRINTF(sc, MTW_DEBUG_RECV, "%s error.\n",
+ (flags & MTW_RX_CRCERR) ? "CRC" : "ICV");
+ goto fail;
+ }
+
+ if (flags & MTW_RX_L2PAD) {
+ MTW_DPRINTF(sc, MTW_DEBUG_RECV,
+ "received RT2860_RX_L2PAD frame\n");
+ len += 2;
+ }
+
+ m->m_data += rxwisize;
+ m->m_pkthdr.len = m->m_len = len;
+
+ wh = mtod(m, struct ieee80211_frame *);
+ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
+ wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED;
+ m->m_flags |= M_WEP;
+ }
+
+ if (len >= sizeof(struct ieee80211_frame_min)) {
+ ni = ieee80211_find_rxnode(ic,
+ mtod(m, struct ieee80211_frame_min *));
+ } else
+ ni = NULL;
+
+ if (ni && ni->ni_flags & IEEE80211_NODE_HT) {
+ m->m_flags |= M_AMPDU;
+ }
+
+ if (__predict_false(flags & MTW_RX_MICERR)) {
+ /* report MIC failures to net80211 for TKIP */
+ if (ni != NULL)
+ ieee80211_notify_michael_failure(ni->ni_vap, wh,
+ rxwi->keyidx);
+ MTW_DPRINTF(sc, MTW_DEBUG_RECV,
+ "MIC error. Someone is lying.\n");
+ goto fail;
+ }
+
+ ant = mtw_maxrssi_chain(sc, rxwi);
+ rssi = rxwi->rssi[ant];
+ nf = mtw_rssi2dbm(sc, rssi, ant);
+
+ if (__predict_false(ieee80211_radiotap_active(ic))) {
+ struct mtw_rx_radiotap_header *tap = &sc->sc_rxtap;
+ uint16_t phy;
+
+ tap->wr_flags = 0;
+ if (flags & MTW_RX_L2PAD)
+ tap->wr_flags |= IEEE80211_RADIOTAP_F_DATAPAD;
+ tap->wr_antsignal = rssi;
+ tap->wr_antenna = ant;
+ tap->wr_dbm_antsignal = mtw_rssi2dbm(sc, rssi, ant);
+ tap->wr_rate = 2; /* in case it can't be found below */
+ //MTW_LOCK(sc);
+
+ // MTW_UNLOCK(sc);
+ phy = le16toh(rxwi->phy);
+ switch (phy >> MT7601_PHY_SHIFT) {
+ case MTW_PHY_CCK:
+ switch ((phy & MTW_PHY_MCS) & ~MTW_PHY_SHPRE) {
+ case 0:
+ tap->wr_rate = 2;
+ break;
+ case 1:
+ tap->wr_rate = 4;
+ break;
+ case 2:
+ tap->wr_rate = 11;
+ break;
+ case 3:
+ tap->wr_rate = 22;
+ break;
+ }
+ if (phy & MTW_PHY_SHPRE)
+ tap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE;
+ break;
+ case MTW_PHY_OFDM:
+ switch (phy & MTW_PHY_MCS) {
+ case 0:
+ tap->wr_rate = 12;
+ break;
+ case 1:
+ tap->wr_rate = 18;
+ break;
+ case 2:
+ tap->wr_rate = 24;
+ break;
+ case 3:
+ tap->wr_rate = 36;
+ break;
+ case 4:
+ tap->wr_rate = 48;
+ break;
+ case 5:
+ tap->wr_rate = 72;
+ break;
+ case 6:
+ tap->wr_rate = 96;
+ break;
+ case 7:
+ tap->wr_rate = 108;
+ break;
+ }
+ break;
+ }
+ }
+
+ NET_EPOCH_ENTER(et);
+ if (ni != NULL) {
+ (void)ieee80211_input(ni, m, rssi, nf);
+ ieee80211_free_node(ni);
+ } else {
+ (void)ieee80211_input_all(ic, m, rssi, nf);
+ }
+ NET_EPOCH_EXIT(et);
+
+ return;
+
+fail:
+ m_freem(m);
+ counter_u64_add(ic->ic_ierrors, 1);
+}
+
+static void
+mtw_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct mtw_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct mbuf *m = NULL;
+ struct mbuf *m0;
+ uint32_t dmalen, mbuf_len;
+ uint16_t rxwisize;
+ int xferlen;
+
+ rxwisize = sizeof(struct mtw_rxwi);
+
+ usbd_xfer_status(xfer, &xferlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ MTW_DPRINTF(sc, MTW_DEBUG_RECV, "rx done, actlen=%d\n",
+ xferlen);
+ if (xferlen < (int)(sizeof(uint32_t) + rxwisize +
+ sizeof(struct mtw_rxd))) {
+ MTW_DPRINTF(sc, MTW_DEBUG_RECV_DESC | MTW_DEBUG_USB,
+ "xfer too short %d %d\n", xferlen,
+ (int)(sizeof(uint32_t) + rxwisize +
+ sizeof(struct mtw_rxd)));
+ goto tr_setup;
+ }
+
+ m = sc->rx_m;
+ sc->rx_m = NULL;
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+ tr_setup:
+
+ if (sc->rx_m == NULL) {
+ sc->rx_m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR,
+ MTW_MAX_RXSZ);
+ }
+ if (sc->rx_m == NULL) {
+ MTW_DPRINTF(sc,
+ MTW_DEBUG_RECV | MTW_DEBUG_RECV_DESC |
+ MTW_DEBUG_USB,
+ "could not allocate mbuf - idle with stall\n");
+ counter_u64_add(ic->ic_ierrors, 1);
+ usbd_xfer_set_stall(xfer);
+ usbd_xfer_set_frames(xfer, 0);
+ } else {
+ /*
+ * Directly loading a mbuf cluster into DMA to
+ * save some data copying. This works because
+ * there is only one cluster.
+ */
+ usbd_xfer_set_frame_data(xfer, 0,
+ mtod(sc->rx_m, caddr_t), MTW_MAX_RXSZ);
+ usbd_xfer_set_frames(xfer, 1);
+ }
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT | MTW_DEBUG_USB,
+ "USB transfer error, %s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ if (error == USB_ERR_TIMEOUT)
+ device_printf(sc->sc_dev, "device timeout %s\n",
+ __func__);
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto tr_setup;
+ }
+ if (sc->rx_m != NULL) {
+ m_freem(sc->rx_m);
+ sc->rx_m = NULL;
+ }
+ break;
+ }
+
+ if (m == NULL)
+ return;
+
+ /* inputting all the frames must be last */
+
+ MTW_UNLOCK(sc);
+
+ m->m_pkthdr.len = m->m_len = xferlen;
+
+ /* HW can aggregate multiple 802.11 frames in a single USB xfer */
+ for (;;) {
+ dmalen = le32toh(*mtod(m, uint32_t *)) & 0xffff;
+
+ if ((dmalen >= (uint32_t)-8) || (dmalen == 0) ||
+ ((dmalen & 3) != 0)) {
+ MTW_DPRINTF(sc, MTW_DEBUG_RECV_DESC | MTW_DEBUG_USB,
+ "bad DMA length %u\n", dmalen);
+ break;
+ }
+ if ((dmalen + 8) > (uint32_t)xferlen) {
+ MTW_DPRINTF(sc, MTW_DEBUG_RECV_DESC | MTW_DEBUG_USB,
+ "bad DMA length %u > %d\n", dmalen + 8, xferlen);
+ break;
+ }
+
+ /* If it is the last one or a single frame, we won't copy. */
+ if ((xferlen -= dmalen + 8) <= 8) {
+ /* trim 32-bit DMA-len header */
+ m->m_data += 4;
+ m->m_pkthdr.len = m->m_len -= 4;
+ mtw_rx_frame(sc, m, dmalen);
+ m = NULL; /* don't free source buffer */
+ break;
+ }
+
+ mbuf_len = dmalen + sizeof(struct mtw_rxd);
+ if (__predict_false(mbuf_len > MCLBYTES)) {
+ MTW_DPRINTF(sc, MTW_DEBUG_RECV_DESC | MTW_DEBUG_USB,
+ "payload is too big: mbuf_len %u\n", mbuf_len);
+ counter_u64_add(ic->ic_ierrors, 1);
+ break;
+ }
+
+ /* copy aggregated frames to another mbuf */
+ m0 = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (__predict_false(m0 == NULL)) {
+ MTW_DPRINTF(sc, MTW_DEBUG_RECV_DESC,
+ "could not allocate mbuf\n");
+ counter_u64_add(ic->ic_ierrors, 1);
+ break;
+ }
+ m_copydata(m, 4 /* skip 32-bit DMA-len header */, mbuf_len,
+ mtod(m0, caddr_t));
+ m0->m_pkthdr.len = m0->m_len = mbuf_len;
+ mtw_rx_frame(sc, m0, dmalen);
+
+ /* update data ptr */
+ m->m_data += mbuf_len + 4;
+ m->m_pkthdr.len = m->m_len -= mbuf_len + 4;
+ }
+
+ /* make sure we free the source buffer, if any */
+ m_freem(m);
+
+#ifdef IEEE80211_SUPPORT_SUPERG
+ ieee80211_ff_age_all(ic, 100);
+#endif
+ MTW_LOCK(sc);
+}
+
+static void
+mtw_tx_free(struct mtw_endpoint_queue *pq, struct mtw_tx_data *data, int txerr)
+{
+
+ ieee80211_tx_complete(data->ni, data->m, txerr);
+ data->m = NULL;
+ data->ni = NULL;
+
+ STAILQ_INSERT_TAIL(&pq->tx_fh, data, next);
+ pq->tx_nfree++;
+}
+static void
+mtw_bulk_tx_callbackN(struct usb_xfer *xfer, usb_error_t error, u_int index)
+{
+ struct mtw_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct mtw_tx_data *data;
+ struct ieee80211vap *vap = NULL;
+ struct usb_page_cache *pc;
+ struct mtw_endpoint_queue *pq = &sc->sc_epq[index];
+ struct mbuf *m;
+ usb_frlength_t size;
+ int actlen;
+ int sumlen;
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT | MTW_DEBUG_USB,
+ "transfer complete: %d bytes @ index %d\n", actlen, index);
+
+ data = usbd_xfer_get_priv(xfer);
+ mtw_tx_free(pq, data, 0);
+ usbd_xfer_set_priv(xfer, NULL);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+ tr_setup:
+ data = STAILQ_FIRST(&pq->tx_qh);
+ if (data == NULL)
+ break;
+
+ STAILQ_REMOVE_HEAD(&pq->tx_qh, next);
+
+ m = data->m;
+
+ size = sizeof(data->desc);
+ if ((m->m_pkthdr.len + size + 3 + 8) > MTW_MAX_TXSZ) {
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT_DESC | MTW_DEBUG_USB,
+ "data overflow, %u bytes\n", m->m_pkthdr.len);
+ mtw_tx_free(pq, data, 1);
+ goto tr_setup;
+ }
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &data->desc, size);
+ usbd_m_copy_in(pc, size, m, 0, m->m_pkthdr.len);
+ size += m->m_pkthdr.len;
+ /*
+ * Align end on a 4-byte boundary, pad 8 bytes (CRC +
+ * 4-byte padding), and be sure to zero those trailing
+ * bytes:
+ */
+ usbd_frame_zero(pc, size, ((-size) & 3) + MTW_DMA_PAD);
+ size += ((-size) & 3) + MTW_DMA_PAD;
+
+ vap = data->ni->ni_vap;
+ if (ieee80211_radiotap_active_vap(vap)) {
+ const struct ieee80211_frame *wh;
+ struct mtw_tx_radiotap_header *tap = &sc->sc_txtap;
+ struct mtw_txwi *txwi =
+ (struct mtw_txwi *)(&data->desc +
+ sizeof(struct mtw_txd));
+ int has_l2pad;
+
+ wh = mtod(m, struct ieee80211_frame *);
+ has_l2pad = IEEE80211_HAS_ADDR4(wh) !=
+ IEEE80211_QOS_HAS_SEQ(wh);
+
+ tap->wt_flags = 0;
+ tap->wt_rate = rt2860_rates[data->ridx].rate;
+ tap->wt_hwqueue = index;
+ if (le16toh(txwi->phy) & MTW_PHY_SHPRE)
+ tap->wt_flags |= IEEE80211_RADIOTAP_F_SHORTPRE;
+ if (has_l2pad)
+ tap->wt_flags |= IEEE80211_RADIOTAP_F_DATAPAD;
+
+ ieee80211_radiotap_tx(vap, m);
+ }
+
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT | MTW_DEBUG_USB,
+ "sending frame len=%u/%u @ index %d\n", m->m_pkthdr.len,
+ size, index);
+
+ usbd_xfer_set_frame_len(xfer, 0, size);
+ usbd_xfer_set_priv(xfer, data);
+ usbd_transfer_submit(xfer);
+ mtw_start(sc);
+
+ break;
+
+ default:
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT | MTW_DEBUG_USB,
+ "USB transfer error, %s\n", usbd_errstr(error));
+
+ data = usbd_xfer_get_priv(xfer);
+
+ if (data != NULL) {
+ if (data->ni != NULL)
+ vap = data->ni->ni_vap;
+ mtw_tx_free(pq, data, error);
+ usbd_xfer_set_priv(xfer, NULL);
+ }
+
+ if (vap == NULL)
+ vap = TAILQ_FIRST(&ic->ic_vaps);
+
+ if (error != USB_ERR_CANCELLED) {
+ if (error == USB_ERR_TIMEOUT) {
+ device_printf(sc->sc_dev, "device timeout %s\n",
+ __func__);
+ uint32_t i = MTW_CMDQ_GET(&sc->cmdq_store);
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT | MTW_DEBUG_USB,
+ "cmdq_store=%d\n", i);
+ sc->cmdq[i].func = mtw_usb_timeout_cb;
+ sc->cmdq[i].arg0 = vap;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+ }
+
+ /*
+ * Try to clear stall first, also if other
+ * errors occur, hence clearing stall
+ * introduces a 50 ms delay:
+ */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+#ifdef IEEE80211_SUPPORT_SUPERG
+ /* XXX TODO: make this deferred rather than unlock/relock */
+ /* XXX TODO: should only do the QoS AC this belongs to */
+ if (pq->tx_nfree >= MTW_TX_RING_COUNT) {
+ MTW_UNLOCK(sc);
+ ieee80211_ff_flush_all(ic);
+ MTW_LOCK(sc);
+ }
+#endif
+}
+
+static void
+mtw_fw_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct mtw_softc *sc = usbd_xfer_softc(xfer);
+
+ int actlen;
+ int ntries, tmp;
+ // struct mtw_txd *data;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+ // data = usbd_xfer_get_priv(xfer);
+ usbd_xfer_set_priv(xfer, NULL);
+ switch (USB_GET_STATE(xfer)) {
+
+ case USB_ST_TRANSFERRED:
+ sc->sc_sent += actlen;
+ memset(sc->txd_fw[sc->sc_idx], 0, actlen);
+
+ if (actlen < 0x2c44 && sc->sc_idx == 0) {
+ return;
+ }
+ if (sc->sc_idx == 3) {
+
+ if ((error = mtw_write_ivb(sc, sc->sc_ivb_1,
+ MTW_MCU_IVB_LEN)) != 0) {
+ device_printf(sc->sc_dev,
+ "Could not write ivb error: %d\n", error);
+ }
+
+ mtw_delay(sc, 10);
+ for (ntries = 0; ntries < 100; ntries++) {
+ if ((error = mtw_read_cfg(sc, MTW_MCU_DMA_ADDR,
+ &tmp)) != 0) {
+ device_printf(sc->sc_dev,
+ "Could not read cfg error: %d\n", error);
+
+ }
+ if (tmp == MTW_MCU_READY) {
+ MTW_DPRINTF(sc, MTW_DEBUG_FIRMWARE,
+ "mcu reaady %d\n", tmp);
+ sc->fwloading = 1;
+ break;
+ }
+
+ mtw_delay(sc, 10);
+ }
+ if (ntries == 100)
+ sc->fwloading = 0;
+ wakeup(&sc->fwloading);
+ return;
+ }
+
+ if (actlen == 0x2c44) {
+ sc->sc_idx++;
+ DELAY(1000);
+ }
+
+ case USB_ST_SETUP: {
+ int dlen = 0;
+ dlen = sc->txd_fw[sc->sc_idx]->len;
+
+ mtw_write_cfg(sc, MTW_MCU_DMA_ADDR, 0x40 + sc->sc_sent);
+ mtw_write_cfg(sc, MTW_MCU_DMA_LEN, (dlen << 16));
+
+ usbd_xfer_set_frame_len(xfer, 0, dlen);
+ usbd_xfer_set_frame_data(xfer, 0, sc->txd_fw[sc->sc_idx], dlen);
+
+ // usbd_xfer_set_priv(xfer,sc->txd[sc->sc_idx]);
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ device_printf(sc->sc_dev, "%s:%d %s\n", __FILE__, __LINE__,
+ usbd_errstr(error));
+ sc->fwloading = 0;
+ wakeup(&sc->fwloading);
+ /*
+ * Print error message and clear stall
+ * for example.
+ */
+ break;
+ }
+ /*
+ * Here it is safe to do something without the private
+ * USB mutex locked.
+ */
+ }
+ return;
+}
+static void
+mtw_bulk_tx_callback0(struct usb_xfer *xfer, usb_error_t error)
+{
+ mtw_bulk_tx_callbackN(xfer, error, 0);
+}
+
+static void
+mtw_bulk_tx_callback1(struct usb_xfer *xfer, usb_error_t error)
+{
+
+
+ mtw_bulk_tx_callbackN(xfer, error, 1);
+}
+
+static void
+mtw_bulk_tx_callback2(struct usb_xfer *xfer, usb_error_t error)
+{
+ mtw_bulk_tx_callbackN(xfer, error, 2);
+}
+
+static void
+mtw_bulk_tx_callback3(struct usb_xfer *xfer, usb_error_t error)
+{
+ mtw_bulk_tx_callbackN(xfer, error, 3);
+}
+
+static void
+mtw_bulk_tx_callback4(struct usb_xfer *xfer, usb_error_t error)
+{
+ mtw_bulk_tx_callbackN(xfer, error, 4);
+}
+
+static void
+mtw_bulk_tx_callback5(struct usb_xfer *xfer, usb_error_t error)
+{
+ mtw_bulk_tx_callbackN(xfer, error, 5);
+}
+
+static void
+mtw_set_tx_desc(struct mtw_softc *sc, struct mtw_tx_data *data)
+{
+ struct mbuf *m = data->m;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = data->ni->ni_vap;
+ struct ieee80211_frame *wh;
+ struct mtw_txd *txd;
+ struct mtw_txwi *txwi;
+ uint16_t xferlen, txwisize;
+ uint16_t mcs;
+ uint8_t ridx = data->ridx;
+ uint8_t pad;
+
+ /* get MCS code from rate index */
+ mcs = rt2860_rates[ridx].mcs;
+
+ txwisize = sizeof(*txwi);
+ xferlen = txwisize + m->m_pkthdr.len;
+
+ /* roundup to 32-bit alignment */
+ xferlen = (xferlen + 3) & ~3;
+
+ txd = (struct mtw_txd *)&data->desc;
+ txd->len = htole16(xferlen);
+
+ wh = mtod(m, struct ieee80211_frame *);
+
+ /*
+ * Ether both are true or both are false, the header
+ * are nicely aligned to 32-bit. So, no L2 padding.
+ */
+ if (IEEE80211_HAS_ADDR4(wh) == IEEE80211_QOS_HAS_SEQ(wh))
+ pad = 0;
+ else
+ pad = 2;
+
+ /* setup TX Wireless Information */
+ txwi = (struct mtw_txwi *)(txd + 1);
+ txwi->len = htole16(m->m_pkthdr.len - pad);
+ if (rt2860_rates[ridx].phy == IEEE80211_T_DS) {
+ mcs |= MTW_PHY_CCK;
+ if (ridx != MTW_RIDX_CCK1 &&
+ (ic->ic_flags & IEEE80211_F_SHPREAMBLE))
+ mcs |= MTW_PHY_SHPRE;
+ } else if (rt2860_rates[ridx].phy == IEEE80211_T_OFDM) {
+ mcs |= MTW_PHY_OFDM;
+ } else if (rt2860_rates[ridx].phy == IEEE80211_T_HT) {
+ /* XXX TODO: [adrian] set short preamble for MCS? */
+ mcs |= MTW_PHY_HT; /* Mixed, not greenfield */
+ }
+ txwi->phy = htole16(mcs);
+
+ /* check if RTS/CTS or CTS-to-self protection is required */
+ if (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
+ ((m->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) ||
+ ((ic->ic_flags & IEEE80211_F_USEPROT) &&
+ rt2860_rates[ridx].phy == IEEE80211_T_OFDM) ||
+ ((ic->ic_htprotmode == IEEE80211_PROT_RTSCTS) &&
+ rt2860_rates[ridx].phy == IEEE80211_T_HT)))
+ txwi->txop |= MTW_TX_TXOP_HT;
+ else
+ txwi->txop |= MTW_TX_TXOP_BACKOFF;
+
+}
+
+/* This function must be called locked */
+static int
+mtw_tx(struct mtw_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct ieee80211_frame *wh;
+
+
+ //const struct ieee80211_txparam *tp = ni->ni_txparms;
+ struct mtw_node *rn = MTW_NODE(ni);
+ struct mtw_tx_data *data;
+ struct mtw_txd *txd;
+ struct mtw_txwi *txwi;
+ uint16_t qos;
+ uint16_t dur;
+ uint16_t qid;
+ uint8_t type;
+ uint8_t tid;
+ uint16_t ridx;
+ uint8_t ctl_ridx;
+ uint16_t qflags;
+ uint8_t xflags = 0;
+
+ int hasqos;
+
+ MTW_LOCK_ASSERT(sc, MA_OWNED);
+
+ wh = mtod(m, struct ieee80211_frame *);
+ const struct ieee80211_txparam *tp = ni->ni_txparms;
+ type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
+
+ qflags = htole16(MTW_TXD_DATA | MTW_TXD_80211 |
+ MTW_TXD_WLAN | MTW_TXD_QSEL_HCCA);
+
+ if ((hasqos = IEEE80211_QOS_HAS_SEQ(wh))) {
+ uint8_t *frm;
+ frm = ieee80211_getqos(wh);
+
+
+ //device_printf(sc->sc_dev,"JSS:frm:%d",*frm);
+ qos = le16toh(*(const uint16_t *)frm);
+ tid = ieee80211_gettid(wh);
+ qid = TID_TO_WME_AC(tid);
+ qflags |= MTW_TXD_QSEL_EDCA;
+ } else {
+ qos = 0;
+ tid = 0;
+ qid = WME_AC_BE;
+ }
+ if (type & IEEE80211_FC0_TYPE_MGT) {
+ qid = 0;
+ }
+
+ if (type != IEEE80211_FC0_TYPE_DATA)
+ qflags |= htole16(MTW_TXD_WIV);
+
+ if (IEEE80211_IS_MULTICAST(wh->i_addr1) ||
+ type != IEEE80211_FC0_TYPE_DATA || m->m_flags & M_EAPOL) {
+ /* XXX TODO: methodize for 11n; use MCS0 for 11NA/11NG */
+ ridx = (ic->ic_curmode == IEEE80211_MODE_11A
+ || ic->ic_curmode == IEEE80211_MODE_11NA) ?
+ MTW_RIDX_OFDM6 : MTW_RIDX_CCK1;
+ if (type == IEEE80211_MODE_11NG) {
+ ridx = 12;
+ }
+ ctl_ridx = rt2860_rates[ridx].ctl_ridx;
+ } else {
+ if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) {
+ ridx = rn->fix_ridx;
+
+ } else {
+ ridx = rn->amrr_ridx;
+ ctl_ridx = rt2860_rates[ridx].ctl_ridx;
+ }
+ }
+
+ if (hasqos)
+ xflags = 0;
+ else
+ xflags = MTW_TX_NSEQ;
+
+ if (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
+ (!hasqos ||
+ (qos & IEEE80211_QOS_ACKPOLICY) !=
+ IEEE80211_QOS_ACKPOLICY_NOACK)) {
+ xflags |= MTW_TX_ACK;
+ if (ic->ic_flags & IEEE80211_F_SHPREAMBLE)
+ dur = rt2860_rates[ctl_ridx].sp_ack_dur;
+ else
+ dur = rt2860_rates[ctl_ridx].lp_ack_dur;
+ USETW(wh->i_dur, dur);
+ }
+ /* reserve slots for mgmt packets, just in case */
+ if (sc->sc_epq[qid].tx_nfree < 3) {
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT, "tx ring %d is full\n", qid);
+ return (-1);
+ }
+
+ data = STAILQ_FIRST(&sc->sc_epq[qid].tx_fh);
+ STAILQ_REMOVE_HEAD(&sc->sc_epq[qid].tx_fh, next);
+ sc->sc_epq[qid].tx_nfree--;
+
+ txd = (struct mtw_txd *)&data->desc;
+ txd->flags = qflags;
+
+ txwi = (struct mtw_txwi *)(txd + 1);
+ txwi->xflags = xflags;
+ txwi->wcid = (type == IEEE80211_FC0_TYPE_DATA) ?
+
+ MTW_AID2WCID(ni->ni_associd) :
+ 0xff;
+
+ /* clear leftover garbage bits */
+ txwi->flags = 0;
+ txwi->txop = 0;
+
+ data->m = m;
+ data->ni = ni;
+ data->ridx = ridx;
+
+ mtw_set_tx_desc(sc, data);
+
+ /*
+ * The chip keeps track of 2 kind of Tx stats,
+ * * TX_STAT_FIFO, for per WCID stats, and
+ * * TX_STA_CNT0 for all-TX-in-one stats.
+ *
+ * To use FIFO stats, we need to store MCS into the driver-private
+ * PacketID field. So that, we can tell whose stats when we read them.
+ * We add 1 to the MCS because setting the PacketID field to 0 means
+ * that we don't want feedback in TX_STAT_FIFO.
+ * And, that's what we want for STA mode, since TX_STA_CNT0 does the
+ * job.
+ *
+ * FIFO stats doesn't count Tx with WCID 0xff, so we do this in
+ * run_tx().
+ */
+
+ if (sc->rvp_cnt > 1 || vap->iv_opmode == IEEE80211_M_HOSTAP ||
+ vap->iv_opmode == IEEE80211_M_MBSS) {
+
+ /*
+ * Unlike PCI based devices, we don't get any interrupt from
+ * USB devices, so we simulate FIFO-is-full interrupt here.
+ * Ralink recommends to drain FIFO stats every 100 ms, but 16
+ * slots quickly get fulled. To prevent overflow, increment a
+ * counter on every FIFO stat request, so we know how many slots
+ * are left. We do this only in HOSTAP or multiple vap mode
+ * since FIFO stats are used only in those modes. We just drain
+ * stats. AMRR gets updated every 1 sec by run_ratectl_cb() via
+ * callout. Call it early. Otherwise overflow.
+ */
+ if (sc->fifo_cnt++ == 10) {
+ /*
+ * With multiple vaps or if_bridge, if_start() is called
+ * with a non-sleepable lock, tcpinp. So, need to defer.
+ */
+ uint32_t i = MTW_CMDQ_GET(&sc->cmdq_store);
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT, "cmdq_store=%d\n", i);
+ sc->cmdq[i].func = mtw_drain_fifo;
+ sc->cmdq[i].arg0 = sc;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+ }
+ }
+
+ STAILQ_INSERT_TAIL(&sc->sc_epq[qid].tx_qh, data, next);
+ usbd_transfer_start(sc->sc_xfer[mtw_wme_ac_xfer_map[qid]]);
+
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT,
+ "sending data frame len=%d rate=%d qid=%d\n",
+ m->m_pkthdr.len +
+ (int)(sizeof(struct mtw_txd) + sizeof(struct mtw_txwi)),
+ rt2860_rates[ridx].rate, qid);
+
+ return (0);
+ }
+
+static int
+mtw_tx_mgt(struct mtw_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct mtw_node *rn = MTW_NODE(ni);
+ struct mtw_tx_data *data;
+ struct ieee80211_frame *wh;
+ struct mtw_txd *txd;
+ struct mtw_txwi *txwi;
+ uint8_t type;
+ uint16_t dur;
+ uint8_t ridx = rn->mgt_ridx;
+ uint8_t xflags = 0;
+ uint8_t wflags = 0;
+
+ MTW_LOCK_ASSERT(sc, MA_OWNED);
+
+ wh = mtod(m, struct ieee80211_frame *);
+
+ /* 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))
+ wflags |= MTW_TX_TS;
+ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
+ xflags |= MTW_TX_ACK;
+
+ dur = ieee80211_ack_duration(ic->ic_rt, rt2860_rates[ridx].rate,
+ ic->ic_flags & IEEE80211_F_SHPREAMBLE);
+ USETW(wh->i_dur, dur);
+ }
+ type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
+ if (sc->sc_epq[0].tx_nfree == 0)
+ /* let caller free mbuf */
+ return (EIO);
+ data = STAILQ_FIRST(&sc->sc_epq[0].tx_fh);
+ STAILQ_REMOVE_HEAD(&sc->sc_epq[0].tx_fh, next);
+ sc->sc_epq[0].tx_nfree--;
+
+ txd = (struct mtw_txd *)&data->desc;
+ txd->flags = htole16(
+ MTW_TXD_DATA | MTW_TXD_80211 | MTW_TXD_WLAN | MTW_TXD_QSEL_EDCA);
+ if (type != IEEE80211_FC0_TYPE_DATA)
+ txd->flags |= htole16(MTW_TXD_WIV);
+
+ txwi = (struct mtw_txwi *)(txd + 1);
+ txwi->wcid = 0xff;
+ txwi->xflags = xflags;
+ txwi->flags = wflags;
+
+ txwi->txop = 0; /* clear leftover garbage bits */
+
+ data->m = m;
+ data->ni = ni;
+ data->ridx = ridx;
+
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT, "sending mgt frame len=%d rate=%d\n",
+ m->m_pkthdr.len +
+ (int)(sizeof(struct mtw_txd) + sizeof(struct mtw_txwi)),
+ rt2860_rates[ridx].rate);
+
+ STAILQ_INSERT_TAIL(&sc->sc_epq[0].tx_qh, data, next);
+
+ usbd_transfer_start(sc->sc_xfer[MTW_BULK_TX_BE]);
+
+ return (0);
+}
+
+static int
+mtw_sendprot(struct mtw_softc *sc, const struct mbuf *m,
+ struct ieee80211_node *ni, int prot, int rate)
+{
+ struct ieee80211com *ic = ni->ni_ic;
+ struct mtw_tx_data *data;
+ struct mtw_txd *txd;
+ struct mtw_txwi *txwi;
+ struct mbuf *mprot;
+ int ridx;
+ int protrate;
+ uint8_t wflags = 0;
+ uint8_t xflags = 0;
+
+ MTW_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* check that there are free slots before allocating the mbuf */
+ if (sc->sc_epq[0].tx_nfree == 0)
+ /* let caller free mbuf */
+ return (ENOBUFS);
+
+ mprot = ieee80211_alloc_prot(ni, m, rate, prot);
+ if (mprot == NULL) {
+ if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1);
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT, "could not allocate mbuf\n");
+ return (ENOBUFS);
+ }
+
+ protrate = ieee80211_ctl_rate(ic->ic_rt, rate);
+ wflags = MTW_TX_FRAG;
+ xflags = 0;
+ if (prot == IEEE80211_PROT_RTSCTS)
+ xflags |= MTW_TX_ACK;
+
+ data = STAILQ_FIRST(&sc->sc_epq[0].tx_fh);
+ STAILQ_REMOVE_HEAD(&sc->sc_epq[0].tx_fh, next);
+ sc->sc_epq[0].tx_nfree--;
+
+ txd = (struct mtw_txd *)&data->desc;
+ txd->flags = RT2860_TX_QSEL_EDCA;
+ txwi = (struct mtw_txwi *)(txd + 1);
+ txwi->wcid = 0xff;
+ txwi->flags = wflags;
+ txwi->xflags = xflags;
+ txwi->txop = 0; /* clear leftover garbage bits */
+
+ data->m = mprot;
+ data->ni = ieee80211_ref_node(ni);
+
+ /* XXX TODO: methodize with MCS rates */
+ for (ridx = 0; ridx < MTW_RIDX_MAX; ridx++)
+ if (rt2860_rates[ridx].rate == protrate)
+ break;
+ data->ridx = ridx;
+
+ mtw_set_tx_desc(sc, data);
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT, "sending prot len=%u rate=%u\n",
+ m->m_pkthdr.len, rate);
+
+ STAILQ_INSERT_TAIL(&sc->sc_epq[0].tx_qh, data, next);
+
+ usbd_transfer_start(sc->sc_xfer[0]);
+
+ return (0);
+}
+
+static int
+mtw_tx_param(struct mtw_softc *sc, struct mbuf *m, struct ieee80211_node *ni,
+ const struct ieee80211_bpf_params *params)
+{
+ struct ieee80211com *ic = ni->ni_ic;
+ struct mtw_tx_data *data;
+ struct mtw_txd *txd;
+ struct mtw_txwi *txwi;
+ uint8_t ridx;
+ uint8_t rate;
+ uint8_t opflags = 0;
+ uint8_t xflags = 0;
+ int error;
+
+ MTW_LOCK_ASSERT(sc, MA_OWNED);
+
+ KASSERT(params != NULL, ("no raw xmit params"));
+
+ rate = params->ibp_rate0;
+ if (!ieee80211_isratevalid(ic->ic_rt, rate)) {
+ /* let caller free mbuf */
+ return (EINVAL);
+ }
+
+ if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0)
+ xflags |= MTW_TX_ACK;
+ if (params->ibp_flags & (IEEE80211_BPF_RTS | IEEE80211_BPF_CTS)) {
+ error = mtw_sendprot(sc, m, ni,
+ params->ibp_flags & IEEE80211_BPF_RTS ?
+ IEEE80211_PROT_RTSCTS :
+ IEEE80211_PROT_CTSONLY,
+ rate);
+ if (error) {
+ device_printf(sc->sc_dev, "%s:%d %d\n", __FILE__,
+ __LINE__, error);
+ return (error);
+ }
+ opflags |= MTW_TX_TXOP_SIFS;
+ }
+
+ if (sc->sc_epq[0].tx_nfree == 0) {
+ /* let caller free mbuf */
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT,
+ "sending raw frame, but tx ring is full\n");
+ return (EIO);
+ }
+ data = STAILQ_FIRST(&sc->sc_epq[0].tx_fh);
+ STAILQ_REMOVE_HEAD(&sc->sc_epq[0].tx_fh, next);
+ sc->sc_epq[0].tx_nfree--;
+
+ txd = (struct mtw_txd *)&data->desc;
+ txd->flags = htole16(
+ MTW_TXD_DATA | MTW_TXD_80211 | MTW_TXD_WLAN | MTW_TXD_QSEL_EDCA);
+ // txd->flags = htole16(MTW_TXD_QSEL_EDCA);
+ txwi = (struct mtw_txwi *)(txd + 1);
+ txwi->wcid = 0xff;
+ txwi->xflags = xflags;
+ txwi->txop = opflags;
+ txwi->flags = 0; /* clear leftover garbage bits */
+
+ data->m = m;
+ data->ni = ni;
+ /* XXX TODO: methodize with MCS rates */
+ for (ridx = 0; ridx < MTW_RIDX_MAX; ridx++)
+ if (rt2860_rates[ridx].rate == rate)
+ break;
+ data->ridx = ridx;
+
+ mtw_set_tx_desc(sc, data);
+
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT, "sending raw frame len=%u rate=%u\n",
+ m->m_pkthdr.len, rate);
+
+ STAILQ_INSERT_TAIL(&sc->sc_epq[0].tx_qh, data, next);
+
+ usbd_transfer_start(sc->sc_xfer[MTW_BULK_RAW_TX]);
+
+ return (0);
+}
+
+static int
+mtw_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
+ const struct ieee80211_bpf_params *params)
+{
+ struct mtw_softc *sc = ni->ni_ic->ic_softc;
+ int error = 0;
+ MTW_LOCK(sc);
+ /* prevent management frames from being sent if we're not ready */
+ if (!(sc->sc_flags & MTW_RUNNING)) {
+ error = ENETDOWN;
+ goto done;
+ }
+
+ if (params == NULL) {
+ /* tx mgt packet */
+ if ((error = mtw_tx_mgt(sc, m, ni)) != 0) {
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT, "mgt tx failed\n");
+ goto done;
+ }
+ } else {
+ /* tx raw packet with param */
+ if ((error = mtw_tx_param(sc, m, ni, params)) != 0) {
+ MTW_DPRINTF(sc, MTW_DEBUG_XMIT,
+ "tx with param failed\n");
+ goto done;
+ }
+ }
+
+done:
+
+ MTW_UNLOCK(sc);
+
+ if (error != 0) {
+ if (m != NULL)
+ m_freem(m);
+ }
+
+ return (error);
+}
+
+static int
+mtw_transmit(struct ieee80211com *ic, struct mbuf *m)
+{
+ struct mtw_softc *sc = ic->ic_softc;
+ int error;
+ MTW_LOCK(sc);
+ if ((sc->sc_flags & MTW_RUNNING) == 0) {
+ MTW_UNLOCK(sc);
+ return (ENXIO);
+ }
+ error = mbufq_enqueue(&sc->sc_snd, m);
+ if (error) {
+ MTW_UNLOCK(sc);
+ return (error);
+ }
+ mtw_start(sc);
+ MTW_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+mtw_start(struct mtw_softc *sc)
+{
+ struct ieee80211_node *ni;
+ struct mbuf *m;
+
+ MTW_LOCK_ASSERT(sc, MA_OWNED);
+
+ if ((sc->sc_flags & MTW_RUNNING) == 0) {
+
+ return;
+ }
+ while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
+ ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
+ if (mtw_tx(sc, m, ni) != 0) {
+ mbufq_prepend(&sc->sc_snd, m);
+ break;
+ }
+ }
+}
+
+static void
+mtw_parent(struct ieee80211com *ic)
+{
+
+ struct mtw_softc *sc = ic->ic_softc;
+
+ MTW_LOCK(sc);
+ if (sc->sc_detached) {
+ MTW_UNLOCK(sc);
+ return;
+ }
+
+ if (!(sc->sc_flags & MTW_RUNNING) && ic->ic_nrunning > 0) {
+ mtw_init_locked(sc);
+ MTW_UNLOCK(sc);
+ ieee80211_start_all(ic);
+ return;
+ }
+ if (!(sc->sc_flags & MTW_RUNNING) && ic->ic_nrunning > 0) {
+ mtw_update_promisc_locked(sc);
+ MTW_UNLOCK(sc);
+ return;
+ }
+ if ((sc->sc_flags & MTW_RUNNING) && sc->rvp_cnt <= 1 &&
+ ic->ic_nrunning == 0) {
+ mtw_stop(sc);
+ MTW_UNLOCK(sc);
+ return;
+ }
+ return;
+}
+
+static void
+mt7601_set_agc(struct mtw_softc *sc, uint8_t agc)
+{
+ uint8_t bbp;
+
+ mtw_bbp_write(sc, 66, agc);
+ mtw_bbp_write(sc, 195, 0x87);
+ bbp = (agc & 0xf0) | 0x08;
+ mtw_bbp_write(sc, 196, bbp);
+}
+
+static int
+mtw_mcu_calibrate(struct mtw_softc *sc, int func, uint32_t val)
+{
+ struct mtw_mcu_cmd_8 cmd;
+
+ cmd.func = htole32(func);
+ cmd.val = htole32(val);
+ return (mtw_mcu_cmd(sc, 31, &cmd, sizeof(struct mtw_mcu_cmd_8)));
+}
+
+static int
+mtw_rf_write(struct mtw_softc *sc, uint8_t bank, uint8_t reg, uint8_t val)
+{
+ uint32_t tmp;
+ int error, ntries, shift;
+
+ for (ntries = 0; ntries < 10; ntries++) {
+ if ((error = mtw_read(sc, MTW_RF_CSR, &tmp)) != 0)
+ return (error);
+ if (!(tmp & MTW_RF_CSR_KICK))
+ break;
+ }
+ if (ntries == 10)
+ return (ETIMEDOUT);
+
+ if (sc->asic_ver == 0x7601)
+ shift = MT7601_BANK_SHIFT;
+ else
+ shift = MT7610_BANK_SHIFT;
+
+ tmp = MTW_RF_CSR_WRITE | MTW_RF_CSR_KICK | (bank & 0xf) << shift |
+ reg << 8 | val;
+ return (mtw_write(sc, MTW_RF_CSR, tmp));
+}
+
+void
+mtw_select_chan_group(struct mtw_softc *sc, int group)
+{
+ uint32_t tmp;
+ uint8_t bbp;
+
+ /* Tx band 20MHz 2G */
+ mtw_read(sc, MTW_TX_BAND_CFG, &tmp);
+ tmp &= ~(
+ MTW_TX_BAND_SEL_2G | MTW_TX_BAND_SEL_5G | MTW_TX_BAND_UPPER_40M);
+ tmp |= (group == 0) ? MTW_TX_BAND_SEL_2G : MTW_TX_BAND_SEL_5G;
+ mtw_write(sc, MTW_TX_BAND_CFG, tmp);
+
+ /* select 20 MHz bandwidth */
+ mtw_bbp_read(sc, 4, &bbp);
+ bbp &= ~0x18;
+ bbp |= 0x40;
+ mtw_bbp_write(sc, 4, bbp);
+
+ /* calibrate BBP */
+ mtw_bbp_write(sc, 69, 0x12);
+ mtw_bbp_write(sc, 91, 0x07);
+ mtw_bbp_write(sc, 195, 0x23);
+ mtw_bbp_write(sc, 196, 0x17);
+ mtw_bbp_write(sc, 195, 0x24);
+ mtw_bbp_write(sc, 196, 0x06);
+ mtw_bbp_write(sc, 195, 0x81);
+ mtw_bbp_write(sc, 196, 0x12);
+ mtw_bbp_write(sc, 195, 0x83);
+ mtw_bbp_write(sc, 196, 0x17);
+ mtw_rf_write(sc, 5, 8, 0x00);
+ // mtw_mcu_calibrate(sc, 0x6, 0x10001);
+
+ /* set initial AGC value */
+ mt7601_set_agc(sc, 0x14);
+}
+
+static int
+mtw_rf_read(struct mtw_softc *sc, uint8_t bank, uint8_t reg, uint8_t *val)
+{
+ uint32_t tmp;
+ int error, ntries, shift;
+
+ for (ntries = 0; ntries < 100; ntries++) {
+ if ((error = mtw_read(sc, MTW_RF_CSR, &tmp)) != 0)
+ return (error);
+ if (!(tmp & MTW_RF_CSR_KICK))
+ break;
+ }
+ if (ntries == 100)
+ return (ETIMEDOUT);
+
+ if (sc->asic_ver == 0x7601)
+ shift = MT7601_BANK_SHIFT;
+ else
+ shift = MT7610_BANK_SHIFT;
+
+ tmp = MTW_RF_CSR_KICK | (bank & 0xf) << shift | reg << 8;
+ if ((error = mtw_write(sc, MTW_RF_CSR, tmp)) != 0)
+ return (error);
+
+ for (ntries = 0; ntries < 100; ntries++) {
+ if ((error = mtw_read(sc, MTW_RF_CSR, &tmp)) != 0)
+ return (error);
+ if (!(tmp & MTW_RF_CSR_KICK))
+ break;
+ }
+ if (ntries == 100)
+ return (ETIMEDOUT);
+
+ *val = tmp & 0xff;
+ return (0);
+}
+static void
+mt7601_set_chan(struct mtw_softc *sc, u_int chan)
+{
+ uint32_t tmp;
+ uint8_t bbp, rf, txpow1;
+ int i;
+ /* find the settings for this channel */
+ for (i = 0; mt7601_rf_chan[i].chan != chan; i++)
+ ;
+
+ mtw_rf_write(sc, 0, 17, mt7601_rf_chan[i].r17);
+ mtw_rf_write(sc, 0, 18, mt7601_rf_chan[i].r18);
+ mtw_rf_write(sc, 0, 19, mt7601_rf_chan[i].r19);
+ mtw_rf_write(sc, 0, 20, mt7601_rf_chan[i].r20);
+
+ /* use Tx power values from EEPROM */
+ txpow1 = sc->txpow1[i];
+
+ /* Tx automatic level control */
+ mtw_read(sc, MTW_TX_ALC_CFG0, &tmp);
+ tmp &= ~0x3f3f;
+ tmp |= (txpow1 & 0x3f);
+ mtw_write(sc, MTW_TX_ALC_CFG0, tmp);
+
+ /* LNA */
+ mtw_bbp_write(sc, 62, 0x37 - sc->lna[0]);
+ mtw_bbp_write(sc, 63, 0x37 - sc->lna[0]);
+ mtw_bbp_write(sc, 64, 0x37 - sc->lna[0]);
+
+ /* VCO calibration */
+ mtw_rf_write(sc, 0, 4, 0x0a);
+ mtw_rf_write(sc, 0, 5, 0x20);
+ mtw_rf_read(sc, 0, 4, &rf);
+ mtw_rf_write(sc, 0, 4, rf | 0x80);
+
+ /* select 20 MHz bandwidth */
+ mtw_bbp_read(sc, 4, &bbp);
+ bbp &= ~0x18;
+ bbp |= 0x40;
+ mtw_bbp_write(sc, 4, bbp);
+ mtw_bbp_write(sc, 178, 0xff);
+}
+
+static int
+mtw_set_chan(struct mtw_softc *sc, struct ieee80211_channel *c)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ u_int chan, group;
+
+ chan = ieee80211_chan2ieee(ic, c);
+ if (chan == 0 || chan == IEEE80211_CHAN_ANY)
+ return (EINVAL);
+
+ /* determine channel group */
+ if (chan <= 14)
+ group = 0;
+ else if (chan <= 64)
+ group = 1;
+ else if (chan <= 128)
+ group = 2;
+ else
+ group = 3;
+
+ if (group != sc->sc_chan_group || !sc->sc_bw_calibrated)
+ mtw_select_chan_group(sc, group);
+
+ sc->sc_chan_group = group;
+
+ /* chipset specific */
+ if (sc->asic_ver == 0x7601)
+ mt7601_set_chan(sc, chan);
+
+ DELAY(1000);
+ return (0);
+}
+
+static void
+mtw_set_channel(struct ieee80211com *ic)
+{
+ struct mtw_softc *sc = ic->ic_softc;
+
+ MTW_LOCK(sc);
+ mtw_set_chan(sc, ic->ic_curchan);
+ MTW_UNLOCK(sc);
+
+ return;
+}
+
+static void
+mtw_getradiocaps(struct ieee80211com *ic, int maxchans, int *nchans,
+ struct ieee80211_channel chans[])
+{
+ // struct mtw_softc *sc = ic->ic_softc;
+ uint8_t bands[IEEE80211_MODE_BYTES];
+
+ memset(bands, 0, sizeof(bands));
+ setbit(bands, IEEE80211_MODE_11B);
+ setbit(bands, IEEE80211_MODE_11G);
+ setbit(bands, IEEE80211_MODE_11NG);
+
+ /* Note: for now, only support HT20 channels */
+ ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0);
+}
+
+static void
+mtw_scan_start(struct ieee80211com *ic)
+{
+ struct mtw_softc *sc = ic->ic_softc;
+ MTW_LOCK(sc);
+ /* abort TSF synchronization */
+ mtw_abort_tsf_sync(sc);
+ mtw_set_bssid(sc, ieee80211broadcastaddr);
+
+ MTW_UNLOCK(sc);
+
+ return;
+}
+
+static void
+mtw_scan_end(struct ieee80211com *ic)
+{
+ struct mtw_softc *sc = ic->ic_softc;
+
+ MTW_LOCK(sc);
+
+ mtw_enable_tsf_sync(sc);
+ mtw_set_bssid(sc, sc->sc_bssid);
+
+ MTW_UNLOCK(sc);
+
+ return;
+}
+
+/*
+ * Could be called from ieee80211_node_timeout()
+ * (non-sleepable thread)
+ */
+static void
+mtw_update_beacon(struct ieee80211vap *vap, int item)
+{
+ struct ieee80211com *ic = vap->iv_ic;
+ struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off;
+ struct ieee80211_node *ni = vap->iv_bss;
+ struct mtw_softc *sc = ic->ic_softc;
+ struct mtw_vap *rvp = MTW_VAP(vap);
+ int mcast = 0;
+ uint32_t i;
+
+ switch (item) {
+ case IEEE80211_BEACON_ERP:
+ mtw_updateslot(ic);
+ break;
+ case IEEE80211_BEACON_HTINFO:
+ mtw_updateprot(ic);
+ break;
+ case IEEE80211_BEACON_TIM:
+ mcast = 1; /*TODO*/
+ break;
+ default:
+ break;
+ }
+
+ setbit(bo->bo_flags, item);
+ if (rvp->beacon_mbuf == NULL) {
+ rvp->beacon_mbuf = ieee80211_beacon_alloc(ni);
+ if (rvp->beacon_mbuf == NULL)
+ return;
+ }
+ ieee80211_beacon_update(ni, rvp->beacon_mbuf, mcast);
+
+ i = MTW_CMDQ_GET(&sc->cmdq_store);
+ MTW_DPRINTF(sc, MTW_DEBUG_BEACON, "cmdq_store=%d\n", i);
+ sc->cmdq[i].func = mtw_update_beacon_cb;
+ sc->cmdq[i].arg0 = vap;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+
+ return;
+}
+
+static void
+mtw_update_beacon_cb(void *arg)
+{
+
+ struct ieee80211vap *vap = arg;
+ struct ieee80211_node *ni = vap->iv_bss;
+ struct mtw_vap *rvp = MTW_VAP(vap);
+ struct ieee80211com *ic = vap->iv_ic;
+ struct mtw_softc *sc = ic->ic_softc;
+ struct mtw_txwi txwi;
+ struct mbuf *m;
+ uint16_t txwisize;
+ uint8_t ridx;
+ if (ni->ni_chan == IEEE80211_CHAN_ANYC)
+ return;
+ if (ic->ic_bsschan == IEEE80211_CHAN_ANYC)
+ return;
+
+ /*
+ * No need to call ieee80211_beacon_update(), mtw_update_beacon()
+ * is taking care of appropriate calls.
+ */
+ if (rvp->beacon_mbuf == NULL) {
+ rvp->beacon_mbuf = ieee80211_beacon_alloc(ni);
+ if (rvp->beacon_mbuf == NULL)
+ return;
+ }
+ m = rvp->beacon_mbuf;
+
+ memset(&txwi, 0, sizeof(txwi));
+ txwi.wcid = 0xff;
+ txwi.len = htole16(m->m_pkthdr.len);
+
+ /* send beacons at the lowest available rate */
+ ridx = (ic->ic_curmode == IEEE80211_MODE_11A) ? MTW_RIDX_OFDM6 :
+ MTW_RIDX_CCK1;
+ txwi.phy = htole16(rt2860_rates[ridx].mcs);
+ if (rt2860_rates[ridx].phy == IEEE80211_T_OFDM)
+ txwi.phy |= htole16(MTW_PHY_OFDM);
+ txwi.txop = MTW_TX_TXOP_HT;
+ txwi.flags = MTW_TX_TS;
+ txwi.xflags = MTW_TX_NSEQ;
+
+ txwisize = sizeof(txwi);
+ mtw_write_region_1(sc, MTW_BCN_BASE, (uint8_t *)&txwi, txwisize);
+ mtw_write_region_1(sc, MTW_BCN_BASE + txwisize, mtod(m, uint8_t *),
+ (m->m_pkthdr.len + 1) & ~1);
+}
+
+static void
+mtw_updateprot(struct ieee80211com *ic)
+{
+ struct mtw_softc *sc = ic->ic_softc;
+ uint32_t i;
+
+ i = MTW_CMDQ_GET(&sc->cmdq_store);
+ MTW_DPRINTF(sc, MTW_DEBUG_BEACON, "test cmdq_store=%d\n", i);
+ sc->cmdq[i].func = mtw_updateprot_cb;
+ sc->cmdq[i].arg0 = ic;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+}
+
+static void
+mtw_updateprot_cb(void *arg)
+{
+
+ struct ieee80211com *ic = arg;
+ struct mtw_softc *sc = ic->ic_softc;
+ uint32_t tmp;
+
+ tmp = RT2860_RTSTH_EN | RT2860_PROT_NAV_SHORT | RT2860_TXOP_ALLOW_ALL;
+ /* setup protection frame rate (MCS code) */
+ tmp |= (ic->ic_curmode == IEEE80211_MODE_11A) ?
+ rt2860_rates[MTW_RIDX_OFDM6].mcs | MTW_PHY_OFDM :
+ rt2860_rates[MTW_RIDX_CCK11].mcs;
+
+ /* CCK frames don't require protection */
+ mtw_write(sc, MTW_CCK_PROT_CFG, tmp);
+ if (ic->ic_flags & IEEE80211_F_USEPROT) {
+ if (ic->ic_protmode == IEEE80211_PROT_RTSCTS)
+ tmp |= RT2860_PROT_CTRL_RTS_CTS;
+ else if (ic->ic_protmode == IEEE80211_PROT_CTSONLY)
+ tmp |= RT2860_PROT_CTRL_CTS;
+ }
+ mtw_write(sc, MTW_OFDM_PROT_CFG, tmp);
+}
+
+static void
+mtw_usb_timeout_cb(void *arg)
+{
+ struct ieee80211vap *vap = arg;
+ struct mtw_softc *sc = vap->iv_ic->ic_softc;
+
+ MTW_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (vap->iv_state == IEEE80211_S_SCAN) {
+ MTW_DPRINTF(sc, MTW_DEBUG_USB | MTW_DEBUG_STATE,
+ "timeout caused by scan\n");
+ /* cancel bgscan */
+ ieee80211_cancel_scan(vap);
+ } else {
+ MTW_DPRINTF(sc, MTW_DEBUG_USB | MTW_DEBUG_STATE,
+ "timeout by unknown cause\n");
+ }
+}
+static int mtw_reset(struct mtw_softc *sc)
+{
+
+ usb_device_request_t req;
+ uint16_t tmp;
+ uint16_t actlen;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = MTW_RESET;
+ USETW(req.wValue, 1);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+ return (usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx,
+ &req, &tmp, 0, &actlen, 1000));
+
+}
+
+
+static void
+mtw_update_promisc_locked(struct mtw_softc *sc)
+{
+
+ uint32_t tmp;
+
+ mtw_read(sc, MTW_RX_FILTR_CFG, &tmp);
+
+ tmp |= MTW_DROP_UC_NOME;
+ if (sc->sc_ic.ic_promisc > 0)
+ tmp &= ~MTW_DROP_UC_NOME;
+
+ mtw_write(sc, MTW_RX_FILTR_CFG, tmp);
+
+ MTW_DPRINTF(sc, MTW_DEBUG_RECV, "%s promiscuous mode\n",
+ (sc->sc_ic.ic_promisc > 0) ? "entering" : "leaving");
+}
+
+static void
+mtw_update_promisc(struct ieee80211com *ic)
+{
+ struct mtw_softc *sc = ic->ic_softc;
+
+ if ((sc->sc_flags & MTW_RUNNING) == 0)
+ return;
+
+ MTW_LOCK(sc);
+ mtw_update_promisc_locked(sc);
+ MTW_UNLOCK(sc);
+}
+
+static void
+mtw_enable_tsf_sync(struct mtw_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ uint32_t tmp;
+ int error;
+ mtw_read(sc, MTW_BCN_TIME_CFG, &tmp);
+ tmp &= ~0x1fffff;
+ tmp |= vap->iv_bss->ni_intval * 16;
+ tmp |= MTW_TSF_TIMER_EN | MTW_TBTT_TIMER_EN;
+
+ /* local TSF is always updated with remote TSF on beacon reception */
+ tmp |= 1 << MTW_TSF_SYNC_MODE_SHIFT;
+ error = mtw_write(sc, MTW_BCN_TIME_CFG, tmp);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "enable_tsf_sync failed error:%d\n",
+ error);
+ }
+ return;
+}
+
+static void
+mtw_enable_mrr(struct mtw_softc *sc)
+{
+#define CCK(mcs) (mcs)
+
+#define OFDM(mcs) (1 << 3 | (mcs))
+ mtw_write(sc, MTW_LG_FBK_CFG0,
+ OFDM(6) << 28 | /* 54->48 */
+ OFDM(5) << 24 | /* 48->36 */
+ OFDM(4) << 20 | /* 36->24 */
+ OFDM(3) << 16 | /* 24->18 */
+ OFDM(2) << 12 | /* 18->12 */
+ OFDM(1) << 8 | /* 12-> 9 */
+ OFDM(0) << 4 | /* 9-> 6 */
+ OFDM(0)); /* 6-> 6 */
+
+ mtw_write(sc, MTW_LG_FBK_CFG1,
+ CCK(2) << 12 | /* 11->5.5 */
+ CCK(1) << 8 | /* 5.5-> 2 */
+ CCK(0) << 4 | /* 2-> 1 */
+ CCK(0)); /* 1-> 1 */
+#undef OFDM
+#undef CCK
+}
+
+static void
+mtw_set_txpreamble(struct mtw_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t tmp;
+
+ mtw_read(sc, MTW_AUTO_RSP_CFG, &tmp);
+ if (ic->ic_flags & IEEE80211_F_SHPREAMBLE)
+ tmp |= MTW_CCK_SHORT_EN;
+ else
+ tmp &= ~MTW_CCK_SHORT_EN;
+ mtw_write(sc, MTW_AUTO_RSP_CFG, tmp);
+}
+
+static void
+mtw_set_basicrates(struct mtw_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ /* set basic rates mask */
+ if (ic->ic_curmode == IEEE80211_MODE_11B)
+ mtw_write(sc, MTW_LEGACY_BASIC_RATE, 0x003);
+ else if (ic->ic_curmode == IEEE80211_MODE_11A)
+ mtw_write(sc, MTW_LEGACY_BASIC_RATE, 0x150);
+ else /* 11g */
+ mtw_write(sc, MTW_LEGACY_BASIC_RATE, 0x17f);
+}
+
+static void
+mtw_set_bssid(struct mtw_softc *sc, const uint8_t *bssid)
+{
+ mtw_write(sc, MTW_MAC_BSSID_DW0,
+ bssid[0] | bssid[1] << 8 | bssid[2] << 16 | bssid[3] << 24);
+ mtw_write(sc, MTW_MAC_BSSID_DW1, bssid[4] | bssid[5] << 8);
+}
+
+static void
+mtw_set_macaddr(struct mtw_softc *sc, const uint8_t *addr)
+{
+ mtw_write(sc, MTW_MAC_ADDR_DW0,
+ addr[0] | addr[1] << 8 | addr[2] << 16 | addr[3] << 24);
+ mtw_write(sc, MTW_MAC_ADDR_DW1, addr[4] | addr[5] << 8 | 0xff << 16);
+}
+
+static void
+mtw_updateslot(struct ieee80211com *ic)
+{
+
+ struct mtw_softc *sc = ic->ic_softc;
+ uint32_t i;
+
+ i = MTW_CMDQ_GET(&sc->cmdq_store);
+ MTW_DPRINTF(sc, MTW_DEBUG_BEACON, "cmdq_store=%d\n", i);
+ sc->cmdq[i].func = mtw_updateslot_cb;
+ sc->cmdq[i].arg0 = ic;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+
+ return;
+}
+
+/* ARGSUSED */
+static void
+mtw_updateslot_cb(void *arg)
+{
+ struct ieee80211com *ic = arg;
+ struct mtw_softc *sc = ic->ic_softc;
+ uint32_t tmp;
+ mtw_read(sc, MTW_BKOFF_SLOT_CFG, &tmp);
+ tmp &= ~0xff;
+ tmp |= IEEE80211_GET_SLOTTIME(ic);
+ mtw_write(sc, MTW_BKOFF_SLOT_CFG, tmp);
+}
+
+static void
+mtw_update_mcast(struct ieee80211com *ic)
+{
+}
+
+static int8_t
+mtw_rssi2dbm(struct mtw_softc *sc, uint8_t rssi, uint8_t rxchain)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_channel *c = ic->ic_curchan;
+ int delta;
+
+ if (IEEE80211_IS_CHAN_5GHZ(c)) {
+ u_int chan = ieee80211_chan2ieee(ic, c);
+ delta = sc->rssi_5ghz[rxchain];
+
+ /* determine channel group */
+ if (chan <= 64)
+ delta -= sc->lna[1];
+ else if (chan <= 128)
+ delta -= sc->lna[2];
+ else
+ delta -= sc->lna[3];
+ } else
+ delta = sc->rssi_2ghz[rxchain] - sc->lna[0];
+
+ return (-12 - delta - rssi);
+}
+static int
+mt7601_bbp_init(struct mtw_softc *sc)
+{
+ uint8_t bbp;
+ int i, error, ntries;
+
+ /* wait for BBP to wake up */
+ for (ntries = 0; ntries < 20; ntries++) {
+ if ((error = mtw_bbp_read(sc, 0, &bbp)) != 0)
+ return (error);
+ if (bbp != 0 && bbp != 0xff)
+ break;
+ }
+
+ if (ntries == 20)
+ return (ETIMEDOUT);
+
+ mtw_bbp_read(sc, 3, &bbp);
+ mtw_bbp_write(sc, 3, 0);
+ mtw_bbp_read(sc, 105, &bbp);
+ mtw_bbp_write(sc, 105, 0);
+
+ /* initialize BBP registers to default values */
+ for (i = 0; i < nitems(mt7601_def_bbp); i++) {
+ if ((error = mtw_bbp_write(sc, mt7601_def_bbp[i].reg,
+ mt7601_def_bbp[i].val)) != 0)
+ return (error);
+ }
+
+ sc->sc_bw_calibrated = 0;
+
+ return (0);
+}
+
+static int
+mt7601_rf_init(struct mtw_softc *sc)
+{
+ int i, error;
+
+ /* RF bank 0 */
+ for (i = 0; i < nitems(mt7601_rf_bank0); i++) {
+ error = mtw_rf_write(sc, 0, mt7601_rf_bank0[i].reg,
+ mt7601_rf_bank0[i].val);
+ if (error != 0)
+ return (error);
+ }
+ /* RF bank 4 */
+ for (i = 0; i < nitems(mt7601_rf_bank4); i++) {
+ error = mtw_rf_write(sc, 4, mt7601_rf_bank4[i].reg,
+ mt7601_rf_bank4[i].val);
+ if (error != 0)
+ return (error);
+ }
+ /* RF bank 5 */
+ for (i = 0; i < nitems(mt7601_rf_bank5); i++) {
+ error = mtw_rf_write(sc, 5, mt7601_rf_bank5[i].reg,
+ mt7601_rf_bank5[i].val);
+ if (error != 0)
+ return (error);
+ }
+ return (0);
+}
+
+static int
+mtw_txrx_enable(struct mtw_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t tmp;
+ int error, ntries;
+ mtw_write(sc, MTW_MAC_SYS_CTRL, MTW_MAC_TX_EN);
+ for (ntries = 0; ntries < 200; ntries++) {
+ if ((error = mtw_read(sc, MTW_WPDMA_GLO_CFG, &tmp)) != 0) {
+ return (error);
+ }
+ if ((tmp & (MTW_TX_DMA_BUSY | MTW_RX_DMA_BUSY)) == 0)
+ break;
+ mtw_delay(sc, 50);
+ }
+ if (ntries == 200) {
+ return (ETIMEDOUT);
+ }
+
+ DELAY(50);
+
+ tmp |= MTW_RX_DMA_EN | MTW_TX_DMA_EN | MTW_TX_WB_DDONE;
+ mtw_write(sc, MTW_WPDMA_GLO_CFG, tmp);
+
+ /* enable Rx bulk aggregation (set timeout and limit) */
+ tmp = MTW_USB_TX_EN | MTW_USB_RX_EN | MTW_USB_RX_AGG_EN |
+ MTW_USB_RX_AGG_TO(128) | MTW_USB_RX_AGG_LMT(2);
+ mtw_write(sc, MTW_USB_DMA_CFG, tmp);
+
+ /* set Rx filter */
+ tmp = MTW_DROP_CRC_ERR | MTW_DROP_PHY_ERR;
+ if (ic->ic_opmode != IEEE80211_M_MONITOR) {
+ tmp |= MTW_DROP_UC_NOME | MTW_DROP_DUPL | MTW_DROP_CTS |
+ MTW_DROP_BA | MTW_DROP_ACK | MTW_DROP_VER_ERR |
+ MTW_DROP_CTRL_RSV | MTW_DROP_CFACK | MTW_DROP_CFEND;
+ if (ic->ic_opmode == IEEE80211_M_STA)
+ tmp |= MTW_DROP_RTS | MTW_DROP_PSPOLL;
+ }
+ mtw_write(sc, MTW_RX_FILTR_CFG, tmp);
+
+ mtw_write(sc, MTW_MAC_SYS_CTRL, MTW_MAC_RX_EN | MTW_MAC_TX_EN);
+ return (0);
+}
+static int
+mt7601_rxdc_cal(struct mtw_softc *sc)
+{
+ uint32_t tmp;
+ uint8_t bbp;
+ int ntries;
+
+ mtw_read(sc, MTW_MAC_SYS_CTRL, &tmp);
+ mtw_write(sc, MTW_MAC_SYS_CTRL, MTW_MAC_RX_EN);
+ mtw_bbp_write(sc, 158, 0x8d);
+ mtw_bbp_write(sc, 159, 0xfc);
+ mtw_bbp_write(sc, 158, 0x8c);
+ mtw_bbp_write(sc, 159, 0x4c);
+
+ for (ntries = 0; ntries < 20; ntries++) {
+ DELAY(300);
+ mtw_bbp_write(sc, 158, 0x8c);
+ mtw_bbp_read(sc, 159, &bbp);
+ if (bbp == 0x0c)
+ break;
+ }
+
+ if (ntries == 20)
+ return (ETIMEDOUT);
+
+ mtw_write(sc, MTW_MAC_SYS_CTRL, 0);
+ mtw_bbp_write(sc, 158, 0x8d);
+ mtw_bbp_write(sc, 159, 0xe0);
+ mtw_write(sc, MTW_MAC_SYS_CTRL, tmp);
+ return (0);
+}
+
+static int
+mt7601_r49_read(struct mtw_softc *sc, uint8_t flag, int8_t *val)
+{
+ uint8_t bbp;
+
+ mtw_bbp_read(sc, 47, &bbp);
+ bbp = 0x90;
+ mtw_bbp_write(sc, 47, bbp);
+ bbp &= ~0x0f;
+ bbp |= flag;
+ mtw_bbp_write(sc, 47, bbp);
+ return (mtw_bbp_read(sc, 49, val));
+}
+
+static int
+mt7601_rf_temperature(struct mtw_softc *sc, int8_t *val)
+{
+ uint32_t rfb, rfs;
+ uint8_t bbp;
+ int ntries;
+
+ mtw_read(sc, MTW_RF_BYPASS0, &rfb);
+ mtw_read(sc, MTW_RF_SETTING0, &rfs);
+ mtw_write(sc, MTW_RF_BYPASS0, 0);
+ mtw_write(sc, MTW_RF_SETTING0, 0x10);
+ mtw_write(sc, MTW_RF_BYPASS0, 0x10);
+
+ mtw_bbp_read(sc, 47, &bbp);
+ bbp &= ~0x7f;
+ bbp |= 0x10;
+ mtw_bbp_write(sc, 47, bbp);
+
+ mtw_bbp_write(sc, 22, 0x40);
+
+ for (ntries = 0; ntries < 10; ntries++) {
+ mtw_bbp_read(sc, 47, &bbp);
+ if ((bbp & 0x10) == 0)
+ break;
+ }
+ if (ntries == 10)
+ return (ETIMEDOUT);
+
+ mt7601_r49_read(sc, MT7601_R47_TEMP, val);
+
+ mtw_bbp_write(sc, 22, 0);
+
+ mtw_bbp_read(sc, 21, &bbp);
+ bbp |= 0x02;
+ mtw_bbp_write(sc, 21, bbp);
+ bbp &= ~0x02;
+ mtw_bbp_write(sc, 21, bbp);
+
+ mtw_write(sc, MTW_RF_BYPASS0, 0);
+ mtw_write(sc, MTW_RF_SETTING0, rfs);
+ mtw_write(sc, MTW_RF_BYPASS0, rfb);
+ return (0);
+}
+
+static int
+mt7601_rf_setup(struct mtw_softc *sc)
+{
+ uint32_t tmp;
+ uint8_t rf;
+ int error;
+
+ if (sc->sc_rf_calibrated)
+ return (0);
+
+ /* init RF registers */
+ if ((error = mt7601_rf_init(sc)) != 0)
+ return (error);
+
+ /* init frequency offset */
+ mtw_rf_write(sc, 0, 12, sc->rf_freq_offset);
+ mtw_rf_read(sc, 0, 12, &rf);
+
+ /* read temperature */
+ mt7601_rf_temperature(sc, &rf);
+ sc->bbp_temp = rf;
+ device_printf(sc->sc_dev, "BBP temp 0x%x\n", rf);
+
+ mtw_rf_read(sc, 0, 7, &rf);
+ if ((error = mtw_mcu_calibrate(sc, 0x1, 0)) != 0)
+ return (error);
+ mtw_delay(sc, 100);
+ mtw_rf_read(sc, 0, 7, &rf);
+
+ /* Calibrate VCO RF 0/4 */
+ mtw_rf_write(sc, 0, 4, 0x0a);
+ mtw_rf_write(sc, 0, 4, 0x20);
+ mtw_rf_read(sc, 0, 4, &rf);
+ mtw_rf_write(sc, 0, 4, rf | 0x80);
+
+ if ((error = mtw_mcu_calibrate(sc, 0x9, 0)) != 0)
+ return (error);
+ if ((error = mt7601_rxdc_cal(sc)) != 0)
+ return (error);
+ if ((error = mtw_mcu_calibrate(sc, 0x6, 1)) != 0)
+ return (error);
+ if ((error = mtw_mcu_calibrate(sc, 0x6, 0)) != 0)
+ return (error);
+ if ((error = mtw_mcu_calibrate(sc, 0x4, 0)) != 0)
+ return (error);
+ if ((error = mtw_mcu_calibrate(sc, 0x5, 0)) != 0)
+ return (error);
+
+ mtw_read(sc, MTW_LDO_CFG0, &tmp);
+ tmp &= ~(1 << 4);
+ tmp |= (1 << 2);
+ mtw_write(sc, MTW_LDO_CFG0, tmp);
+
+ if ((error = mtw_mcu_calibrate(sc, 0x8, 0)) != 0)
+ return (error);
+ if ((error = mt7601_rxdc_cal(sc)) != 0)
+ return (error);
+
+ sc->sc_rf_calibrated = 1;
+ return (0);
+}
+
+static void
+mtw_set_txrts(struct mtw_softc *sc)
+{
+ uint32_t tmp;
+
+ /* set RTS threshold */
+ mtw_read(sc, MTW_TX_RTS_CFG, &tmp);
+ tmp &= ~0xffff00;
+ tmp |= 0x1000 << MTW_RTS_THRES_SHIFT;
+ mtw_write(sc, MTW_TX_RTS_CFG, tmp);
+}
+static int
+mtw_mcu_radio(struct mtw_softc *sc, int func, uint32_t val)
+{
+ struct mtw_mcu_cmd_16 cmd;
+
+ cmd.r1 = htole32(func);
+ cmd.r2 = htole32(val);
+ cmd.r3 = 0;
+ cmd.r4 = 0;
+ return (mtw_mcu_cmd(sc, 20, &cmd, sizeof(struct mtw_mcu_cmd_16)));
+}
+static void
+mtw_init_locked(struct mtw_softc *sc)
+{
+
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ uint32_t tmp;
+ int i, error, ridx, ntries;
+ if (ic->ic_nrunning > 1)
+ return;
+ mtw_stop(sc);
+
+ for (i = 0; i != MTW_EP_QUEUES; i++)
+ mtw_setup_tx_list(sc, &sc->sc_epq[i]);
+
+ for (ntries = 0; ntries < 100; ntries++) {
+ if ((error = mtw_read(sc, MTW_WPDMA_GLO_CFG, &tmp)) != 0)
+ goto fail;
+ if ((tmp & (MTW_TX_DMA_BUSY | MTW_RX_DMA_BUSY)) == 0)
+ break;
+ DELAY(1000);
+ }
+ if (ntries == 100) {
+ device_printf(sc->sc_dev, "timeout waiting for DMA engine\n");
+ error = ETIMEDOUT;
+ goto fail;
+ }
+ tmp &= 0xff0;
+ tmp |= MTW_TX_WB_DDONE;
+ mtw_write(sc, MTW_WPDMA_GLO_CFG, tmp);
+
+ mtw_set_leds(sc, MTW_LED_MODE_ON);
+ /* reset MAC and baseband */
+ mtw_write(sc, MTW_MAC_SYS_CTRL, MTW_BBP_HRST | MTW_MAC_SRST);
+ mtw_write(sc, MTW_USB_DMA_CFG, 0);
+ mtw_write(sc, MTW_MAC_SYS_CTRL, 0);
+
+ /* init MAC values */
+ if (sc->asic_ver == 0x7601) {
+ for (i = 0; i < nitems(mt7601_def_mac); i++)
+ mtw_write(sc, mt7601_def_mac[i].reg,
+ mt7601_def_mac[i].val);
+ }
+
+ /* wait while MAC is busy */
+ for (ntries = 0; ntries < 100; ntries++) {
+ if ((error = mtw_read(sc, MTW_MAC_STATUS_REG, &tmp)) != 0)
+ goto fail;
+ if (!(tmp & (MTW_RX_STATUS_BUSY | MTW_TX_STATUS_BUSY)))
+ break;
+ DELAY(1000);
+ }
+ if (ntries == 100) {
+ error = ETIMEDOUT;
+ goto fail;
+ }
+
+ /* set MAC address */
+
+ mtw_set_macaddr(sc, vap ? vap->iv_myaddr : ic->ic_macaddr);
+
+ /* clear WCID attribute table */
+ mtw_set_region_4(sc, MTW_WCID_ATTR(0), 1, 8 * 32);
+
+ mtw_write(sc, 0x1648, 0x00830083);
+ mtw_read(sc, MTW_FCE_L2_STUFF, &tmp);
+ tmp &= ~MTW_L2S_WR_MPDU_LEN_EN;
+ mtw_write(sc, MTW_FCE_L2_STUFF, tmp);
+
+ /* RTS config */
+ mtw_set_txrts(sc);
+
+ /* clear Host to MCU mailbox */
+ mtw_write(sc, MTW_BBP_CSR, 0);
+ mtw_write(sc, MTW_H2M_MAILBOX, 0);
+
+ /* clear RX WCID search table */
+ mtw_set_region_4(sc, MTW_WCID_ENTRY(0), 0xffffffff, 512);
+
+ /* abort TSF synchronization */
+ mtw_abort_tsf_sync(sc);
+
+ mtw_read(sc, MTW_US_CYC_CNT, &tmp);
+ tmp = (tmp & ~0xff);
+ if (sc->asic_ver == 0x7601)
+ tmp |= 0x1e;
+ mtw_write(sc, MTW_US_CYC_CNT, tmp);
+
+ /* clear shared key table */
+ mtw_set_region_4(sc, MTW_SKEY(0, 0), 0, 8 * 32);
+
+ /* clear IV/EIV table */
+ mtw_set_region_4(sc, MTW_IVEIV(0), 0, 8 * 32);
+
+ /* clear shared key mode */
+ mtw_write(sc, MTW_SKEY_MODE_0_7, 0);
+ mtw_write(sc, MTW_SKEY_MODE_8_15, 0);
+
+ /* txop truncation */
+ mtw_write(sc, MTW_TXOP_CTRL_CFG, 0x0000583f);
+
+ /* init Tx power for all Tx rates */
+ for (ridx = 0; ridx < 5; ridx++) {
+ if (sc->txpow20mhz[ridx] == 0xffffffff)
+ continue;
+ mtw_write(sc, MTW_TX_PWR_CFG(ridx), sc->txpow20mhz[ridx]);
+ }
+ mtw_write(sc, MTW_TX_PWR_CFG7, 0);
+ mtw_write(sc, MTW_TX_PWR_CFG9, 0);
+
+ mtw_read(sc, MTW_CMB_CTRL, &tmp);
+ tmp &= ~(1 << 18 | 1 << 14);
+ mtw_write(sc, MTW_CMB_CTRL, tmp);
+
+ /* clear USB DMA */
+ mtw_write(sc, MTW_USB_DMA_CFG,
+ MTW_USB_TX_EN | MTW_USB_RX_EN | MTW_USB_RX_AGG_EN |
+ MTW_USB_TX_CLEAR | MTW_USB_TXOP_HALT | MTW_USB_RX_WL_DROP);
+ mtw_delay(sc, 50);
+ mtw_read(sc, MTW_USB_DMA_CFG, &tmp);
+ tmp &= ~(MTW_USB_TX_CLEAR | MTW_USB_TXOP_HALT | MTW_USB_RX_WL_DROP);
+ mtw_write(sc, MTW_USB_DMA_CFG, tmp);
+
+ /* enable radio */
+ mtw_mcu_radio(sc, 0x31, 0);
+
+ /* init RF registers */
+ if (sc->asic_ver == 0x7601)
+ mt7601_rf_init(sc);
+
+ /* init baseband registers */
+ if (sc->asic_ver == 0x7601)
+ error = mt7601_bbp_init(sc);
+
+ if (error != 0) {
+ device_printf(sc->sc_dev, "could not initialize BBP\n");
+ goto fail;
+ }
+
+ /* setup and calibrate RF */
+ error = mt7601_rf_setup(sc);
+
+ if (error != 0) {
+ device_printf(sc->sc_dev, "could not initialize RF\n");
+ goto fail;
+ }
+
+ /* select default channel */
+ mtw_set_chan(sc, ic->ic_curchan);
+
+ /* setup initial protection mode */
+ mtw_updateprot_cb(ic);
+
+ sc->sc_flags |= MTW_RUNNING;
+ sc->cmdq_run = MTW_CMDQ_GO;
+ for (i = 0; i != MTW_N_XFER; i++)
+ usbd_xfer_set_stall(sc->sc_xfer[i]);
+
+ usbd_transfer_start(sc->sc_xfer[MTW_BULK_RX]);
+
+ error = mtw_txrx_enable(sc);
+ if (error != 0) {
+ goto fail;
+ }
+
+ return;
+
+fail:
+
+ mtw_stop(sc);
+ return;
+}
+
+static void
+mtw_stop(void *arg)
+{
+ struct mtw_softc *sc = (struct mtw_softc *)arg;
+ uint32_t tmp;
+ int i, ntries, error;
+
+ MTW_LOCK_ASSERT(sc, MA_OWNED);
+
+ sc->sc_flags &= ~MTW_RUNNING;
+
+ sc->ratectl_run = MTW_RATECTL_OFF;
+ sc->cmdq_run = sc->cmdq_key_set;
+
+ MTW_UNLOCK(sc);
+
+ for (i = 0; i < MTW_N_XFER; i++)
+ usbd_transfer_drain(sc->sc_xfer[i]);
+
+ MTW_LOCK(sc);
+
+ mtw_drain_mbufq(sc);
+
+ if (sc->rx_m != NULL) {
+ m_free(sc->rx_m);
+ sc->rx_m = NULL;
+ }
+
+ /* Disable Tx/Rx DMA. */
+ mtw_read(sc, MTW_WPDMA_GLO_CFG, &tmp);
+ tmp &= ~(MTW_RX_DMA_EN | MTW_TX_DMA_EN);
+ mtw_write(sc, MTW_WPDMA_GLO_CFG, tmp);
+ // mtw_usb_dma_write(sc, 0);
+
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (mtw_read(sc, MTW_WPDMA_GLO_CFG, &tmp) != 0)
+ break;
+ if ((tmp & (MTW_TX_DMA_BUSY | MTW_RX_DMA_BUSY)) == 0)
+ break;
+ DELAY(10);
+ }
+ if (ntries == 100) {
+ device_printf(sc->sc_dev, "timeout waiting for DMA engine\n");
+ }
+
+ /* stop MAC Tx/Rx */
+ mtw_read(sc, MTW_MAC_SYS_CTRL, &tmp);
+ tmp &= ~(MTW_MAC_RX_EN | MTW_MAC_TX_EN);
+ mtw_write(sc, MTW_MAC_SYS_CTRL, tmp);
+
+ /* disable RTS retry */
+ mtw_read(sc, MTW_TX_RTS_CFG, &tmp);
+ tmp &= ~0xff;
+ mtw_write(sc, MTW_TX_RTS_CFG, tmp);
+
+ /* US_CYC_CFG */
+ mtw_read(sc, MTW_US_CYC_CNT, &tmp);
+ tmp = (tmp & ~0xff);
+ mtw_write(sc, MTW_US_CYC_CNT, tmp);
+
+ /* stop PBF */
+ mtw_read(sc, MTW_PBF_CFG, &tmp);
+ tmp &= ~0x3;
+ mtw_write(sc, MTW_PBF_CFG, tmp);
+
+ /* wait for pending Tx to complete */
+ for (ntries = 0; ntries < 100; ntries++) {
+ if ((error = mtw_read(sc, MTW_TXRXQ_PCNT, &tmp)) != 0)
+ break;
+ if ((tmp & MTW_TX2Q_PCNT_MASK) == 0)
+ break;
+ }
+
+}
+
+static void
+mtw_delay(struct mtw_softc *sc, u_int ms)
+{
+ usb_pause_mtx(mtx_owned(&sc->sc_mtx) ? &sc->sc_mtx : NULL,
+ USB_MS_TO_TICKS(ms));
+}
+
+static void
+mtw_update_chw(struct ieee80211com *ic)
+{
+
+ printf("%s: TODO\n", __func__);
+}
+
+static int
+mtw_ampdu_enable(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap)
+{
+
+ /* For now, no A-MPDU TX support in the driver */
+ return (0);
+}
+
+static device_method_t mtw_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, mtw_match),
+ DEVMETHOD(device_attach, mtw_attach),
+ DEVMETHOD(device_detach, mtw_detach), DEVMETHOD_END
+};
+
+static driver_t mtw_driver = { .name = "mtw",
+ .methods = mtw_methods,
+ .size = sizeof(struct mtw_softc) };
+
+DRIVER_MODULE(mtw, uhub, mtw_driver, mtw_driver_loaded, NULL);
+MODULE_DEPEND(mtw, wlan, 1, 1, 1);
+MODULE_DEPEND(mtw, usb, 1, 1, 1);
+MODULE_DEPEND(mtw, firmware, 1, 1, 1);
+MODULE_VERSION(mtw, 1);
+USB_PNP_HOST_INFO(mtw_devs);
diff --git a/sys/dev/usb/wlan/if_mtwreg.h b/sys/dev/usb/wlan/if_mtwreg.h
new file mode 100644
index 000000000000..05af4f4f6cf3
--- /dev/null
+++ b/sys/dev/usb/wlan/if_mtwreg.h
@@ -0,0 +1,1439 @@
+/* $OpenBSD: mtwreg.h,v 1.2 2022/07/27 06:41:04 hastings Exp $ */
+/*
+ * Copyright (c) 2007 Damien Bergamini <damien.bergamini@free.fr>
+ * Copyright (c) 2021 James Hastings
+ *
+ * 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 MTW_ASIC_VER 0x0000
+#define MTW_CMB_CTRL 0x0020
+#define MTW_EFUSE_CTRL 0x0024
+#define MTW_EFUSE_DATA0 0x0028
+#define MTW_EFUSE_DATA1 0x002c
+#define MTW_EFUSE_DATA2 0x0030
+#define MTW_EFUSE_DATA3 0x0034
+#define MTW_OSC_CTRL 0x0038
+#define MTW_COEX_CFG0 0x0040
+#define MTW_PLL_CTRL 0x0050
+#define MTW_LDO_CFG0 0x006c
+#define MTW_LDO_CFG1 0x0070
+#define MTW_WLAN_CTRL 0x0080
+
+/* SCH/DMA registers */
+#define MTW_INT_STATUS 0x0200
+#define RT2860_INT_MASK 0x0204
+#define MTW_WPDMA_GLO_CFG 0x0208
+#define RT2860_WPDMA_RST_IDX 0x020c
+#define RT2860_DELAY_INT_CFG 0x0210
+#define MTW_WMM_AIFSN_CFG 0x0214
+#define MTW_WMM_CWMIN_CFG 0x0218
+#define MTW_WMM_CWMAX_CFG 0x021c
+#define MTW_WMM_TXOP0_CFG 0x0220
+#define MTW_WMM_TXOP1_CFG 0x0224
+#define RT2860_GPIO_CTRL 0x0228
+#define RT2860_MCU_CMD_REG 0x022c
+#define MTW_MCU_DMA_ADDR 0x0230
+#define MTW_MCU_DMA_LEN 0x0234
+#define MTW_USB_DMA_CFG 0x0238
+#define RT2860_TX_BASE_PTR(qid) (0x0230 + (qid) * 16)
+#define RT2860_TX_MAX_CNT(qid) (0x0234 + (qid) * 16)
+#define RT2860_TX_CTX_IDX(qid) (0x0238 + (qid) * 16)
+#define RT2860_TX_DTX_IDX(qid) (0x023c + (qid) * 16)
+#define MTW_TSO_CTRL 0x0250
+#define MTW_HDR_TRANS_CTRL 0x0260
+#define RT2860_RX_BASE_PTR 0x0290
+#define RT2860_RX_MAX_CNT 0x0294
+#define RT2860_RX_CALC_IDX 0x0298
+#define RT2860_FS_DRX_IDX 0x029c
+#define MTW_US_CYC_CNT 0x02a4
+
+#define MTW_TX_RING_BASE 0x0300
+#define MTW_RX_RING_BASE 0x03c0
+
+/* Packet Buffer registers */
+#define MTW_SYS_CTRL 0x0400
+#define MTW_PBF_CFG 0x0404
+#define MTW_TX_MAX_PCNT 0x0408
+#define MTW_RX_MAX_PCNT 0x040c
+#define MTW_PBF_CTRL 0x0410
+#define RT2860_BUF_CTRL 0x0410
+#define RT2860_MCU_INT_STA 0x0414
+#define RT2860_MCU_INT_ENA 0x0418
+#define RT2860_TXQ_IO(qid) (0x041c + (qid) * 4)
+#define MTW_BCN_OFFSET0 0x041c
+#define MTW_BCN_OFFSET1 0x0420
+#define MTW_BCN_OFFSET2 0x0424
+#define MTW_BCN_OFFSET3 0x0428
+#define RT2860_RX0Q_IO 0x0424
+#define MTW_RXQ_STA 0x0430
+#define MTW_TXQ_STA 0x0434
+#define MTW_TXRXQ_PCNT 0x0438
+
+/* RF registers */
+#define MTW_RF_CSR 0x0500
+#define MTW_RF_BYPASS0 0x0504
+#define MTW_RF_BYPASS1 0x0508
+#define MTW_RF_SETTING0 0x050C
+#define MTW_RF_MISC 0x0518
+#define MTW_RF_DATA_WR 0x0524
+#define MTW_RF_CTRL 0x0528
+#define MTW_RF_DATA_RD 0x052c
+
+/* MCU registers */
+#define MTW_MCU_RESET_CTL 0x070c
+#define MTW_MCU_INT_LEVEL 0x0718
+#define MTW_MCU_COM_REG0 0x0730
+#define MTW_MCU_COM_REG1 0x0734
+#define MTW_MCU_COM_REG2 0x0738
+#define MTW_MCU_COM_REG3 0x073c
+#define MTW_FCE_PSE_CTRL 0x0800
+#define MTW_FCE_PARAMETERS 0x0804
+#define MTW_FCE_CSO 0x0808
+#define MTW_FCE_L2_STUFF 0x080c
+#define MTW_FCE_WLAN_FLOW_CTRL 0x0824
+#define MTW_TX_CPU_FCE_BASE 0x09a0
+#define MTW_TX_CPU_FCE_MAX_COUNT 0x09a4
+#define MTW_MCU_FW_IDX 0x09a8
+#define MTW_FCE_PDMA 0x09c4
+#define MTW_FCE_SKIP_FS 0x0a6c
+
+/* MAC registers */
+#define MTW_MAC_VER_ID 0x1000
+#define MTW_MAC_SYS_CTRL 0x1004
+#define MTW_MAC_ADDR_DW0 0x1008
+#define MTW_MAC_ADDR_DW1 0x100c
+#define MTW_MAC_BSSID_DW0 0x1010
+#define MTW_MAC_BSSID_DW1 0x1014
+#define MTW_MAX_LEN_CFG 0x1018
+#define MTW_BBP_CSR 0x101c
+#define MTW_LED_CFG 0x102c
+#define MTW_AMPDU_MAX_LEN_20M1S 0x1030
+#define MTW_AMPDU_MAX_LEN_20M2S 0x1034
+#define MTW_AMPDU_MAX_LEN_40M1S 0x1038
+#define MTW_AMPDU_MAX_LEN_40M2S 0x103c
+#define MTW_AMPDU_MAX_LEN 0x1040
+
+/* MAC Timing control registers */
+#define MTW_XIFS_TIME_CFG 0x1100
+#define MTW_BKOFF_SLOT_CFG 0x1104
+#define RT2860_NAV_TIME_CFG 0x1108
+#define RT2860_CH_TIME_CFG 0x110c
+#define RT2860_PBF_LIFE_TIMER 0x1110
+#define MTW_BCN_TIME_CFG 0x1114
+#define MTW_TBTT_SYNC_CFG 0x1118
+#define MTW_TSF_TIMER_DW0 0x111c
+#define MTW_TSF_TIMER_DW1 0x1120
+#define RT2860_TBTT_TIMER 0x1124
+#define MTW_INT_TIMER_CFG 0x1128
+#define RT2860_INT_TIMER_EN 0x112c
+#define RT2860_CH_IDLE_TIME 0x1130
+
+/* MAC Power Save configuration registers */
+#define MTW_MAC_STATUS_REG 0x1200
+#define MTW_PWR_PIN_CFG 0x1204
+#define MTW_AUTO_WAKEUP_CFG 0x1208
+#define MTW_AUX_CLK_CFG 0x120c
+#define MTW_BBP_PA_MODE_CFG0 0x1214
+#define MTW_BBP_PA_MODE_CFG1 0x1218
+#define MTW_RF_PA_MODE_CFG0 0x121c
+#define MTW_RF_PA_MODE_CFG1 0x1220
+#define MTW_RF_PA_MODE_ADJ0 0x1228
+#define MTW_RF_PA_MODE_ADJ1 0x122c
+#define MTW_DACCLK_EN_DLY_CFG 0x1264 /* MT7612 */
+
+/* MAC TX configuration registers */
+#define MTW_EDCA_AC_CFG(aci) (0x1300 + (aci) * 4)
+#define MTW_EDCA_TID_AC_MAP 0x1310
+#define MTW_TX_PWR_CFG(ridx) (0x1314 + (ridx) * 4)
+#define MTW_TX_PIN_CFG 0x1328
+#define MTW_TX_BAND_CFG 0x132c
+#define MTW_TX_SW_CFG0 0x1330
+#define MTW_TX_SW_CFG1 0x1334
+#define MTW_TX_SW_CFG2 0x1338
+#define RT2860_TXOP_THRES_CFG 0x133c
+#define MTW_TXOP_CTRL_CFG 0x1340
+#define MTW_TX_RTS_CFG 0x1344
+#define MTW_TX_TIMEOUT_CFG 0x1348
+#define MTW_TX_RETRY_CFG 0x134c
+#define MTW_TX_LINK_CFG 0x1350
+#define MTW_HT_FBK_CFG0 0x1354
+#define MTW_HT_FBK_CFG1 0x1358
+#define MTW_LG_FBK_CFG0 0x135c
+#define MTW_LG_FBK_CFG1 0x1360
+#define MTW_CCK_PROT_CFG 0x1364
+#define MTW_OFDM_PROT_CFG 0x1368
+#define MTW_MM20_PROT_CFG 0x136c
+#define MTW_MM40_PROT_CFG 0x1370
+#define MTW_GF20_PROT_CFG 0x1374
+#define MTW_GF40_PROT_CFG 0x1378
+#define RT2860_EXP_CTS_TIME 0x137c
+#define MTW_EXP_ACK_TIME 0x1380
+#define MTW_TX_PWR_CFG5 0x1384
+#define MTW_TX_PWR_CFG6 0x1388
+#define MTW_TX_PWR_EXT_CFG(ridx) (0x1390 + (ridx) * 4)
+#define MTW_TX0_RF_GAIN_CORR 0x13a0
+#define MTW_TX1_RF_GAIN_CORR 0x13a4
+#define MTW_TX0_RF_GAIN_ATTEN 0x13a8
+#define MTW_TX_ALC_CFG3 0x13ac
+#define MTW_TX_ALC_CFG0 0x13b0
+#define MTW_TX_ALC_CFG1 0x13b4
+#define MTW_TX_ALC_CFG4 0x13c0
+#define MTW_TX_ALC_VGA3 0x13c8
+#define MTW_TX_PWR_CFG7 0x13d4
+#define MTW_TX_PWR_CFG8 0x13d8
+#define MTW_TX_PWR_CFG9 0x13dc
+#define MTW_VHT20_PROT_CFG 0x13e0
+#define MTW_VHT40_PROT_CFG 0x13e4
+#define MTW_VHT80_PROT_CFG 0x13e8
+#define MTW_TX_PIFS_CFG 0x13ec /* MT761X */
+
+/* MAC RX configuration registers */
+#define MTW_RX_FILTR_CFG 0x1400
+#define MTW_AUTO_RSP_CFG 0x1404
+#define MTW_LEGACY_BASIC_RATE 0x1408
+#define MTW_HT_BASIC_RATE 0x140c
+#define MTW_HT_CTRL_CFG 0x1410
+#define RT2860_SIFS_COST_CFG 0x1414
+#define RT2860_RX_PARSER_CFG 0x1418
+
+/* MAC Security configuration registers */
+#define RT2860_TX_SEC_CNT0 0x1500
+#define RT2860_RX_SEC_CNT0 0x1504
+#define RT2860_CCMP_FC_MUTE 0x1508
+#define MTW_PN_PAD_MODE 0x150c /* MT761X */
+
+/* MAC HCCA/PSMP configuration registers */
+#define MTW_TXOP_HLDR_ADDR0 0x1600
+#define MTW_TXOP_HLDR_ADDR1 0x1604
+#define MTW_TXOP_HLDR_ET 0x1608
+#define RT2860_QOS_CFPOLL_RA_DW0 0x160c
+#define RT2860_QOS_CFPOLL_A1_DW1 0x1610
+#define RT2860_QOS_CFPOLL_QC 0x1614
+#define MTW_PROT_AUTO_TX_CFG 0x1648
+
+/* MAC Statistics Counters */
+#define MTW_RX_STA_CNT0 0x1700
+#define MTW_RX_STA_CNT1 0x1704
+#define MTW_RX_STA_CNT2 0x1708
+#define MTW_TX_STA_CNT0 0x170c
+#define MTW_TX_STA_CNT1 0x1710
+#define MTW_TX_STA_CNT2 0x1714
+#define MTW_TX_STAT_FIFO 0x1718
+
+/* RX WCID search table */
+#define MTW_WCID_ENTRY(wcid) (0x1800 + (wcid) * 8)
+
+/* MT761x Baseband */
+#define MTW_BBP_CORE(x) (0x2000 + (x) * 4)
+#define MTW_BBP_IBI(x) (0x2100 + (x) * 4)
+#define MTW_BBP_AGC(x) (0x2300 + (x) * 4)
+#define MTW_BBP_TXC(x) (0x2400 + (x) * 4)
+#define MTW_BBP_RXC(x) (0x2500 + (x) * 4)
+#define MTW_BBP_TXQ(x) (0x2600 + (x) * 4)
+#define MTW_BBP_TXBE(x) (0x2700 + (x) * 4)
+#define MTW_BBP_RXFE(x) (0x2800 + (x) * 4)
+#define MTW_BBP_RXO(x) (0x2900 + (x) * 4)
+#define MTW_BBP_DFS(x) (0x2a00 + (x) * 4)
+#define MTW_BBP_TR(x) (0x2b00 + (x) * 4)
+#define MTW_BBP_CAL(x) (0x2c00 + (x) * 4)
+#define MTW_BBP_DSC(x) (0x2e00 + (x) * 4)
+#define MTW_BBP_PFMU(x) (0x2f00 + (x) * 4)
+
+#define MTW_SKEY_MODE_16_23 0x7008
+#define MTW_SKEY_MODE_24_31 0x700c
+#define MTW_H2M_MAILBOX 0x7010
+
+/* Pair-wise key table */
+#define MTW_PKEY(wcid) (0x8000 + (wcid) * 32)
+
+/* USB 3.0 DMA */
+#define MTW_USB_U3DMA_CFG 0x9018
+
+/* IV/EIV table */
+#define MTW_IVEIV(wcid) (0xa000 + (wcid) * 8)
+
+/* WCID attribute table */
+#define MTW_WCID_ATTR(wcid) (0xa800 + (wcid) * 4)
+
+/* Shared Key Table */
+#define MTW_SKEY(vap, kidx) ((vap & 8) ? MTW_SKEY_1(vap, kidx) : \
+ MTW_SKEY_0(vap, kidx))
+#define MTW_SKEY_0(vap, kidx) (0xac00 + (4 * (vap) + (kidx)) * 32)
+#define MTW_SKEY_1(vap, kidx) (0xb400 + (4 * ((vap) & 7) + (kidx)) * 32)
+
+/* Shared Key Mode */
+#define MTW_SKEY_MODE_0_7 0xb000
+#define MTW_SKEY_MODE_8_15 0xb004
+
+/* Shared Key Mode */
+#define MTW_SKEY_MODE_BASE 0xb000
+
+/* Beacon */
+#define MTW_BCN_BASE 0xc000
+
+/* possible flags for register CMB_CTRL 0x0020 */
+#define MTW_PLL_LD (1U << 23)
+#define MTW_XTAL_RDY (1U << 22)
+
+/* possible flags for register EFUSE_CTRL 0x0024 */
+#define MTW_SEL_EFUSE (1U << 31)
+#define MTW_EFSROM_KICK (1U << 30)
+#define MTW_EFSROM_AIN_MASK 0x03ff0000
+#define MTW_EFSROM_AIN_SHIFT 16
+#define MTW_EFSROM_MODE_MASK 0x000000c0
+#define MTW_EFUSE_AOUT_MASK 0x0000003f
+
+/* possible flags for register OSC_CTRL 0x0038 */
+#define MTW_OSC_EN (1U << 31)
+#define MTW_OSC_CAL_REQ (1U << 30)
+#define MTW_OSC_CLK_32K_VLD (1U << 29)
+#define MTW_OSC_CAL_ACK (1U << 28)
+#define MTW_OSC_CAL_CNT (0xfff << 16)
+#define MTW_OSC_REF_CYCLE 0x1fff
+
+/* possible flags for register WLAN_CTRL 0x0080 */
+#define MTW_GPIO_OUT_OE_ALL (0xff << 24)
+#define MTW_GPIO_OUT_ALL (0xff << 16)
+#define MTW_GPIO_IN_ALL (0xff << 8)
+#define MTW_THERM_CKEN (1U << 9)
+#define MTW_THERM_RST (1U << 8)
+#define MTW_INV_TR_SW0 (1U << 6)
+#define MTW_FRC_WL_ANT_SET (1U << 5)
+#define MTW_PCIE_APP0_CLK_REQ (1U << 4)
+#define MTW_WLAN_RESET (1U << 3)
+#define MTW_WLAN_RESET_RF (1U << 2)
+#define MTW_WLAN_CLK_EN (1U << 1)
+#define MTW_WLAN_EN (1U << 0)
+
+/* possible flags for registers INT_STATUS/INT_MASK 0x0200 */
+#define RT2860_TX_COHERENT (1 << 17)
+#define RT2860_RX_COHERENT (1 << 16)
+#define RT2860_MAC_INT_4 (1 << 15)
+#define RT2860_MAC_INT_3 (1 << 14)
+#define RT2860_MAC_INT_2 (1 << 13)
+#define RT2860_MAC_INT_1 (1 << 12)
+#define RT2860_MAC_INT_0 (1 << 11)
+#define RT2860_TX_RX_COHERENT (1 << 10)
+#define RT2860_MCU_CMD_INT (1 << 9)
+#define RT2860_TX_DONE_INT5 (1 << 8)
+#define RT2860_TX_DONE_INT4 (1 << 7)
+#define RT2860_TX_DONE_INT3 (1 << 6)
+#define RT2860_TX_DONE_INT2 (1 << 5)
+#define RT2860_TX_DONE_INT1 (1 << 4)
+#define RT2860_TX_DONE_INT0 (1 << 3)
+#define RT2860_RX_DONE_INT (1 << 2)
+#define RT2860_TX_DLY_INT (1 << 1)
+#define RT2860_RX_DLY_INT (1 << 0)
+
+/* possible flags for register WPDMA_GLO_CFG 0x0208 */
+#define MTW_HDR_SEG_LEN_SHIFT 8
+#define MTW_BIG_ENDIAN (1 << 7)
+#define MTW_TX_WB_DDONE (1 << 6)
+#define MTW_WPDMA_BT_SIZE_SHIFT 4
+#define MTW_WPDMA_BT_SIZE16 0
+#define MTW_WPDMA_BT_SIZE32 1
+#define MTW_WPDMA_BT_SIZE64 2
+#define MTW_WPDMA_BT_SIZE128 3
+#define MTW_RX_DMA_BUSY (1 << 3)
+#define MTW_RX_DMA_EN (1 << 2)
+#define MTW_TX_DMA_BUSY (1 << 1)
+#define MTW_TX_DMA_EN (1 << 0)
+
+/* possible flags for register DELAY_INT_CFG */
+#define RT2860_TXDLY_INT_EN (1U << 31)
+#define RT2860_TXMAX_PINT_SHIFT 24
+#define RT2860_TXMAX_PTIME_SHIFT 16
+#define RT2860_RXDLY_INT_EN (1U << 15)
+#define RT2860_RXMAX_PINT_SHIFT 8
+#define RT2860_RXMAX_PTIME_SHIFT 0
+
+/* possible flags for register GPIO_CTRL */
+#define RT2860_GPIO_D_SHIFT 8
+#define RT2860_GPIO_O_SHIFT 0
+
+/* possible flags for register MCU_DMA_ADDR 0x0230 */
+#define MTW_MCU_READY (1U << 0)
+
+/* possible flags for register USB_DMA_CFG 0x0238 */
+#define MTW_USB_TX_BUSY (1U << 31)
+#define MTW_USB_RX_BUSY (1U << 30)
+#define MTW_USB_EPOUT_VLD_SHIFT 24
+#define MTW_USB_RX_WL_DROP (1U << 25)
+#define MTW_USB_TX_EN (1U << 23)
+#define MTW_USB_RX_EN (1U << 22)
+#define MTW_USB_RX_AGG_EN (1U << 21)
+#define MTW_USB_TXOP_HALT (1U << 20)
+#define MTW_USB_TX_CLEAR (1U << 19)
+#define MTW_USB_PHY_WD_EN (1U << 16)
+#define MTW_USB_PHY_MAN_RST (1U << 15)
+#define MTW_USB_RX_AGG_LMT(x) ((x) << 8) /* in unit of 1KB */
+#define MTW_USB_RX_AGG_TO(x) ((x) & 0xff) /* in unit of 33ns */
+
+/* possible flags for register US_CYC_CNT 0x02a4 */
+#define RT2860_TEST_EN (1 << 24)
+#define RT2860_TEST_SEL_SHIFT 16
+#define RT2860_BT_MODE_EN (1 << 8)
+#define RT2860_US_CYC_CNT_SHIFT 0
+
+/* possible flags for register PBF_CFG 0x0404 */
+#define MTW_PBF_CFG_RX_DROP (1 << 8)
+#define MTW_PBF_CFG_RX0Q_EN (1 << 4)
+#define MTW_PBF_CFG_TX3Q_EN (1 << 3)
+#define MTW_PBF_CFG_TX2Q_EN (1 << 2)
+#define MTW_PBF_CFG_TX1Q_EN (1 << 1)
+#define MTW_PBF_CFG_TX0Q_EN (1 << 0)
+
+/* possible flags for register BUF_CTRL 0x0410 */
+#define RT2860_WRITE_TXQ(qid) (1 << (11 - (qid)))
+#define RT2860_NULL0_KICK (1 << 7)
+#define RT2860_NULL1_KICK (1 << 6)
+#define RT2860_BUF_RESET (1 << 5)
+#define RT2860_READ_TXQ(qid) (1 << (3 - (qid))
+#define RT2860_READ_RX0Q (1 << 0)
+
+/* possible flags for registers MCU_INT_STA/MCU_INT_ENA */
+#define RT2860_MCU_MAC_INT_8 (1 << 24)
+#define RT2860_MCU_MAC_INT_7 (1 << 23)
+#define RT2860_MCU_MAC_INT_6 (1 << 22)
+#define RT2860_MCU_MAC_INT_4 (1 << 20)
+#define RT2860_MCU_MAC_INT_3 (1 << 19)
+#define RT2860_MCU_MAC_INT_2 (1 << 18)
+#define RT2860_MCU_MAC_INT_1 (1 << 17)
+#define RT2860_MCU_MAC_INT_0 (1 << 16)
+#define RT2860_DTX0_INT (1 << 11)
+#define RT2860_DTX1_INT (1 << 10)
+#define RT2860_DTX2_INT (1 << 9)
+#define RT2860_DRX0_INT (1 << 8)
+#define RT2860_HCMD_INT (1 << 7)
+#define RT2860_N0TX_INT (1 << 6)
+#define RT2860_N1TX_INT (1 << 5)
+#define RT2860_BCNTX_INT (1 << 4)
+#define RT2860_MTX0_INT (1 << 3)
+#define RT2860_MTX1_INT (1 << 2)
+#define RT2860_MTX2_INT (1 << 1)
+#define RT2860_MRX0_INT (1 << 0)
+
+/* possible flags for register TXRXQ_PCNT 0x0438 */
+#define MTW_RX0Q_PCNT_MASK 0xff000000
+#define MTW_TX2Q_PCNT_MASK 0x00ff0000
+#define MTW_TX1Q_PCNT_MASK 0x0000ff00
+#define MTW_TX0Q_PCNT_MASK 0x000000ff
+
+/* possible flags for register RF_CSR_CFG 0x0500 */
+#define MTW_RF_CSR_KICK (1U << 31)
+#define MTW_RF_CSR_WRITE (1U << 30)
+#define MT7610_BANK_SHIFT 15
+#define MT7601_BANK_SHIFT 14
+
+/* possible flags for register FCE_L2_STUFF 0x080c */
+#define MTW_L2S_WR_MPDU_LEN_EN (1 << 4)
+
+/* possible flag for register DEBUG_INDEX */
+#define RT5592_SEL_XTAL (1U << 31)
+
+/* possible flags for register MAC_SYS_CTRL 0x1004 */
+#define MTW_RX_TS_EN (1 << 7)
+#define MTW_WLAN_HALT_EN (1 << 6)
+#define MTW_PBF_LOOP_EN (1 << 5)
+#define MTW_CONT_TX_TEST (1 << 4)
+#define MTW_MAC_RX_EN (1 << 3)
+#define MTW_MAC_TX_EN (1 << 2)
+#define MTW_BBP_HRST (1 << 1)
+#define MTW_MAC_SRST (1 << 0)
+
+/* possible flags for register MAC_BSSID_DW1 0x100c */
+#define RT2860_MULTI_BCN_NUM_SHIFT 18
+#define RT2860_MULTI_BSSID_MODE_SHIFT 16
+
+/* possible flags for register MAX_LEN_CFG 0x1018 */
+#define RT2860_MIN_MPDU_LEN_SHIFT 16
+#define RT2860_MAX_PSDU_LEN_SHIFT 12
+#define RT2860_MAX_PSDU_LEN8K 0
+#define RT2860_MAX_PSDU_LEN16K 1
+#define RT2860_MAX_PSDU_LEN32K 2
+#define RT2860_MAX_PSDU_LEN64K 3
+#define RT2860_MAX_MPDU_LEN_SHIFT 0
+
+/* possible flags for registers BBP_CSR_CFG 0x101c */
+#define MTW_BBP_CSR_KICK (1 << 17)
+#define MTW_BBP_CSR_READ (1 << 16)
+#define MTW_BBP_ADDR_SHIFT 8
+#define MTW_BBP_DATA_SHIFT 0
+
+/* possible flags for register LED_CFG */
+#define MTW_LED_MODE_ON 0
+#define MTW_LED_MODE_DIM 1
+#define MTW_LED_MODE_BLINK_TX 2
+#define MTW_LED_MODE_SLOW_BLINK 3
+
+/* possible flags for register XIFS_TIME_CFG 0x1100 */
+#define MTW_BB_RXEND_EN (1 << 29)
+#define MTW_EIFS_TIME_SHIFT 20
+#define MTW_OFDM_XIFS_TIME_SHIFT 16
+#define MTW_OFDM_SIFS_TIME_SHIFT 8
+#define MTW_CCK_SIFS_TIME_SHIFT 0
+
+/* possible flags for register BKOFF_SLOT_CFG 0x1104 */
+#define MTW_CC_DELAY_TIME_SHIFT 8
+#define MTW_SLOT_TIME 0
+
+/* possible flags for register NAV_TIME_CFG */
+#define RT2860_NAV_UPD (1U << 31)
+#define RT2860_NAV_UPD_VAL_SHIFT 16
+#define RT2860_NAV_CLR_EN (1U << 15)
+#define RT2860_NAV_TIMER_SHIFT 0
+
+/* possible flags for register CH_TIME_CFG */
+#define RT2860_EIFS_AS_CH_BUSY (1 << 4)
+#define RT2860_NAV_AS_CH_BUSY (1 << 3)
+#define RT2860_RX_AS_CH_BUSY (1 << 2)
+#define RT2860_TX_AS_CH_BUSY (1 << 1)
+#define RT2860_CH_STA_TIMER_EN (1 << 0)
+
+/* possible values for register BCN_TIME_CFG 0x1114 */
+#define MTW_TSF_INS_COMP_SHIFT 24
+#define MTW_BCN_TX_EN (1 << 20)
+#define MTW_TBTT_TIMER_EN (1 << 19)
+#define MTW_TSF_SYNC_MODE_SHIFT 17
+#define MTW_TSF_SYNC_MODE_DIS 0
+#define MTW_TSF_SYNC_MODE_STA 1
+#define MTW_TSF_SYNC_MODE_IBSS 2
+#define MTW_TSF_SYNC_MODE_HOSTAP 3
+#define MTW_TSF_TIMER_EN (1 << 16)
+#define MTW_BCN_INTVAL_SHIFT 0
+
+/* possible flags for register TBTT_SYNC_CFG 0x1118 */
+#define RT2860_BCN_CWMIN_SHIFT 20
+#define RT2860_BCN_AIFSN_SHIFT 16
+#define RT2860_BCN_EXP_WIN_SHIFT 8
+#define RT2860_TBTT_ADJUST_SHIFT 0
+
+/* possible flags for register INT_TIMER_CFG 0x1128 */
+#define RT2860_GP_TIMER_SHIFT 16
+#define RT2860_PRE_TBTT_TIMER_SHIFT 0
+
+/* possible flags for register INT_TIMER_EN */
+#define RT2860_GP_TIMER_EN (1 << 1)
+#define RT2860_PRE_TBTT_INT_EN (1 << 0)
+
+/* possible flags for register MAC_STATUS_REG 0x1200 */
+#define MTW_RX_STATUS_BUSY (1 << 1)
+#define MTW_TX_STATUS_BUSY (1 << 0)
+
+/* possible flags for register PWR_PIN_CFG 0x1204 */
+#define RT2860_IO_ADDA_PD (1 << 3)
+#define RT2860_IO_PLL_PD (1 << 2)
+#define RT2860_IO_RA_PE (1 << 1)
+#define RT2860_IO_RF_PE (1 << 0)
+
+/* possible flags for register AUTO_WAKEUP_CFG 0x1208 */
+#define MTW_AUTO_WAKEUP_EN (1 << 15)
+#define MTW_SLEEP_TBTT_NUM_SHIFT 8
+#define MTW_WAKEUP_LEAD_TIME_SHIFT 0
+
+/* possible flags for register TX_PIN_CFG 0x1328 */
+#define RT2860_TRSW_POL (1U << 19)
+#define RT2860_TRSW_EN (1U << 18)
+#define RT2860_RFTR_POL (1U << 17)
+#define RT2860_RFTR_EN (1U << 16)
+#define RT2860_LNA_PE_G1_POL (1U << 15)
+#define RT2860_LNA_PE_A1_POL (1U << 14)
+#define RT2860_LNA_PE_G0_POL (1U << 13)
+#define RT2860_LNA_PE_A0_POL (1U << 12)
+#define RT2860_LNA_PE_G1_EN (1U << 11)
+#define RT2860_LNA_PE_A1_EN (1U << 10)
+#define RT2860_LNA_PE1_EN (RT2860_LNA_PE_A1_EN | RT2860_LNA_PE_G1_EN)
+#define RT2860_LNA_PE_G0_EN (1U << 9)
+#define RT2860_LNA_PE_A0_EN (1U << 8)
+#define RT2860_LNA_PE0_EN (RT2860_LNA_PE_A0_EN | RT2860_LNA_PE_G0_EN)
+#define RT2860_PA_PE_G1_POL (1U << 7)
+#define RT2860_PA_PE_A1_POL (1U << 6)
+#define RT2860_PA_PE_G0_POL (1U << 5)
+#define RT2860_PA_PE_A0_POL (1U << 4)
+#define RT2860_PA_PE_G1_EN (1U << 3)
+#define RT2860_PA_PE_A1_EN (1U << 2)
+#define RT2860_PA_PE_G0_EN (1U << 1)
+#define RT2860_PA_PE_A0_EN (1U << 0)
+
+/* possible flags for register TX_BAND_CFG 0x132c */
+#define MTW_TX_BAND_SEL_2G (1 << 2)
+#define MTW_TX_BAND_SEL_5G (1 << 1)
+#define MTW_TX_BAND_UPPER_40M (1 << 0)
+
+/* possible flags for register TX_SW_CFG0 0x1330 */
+#define RT2860_DLY_RFTR_EN_SHIFT 24
+#define RT2860_DLY_TRSW_EN_SHIFT 16
+#define RT2860_DLY_PAPE_EN_SHIFT 8
+#define RT2860_DLY_TXPE_EN_SHIFT 0
+
+/* possible flags for register TX_SW_CFG1 0x1334 */
+#define RT2860_DLY_RFTR_DIS_SHIFT 16
+#define RT2860_DLY_TRSW_DIS_SHIFT 8
+#define RT2860_DLY_PAPE_DIS SHIFT 0
+
+/* possible flags for register TX_SW_CFG2 0x1338 */
+#define RT2860_DLY_LNA_EN_SHIFT 24
+#define RT2860_DLY_LNA_DIS_SHIFT 16
+#define RT2860_DLY_DAC_EN_SHIFT 8
+#define RT2860_DLY_DAC_DIS_SHIFT 0
+
+/* possible flags for register TXOP_THRES_CFG 0x133c */
+#define RT2860_TXOP_REM_THRES_SHIFT 24
+#define RT2860_CF_END_THRES_SHIFT 16
+#define RT2860_RDG_IN_THRES 8
+#define RT2860_RDG_OUT_THRES 0
+
+/* possible flags for register TXOP_CTRL_CFG 0x1340 */
+#define MTW_TXOP_ED_CCA_EN (1 << 20)
+#define MTW_EXT_CW_MIN_SHIFT 16
+#define MTW_EXT_CCA_DLY_SHIFT 8
+#define MTW_EXT_CCA_EN (1 << 7)
+#define MTW_LSIG_TXOP_EN (1 << 6)
+#define MTW_TXOP_TRUN_EN_MIMOPS (1 << 4)
+#define MTW_TXOP_TRUN_EN_TXOP (1 << 3)
+#define MTW_TXOP_TRUN_EN_RATE (1 << 2)
+#define MTW_TXOP_TRUN_EN_AC (1 << 1)
+#define MTW_TXOP_TRUN_EN_TIMEOUT (1 << 0)
+
+/* possible flags for register TX_RTS_CFG 0x1344 */
+#define MTW_RTS_FBK_EN (1 << 24)
+#define MTW_RTS_THRES_SHIFT 8
+#define MTW_RTS_RTY_LIMIT_SHIFT 0
+
+/* possible flags for register TX_TIMEOUT_CFG 0x1348 */
+#define MTW_TXOP_TIMEOUT_SHIFT 16
+#define MTW_RX_ACK_TIMEOUT_SHIFT 8
+#define MTW_MPDU_LIFE_TIME_SHIFT 4
+
+/* possible flags for register TX_RETRY_CFG 0x134c */
+#define MTW_TX_AUTOFB_EN (1 << 30)
+#define MTW_AGG_RTY_MODE_TIMER (1 << 29)
+#define MTW_NAG_RTY_MODE_TIMER (1 << 28)
+#define MTW_LONG_RTY_THRES_SHIFT 16
+#define MTW_LONG_RTY_LIMIT_SHIFT 8
+#define MTW_SHORT_RTY_LIMIT_SHIFT 0
+
+/* possible flags for register TX_LINK_CFG 0x1350 */
+#define MTW_REMOTE_MFS_SHIFT 24
+#define MTW_REMOTE_MFB_SHIFT 16
+#define MTW_TX_CFACK_EN (1 << 12)
+#define MTW_TX_RDG_EN (1 << 11)
+#define MTW_TX_MRQ_EN (1 << 10)
+#define MTW_REMOTE_UMFS_EN (1 << 9)
+#define MTW_TX_MFB_EN (1 << 8)
+#define MTW_REMOTE_MFB_LT_SHIFT 0
+
+/* possible flags for registers *_PROT_CFG */
+#define RT2860_RTSTH_EN (1 << 26)
+#define RT2860_TXOP_ALLOW_GF40 (1 << 25)
+#define RT2860_TXOP_ALLOW_GF20 (1 << 24)
+#define RT2860_TXOP_ALLOW_MM40 (1 << 23)
+#define RT2860_TXOP_ALLOW_MM20 (1 << 22)
+#define RT2860_TXOP_ALLOW_OFDM (1 << 21)
+#define RT2860_TXOP_ALLOW_CCK (1 << 20)
+#define RT2860_TXOP_ALLOW_ALL (0x3f << 20)
+#define RT2860_PROT_NAV_SHORT (1 << 18)
+#define RT2860_PROT_NAV_LONG (2 << 18)
+#define RT2860_PROT_CTRL_RTS_CTS (1 << 16)
+#define RT2860_PROT_CTRL_CTS (2 << 16)
+
+/* possible flags for registers EXP_{CTS,ACK}_TIME */
+#define RT2860_EXP_OFDM_TIME_SHIFT 16
+#define RT2860_EXP_CCK_TIME_SHIFT 0
+
+/* possible flags for register RX_FILTR_CFG 0x1400 */
+#define MTW_DROP_CTRL_RSV (1 << 16)
+#define MTW_DROP_BAR (1 << 15)
+#define MTW_DROP_BA (1 << 14)
+#define MTW_DROP_PSPOLL (1 << 13)
+#define MTW_DROP_RTS (1 << 12)
+#define MTW_DROP_CTS (1 << 11)
+#define MTW_DROP_ACK (1 << 10)
+#define MTW_DROP_CFEND (1 << 9)
+#define MTW_DROP_CFACK (1 << 8)
+#define MTW_DROP_DUPL (1 << 7)
+#define MTW_DROP_BC (1 << 6)
+#define MTW_DROP_MC (1 << 5)
+#define MTW_DROP_VER_ERR (1 << 4)
+#define MTW_DROP_NOT_MYBSS (1 << 3)
+#define MTW_DROP_UC_NOME (1 << 2)
+#define MTW_DROP_PHY_ERR (1 << 1)
+#define MTW_DROP_CRC_ERR (1 << 0)
+
+/* possible flags for register AUTO_RSP_CFG 0x1404 */
+#define MTW_CTRL_PWR_BIT (1 << 7)
+#define MTW_BAC_ACK_POLICY (1 << 6)
+#define MTW_CCK_SHORT_EN (1 << 4)
+#define MTW_CTS_40M_REF_EN (1 << 3)
+#define MTW_CTS_40M_MODE_EN (1 << 2)
+#define MTW_BAC_ACKPOLICY_EN (1 << 1)
+#define MTW_AUTO_RSP_EN (1 << 0)
+
+/* possible flags for register SIFS_COST_CFG */
+#define RT2860_OFDM_SIFS_COST_SHIFT 8
+#define RT2860_CCK_SIFS_COST_SHIFT 0
+
+/* possible flags for register TXOP_HLDR_ET 0x1608 */
+#define MTW_TXOP_ETM1_EN (1 << 25)
+#define MTW_TXOP_ETM0_EN (1 << 24)
+#define MTW_TXOP_ETM_THRES_SHIFT 16
+#define MTW_TXOP_ETO_EN (1 << 8)
+#define MTW_TXOP_ETO_THRES_SHIFT 1
+#define MTW_PER_RX_RST_EN (1 << 0)
+
+/* possible flags for register TX_STAT_FIFO 0x1718 */
+#define MTW_TXQ_MCS_SHIFT 16
+#define MTW_TXQ_WCID_SHIFT 8
+#define MTW_TXQ_ACKREQ (1 << 7)
+#define MTW_TXQ_AGG (1 << 6)
+#define MTW_TXQ_OK (1 << 5)
+#define MTW_TXQ_PID_SHIFT 1
+#define MTW_TXQ_VLD (1 << 0)
+
+/* possible flags for register TX_STAT_FIFO_EXT 0x1798 */
+#define MTW_TXQ_PKTID_SHIFT 8
+#define MTW_TXQ_RETRY_SHIFT 0
+
+/* possible flags for register WCID_ATTR 0xa800 */
+#define MTW_MODE_NOSEC 0
+#define MTW_MODE_WEP40 1
+#define MTW_MODE_WEP104 2
+#define MTW_MODE_TKIP 3
+#define MTW_MODE_AES_CCMP 4
+#define MTW_MODE_CKIP40 5
+#define MTW_MODE_CKIP104 6
+#define MTW_MODE_CKIP128 7
+#define MTW_RX_PKEY_EN (1 << 0)
+
+/* possible flags for MT7601 BBP register 47 */
+#define MT7601_R47_MASK 0x07
+#define MT7601_R47_TSSI (0 << 0)
+#define MT7601_R47_PKT (1 << 0)
+#define MT7601_R47_TXRATE (1 << 1)
+#define MT7601_R47_TEMP (1 << 2)
+
+#define MTW_RXQ_WLAN 0
+#define MTW_RXQ_MCU 1
+#define MTW_TXQ_MCU 5
+
+enum mtw_phy_mode {
+ MTW_PHY_CCK,
+ MTW_PHY_OFDM,
+ MTW_PHY_HT,
+ MTW_PHY_HT_GF,
+ MTW_PHY_VHT,
+};
+
+/* RT2860 TX descriptor */
+struct rt2860_txd {
+ uint32_t sdp0; /* Segment Data Pointer 0 */
+ uint16_t sdl1; /* Segment Data Length 1 */
+#define RT2860_TX_BURST (1 << 15)
+#define RT2860_TX_LS1 (1 << 14) /* SDP1 is the last segment */
+
+ uint16_t sdl0; /* Segment Data Length 0 */
+#define RT2860_TX_DDONE (1 << 15)
+#define RT2860_TX_LS0 (1 << 14) /* SDP0 is the last segment */
+
+ uint32_t sdp1; /* Segment Data Pointer 1 */
+ uint8_t reserved[3];
+ uint8_t flags;
+#define RT2860_TX_QSEL_SHIFT 1
+#define RT2860_TX_QSEL_MGMT (0 << 1)
+#define RT2860_TX_QSEL_HCCA (1 << 1)
+#define RT2860_TX_QSEL_EDCA (2 << 1)
+#define RT2860_TX_WIV (1 << 0)
+} __packed;
+
+/* TX descriptor */
+struct mtw_txd {
+ uint16_t len;
+ uint16_t flags;
+#define MTW_TXD_CMD (1 << 14)
+#define MTW_TXD_DATA (0 << 14)
+#define MTW_TXD_MCU (2 << 11)
+#define MTW_TXD_WLAN (0 << 11)
+#define MTW_TXD_QSEL_EDCA (2 << 9)
+#define MTW_TXD_QSEL_HCCA (1 << 9)
+#define MTW_TXD_QSEL_MGMT (0 << 9)
+#define MTW_TXD_WIV (1 << 8)
+#define MTW_TXD_CMD_SHIFT 4
+#define MTW_TXD_80211 (1 << 3)
+} __packed;
+struct mtw_txd_fw {
+ uint16_t len;
+ uint16_t flags;
+uint8_t fw[0x2c44];
+} __packed;
+/* TX Wireless Information */
+struct mtw_txwi {
+ uint8_t flags;
+#define MTW_TX_MPDU_DSITY_SHIFT 5
+#define MTW_TX_AMPDU (1 << 4)
+#define MTW_TX_TS (1 << 3)
+#define MTW_TX_CFACK (1 << 2)
+#define MTW_TX_MMPS (1 << 1)
+#define MTW_TX_FRAG (1 << 0)
+
+ uint8_t txop;
+#define MTW_TX_TXOP_HT 0
+#define MTW_TX_TXOP_PIFS 1
+#define MTW_TX_TXOP_SIFS 2
+#define MTW_TX_TXOP_BACKOFF 3
+
+ uint16_t phy;
+#define MT7650_PHY_MODE 0xe000
+#define MT7601_PHY_MODE 0xc000
+#define MT7601_PHY_SHIFT 14
+#define MT7650_PHY_SHIFT 13
+#define MT7650_PHY_SGI (1 << 9)
+#define MT7601_PHY_SGI (1 << 8)
+#define MTW_PHY_BW20 (0 << 7)
+#define MTW_PHY_BW40 (1 << 7)
+#define MTW_PHY_BW80 (2 << 7)
+#define MTW_PHY_BW160 (3 << 7)
+#define MTW_PHY_LDPC (1 << 6)
+#define MTW_PHY_MCS 0x3f
+#define MTW_PHY_SHPRE (1 << 3)
+
+ uint8_t xflags;
+#define MTW_TX_BAWINSIZE_SHIFT 2
+#define MTW_TX_NSEQ (1 << 1)
+#define MTW_TX_ACK (1 << 0)
+
+ uint8_t wcid; /* Wireless Client ID */
+ uint16_t len;
+#define MTW_TX_PID_SHIFT 12
+
+ uint32_t iv;
+ uint32_t eiv;
+ uint32_t reserved1;
+} __packed;
+
+/* RT2860 RX descriptor */
+struct rt2860_rxd {
+ uint32_t sdp0;
+ uint16_t sdl1; /* unused */
+ uint16_t sdl0;
+#define MTW_RX_DDONE (1 << 15)
+#define MTW_RX_LS0 (1 << 14)
+
+ uint32_t sdp1; /* unused */
+ uint32_t flags;
+#define MTW_RX_DEC (1 << 16)
+#define MTW_RX_AMPDU (1 << 15)
+#define MTW_RX_L2PAD (1 << 14)
+#define MTW_RX_RSSI (1 << 13)
+#define MTW_RX_HTC (1 << 12)
+#define MTW_RX_AMSDU (1 << 11)
+#define MTW_RX_MICERR (1 << 10)
+#define MTW_RX_ICVERR (1 << 9)
+#define MTW_RX_CRCERR (1 << 8)
+#define MTW_RX_MYBSS (1 << 7)
+#define MTW_RX_BC (1 << 6)
+#define MTW_RX_MC (1 << 5)
+#define MTW_RX_UC2ME (1 << 4)
+#define MTW_RX_FRAG (1 << 3)
+#define MTW_RX_NULL (1 << 2)
+#define MTW_RX_DATA (1 << 1)
+#define MTW_RX_BA (1 << 0)
+} __packed;
+
+/* RX descriptor */
+struct mtw_rxd {
+ uint16_t len;
+#define MTW_RXD_SELF_GEN (1 << 15)
+#define MTW_RXD_LEN 0x3fff
+
+ uint16_t flags;
+} __packed;
+
+/* RX Wireless Information */
+struct mtw_rxwi {
+ uint32_t flags;
+ uint8_t wcid;
+ uint8_t keyidx;
+#define MTW_RX_UDF_SHIFT 5
+#define MTW_RX_BSS_IDX_SHIFT 2
+
+ uint16_t len;
+#define MTW_RX_TID_SHIFT 12
+
+ uint16_t seq;
+ uint16_t phy;
+ uint8_t rssi[4];
+ uint32_t reserved1;
+ uint32_t reserved2;
+ uint32_t reserved3;
+} __packed __aligned(4);
+
+/* MCU Command */
+struct mtw_mcu_cmd_8 {
+ uint32_t func;
+ uint32_t val;
+} __packed __aligned(4);
+
+struct mtw_mcu_cmd_16 {
+ uint32_t r1;
+ uint32_t r2;
+ uint32_t r3;
+ uint32_t r4;
+} __packed __aligned(4);
+
+#define MTW_DMA_PAD 4
+
+/* first DMA segment contains TXWI + 802.11 header + 32-bit padding */
+#define MTW_TXWI_DMASZ \
+ (sizeof (struct mtw_txwi) + \
+ sizeof (struct ieee80211_htframe) + \
+ sizeof (uint16_t))
+
+#define MT7601_RF_7601 0x7601 /* 1T1R */
+#define MT7610_RF_7610 0x7610 /* 1T1R */
+#define MT7612_RF_7612 0x7612 /* 2T2R */
+
+#define MTW_CONFIG_NO 1
+
+/* USB vendor request */
+#define MTW_RESET 0x1
+#define MTW_WRITE_2 0x2
+#define MTW_WRITE_REGION_1 0x6
+#define MTW_READ_REGION_1 0x7
+#define MTW_EEPROM_READ 0x9
+#define MTW_WRITE_CFG 0x46
+#define MTW_READ_CFG 0x47
+
+/* eFUSE ROM */
+#define MTW_EEPROM_CHIPID 0x00
+#define MTW_EEPROM_VERSION 0x01
+#define MTW_EEPROM_MAC01 0x02
+#define MTW_EEPROM_MAC23 0x03
+#define MTW_EEPROM_MAC45 0x04
+#define MTW_EEPROM_ANTENNA 0x1a
+#define MTW_EEPROM_CONFIG 0x1b
+#define MTW_EEPROM_COUNTRY 0x1c
+#define MTW_EEPROM_FREQ_OFFSET 0x1d
+#define MTW_EEPROM_LED1 0x1e
+#define MTW_EEPROM_LED2 0x1f
+#define MTW_EEPROM_LED3 0x20
+#define MTW_EEPROM_LNA 0x22
+#define MTW_EEPROM_RSSI1_2GHZ 0x23
+#define MTW_EEPROM_RSSI2_2GHZ 0x24
+#define MTW_EEPROM_RSSI1_5GHZ 0x25
+#define MTW_EEPROM_RSSI2_5GHZ 0x26
+#define MTW_EEPROM_DELTAPWR 0x28
+#define MTW_EEPROM_PWR2GHZ_BASE1 0x29
+#define MTW_EEPROM_PWR2GHZ_BASE2 0x30
+#define MTW_EEPROM_TSSI1_2GHZ 0x37
+#define MTW_EEPROM_TSSI2_2GHZ 0x38
+#define MTW_EEPROM_TSSI3_2GHZ 0x39
+#define MTW_EEPROM_TSSI4_2GHZ 0x3a
+#define MTW_EEPROM_TSSI5_2GHZ 0x3b
+#define MTW_EEPROM_PWR5GHZ_BASE1 0x3c
+#define MTW_NIC_CONF2 0x42
+#define MTW_EEPROM_PWR5GHZ_BASE2 0x53
+#define MTW_TXPWR_EXT_PA_5G 0x54
+#define MTW_TXPWR_START_2G_0 0x56
+#define MTW_TXPWR_START_2G_1 0x5c
+#define MTW_TXPWR_START_5G_0 0x62
+#define RT2860_EEPROM_TSSI1_5GHZ 0x6a
+#define RT2860_EEPROM_TSSI2_5GHZ 0x6b
+#define RT2860_EEPROM_TSSI3_5GHZ 0x6c
+#define RT2860_EEPROM_TSSI4_5GHZ 0x6d
+#define RT2860_EEPROM_TSSI5_5GHZ 0x6e
+#define MTW_TX_TSSI_SLOPE 0x6e
+#define MTW_EEPROM_RPWR 0x6f
+
+/* led related */
+#define CMD_LED_MODE 0x10
+#define CMD_MODE_ON 0x0
+static const struct rt2860_rate {
+ uint8_t rate;
+ uint8_t mcs;
+ enum ieee80211_phytype phy;
+ uint8_t ctl_ridx;
+ uint16_t sp_ack_dur;
+ uint16_t lp_ack_dur;
+} rt2860_rates[] = {
+ { 2, 0, IEEE80211_T_DS, 0, 314, 314 },
+ { 4, 1, IEEE80211_T_DS, 1, 258, 162 },
+ { 11, 2, IEEE80211_T_DS, 2, 223, 127 },
+ { 22, 3, IEEE80211_T_DS, 3, 213, 117 },
+ { 12, 0, IEEE80211_T_OFDM, 4, 60, 60 },
+ { 18, 1, IEEE80211_T_OFDM, 4, 52, 52 },
+ { 24, 2, IEEE80211_T_OFDM, 6, 48, 48 },
+ { 36, 3, IEEE80211_T_OFDM, 6, 44, 44 },
+ { 48, 4, IEEE80211_T_OFDM, 8, 44, 44 },
+ { 72, 5, IEEE80211_T_OFDM, 8, 40, 40 },
+ { 96, 6, IEEE80211_T_OFDM, 8, 40, 40 },
+ { 108, 7, IEEE80211_T_OFDM, 8, 40, 40 },
+ { 0x80, 0, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x81, 1, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x82, 2, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x83, 3, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x84, 4, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x85, 5, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x86, 6, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x87, 7, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x88, 8, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x89, 9, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x8a, 10, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x8b, 11, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x8c, 12, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x8d, 13, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x8e, 14, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x8f, 15, IEEE80211_T_HT, 4, 60, 60 },
+
+ /* MCS - 3 streams */
+ { 0x90, 16, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x91, 17, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x92, 18, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x93, 19, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x94, 20, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x95, 21, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x96, 22, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x97, 23, IEEE80211_T_HT, 4, 60, 60 }
+};
+/* These are indexes into the above rt2860_rates[] array */
+#define MTW_RIDX_CCK1 0
+#define MTW_RIDX_CCK11 3
+#define MTW_RIDX_OFDM6 4
+#define MTW_RIDX_MCS0 12
+#define MTW_RIDX_MAX 36
+
+#define MT7601_RF_CHAN \
+ { 1, 0x99, 0x99, 0x09, 0x50 }, \
+ { 2, 0x46, 0x44, 0x0a, 0x50 }, \
+ { 3, 0xec, 0xee, 0x0a, 0x50 }, \
+ { 4, 0x99, 0x99, 0x0b, 0x50 }, \
+ { 5, 0x46, 0x44, 0x08, 0x51 }, \
+ { 6, 0xec, 0xee, 0x08, 0x51 }, \
+ { 7, 0x99, 0x99, 0x09, 0x51 }, \
+ { 8, 0x46, 0x44, 0x0a, 0x51 }, \
+ { 9, 0xec, 0xee, 0x0a, 0x51 }, \
+ { 10, 0x99, 0x99, 0x0b, 0x51 }, \
+ { 11, 0x46, 0x44, 0x08, 0x52 }, \
+ { 12, 0xec, 0xee, 0x08, 0x52 }, \
+ { 13, 0x99, 0x99, 0x09, 0x52 }, \
+ { 14, 0x33, 0x33, 0x0b, 0x52 }
+
+/*
+ * Default values for MAC registers.
+ */
+#define MT7601_DEF_MAC \
+ { MTW_BCN_OFFSET0, 0x18100800 }, \
+ { MTW_BCN_OFFSET1, 0x38302820 }, \
+ { MTW_BCN_OFFSET2, 0x58504840 }, \
+ { MTW_BCN_OFFSET3, 0x78706860 }, \
+ { MTW_MAC_SYS_CTRL, 0x0000000c }, \
+ { MTW_MAX_LEN_CFG, 0x000a3fff }, \
+ { MTW_AMPDU_MAX_LEN_20M1S, 0x77777777 }, \
+ { MTW_AMPDU_MAX_LEN_20M2S, 0x77777777 }, \
+ { MTW_AMPDU_MAX_LEN_40M1S, 0x77777777 }, \
+ { MTW_AMPDU_MAX_LEN_40M2S, 0x77777777 }, \
+ { MTW_XIFS_TIME_CFG, 0x33a41010 }, \
+ { MTW_BKOFF_SLOT_CFG, 0x00000209 }, \
+ { MTW_TBTT_SYNC_CFG, 0x00422010 }, \
+ { MTW_INT_TIMER_CFG, 0x00000000 }, \
+ { MTW_PWR_PIN_CFG, 0x00000000 }, \
+ { MTW_AUTO_WAKEUP_CFG, 0x00000014 }, \
+ { MTW_EDCA_AC_CFG(0), 0x000a4360 }, \
+ { MTW_EDCA_AC_CFG(1), 0x000a4700 }, \
+ { MTW_EDCA_AC_CFG(2), 0x00043338 }, \
+ { MTW_EDCA_AC_CFG(3), 0x0003222f }, \
+ { MTW_TX_PIN_CFG, 0x33150f0f }, \
+ { MTW_TX_BAND_CFG, 0x00000005 }, \
+ { MTW_TX_SW_CFG0, 0x00000402 }, \
+ { MTW_TX_SW_CFG1, 0x00000000 }, \
+ { MTW_TX_SW_CFG2, 0x00000000 }, \
+ { MTW_TXOP_CTRL_CFG, 0x0000583f }, \
+ { MTW_TX_RTS_CFG, 0x01100020 }, \
+ { MTW_TX_TIMEOUT_CFG, 0x000a2090 }, \
+ { MTW_TX_RETRY_CFG, 0x47d01f0f }, \
+ { MTW_TX_LINK_CFG, 0x007f1820 }, \
+ { MTW_HT_FBK_CFG1, 0xedcba980 }, \
+ { MTW_CCK_PROT_CFG, 0x07f40000 }, \
+ { MTW_OFDM_PROT_CFG, 0x07f60000 }, \
+ { MTW_MM20_PROT_CFG, 0x01750003 }, \
+ { MTW_MM40_PROT_CFG, 0x03f50003 }, \
+ { MTW_GF20_PROT_CFG, 0x01750003 }, \
+ { MTW_GF40_PROT_CFG, 0x03f50003 }, \
+ { MTW_EXP_ACK_TIME, 0x002400ca }, \
+ { MTW_TX_PWR_CFG5, 0x00000000 }, \
+ { MTW_TX_PWR_CFG6, 0x01010101 }, \
+ { MTW_TX0_RF_GAIN_CORR, 0x003b0005 }, \
+ { MTW_TX1_RF_GAIN_CORR, 0x00000000 }, \
+ { MTW_TX0_RF_GAIN_ATTEN, 0x00006969 }, \
+ { MTW_TX_ALC_CFG3, 0x6c6c6c6c }, \
+ { MTW_TX_ALC_CFG0, 0x2f2f0005 }, \
+ { MTW_TX_ALC_CFG4, 0x00000400 }, \
+ { MTW_TX_ALC_VGA3, 0x00060006 }, \
+ { MTW_RX_FILTR_CFG, 0x00015f97 }, \
+ { MTW_AUTO_RSP_CFG, 0x00000003 }, \
+ { MTW_LEGACY_BASIC_RATE, 0x0000015f }, \
+ { MTW_HT_BASIC_RATE, 0x00008003 }, \
+ { MTW_RX_MAX_PCNT, 0x0000009f }, \
+ { MTW_WPDMA_GLO_CFG, 0x00000030 }, \
+ { MTW_WMM_AIFSN_CFG, 0x00002273 }, \
+ { MTW_WMM_CWMIN_CFG, 0x00002344 }, \
+ { MTW_WMM_CWMAX_CFG, 0x000034aa }, \
+ { MTW_TSO_CTRL, 0x00000000 }, \
+ { MTW_SYS_CTRL, 0x00080c00 }, \
+ { MTW_FCE_PSE_CTRL, 0x00000001 }, \
+ { MTW_AUX_CLK_CFG, 0x00000000 }, \
+ { MTW_BBP_PA_MODE_CFG0, 0x010055ff }, \
+ { MTW_BBP_PA_MODE_CFG1, 0x00550055 }, \
+ { MTW_RF_PA_MODE_CFG0, 0x010055ff }, \
+ { MTW_RF_PA_MODE_CFG1, 0x00550055 }, \
+ { 0x0a38, 0x00000000 }, \
+ { MTW_BBP_CSR, 0x00000000 }, \
+ { MTW_PBF_CFG, 0x7f723c1f }
+
+/*
+ * Default values for Baseband registers
+ */
+#define MT7601_DEF_BBP \
+ { 1, 0x04 }, \
+ { 4, 0x40 }, \
+ { 20, 0x06 }, \
+ { 31, 0x08 }, \
+ { 178, 0xff }, \
+ { 66, 0x14 }, \
+ { 68, 0x8b }, \
+ { 69, 0x12 }, \
+ { 70, 0x09 }, \
+ { 73, 0x11 }, \
+ { 75, 0x60 }, \
+ { 76, 0x44 }, \
+ { 84, 0x9a }, \
+ { 86, 0x38 }, \
+ { 91, 0x07 }, \
+ { 92, 0x02 }, \
+ { 99, 0x50 }, \
+ { 101, 0x00 }, \
+ { 103, 0xc0 }, \
+ { 104, 0x92 }, \
+ { 105, 0x3c }, \
+ { 106, 0x03 }, \
+ { 128, 0x12 }, \
+ { 142, 0x04 }, \
+ { 143, 0x37 }, \
+ { 142, 0x03 }, \
+ { 143, 0x99 }, \
+ { 160, 0xeb }, \
+ { 161, 0xc4 }, \
+ { 162, 0x77 }, \
+ { 163, 0xf9 }, \
+ { 164, 0x88 }, \
+ { 165, 0x80 }, \
+ { 166, 0xff }, \
+ { 167, 0xe4 }, \
+ { 195, 0x00 }, \
+ { 196, 0x00 }, \
+ { 195, 0x01 }, \
+ { 196, 0x04 }, \
+ { 195, 0x02 }, \
+ { 196, 0x20 }, \
+ { 195, 0x03 }, \
+ { 196, 0x0a }, \
+ { 195, 0x06 }, \
+ { 196, 0x16 }, \
+ { 195, 0x07 }, \
+ { 196, 0x05 }, \
+ { 195, 0x08 }, \
+ { 196, 0x37 }, \
+ { 195, 0x0a }, \
+ { 196, 0x15 }, \
+ { 195, 0x0b }, \
+ { 196, 0x17 }, \
+ { 195, 0x0c }, \
+ { 196, 0x06 }, \
+ { 195, 0x0d }, \
+ { 196, 0x09 }, \
+ { 195, 0x0e }, \
+ { 196, 0x05 }, \
+ { 195, 0x0f }, \
+ { 196, 0x09 }, \
+ { 195, 0x10 }, \
+ { 196, 0x20 }, \
+ { 195, 0x20 }, \
+ { 196, 0x17 }, \
+ { 195, 0x21 }, \
+ { 196, 0x06 }, \
+ { 195, 0x22 }, \
+ { 196, 0x09 }, \
+ { 195, 0x23 }, \
+ { 196, 0x17 }, \
+ { 195, 0x24 }, \
+ { 196, 0x06 }, \
+ { 195, 0x25 }, \
+ { 196, 0x09 }, \
+ { 195, 0x26 }, \
+ { 196, 0x17 }, \
+ { 195, 0x27 }, \
+ { 196, 0x06 }, \
+ { 195, 0x28 }, \
+ { 196, 0x09 }, \
+ { 195, 0x29 }, \
+ { 196, 0x05 }, \
+ { 195, 0x2a }, \
+ { 196, 0x09 }, \
+ { 195, 0x80 }, \
+ { 196, 0x8b }, \
+ { 195, 0x81 }, \
+ { 196, 0x12 }, \
+ { 195, 0x82 }, \
+ { 196, 0x09 }, \
+ { 195, 0x83 }, \
+ { 196, 0x17 }, \
+ { 195, 0x84 }, \
+ { 196, 0x11 }, \
+ { 195, 0x85 }, \
+ { 196, 0x00 }, \
+ { 195, 0x86 }, \
+ { 196, 0x00 }, \
+ { 195, 0x87 }, \
+ { 196, 0x18 }, \
+ { 195, 0x88 }, \
+ { 196, 0x60 }, \
+ { 195, 0x89 }, \
+ { 196, 0x44 }, \
+ { 195, 0x8a }, \
+ { 196, 0x8b }, \
+ { 195, 0x8b }, \
+ { 196, 0x8b }, \
+ { 195, 0x8c }, \
+ { 196, 0x8b }, \
+ { 195, 0x8d }, \
+ { 196, 0x8b }, \
+ { 195, 0x8e }, \
+ { 196, 0x09 }, \
+ { 195, 0x8f }, \
+ { 196, 0x09 }, \
+ { 195, 0x90 }, \
+ { 196, 0x09 }, \
+ { 195, 0x91 }, \
+ { 196, 0x09 }, \
+ { 195, 0x92 }, \
+ { 196, 0x11 }, \
+ { 195, 0x93 }, \
+ { 196, 0x11 }, \
+ { 195, 0x94 }, \
+ { 196, 0x11 }, \
+ { 195, 0x95 }, \
+ { 196, 0x11 }, \
+ { 47, 0x80 }, \
+ { 60, 0x80 }, \
+ { 150, 0xd2 }, \
+ { 151, 0x32 }, \
+ { 152, 0x23 }, \
+ { 153, 0x41 }, \
+ { 154, 0x00 }, \
+ { 155, 0x4f }, \
+ { 253, 0x7e }, \
+ { 195, 0x30 }, \
+ { 196, 0x32 }, \
+ { 195, 0x31 }, \
+ { 196, 0x23 }, \
+ { 195, 0x32 }, \
+ { 196, 0x45 }, \
+ { 195, 0x35 }, \
+ { 196, 0x4a }, \
+ { 195, 0x36 }, \
+ { 196, 0x5a }, \
+ { 195, 0x37 }, \
+ { 196, 0x5a }
+
+/*
+ * Default values for RF registers
+ */
+#define MT7601_BANK0_RF \
+ { 0, 0x02 }, \
+ { 1, 0x01 }, \
+ { 2, 0x11 }, \
+ { 3, 0xff }, \
+ { 4, 0x0a }, \
+ { 5, 0x20 }, \
+ { 6, 0x00 }, \
+ { 7, 0x00 }, \
+ { 8, 0x00 }, \
+ { 9, 0x00 }, \
+ { 10, 0x00 }, \
+ { 11, 0x21 }, \
+ { 13, 0x00 }, \
+ { 14, 0x7c }, \
+ { 15, 0x22 }, \
+ { 16, 0x80 }, \
+ { 17, 0x99 }, \
+ { 18, 0x99 }, \
+ { 19, 0x09 }, \
+ { 20, 0x50 }, \
+ { 21, 0xb0 }, \
+ { 22, 0x00 }, \
+ { 23, 0xc5 }, \
+ { 24, 0xfc }, \
+ { 25, 0x40 }, \
+ { 26, 0x4d }, \
+ { 27, 0x02 }, \
+ { 28, 0x72 }, \
+ { 29, 0x01 }, \
+ { 30, 0x00 }, \
+ { 31, 0x00 }, \
+ { 32, 0x00 }, \
+ { 33, 0x00 }, \
+ { 34, 0x23 }, \
+ { 35, 0x01 }, \
+ { 36, 0x00 }, \
+ { 37, 0x00 }, \
+ { 38, 0x00 }, \
+ { 39, 0x20 }, \
+ { 40, 0x00 }, \
+ { 41, 0xd0 }, \
+ { 42, 0x1b }, \
+ { 43, 0x02 }, \
+ { 44, 0x00 }
+
+#define MT7601_BANK4_RF \
+ { 0, 0x01 }, \
+ { 1, 0x00 }, \
+ { 2, 0x00 }, \
+ { 3, 0x00 }, \
+ { 4, 0x00 }, \
+ { 5, 0x08 }, \
+ { 6, 0x00 }, \
+ { 7, 0x5b }, \
+ { 8, 0x52 }, \
+ { 9, 0xb6 }, \
+ { 10, 0x57 }, \
+ { 11, 0x33 }, \
+ { 12, 0x22 }, \
+ { 13, 0x3d }, \
+ { 14, 0x3e }, \
+ { 15, 0x13 }, \
+ { 16, 0x22 }, \
+ { 17, 0x23 }, \
+ { 18, 0x02 }, \
+ { 19, 0xa4 }, \
+ { 20, 0x01 }, \
+ { 21, 0x12 }, \
+ { 22, 0x80 }, \
+ { 23, 0xb3 }, \
+ { 24, 0x00 }, \
+ { 25, 0x00 }, \
+ { 26, 0x00 }, \
+ { 27, 0x00 }, \
+ { 28, 0x18 }, \
+ { 29, 0xee }, \
+ { 30, 0x6b }, \
+ { 31, 0x31 }, \
+ { 32, 0x5d }, \
+ { 33, 0x00 }, \
+ { 34, 0x96 }, \
+ { 35, 0x55 }, \
+ { 36, 0x08 }, \
+ { 37, 0xbb }, \
+ { 38, 0xb3 }, \
+ { 39, 0xb3 }, \
+ { 40, 0x03 }, \
+ { 41, 0x00 }, \
+ { 42, 0x00 }, \
+ { 43, 0xc5 }, \
+ { 44, 0xc5 }, \
+ { 45, 0xc5 }, \
+ { 46, 0x07 }, \
+ { 47, 0xa8 }, \
+ { 48, 0xef }, \
+ { 49, 0x1a }, \
+ { 54, 0x07 }, \
+ { 55, 0xa7 }, \
+ { 56, 0xcc }, \
+ { 57, 0x14 }, \
+ { 58, 0x07 }, \
+ { 59, 0xa8 }, \
+ { 60, 0xd7 }, \
+ { 61, 0x10 }, \
+ { 62, 0x1c }, \
+ { 63, 0x00 }
+
+#define MT7601_BANK5_RF \
+ { 0, 0x47 }, \
+ { 1, 0x00 }, \
+ { 2, 0x00 }, \
+ { 3, 0x08 }, \
+ { 4, 0x04 }, \
+ { 5, 0x20 }, \
+ { 6, 0x3a }, \
+ { 7, 0x3a }, \
+ { 8, 0x00 }, \
+ { 9, 0x00 }, \
+ { 10, 0x10 }, \
+ { 11, 0x10 }, \
+ { 12, 0x10 }, \
+ { 13, 0x10 }, \
+ { 14, 0x10 }, \
+ { 15, 0x20 }, \
+ { 16, 0x22 }, \
+ { 17, 0x7c }, \
+ { 18, 0x00 }, \
+ { 19, 0x00 }, \
+ { 20, 0x00 }, \
+ { 21, 0xf1 }, \
+ { 22, 0x11 }, \
+ { 23, 0x02 }, \
+ { 24, 0x41 }, \
+ { 25, 0x20 }, \
+ { 26, 0x00 }, \
+ { 27, 0xd7 }, \
+ { 28, 0xa2 }, \
+ { 29, 0x20 }, \
+ { 30, 0x49 }, \
+ { 31, 0x20 }, \
+ { 32, 0x04 }, \
+ { 33, 0xf1 }, \
+ { 34, 0xa1 }, \
+ { 35, 0x01 }, \
+ { 41, 0x00 }, \
+ { 42, 0x00 }, \
+ { 43, 0x00 }, \
+ { 44, 0x00 }, \
+ { 45, 0x00 }, \
+ { 46, 0x00 }, \
+ { 47, 0x00 }, \
+ { 48, 0x00 }, \
+ { 49, 0x00 }, \
+ { 50, 0x00 }, \
+ { 51, 0x00 }, \
+ { 52, 0x00 }, \
+ { 53, 0x00 }, \
+ { 54, 0x00 }, \
+ { 55, 0x00 }, \
+ { 56, 0x00 }, \
+ { 57, 0x00 }, \
+ { 58, 0x31 }, \
+ { 59, 0x31 }, \
+ { 60, 0x0a }, \
+ { 61, 0x02 }, \
+ { 62, 0x00 }, \
+ { 63, 0x00 }
+union mtw_stats {
+ uint32_t raw;
+ struct {
+ uint16_t fail;
+ uint16_t pad;
+ } error;
+ struct {
+ uint16_t success;
+ uint16_t retry;
+ } tx;
+} __aligned(4);
diff --git a/sys/dev/usb/wlan/if_mtwvar.h b/sys/dev/usb/wlan/if_mtwvar.h
new file mode 100644
index 000000000000..3cf4c4f9c94e
--- /dev/null
+++ b/sys/dev/usb/wlan/if_mtwvar.h
@@ -0,0 +1,387 @@
+/* $OpenBSD: if_mtwvar.h,v 1.1 2021/12/20 13:59:02 hastings Exp $ */
+/*
+ * Copyright (c) 2008,2009 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 MTW_MAX_RXSZ \
+ 4096
+#if 0
+ (sizeof (uint32_t) + \
+ sizeof (struct mtw_rxwi) + \
+ sizeof (uint16_t) + \
+ MCLBYTES + \
+ sizeof (struct mtw_rxd))
+#endif
+
+#define MTW_TX_TIMEOUT 5000 /* ms */
+#define MTW_VAP_MAX 8
+#define MTW_RX_RING_COUNT 1
+#define MTW_TX_RING_COUNT 32
+
+#define MTW_RXQ_COUNT 2
+#define MTW_TXQ_COUNT 6
+
+#define MTW_WCID_MAX 64
+#define MTW_AID2WCID(aid) (1 + ((aid) & 0x7))
+
+struct mtw_rx_radiotap_header {
+ struct ieee80211_radiotap_header wr_ihdr;
+ uint64_t wr_tsf;
+ uint8_t wr_flags;
+ uint8_t wr_rate;
+ uint16_t wr_chan_freq;
+ uint16_t wr_chan_flags;
+ uint8_t wr_dbm_antsignal;
+ uint8_t wr_antenna;
+ uint8_t wr_antsignal;
+} __packed;
+#define MTW_RATECTL_OFF 0
+#define MTW_RX_RADIOTAP_PRESENT \
+ (1 << IEEE80211_RADIOTAP_FLAGS | \
+ 1 << IEEE80211_RADIOTAP_RATE | \
+ 1 << IEEE80211_RADIOTAP_CHANNEL | \
+ 1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL | \
+ 1 << IEEE80211_RADIOTAP_ANTENNA | \
+ 1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL)
+struct mtw_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_hwqueue;
+} __packed;
+
+#define MTW_TX_RADIOTAP_PRESENT \
+ (1 << IEEE80211_RADIOTAP_FLAGS | \
+ 1 << IEEE80211_RADIOTAP_RATE | \
+ 1 << IEEE80211_RADIOTAP_CHANNEL)
+
+struct mtw_softc;
+
+struct mtw_fw_data {
+ uint16_t len;
+ uint16_t flags;
+
+ uint8_t *buf;
+ uint32_t buflen;
+
+};
+struct mtw_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)
+#define RT2573_TX_TKIPMIC (1 << 8)
+#define RT2573_TX_KEY_PAIR (1 << 9)
+#define RT2573_TX_KEY_ID(id) (((id) & 0x3f) << 10)
+#define RT2573_TX_CIP_MODE(m) ((m) << 29)
+
+ 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)
+
+ uint8_t hdrlen;
+ uint8_t xflags;
+#define RT2573_TX_HWSEQ (1 << 4)
+
+ 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 mtw_tx_data {
+ STAILQ_ENTRY(mtw_tx_data) next;
+ struct mbuf *m;
+ struct mtw_softc *sc;
+ struct usbd_xfer *xfer;
+ uint8_t qid;
+ uint8_t ridx;
+ uint32_t buflen;
+ //struct mtw_tx_desc desc;
+ struct ieee80211_node *ni;
+ //struct mtw_txd desc;
+ uint8_t desc[sizeof(struct mtw_txd)+sizeof(struct mtw_txwi)];
+
+};
+
+struct mtw_rx_data {
+ STAILQ_ENTRY(mtw_rx_data) next;
+ struct mtw_softc *sc;
+ struct usbd_xfer *xfer;
+
+ uint8_t *buf;
+};
+
+struct mtw_tx_ring {
+ struct mtw_tx_data data[MTW_TX_RING_COUNT];
+ struct usbd_pipe *pipeh;
+ int cur;
+ int queued;
+ uint8_t pipe_no;
+};
+
+struct mtw_rx_ring {
+ struct mtw_rx_data data[MTW_RX_RING_COUNT];
+ struct usbd_pipe *pipeh;
+ uint8_t pipe_no;
+};
+
+struct mtw_vap {
+ struct ieee80211vap vap;
+ struct mbuf *beacon_mbuf;
+
+ int (*newstate)(struct ieee80211vap *,
+ enum ieee80211_state, int);
+ void (*recv_mgmt)(struct ieee80211_node *,
+ struct mbuf *, int,
+ const struct ieee80211_rx_stats *,
+ int, int);
+
+ uint8_t rvp_id;
+};
+#define MTW_VAP(vap) ((struct mtw_vap *)(vap))
+struct mtw_host_cmd {
+ void (*cb)(struct mtw_softc *, void *);
+ uint8_t data[256];
+};
+
+struct mtw_cmd_newstate {
+ enum ieee80211_state state;
+ int arg;
+};
+
+struct mtw_cmd_key {
+ struct ieee80211_key key;
+ struct ieee80211_node *ni;
+};
+
+#define MTW_HOST_CMD_RING_COUNT 32
+struct mtw_host_cmd_ring {
+ struct mtw_host_cmd cmd[MTW_HOST_CMD_RING_COUNT];
+ int cur;
+ int next;
+ int queued;
+};
+
+
+
+struct mtw_node {
+ struct ieee80211_node ni;
+ uint8_t mgt_ridx;
+ uint8_t amrr_ridx;
+ uint8_t fix_ridx;
+
+};
+#define MTW_NODE(ni) ((struct mtw_node *)(ni))
+
+struct mtw_mcu_tx {
+ struct mtw_softc *sc;
+ struct usbd_xfer *xfer;
+ struct usbd_pipe *pipeh;
+ uint8_t pipe_no;
+ uint8_t *buf;
+ int8_t seq;
+};
+
+#define MTW_MCU_IVB_LEN 0x40
+struct mtw_ucode_hdr {
+ uint32_t ilm_len;
+ uint32_t dlm_len;
+ uint16_t build_ver;
+ uint16_t fw_ver;
+ uint8_t pad[4];
+ char build_time[16];
+} __packed;
+
+struct mtw_ucode {
+ struct mtw_ucode_hdr hdr;
+ uint8_t ivb[MTW_MCU_IVB_LEN];
+ uint8_t data[];
+} __packed;
+
+STAILQ_HEAD(mtw_tx_data_head, mtw_tx_data);
+struct mtw_endpoint_queue {
+ struct mtw_tx_data tx_data[MTW_TX_RING_COUNT];
+ struct mtw_tx_data_head tx_qh;
+ struct mtw_tx_data_head tx_fh;
+ uint32_t tx_nfree;
+};
+
+struct mtw_cmdq {
+ void *arg0;
+ void *arg1;
+ void (*func)(void *);
+ struct ieee80211_key *k;
+ struct ieee80211_key key;
+ uint8_t mac[IEEE80211_ADDR_LEN];
+ uint8_t wcid;
+};
+enum {
+ MTW_BULK_RX, /* = WME_AC_BK */
+ //MTW_BULK_RX1,
+ MTW_BULK_TX_BE, /* = WME_AC_BE */
+ MTW_BULK_TX_VI, /* = WME_AC_VI */
+ MTW_BULK_TX_VO, /* = WME_AC_VO */
+ MTW_BULK_TX_HCCA,
+ MTW_BULK_TX_PRIO,
+ MTW_BULK_TX_BK,
+ MTW_BULK_FW_CMD,
+ MTW_BULK_RAW_TX,
+ MTW_N_XFER,
+};
+#define MTW_TXCNT 0
+#define MTW_SUCCESS 1
+#define MTW_RETRY 2
+#define MTW_EP_QUEUES 6
+#define MTW_FLAG_FWLOAD_NEEDED 0x01
+#define MTW_RUNNING 0x02
+struct mtw_softc {
+ device_t sc_dev;
+ int sc_idx;
+ struct ieee80211com sc_ic;
+ struct ieee80211_ratectl_tx_stats sc_txs;
+ int (*sc_newstate)(struct ieee80211com *,
+ enum ieee80211_state, int);
+ int (*sc_srom_read)(struct mtw_softc *,
+ uint16_t, uint16_t *);
+#define MTW_CMDQ_MAX 16
+#define MTW_CMDQ_MASQ (MTW_CMDQ_MAX - 1)
+#define MTW_CMDQ_ABORT 0
+#define MTW_CMDQ_GO 1
+ struct mbuf *rx_m;
+ uint8_t runbmap;
+ uint8_t running;
+ uint8_t ap_running;
+ uint8_t adhoc_running;
+ uint8_t sta_running;
+ uint8_t fwloading;
+ uint16_t wcid_stats[MTW_WCID_MAX + 1][3];
+ struct mbufq sc_snd;
+ uint8_t cmdq_exec;
+ uint8_t fifo_cnt;
+ uint32_t sc_flags;
+ uint8_t rvp_cnt;
+ uint8_t cmdq_run;
+ uint8_t rvp_bmap;
+ struct mtw_cmdq cmdq[MTW_CMDQ_MAX];
+ struct task cmdq_task;
+ uint8_t cmdq_mtw;
+ uint8_t cmdq_key_set;
+ struct usb_device *sc_udev;
+ struct usb_interface *sc_iface;
+ uint32_t cmdq_store;
+ struct mtx sc_mtx;
+ uint32_t sc_mcu_xferlen;
+ struct usb_xfer *sc_xfer[MTW_N_XFER];
+ uint16_t asic_ver;
+ uint16_t asic_rev;
+ uint16_t mac_ver;
+ uint16_t mac_rev;
+ uint16_t rf_rev;
+ int ridx;
+ int amrr_ridx;
+ uint8_t freq;
+ uint8_t ntxchains;
+ uint8_t nrxchains;
+
+ struct mtw_txd_fw *txd_fw[4];
+ int sc_sent;
+ uint8_t sc_ivb_1[MTW_MCU_IVB_LEN];
+ struct mtw_endpoint_queue sc_epq[MTW_BULK_RX];
+ uint8_t rfswitch;
+ uint8_t ext_2ghz_lna;
+ uint8_t ext_5ghz_lna;
+ uint8_t calib_2ghz;
+ uint8_t calib_5ghz;
+ uint8_t txmixgain_2ghz;
+ uint8_t txmixgain_5ghz;
+ int8_t txpow1[54];
+ int8_t txpow2[54];
+ int8_t txpow3[54];
+ int8_t rssi_2ghz[3];
+ int8_t rssi_5ghz[3];
+ uint8_t lna[4];
+
+ uint8_t leds;
+ uint16_t led[3];
+ uint32_t txpow20mhz[5];
+ uint32_t txpow40mhz_2ghz[5];
+ uint32_t txpow40mhz_5ghz[5];
+
+ int8_t bbp_temp;
+ uint8_t rf_freq_offset;
+ uint32_t rf_pa_mode[2];
+ int sc_rf_calibrated;
+ int sc_bw_calibrated;
+ int sc_chan_group;
+
+
+
+
+ uint8_t cmd_seq;
+ uint8_t sc_detached;
+ struct mtw_tx_ring sc_mcu;
+ struct mtw_rx_ring rxq[MTW_RXQ_COUNT];
+ struct mtw_tx_ring txq[MTW_TXQ_COUNT];
+ struct task ratectl_task;
+ struct usb_callout ratectl_ch;
+ uint8_t ratectl_run;
+ //struct mtw_host_cmd_ring cmdq;
+ uint8_t qfullmsk;
+ int sc_tx_timer;
+
+ uint8_t sc_bssid[IEEE80211_ADDR_LEN];
+
+ union {
+ struct mtw_rx_radiotap_header th;
+ uint8_t pad[64];
+ } sc_rxtapu;
+#define sc_rxtap sc_rxtapu.th
+ int sc_rxtap_len;
+
+ union {
+ struct mtw_tx_radiotap_header th;
+ uint8_t pad[64];
+ uint8_t wt_hwqueue;
+
+ } sc_txtapu;
+#define sc_txtap sc_txtapu.th
+ int sc_txtap_len;
+ int sc_key_tasks;
+};
+#define MTW_LOCK(sc) mtx_lock(&(sc)->sc_mtx)
+#define MTW_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx)
+#define MTW_LOCK_ASSERT(sc, t) mtx_assert(&(sc)->sc_mtx, t)
diff --git a/sys/dev/usb/wlan/if_rsu.c b/sys/dev/usb/wlan/if_rsu.c
new file mode 100644
index 000000000000..07f7b6f3a708
--- /dev/null
+++ b/sys/dev/usb/wlan/if_rsu.c
@@ -0,0 +1,3762 @@
+/* $OpenBSD: if_rsu.c,v 1.17 2013/04/15 09:23:01 mglocker Exp $ */
+
+/*-
+ * Copyright (c) 2010 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.
+ */
+/*
+ * Driver for Realtek RTL8188SU/RTL8191SU/RTL8192SU.
+ *
+ * TODO:
+ * o tx a-mpdu
+ * o hostap / ibss / mesh
+ * o power-save operation
+ */
+
+#include "opt_wlan.h"
+
+#include <sys/param.h>
+#include <sys/endian.h>
+#include <sys/sockio.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/kernel.h>
+#include <sys/socket.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/bus.h>
+#include <sys/firmware.h>
+#include <sys/module.h>
+
+#include <net/bpf.h>
+#include <net/if.h>
+#include <net/if_var.h>
+#include <net/if_arp.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+#include <net/if_types.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_var.h>
+#include <netinet/if_ether.h>
+#include <netinet/ip.h>
+
+#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 "usbdevs.h"
+
+#include <dev/rtwn/if_rtwn_ridx.h> /* XXX */
+#include <dev/usb/wlan/if_rsureg.h>
+
+#define RSU_RATE_IS_CCK RTWN_RATE_IS_CCK
+
+#ifdef USB_DEBUG
+static int rsu_debug = 0;
+SYSCTL_NODE(_hw_usb, OID_AUTO, rsu, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB rsu");
+SYSCTL_INT(_hw_usb_rsu, OID_AUTO, debug, CTLFLAG_RWTUN, &rsu_debug, 0,
+ "Debug level");
+#define RSU_DPRINTF(_sc, _flg, ...) \
+ do \
+ if (((_flg) == (RSU_DEBUG_ANY)) || (rsu_debug & (_flg))) \
+ device_printf((_sc)->sc_dev, __VA_ARGS__); \
+ while (0)
+#else
+#define RSU_DPRINTF(_sc, _flg, ...)
+#endif
+
+static int rsu_enable_11n = 1;
+TUNABLE_INT("hw.usb.rsu.enable_11n", &rsu_enable_11n);
+
+#define RSU_DEBUG_ANY 0xffffffff
+#define RSU_DEBUG_TX 0x00000001
+#define RSU_DEBUG_RX 0x00000002
+#define RSU_DEBUG_RESET 0x00000004
+#define RSU_DEBUG_CALIB 0x00000008
+#define RSU_DEBUG_STATE 0x00000010
+#define RSU_DEBUG_SCAN 0x00000020
+#define RSU_DEBUG_FWCMD 0x00000040
+#define RSU_DEBUG_TXDONE 0x00000080
+#define RSU_DEBUG_FW 0x00000100
+#define RSU_DEBUG_FWDBG 0x00000200
+#define RSU_DEBUG_AMPDU 0x00000400
+#define RSU_DEBUG_KEY 0x00000800
+#define RSU_DEBUG_USB 0x00001000
+
+static const STRUCT_USB_HOST_ID rsu_devs[] = {
+#define RSU_HT_NOT_SUPPORTED 0
+#define RSU_HT_SUPPORTED 1
+#define RSU_DEV_HT(v,p) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, \
+ RSU_HT_SUPPORTED) }
+#define RSU_DEV(v,p) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, \
+ RSU_HT_NOT_SUPPORTED) }
+ RSU_DEV(ASUS, RTL8192SU),
+ RSU_DEV(AZUREWAVE, RTL8192SU_4),
+ RSU_DEV(SITECOMEU, WLA1000),
+ RSU_DEV_HT(ACCTON, RTL8192SU),
+ RSU_DEV_HT(ASUS, USBN10),
+ RSU_DEV_HT(AZUREWAVE, RTL8192SU_1),
+ RSU_DEV_HT(AZUREWAVE, RTL8192SU_2),
+ RSU_DEV_HT(AZUREWAVE, RTL8192SU_3),
+ RSU_DEV_HT(AZUREWAVE, RTL8192SU_5),
+ RSU_DEV_HT(BELKIN, RTL8192SU_1),
+ RSU_DEV_HT(BELKIN, RTL8192SU_2),
+ RSU_DEV_HT(BELKIN, RTL8192SU_3),
+ RSU_DEV_HT(CONCEPTRONIC2, RTL8192SU_1),
+ RSU_DEV_HT(CONCEPTRONIC2, RTL8192SU_2),
+ RSU_DEV_HT(CONCEPTRONIC2, RTL8192SU_3),
+ RSU_DEV_HT(COREGA, RTL8192SU),
+ RSU_DEV_HT(DLINK2, DWA131A1),
+ RSU_DEV_HT(DLINK2, RTL8192SU_1),
+ RSU_DEV_HT(DLINK2, RTL8192SU_2),
+ RSU_DEV_HT(EDIMAX, RTL8192SU_1),
+ RSU_DEV_HT(EDIMAX, RTL8192SU_2),
+ RSU_DEV_HT(EDIMAX, EW7622UMN),
+ RSU_DEV_HT(GUILLEMOT, HWGUN54),
+ RSU_DEV_HT(GUILLEMOT, HWNUM300),
+ RSU_DEV_HT(HAWKING, RTL8192SU_1),
+ RSU_DEV_HT(HAWKING, RTL8192SU_2),
+ RSU_DEV_HT(PLANEX2, GWUSNANO),
+ RSU_DEV_HT(REALTEK, RTL8171),
+ RSU_DEV_HT(REALTEK, RTL8172),
+ RSU_DEV_HT(REALTEK, RTL8173),
+ RSU_DEV_HT(REALTEK, RTL8174),
+ RSU_DEV_HT(REALTEK, RTL8192SU),
+ RSU_DEV_HT(REALTEK, RTL8712),
+ RSU_DEV_HT(REALTEK, RTL8713),
+ RSU_DEV_HT(SENAO, RTL8192SU_1),
+ RSU_DEV_HT(SENAO, RTL8192SU_2),
+ RSU_DEV_HT(SITECOMEU, WL349V1),
+ RSU_DEV_HT(SITECOMEU, WL353),
+ RSU_DEV_HT(SITECOMEU, RTL8188S),
+ RSU_DEV_HT(SWEEX2, LW154),
+ RSU_DEV_HT(TRENDNET, TEW646UBH),
+#undef RSU_DEV_HT
+#undef RSU_DEV
+};
+
+static device_probe_t rsu_match;
+static device_attach_t rsu_attach;
+static device_detach_t rsu_detach;
+static usb_callback_t rsu_bulk_tx_callback_be_bk;
+static usb_callback_t rsu_bulk_tx_callback_vi_vo;
+static usb_callback_t rsu_bulk_tx_callback_h2c;
+static usb_callback_t rsu_bulk_rx_callback;
+static usb_error_t rsu_do_request(struct rsu_softc *,
+ struct usb_device_request *, void *);
+static struct ieee80211vap *
+ rsu_vap_create(struct ieee80211com *, const char name[IFNAMSIZ],
+ int, enum ieee80211_opmode, int,
+ const uint8_t bssid[IEEE80211_ADDR_LEN],
+ const uint8_t mac[IEEE80211_ADDR_LEN]);
+static void rsu_vap_delete(struct ieee80211vap *);
+static void rsu_scan_start(struct ieee80211com *);
+static void rsu_scan_end(struct ieee80211com *);
+static void rsu_getradiocaps(struct ieee80211com *, int, int *,
+ struct ieee80211_channel[]);
+static void rsu_set_channel(struct ieee80211com *);
+static void rsu_scan_curchan(struct ieee80211_scan_state *, unsigned long);
+static void rsu_scan_mindwell(struct ieee80211_scan_state *);
+static void rsu_update_promisc(struct ieee80211com *);
+static uint8_t rsu_get_multi_pos(const uint8_t[]);
+static void rsu_set_multi(struct rsu_softc *);
+static void rsu_update_mcast(struct ieee80211com *);
+static int rsu_alloc_rx_list(struct rsu_softc *);
+static void rsu_free_rx_list(struct rsu_softc *);
+static int rsu_alloc_tx_list(struct rsu_softc *);
+static void rsu_free_tx_list(struct rsu_softc *);
+static void rsu_free_list(struct rsu_softc *, struct rsu_data [], int);
+static struct rsu_data *_rsu_getbuf(struct rsu_softc *);
+static struct rsu_data *rsu_getbuf(struct rsu_softc *);
+static void rsu_freebuf(struct rsu_softc *, struct rsu_data *);
+static int rsu_write_region_1(struct rsu_softc *, uint16_t, uint8_t *,
+ int);
+static void rsu_write_1(struct rsu_softc *, uint16_t, uint8_t);
+static void rsu_write_2(struct rsu_softc *, uint16_t, uint16_t);
+static void rsu_write_4(struct rsu_softc *, uint16_t, uint32_t);
+static int rsu_read_region_1(struct rsu_softc *, uint16_t, uint8_t *,
+ int);
+static uint8_t rsu_read_1(struct rsu_softc *, uint16_t);
+static uint16_t rsu_read_2(struct rsu_softc *, uint16_t);
+static uint32_t rsu_read_4(struct rsu_softc *, uint16_t);
+static int rsu_fw_iocmd(struct rsu_softc *, uint32_t);
+static uint8_t rsu_efuse_read_1(struct rsu_softc *, uint16_t);
+static int rsu_read_rom(struct rsu_softc *);
+static int rsu_fw_cmd(struct rsu_softc *, uint8_t, void *, int);
+static void rsu_calib_task(void *, int);
+static void rsu_tx_task(void *, int);
+static void rsu_set_led(struct rsu_softc *, int);
+static int rsu_monitor_newstate(struct ieee80211vap *,
+ enum ieee80211_state, int);
+static int rsu_newstate(struct ieee80211vap *, enum ieee80211_state, int);
+static int rsu_key_alloc(struct ieee80211vap *, struct ieee80211_key *,
+ ieee80211_keyix *, ieee80211_keyix *);
+static int rsu_process_key(struct ieee80211vap *,
+ const struct ieee80211_key *, int);
+static int rsu_key_set(struct ieee80211vap *,
+ const struct ieee80211_key *);
+static int rsu_key_delete(struct ieee80211vap *,
+ const struct ieee80211_key *);
+static int rsu_cam_read(struct rsu_softc *, uint8_t, uint32_t *);
+static void rsu_cam_write(struct rsu_softc *, uint8_t, uint32_t);
+static int rsu_key_check(struct rsu_softc *, ieee80211_keyix, int);
+static uint8_t rsu_crypto_mode(struct rsu_softc *, u_int, int);
+static int rsu_set_key_group(struct rsu_softc *,
+ const struct ieee80211_key *);
+static int rsu_set_key_pair(struct rsu_softc *,
+ const struct ieee80211_key *);
+static int rsu_reinit_static_keys(struct rsu_softc *);
+static int rsu_delete_key(struct rsu_softc *sc, ieee80211_keyix);
+static void rsu_delete_key_pair_cb(void *, int);
+static int rsu_site_survey(struct rsu_softc *,
+ struct ieee80211_scan_ssid *);
+static int rsu_join_bss(struct rsu_softc *, struct ieee80211_node *);
+static int rsu_disconnect(struct rsu_softc *);
+static int rsu_hwrssi_to_rssi(struct rsu_softc *, int hw_rssi);
+static void rsu_event_survey(struct rsu_softc *, uint8_t *, int);
+static void rsu_event_join_bss(struct rsu_softc *, uint8_t *, int);
+static void rsu_rx_event(struct rsu_softc *, uint8_t, uint8_t *, int);
+static void rsu_rx_multi_event(struct rsu_softc *, uint8_t *, int);
+static int8_t rsu_get_rssi(struct rsu_softc *, int, void *);
+static struct mbuf * rsu_rx_copy_to_mbuf(struct rsu_softc *,
+ struct r92s_rx_stat *, int);
+static uint32_t rsu_get_tsf_low(struct rsu_softc *);
+static uint32_t rsu_get_tsf_high(struct rsu_softc *);
+static struct ieee80211_node * rsu_rx_frame(struct rsu_softc *, struct mbuf *);
+static struct mbuf * rsu_rx_multi_frame(struct rsu_softc *, uint8_t *, int);
+static struct mbuf *
+ rsu_rxeof(struct usb_xfer *, struct rsu_data *);
+static void rsu_txeof(struct usb_xfer *, struct rsu_data *);
+static int rsu_raw_xmit(struct ieee80211_node *, struct mbuf *,
+ const struct ieee80211_bpf_params *);
+static void rsu_rxfilter_init(struct rsu_softc *);
+static void rsu_rxfilter_set(struct rsu_softc *, uint32_t, uint32_t);
+static void rsu_rxfilter_refresh(struct rsu_softc *);
+static int rsu_init(struct rsu_softc *);
+static int rsu_tx_start(struct rsu_softc *, struct ieee80211_node *,
+ struct mbuf *, struct rsu_data *);
+static int rsu_transmit(struct ieee80211com *, struct mbuf *);
+static void rsu_start(struct rsu_softc *);
+static void _rsu_start(struct rsu_softc *);
+static int rsu_ioctl_net(struct ieee80211com *, u_long, void *);
+static void rsu_parent(struct ieee80211com *);
+static void rsu_stop(struct rsu_softc *);
+static void rsu_ms_delay(struct rsu_softc *, int);
+
+static device_method_t rsu_methods[] = {
+ DEVMETHOD(device_probe, rsu_match),
+ DEVMETHOD(device_attach, rsu_attach),
+ DEVMETHOD(device_detach, rsu_detach),
+
+ DEVMETHOD_END
+};
+
+static driver_t rsu_driver = {
+ .name = "rsu",
+ .methods = rsu_methods,
+ .size = sizeof(struct rsu_softc)
+};
+
+DRIVER_MODULE(rsu, uhub, rsu_driver, NULL, NULL);
+MODULE_DEPEND(rsu, wlan, 1, 1, 1);
+MODULE_DEPEND(rsu, usb, 1, 1, 1);
+MODULE_DEPEND(rsu, firmware, 1, 1, 1);
+MODULE_VERSION(rsu, 1);
+USB_PNP_HOST_INFO(rsu_devs);
+
+static uint8_t rsu_wme_ac_xfer_map[4] = {
+ [WME_AC_BE] = RSU_BULK_TX_BE_BK,
+ [WME_AC_BK] = RSU_BULK_TX_BE_BK,
+ [WME_AC_VI] = RSU_BULK_TX_VI_VO,
+ [WME_AC_VO] = RSU_BULK_TX_VI_VO,
+};
+
+/* XXX hard-coded */
+#define RSU_H2C_ENDPOINT 3
+
+static const struct usb_config rsu_config[RSU_N_TRANSFER] = {
+ [RSU_BULK_RX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = RSU_RXBUFSZ,
+ .flags = {
+ .pipe_bof = 1,
+ .short_xfer_ok = 1
+ },
+ .callback = rsu_bulk_rx_callback
+ },
+ [RSU_BULK_TX_BE_BK] = {
+ .type = UE_BULK,
+ .endpoint = 0x06,
+ .direction = UE_DIR_OUT,
+ .bufsize = RSU_TXBUFSZ,
+ .flags = {
+ .ext_buffer = 1,
+ .pipe_bof = 1,
+ .force_short_xfer = 1
+ },
+ .callback = rsu_bulk_tx_callback_be_bk,
+ .timeout = RSU_TX_TIMEOUT
+ },
+ [RSU_BULK_TX_VI_VO] = {
+ .type = UE_BULK,
+ .endpoint = 0x04,
+ .direction = UE_DIR_OUT,
+ .bufsize = RSU_TXBUFSZ,
+ .flags = {
+ .ext_buffer = 1,
+ .pipe_bof = 1,
+ .force_short_xfer = 1
+ },
+ .callback = rsu_bulk_tx_callback_vi_vo,
+ .timeout = RSU_TX_TIMEOUT
+ },
+ [RSU_BULK_TX_H2C] = {
+ .type = UE_BULK,
+ .endpoint = 0x0d,
+ .direction = UE_DIR_OUT,
+ .bufsize = RSU_TXBUFSZ,
+ .flags = {
+ .ext_buffer = 1,
+ .pipe_bof = 1,
+ .short_xfer_ok = 1
+ },
+ .callback = rsu_bulk_tx_callback_h2c,
+ .timeout = RSU_TX_TIMEOUT
+ },
+};
+
+static int
+rsu_match(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+
+ if (uaa->usb_mode != USB_MODE_HOST ||
+ uaa->info.bIfaceIndex != 0 ||
+ uaa->info.bConfigIndex != 0)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(rsu_devs, sizeof(rsu_devs), uaa));
+}
+
+static int
+rsu_send_mgmt(struct ieee80211_node *ni, int type, int arg)
+{
+
+ return (ENOTSUP);
+}
+
+static void
+rsu_update_chw(struct ieee80211com *ic)
+{
+
+}
+
+/*
+ * notification from net80211 that it'd like to do A-MPDU on the given TID.
+ *
+ * Note: this actually hangs traffic at the present moment, so don't use it.
+ * The firmware debug does indiciate it's sending and establishing a TX AMPDU
+ * session, but then no traffic flows.
+ */
+static int
+rsu_ampdu_enable(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap)
+{
+#if 0
+ struct rsu_softc *sc = ni->ni_ic->ic_softc;
+ struct r92s_add_ba_req req;
+
+ /* Don't enable if it's requested or running */
+ if (IEEE80211_AMPDU_REQUESTED(tap))
+ return (0);
+ if (IEEE80211_AMPDU_RUNNING(tap))
+ return (0);
+
+ /* We've decided to send addba; so send it */
+ req.tid = htole32(tap->txa_tid);
+
+ /* Attempt net80211 state */
+ if (ieee80211_ampdu_tx_request_ext(ni, tap->txa_tid) != 1)
+ return (0);
+
+ /* Send the firmware command */
+ RSU_DPRINTF(sc, RSU_DEBUG_AMPDU, "%s: establishing AMPDU TX for TID %d\n",
+ __func__,
+ tap->txa_tid);
+
+ RSU_LOCK(sc);
+ if (rsu_fw_cmd(sc, R92S_CMD_ADDBA_REQ, &req, sizeof(req)) != 1) {
+ RSU_UNLOCK(sc);
+ /* Mark failure */
+ (void) ieee80211_ampdu_tx_request_active_ext(ni, tap->txa_tid, 0);
+ return (0);
+ }
+ RSU_UNLOCK(sc);
+
+ /* Mark success; we don't get any further notifications */
+ (void) ieee80211_ampdu_tx_request_active_ext(ni, tap->txa_tid, 1);
+#endif
+ /* Return 0, we're driving this ourselves */
+ return (0);
+}
+
+static int
+rsu_wme_update(struct ieee80211com *ic)
+{
+
+ /* Firmware handles this; not our problem */
+ return (0);
+}
+
+static int
+rsu_attach(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+ struct rsu_softc *sc = device_get_softc(self);
+ struct ieee80211com *ic = &sc->sc_ic;
+ int error;
+ uint8_t iface_index;
+ struct usb_interface *iface;
+ const char *rft;
+
+ device_set_usb_desc(self);
+ sc->sc_udev = uaa->device;
+ sc->sc_dev = self;
+ sc->sc_rx_checksum_enable = 1;
+ if (rsu_enable_11n)
+ sc->sc_ht = !! (USB_GET_DRIVER_INFO(uaa) & RSU_HT_SUPPORTED);
+
+ /* Get number of endpoints */
+ iface = usbd_get_iface(sc->sc_udev, 0);
+ sc->sc_nendpoints = iface->idesc->bNumEndpoints;
+
+ /* Endpoints are hard-coded for now, so enforce 4-endpoint only */
+ if (sc->sc_nendpoints != 4) {
+ device_printf(sc->sc_dev,
+ "the driver currently only supports 4-endpoint devices\n");
+ return (ENXIO);
+ }
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(self), MTX_NETWORK_LOCK,
+ MTX_DEF);
+ RSU_DELKEY_BMAP_LOCK_INIT(sc);
+ TIMEOUT_TASK_INIT(taskqueue_thread, &sc->calib_task, 0,
+ rsu_calib_task, sc);
+ TASK_INIT(&sc->del_key_task, 0, rsu_delete_key_pair_cb, sc);
+ TASK_INIT(&sc->tx_task, 0, rsu_tx_task, sc);
+ mbufq_init(&sc->sc_snd, ifqmaxlen);
+
+ /* Allocate Tx/Rx buffers. */
+ error = rsu_alloc_rx_list(sc);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "could not allocate Rx buffers\n");
+ goto fail_usb;
+ }
+
+ error = rsu_alloc_tx_list(sc);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "could not allocate Tx buffers\n");
+ rsu_free_rx_list(sc);
+ goto fail_usb;
+ }
+
+ iface_index = 0;
+ error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer,
+ rsu_config, RSU_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(sc->sc_dev,
+ "could not allocate USB transfers, err=%s\n",
+ usbd_errstr(error));
+ goto fail_usb;
+ }
+ RSU_LOCK(sc);
+ /* Read chip revision. */
+ sc->cut = MS(rsu_read_4(sc, R92S_PMC_FSM), R92S_PMC_FSM_CUT);
+ if (sc->cut != 3)
+ sc->cut = (sc->cut >> 1) + 1;
+ error = rsu_read_rom(sc);
+ RSU_UNLOCK(sc);
+ if (error != 0) {
+ device_printf(self, "could not read ROM\n");
+ goto fail_rom;
+ }
+
+ /* Figure out TX/RX streams */
+ switch (sc->rom[84]) {
+ case 0x0:
+ sc->sc_rftype = RTL8712_RFCONFIG_1T1R;
+ sc->sc_nrxstream = 1;
+ sc->sc_ntxstream = 1;
+ rft = "1T1R";
+ break;
+ case 0x1:
+ sc->sc_rftype = RTL8712_RFCONFIG_1T2R;
+ sc->sc_nrxstream = 2;
+ sc->sc_ntxstream = 1;
+ rft = "1T2R";
+ break;
+ case 0x2:
+ sc->sc_rftype = RTL8712_RFCONFIG_2T2R;
+ sc->sc_nrxstream = 2;
+ sc->sc_ntxstream = 2;
+ rft = "2T2R";
+ break;
+ case 0x3: /* "green" NIC */
+ sc->sc_rftype = RTL8712_RFCONFIG_1T2R;
+ sc->sc_nrxstream = 2;
+ sc->sc_ntxstream = 1;
+ rft = "1T2R ('green')";
+ break;
+ default:
+ device_printf(sc->sc_dev,
+ "%s: unknown board type (rfconfig=0x%02x)\n",
+ __func__,
+ sc->rom[84]);
+ goto fail_rom;
+ }
+
+ IEEE80211_ADDR_COPY(ic->ic_macaddr, &sc->rom[0x12]);
+ device_printf(self, "MAC/BB RTL8712 cut %d %s\n", sc->cut, rft);
+
+ ic->ic_softc = sc;
+ ic->ic_name = device_get_nameunit(self);
+ 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 */
+#if 0
+ IEEE80211_C_BGSCAN | /* Background scan. */
+#endif
+ IEEE80211_C_SHPREAMBLE | /* Short preamble supported. */
+ IEEE80211_C_WME | /* WME/QoS */
+ IEEE80211_C_SHSLOT | /* Short slot time supported. */
+ IEEE80211_C_WPA; /* WPA/RSN. */
+
+ ic->ic_cryptocaps =
+ IEEE80211_CRYPTO_WEP |
+ IEEE80211_CRYPTO_TKIP |
+ IEEE80211_CRYPTO_AES_CCM;
+
+ /* Check if HT support is present. */
+ if (sc->sc_ht) {
+ device_printf(sc->sc_dev, "%s: enabling 11n\n", __func__);
+
+ /* Enable basic HT */
+ ic->ic_htcaps = IEEE80211_HTC_HT |
+#if 0
+ IEEE80211_HTC_AMPDU |
+#endif
+ IEEE80211_HTC_AMSDU |
+ IEEE80211_HTCAP_MAXAMSDU_3839 |
+ IEEE80211_HTCAP_SMPS_OFF;
+ ic->ic_htcaps |= IEEE80211_HTCAP_CHWIDTH40;
+
+ /* set number of spatial streams */
+ ic->ic_txstream = sc->sc_ntxstream;
+ ic->ic_rxstream = sc->sc_nrxstream;
+ }
+ ic->ic_flags_ext |= IEEE80211_FEXT_SCAN_OFFLOAD;
+
+ rsu_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans,
+ ic->ic_channels);
+
+ ieee80211_ifattach(ic);
+ ic->ic_raw_xmit = rsu_raw_xmit;
+ ic->ic_scan_start = rsu_scan_start;
+ ic->ic_scan_end = rsu_scan_end;
+ ic->ic_getradiocaps = rsu_getradiocaps;
+ ic->ic_set_channel = rsu_set_channel;
+ ic->ic_scan_curchan = rsu_scan_curchan;
+ ic->ic_scan_mindwell = rsu_scan_mindwell;
+ ic->ic_vap_create = rsu_vap_create;
+ ic->ic_vap_delete = rsu_vap_delete;
+ ic->ic_update_promisc = rsu_update_promisc;
+ ic->ic_update_mcast = rsu_update_mcast;
+ ic->ic_ioctl = rsu_ioctl_net;
+ ic->ic_parent = rsu_parent;
+ ic->ic_transmit = rsu_transmit;
+ ic->ic_send_mgmt = rsu_send_mgmt;
+ ic->ic_update_chw = rsu_update_chw;
+ ic->ic_ampdu_enable = rsu_ampdu_enable;
+ ic->ic_wme.wme_update = rsu_wme_update;
+
+ ieee80211_radiotap_attach(ic, &sc->sc_txtap.wt_ihdr,
+ sizeof(sc->sc_txtap), RSU_TX_RADIOTAP_PRESENT,
+ &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap),
+ RSU_RX_RADIOTAP_PRESENT);
+
+ if (bootverbose)
+ ieee80211_announce(ic);
+
+ return (0);
+
+fail_rom:
+ usbd_transfer_unsetup(sc->sc_xfer, RSU_N_TRANSFER);
+fail_usb:
+ mtx_destroy(&sc->sc_mtx);
+ return (ENXIO);
+}
+
+static int
+rsu_detach(device_t self)
+{
+ struct rsu_softc *sc = device_get_softc(self);
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ rsu_stop(sc);
+
+ usbd_transfer_unsetup(sc->sc_xfer, RSU_N_TRANSFER);
+
+ /*
+ * Free buffers /before/ we detach from net80211, else node
+ * references to destroyed vaps will lead to a panic.
+ */
+ /* Free Tx/Rx buffers. */
+ RSU_LOCK(sc);
+ rsu_free_tx_list(sc);
+ rsu_free_rx_list(sc);
+ RSU_UNLOCK(sc);
+
+ /* Frames are freed; detach from net80211 */
+ ieee80211_ifdetach(ic);
+
+ taskqueue_drain_timeout(taskqueue_thread, &sc->calib_task);
+ taskqueue_drain(taskqueue_thread, &sc->del_key_task);
+ taskqueue_drain(taskqueue_thread, &sc->tx_task);
+
+ RSU_DELKEY_BMAP_LOCK_DESTROY(sc);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static usb_error_t
+rsu_do_request(struct rsu_softc *sc, struct usb_device_request *req,
+ void *data)
+{
+ usb_error_t err;
+ int ntries = 10;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ while (ntries--) {
+ err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx,
+ req, data, 0, NULL, 250 /* ms */);
+ if (err == 0 || err == USB_ERR_NOT_CONFIGURED)
+ break;
+ RSU_DPRINTF(sc, RSU_DEBUG_USB,
+ "Control request failed, %s (retries left: %d)\n",
+ usbd_errstr(err), ntries);
+ rsu_ms_delay(sc, 10);
+ }
+
+ return (err);
+}
+
+static struct ieee80211vap *
+rsu_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
+ enum ieee80211_opmode opmode, int flags,
+ const uint8_t bssid[IEEE80211_ADDR_LEN],
+ const uint8_t mac[IEEE80211_ADDR_LEN])
+{
+ struct rsu_softc *sc = ic->ic_softc;
+ struct rsu_vap *uvp;
+ struct ieee80211vap *vap;
+ if_t ifp;
+
+ if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */
+ return (NULL);
+
+ uvp = malloc(sizeof(struct rsu_vap), M_80211_VAP, M_WAITOK | M_ZERO);
+ vap = &uvp->vap;
+
+ if (ieee80211_vap_setup(ic, vap, name, unit, opmode,
+ flags, bssid) != 0) {
+ /* out of memory */
+ free(uvp, M_80211_VAP);
+ return (NULL);
+ }
+
+ ifp = vap->iv_ifp;
+ if_setcapabilities(ifp, IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6);
+ RSU_LOCK(sc);
+ if (sc->sc_rx_checksum_enable)
+ if_setcapenablebit(ifp, IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6, 0);
+ RSU_UNLOCK(sc);
+
+ /* override state transition machine */
+ uvp->newstate = vap->iv_newstate;
+ if (opmode == IEEE80211_M_MONITOR)
+ vap->iv_newstate = rsu_monitor_newstate;
+ else
+ vap->iv_newstate = rsu_newstate;
+ vap->iv_key_alloc = rsu_key_alloc;
+ vap->iv_key_set = rsu_key_set;
+ vap->iv_key_delete = rsu_key_delete;
+
+ /* Limits from the r92su driver */
+ vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_16;
+ vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_32K;
+
+ /* complete setup */
+ ieee80211_vap_attach(vap, ieee80211_media_change,
+ ieee80211_media_status, mac);
+ ic->ic_opmode = opmode;
+
+ return (vap);
+}
+
+static void
+rsu_vap_delete(struct ieee80211vap *vap)
+{
+ struct rsu_vap *uvp = RSU_VAP(vap);
+
+ ieee80211_vap_detach(vap);
+ free(uvp, M_80211_VAP);
+}
+
+static void
+rsu_scan_start(struct ieee80211com *ic)
+{
+ struct rsu_softc *sc = ic->ic_softc;
+ struct ieee80211_scan_state *ss = ic->ic_scan;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ int error;
+
+ /* Scanning is done by the firmware. */
+ RSU_LOCK(sc);
+ sc->sc_active_scan = !!(ss->ss_flags & IEEE80211_SCAN_ACTIVE);
+ /* XXX TODO: force awake if in network-sleep? */
+ error = rsu_site_survey(sc, ss->ss_nssid > 0 ? &ss->ss_ssid[0] : NULL);
+ RSU_UNLOCK(sc);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "could not send site survey command\n");
+ ieee80211_cancel_scan(vap);
+ }
+}
+
+static void
+rsu_scan_end(struct ieee80211com *ic)
+{
+ /* Nothing to do here. */
+}
+
+static void
+rsu_getradiocaps(struct ieee80211com *ic,
+ int maxchans, int *nchans, struct ieee80211_channel chans[])
+{
+ struct rsu_softc *sc = ic->ic_softc;
+ uint8_t bands[IEEE80211_MODE_BYTES];
+
+ /* Set supported .11b and .11g rates. */
+ memset(bands, 0, sizeof(bands));
+ setbit(bands, IEEE80211_MODE_11B);
+ setbit(bands, IEEE80211_MODE_11G);
+ if (sc->sc_ht)
+ setbit(bands, IEEE80211_MODE_11NG);
+ ieee80211_add_channels_default_2ghz(chans, maxchans, nchans,
+ bands, (ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40) ?
+ NET80211_CBW_FLAG_HT40 : 0);
+}
+
+static void
+rsu_set_channel(struct ieee80211com *ic)
+{
+ struct rsu_softc *sc = ic->ic_softc;
+
+ /*
+ * Only need to set the channel in Monitor mode. AP scanning and auth
+ * are already taken care of by their respective firmware commands.
+ */
+ if (ic->ic_opmode == IEEE80211_M_MONITOR) {
+ struct r92s_set_channel cmd;
+ int error;
+
+ cmd.channel = IEEE80211_CHAN2IEEE(ic->ic_curchan);
+
+ RSU_LOCK(sc);
+ error = rsu_fw_cmd(sc, R92S_CMD_SET_CHANNEL, &cmd,
+ sizeof(cmd));
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "%s: error %d setting channel\n", __func__,
+ error);
+ }
+ RSU_UNLOCK(sc);
+ }
+}
+
+static void
+rsu_scan_curchan(struct ieee80211_scan_state *ss, unsigned long maxdwell)
+{
+ /* Scan is done in rsu_scan_start(). */
+}
+
+/**
+ * Called by the net80211 framework to indicate
+ * the minimum dwell time has been met, terminate the scan.
+ * We don't actually terminate the scan as the firmware will notify
+ * us when it's finished and we have no way to interrupt it.
+ */
+static void
+rsu_scan_mindwell(struct ieee80211_scan_state *ss)
+{
+ /* NB: don't try to abort scan; wait for firmware to finish */
+}
+
+static void
+rsu_update_promisc(struct ieee80211com *ic)
+{
+ struct rsu_softc *sc = ic->ic_softc;
+
+ RSU_LOCK(sc);
+ if (sc->sc_running)
+ rsu_rxfilter_refresh(sc);
+ RSU_UNLOCK(sc);
+}
+
+/*
+ * The same as rtwn_get_multi_pos() / rtwn_set_multi().
+ */
+static uint8_t
+rsu_get_multi_pos(const uint8_t maddr[])
+{
+ uint64_t mask = 0x00004d101df481b4;
+ uint8_t pos = 0x27; /* initial value */
+ int i, j;
+
+ for (i = 0; i < IEEE80211_ADDR_LEN; i++)
+ for (j = (i == 0) ? 1 : 0; j < 8; j++)
+ if ((maddr[i] >> j) & 1)
+ pos ^= (mask >> (i * 8 + j - 1));
+
+ pos &= 0x3f;
+
+ return (pos);
+}
+
+static u_int
+rsu_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
+{
+ uint32_t *mfilt = arg;
+ uint8_t pos;
+
+ pos = rsu_get_multi_pos(LLADDR(sdl));
+ mfilt[pos / 32] |= (1 << (pos % 32));
+
+ return (1);
+}
+
+static void
+rsu_set_multi(struct rsu_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t mfilt[2];
+
+ RSU_ASSERT_LOCKED(sc);
+
+ /* general structure was copied from ath(4). */
+ if (ic->ic_allmulti == 0) {
+ struct ieee80211vap *vap;
+
+ /*
+ * Merge multicast addresses to form the hardware filter.
+ */
+ mfilt[0] = mfilt[1] = 0;
+ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next)
+ if_foreach_llmaddr(vap->iv_ifp, rsu_hash_maddr, &mfilt);
+ } else
+ mfilt[0] = mfilt[1] = ~0;
+
+ rsu_write_4(sc, R92S_MAR + 0, mfilt[0]);
+ rsu_write_4(sc, R92S_MAR + 4, mfilt[1]);
+
+ RSU_DPRINTF(sc, RSU_DEBUG_STATE, "%s: MC filter %08x:%08x\n",
+ __func__, mfilt[0], mfilt[1]);
+}
+
+static void
+rsu_update_mcast(struct ieee80211com *ic)
+{
+ struct rsu_softc *sc = ic->ic_softc;
+
+ RSU_LOCK(sc);
+ if (sc->sc_running)
+ rsu_set_multi(sc);
+ RSU_UNLOCK(sc);
+}
+
+static int
+rsu_alloc_list(struct rsu_softc *sc, struct rsu_data data[],
+ int ndata, int maxsz)
+{
+ int i, error;
+
+ for (i = 0; i < ndata; i++) {
+ struct rsu_data *dp = &data[i];
+ dp->sc = sc;
+ dp->m = NULL;
+ dp->buf = malloc(maxsz, M_USBDEV, M_NOWAIT);
+ if (dp->buf == NULL) {
+ device_printf(sc->sc_dev,
+ "could not allocate buffer\n");
+ error = ENOMEM;
+ goto fail;
+ }
+ dp->ni = NULL;
+ }
+
+ return (0);
+fail:
+ rsu_free_list(sc, data, ndata);
+ return (error);
+}
+
+static int
+rsu_alloc_rx_list(struct rsu_softc *sc)
+{
+ int error, i;
+
+ error = rsu_alloc_list(sc, sc->sc_rx, RSU_RX_LIST_COUNT,
+ RSU_RXBUFSZ);
+ if (error != 0)
+ return (error);
+
+ STAILQ_INIT(&sc->sc_rx_active);
+ STAILQ_INIT(&sc->sc_rx_inactive);
+
+ for (i = 0; i < RSU_RX_LIST_COUNT; i++)
+ STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i], next);
+
+ return (0);
+}
+
+static int
+rsu_alloc_tx_list(struct rsu_softc *sc)
+{
+ int error, i;
+
+ error = rsu_alloc_list(sc, sc->sc_tx, RSU_TX_LIST_COUNT,
+ RSU_TXBUFSZ);
+ if (error != 0)
+ return (error);
+
+ STAILQ_INIT(&sc->sc_tx_inactive);
+
+ for (i = 0; i != RSU_N_TRANSFER; i++) {
+ STAILQ_INIT(&sc->sc_tx_active[i]);
+ STAILQ_INIT(&sc->sc_tx_pending[i]);
+ }
+
+ for (i = 0; i < RSU_TX_LIST_COUNT; i++) {
+ STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i], next);
+ }
+
+ return (0);
+}
+
+static void
+rsu_free_tx_list(struct rsu_softc *sc)
+{
+ int i;
+
+ /* prevent further allocations from TX list(s) */
+ STAILQ_INIT(&sc->sc_tx_inactive);
+
+ for (i = 0; i != RSU_N_TRANSFER; i++) {
+ STAILQ_INIT(&sc->sc_tx_active[i]);
+ STAILQ_INIT(&sc->sc_tx_pending[i]);
+ }
+
+ rsu_free_list(sc, sc->sc_tx, RSU_TX_LIST_COUNT);
+}
+
+static void
+rsu_free_rx_list(struct rsu_softc *sc)
+{
+ /* prevent further allocations from RX list(s) */
+ STAILQ_INIT(&sc->sc_rx_inactive);
+ STAILQ_INIT(&sc->sc_rx_active);
+
+ rsu_free_list(sc, sc->sc_rx, RSU_RX_LIST_COUNT);
+}
+
+static void
+rsu_free_list(struct rsu_softc *sc, struct rsu_data data[], int ndata)
+{
+ int i;
+
+ for (i = 0; i < ndata; i++) {
+ struct rsu_data *dp = &data[i];
+
+ if (dp->buf != NULL) {
+ free(dp->buf, M_USBDEV);
+ dp->buf = NULL;
+ }
+ if (dp->ni != NULL) {
+ ieee80211_free_node(dp->ni);
+ dp->ni = NULL;
+ }
+ }
+}
+
+static struct rsu_data *
+_rsu_getbuf(struct rsu_softc *sc)
+{
+ struct rsu_data *bf;
+
+ bf = STAILQ_FIRST(&sc->sc_tx_inactive);
+ if (bf != NULL)
+ STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next);
+ else
+ bf = NULL;
+ return (bf);
+}
+
+static struct rsu_data *
+rsu_getbuf(struct rsu_softc *sc)
+{
+ struct rsu_data *bf;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ bf = _rsu_getbuf(sc);
+ if (bf == NULL) {
+ RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: no buffers\n", __func__);
+ }
+ return (bf);
+}
+
+static void
+rsu_freebuf(struct rsu_softc *sc, struct rsu_data *bf)
+{
+
+ RSU_ASSERT_LOCKED(sc);
+ STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, bf, next);
+}
+
+static int
+rsu_write_region_1(struct rsu_softc *sc, uint16_t addr, uint8_t *buf,
+ int len)
+{
+ usb_device_request_t req;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = R92S_REQ_REGS;
+ USETW(req.wValue, addr);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, len);
+
+ return (rsu_do_request(sc, &req, buf));
+}
+
+static void
+rsu_write_1(struct rsu_softc *sc, uint16_t addr, uint8_t val)
+{
+ rsu_write_region_1(sc, addr, &val, 1);
+}
+
+static void
+rsu_write_2(struct rsu_softc *sc, uint16_t addr, uint16_t val)
+{
+ val = htole16(val);
+ rsu_write_region_1(sc, addr, (uint8_t *)&val, 2);
+}
+
+static void
+rsu_write_4(struct rsu_softc *sc, uint16_t addr, uint32_t val)
+{
+ val = htole32(val);
+ rsu_write_region_1(sc, addr, (uint8_t *)&val, 4);
+}
+
+static int
+rsu_read_region_1(struct rsu_softc *sc, uint16_t addr, uint8_t *buf,
+ int len)
+{
+ usb_device_request_t req;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = R92S_REQ_REGS;
+ USETW(req.wValue, addr);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, len);
+
+ return (rsu_do_request(sc, &req, buf));
+}
+
+static uint8_t
+rsu_read_1(struct rsu_softc *sc, uint16_t addr)
+{
+ uint8_t val;
+
+ if (rsu_read_region_1(sc, addr, &val, 1) != 0)
+ return (0xff);
+ return (val);
+}
+
+static uint16_t
+rsu_read_2(struct rsu_softc *sc, uint16_t addr)
+{
+ uint16_t val;
+
+ if (rsu_read_region_1(sc, addr, (uint8_t *)&val, 2) != 0)
+ return (0xffff);
+ return (le16toh(val));
+}
+
+static uint32_t
+rsu_read_4(struct rsu_softc *sc, uint16_t addr)
+{
+ uint32_t val;
+
+ if (rsu_read_region_1(sc, addr, (uint8_t *)&val, 4) != 0)
+ return (0xffffffff);
+ return (le32toh(val));
+}
+
+static int
+rsu_fw_iocmd(struct rsu_softc *sc, uint32_t iocmd)
+{
+ int ntries;
+
+ rsu_write_4(sc, R92S_IOCMD_CTRL, iocmd);
+ rsu_ms_delay(sc, 1);
+ for (ntries = 0; ntries < 50; ntries++) {
+ if (rsu_read_4(sc, R92S_IOCMD_CTRL) == 0)
+ return (0);
+ rsu_ms_delay(sc, 1);
+ }
+ return (ETIMEDOUT);
+}
+
+static uint8_t
+rsu_efuse_read_1(struct rsu_softc *sc, uint16_t addr)
+{
+ uint32_t reg;
+ int ntries;
+
+ reg = rsu_read_4(sc, R92S_EFUSE_CTRL);
+ reg = RW(reg, R92S_EFUSE_CTRL_ADDR, addr);
+ reg &= ~R92S_EFUSE_CTRL_VALID;
+ rsu_write_4(sc, R92S_EFUSE_CTRL, reg);
+ /* Wait for read operation to complete. */
+ for (ntries = 0; ntries < 100; ntries++) {
+ reg = rsu_read_4(sc, R92S_EFUSE_CTRL);
+ if (reg & R92S_EFUSE_CTRL_VALID)
+ return (MS(reg, R92S_EFUSE_CTRL_DATA));
+ rsu_ms_delay(sc, 1);
+ }
+ device_printf(sc->sc_dev,
+ "could not read efuse byte at address 0x%x\n", addr);
+ return (0xff);
+}
+
+static int
+rsu_read_rom(struct rsu_softc *sc)
+{
+ uint8_t *rom = sc->rom;
+ uint16_t addr = 0;
+ uint32_t reg;
+ uint8_t off, msk;
+ int i;
+
+ /* Make sure that ROM type is eFuse and that autoload succeeded. */
+ reg = rsu_read_1(sc, R92S_EE_9346CR);
+ if ((reg & (R92S_9356SEL | R92S_EEPROM_EN)) != R92S_EEPROM_EN)
+ return (EIO);
+
+ /* Turn on 2.5V to prevent eFuse leakage. */
+ reg = rsu_read_1(sc, R92S_EFUSE_TEST + 3);
+ rsu_write_1(sc, R92S_EFUSE_TEST + 3, reg | 0x80);
+ rsu_ms_delay(sc, 1);
+ rsu_write_1(sc, R92S_EFUSE_TEST + 3, reg & ~0x80);
+
+ /* Read full ROM image. */
+ memset(&sc->rom, 0xff, sizeof(sc->rom));
+ while (addr < 512) {
+ reg = rsu_efuse_read_1(sc, addr);
+ if (reg == 0xff)
+ break;
+ addr++;
+ off = reg >> 4;
+ msk = reg & 0xf;
+ for (i = 0; i < 4; i++) {
+ if (msk & (1 << i))
+ continue;
+ rom[off * 8 + i * 2 + 0] =
+ rsu_efuse_read_1(sc, addr);
+ addr++;
+ rom[off * 8 + i * 2 + 1] =
+ rsu_efuse_read_1(sc, addr);
+ addr++;
+ }
+ }
+#ifdef USB_DEBUG
+ if (rsu_debug & RSU_DEBUG_RESET) {
+ /* Dump ROM content. */
+ printf("\n");
+ for (i = 0; i < sizeof(sc->rom); i++)
+ printf("%02x:", rom[i]);
+ printf("\n");
+ }
+#endif
+ return (0);
+}
+
+static int
+rsu_fw_cmd(struct rsu_softc *sc, uint8_t code, void *buf, int len)
+{
+ const uint8_t which = RSU_H2C_ENDPOINT;
+ struct rsu_data *data;
+ struct r92s_tx_desc *txd;
+ struct r92s_fw_cmd_hdr *cmd;
+ int cmdsz;
+ int xferlen;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ data = rsu_getbuf(sc);
+ if (data == NULL)
+ return (ENOMEM);
+
+ /* Blank the entire payload, just to be safe */
+ memset(data->buf, '\0', RSU_TXBUFSZ);
+
+ /* Round-up command length to a multiple of 8 bytes. */
+ /* XXX TODO: is this required? */
+ cmdsz = (len + 7) & ~7;
+
+ xferlen = sizeof(*txd) + sizeof(*cmd) + cmdsz;
+ KASSERT(xferlen <= RSU_TXBUFSZ, ("%s: invalid length", __func__));
+ memset(data->buf, 0, xferlen);
+
+ /* Setup Tx descriptor. */
+ txd = (struct r92s_tx_desc *)data->buf;
+ txd->txdw0 = htole32(
+ SM(R92S_TXDW0_OFFSET, sizeof(*txd)) |
+ SM(R92S_TXDW0_PKTLEN, sizeof(*cmd) + cmdsz) |
+ R92S_TXDW0_OWN | R92S_TXDW0_FSG | R92S_TXDW0_LSG);
+ txd->txdw1 = htole32(SM(R92S_TXDW1_QSEL, R92S_TXDW1_QSEL_H2C));
+
+ /* Setup command header. */
+ cmd = (struct r92s_fw_cmd_hdr *)&txd[1];
+ cmd->len = htole16(cmdsz);
+ cmd->code = code;
+ cmd->seq = sc->cmd_seq;
+ sc->cmd_seq = (sc->cmd_seq + 1) & 0x7f;
+
+ /* Copy command payload. */
+ memcpy(&cmd[1], buf, len);
+
+ RSU_DPRINTF(sc, RSU_DEBUG_TX | RSU_DEBUG_FWCMD,
+ "%s: Tx cmd code=0x%x len=0x%x\n",
+ __func__, code, cmdsz);
+ data->buflen = xferlen;
+ STAILQ_INSERT_TAIL(&sc->sc_tx_pending[which], data, next);
+ usbd_transfer_start(sc->sc_xfer[which]);
+
+ return (0);
+}
+
+/* ARGSUSED */
+static void
+rsu_calib_task(void *arg, int pending __unused)
+{
+ struct rsu_softc *sc = arg;
+#ifdef notyet
+ uint32_t reg;
+#endif
+
+ RSU_DPRINTF(sc, RSU_DEBUG_CALIB, "%s: running calibration task\n",
+ __func__);
+
+ RSU_LOCK(sc);
+#ifdef notyet
+ /* Read WPS PBC status. */
+ rsu_write_1(sc, R92S_MAC_PINMUX_CTRL,
+ R92S_GPIOMUX_EN | SM(R92S_GPIOSEL_GPIO, R92S_GPIOSEL_GPIO_JTAG));
+ rsu_write_1(sc, R92S_GPIO_IO_SEL,
+ rsu_read_1(sc, R92S_GPIO_IO_SEL) & ~R92S_GPIO_WPS);
+ reg = rsu_read_1(sc, R92S_GPIO_CTRL);
+ if (reg != 0xff && (reg & R92S_GPIO_WPS))
+ RSU_DPRINTF(sc, RSU_DEBUG_CALIB, "WPS PBC is pushed\n");
+#endif
+ /* Read current signal level. */
+ if (rsu_fw_iocmd(sc, 0xf4000001) == 0) {
+ sc->sc_currssi = rsu_read_4(sc, R92S_IOCMD_DATA);
+ RSU_DPRINTF(sc, RSU_DEBUG_CALIB, "%s: RSSI=%d (%d)\n",
+ __func__, sc->sc_currssi,
+ rsu_hwrssi_to_rssi(sc, sc->sc_currssi));
+ }
+ if (sc->sc_calibrating)
+ taskqueue_enqueue_timeout(taskqueue_thread, &sc->calib_task, hz);
+ RSU_UNLOCK(sc);
+}
+
+static void
+rsu_tx_task(void *arg, int pending __unused)
+{
+ struct rsu_softc *sc = arg;
+
+ RSU_LOCK(sc);
+ _rsu_start(sc);
+ RSU_UNLOCK(sc);
+}
+
+#define RSU_PWR_UNKNOWN 0x0
+#define RSU_PWR_ACTIVE 0x1
+#define RSU_PWR_OFF 0x2
+#define RSU_PWR_SLEEP 0x3
+
+/*
+ * Set the current power state.
+ *
+ * The rtlwifi code doesn't do this so aggressively; it
+ * waits for an idle period after association with
+ * no traffic before doing this.
+ *
+ * For now - it's on in all states except RUN, and
+ * in RUN it'll transition to allow sleep.
+ */
+
+struct r92s_pwr_cmd {
+ uint8_t mode;
+ uint8_t smart_ps;
+ uint8_t bcn_pass_time;
+};
+
+static int
+rsu_set_fw_power_state(struct rsu_softc *sc, int state)
+{
+ struct r92s_set_pwr_mode cmd;
+ //struct r92s_pwr_cmd cmd;
+ int error;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ /* only change state if required */
+ if (sc->sc_curpwrstate == state)
+ return (0);
+
+ memset(&cmd, 0, sizeof(cmd));
+
+ switch (state) {
+ case RSU_PWR_ACTIVE:
+ /* Force the hardware awake */
+ rsu_write_1(sc, R92S_USB_HRPWM,
+ R92S_USB_HRPWM_PS_ST_ACTIVE | R92S_USB_HRPWM_PS_ALL_ON);
+ cmd.mode = R92S_PS_MODE_ACTIVE;
+ break;
+ case RSU_PWR_SLEEP:
+ cmd.mode = R92S_PS_MODE_DTIM; /* XXX configurable? */
+ cmd.smart_ps = 1; /* XXX 2 if doing p2p */
+ cmd.bcn_pass_time = 5; /* in 100mS usb.c, linux/rtlwifi */
+ break;
+ case RSU_PWR_OFF:
+ cmd.mode = R92S_PS_MODE_RADIOOFF;
+ break;
+ default:
+ device_printf(sc->sc_dev, "%s: unknown ps mode (%d)\n",
+ __func__,
+ state);
+ return (ENXIO);
+ }
+
+ RSU_DPRINTF(sc, RSU_DEBUG_RESET,
+ "%s: setting ps mode to %d (mode %d)\n",
+ __func__, state, cmd.mode);
+ error = rsu_fw_cmd(sc, R92S_CMD_SET_PWR_MODE, &cmd, sizeof(cmd));
+ if (error == 0)
+ sc->sc_curpwrstate = state;
+
+ return (error);
+}
+
+static void
+rsu_set_led(struct rsu_softc *sc, int on)
+{
+ rsu_write_1(sc, R92S_LEDCFG,
+ (rsu_read_1(sc, R92S_LEDCFG) & 0xf0) | (!on << 3));
+}
+
+static int
+rsu_monitor_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate,
+ int arg)
+{
+ struct ieee80211com *ic = vap->iv_ic;
+ struct rsu_softc *sc = ic->ic_softc;
+ struct rsu_vap *uvp = RSU_VAP(vap);
+
+ if (vap->iv_state != nstate) {
+ IEEE80211_UNLOCK(ic);
+ RSU_LOCK(sc);
+
+ switch (nstate) {
+ case IEEE80211_S_INIT:
+ sc->sc_vap_is_running = 0;
+ rsu_set_led(sc, 0);
+ break;
+ case IEEE80211_S_RUN:
+ sc->sc_vap_is_running = 1;
+ rsu_set_led(sc, 1);
+ break;
+ default:
+ /* NOTREACHED */
+ break;
+ }
+ rsu_rxfilter_refresh(sc);
+
+ RSU_UNLOCK(sc);
+ IEEE80211_LOCK(ic);
+ }
+
+ return (uvp->newstate(vap, nstate, arg));
+}
+
+static int
+rsu_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
+{
+ struct rsu_vap *uvp = RSU_VAP(vap);
+ struct ieee80211com *ic = vap->iv_ic;
+ struct rsu_softc *sc = ic->ic_softc;
+ struct ieee80211_node *ni;
+ struct ieee80211_rateset *rs;
+ enum ieee80211_state ostate;
+ int error, startcal = 0;
+
+ ostate = vap->iv_state;
+ RSU_DPRINTF(sc, RSU_DEBUG_STATE, "%s: %s -> %s\n",
+ __func__,
+ ieee80211_state_name[ostate],
+ ieee80211_state_name[nstate]);
+
+ IEEE80211_UNLOCK(ic);
+ if (ostate == IEEE80211_S_RUN) {
+ RSU_LOCK(sc);
+ /* Stop calibration. */
+ sc->sc_calibrating = 0;
+
+ /* Pause Tx for AC queues. */
+ rsu_write_1(sc, R92S_TXPAUSE, R92S_TXPAUSE_AC);
+ usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10));
+
+ RSU_UNLOCK(sc);
+ taskqueue_drain_timeout(taskqueue_thread, &sc->calib_task);
+ taskqueue_drain(taskqueue_thread, &sc->tx_task);
+ RSU_LOCK(sc);
+ /* Disassociate from our current BSS. */
+ rsu_disconnect(sc);
+ usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(10));
+
+ /* Refresh Rx filter (may be modified by firmware). */
+ sc->sc_vap_is_running = 0;
+ rsu_rxfilter_refresh(sc);
+
+ /* Reinstall static keys. */
+ if (sc->sc_running)
+ rsu_reinit_static_keys(sc);
+ } else
+ RSU_LOCK(sc);
+ switch (nstate) {
+ case IEEE80211_S_INIT:
+ (void) rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE);
+ break;
+ case IEEE80211_S_AUTH:
+ ni = ieee80211_ref_node(vap->iv_bss);
+ (void) rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE);
+ error = rsu_join_bss(sc, ni);
+ ieee80211_free_node(ni);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "could not send join command\n");
+ }
+ break;
+ case IEEE80211_S_RUN:
+ /* Flush all AC queues. */
+ rsu_write_1(sc, R92S_TXPAUSE, 0);
+
+ ni = ieee80211_ref_node(vap->iv_bss);
+ rs = &ni->ni_rates;
+ /* Indicate highest supported rate. */
+ ieee80211_node_set_txrate_dot11rate(ni,
+ rs->rs_rates[rs->rs_nrates - 1]);
+ (void) rsu_set_fw_power_state(sc, RSU_PWR_SLEEP);
+ ieee80211_free_node(ni);
+ startcal = 1;
+ break;
+ default:
+ break;
+ }
+ if (startcal != 0) {
+ sc->sc_calibrating = 1;
+ /* Start periodic calibration. */
+ taskqueue_enqueue_timeout(taskqueue_thread, &sc->calib_task,
+ hz);
+ }
+ RSU_UNLOCK(sc);
+ IEEE80211_LOCK(ic);
+ return (uvp->newstate(vap, nstate, arg));
+}
+
+static int
+rsu_key_alloc(struct ieee80211vap *vap, struct ieee80211_key *k,
+ ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix)
+{
+ struct rsu_softc *sc = vap->iv_ic->ic_softc;
+ int is_checked = 0;
+
+ if (ieee80211_is_key_global(vap, k)) {
+ *keyix = ieee80211_crypto_get_key_wepidx(vap, k);
+ } else {
+ /* Note: assumes this is a pairwise key */
+ if (vap->iv_opmode != IEEE80211_M_STA) {
+ *keyix = 0;
+ /* TODO: obtain keyix from node id */
+ is_checked = 1;
+ k->wk_flags |= IEEE80211_KEY_SWCRYPT;
+ } else
+ *keyix = R92S_MACID_BSS;
+ }
+
+ if (!is_checked) {
+ RSU_LOCK(sc);
+ if (isset(sc->keys_bmap, *keyix)) {
+ device_printf(sc->sc_dev,
+ "%s: key slot %d is already used!\n",
+ __func__, *keyix);
+ RSU_UNLOCK(sc);
+ return (0);
+ }
+ setbit(sc->keys_bmap, *keyix);
+ RSU_UNLOCK(sc);
+ }
+
+ *rxkeyix = *keyix;
+
+ return (1);
+}
+
+static int
+rsu_process_key(struct ieee80211vap *vap, const struct ieee80211_key *k,
+ int set)
+{
+ struct rsu_softc *sc = vap->iv_ic->ic_softc;
+ int ret;
+
+ if (k->wk_flags & IEEE80211_KEY_SWCRYPT) {
+ /* Not for us. */
+ return (1);
+ }
+
+ /* Handle group keys. */
+ if (ieee80211_is_key_global(vap, k)) {
+ KASSERT(k->wk_keyix < nitems(sc->group_keys),
+ ("keyix %u > %zu\n", k->wk_keyix, nitems(sc->group_keys)));
+
+ RSU_LOCK(sc);
+ sc->group_keys[k->wk_keyix] = (set ? k : NULL);
+ if (!sc->sc_running) {
+ /* Static keys will be set during device startup. */
+ RSU_UNLOCK(sc);
+ return (1);
+ }
+
+ if (set)
+ ret = rsu_set_key_group(sc, k);
+ else
+ ret = rsu_delete_key(sc, k->wk_keyix);
+ RSU_UNLOCK(sc);
+
+ return (!ret);
+ }
+
+ if (set) {
+ /* wait for pending key removal */
+ taskqueue_drain(taskqueue_thread, &sc->del_key_task);
+
+ RSU_LOCK(sc);
+ ret = rsu_set_key_pair(sc, k);
+ RSU_UNLOCK(sc);
+ } else {
+ RSU_DELKEY_BMAP_LOCK(sc);
+ setbit(sc->free_keys_bmap, k->wk_keyix);
+ RSU_DELKEY_BMAP_UNLOCK(sc);
+
+ /* workaround ieee80211_node_delucastkey() locking */
+ taskqueue_enqueue(taskqueue_thread, &sc->del_key_task);
+ ret = 0; /* fake success */
+ }
+
+ return (!ret);
+}
+
+static int
+rsu_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k)
+{
+ return (rsu_process_key(vap, k, 1));
+}
+
+static int
+rsu_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k)
+{
+ return (rsu_process_key(vap, k, 0));
+}
+
+static int
+rsu_cam_read(struct rsu_softc *sc, uint8_t addr, uint32_t *val)
+{
+ int ntries;
+
+ rsu_write_4(sc, R92S_CAMCMD,
+ R92S_CAMCMD_POLLING | SM(R92S_CAMCMD_ADDR, addr));
+ for (ntries = 0; ntries < 10; ntries++) {
+ if (!(rsu_read_4(sc, R92S_CAMCMD) & R92S_CAMCMD_POLLING))
+ break;
+
+ usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(1));
+ }
+ if (ntries == 10) {
+ device_printf(sc->sc_dev,
+ "%s: cannot read CAM entry at address %02X\n",
+ __func__, addr);
+ return (ETIMEDOUT);
+ }
+
+ *val = rsu_read_4(sc, R92S_CAMREAD);
+
+ return (0);
+}
+
+static void
+rsu_cam_write(struct rsu_softc *sc, uint8_t addr, uint32_t data)
+{
+
+ rsu_write_4(sc, R92S_CAMWRITE, data);
+ rsu_write_4(sc, R92S_CAMCMD,
+ R92S_CAMCMD_POLLING | R92S_CAMCMD_WRITE |
+ SM(R92S_CAMCMD_ADDR, addr));
+}
+
+static int
+rsu_key_check(struct rsu_softc *sc, ieee80211_keyix keyix, int is_valid)
+{
+ uint32_t val;
+ int error, ntries;
+
+ for (ntries = 0; ntries < 20; ntries++) {
+ usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(1));
+
+ error = rsu_cam_read(sc, R92S_CAM_CTL0(keyix), &val);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "%s: cannot check key status!\n", __func__);
+ return (error);
+ }
+ if (((val & R92S_CAM_VALID) == 0) ^ is_valid)
+ break;
+ }
+ if (ntries == 20) {
+ device_printf(sc->sc_dev,
+ "%s: key %d is %s marked as valid, rejecting request\n",
+ __func__, keyix, is_valid ? "not" : "still");
+ return (EIO);
+ }
+
+ return (0);
+}
+
+/*
+ * Map net80211 cipher to RTL8712 security mode.
+ */
+static uint8_t
+rsu_crypto_mode(struct rsu_softc *sc, u_int cipher, int keylen)
+{
+ switch (cipher) {
+ case IEEE80211_CIPHER_WEP:
+ return keylen < 8 ? R92S_KEY_ALGO_WEP40 : R92S_KEY_ALGO_WEP104;
+ case IEEE80211_CIPHER_TKIP:
+ return R92S_KEY_ALGO_TKIP;
+ case IEEE80211_CIPHER_AES_CCM:
+ return R92S_KEY_ALGO_AES;
+ default:
+ device_printf(sc->sc_dev, "unknown cipher %d\n", cipher);
+ return R92S_KEY_ALGO_INVALID;
+ }
+}
+
+static int
+rsu_set_key_group(struct rsu_softc *sc, const struct ieee80211_key *k)
+{
+ struct r92s_fw_cmd_set_key key;
+ uint8_t algo;
+ int error;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ /* Map net80211 cipher to HW crypto algorithm. */
+ algo = rsu_crypto_mode(sc, k->wk_cipher->ic_cipher, k->wk_keylen);
+ if (algo == R92S_KEY_ALGO_INVALID)
+ return (EINVAL);
+
+ memset(&key, 0, sizeof(key));
+ key.algo = algo;
+ key.cam_id = k->wk_keyix;
+ key.grpkey = (k->wk_flags & IEEE80211_KEY_GROUP) != 0;
+ memcpy(key.key, k->wk_key, MIN(k->wk_keylen, sizeof(key.key)));
+
+ RSU_DPRINTF(sc, RSU_DEBUG_KEY | RSU_DEBUG_FWCMD,
+ "%s: keyix %u, group %u, algo %u/%u, flags %04X, len %u, "
+ "macaddr %s\n", __func__, key.cam_id, key.grpkey,
+ k->wk_cipher->ic_cipher, key.algo, k->wk_flags, k->wk_keylen,
+ ether_sprintf(k->wk_macaddr));
+
+ error = rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key));
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "%s: cannot send firmware command, error %d\n",
+ __func__, error);
+ return (error);
+ }
+
+ return (rsu_key_check(sc, k->wk_keyix, 1));
+}
+
+static int
+rsu_set_key_pair(struct rsu_softc *sc, const struct ieee80211_key *k)
+{
+ struct r92s_fw_cmd_set_key_mac key;
+ uint8_t algo;
+ int error;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ if (!sc->sc_running)
+ return (ESHUTDOWN);
+
+ /* Map net80211 cipher to HW crypto algorithm. */
+ algo = rsu_crypto_mode(sc, k->wk_cipher->ic_cipher, k->wk_keylen);
+ if (algo == R92S_KEY_ALGO_INVALID)
+ return (EINVAL);
+
+ memset(&key, 0, sizeof(key));
+ key.algo = algo;
+ memcpy(key.macaddr, k->wk_macaddr, sizeof(key.macaddr));
+ memcpy(key.key, k->wk_key, MIN(k->wk_keylen, sizeof(key.key)));
+
+ RSU_DPRINTF(sc, RSU_DEBUG_KEY | RSU_DEBUG_FWCMD,
+ "%s: keyix %u, algo %u/%u, flags %04X, len %u, macaddr %s\n",
+ __func__, k->wk_keyix, k->wk_cipher->ic_cipher, key.algo,
+ k->wk_flags, k->wk_keylen, ether_sprintf(key.macaddr));
+
+ error = rsu_fw_cmd(sc, R92S_CMD_SET_STA_KEY, &key, sizeof(key));
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "%s: cannot send firmware command, error %d\n",
+ __func__, error);
+ return (error);
+ }
+
+ return (rsu_key_check(sc, k->wk_keyix, 1));
+}
+
+static int
+rsu_reinit_static_keys(struct rsu_softc *sc)
+{
+ int i, error;
+
+ for (i = 0; i < nitems(sc->group_keys); i++) {
+ if (sc->group_keys[i] != NULL) {
+ error = rsu_set_key_group(sc, sc->group_keys[i]);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "%s: failed to set static key %d, "
+ "error %d\n", __func__, i, error);
+ return (error);
+ }
+ }
+ }
+
+ return (0);
+}
+
+static int
+rsu_delete_key(struct rsu_softc *sc, ieee80211_keyix keyix)
+{
+ struct r92s_fw_cmd_set_key key;
+ uint32_t val;
+ int error;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ if (!sc->sc_running)
+ return (0);
+
+ /* check if it was automatically removed by firmware */
+ error = rsu_cam_read(sc, R92S_CAM_CTL0(keyix), &val);
+ if (error == 0 && (val & R92S_CAM_VALID) == 0) {
+ RSU_DPRINTF(sc, RSU_DEBUG_KEY,
+ "%s: key %u does not exist\n", __func__, keyix);
+ clrbit(sc->keys_bmap, keyix);
+ return (0);
+ }
+
+ memset(&key, 0, sizeof(key));
+ key.cam_id = keyix;
+
+ RSU_DPRINTF(sc, RSU_DEBUG_KEY | RSU_DEBUG_FWCMD,
+ "%s: removing key %u\n", __func__, key.cam_id);
+
+ error = rsu_fw_cmd(sc, R92S_CMD_SET_KEY, &key, sizeof(key));
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "%s: cannot send firmware command, error %d\n",
+ __func__, error);
+ goto finish;
+ }
+
+ usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(5));
+
+ /*
+ * Clear 'valid' bit manually (cannot be done via firmware command).
+ * Used for key check + when firmware command cannot be sent.
+ */
+finish:
+ rsu_cam_write(sc, R92S_CAM_CTL0(keyix), 0);
+
+ clrbit(sc->keys_bmap, keyix);
+
+ return (rsu_key_check(sc, keyix, 0));
+}
+
+static void
+rsu_delete_key_pair_cb(void *arg, int pending __unused)
+{
+ struct rsu_softc *sc = arg;
+ int i;
+
+ RSU_DELKEY_BMAP_LOCK(sc);
+ for (i = IEEE80211_WEP_NKID; i < R92S_CAM_ENTRY_LIMIT; i++) {
+ if (isset(sc->free_keys_bmap, i)) {
+ RSU_DELKEY_BMAP_UNLOCK(sc);
+
+ RSU_LOCK(sc);
+ RSU_DPRINTF(sc, RSU_DEBUG_KEY,
+ "%s: calling rsu_delete_key() with keyix = %d\n",
+ __func__, i);
+ (void) rsu_delete_key(sc, i);
+ RSU_UNLOCK(sc);
+
+ RSU_DELKEY_BMAP_LOCK(sc);
+ clrbit(sc->free_keys_bmap, i);
+
+ /* bmap can be changed */
+ i = IEEE80211_WEP_NKID - 1;
+ continue;
+ }
+ }
+ RSU_DELKEY_BMAP_UNLOCK(sc);
+}
+
+static int
+rsu_site_survey(struct rsu_softc *sc, struct ieee80211_scan_ssid *ssid)
+{
+ struct r92s_fw_cmd_sitesurvey cmd;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ memset(&cmd, 0, sizeof(cmd));
+ /* TODO: passive channels? */
+ if (sc->sc_active_scan)
+ cmd.active = htole32(1);
+ cmd.limit = htole32(48);
+
+ if (ssid != NULL) {
+ sc->sc_extra_scan = 1;
+ cmd.ssidlen = htole32(ssid->len);
+ memcpy(cmd.ssid, ssid->ssid, ssid->len);
+ }
+#ifdef USB_DEBUG
+ if (rsu_debug & (RSU_DEBUG_SCAN | RSU_DEBUG_FWCMD)) {
+ device_printf(sc->sc_dev,
+ "sending site survey command, active %d",
+ le32toh(cmd.active));
+ if (ssid != NULL) {
+ printf(", ssid: ");
+ ieee80211_print_essid(cmd.ssid, le32toh(cmd.ssidlen));
+ }
+ printf("\n");
+ }
+#endif
+ return (rsu_fw_cmd(sc, R92S_CMD_SITE_SURVEY, &cmd, sizeof(cmd)));
+}
+
+static int
+rsu_join_bss(struct rsu_softc *sc, struct ieee80211_node *ni)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct ndis_wlan_bssid_ex *bss;
+ struct ndis_802_11_fixed_ies *fixed;
+ struct r92s_fw_cmd_auth auth;
+ uint8_t buf[sizeof(*bss) + 128] __aligned(4);
+ uint8_t *frm;
+ uint8_t opmode;
+ int error;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ /* Let the FW decide the opmode based on the capinfo field. */
+ opmode = NDIS802_11AUTOUNKNOWN;
+ RSU_DPRINTF(sc, RSU_DEBUG_RESET,
+ "%s: setting operating mode to %d\n",
+ __func__, opmode);
+ error = rsu_fw_cmd(sc, R92S_CMD_SET_OPMODE, &opmode, sizeof(opmode));
+ if (error != 0)
+ return (error);
+
+ memset(&auth, 0, sizeof(auth));
+ if (vap->iv_flags & IEEE80211_F_WPA) {
+ auth.mode = R92S_AUTHMODE_WPA;
+ auth.dot1x = (ni->ni_authmode == IEEE80211_AUTH_8021X);
+ } else
+ auth.mode = R92S_AUTHMODE_OPEN;
+ RSU_DPRINTF(sc, RSU_DEBUG_RESET,
+ "%s: setting auth mode to %d\n",
+ __func__, auth.mode);
+ error = rsu_fw_cmd(sc, R92S_CMD_SET_AUTH, &auth, sizeof(auth));
+ if (error != 0)
+ return (error);
+
+ memset(buf, 0, sizeof(buf));
+ bss = (struct ndis_wlan_bssid_ex *)buf;
+ IEEE80211_ADDR_COPY(bss->macaddr, ni->ni_bssid);
+ bss->ssid.ssidlen = htole32(ni->ni_esslen);
+ memcpy(bss->ssid.ssid, ni->ni_essid, ni->ni_esslen);
+ if (vap->iv_flags & (IEEE80211_F_PRIVACY | IEEE80211_F_WPA))
+ bss->privacy = htole32(1);
+ bss->rssi = htole32(ni->ni_avgrssi);
+ if (ic->ic_curmode == IEEE80211_MODE_11B)
+ bss->networktype = htole32(NDIS802_11DS);
+ else
+ bss->networktype = htole32(NDIS802_11OFDM24);
+ bss->config.len = htole32(sizeof(bss->config));
+ bss->config.bintval = htole32(ni->ni_intval);
+ bss->config.dsconfig = htole32(ieee80211_chan2ieee(ic, ni->ni_chan));
+ bss->inframode = htole32(NDIS802_11INFRASTRUCTURE);
+ /* XXX verify how this is supposed to look! */
+ memcpy(bss->supprates, ni->ni_rates.rs_rates,
+ ni->ni_rates.rs_nrates);
+ /* Write the fixed fields of the beacon frame. */
+ fixed = (struct ndis_802_11_fixed_ies *)&bss[1];
+ memcpy(&fixed->tstamp, ni->ni_tstamp.data, 8);
+ fixed->bintval = htole16(ni->ni_intval);
+ fixed->capabilities = htole16(ni->ni_capinfo);
+ /* Write IEs to be included in the association request. */
+ frm = (uint8_t *)&fixed[1];
+ frm = ieee80211_add_rsn(frm, vap);
+ frm = ieee80211_add_wpa(frm, vap);
+ frm = ieee80211_add_qos(frm, ni);
+ if ((ic->ic_flags & IEEE80211_F_WME) &&
+ (ni->ni_ies.wme_ie != NULL))
+ frm = ieee80211_add_wme_info(frm, &ic->ic_wme, ni);
+ if (ni->ni_flags & IEEE80211_NODE_HT) {
+ frm = ieee80211_add_htcap(frm, ni);
+ frm = ieee80211_add_htinfo(frm, ni);
+ }
+ bss->ieslen = htole32(frm - (uint8_t *)fixed);
+ bss->len = htole32(((frm - buf) + 3) & ~3);
+ RSU_DPRINTF(sc, RSU_DEBUG_RESET | RSU_DEBUG_FWCMD,
+ "%s: sending join bss command to %s chan %d\n",
+ __func__,
+ ether_sprintf(bss->macaddr), le32toh(bss->config.dsconfig));
+ return (rsu_fw_cmd(sc, R92S_CMD_JOIN_BSS, buf, sizeof(buf)));
+}
+
+static int
+rsu_disconnect(struct rsu_softc *sc)
+{
+ uint32_t zero = 0; /* :-) */
+
+ /* Disassociate from our current BSS. */
+ RSU_DPRINTF(sc, RSU_DEBUG_STATE | RSU_DEBUG_FWCMD,
+ "%s: sending disconnect command\n", __func__);
+ return (rsu_fw_cmd(sc, R92S_CMD_DISCONNECT, &zero, sizeof(zero)));
+}
+
+/*
+ * Map the hardware provided RSSI value to a signal level.
+ * For the most part it's just something we divide by and cap
+ * so it doesn't overflow the representation by net80211.
+ */
+static int
+rsu_hwrssi_to_rssi(struct rsu_softc *sc, int hw_rssi)
+{
+ int v;
+
+ if (hw_rssi == 0)
+ return (0);
+ v = hw_rssi >> 4;
+ if (v > 80)
+ v = 80;
+ return (v);
+}
+
+CTASSERT(MCLBYTES > sizeof(struct ieee80211_frame));
+
+static void
+rsu_event_survey(struct rsu_softc *sc, uint8_t *buf, int len)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_frame *wh;
+ struct ndis_wlan_bssid_ex *bss;
+ struct ieee80211_rx_stats rxs;
+ struct mbuf *m;
+ uint32_t ieslen;
+ uint32_t pktlen;
+
+ if (__predict_false(len < sizeof(*bss)))
+ return;
+ bss = (struct ndis_wlan_bssid_ex *)buf;
+ ieslen = le32toh(bss->ieslen);
+ /* range check length of information element */
+ if (__predict_false(ieslen > (uint32_t)(len - sizeof(*bss))))
+ return;
+
+ RSU_DPRINTF(sc, RSU_DEBUG_SCAN,
+ "%s: found BSS %s: len=%d chan=%d inframode=%d "
+ "networktype=%d privacy=%d, RSSI=%d\n",
+ __func__,
+ ether_sprintf(bss->macaddr), ieslen,
+ le32toh(bss->config.dsconfig), le32toh(bss->inframode),
+ le32toh(bss->networktype), le32toh(bss->privacy),
+ le32toh(bss->rssi));
+
+ /* Build a fake beacon frame to let net80211 do all the parsing. */
+ /* XXX TODO: just call the new scan API methods! */
+ if (__predict_false(ieslen > (size_t)(MCLBYTES - sizeof(*wh))))
+ return;
+ pktlen = sizeof(*wh) + ieslen;
+ m = m_get2(pktlen, M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (__predict_false(m == NULL))
+ return;
+ wh = mtod(m, struct ieee80211_frame *);
+ wh->i_fc[0] = IEEE80211_FC0_VERSION_0 | IEEE80211_FC0_TYPE_MGT |
+ IEEE80211_FC0_SUBTYPE_BEACON;
+ wh->i_fc[1] = IEEE80211_FC1_DIR_NODS;
+ USETW(wh->i_dur, 0);
+ IEEE80211_ADDR_COPY(wh->i_addr1, ieee80211broadcastaddr);
+ IEEE80211_ADDR_COPY(wh->i_addr2, bss->macaddr);
+ IEEE80211_ADDR_COPY(wh->i_addr3, bss->macaddr);
+ *(uint16_t *)wh->i_seq = 0;
+ memcpy(&wh[1], (uint8_t *)&bss[1], ieslen);
+
+ /* Finalize mbuf. */
+ m->m_pkthdr.len = m->m_len = pktlen;
+
+ /* Set channel flags for input path */
+ bzero(&rxs, sizeof(rxs));
+ rxs.r_flags |= IEEE80211_R_IEEE | IEEE80211_R_FREQ;
+ rxs.r_flags |= IEEE80211_R_BAND;
+ rxs.r_flags |= IEEE80211_R_NF | IEEE80211_R_RSSI;
+ rxs.c_ieee = le32toh(bss->config.dsconfig);
+ rxs.c_freq = ieee80211_ieee2mhz(rxs.c_ieee, IEEE80211_CHAN_2GHZ);
+ rxs.c_band = IEEE80211_CHAN_2GHZ;
+ /* This is a number from 0..100; so let's just divide it down a bit */
+ rxs.c_rssi = le32toh(bss->rssi) / 2;
+ rxs.c_nf = -96;
+ if (ieee80211_add_rx_params(m, &rxs) == 0)
+ return;
+
+ /* XXX avoid a LOR */
+ RSU_UNLOCK(sc);
+ ieee80211_input_mimo_all(ic, m);
+ RSU_LOCK(sc);
+}
+
+static void
+rsu_event_join_bss(struct rsu_softc *sc, uint8_t *buf, int len)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ struct ieee80211_node *ni = vap->iv_bss;
+ struct r92s_event_join_bss *rsp;
+ uint32_t tmp;
+ int res;
+
+ if (__predict_false(len < sizeof(*rsp)))
+ return;
+ rsp = (struct r92s_event_join_bss *)buf;
+ res = (int)le32toh(rsp->join_res);
+
+ RSU_DPRINTF(sc, RSU_DEBUG_STATE | RSU_DEBUG_FWCMD,
+ "%s: Rx join BSS event len=%d res=%d\n",
+ __func__, len, res);
+
+ /*
+ * XXX Don't do this; there's likely a better way to tell
+ * the caller we failed.
+ */
+ if (res <= 0) {
+ RSU_UNLOCK(sc);
+ ieee80211_new_state(vap, IEEE80211_S_SCAN, -1);
+ RSU_LOCK(sc);
+ return;
+ }
+
+ tmp = le32toh(rsp->associd);
+ if (tmp >= vap->iv_max_aid) {
+ RSU_DPRINTF(sc, RSU_DEBUG_ANY, "Assoc ID overflow\n");
+ tmp = 1;
+ }
+ RSU_DPRINTF(sc, RSU_DEBUG_STATE | RSU_DEBUG_FWCMD,
+ "%s: associated with %s associd=%d\n",
+ __func__, ether_sprintf(rsp->bss.macaddr), tmp);
+ /* XXX is this required? What's the top two bits for again? */
+ ni->ni_associd = tmp | 0xc000;
+
+ /* Refresh Rx filter (was changed by firmware). */
+ sc->sc_vap_is_running = 1;
+ rsu_rxfilter_refresh(sc);
+
+ RSU_UNLOCK(sc);
+ ieee80211_new_state(vap, IEEE80211_S_RUN,
+ IEEE80211_FC0_SUBTYPE_ASSOC_RESP);
+ RSU_LOCK(sc);
+}
+
+static void
+rsu_event_addba_req_report(struct rsu_softc *sc, uint8_t *buf, int len)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ struct r92s_add_ba_event *ba = (void *) buf;
+ struct ieee80211_node *ni;
+
+ if (len < sizeof(*ba)) {
+ device_printf(sc->sc_dev, "%s: short read (%d)\n", __func__, len);
+ return;
+ }
+
+ if (vap == NULL)
+ return;
+
+ RSU_DPRINTF(sc, RSU_DEBUG_AMPDU, "%s: mac=%s, tid=%d, ssn=%d\n",
+ __func__,
+ ether_sprintf(ba->mac_addr),
+ (int) ba->tid,
+ (int) le16toh(ba->ssn));
+
+ /* XXX do node lookup; this is STA specific */
+
+ ni = ieee80211_ref_node(vap->iv_bss);
+ ieee80211_ampdu_rx_start_ext(ni, ba->tid, le16toh(ba->ssn) >> 4, 32);
+ ieee80211_free_node(ni);
+}
+
+static void
+rsu_rx_event(struct rsu_softc *sc, uint8_t code, uint8_t *buf, int len)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+
+ RSU_DPRINTF(sc, RSU_DEBUG_RX | RSU_DEBUG_FWCMD,
+ "%s: Rx event code=%d len=%d\n", __func__, code, len);
+ switch (code) {
+ case R92S_EVT_SURVEY:
+ rsu_event_survey(sc, buf, len);
+ break;
+ case R92S_EVT_SURVEY_DONE:
+ RSU_DPRINTF(sc, RSU_DEBUG_SCAN,
+ "%s: %s scan done, found %d BSS\n",
+ __func__, sc->sc_extra_scan ? "direct" : "broadcast",
+ le32toh(*(uint32_t *)buf));
+ if (sc->sc_extra_scan == 1) {
+ /* Send broadcast probe request. */
+ sc->sc_extra_scan = 0;
+ if (vap != NULL && rsu_site_survey(sc, NULL) != 0) {
+ RSU_UNLOCK(sc);
+ ieee80211_cancel_scan(vap);
+ RSU_LOCK(sc);
+ }
+ break;
+ }
+ if (vap != NULL) {
+ RSU_UNLOCK(sc);
+ ieee80211_scan_done(vap);
+ RSU_LOCK(sc);
+ }
+ break;
+ case R92S_EVT_JOIN_BSS:
+ if (vap->iv_state == IEEE80211_S_AUTH)
+ rsu_event_join_bss(sc, buf, len);
+ break;
+ case R92S_EVT_DEL_STA:
+ RSU_DPRINTF(sc, RSU_DEBUG_FWCMD | RSU_DEBUG_STATE,
+ "%s: disassociated from %s\n", __func__,
+ ether_sprintf(buf));
+ if (vap->iv_state == IEEE80211_S_RUN &&
+ IEEE80211_ADDR_EQ(vap->iv_bss->ni_bssid, buf)) {
+ RSU_UNLOCK(sc);
+ ieee80211_new_state(vap, IEEE80211_S_SCAN, -1);
+ RSU_LOCK(sc);
+ }
+ break;
+ case R92S_EVT_WPS_PBC:
+ RSU_DPRINTF(sc, RSU_DEBUG_RX | RSU_DEBUG_FWCMD,
+ "%s: WPS PBC pushed.\n", __func__);
+ break;
+ case R92S_EVT_FWDBG:
+ buf[60] = '\0';
+ RSU_DPRINTF(sc, RSU_DEBUG_FWDBG, "FWDBG: %s\n", (char *)buf);
+ break;
+ case R92S_EVT_ADDBA_REQ_REPORT:
+ rsu_event_addba_req_report(sc, buf, len);
+ break;
+ default:
+ device_printf(sc->sc_dev, "%s: unhandled code (%d)\n", __func__, code);
+ break;
+ }
+}
+
+static void
+rsu_rx_multi_event(struct rsu_softc *sc, uint8_t *buf, int len)
+{
+ struct r92s_fw_cmd_hdr *cmd;
+ int cmdsz;
+
+ RSU_DPRINTF(sc, RSU_DEBUG_RX, "%s: Rx events len=%d\n", __func__, len);
+
+ /* Skip Rx status. */
+ buf += sizeof(struct r92s_rx_stat);
+ len -= sizeof(struct r92s_rx_stat);
+
+ /* Process all events. */
+ for (;;) {
+ /* Check that command header fits. */
+ if (__predict_false(len < sizeof(*cmd)))
+ break;
+ cmd = (struct r92s_fw_cmd_hdr *)buf;
+ /* Check that command payload fits. */
+ cmdsz = le16toh(cmd->len);
+ if (__predict_false(len < sizeof(*cmd) + cmdsz))
+ break;
+
+ /* Process firmware event. */
+ rsu_rx_event(sc, cmd->code, (uint8_t *)&cmd[1], cmdsz);
+
+ if (!(cmd->seq & R92S_FW_CMD_MORE))
+ break;
+ buf += sizeof(*cmd) + cmdsz;
+ len -= sizeof(*cmd) + cmdsz;
+ }
+}
+
+static int8_t
+rsu_get_rssi(struct rsu_softc *sc, int rate, void *physt)
+{
+ static const int8_t cckoff[] = { 14, -2, -20, -40 };
+ struct r92s_rx_phystat *phy;
+ struct r92s_rx_cck *cck;
+ uint8_t rpt;
+ int8_t rssi;
+
+ if (rate <= 3) {
+ cck = (struct r92s_rx_cck *)physt;
+ rpt = (cck->agc_rpt >> 6) & 0x3;
+ rssi = cck->agc_rpt & 0x3e;
+ rssi = cckoff[rpt] - rssi;
+ } else { /* OFDM/HT. */
+ phy = (struct r92s_rx_phystat *)physt;
+ rssi = ((le32toh(phy->phydw1) >> 1) & 0x7f) - 106;
+ }
+ return (rssi);
+}
+
+static struct mbuf *
+rsu_rx_copy_to_mbuf(struct rsu_softc *sc, struct r92s_rx_stat *stat,
+ int totlen)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct mbuf *m;
+ uint32_t rxdw0;
+ int pktlen;
+
+ rxdw0 = le32toh(stat->rxdw0);
+ if (__predict_false(rxdw0 & (R92S_RXDW0_CRCERR | R92S_RXDW0_ICVERR))) {
+ RSU_DPRINTF(sc, RSU_DEBUG_RX,
+ "%s: RX flags error (%s)\n", __func__,
+ rxdw0 & R92S_RXDW0_CRCERR ? "CRC" : "ICV");
+ goto fail;
+ }
+
+ pktlen = MS(rxdw0, R92S_RXDW0_PKTLEN);
+ if (__predict_false(pktlen < sizeof (struct ieee80211_frame_ack))) {
+ RSU_DPRINTF(sc, RSU_DEBUG_RX,
+ "%s: frame is too short: %d\n", __func__, pktlen);
+ goto fail;
+ }
+
+ m = m_get2(totlen, M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (__predict_false(m == NULL)) {
+ device_printf(sc->sc_dev,
+ "%s: could not allocate RX mbuf, totlen %d\n",
+ __func__, totlen);
+ goto fail;
+ }
+
+ /* Finalize mbuf. */
+ memcpy(mtod(m, uint8_t *), (uint8_t *)stat, totlen);
+ m->m_pkthdr.len = m->m_len = totlen;
+
+ return (m);
+fail:
+ counter_u64_add(ic->ic_ierrors, 1);
+ return (NULL);
+}
+
+static uint32_t
+rsu_get_tsf_low(struct rsu_softc *sc)
+{
+ return (rsu_read_4(sc, R92S_TSFTR));
+}
+
+static uint32_t
+rsu_get_tsf_high(struct rsu_softc *sc)
+{
+ return (rsu_read_4(sc, R92S_TSFTR + 4));
+}
+
+static struct ieee80211_node *
+rsu_rx_frame(struct rsu_softc *sc, struct mbuf *m)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_frame_min *wh;
+ struct ieee80211_rx_stats rxs;
+ struct r92s_rx_stat *stat;
+ uint32_t rxdw0, rxdw3;
+ uint8_t cipher, rate;
+ int infosz;
+ int rssi;
+
+ stat = mtod(m, struct r92s_rx_stat *);
+ rxdw0 = le32toh(stat->rxdw0);
+ rxdw3 = le32toh(stat->rxdw3);
+
+ rate = MS(rxdw3, R92S_RXDW3_RATE);
+ cipher = MS(rxdw0, R92S_RXDW0_CIPHER);
+ infosz = MS(rxdw0, R92S_RXDW0_INFOSZ) * 8;
+
+ /* Get RSSI from PHY status descriptor if present. */
+ if (infosz != 0 && (rxdw0 & R92S_RXDW0_PHYST))
+ rssi = rsu_get_rssi(sc, rate, &stat[1]);
+ else {
+ /* Cheat and get the last calibrated RSSI */
+ rssi = rsu_hwrssi_to_rssi(sc, sc->sc_currssi);
+ }
+
+ /* Hardware does Rx TCP checksum offload. */
+ /*
+ * This flag can be set for some other
+ * (e.g., EAPOL) frame types, so don't rely on it.
+ */
+ if (rxdw3 & R92S_RXDW3_TCPCHKVALID) {
+ RSU_DPRINTF(sc, RSU_DEBUG_RX,
+ "%s: TCP/IP checksums: %schecked / %schecked\n",
+ __func__,
+ (rxdw3 & R92S_RXDW3_TCPCHKRPT) ? "" : "not ",
+ (rxdw3 & R92S_RXDW3_IPCHKRPT) ? "" : "not ");
+
+ /*
+ * 'IP header checksum valid' bit will not be set if
+ * the frame was not checked / has incorrect checksum /
+ * does not have checksum (IPv6).
+ *
+ * NB: if DF bit is not set then frame will not be checked.
+ */
+ if (rxdw3 & R92S_RXDW3_IPCHKRPT) {
+ m->m_pkthdr.csum_flags = CSUM_IP_CHECKED;
+ m->m_pkthdr.csum_flags |= CSUM_IP_VALID;
+ }
+
+ /*
+ * This is independent of the above check.
+ */
+ if (rxdw3 & R92S_RXDW3_TCPCHKRPT) {
+ m->m_pkthdr.csum_flags |= CSUM_DATA_VALID;
+ m->m_pkthdr.csum_flags |= CSUM_PSEUDO_HDR;
+ m->m_pkthdr.csum_data = 0xffff;
+ }
+ }
+
+ /* RX flags */
+
+ /* Set channel flags for input path */
+ bzero(&rxs, sizeof(rxs));
+
+ /* normal RSSI */
+ rxs.r_flags |= IEEE80211_R_NF | IEEE80211_R_RSSI;
+ rxs.c_rssi = rssi;
+ rxs.c_nf = -96;
+
+ /* Rate */
+ if (rate < 12) {
+ rxs.c_rate = ridx2rate[rate];
+ if (RSU_RATE_IS_CCK(rate))
+ rxs.c_pktflags |= IEEE80211_RX_F_CCK;
+ else
+ rxs.c_pktflags |= IEEE80211_RX_F_OFDM;
+ } else {
+ rxs.c_rate = IEEE80211_RATE_MCS | (rate - 12);
+ rxs.c_pktflags |= IEEE80211_RX_F_HT;
+ }
+
+ if (ieee80211_radiotap_active(ic)) {
+ struct rsu_rx_radiotap_header *tap = &sc->sc_rxtap;
+
+ /* Map HW rate index to 802.11 rate. */
+ tap->wr_flags = 0; /* TODO */
+ tap->wr_tsft = rsu_get_tsf_high(sc);
+ if (le32toh(stat->tsf_low) > rsu_get_tsf_low(sc))
+ tap->wr_tsft--;
+ tap->wr_tsft = (uint64_t)htole32(tap->wr_tsft) << 32;
+ tap->wr_tsft += stat->tsf_low;
+
+ tap->wr_rate = rxs.c_rate;
+ tap->wr_dbm_antsignal = rssi;
+ };
+
+ (void) ieee80211_add_rx_params(m, &rxs);
+
+ /* Drop descriptor. */
+ m_adj(m, sizeof(*stat) + infosz);
+ wh = mtod(m, struct ieee80211_frame_min *);
+ if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) &&
+ cipher != R92S_KEY_ALGO_NONE) {
+ m->m_flags |= M_WEP;
+ }
+
+ RSU_DPRINTF(sc, RSU_DEBUG_RX,
+ "%s: Rx frame len %d, rate %d, infosz %d\n",
+ __func__, m->m_len, rate, infosz);
+
+ if (m->m_len >= sizeof(*wh))
+ return (ieee80211_find_rxnode(ic, wh));
+
+ return (NULL);
+}
+
+static struct mbuf *
+rsu_rx_multi_frame(struct rsu_softc *sc, uint8_t *buf, int len)
+{
+ struct r92s_rx_stat *stat;
+ uint32_t rxdw0;
+ int totlen, pktlen, infosz, npkts;
+ struct mbuf *m, *m0 = NULL, *prevm = NULL;
+
+ /*
+ * don't pass packets to the ieee80211 framework if the driver isn't
+ * RUNNING.
+ */
+ if (!sc->sc_running)
+ return (NULL);
+
+ /* Get the number of encapsulated frames. */
+ stat = (struct r92s_rx_stat *)buf;
+ npkts = MS(le32toh(stat->rxdw2), R92S_RXDW2_PKTCNT);
+ RSU_DPRINTF(sc, RSU_DEBUG_RX,
+ "%s: Rx %d frames in one chunk\n", __func__, npkts);
+
+ /* Process all of them. */
+ while (npkts-- > 0) {
+ if (__predict_false(len < sizeof(*stat)))
+ break;
+ stat = (struct r92s_rx_stat *)buf;
+ rxdw0 = le32toh(stat->rxdw0);
+
+ pktlen = MS(rxdw0, R92S_RXDW0_PKTLEN);
+ if (__predict_false(pktlen == 0))
+ break;
+
+ infosz = MS(rxdw0, R92S_RXDW0_INFOSZ) * 8;
+
+ /* Make sure everything fits in xfer. */
+ totlen = sizeof(*stat) + infosz + pktlen;
+ if (__predict_false(totlen > len))
+ break;
+
+ /* Process 802.11 frame. */
+ m = rsu_rx_copy_to_mbuf(sc, stat, totlen);
+ if (m0 == NULL)
+ m0 = m;
+ if (prevm == NULL)
+ prevm = m;
+ else {
+ prevm->m_next = m;
+ prevm = m;
+ }
+ /* Next chunk is 128-byte aligned. */
+ totlen = (totlen + 127) & ~127;
+ buf += totlen;
+ len -= totlen;
+ }
+
+ return (m0);
+}
+
+static struct mbuf *
+rsu_rxeof(struct usb_xfer *xfer, struct rsu_data *data)
+{
+ struct rsu_softc *sc = data->sc;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct r92s_rx_stat *stat;
+ int len;
+
+ usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+ if (__predict_false(len < sizeof(*stat))) {
+ RSU_DPRINTF(sc, RSU_DEBUG_RX, "xfer too short %d\n", len);
+ counter_u64_add(ic->ic_ierrors, 1);
+ return (NULL);
+ }
+ /* Determine if it is a firmware C2H event or an 802.11 frame. */
+ stat = (struct r92s_rx_stat *)data->buf;
+ if ((le32toh(stat->rxdw1) & 0x1ff) == 0x1ff) {
+ rsu_rx_multi_event(sc, data->buf, len);
+ /* No packets to process. */
+ return (NULL);
+ } else
+ return (rsu_rx_multi_frame(sc, data->buf, len));
+}
+
+static void
+rsu_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct rsu_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_node *ni;
+ struct mbuf *m = NULL, *next;
+ struct rsu_data *data;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ data = STAILQ_FIRST(&sc->sc_rx_active);
+ if (data == NULL)
+ goto tr_setup;
+ STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next);
+ m = rsu_rxeof(xfer, data);
+ STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ data = STAILQ_FIRST(&sc->sc_rx_inactive);
+ if (data == NULL) {
+ KASSERT(m == NULL, ("mbuf isn't NULL"));
+ return;
+ }
+ STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next);
+ STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next);
+ usbd_xfer_set_frame_data(xfer, 0, data->buf,
+ usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ /*
+ * To avoid LOR we should unlock our private mutex here to call
+ * ieee80211_input() because here is at the end of a USB
+ * callback and safe to unlock.
+ */
+ while (m != NULL) {
+ next = m->m_next;
+ m->m_next = NULL;
+
+ ni = rsu_rx_frame(sc, m);
+ RSU_UNLOCK(sc);
+
+ if (ni != NULL) {
+ if (ni->ni_flags & IEEE80211_NODE_HT)
+ m->m_flags |= M_AMPDU;
+ (void)ieee80211_input_mimo(ni, m);
+ ieee80211_free_node(ni);
+ } else
+ (void)ieee80211_input_mimo_all(ic, m);
+
+ RSU_LOCK(sc);
+ m = next;
+ }
+ break;
+ default:
+ /* needs it to the inactive queue due to a error. */
+ data = STAILQ_FIRST(&sc->sc_rx_active);
+ if (data != NULL) {
+ STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next);
+ STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next);
+ }
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto tr_setup;
+ }
+ break;
+ }
+
+}
+
+static void
+rsu_txeof(struct usb_xfer *xfer, struct rsu_data *data)
+{
+#ifdef USB_DEBUG
+ struct rsu_softc *sc = usbd_xfer_softc(xfer);
+#endif
+
+ RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: called; data=%p\n",
+ __func__,
+ data);
+
+ if (data->m) {
+ /* XXX status? */
+ ieee80211_tx_complete(data->ni, data->m, 0);
+ data->m = NULL;
+ data->ni = NULL;
+ }
+}
+
+static void
+rsu_bulk_tx_callback_sub(struct usb_xfer *xfer, usb_error_t error,
+ uint8_t which)
+{
+ struct rsu_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct rsu_data *data;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ data = STAILQ_FIRST(&sc->sc_tx_active[which]);
+ if (data == NULL)
+ goto tr_setup;
+ RSU_DPRINTF(sc, RSU_DEBUG_TXDONE, "%s: transfer done %p\n",
+ __func__, data);
+ STAILQ_REMOVE_HEAD(&sc->sc_tx_active[which], next);
+ rsu_txeof(xfer, data);
+ rsu_freebuf(sc, data);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ data = STAILQ_FIRST(&sc->sc_tx_pending[which]);
+ if (data == NULL) {
+ RSU_DPRINTF(sc, RSU_DEBUG_TXDONE,
+ "%s: empty pending queue sc %p\n", __func__, sc);
+ return;
+ }
+ STAILQ_REMOVE_HEAD(&sc->sc_tx_pending[which], next);
+ STAILQ_INSERT_TAIL(&sc->sc_tx_active[which], data, next);
+ usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen);
+ RSU_DPRINTF(sc, RSU_DEBUG_TXDONE,
+ "%s: submitting transfer %p\n",
+ __func__,
+ data);
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ data = STAILQ_FIRST(&sc->sc_tx_active[which]);
+ if (data != NULL) {
+ STAILQ_REMOVE_HEAD(&sc->sc_tx_active[which], next);
+ rsu_txeof(xfer, data);
+ rsu_freebuf(sc, data);
+ }
+ counter_u64_add(ic->ic_oerrors, 1);
+
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+
+ /*
+ * XXX TODO: if the queue is low, flush out FF TX frames.
+ * Remember to unlock the driver for now; net80211 doesn't
+ * defer it for us.
+ */
+}
+
+static void
+rsu_bulk_tx_callback_be_bk(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct rsu_softc *sc = usbd_xfer_softc(xfer);
+
+ rsu_bulk_tx_callback_sub(xfer, error, RSU_BULK_TX_BE_BK);
+
+ /* This kicks the TX taskqueue */
+ rsu_start(sc);
+}
+
+static void
+rsu_bulk_tx_callback_vi_vo(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct rsu_softc *sc = usbd_xfer_softc(xfer);
+
+ rsu_bulk_tx_callback_sub(xfer, error, RSU_BULK_TX_VI_VO);
+
+ /* This kicks the TX taskqueue */
+ rsu_start(sc);
+}
+
+static void
+rsu_bulk_tx_callback_h2c(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct rsu_softc *sc = usbd_xfer_softc(xfer);
+
+ rsu_bulk_tx_callback_sub(xfer, error, RSU_BULK_TX_H2C);
+
+ /* This kicks the TX taskqueue */
+ rsu_start(sc);
+}
+
+/*
+ * Transmit the given frame.
+ *
+ * This doesn't free the node or mbuf upon failure.
+ */
+static int
+rsu_tx_start(struct rsu_softc *sc, struct ieee80211_node *ni,
+ struct mbuf *m0, struct rsu_data *data)
+{
+ const struct ieee80211_txparam *tp = ni->ni_txparms;
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct ieee80211_frame *wh;
+ struct ieee80211_key *k = NULL;
+ struct r92s_tx_desc *txd;
+ uint8_t rate, ridx, type, cipher, qos;
+ int prio = 0;
+ uint8_t which;
+ int hasqos;
+ int ismcast;
+ int xferlen;
+ int qid;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ wh = mtod(m0, struct ieee80211_frame *);
+ type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
+ ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1);
+
+ RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: data=%p, m=%p\n",
+ __func__, data, m0);
+
+ /* Choose a TX rate index. */
+ if (type == IEEE80211_FC0_TYPE_MGT ||
+ type == IEEE80211_FC0_TYPE_CTL ||
+ (m0->m_flags & M_EAPOL) != 0)
+ rate = tp->mgmtrate;
+ else if (ismcast)
+ rate = tp->mcastrate;
+ else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE)
+ rate = tp->ucastrate;
+ else
+ rate = 0;
+
+ if (rate != 0)
+ ridx = rate2ridx(rate);
+
+ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
+ 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 */
+ return (ENOBUFS);
+ }
+ wh = mtod(m0, struct ieee80211_frame *);
+ }
+ /* If we have QoS then use it */
+ /* XXX TODO: mbuf WME/PRI versus TID? */
+ if (IEEE80211_QOS_HAS_SEQ(wh)) {
+ /* Has QoS */
+ prio = M_WME_GETAC(m0);
+ which = rsu_wme_ac_xfer_map[prio];
+ hasqos = 1;
+ qos = ((const struct ieee80211_qosframe *)wh)->i_qos[0];
+ } else {
+ /* Non-QoS TID */
+ /* XXX TODO: tid=0 for non-qos TID? */
+ which = rsu_wme_ac_xfer_map[WME_AC_BE];
+ hasqos = 0;
+ prio = 0;
+ qos = 0;
+ }
+
+ qid = rsu_ac2qid[prio];
+#if 0
+ switch (type) {
+ case IEEE80211_FC0_TYPE_CTL:
+ case IEEE80211_FC0_TYPE_MGT:
+ which = rsu_wme_ac_xfer_map[WME_AC_VO];
+ break;
+ default:
+ which = rsu_wme_ac_xfer_map[M_WME_GETAC(m0)];
+ break;
+ }
+ hasqos = 0;
+#endif
+
+ RSU_DPRINTF(sc, RSU_DEBUG_TX, "%s: pri=%d, which=%d, hasqos=%d\n",
+ __func__,
+ prio,
+ which,
+ hasqos);
+
+ /* Fill Tx descriptor. */
+ txd = (struct r92s_tx_desc *)data->buf;
+ memset(txd, 0, sizeof(*txd));
+
+ txd->txdw0 |= htole32(
+ SM(R92S_TXDW0_PKTLEN, m0->m_pkthdr.len) |
+ SM(R92S_TXDW0_OFFSET, sizeof(*txd)) |
+ R92S_TXDW0_OWN | R92S_TXDW0_FSG | R92S_TXDW0_LSG);
+
+ txd->txdw1 |= htole32(
+ SM(R92S_TXDW1_MACID, R92S_MACID_BSS) | SM(R92S_TXDW1_QSEL, qid));
+ if (!hasqos)
+ txd->txdw1 |= htole32(R92S_TXDW1_NONQOS);
+ if (k != NULL && !(k->wk_flags & IEEE80211_KEY_SWENCRYPT)) {
+ switch (k->wk_cipher->ic_cipher) {
+ case IEEE80211_CIPHER_WEP:
+ cipher = R92S_TXDW1_CIPHER_WEP;
+ break;
+ case IEEE80211_CIPHER_TKIP:
+ cipher = R92S_TXDW1_CIPHER_TKIP;
+ break;
+ case IEEE80211_CIPHER_AES_CCM:
+ cipher = R92S_TXDW1_CIPHER_AES;
+ break;
+ default:
+ cipher = R92S_TXDW1_CIPHER_NONE;
+ }
+ txd->txdw1 |= htole32(
+ SM(R92S_TXDW1_CIPHER, cipher) |
+ SM(R92S_TXDW1_KEYIDX, k->wk_keyix));
+ }
+ /* XXX todo: set AGGEN bit if appropriate? */
+ txd->txdw2 |= htole32(R92S_TXDW2_BK);
+ if (ismcast)
+ txd->txdw2 |= htole32(R92S_TXDW2_BMCAST);
+
+ if (!ismcast && (!qos || (qos & IEEE80211_QOS_ACKPOLICY) !=
+ IEEE80211_QOS_ACKPOLICY_NOACK)) {
+ txd->txdw2 |= htole32(R92S_TXDW2_RTY_LMT_ENA);
+ txd->txdw2 |= htole32(SM(R92S_TXDW2_RTY_LMT, tp->maxretry));
+ }
+
+ /* Force mgmt / mcast / ucast rate if needed. */
+ if (rate != 0) {
+ /* Data rate fallback limit (max). */
+ txd->txdw5 |= htole32(SM(R92S_TXDW5_DATARATE_FB_LMT, 0x1f));
+ txd->txdw5 |= htole32(SM(R92S_TXDW5_DATARATE, ridx));
+ txd->txdw4 |= htole32(R92S_TXDW4_DRVRATE);
+ }
+
+ /*
+ * Firmware will use and increment the sequence number for the
+ * specified priority.
+ */
+ txd->txdw3 |= htole32(SM(R92S_TXDW3_SEQ, prio));
+
+ if (ieee80211_radiotap_active_vap(vap)) {
+ struct rsu_tx_radiotap_header *tap = &sc->sc_txtap;
+
+ tap->wt_flags = 0;
+ ieee80211_radiotap_tx(vap, m0);
+ }
+
+ xferlen = sizeof(*txd) + m0->m_pkthdr.len;
+ KASSERT(xferlen <= RSU_TXBUFSZ, ("%s: invalid length", __func__));
+ m_copydata(m0, 0, m0->m_pkthdr.len, (caddr_t)&txd[1]);
+
+ data->buflen = xferlen;
+ data->ni = ni;
+ data->m = m0;
+ STAILQ_INSERT_TAIL(&sc->sc_tx_pending[which], data, next);
+
+ /* start transfer, if any */
+ usbd_transfer_start(sc->sc_xfer[which]);
+ return (0);
+}
+
+static int
+rsu_transmit(struct ieee80211com *ic, struct mbuf *m)
+{
+ struct rsu_softc *sc = ic->ic_softc;
+ int error;
+
+ RSU_LOCK(sc);
+ if (!sc->sc_running) {
+ RSU_UNLOCK(sc);
+ return (ENXIO);
+ }
+
+ /*
+ * XXX TODO: ensure that we treat 'm' as a list of frames
+ * to transmit!
+ */
+ error = mbufq_enqueue(&sc->sc_snd, m);
+ if (error) {
+ RSU_DPRINTF(sc, RSU_DEBUG_TX,
+ "%s: mbufq_enable: failed (%d)\n",
+ __func__,
+ error);
+ RSU_UNLOCK(sc);
+ return (error);
+ }
+ RSU_UNLOCK(sc);
+
+ /* This kicks the TX taskqueue */
+ rsu_start(sc);
+
+ return (0);
+}
+
+static void
+rsu_drain_mbufq(struct rsu_softc *sc)
+{
+ struct mbuf *m;
+ struct ieee80211_node *ni;
+
+ RSU_ASSERT_LOCKED(sc);
+ while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
+ ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
+ m->m_pkthdr.rcvif = NULL;
+ ieee80211_free_node(ni);
+ m_freem(m);
+ }
+}
+
+static void
+_rsu_start(struct rsu_softc *sc)
+{
+ struct ieee80211_node *ni;
+ struct rsu_data *bf;
+ struct mbuf *m;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
+ bf = rsu_getbuf(sc);
+ if (bf == NULL) {
+ RSU_DPRINTF(sc, RSU_DEBUG_TX,
+ "%s: failed to get buffer\n", __func__);
+ mbufq_prepend(&sc->sc_snd, m);
+ break;
+ }
+
+ ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
+ m->m_pkthdr.rcvif = NULL;
+
+ if (rsu_tx_start(sc, ni, m, bf) != 0) {
+ RSU_DPRINTF(sc, RSU_DEBUG_TX,
+ "%s: failed to transmit\n", __func__);
+ if_inc_counter(ni->ni_vap->iv_ifp,
+ IFCOUNTER_OERRORS, 1);
+ rsu_freebuf(sc, bf);
+ ieee80211_free_node(ni);
+ m_freem(m);
+ break;
+ }
+ }
+}
+
+static void
+rsu_start(struct rsu_softc *sc)
+{
+
+ taskqueue_enqueue(taskqueue_thread, &sc->tx_task);
+}
+
+static int
+rsu_ioctl_net(struct ieee80211com *ic, u_long cmd, void *data)
+{
+ struct rsu_softc *sc = ic->ic_softc;
+ struct ifreq *ifr = (struct ifreq *)data;
+ int error;
+
+ error = 0;
+ switch (cmd) {
+ case SIOCSIFCAP:
+ {
+ struct ieee80211vap *vap;
+ int rxmask;
+
+ rxmask = ifr->ifr_reqcap & (IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6);
+
+ RSU_LOCK(sc);
+ /* Both RXCSUM bits must be set (or unset). */
+ if (sc->sc_rx_checksum_enable &&
+ rxmask != (IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6)) {
+ rxmask = 0;
+ sc->sc_rx_checksum_enable = 0;
+ rsu_rxfilter_set(sc, R92S_RCR_TCP_OFFLD_EN, 0);
+ } else if (!sc->sc_rx_checksum_enable && rxmask != 0) {
+ rxmask = IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6;
+ sc->sc_rx_checksum_enable = 1;
+ rsu_rxfilter_set(sc, 0, R92S_RCR_TCP_OFFLD_EN);
+ } else {
+ /* Nothing to do. */
+ RSU_UNLOCK(sc);
+ break;
+ }
+ RSU_UNLOCK(sc);
+
+ IEEE80211_LOCK(ic); /* XXX */
+ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
+ if_t ifp = vap->iv_ifp;
+
+ if_setcapenablebit(ifp, 0,
+ IFCAP_RXCSUM | IFCAP_RXCSUM_IPV6);
+ if_setcapenablebit(ifp, rxmask, 0);
+ }
+ IEEE80211_UNLOCK(ic);
+ break;
+ }
+ default:
+ error = ENOTTY; /* for net80211 */
+ break;
+ }
+
+ return (error);
+}
+
+static void
+rsu_parent(struct ieee80211com *ic)
+{
+ struct rsu_softc *sc = ic->ic_softc;
+
+ if (ic->ic_nrunning > 0) {
+ if (rsu_init(sc) == 0)
+ ieee80211_start_all(ic);
+ else {
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ if (vap != NULL)
+ ieee80211_stop(vap);
+ }
+ } else
+ rsu_stop(sc);
+}
+
+/*
+ * Power on sequence for A-cut adapters.
+ */
+static void
+rsu_power_on_acut(struct rsu_softc *sc)
+{
+ uint32_t reg;
+
+ rsu_write_1(sc, R92S_SPS0_CTRL + 1, 0x53);
+ rsu_write_1(sc, R92S_SPS0_CTRL + 0, 0x57);
+
+ /* Enable AFE macro block's bandgap and Mbias. */
+ rsu_write_1(sc, R92S_AFE_MISC,
+ rsu_read_1(sc, R92S_AFE_MISC) |
+ R92S_AFE_MISC_BGEN | R92S_AFE_MISC_MBEN);
+ /* Enable LDOA15 block. */
+ rsu_write_1(sc, R92S_LDOA15_CTRL,
+ rsu_read_1(sc, R92S_LDOA15_CTRL) | R92S_LDA15_EN);
+
+ rsu_write_1(sc, R92S_SPS1_CTRL,
+ rsu_read_1(sc, R92S_SPS1_CTRL) | R92S_SPS1_LDEN);
+ rsu_ms_delay(sc, 2000);
+ /* Enable switch regulator block. */
+ rsu_write_1(sc, R92S_SPS1_CTRL,
+ rsu_read_1(sc, R92S_SPS1_CTRL) | R92S_SPS1_SWEN);
+
+ rsu_write_4(sc, R92S_SPS1_CTRL, 0x00a7b267);
+
+ rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1,
+ rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) | 0x08);
+
+ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1,
+ rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x20);
+
+ rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1,
+ rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) & ~0x90);
+
+ /* Enable AFE clock. */
+ rsu_write_1(sc, R92S_AFE_XTAL_CTRL + 1,
+ rsu_read_1(sc, R92S_AFE_XTAL_CTRL + 1) & ~0x04);
+ /* Enable AFE PLL macro block. */
+ rsu_write_1(sc, R92S_AFE_PLL_CTRL,
+ rsu_read_1(sc, R92S_AFE_PLL_CTRL) | 0x11);
+ /* Attach AFE PLL to MACTOP/BB. */
+ rsu_write_1(sc, R92S_SYS_ISO_CTRL,
+ rsu_read_1(sc, R92S_SYS_ISO_CTRL) & ~0x11);
+
+ /* Switch to 40MHz clock instead of 80MHz. */
+ rsu_write_2(sc, R92S_SYS_CLKR,
+ rsu_read_2(sc, R92S_SYS_CLKR) & ~R92S_SYS_CLKSEL);
+
+ /* Enable MAC clock. */
+ rsu_write_2(sc, R92S_SYS_CLKR,
+ rsu_read_2(sc, R92S_SYS_CLKR) |
+ R92S_MAC_CLK_EN | R92S_SYS_CLK_EN);
+
+ rsu_write_1(sc, R92S_PMC_FSM, 0x02);
+
+ /* Enable digital core and IOREG R/W. */
+ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1,
+ rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x08);
+
+ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1,
+ rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x80);
+
+ /* Switch the control path to firmware. */
+ reg = rsu_read_2(sc, R92S_SYS_CLKR);
+ reg = (reg & ~R92S_SWHW_SEL) | R92S_FWHW_SEL;
+ rsu_write_2(sc, R92S_SYS_CLKR, reg);
+
+ rsu_write_2(sc, R92S_CR, 0x37fc);
+
+ /* Fix USB RX FIFO issue. */
+ rsu_write_1(sc, 0xfe5c,
+ rsu_read_1(sc, 0xfe5c) | 0x80);
+ rsu_write_1(sc, 0x00ab,
+ rsu_read_1(sc, 0x00ab) | 0xc0);
+
+ rsu_write_1(sc, R92S_SYS_CLKR,
+ rsu_read_1(sc, R92S_SYS_CLKR) & ~R92S_SYS_CPU_CLKSEL);
+}
+
+/*
+ * Power on sequence for B-cut and C-cut adapters.
+ */
+static void
+rsu_power_on_bcut(struct rsu_softc *sc)
+{
+ uint32_t reg;
+ int ntries;
+
+ /* Prevent eFuse leakage. */
+ rsu_write_1(sc, 0x37, 0xb0);
+ rsu_ms_delay(sc, 10);
+ rsu_write_1(sc, 0x37, 0x30);
+
+ /* Switch the control path to hardware. */
+ reg = rsu_read_2(sc, R92S_SYS_CLKR);
+ if (reg & R92S_FWHW_SEL) {
+ rsu_write_2(sc, R92S_SYS_CLKR,
+ reg & ~(R92S_SWHW_SEL | R92S_FWHW_SEL));
+ }
+ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1,
+ rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) & ~0x8c);
+ rsu_ms_delay(sc, 1);
+
+ rsu_write_1(sc, R92S_SPS0_CTRL + 1, 0x53);
+ rsu_write_1(sc, R92S_SPS0_CTRL + 0, 0x57);
+
+ reg = rsu_read_1(sc, R92S_AFE_MISC);
+ rsu_write_1(sc, R92S_AFE_MISC, reg | R92S_AFE_MISC_BGEN);
+ rsu_write_1(sc, R92S_AFE_MISC, reg | R92S_AFE_MISC_BGEN |
+ R92S_AFE_MISC_MBEN | R92S_AFE_MISC_I32_EN);
+
+ /* Enable PLL. */
+ rsu_write_1(sc, R92S_LDOA15_CTRL,
+ rsu_read_1(sc, R92S_LDOA15_CTRL) | R92S_LDA15_EN);
+
+ rsu_write_1(sc, R92S_LDOV12D_CTRL,
+ rsu_read_1(sc, R92S_LDOV12D_CTRL) | R92S_LDV12_EN);
+
+ rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1,
+ rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) | 0x08);
+
+ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1,
+ rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x20);
+
+ /* Support 64KB IMEM. */
+ rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1,
+ rsu_read_1(sc, R92S_SYS_ISO_CTRL + 1) & ~0x97);
+
+ /* Enable AFE clock. */
+ rsu_write_1(sc, R92S_AFE_XTAL_CTRL + 1,
+ rsu_read_1(sc, R92S_AFE_XTAL_CTRL + 1) & ~0x04);
+ /* Enable AFE PLL macro block. */
+ reg = rsu_read_1(sc, R92S_AFE_PLL_CTRL);
+ rsu_write_1(sc, R92S_AFE_PLL_CTRL, reg | 0x11);
+ rsu_ms_delay(sc, 1);
+ rsu_write_1(sc, R92S_AFE_PLL_CTRL, reg | 0x51);
+ rsu_ms_delay(sc, 1);
+ rsu_write_1(sc, R92S_AFE_PLL_CTRL, reg | 0x11);
+ rsu_ms_delay(sc, 1);
+
+ /* Attach AFE PLL to MACTOP/BB. */
+ rsu_write_1(sc, R92S_SYS_ISO_CTRL,
+ rsu_read_1(sc, R92S_SYS_ISO_CTRL) & ~0x11);
+
+ /* Switch to 40MHz clock. */
+ rsu_write_1(sc, R92S_SYS_CLKR, 0x00);
+ /* Disable CPU clock and 80MHz SSC. */
+ rsu_write_1(sc, R92S_SYS_CLKR,
+ rsu_read_1(sc, R92S_SYS_CLKR) | 0xa0);
+ /* Enable MAC clock. */
+ rsu_write_2(sc, R92S_SYS_CLKR,
+ rsu_read_2(sc, R92S_SYS_CLKR) |
+ R92S_MAC_CLK_EN | R92S_SYS_CLK_EN);
+
+ rsu_write_1(sc, R92S_PMC_FSM, 0x02);
+
+ /* Enable digital core and IOREG R/W. */
+ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1,
+ rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x08);
+
+ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1,
+ rsu_read_1(sc, R92S_SYS_FUNC_EN + 1) | 0x80);
+
+ /* Switch the control path to firmware. */
+ reg = rsu_read_2(sc, R92S_SYS_CLKR);
+ reg = (reg & ~R92S_SWHW_SEL) | R92S_FWHW_SEL;
+ rsu_write_2(sc, R92S_SYS_CLKR, reg);
+
+ rsu_write_2(sc, R92S_CR, 0x37fc);
+
+ /* Fix USB RX FIFO issue. */
+ rsu_write_1(sc, 0xfe5c,
+ rsu_read_1(sc, 0xfe5c) | 0x80);
+
+ rsu_write_1(sc, R92S_SYS_CLKR,
+ rsu_read_1(sc, R92S_SYS_CLKR) & ~R92S_SYS_CPU_CLKSEL);
+
+ rsu_write_1(sc, 0xfe1c, 0x80);
+
+ /* Make sure TxDMA is ready to download firmware. */
+ for (ntries = 0; ntries < 20; ntries++) {
+ reg = rsu_read_1(sc, R92S_TCR);
+ if ((reg & (R92S_TCR_IMEM_CHK_RPT | R92S_TCR_EMEM_CHK_RPT)) ==
+ (R92S_TCR_IMEM_CHK_RPT | R92S_TCR_EMEM_CHK_RPT))
+ break;
+ rsu_ms_delay(sc, 1);
+ }
+ if (ntries == 20) {
+ RSU_DPRINTF(sc, RSU_DEBUG_RESET | RSU_DEBUG_TX,
+ "%s: TxDMA is not ready\n",
+ __func__);
+ /* Reset TxDMA. */
+ reg = rsu_read_1(sc, R92S_CR);
+ rsu_write_1(sc, R92S_CR, reg & ~R92S_CR_TXDMA_EN);
+ rsu_ms_delay(sc, 1);
+ rsu_write_1(sc, R92S_CR, reg | R92S_CR_TXDMA_EN);
+ }
+}
+
+static void
+rsu_power_off(struct rsu_softc *sc)
+{
+ /* Turn RF off. */
+ rsu_write_1(sc, R92S_RF_CTRL, 0x00);
+ rsu_ms_delay(sc, 5);
+
+ /* Turn MAC off. */
+ /* Switch control path. */
+ rsu_write_1(sc, R92S_SYS_CLKR + 1, 0x38);
+ /* Reset MACTOP. */
+ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, 0x70);
+ rsu_write_1(sc, R92S_PMC_FSM, 0x06);
+ rsu_write_1(sc, R92S_SYS_ISO_CTRL + 0, 0xf9);
+ rsu_write_1(sc, R92S_SYS_ISO_CTRL + 1, 0xe8);
+
+ /* Disable AFE PLL. */
+ rsu_write_1(sc, R92S_AFE_PLL_CTRL, 0x00);
+ /* Disable A15V. */
+ rsu_write_1(sc, R92S_LDOA15_CTRL, 0x54);
+ /* Disable eFuse 1.2V. */
+ rsu_write_1(sc, R92S_SYS_FUNC_EN + 1, 0x50);
+ rsu_write_1(sc, R92S_LDOV12D_CTRL, 0x24);
+ /* Enable AFE macro block's bandgap and Mbias. */
+ rsu_write_1(sc, R92S_AFE_MISC, 0x30);
+ /* Disable 1.6V LDO. */
+ rsu_write_1(sc, R92S_SPS0_CTRL + 0, 0x56);
+ rsu_write_1(sc, R92S_SPS0_CTRL + 1, 0x43);
+
+ /* Firmware - tell it to switch things off */
+ (void) rsu_set_fw_power_state(sc, RSU_PWR_OFF);
+}
+
+static int
+rsu_fw_loadsection(struct rsu_softc *sc, const uint8_t *buf, int len)
+{
+ const uint8_t which = rsu_wme_ac_xfer_map[WME_AC_VO];
+ struct rsu_data *data;
+ struct r92s_tx_desc *txd;
+ int mlen;
+
+ while (len > 0) {
+ data = rsu_getbuf(sc);
+ if (data == NULL)
+ return (ENOMEM);
+ txd = (struct r92s_tx_desc *)data->buf;
+ memset(txd, 0, sizeof(*txd));
+ if (len <= RSU_TXBUFSZ - sizeof(*txd)) {
+ /* Last chunk. */
+ txd->txdw0 |= htole32(R92S_TXDW0_LINIP);
+ mlen = len;
+ } else
+ mlen = RSU_TXBUFSZ - sizeof(*txd);
+ txd->txdw0 |= htole32(SM(R92S_TXDW0_PKTLEN, mlen));
+ memcpy(&txd[1], buf, mlen);
+ data->buflen = sizeof(*txd) + mlen;
+ RSU_DPRINTF(sc, RSU_DEBUG_TX | RSU_DEBUG_FW | RSU_DEBUG_RESET,
+ "%s: starting transfer %p\n",
+ __func__, data);
+ STAILQ_INSERT_TAIL(&sc->sc_tx_pending[which], data, next);
+ buf += mlen;
+ len -= mlen;
+ }
+ usbd_transfer_start(sc->sc_xfer[which]);
+ return (0);
+}
+
+CTASSERT(sizeof(size_t) >= sizeof(uint32_t));
+
+static int
+rsu_load_firmware(struct rsu_softc *sc)
+{
+ const struct r92s_fw_hdr *hdr;
+ struct r92s_fw_priv dmem;
+ struct ieee80211com *ic = &sc->sc_ic;
+ const uint8_t *imem, *emem;
+ uint32_t imemsz, ememsz;
+ const struct firmware *fw;
+ size_t size;
+ uint32_t reg;
+ int ntries, error;
+
+ if (rsu_read_1(sc, R92S_TCR) & R92S_TCR_FWRDY) {
+ RSU_DPRINTF(sc, RSU_DEBUG_ANY,
+ "%s: Firmware already loaded\n",
+ __func__);
+ return (0);
+ }
+
+ RSU_UNLOCK(sc);
+ /* Read firmware image from the filesystem. */
+ if ((fw = firmware_get("rsu-rtl8712fw")) == NULL) {
+ device_printf(sc->sc_dev,
+ "%s: failed load firmware of file rsu-rtl8712fw\n",
+ __func__);
+ RSU_LOCK(sc);
+ return (ENXIO);
+ }
+ RSU_LOCK(sc);
+ size = fw->datasize;
+ if (size < sizeof(*hdr)) {
+ device_printf(sc->sc_dev, "firmware too short\n");
+ error = EINVAL;
+ goto fail;
+ }
+ hdr = (const struct r92s_fw_hdr *)fw->data;
+ if (hdr->signature != htole16(0x8712) &&
+ hdr->signature != htole16(0x8192)) {
+ device_printf(sc->sc_dev,
+ "invalid firmware signature 0x%x\n",
+ le16toh(hdr->signature));
+ error = EINVAL;
+ goto fail;
+ }
+ RSU_DPRINTF(sc, RSU_DEBUG_FW, "FW V%d %02x-%02x %02x:%02x\n",
+ le16toh(hdr->version), hdr->month, hdr->day, hdr->hour,
+ hdr->minute);
+
+ /* Make sure that driver and firmware are in sync. */
+ if (hdr->privsz != htole32(sizeof(dmem))) {
+ device_printf(sc->sc_dev, "unsupported firmware image\n");
+ error = EINVAL;
+ goto fail;
+ }
+ /* Get FW sections sizes. */
+ imemsz = le32toh(hdr->imemsz);
+ ememsz = le32toh(hdr->sramsz);
+ /* Check that all FW sections fit in image. */
+ if (imemsz > (size_t)(size - sizeof(*hdr)) ||
+ ememsz > (size_t)(size - sizeof(*hdr) - imemsz)) {
+ device_printf(sc->sc_dev, "firmware too short\n");
+ error = EINVAL;
+ goto fail;
+ }
+ imem = (const uint8_t *)&hdr[1];
+ emem = imem + imemsz;
+
+ /* Load IMEM section. */
+ error = rsu_fw_loadsection(sc, imem, imemsz);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "could not load firmware section %s\n", "IMEM");
+ goto fail;
+ }
+ /* Wait for load to complete. */
+ for (ntries = 0; ntries != 50; ntries++) {
+ rsu_ms_delay(sc, 10);
+ reg = rsu_read_1(sc, R92S_TCR);
+ if (reg & R92S_TCR_IMEM_CODE_DONE)
+ break;
+ }
+ if (ntries == 50) {
+ device_printf(sc->sc_dev, "timeout waiting for IMEM transfer\n");
+ error = ETIMEDOUT;
+ goto fail;
+ }
+ /* Load EMEM section. */
+ error = rsu_fw_loadsection(sc, emem, ememsz);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "could not load firmware section %s\n", "EMEM");
+ goto fail;
+ }
+ /* Wait for load to complete. */
+ for (ntries = 0; ntries != 50; ntries++) {
+ rsu_ms_delay(sc, 10);
+ reg = rsu_read_2(sc, R92S_TCR);
+ if (reg & R92S_TCR_EMEM_CODE_DONE)
+ break;
+ }
+ if (ntries == 50) {
+ device_printf(sc->sc_dev, "timeout waiting for EMEM transfer\n");
+ error = ETIMEDOUT;
+ goto fail;
+ }
+ /* Enable CPU. */
+ rsu_write_1(sc, R92S_SYS_CLKR,
+ rsu_read_1(sc, R92S_SYS_CLKR) | R92S_SYS_CPU_CLKSEL);
+ if (!(rsu_read_1(sc, R92S_SYS_CLKR) & R92S_SYS_CPU_CLKSEL)) {
+ device_printf(sc->sc_dev, "could not enable system clock\n");
+ error = EIO;
+ goto fail;
+ }
+ rsu_write_2(sc, R92S_SYS_FUNC_EN,
+ rsu_read_2(sc, R92S_SYS_FUNC_EN) | R92S_FEN_CPUEN);
+ if (!(rsu_read_2(sc, R92S_SYS_FUNC_EN) & R92S_FEN_CPUEN)) {
+ device_printf(sc->sc_dev,
+ "could not enable microcontroller\n");
+ error = EIO;
+ goto fail;
+ }
+ /* Wait for CPU to initialize. */
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (rsu_read_1(sc, R92S_TCR) & R92S_TCR_IMEM_RDY)
+ break;
+ rsu_ms_delay(sc, 1);
+ }
+ if (ntries == 100) {
+ device_printf(sc->sc_dev,
+ "timeout waiting for microcontroller\n");
+ error = ETIMEDOUT;
+ goto fail;
+ }
+
+ /* Update DMEM section before loading. */
+ memset(&dmem, 0, sizeof(dmem));
+ dmem.hci_sel = R92S_HCI_SEL_USB | R92S_HCI_SEL_8172;
+ dmem.nendpoints = sc->sc_nendpoints;
+ dmem.chip_version = sc->cut;
+ dmem.rf_config = sc->sc_rftype;
+ dmem.vcs_type = R92S_VCS_TYPE_AUTO;
+ dmem.vcs_mode = R92S_VCS_MODE_RTS_CTS;
+ dmem.turbo_mode = 0;
+ dmem.bw40_en = !! (ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40);
+ dmem.amsdu2ampdu_en = !! (sc->sc_ht);
+ dmem.ampdu_en = !! (sc->sc_ht);
+ dmem.agg_offload = !! (sc->sc_ht);
+ dmem.qos_en = 1;
+ dmem.ps_offload = 1;
+ dmem.lowpower_mode = 1; /* XXX TODO: configurable? */
+ /* Load DMEM section. */
+ error = rsu_fw_loadsection(sc, (uint8_t *)&dmem, sizeof(dmem));
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "could not load firmware section %s\n", "DMEM");
+ goto fail;
+ }
+ /* Wait for load to complete. */
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (rsu_read_1(sc, R92S_TCR) & R92S_TCR_DMEM_CODE_DONE)
+ break;
+ rsu_ms_delay(sc, 1);
+ }
+ if (ntries == 100) {
+ device_printf(sc->sc_dev, "timeout waiting for %s transfer\n",
+ "DMEM");
+ error = ETIMEDOUT;
+ goto fail;
+ }
+ /* Wait for firmware readiness. */
+ for (ntries = 0; ntries < 60; ntries++) {
+ if (!(rsu_read_1(sc, R92S_TCR) & R92S_TCR_FWRDY))
+ break;
+ rsu_ms_delay(sc, 1);
+ }
+ if (ntries == 60) {
+ device_printf(sc->sc_dev,
+ "timeout waiting for firmware readiness\n");
+ error = ETIMEDOUT;
+ goto fail;
+ }
+ fail:
+ firmware_put(fw, FIRMWARE_UNLOAD);
+ return (error);
+}
+
+static int
+rsu_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
+ const struct ieee80211_bpf_params *params)
+{
+ struct ieee80211com *ic = ni->ni_ic;
+ struct rsu_softc *sc = ic->ic_softc;
+ struct rsu_data *bf;
+
+ /* prevent management frames from being sent if we're not ready */
+ if (!sc->sc_running) {
+ m_freem(m);
+ return (ENETDOWN);
+ }
+ RSU_LOCK(sc);
+ bf = rsu_getbuf(sc);
+ if (bf == NULL) {
+ m_freem(m);
+ RSU_UNLOCK(sc);
+ return (ENOBUFS);
+ }
+ if (rsu_tx_start(sc, ni, m, bf) != 0) {
+ m_freem(m);
+ rsu_freebuf(sc, bf);
+ RSU_UNLOCK(sc);
+ return (EIO);
+ }
+ RSU_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+rsu_rxfilter_init(struct rsu_softc *sc)
+{
+ uint32_t reg;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ /* Setup multicast filter. */
+ rsu_set_multi(sc);
+
+ /* Adjust Rx filter. */
+ reg = rsu_read_4(sc, R92S_RCR);
+ reg &= ~R92S_RCR_AICV;
+ reg |= R92S_RCR_APP_PHYSTS;
+ if (sc->sc_rx_checksum_enable)
+ reg |= R92S_RCR_TCP_OFFLD_EN;
+ rsu_write_4(sc, R92S_RCR, reg);
+
+ /* Update dynamic Rx filter parts. */
+ rsu_rxfilter_refresh(sc);
+}
+
+static void
+rsu_rxfilter_set(struct rsu_softc *sc, uint32_t clear, uint32_t set)
+{
+ /* NB: firmware can touch this register too. */
+ rsu_write_4(sc, R92S_RCR,
+ (rsu_read_4(sc, R92S_RCR) & ~clear) | set);
+}
+
+static void
+rsu_rxfilter_refresh(struct rsu_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t mask_all, mask_min;
+
+ RSU_ASSERT_LOCKED(sc);
+
+ /* NB: RCR_AMF / RXFLTMAP_MGT are used by firmware. */
+ mask_all = R92S_RCR_ACF | R92S_RCR_AAP;
+ mask_min = R92S_RCR_APM;
+ if (sc->sc_vap_is_running)
+ mask_min |= R92S_RCR_CBSSID;
+ else
+ mask_all |= R92S_RCR_ADF;
+
+ if (ic->ic_opmode == IEEE80211_M_MONITOR) {
+ uint16_t rxfltmap;
+ if (sc->sc_vap_is_running)
+ rxfltmap = 0;
+ else
+ rxfltmap = R92S_RXFLTMAP_MGT_DEF;
+ rsu_write_2(sc, R92S_RXFLTMAP_MGT, rxfltmap);
+ }
+
+ if (ic->ic_promisc == 0 && ic->ic_opmode != IEEE80211_M_MONITOR)
+ rsu_rxfilter_set(sc, mask_all, mask_min);
+ else
+ rsu_rxfilter_set(sc, mask_min, mask_all);
+}
+
+static int
+rsu_init(struct rsu_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ uint8_t macaddr[IEEE80211_ADDR_LEN];
+ int error;
+ int i;
+
+ RSU_LOCK(sc);
+
+ if (sc->sc_running) {
+ RSU_UNLOCK(sc);
+ return (0);
+ }
+
+ /* Ensure the mbuf queue is drained */
+ rsu_drain_mbufq(sc);
+
+ /* Reset power management state. */
+ rsu_write_1(sc, R92S_USB_HRPWM, 0);
+
+ /* Power on adapter. */
+ if (sc->cut == 1)
+ rsu_power_on_acut(sc);
+ else
+ rsu_power_on_bcut(sc);
+
+ /* Load firmware. */
+ error = rsu_load_firmware(sc);
+ if (error != 0)
+ goto fail;
+
+ rsu_write_4(sc, R92S_CR,
+ rsu_read_4(sc, R92S_CR) & ~0xff000000);
+
+ /* Use 128 bytes pages. */
+ rsu_write_1(sc, 0x00b5,
+ rsu_read_1(sc, 0x00b5) | 0x01);
+ /* Enable USB Rx aggregation. */
+ rsu_write_1(sc, 0x00bd,
+ rsu_read_1(sc, 0x00bd) | 0x80);
+ /* Set USB Rx aggregation threshold. */
+ rsu_write_1(sc, 0x00d9, 0x01);
+ /* Set USB Rx aggregation timeout (1.7ms/4). */
+ rsu_write_1(sc, 0xfe5b, 0x04);
+ /* Fix USB Rx FIFO issue. */
+ rsu_write_1(sc, 0xfe5c,
+ rsu_read_1(sc, 0xfe5c) | 0x80);
+
+ /* Set MAC address. */
+ IEEE80211_ADDR_COPY(macaddr, vap ? vap->iv_myaddr : ic->ic_macaddr);
+ rsu_write_region_1(sc, R92S_MACID, macaddr, IEEE80211_ADDR_LEN);
+
+ /* It really takes 1.5 seconds for the firmware to boot: */
+ usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(2000));
+
+ RSU_DPRINTF(sc, RSU_DEBUG_RESET, "%s: setting MAC address to %s\n",
+ __func__,
+ ether_sprintf(macaddr));
+ error = rsu_fw_cmd(sc, R92S_CMD_SET_MAC_ADDRESS, macaddr,
+ IEEE80211_ADDR_LEN);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "could not set MAC address\n");
+ goto fail;
+ }
+
+ /* Initialize Rx filter. */
+ rsu_rxfilter_init(sc);
+
+ /* Set PS mode fully active */
+ error = rsu_set_fw_power_state(sc, RSU_PWR_ACTIVE);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "could not set PS mode\n");
+ goto fail;
+ }
+
+ /* Install static keys (if any). */
+ error = rsu_reinit_static_keys(sc);
+ if (error != 0)
+ goto fail;
+
+ sc->sc_extra_scan = 0;
+ usbd_transfer_start(sc->sc_xfer[RSU_BULK_RX]);
+
+ /* We're ready to go. */
+ sc->sc_running = 1;
+ RSU_UNLOCK(sc);
+
+ return (0);
+fail:
+ /* Need to stop all failed transfers, if any */
+ for (i = 0; i != RSU_N_TRANSFER; i++)
+ usbd_transfer_stop(sc->sc_xfer[i]);
+ RSU_UNLOCK(sc);
+
+ return (error);
+}
+
+static void
+rsu_stop(struct rsu_softc *sc)
+{
+ int i;
+
+ RSU_LOCK(sc);
+ if (!sc->sc_running) {
+ RSU_UNLOCK(sc);
+ return;
+ }
+
+ sc->sc_running = 0;
+ sc->sc_vap_is_running = 0;
+ sc->sc_calibrating = 0;
+ taskqueue_cancel_timeout(taskqueue_thread, &sc->calib_task, NULL);
+ taskqueue_cancel(taskqueue_thread, &sc->tx_task, NULL);
+
+ /* Power off adapter. */
+ rsu_power_off(sc);
+
+ /*
+ * CAM is not accessible after shutdown;
+ * all entries are marked (by firmware?) as invalid.
+ */
+ memset(sc->free_keys_bmap, 0, sizeof(sc->free_keys_bmap));
+ memset(sc->keys_bmap, 0, sizeof(sc->keys_bmap));
+
+ for (i = 0; i < RSU_N_TRANSFER; i++)
+ usbd_transfer_stop(sc->sc_xfer[i]);
+
+ /* Ensure the mbuf queue is drained */
+ rsu_drain_mbufq(sc);
+ RSU_UNLOCK(sc);
+}
+
+/*
+ * Note: usb_pause_mtx() actually releases the mutex before calling pause(),
+ * which breaks any kind of driver serialisation.
+ */
+static void
+rsu_ms_delay(struct rsu_softc *sc, int ms)
+{
+
+ //usb_pause_mtx(&sc->sc_mtx, hz / 1000);
+ DELAY(ms * 1000);
+}
diff --git a/sys/dev/usb/wlan/if_rsureg.h b/sys/dev/usb/wlan/if_rsureg.h
new file mode 100644
index 000000000000..fb706a4d9b1a
--- /dev/null
+++ b/sys/dev/usb/wlan/if_rsureg.h
@@ -0,0 +1,903 @@
+/*-
+ * Copyright (c) 2010 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.
+ *
+ * $OpenBSD: if_rsureg.h,v 1.3 2013/04/15 09:23:01 mglocker Exp $
+ */
+
+/* USB Requests. */
+#define R92S_REQ_REGS 0x05
+
+/*
+ * MAC registers.
+ */
+#define R92S_SYSCFG 0x0000
+#define R92S_SYS_ISO_CTRL (R92S_SYSCFG + 0x000)
+#define R92S_SYS_FUNC_EN (R92S_SYSCFG + 0x002)
+#define R92S_PMC_FSM (R92S_SYSCFG + 0x004)
+#define R92S_SYS_CLKR (R92S_SYSCFG + 0x008)
+#define R92S_EE_9346CR (R92S_SYSCFG + 0x00a)
+#define R92S_AFE_MISC (R92S_SYSCFG + 0x010)
+#define R92S_SPS0_CTRL (R92S_SYSCFG + 0x011)
+#define R92S_SPS1_CTRL (R92S_SYSCFG + 0x018)
+#define R92S_RF_CTRL (R92S_SYSCFG + 0x01f)
+#define R92S_LDOA15_CTRL (R92S_SYSCFG + 0x020)
+#define R92S_LDOV12D_CTRL (R92S_SYSCFG + 0x021)
+#define R92S_AFE_XTAL_CTRL (R92S_SYSCFG + 0x026)
+#define R92S_AFE_PLL_CTRL (R92S_SYSCFG + 0x028)
+#define R92S_EFUSE_CTRL (R92S_SYSCFG + 0x030)
+#define R92S_EFUSE_TEST (R92S_SYSCFG + 0x034)
+#define R92S_EFUSE_CLK_CTRL (R92S_SYSCFG + 0x2f8)
+
+#define R92S_CMDCTRL 0x0040
+#define R92S_CR (R92S_CMDCTRL + 0x000)
+#define R92S_TXPAUSE (R92S_CMDCTRL + 0x002)
+#define R92S_TCR (R92S_CMDCTRL + 0x004)
+#define R92S_RCR (R92S_CMDCTRL + 0x008)
+
+#define R92S_MACIDSETTING 0x0050
+#define R92S_MACID (R92S_MACIDSETTING + 0x000)
+#define R92S_MAR (R92S_MACIDSETTING + 0x010)
+
+#define R92S_TIMECTRL 0x0080
+#define R92S_TSFTR (R92S_TIMECTRL + 0x000)
+
+#define R92S_FIFOCTRL 0x00a0
+#define R92S_RXFLTMAP_MGT (R92S_FIFOCTRL + 0x076)
+#define R92S_RXFLTMAP_CTL (R92S_FIFOCTRL + 0x078)
+#define R92S_RXFLTMAP_DATA (R92S_FIFOCTRL + 0x07a)
+#define R92S_RXFLTMAP_MESH (R92S_FIFOCTRL + 0x07c)
+
+#define R92S_SECURITY 0x0240
+#define R92S_CAMCMD (R92S_SECURITY + 0x000)
+#define R92S_CAMWRITE (R92S_SECURITY + 0x004)
+#define R92S_CAMREAD (R92S_SECURITY + 0x008)
+
+#define R92S_GP 0x02e0
+#define R92S_GPIO_CTRL (R92S_GP + 0x00c)
+#define R92S_GPIO_IO_SEL (R92S_GP + 0x00e)
+#define R92S_MAC_PINMUX_CTRL (R92S_GP + 0x011)
+#define R92S_LEDCFG (R92S_GP + 0x012)
+
+#define R92S_IOCMD_CTRL 0x0370
+#define R92S_IOCMD_DATA 0x0374
+
+#define R92S_USB_HRPWM 0xfe58
+
+/* Bits for R92S_SYS_FUNC_EN. */
+#define R92S_FEN_CPUEN 0x0400
+
+/* Bits for R92S_PMC_FSM. */
+#define R92S_PMC_FSM_CUT_M 0x000f8000
+#define R92S_PMC_FSM_CUT_S 15
+
+/* Bits for R92S_SYS_CLKR. */
+#define R92S_SYS_CLKSEL 0x0001
+#define R92S_SYS_PS_CLKSEL 0x0002
+#define R92S_SYS_CPU_CLKSEL 0x0004
+#define R92S_MAC_CLK_EN 0x0800
+#define R92S_SYS_CLK_EN 0x1000
+#define R92S_SWHW_SEL 0x4000
+#define R92S_FWHW_SEL 0x8000
+
+/* Bits for R92S_EE_9346CR. */
+#define R92S_9356SEL 0x10
+#define R92S_EEPROM_EN 0x20
+
+/* Bits for R92S_AFE_MISC. */
+#define R92S_AFE_MISC_BGEN 0x01
+#define R92S_AFE_MISC_MBEN 0x02
+#define R92S_AFE_MISC_I32_EN 0x08
+
+/* Bits for R92S_SPS1_CTRL. */
+#define R92S_SPS1_LDEN 0x01
+#define R92S_SPS1_SWEN 0x02
+
+/* Bits for R92S_LDOA15_CTRL. */
+#define R92S_LDA15_EN 0x01
+
+/* Bits for R92S_LDOV12D_CTRL. */
+#define R92S_LDV12_EN 0x01
+
+/* Bits for R92C_EFUSE_CTRL. */
+#define R92S_EFUSE_CTRL_DATA_M 0x000000ff
+#define R92S_EFUSE_CTRL_DATA_S 0
+#define R92S_EFUSE_CTRL_ADDR_M 0x0003ff00
+#define R92S_EFUSE_CTRL_ADDR_S 8
+#define R92S_EFUSE_CTRL_VALID 0x80000000
+
+/* Bits for R92S_CR. */
+#define R92S_CR_TXDMA_EN 0x10
+
+/* Bits for R92S_TXPAUSE. */
+#define R92S_TXPAUSE_VO 0x01
+#define R92S_TXPAUSE_VI 0x02
+#define R92S_TXPAUSE_BE 0x04
+#define R92S_TXPAUSE_BK 0x08
+#define R92S_TXPAUSE_MGT 0x10
+#define R92S_TXPAUSE_HIGH 0x20
+#define R92S_TXPAUSE_HCCA 0x40
+
+/* Shortcuts. */
+#define R92S_TXPAUSE_AC \
+ (R92S_TXPAUSE_VO | R92S_TXPAUSE_VI | \
+ R92S_TXPAUSE_BE | R92S_TXPAUSE_BK)
+
+#define R92S_TXPAUSE_ALL \
+ (R92S_TXPAUSE_AC | R92S_TXPAUSE_MGT | \
+ R92S_TXPAUSE_HIGH | R92S_TXPAUSE_HCCA | 0x80)
+
+/* Bits for R92S_TCR. */
+#define R92S_TCR_IMEM_CODE_DONE 0x01
+#define R92S_TCR_IMEM_CHK_RPT 0x02
+#define R92S_TCR_EMEM_CODE_DONE 0x04
+#define R92S_TCR_EMEM_CHK_RPT 0x08
+#define R92S_TCR_DMEM_CODE_DONE 0x10
+#define R92S_TCR_IMEM_RDY 0x20
+#define R92S_TCR_FWRDY 0x80
+
+/* Bits for R92S_RCR. */
+#define R92S_RCR_AAP 0x00000001
+#define R92S_RCR_APM 0x00000002
+#define R92S_RCR_AM 0x00000004
+#define R92S_RCR_AB 0x00000008
+#define R92S_RCR_ACRC32 0x00000020
+#define R92S_RCR_AICV 0x00001000
+#define R92S_RCR_APP_ICV 0x00010000
+#define R92S_RCR_APP_MIC 0x00020000
+#define R92S_RCR_ADF 0x00040000
+#define R92S_RCR_ACF 0x00080000
+#define R92S_RCR_AMF 0x00100000
+#define R92S_RCR_ADD3 0x00200000
+#define R92S_RCR_APWRMGT 0x00400000
+#define R92S_RCR_CBSSID 0x00800000
+#define R92S_RCR_APP_PHYSTS 0x02000000
+#define R92S_RCR_TCP_OFFLD_EN 0x04000000
+#define R92S_RCR_ENMBID 0x08000000
+
+/* Bits for R92S_RXFLTMAP*. */
+#define R92S_RXFLTMAP_MGT_DEF 0x3f3f
+#define R92S_RXFLTMAP_FW(subtype) \
+ (1 << ((subtype) >> IEEE80211_FC0_SUBTYPE_SHIFT))
+
+/* Bits for R92S_GPIO_IO_SEL. */
+#define R92S_GPIO_WPS 0x10
+
+/* Bits for R92S_MAC_PINMUX_CTRL. */
+#define R92S_GPIOSEL_GPIO_M 0x03
+#define R92S_GPIOSEL_GPIO_S 0
+#define R92S_GPIOSEL_GPIO_JTAG 0
+#define R92S_GPIOSEL_GPIO_PHYDBG 1
+#define R92S_GPIOSEL_GPIO_BT 2
+#define R92S_GPIOSEL_GPIO_WLANDBG 3
+#define R92S_GPIOMUX_EN 0x08
+
+/* Bits for R92S_CAMCMD. */
+#define R92S_CAMCMD_ADDR_M 0x000000ff
+#define R92S_CAMCMD_ADDR_S 0
+#define R92S_CAMCMD_READ 0x00000000
+#define R92S_CAMCMD_WRITE 0x00010000
+#define R92S_CAMCMD_POLLING 0x80000000
+
+/*
+ * CAM entries.
+ */
+#define R92S_CAM_ENTRY_LIMIT 32
+#define R92S_CAM_ENTRY_BYTES howmany(R92S_CAM_ENTRY_LIMIT, NBBY)
+
+#define R92S_CAM_CTL0(entry) ((entry) * 8 + 0)
+#define R92S_CAM_CTL1(entry) ((entry) * 8 + 1)
+#define R92S_CAM_KEY(entry, i) ((entry) * 8 + 2 + (i))
+
+/* Bits for R92S_CAM_CTL0(i). */
+#define R92S_CAM_KEYID_M 0x00000003
+#define R92S_CAM_KEYID_S 0
+#define R92S_CAM_ALGO_M 0x0000001c
+#define R92S_CAM_ALGO_S 2
+#define R92S_CAM_VALID 0x00008000
+#define R92S_CAM_MACLO_M 0xffff0000
+#define R92S_CAM_MACLO_S 16
+
+/* Bits for R92S_IOCMD_CTRL. */
+#define R92S_IOCMD_CLASS_M 0xff000000
+#define R92S_IOCMD_CLASS_S 24
+#define R92S_IOCMD_CLASS_BB_RF 0xf0
+#define R92S_IOCMD_VALUE_M 0x00ffff00
+#define R92S_IOCMD_VALUE_S 8
+#define R92S_IOCMD_INDEX_M 0x000000ff
+#define R92S_IOCMD_INDEX_S 0
+#define R92S_IOCMD_INDEX_BB_READ 0
+#define R92S_IOCMD_INDEX_BB_WRITE 1
+#define R92S_IOCMD_INDEX_RF_READ 2
+#define R92S_IOCMD_INDEX_RF_WRITE 3
+
+/* Bits for R92S_USB_HRPWM. */
+#define R92S_USB_HRPWM_PS_ALL_ON 0x04
+#define R92S_USB_HRPWM_PS_ST_ACTIVE 0x08
+
+/*
+ * Macros to access subfields in registers.
+ */
+/* Mask and Shift (getter). */
+#define MS(val, field) \
+ (((val) & field##_M) >> field##_S)
+
+/* Shift and Mask (setter). */
+#define SM(field, val) \
+ (((val) << field##_S) & field##_M)
+
+/* Rewrite. */
+#define RW(var, field, val) \
+ (((var) & ~field##_M) | SM(field, val))
+
+/*
+ * ROM field with RF config.
+ */
+enum {
+ RTL8712_RFCONFIG_1T = 0x10,
+ RTL8712_RFCONFIG_2T = 0x20,
+ RTL8712_RFCONFIG_1R = 0x01,
+ RTL8712_RFCONFIG_2R = 0x02,
+ RTL8712_RFCONFIG_1T1R = 0x11,
+ RTL8712_RFCONFIG_1T2R = 0x12,
+ RTL8712_RFCONFIG_TURBO = 0x92,
+ RTL8712_RFCONFIG_2T2R = 0x22
+};
+
+/*
+ * Firmware image header.
+ */
+struct r92s_fw_priv {
+ /* QWORD0 */
+ uint16_t signature;
+ uint8_t hci_sel;
+#define R92S_HCI_SEL_PCIE 0x01
+#define R92S_HCI_SEL_USB 0x02
+#define R92S_HCI_SEL_SDIO 0x04
+#define R92S_HCI_SEL_8172 0x10
+#define R92S_HCI_SEL_AP 0x80
+
+ uint8_t chip_version;
+ uint16_t custid;
+ uint8_t rf_config;
+//0x11: 1T1R, 0x12: 1T2R, 0x92: 1T2R turbo, 0x22: 2T2R
+ uint8_t nendpoints;
+ /* QWORD1 */
+ uint32_t regulatory;
+ uint8_t rfintfs;
+ uint8_t def_nettype;
+ uint8_t turbo_mode;
+ uint8_t lowpower_mode;
+ /* QWORD2 */
+ uint8_t lbk_mode;
+ uint8_t mp_mode;
+ uint8_t vcs_type;
+#define R92S_VCS_TYPE_DISABLE 0
+#define R92S_VCS_TYPE_ENABLE 1
+#define R92S_VCS_TYPE_AUTO 2
+
+ uint8_t vcs_mode;
+#define R92S_VCS_MODE_NONE 0
+#define R92S_VCS_MODE_RTS_CTS 1
+#define R92S_VCS_MODE_CTS2SELF 2
+
+ uint32_t reserved1;
+ /* QWORD3 */
+ uint8_t qos_en;
+ uint8_t bw40_en;
+ uint8_t amsdu2ampdu_en;
+ uint8_t ampdu_en;
+ uint8_t rc_offload;
+ uint8_t agg_offload;
+ uint16_t reserved2;
+ /* QWORD4 */
+ uint8_t beacon_offload;
+ uint8_t mlme_offload;
+ uint8_t hwpc_offload;
+ uint8_t tcpcsum_offload;
+ uint8_t tcp_offload;
+ uint8_t ps_offload;
+ uint8_t wwlan_offload;
+ uint8_t reserved3;
+ /* QWORD5 */
+ uint16_t tcp_tx_len;
+ uint16_t tcp_rx_len;
+ uint32_t reserved4;
+} __packed;
+
+struct r92s_fw_hdr {
+ uint16_t signature;
+ uint16_t version;
+ uint32_t dmemsz;
+ uint32_t imemsz;
+ uint32_t sramsz;
+ uint32_t privsz;
+ uint16_t efuse_addr;
+ uint16_t h2c_resp_addr;
+ uint32_t svnrev;
+ uint8_t month;
+ uint8_t day;
+ uint8_t hour;
+ uint8_t minute;
+ struct r92s_fw_priv priv;
+} __packed;
+
+/* Structure for FW commands and FW events notifications. */
+struct r92s_fw_cmd_hdr {
+ uint16_t len;
+ uint8_t code;
+ uint8_t seq;
+#define R92S_FW_CMD_MORE 0x80
+
+ uint32_t reserved;
+} __packed;
+
+/* FW commands codes. */
+#define R92S_CMD_READ_MACREG 0
+#define R92S_CMD_WRITE_MACREG 1
+#define R92S_CMD_READ_BBREG 2
+#define R92S_CMD_WRITE_BBREG 3
+#define R92S_CMD_READ_RFREG 4
+#define R92S_CMD_WRITE_RFREG 5
+#define R92S_CMD_READ_EEPROM 6
+#define R92S_CMD_WRITE_EEPROM 7
+#define R92S_CMD_READ_EFUSE 8
+#define R92S_CMD_WRITE_EFUSE 9
+#define R92S_CMD_READ_CAM 10
+#define R92S_CMD_WRITE_CAM 11
+#define R92S_CMD_SET_BCNITV 12
+#define R92S_CMD_SET_MBIDCFG 13
+#define R92S_CMD_JOIN_BSS 14
+#define R92S_CMD_DISCONNECT 15
+#define R92S_CMD_CREATE_BSS 16
+#define R92S_CMD_SET_OPMODE 17
+#define R92S_CMD_SITE_SURVEY 18
+#define R92S_CMD_SET_AUTH 19
+#define R92S_CMD_SET_KEY 20
+#define R92S_CMD_SET_STA_KEY 21
+#define R92S_CMD_SET_ASSOC_STA 22
+#define R92S_CMD_DEL_ASSOC_STA 23
+#define R92S_CMD_SET_STAPWRSTATE 24
+#define R92S_CMD_SET_BASIC_RATE 25
+#define R92S_CMD_GET_BASIC_RATE 26
+#define R92S_CMD_SET_DATA_RATE 27
+#define R92S_CMD_GET_DATA_RATE 28
+#define R92S_CMD_SET_PHY_INFO 29
+#define R92S_CMD_GET_PHY_INFO 30
+#define R92S_CMD_SET_PHY 31
+#define R92S_CMD_GET_PHY 32
+#define R92S_CMD_READ_RSSI 33
+#define R92S_CMD_READ_GAIN 34
+#define R92S_CMD_SET_ATIM 35
+#define R92S_CMD_SET_PWR_MODE 36
+#define R92S_CMD_JOIN_BSS_RPT 37
+#define R92S_CMD_SET_RA_TABLE 38
+#define R92S_CMD_GET_RA_TABLE 39
+#define R92S_CMD_GET_CCX_REPORT 40
+#define R92S_CMD_GET_DTM_REPORT 41
+#define R92S_CMD_GET_TXRATE_STATS 42
+#define R92S_CMD_SET_USB_SUSPEND 43
+#define R92S_CMD_SET_H2C_LBK 44
+#define R92S_CMD_ADDBA_REQ 45
+#define R92S_CMD_SET_CHANNEL 46
+#define R92S_CMD_SET_TXPOWER 47
+#define R92S_CMD_SWITCH_ANTENNA 48
+#define R92S_CMD_SET_CRYSTAL_CAL 49
+#define R92S_CMD_SET_SINGLE_CARRIER_TX 50
+#define R92S_CMD_SET_SINGLE_TONE_TX 51
+#define R92S_CMD_SET_CARRIER_SUPPR_TX 52
+#define R92S_CMD_SET_CONTINUOUS_TX 53
+#define R92S_CMD_SWITCH_BANDWIDTH 54
+#define R92S_CMD_TX_BEACON 55
+#define R92S_CMD_SET_POWER_TRACKING 56
+#define R92S_CMD_AMSDU_TO_AMPDU 57
+#define R92S_CMD_SET_MAC_ADDRESS 58
+#define R92S_CMD_GET_H2C_LBK 59
+#define R92S_CMD_SET_PBREQ_IE 60
+#define R92S_CMD_SET_ASSOCREQ_IE 61
+#define R92S_CMD_SET_PBRESP_IE 62
+#define R92S_CMD_SET_ASSOCRESP_IE 63
+#define R92S_CMD_GET_CURDATARATE 64
+#define R92S_CMD_GET_TXRETRY_CNT 65
+#define R92S_CMD_GET_RXRETRY_CNT 66
+#define R92S_CMD_GET_BCNOK_CNT 67
+#define R92S_CMD_GET_BCNERR_CNT 68
+#define R92S_CMD_GET_CURTXPWR_LEVEL 69
+#define R92S_CMD_SET_DIG 70
+#define R92S_CMD_SET_RA 71
+#define R92S_CMD_SET_PT 72
+#define R92S_CMD_READ_TSSI 73
+
+/* FW events notifications codes. */
+#define R92S_EVT_READ_MACREG 0
+#define R92S_EVT_READ_BBREG 1
+#define R92S_EVT_READ_RFREG 2
+#define R92S_EVT_READ_EEPROM 3
+#define R92S_EVT_READ_EFUSE 4
+#define R92S_EVT_READ_CAM 5
+#define R92S_EVT_GET_BASICRATE 6
+#define R92S_EVT_GET_DATARATE 7
+#define R92S_EVT_SURVEY 8
+#define R92S_EVT_SURVEY_DONE 9
+#define R92S_EVT_JOIN_BSS 10
+#define R92S_EVT_ADD_STA 11
+#define R92S_EVT_DEL_STA 12
+#define R92S_EVT_ATIM_DONE 13
+#define R92S_EVT_TX_REPORT 14
+#define R92S_EVT_CCX_REPORT 15
+#define R92S_EVT_DTM_REPORT 16
+#define R92S_EVT_TXRATE_STATS 17
+#define R92S_EVT_C2H_LBK 18
+#define R92S_EVT_FWDBG 19
+#define R92S_EVT_C2H_FEEDBACK 20
+#define R92S_EVT_ADDBA 21
+#define R92S_EVT_C2H_BCN 22
+#define R92S_EVT_PWR_STATE 23
+#define R92S_EVT_WPS_PBC 24
+#define R92S_EVT_ADDBA_REQ_REPORT 25
+
+/* Structure for R92S_CMD_SITE_SURVEY. */
+struct r92s_fw_cmd_sitesurvey {
+ uint32_t active;
+ uint32_t limit;
+ uint32_t ssidlen;
+ uint8_t ssid[32 + 1];
+} __packed;
+
+/* Structure for R92S_CMD_SET_AUTH. */
+struct r92s_fw_cmd_auth {
+ uint8_t mode;
+#define R92S_AUTHMODE_OPEN 0
+#define R92S_AUTHMODE_SHARED 1
+#define R92S_AUTHMODE_WPA 2
+
+ uint8_t dot1x;
+} __packed;
+
+/* Structure for R92S_CMD_SET_KEY. */
+struct r92s_fw_cmd_set_key {
+ uint8_t algo;
+#define R92S_KEY_ALGO_NONE 0
+#define R92S_KEY_ALGO_WEP40 1
+#define R92S_KEY_ALGO_TKIP 2
+#define R92S_KEY_ALGO_TKIP_MMIC 3
+#define R92S_KEY_ALGO_AES 4
+#define R92S_KEY_ALGO_WEP104 5
+#define R92S_KEY_ALGO_INVALID 0xff /* for rsu_crypto_mode() only */
+
+ uint8_t cam_id;
+ uint8_t grpkey;
+ uint8_t key[IEEE80211_KEYBUF_SIZE];
+} __packed;
+
+/* Structure for R92S_CMD_SET_STA_KEY. */
+struct r92s_fw_cmd_set_key_mac {
+ uint8_t macaddr[IEEE80211_ADDR_LEN];
+ uint8_t algo;
+ uint8_t key[IEEE80211_KEYBUF_SIZE];
+} __packed;
+
+/* Structures for R92S_EVENT_SURVEY/R92S_CMD_JOIN_BSS. */
+/* NDIS_802_11_SSID. */
+struct ndis_802_11_ssid {
+ uint32_t ssidlen;
+ uint8_t ssid[32];
+} __packed;
+
+/* NDIS_802_11_CONFIGURATION_FH. */
+struct ndis_802_11_configuration_fh {
+ uint32_t len;
+ uint32_t hoppattern;
+ uint32_t hopset;
+ uint32_t dwelltime;
+} __packed;
+
+/* NDIS_802_11_CONFIGURATION. */
+struct ndis_802_11_configuration {
+ uint32_t len;
+ uint32_t bintval;
+ uint32_t atim;
+ uint32_t dsconfig;
+ struct ndis_802_11_configuration_fh fhconfig;
+} __packed;
+
+/* NDIS_WLAN_BSSID_EX. */
+struct ndis_wlan_bssid_ex {
+ uint32_t len;
+ uint8_t macaddr[IEEE80211_ADDR_LEN];
+ uint8_t reserved[2];
+ struct ndis_802_11_ssid ssid;
+ uint32_t privacy;
+ int32_t rssi;
+ uint32_t networktype;
+#define NDIS802_11FH 0
+#define NDIS802_11DS 1
+#define NDIS802_11OFDM5 2
+#define NDIS802_11OFDM24 3
+#define NDIS802_11AUTOMODE 4
+
+ struct ndis_802_11_configuration config;
+ uint32_t inframode;
+#define NDIS802_11IBSS 0
+#define NDIS802_11INFRASTRUCTURE 1
+#define NDIS802_11AUTOUNKNOWN 2
+#define NDIS802_11MONITOR 3
+#define NDIS802_11APMODE 4
+
+ uint8_t supprates[16];
+ uint32_t ieslen;
+ /* Followed by ``ieslen'' bytes. */
+} __packed;
+
+/* NDIS_802_11_FIXED_IEs. */
+struct ndis_802_11_fixed_ies {
+ uint8_t tstamp[8];
+ uint16_t bintval;
+ uint16_t capabilities;
+} __packed;
+
+/* Structure for R92S_CMD_SET_PWR_MODE. */
+struct r92s_set_pwr_mode {
+ uint8_t mode;
+#define R92S_PS_MODE_ACTIVE 0
+#define R92S_PS_MODE_MIN 1
+#define R92S_PS_MODE_MAX 2
+#define R92S_PS_MODE_DTIM 3
+#define R92S_PS_MODE_VOIP 4
+#define R92S_PS_MODE_UAPSD_WMM 5
+#define R92S_PS_MODE_UAPSD 6
+#define R92S_PS_MODE_IBSS 7
+#define R92S_PS_MODE_WWLAN 8
+#define R92S_PS_MODE_RADIOOFF 9
+#define R92S_PS_MODE_DISABLE 10
+
+ uint8_t low_traffic_en;
+ uint8_t lpnav_en;
+ uint8_t rf_low_snr_en;
+ uint8_t dps_en;
+ uint8_t bcn_rx_en;
+ uint8_t bcn_pass_cnt;
+ uint8_t bcn_to;
+ uint16_t bcn_itv;
+ uint8_t app_itv;
+ uint8_t awake_bcn_itv;
+ uint8_t smart_ps;
+ uint8_t bcn_pass_time;
+} __packed;
+
+/* Structure for R92S_CMD_SET_CHANNEL. */
+struct r92s_set_channel {
+ uint32_t channel;
+} __packed;
+
+/* Structure for event R92S_EVENT_JOIN_BSS. */
+struct r92s_event_join_bss {
+ uint32_t next;
+ uint32_t prev;
+ uint32_t networktype;
+ uint32_t fixed;
+ uint32_t lastscanned;
+ uint32_t associd;
+ uint32_t join_res;
+ struct ndis_wlan_bssid_ex bss;
+} __packed;
+
+#define R92S_MACID_BSS 5 /* XXX hardcoded somewhere */
+
+/* Rx MAC descriptor. */
+struct r92s_rx_stat {
+ uint32_t rxdw0;
+#define R92S_RXDW0_PKTLEN_M 0x00003fff
+#define R92S_RXDW0_PKTLEN_S 0
+#define R92S_RXDW0_CRCERR 0x00004000
+#define R92S_RXDW0_ICVERR 0x00008000
+#define R92S_RXDW0_INFOSZ_M 0x000f0000
+#define R92S_RXDW0_INFOSZ_S 16
+#define R92S_RXDW0_CIPHER_M 0x00700000
+#define R92S_RXDW0_CIPHER_S 20
+#define R92S_RXDW0_QOS 0x00800000
+#define R92S_RXDW0_SHIFT_M 0x03000000
+#define R92S_RXDW0_SHIFT_S 24
+#define R92S_RXDW0_PHYST 0x04000000
+#define R92S_RXDW0_DECRYPTED 0x08000000
+
+ uint32_t rxdw1;
+#define R92S_RXDW1_MOREFRAG 0x08000000
+
+ uint32_t rxdw2;
+#define R92S_RXDW2_FRAG_M 0x0000f000
+#define R92S_RXDW2_FRAG_S 12
+#define R92S_RXDW2_PKTCNT_M 0x00ff0000
+#define R92S_RXDW2_PKTCNT_S 16
+
+ uint32_t rxdw3;
+#define R92S_RXDW3_RATE_M 0x0000003f
+#define R92S_RXDW3_RATE_S 0
+#define R92S_RXDW3_TCPCHKRPT 0x00000800
+#define R92S_RXDW3_IPCHKRPT 0x00001000
+#define R92S_RXDW3_TCPCHKVALID 0x00002000
+#define R92S_RXDW3_HTC 0x00004000
+
+ uint32_t rxdw4;
+ uint32_t tsf_low;
+} __packed __aligned(4);
+
+/* Rx PHY descriptor. */
+struct r92s_rx_phystat {
+ uint32_t phydw0;
+ uint32_t phydw1;
+ uint32_t phydw2;
+ uint32_t phydw3;
+ uint32_t phydw4;
+ uint32_t phydw5;
+ uint32_t phydw6;
+ uint32_t phydw7;
+} __packed __aligned(4);
+
+/* Rx PHY CCK descriptor. */
+struct r92s_rx_cck {
+ uint8_t adc_pwdb[4];
+ uint8_t sq_rpt;
+ uint8_t agc_rpt;
+} __packed;
+
+/* Tx MAC descriptor. */
+struct r92s_tx_desc {
+ uint32_t txdw0;
+#define R92S_TXDW0_PKTLEN_M 0x0000ffff
+#define R92S_TXDW0_PKTLEN_S 0
+#define R92S_TXDW0_OFFSET_M 0x00ff0000
+#define R92S_TXDW0_OFFSET_S 16
+#define R92S_TXDW0_TYPE_M 0x03000000
+#define R92S_TXDW0_TYPE_S 24
+#define R92S_TXDW0_LSG 0x04000000
+#define R92S_TXDW0_FSG 0x08000000
+#define R92S_TXDW0_LINIP 0x10000000
+#define R92S_TXDW0_OWN 0x80000000
+
+ uint32_t txdw1;
+#define R92S_TXDW1_MACID_M 0x0000001f
+#define R92S_TXDW1_MACID_S 0
+#define R92S_TXDW1_MOREDATA 0x00000020
+#define R92S_TXDW1_MOREFRAG 0x00000040
+#define R92S_TXDW1_QSEL_M 0x00001f00
+#define R92S_TXDW1_QSEL_S 8
+#define R92S_TXDW1_QSEL_BE 0x03
+#define R92S_TXDW1_QSEL_H2C 0x13
+#define R92S_TXDW1_NONQOS 0x00010000
+#define R92S_TXDW1_KEYIDX_M 0x00060000
+#define R92S_TXDW1_KEYIDX_S 17
+#define R92S_TXDW1_CIPHER_M 0x00c00000
+#define R92S_TXDW1_CIPHER_S 22
+#define R92S_TXDW1_CIPHER_NONE 0
+#define R92S_TXDW1_CIPHER_WEP 1
+#define R92S_TXDW1_CIPHER_TKIP 2
+#define R92S_TXDW1_CIPHER_AES 3
+#define R92S_TXDW1_HWPC 0x80000000
+
+ uint32_t txdw2;
+#define R92S_TXDW2_RTY_LMT_M 0x0000003f
+#define R92S_TXDW2_RTY_LMT_S 0
+#define R92S_TXDW2_RTY_LMT_ENA 0x00000040
+#define R92S_TXDW2_BMCAST 0x00000080
+#define R92S_TXDW2_AGGEN 0x20000000
+#define R92S_TXDW2_BK 0x40000000
+
+ uint32_t txdw3;
+#define R92S_TXDW3_SEQ_M 0x0fff0000
+#define R92S_TXDW3_SEQ_S 16
+#define R92S_TXDW3_FRAG_M 0xf0000000
+#define R92S_TXDW3_FRAG_S 28
+
+ uint32_t txdw4;
+#define R92S_TXDW4_TXBW 0x00040000
+#define R92S_TXDW4_DRVRATE 0x80000000
+
+ uint32_t txdw5;
+#define R92S_TXDW5_DATARATE_M 0x00007e00
+#define R92S_TXDW5_DATARATE_S 9
+#define R92S_TXDW5_DISFB 0x00008000
+#define R92S_TXDW5_DATARATE_FB_LMT_M 0x001f0000
+#define R92S_TXDW5_DATARATE_FB_LMT_S 16
+
+ uint16_t ipchksum;
+ uint16_t tcpchksum;
+
+ uint16_t txbufsize;
+ uint16_t reserved1;
+} __packed __aligned(4);
+
+struct r92s_add_ba_event {
+ uint8_t mac_addr[IEEE80211_ADDR_LEN];
+ uint16_t ssn;
+ uint8_t tid;
+};
+
+struct r92s_add_ba_req {
+ uint32_t tid;
+};
+
+/*
+ * Driver definitions.
+ */
+#define RSU_RX_LIST_COUNT 1
+#define RSU_TX_LIST_COUNT 32
+
+#define RSU_RXBUFSZ (30 * 1024)
+#define RSU_TXBUFSZ \
+ ((sizeof(struct r92s_tx_desc) + IEEE80211_MAX_LEN + 3) & ~3)
+
+#define RSU_TX_TIMEOUT 5000 /* ms */
+#define RSU_CMD_TIMEOUT 2000 /* ms */
+
+/* Queue ids (used by soft only). */
+#define RSU_QID_BCN 0
+#define RSU_QID_MGT 1
+#define RSU_QID_BMC 2
+#define RSU_QID_VO 3
+#define RSU_QID_VI 4
+#define RSU_QID_BE 5
+#define RSU_QID_BK 6
+#define RSU_QID_RXOFF 7
+#define RSU_QID_H2C 8
+#define RSU_QID_C2H 9
+
+/* Map AC to queue id. */
+static const uint8_t rsu_ac2qid[WME_NUM_AC] = {
+ RSU_QID_BE,
+ RSU_QID_BK,
+ RSU_QID_VI,
+ RSU_QID_VO
+};
+
+/* Pipe index to endpoint address mapping. */
+static const uint8_t r92s_epaddr[] =
+ { 0x83, 0x04, 0x06, 0x0d,
+ 0x05, 0x07,
+ 0x89, 0x0a, 0x0b, 0x0c };
+
+/* Queue id to pipe index mapping for 4 endpoints configurations. */
+static const uint8_t rsu_qid2idx_4ep[] =
+ { 3, 3, 3, 1, 1, 2, 2, 0, 3, 0 };
+
+/* Queue id to pipe index mapping for 6 endpoints configurations. */
+static const uint8_t rsu_qid2idx_6ep[] =
+ { 3, 3, 3, 1, 4, 2, 5, 0, 3, 0 };
+
+/* Queue id to pipe index mapping for 11 endpoints configurations. */
+static const uint8_t rsu_qid2idx_11ep[] =
+ { 7, 9, 8, 1, 4, 2, 5, 0, 3, 6 };
+
+struct rsu_rx_radiotap_header {
+ struct ieee80211_radiotap_header wr_ihdr;
+ uint64_t wr_tsft;
+ uint8_t wr_flags;
+ uint8_t wr_rate;
+ uint16_t wr_chan_freq;
+ uint16_t wr_chan_flags;
+ uint8_t wr_dbm_antsignal;
+} __packed __aligned(8);
+
+#define RSU_RX_RADIOTAP_PRESENT \
+ (1 << IEEE80211_RADIOTAP_TSFT | \
+ 1 << IEEE80211_RADIOTAP_FLAGS | \
+ 1 << IEEE80211_RADIOTAP_RATE | \
+ 1 << IEEE80211_RADIOTAP_CHANNEL | \
+ 1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL)
+
+struct rsu_tx_radiotap_header {
+ struct ieee80211_radiotap_header wt_ihdr;
+ uint8_t wt_flags;
+ uint8_t wt_pad;
+ uint16_t wt_chan_freq;
+ uint16_t wt_chan_flags;
+} __packed;
+
+#define RSU_TX_RADIOTAP_PRESENT \
+ (1 << IEEE80211_RADIOTAP_FLAGS | \
+ 1 << IEEE80211_RADIOTAP_CHANNEL)
+
+struct rsu_softc;
+
+enum {
+ RSU_BULK_RX,
+ RSU_BULK_TX_BE_BK, /* = WME_AC_BE/BK */
+ RSU_BULK_TX_VI_VO, /* = WME_AC_VI/VO */
+ RSU_BULK_TX_H2C, /* H2C */
+ RSU_N_TRANSFER,
+};
+
+struct rsu_data {
+ struct rsu_softc *sc;
+ uint8_t *buf;
+ uint16_t buflen;
+ struct mbuf *m;
+ struct ieee80211_node *ni;
+ STAILQ_ENTRY(rsu_data) next;
+};
+
+struct rsu_vap {
+ struct ieee80211vap vap;
+
+ int (*newstate)(struct ieee80211vap *,
+ enum ieee80211_state, int);
+};
+#define RSU_VAP(vap) ((struct rsu_vap *)(vap))
+
+#define RSU_LOCK(sc) mtx_lock(&(sc)->sc_mtx)
+#define RSU_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx)
+#define RSU_ASSERT_LOCKED(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED)
+
+#define RSU_DELKEY_BMAP_LOCK_INIT(_sc) \
+ mtx_init(&(_sc)->free_keys_bmap_mtx, "bmap lock", NULL, MTX_DEF)
+#define RSU_DELKEY_BMAP_LOCK(_sc) mtx_lock(&(_sc)->free_keys_bmap_mtx)
+#define RSU_DELKEY_BMAP_UNLOCK(_sc) mtx_unlock(&(_sc)->free_keys_bmap_mtx)
+#define RSU_DELKEY_BMAP_LOCK_DESTROY(_sc) \
+ mtx_destroy(&(_sc)->free_keys_bmap_mtx)
+
+struct rsu_softc {
+ struct ieee80211com sc_ic;
+ struct mbufq sc_snd;
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+
+ struct timeout_task calib_task;
+ struct task tx_task;
+ struct mtx sc_mtx;
+ int sc_ht;
+ int sc_nendpoints;
+ int sc_curpwrstate;
+ int sc_currssi;
+
+ u_int sc_running:1,
+ sc_vap_is_running:1,
+ sc_rx_checksum_enable:1,
+ sc_calibrating:1,
+ sc_active_scan:1,
+ sc_extra_scan:1;
+ u_int cut;
+ uint8_t sc_rftype;
+ int8_t sc_nrxstream;
+ int8_t sc_ntxstream;
+ struct rsu_data sc_rx[RSU_RX_LIST_COUNT];
+ struct rsu_data sc_tx[RSU_TX_LIST_COUNT];
+ uint8_t cmd_seq;
+ uint8_t rom[128];
+ struct usb_xfer *sc_xfer[RSU_N_TRANSFER];
+
+ STAILQ_HEAD(, rsu_data) sc_rx_active;
+ STAILQ_HEAD(, rsu_data) sc_rx_inactive;
+ STAILQ_HEAD(, rsu_data) sc_tx_active[RSU_N_TRANSFER];
+ STAILQ_HEAD(, rsu_data) sc_tx_inactive;
+ STAILQ_HEAD(, rsu_data) sc_tx_pending[RSU_N_TRANSFER];
+
+ struct task del_key_task;
+ uint8_t keys_bmap[R92S_CAM_ENTRY_BYTES];
+ const struct ieee80211_key *group_keys[IEEE80211_WEP_NKID];
+
+ struct mtx free_keys_bmap_mtx;
+ uint8_t free_keys_bmap[R92S_CAM_ENTRY_BYTES];
+
+ union {
+ struct rsu_rx_radiotap_header th;
+ uint8_t pad[64];
+ } sc_rxtapu;
+#define sc_rxtap sc_rxtapu.th
+
+ union {
+ struct rsu_tx_radiotap_header th;
+ uint8_t pad[64];
+ } sc_txtapu;
+#define sc_txtap sc_txtapu.th
+};
diff --git a/sys/dev/usb/wlan/if_rum.c b/sys/dev/usb/wlan/if_rum.c
new file mode 100644
index 000000000000..b822766f0ba5
--- /dev/null
+++ b/sys/dev/usb/wlan/if_rum.c
@@ -0,0 +1,3292 @@
+
+/*-
+ * Copyright (c) 2005-2007 Damien Bergamini <damien.bergamini@free.fr>
+ * Copyright (c) 2006 Niall O'Higgins <niallo@openbsd.org>
+ * Copyright (c) 2007-2008 Hans Petter Selasky <hselasky@FreeBSD.org>
+ * Copyright (c) 2015 Andriy Voskoboinyk <avos@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.
+ */
+
+/*-
+ * Ralink Technology RT2501USB/RT2601USB chipset driver
+ * http://www.ralinktech.com.tw/
+ */
+
+#include "opt_wlan.h"
+
+#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 <net/bpf.h>
+#include <net/if.h>
+#include <net/if_var.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 <net80211/ieee80211_ratectl.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR rum_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/wlan/if_rumreg.h>
+#include <dev/usb/wlan/if_rumvar.h>
+#include <dev/usb/wlan/if_rumfw.h>
+
+#ifdef USB_DEBUG
+static int rum_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, rum, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB rum");
+SYSCTL_INT(_hw_usb_rum, OID_AUTO, debug, CTLFLAG_RWTUN, &rum_debug, 0,
+ "Debug level");
+#endif
+
+static const STRUCT_USB_HOST_ID rum_devs[] = {
+#define RUM_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) }
+ RUM_DEV(ABOCOM, HWU54DM),
+ RUM_DEV(ABOCOM, RT2573_2),
+ RUM_DEV(ABOCOM, RT2573_3),
+ RUM_DEV(ABOCOM, RT2573_4),
+ RUM_DEV(ABOCOM, WUG2700),
+ RUM_DEV(AMIT, CGWLUSB2GO),
+ RUM_DEV(ASUS, RT2573_1),
+ RUM_DEV(ASUS, RT2573_2),
+ RUM_DEV(BELKIN, F5D7050A),
+ RUM_DEV(BELKIN, F5D9050V3),
+ RUM_DEV(CISCOLINKSYS, WUSB54GC),
+ RUM_DEV(CISCOLINKSYS, WUSB54GR),
+ RUM_DEV(CONCEPTRONIC2, C54RU2),
+ RUM_DEV(COREGA, CGWLUSB2GL),
+ RUM_DEV(COREGA, CGWLUSB2GPX),
+ RUM_DEV(DICKSMITH, CWD854F),
+ RUM_DEV(DICKSMITH, RT2573),
+ RUM_DEV(EDIMAX, EW7318USG),
+ RUM_DEV(DLINK2, DWLG122C1),
+ RUM_DEV(DLINK2, WUA1340),
+ RUM_DEV(DLINK2, DWA111),
+ RUM_DEV(DLINK2, DWA110),
+ RUM_DEV(GIGABYTE, GNWB01GS),
+ RUM_DEV(GIGABYTE, GNWI05GS),
+ RUM_DEV(GIGASET, RT2573),
+ RUM_DEV(GOODWAY, RT2573),
+ RUM_DEV(GUILLEMOT, HWGUSB254LB),
+ RUM_DEV(GUILLEMOT, HWGUSB254V2AP),
+ RUM_DEV(HUAWEI3COM, WUB320G),
+ RUM_DEV(MELCO, G54HP),
+ RUM_DEV(MELCO, SG54HP),
+ RUM_DEV(MELCO, SG54HG),
+ RUM_DEV(MELCO, WLIUCG),
+ RUM_DEV(MELCO, WLRUCG),
+ RUM_DEV(MELCO, WLRUCGAOSS),
+ RUM_DEV(MSI, RT2573_1),
+ RUM_DEV(MSI, RT2573_2),
+ RUM_DEV(MSI, RT2573_3),
+ RUM_DEV(MSI, RT2573_4),
+ RUM_DEV(NOVATECH, RT2573),
+ RUM_DEV(PLANEX2, GWUS54HP),
+ RUM_DEV(PLANEX2, GWUS54MINI2),
+ RUM_DEV(PLANEX2, GWUSMM),
+ RUM_DEV(QCOM, RT2573),
+ RUM_DEV(QCOM, RT2573_2),
+ RUM_DEV(QCOM, RT2573_3),
+ RUM_DEV(RALINK, RT2573),
+ RUM_DEV(RALINK, RT2573_2),
+ RUM_DEV(RALINK, RT2671),
+ RUM_DEV(SITECOMEU, WL113R2),
+ RUM_DEV(SITECOMEU, WL172),
+ RUM_DEV(SPARKLAN, RT2573),
+ RUM_DEV(SURECOM, RT2573),
+#undef RUM_DEV
+};
+
+static device_probe_t rum_match;
+static device_attach_t rum_attach;
+static device_detach_t rum_detach;
+
+static usb_callback_t rum_bulk_read_callback;
+static usb_callback_t rum_bulk_write_callback;
+
+static usb_error_t rum_do_request(struct rum_softc *sc,
+ struct usb_device_request *req, void *data);
+static usb_error_t rum_do_mcu_request(struct rum_softc *sc, int);
+static struct ieee80211vap *rum_vap_create(struct ieee80211com *,
+ const char [IFNAMSIZ], int, enum ieee80211_opmode,
+ int, const uint8_t [IEEE80211_ADDR_LEN],
+ const uint8_t [IEEE80211_ADDR_LEN]);
+static void rum_vap_delete(struct ieee80211vap *);
+static void rum_cmdq_cb(void *, int);
+static int rum_cmd_sleepable(struct rum_softc *, const void *,
+ size_t, uint8_t, CMD_FUNC_PROTO);
+static void rum_tx_free(struct rum_tx_data *, int);
+static void rum_setup_tx_list(struct rum_softc *);
+static void rum_reset_tx_list(struct rum_softc *,
+ struct ieee80211vap *);
+static void rum_unsetup_tx_list(struct rum_softc *);
+static void rum_beacon_miss(struct ieee80211vap *);
+static void rum_sta_recv_mgmt(struct ieee80211_node *,
+ struct mbuf *, int,
+ const struct ieee80211_rx_stats *, int, int);
+static int rum_set_power_state(struct rum_softc *, int);
+static int rum_newstate(struct ieee80211vap *,
+ enum ieee80211_state, int);
+static uint8_t rum_crypto_mode(struct rum_softc *, u_int, int);
+static void rum_setup_tx_desc(struct rum_softc *,
+ struct rum_tx_desc *, struct ieee80211_key *,
+ uint32_t, uint8_t, uint8_t, int, int, int);
+static uint32_t rum_tx_crypto_flags(struct rum_softc *,
+ struct ieee80211_node *,
+ const struct ieee80211_key *);
+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 int rum_transmit(struct ieee80211com *, struct mbuf *);
+static void rum_start(struct rum_softc *);
+static void rum_parent(struct ieee80211com *);
+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 usb_error_t rum_write(struct rum_softc *, uint16_t, uint32_t);
+static usb_error_t rum_write_multi(struct rum_softc *, uint16_t, void *,
+ size_t);
+static usb_error_t rum_setbits(struct rum_softc *, uint16_t, uint32_t);
+static usb_error_t rum_clrbits(struct rum_softc *, uint16_t, uint32_t);
+static usb_error_t rum_modbits(struct rum_softc *, uint16_t, uint32_t,
+ uint32_t);
+static int rum_bbp_busy(struct rum_softc *);
+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_set_maxretry(struct rum_softc *,
+ struct ieee80211vap *);
+static int rum_enable_tsf_sync(struct rum_softc *);
+static void rum_enable_tsf(struct rum_softc *);
+static void rum_abort_tsf_sync(struct rum_softc *);
+static void rum_get_tsf(struct rum_softc *, uint64_t *);
+static void rum_update_slot_cb(struct rum_softc *,
+ union sec_param *, uint8_t);
+static void rum_update_slot(struct ieee80211com *);
+static int rum_wme_update(struct ieee80211com *);
+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_mcast(struct ieee80211com *);
+static void rum_update_promisc(struct ieee80211com *);
+static void rum_setpromisc(struct rum_softc *);
+static const char *rum_get_rf(int);
+static void rum_read_eeprom(struct rum_softc *);
+static int rum_bbp_wakeup(struct rum_softc *);
+static int rum_bbp_init(struct rum_softc *);
+static void rum_clr_shkey_regs(struct rum_softc *);
+static int rum_init(struct rum_softc *);
+static void rum_stop(struct rum_softc *);
+static void rum_load_microcode(struct rum_softc *, const uint8_t *,
+ size_t);
+static int rum_set_sleep_time(struct rum_softc *, uint16_t);
+static int rum_reset(struct ieee80211vap *, u_long);
+static int rum_set_beacon(struct rum_softc *,
+ struct ieee80211vap *);
+static int rum_alloc_beacon(struct rum_softc *,
+ struct ieee80211vap *);
+static void rum_update_beacon_cb(struct rum_softc *,
+ union sec_param *, uint8_t);
+static void rum_update_beacon(struct ieee80211vap *, int);
+static int rum_common_key_set(struct rum_softc *,
+ struct ieee80211_key *, uint16_t);
+static void rum_group_key_set_cb(struct rum_softc *,
+ union sec_param *, uint8_t);
+static void rum_group_key_del_cb(struct rum_softc *,
+ union sec_param *, uint8_t);
+static void rum_pair_key_set_cb(struct rum_softc *,
+ union sec_param *, uint8_t);
+static void rum_pair_key_del_cb(struct rum_softc *,
+ union sec_param *, uint8_t);
+static int rum_key_alloc(struct ieee80211vap *,
+ struct ieee80211_key *, ieee80211_keyix *,
+ ieee80211_keyix *);
+static int rum_key_set(struct ieee80211vap *,
+ const struct ieee80211_key *);
+static int rum_key_delete(struct ieee80211vap *,
+ const struct ieee80211_key *);
+static int rum_raw_xmit(struct ieee80211_node *, struct mbuf *,
+ const struct ieee80211_bpf_params *);
+static void rum_scan_start(struct ieee80211com *);
+static void rum_scan_end(struct ieee80211com *);
+static void rum_set_channel(struct ieee80211com *);
+static void rum_getradiocaps(struct ieee80211com *, int, int *,
+ struct ieee80211_channel[]);
+static int rum_get_rssi(struct rum_softc *, uint8_t);
+static void rum_ratectl_start(struct rum_softc *,
+ struct ieee80211_node *);
+static void rum_ratectl_timeout(void *);
+static void rum_ratectl_task(void *, int);
+static int rum_pause(struct rum_softc *, int);
+
+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_CSR2, 0x00000000 },
+ { RT2573_SEC_CSR3, 0x00000000 },
+ { RT2573_SEC_CSR4, 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 uint8_t rum_chan_5ghz[] =
+ { 34, 36, 38, 40, 42, 44, 46, 48, 52, 56, 60, 64,
+ 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140,
+ 149, 153, 157, 161, 165 };
+
+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 const struct usb_config rum_config[RUM_N_TRANSFER] = {
+ [RUM_BULK_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = (MCLBYTES + RT2573_TX_DESC_SIZE + 8),
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = rum_bulk_write_callback,
+ .timeout = 5000, /* ms */
+ },
+ [RUM_BULK_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = (MCLBYTES + RT2573_RX_DESC_SIZE),
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = rum_bulk_read_callback,
+ },
+};
+
+static int
+rum_match(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != 0)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != RT2573_IFACE_INDEX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(rum_devs, sizeof(rum_devs), uaa));
+}
+
+static int
+rum_attach(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+ struct rum_softc *sc = device_get_softc(self);
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t tmp;
+ uint8_t iface_index;
+ int error, ntries;
+
+ device_set_usb_desc(self);
+ sc->sc_udev = uaa->device;
+ sc->sc_dev = self;
+
+ RUM_LOCK_INIT(sc);
+ RUM_CMDQ_LOCK_INIT(sc);
+ mbufq_init(&sc->sc_snd, ifqmaxlen);
+
+ iface_index = RT2573_IFACE_INDEX;
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, rum_config, RUM_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(self, "could not allocate USB transfers, "
+ "err=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+
+ RUM_LOCK(sc);
+ /* retrieve RT2573 rev. no */
+ for (ntries = 0; ntries < 100; ntries++) {
+ if ((tmp = rum_read(sc, RT2573_MAC_CSR0)) != 0)
+ break;
+ if (rum_pause(sc, hz / 100))
+ break;
+ }
+ if (ntries == 100) {
+ device_printf(sc->sc_dev, "timeout waiting for chip to settle\n");
+ RUM_UNLOCK(sc);
+ goto detach;
+ }
+
+ /* retrieve MAC address and various other things from EEPROM */
+ rum_read_eeprom(sc);
+
+ device_printf(sc->sc_dev, "MAC/BBP RT2573 (rev 0x%05x), RF %s\n",
+ tmp, rum_get_rf(sc->rf_rev));
+
+ rum_load_microcode(sc, rt2573_ucode, sizeof(rt2573_ucode));
+ RUM_UNLOCK(sc);
+
+ ic->ic_softc = sc;
+ ic->ic_name = device_get_nameunit(self);
+ 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_AHDEMO /* adhoc demo mode */
+ | 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 */
+ | IEEE80211_C_WME /* 802.11e */
+ | IEEE80211_C_PMGT /* Station-side power mgmt */
+ | IEEE80211_C_SWSLEEP /* net80211 managed power mgmt */
+ ;
+
+ ic->ic_cryptocaps =
+ IEEE80211_CRYPTO_WEP |
+ IEEE80211_CRYPTO_AES_CCM |
+ IEEE80211_CRYPTO_TKIPMIC |
+ IEEE80211_CRYPTO_TKIP;
+
+ rum_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans,
+ ic->ic_channels);
+
+ ieee80211_ifattach(ic);
+ ic->ic_update_promisc = rum_update_promisc;
+ ic->ic_raw_xmit = rum_raw_xmit;
+ ic->ic_scan_start = rum_scan_start;
+ ic->ic_scan_end = rum_scan_end;
+ ic->ic_set_channel = rum_set_channel;
+ ic->ic_getradiocaps = rum_getradiocaps;
+ ic->ic_transmit = rum_transmit;
+ ic->ic_parent = rum_parent;
+ ic->ic_vap_create = rum_vap_create;
+ ic->ic_vap_delete = rum_vap_delete;
+ ic->ic_updateslot = rum_update_slot;
+ ic->ic_wme.wme_update = rum_wme_update;
+ ic->ic_update_mcast = rum_update_mcast;
+
+ ieee80211_radiotap_attach(ic,
+ &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap),
+ RT2573_TX_RADIOTAP_PRESENT,
+ &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap),
+ RT2573_RX_RADIOTAP_PRESENT);
+
+ TASK_INIT(&sc->cmdq_task, 0, rum_cmdq_cb, sc);
+
+ if (bootverbose)
+ ieee80211_announce(ic);
+
+ return (0);
+
+detach:
+ rum_detach(self);
+ return (ENXIO); /* failure */
+}
+
+static int
+rum_detach(device_t self)
+{
+ struct rum_softc *sc = device_get_softc(self);
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ /* Prevent further ioctls */
+ RUM_LOCK(sc);
+ sc->sc_detached = 1;
+ RUM_UNLOCK(sc);
+
+ /* stop all USB transfers */
+ usbd_transfer_unsetup(sc->sc_xfer, RUM_N_TRANSFER);
+
+ /* free TX list, if any */
+ RUM_LOCK(sc);
+ rum_unsetup_tx_list(sc);
+ RUM_UNLOCK(sc);
+
+ if (ic->ic_softc == sc) {
+ ieee80211_draintask(ic, &sc->cmdq_task);
+ ieee80211_ifdetach(ic);
+ }
+
+ mbufq_drain(&sc->sc_snd);
+ RUM_CMDQ_LOCK_DESTROY(sc);
+ RUM_LOCK_DESTROY(sc);
+
+ return (0);
+}
+
+static usb_error_t
+rum_do_request(struct rum_softc *sc,
+ struct usb_device_request *req, void *data)
+{
+ usb_error_t err;
+ int ntries = 10;
+
+ while (ntries--) {
+ err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx,
+ req, data, 0, NULL, 250 /* ms */);
+ if (err == 0)
+ break;
+
+ DPRINTFN(1, "Control request failed, %s (retrying)\n",
+ usbd_errstr(err));
+ if (rum_pause(sc, hz / 100))
+ break;
+ }
+ return (err);
+}
+
+static usb_error_t
+rum_do_mcu_request(struct rum_softc *sc, int request)
+{
+ struct usb_device_request req;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = RT2573_MCU_CNTL;
+ USETW(req.wValue, request);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+
+ return (rum_do_request(sc, &req, NULL));
+}
+
+static struct ieee80211vap *
+rum_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
+ enum ieee80211_opmode opmode, int flags,
+ const uint8_t bssid[IEEE80211_ADDR_LEN],
+ const uint8_t mac[IEEE80211_ADDR_LEN])
+{
+ struct rum_softc *sc = ic->ic_softc;
+ struct rum_vap *rvp;
+ struct ieee80211vap *vap;
+
+ if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */
+ return NULL;
+ rvp = malloc(sizeof(struct rum_vap), M_80211_VAP, M_WAITOK | M_ZERO);
+ vap = &rvp->vap;
+ /* enable s/w bmiss handling for sta mode */
+
+ if (ieee80211_vap_setup(ic, vap, name, unit, opmode,
+ flags | IEEE80211_CLONE_NOBEACONS, bssid) != 0) {
+ /* out of memory */
+ free(rvp, M_80211_VAP);
+ return (NULL);
+ }
+
+ /* override state transition machine */
+ rvp->newstate = vap->iv_newstate;
+ vap->iv_newstate = rum_newstate;
+ vap->iv_key_alloc = rum_key_alloc;
+ vap->iv_key_set = rum_key_set;
+ vap->iv_key_delete = rum_key_delete;
+ vap->iv_update_beacon = rum_update_beacon;
+ vap->iv_reset = rum_reset;
+ vap->iv_max_aid = RT2573_ADDR_MAX;
+
+ if (opmode == IEEE80211_M_STA) {
+ /*
+ * Move device to the sleep state when
+ * beacon is received and there is no data for us.
+ *
+ * Used only for IEEE80211_S_SLEEP state.
+ */
+ rvp->recv_mgmt = vap->iv_recv_mgmt;
+ vap->iv_recv_mgmt = rum_sta_recv_mgmt;
+
+ /* Ignored while sleeping. */
+ rvp->bmiss = vap->iv_bmiss;
+ vap->iv_bmiss = rum_beacon_miss;
+ }
+
+ usb_callout_init_mtx(&rvp->ratectl_ch, &sc->sc_mtx, 0);
+ TASK_INIT(&rvp->ratectl_task, 0, rum_ratectl_task, rvp);
+ ieee80211_ratectl_init(vap);
+ ieee80211_ratectl_setinterval(vap, 1000 /* 1 sec */);
+ /* complete setup */
+ ieee80211_vap_attach(vap, ieee80211_media_change,
+ ieee80211_media_status, mac);
+ ic->ic_opmode = opmode;
+ return vap;
+}
+
+static void
+rum_vap_delete(struct ieee80211vap *vap)
+{
+ struct rum_vap *rvp = RUM_VAP(vap);
+ struct ieee80211com *ic = vap->iv_ic;
+ struct rum_softc *sc = ic->ic_softc;
+ int i;
+
+ /* Put vap into INIT state. */
+ ieee80211_new_state(vap, IEEE80211_S_INIT, -1);
+ for (i = 0; i < NET80211_IV_NSTATE_NUM; i++)
+ ieee80211_draintask(ic, &vap->iv_nstate_task[i]);
+
+ RUM_LOCK(sc);
+ /* Cancel any unfinished Tx. */
+ rum_reset_tx_list(sc, vap);
+ RUM_UNLOCK(sc);
+
+ usb_callout_drain(&rvp->ratectl_ch);
+ ieee80211_draintask(ic, &rvp->ratectl_task);
+ ieee80211_ratectl_deinit(vap);
+ ieee80211_vap_detach(vap);
+ m_freem(rvp->bcn_mbuf);
+ free(rvp, M_80211_VAP);
+}
+
+static void
+rum_cmdq_cb(void *arg, int pending)
+{
+ struct rum_softc *sc = arg;
+ struct rum_cmdq *rc;
+
+ RUM_CMDQ_LOCK(sc);
+ while (sc->cmdq[sc->cmdq_first].func != NULL) {
+ rc = &sc->cmdq[sc->cmdq_first];
+ RUM_CMDQ_UNLOCK(sc);
+
+ RUM_LOCK(sc);
+ rc->func(sc, &rc->data, rc->rvp_id);
+ RUM_UNLOCK(sc);
+
+ RUM_CMDQ_LOCK(sc);
+ memset(rc, 0, sizeof (*rc));
+ sc->cmdq_first = (sc->cmdq_first + 1) % RUM_CMDQ_SIZE;
+ }
+ RUM_CMDQ_UNLOCK(sc);
+}
+
+static int
+rum_cmd_sleepable(struct rum_softc *sc, const void *ptr, size_t len,
+ uint8_t rvp_id, CMD_FUNC_PROTO)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ KASSERT(len <= sizeof(union sec_param), ("buffer overflow"));
+
+ RUM_CMDQ_LOCK(sc);
+ if (sc->cmdq[sc->cmdq_last].func != NULL) {
+ device_printf(sc->sc_dev, "%s: cmdq overflow\n", __func__);
+ RUM_CMDQ_UNLOCK(sc);
+
+ return EAGAIN;
+ }
+
+ if (ptr != NULL)
+ memcpy(&sc->cmdq[sc->cmdq_last].data, ptr, len);
+ sc->cmdq[sc->cmdq_last].rvp_id = rvp_id;
+ sc->cmdq[sc->cmdq_last].func = func;
+ sc->cmdq_last = (sc->cmdq_last + 1) % RUM_CMDQ_SIZE;
+ RUM_CMDQ_UNLOCK(sc);
+
+ ieee80211_runtask(ic, &sc->cmdq_task);
+
+ return 0;
+}
+
+static void
+rum_tx_free(struct rum_tx_data *data, int txerr)
+{
+ struct rum_softc *sc = data->sc;
+
+ if (data->m != NULL) {
+ ieee80211_tx_complete(data->ni, data->m, txerr);
+ data->m = NULL;
+ data->ni = NULL;
+ }
+ STAILQ_INSERT_TAIL(&sc->tx_free, data, next);
+ sc->tx_nfree++;
+}
+
+static void
+rum_setup_tx_list(struct rum_softc *sc)
+{
+ struct rum_tx_data *data;
+ int i;
+
+ sc->tx_nfree = 0;
+ STAILQ_INIT(&sc->tx_q);
+ STAILQ_INIT(&sc->tx_free);
+
+ for (i = 0; i < RUM_TX_LIST_COUNT; i++) {
+ data = &sc->tx_data[i];
+
+ data->sc = sc;
+ STAILQ_INSERT_TAIL(&sc->tx_free, data, next);
+ sc->tx_nfree++;
+ }
+}
+
+static void
+rum_reset_tx_list(struct rum_softc *sc, struct ieee80211vap *vap)
+{
+ struct rum_tx_data *data, *tmp;
+
+ KASSERT(vap != NULL, ("%s: vap is NULL\n", __func__));
+
+ STAILQ_FOREACH_SAFE(data, &sc->tx_q, next, tmp) {
+ if (data->ni != NULL && data->ni->ni_vap == vap) {
+ ieee80211_free_node(data->ni);
+ data->ni = NULL;
+
+ KASSERT(data->m != NULL, ("%s: m is NULL\n",
+ __func__));
+ m_freem(data->m);
+ data->m = NULL;
+
+ STAILQ_REMOVE(&sc->tx_q, data, rum_tx_data, next);
+ STAILQ_INSERT_TAIL(&sc->tx_free, data, next);
+ sc->tx_nfree++;
+ }
+ }
+}
+
+static void
+rum_unsetup_tx_list(struct rum_softc *sc)
+{
+ struct rum_tx_data *data;
+ int i;
+
+ /* make sure any subsequent use of the queues will fail */
+ sc->tx_nfree = 0;
+ STAILQ_INIT(&sc->tx_q);
+ STAILQ_INIT(&sc->tx_free);
+
+ /* free up all node references and mbufs */
+ for (i = 0; i < RUM_TX_LIST_COUNT; i++) {
+ data = &sc->tx_data[i];
+
+ if (data->m != NULL) {
+ m_freem(data->m);
+ data->m = NULL;
+ }
+ if (data->ni != NULL) {
+ ieee80211_free_node(data->ni);
+ data->ni = NULL;
+ }
+ }
+}
+
+static void
+rum_beacon_miss(struct ieee80211vap *vap)
+{
+ struct ieee80211com *ic = vap->iv_ic;
+ struct rum_softc *sc = ic->ic_softc;
+ struct rum_vap *rvp = RUM_VAP(vap);
+ int sleep;
+
+ RUM_LOCK(sc);
+ if (sc->sc_sleeping && sc->sc_sleep_end < ticks) {
+ DPRINTFN(12, "dropping 'sleeping' bit, "
+ "device must be awake now\n");
+
+ sc->sc_sleeping = 0;
+ }
+
+ sleep = sc->sc_sleeping;
+ RUM_UNLOCK(sc);
+
+ if (!sleep)
+ rvp->bmiss(vap);
+#ifdef USB_DEBUG
+ else
+ DPRINTFN(13, "bmiss event is ignored whilst sleeping\n");
+#endif
+}
+
+static void
+rum_sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m, int subtype,
+ const struct ieee80211_rx_stats *rxs,
+ int rssi, int nf)
+{
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct rum_softc *sc = vap->iv_ic->ic_softc;
+ struct rum_vap *rvp = RUM_VAP(vap);
+
+ if (vap->iv_state == IEEE80211_S_SLEEP &&
+ subtype == IEEE80211_FC0_SUBTYPE_BEACON) {
+ RUM_LOCK(sc);
+ DPRINTFN(12, "beacon, mybss %d (flags %02X)\n",
+ !!(sc->last_rx_flags & RT2573_RX_MYBSS),
+ sc->last_rx_flags);
+
+ if ((sc->last_rx_flags & (RT2573_RX_MYBSS | RT2573_RX_BC)) ==
+ (RT2573_RX_MYBSS | RT2573_RX_BC)) {
+ /*
+ * Put it to sleep here; in case if there is a data
+ * for us, iv_recv_mgmt() will wakeup the device via
+ * SLEEP -> RUN state transition.
+ */
+ rum_set_power_state(sc, 1);
+ }
+ RUM_UNLOCK(sc);
+ }
+
+ rvp->recv_mgmt(ni, m, subtype, rxs, rssi, nf);
+}
+
+static int
+rum_set_power_state(struct rum_softc *sc, int sleep)
+{
+ usb_error_t uerror;
+
+ RUM_LOCK_ASSERT(sc);
+
+ DPRINTFN(12, "moving to %s state (sleep time %u)\n",
+ sleep ? "sleep" : "awake", sc->sc_sleep_time);
+
+ uerror = rum_do_mcu_request(sc,
+ sleep ? RT2573_MCU_SLEEP : RT2573_MCU_WAKEUP);
+ if (uerror != USB_ERR_NORMAL_COMPLETION) {
+ device_printf(sc->sc_dev,
+ "%s: could not change power state: %s\n",
+ __func__, usbd_errstr(uerror));
+ return (EIO);
+ }
+
+ sc->sc_sleeping = !!sleep;
+ sc->sc_sleep_end = sleep ? ticks + sc->sc_sleep_time : 0;
+
+ return (0);
+}
+
+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_softc;
+ const struct ieee80211_txparam *tp;
+ enum ieee80211_state ostate;
+ struct ieee80211_node *ni;
+ usb_error_t uerror;
+ int ret = 0;
+
+ ostate = vap->iv_state;
+ DPRINTF("%s -> %s\n",
+ ieee80211_state_name[ostate],
+ ieee80211_state_name[nstate]);
+
+ IEEE80211_UNLOCK(ic);
+ RUM_LOCK(sc);
+ usb_callout_stop(&rvp->ratectl_ch);
+
+ if (ostate == IEEE80211_S_SLEEP && vap->iv_opmode == IEEE80211_M_STA) {
+ rum_clrbits(sc, RT2573_TXRX_CSR4, RT2573_ACKCTS_PWRMGT);
+ rum_clrbits(sc, RT2573_MAC_CSR11, RT2573_AUTO_WAKEUP);
+
+ /*
+ * Ignore any errors;
+ * any subsequent TX will wakeup it anyway
+ */
+ (void) rum_set_power_state(sc, 0);
+ }
+
+ switch (nstate) {
+ case IEEE80211_S_INIT:
+ if (ostate == IEEE80211_S_RUN)
+ rum_abort_tsf_sync(sc);
+
+ break;
+
+ case IEEE80211_S_RUN:
+ if (ostate == IEEE80211_S_SLEEP)
+ break; /* already handled */
+
+ ni = ieee80211_ref_node(vap->iv_bss);
+
+ if (vap->iv_opmode != IEEE80211_M_MONITOR) {
+ if (ic->ic_bsschan == IEEE80211_CHAN_ANYC ||
+ ni->ni_chan == IEEE80211_CHAN_ANYC) {
+ ret = EINVAL;
+ goto run_fail;
+ }
+ rum_update_slot_cb(sc, NULL, 0);
+ rum_enable_mrr(sc);
+ rum_set_txpreamble(sc);
+ rum_set_basicrates(sc);
+ rum_set_maxretry(sc, vap);
+ IEEE80211_ADDR_COPY(sc->sc_bssid, ni->ni_bssid);
+ rum_set_bssid(sc, sc->sc_bssid);
+ }
+
+ if (vap->iv_opmode == IEEE80211_M_HOSTAP ||
+ vap->iv_opmode == IEEE80211_M_IBSS) {
+ if ((ret = rum_alloc_beacon(sc, vap)) != 0)
+ goto run_fail;
+ }
+
+ if (vap->iv_opmode != IEEE80211_M_MONITOR &&
+ vap->iv_opmode != IEEE80211_M_AHDEMO) {
+ if ((ret = rum_enable_tsf_sync(sc)) != 0)
+ goto run_fail;
+ } else
+ rum_enable_tsf(sc);
+
+ /* enable automatic rate adaptation */
+ tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)];
+ if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE)
+ rum_ratectl_start(sc, ni);
+run_fail:
+ ieee80211_free_node(ni);
+ break;
+ case IEEE80211_S_SLEEP:
+ /* Implemented for STA mode only. */
+ if (vap->iv_opmode != IEEE80211_M_STA)
+ break;
+
+ uerror = rum_setbits(sc, RT2573_MAC_CSR11, RT2573_AUTO_WAKEUP);
+ if (uerror != USB_ERR_NORMAL_COMPLETION) {
+ ret = EIO;
+ break;
+ }
+
+ uerror = rum_setbits(sc, RT2573_TXRX_CSR4, RT2573_ACKCTS_PWRMGT);
+ if (uerror != USB_ERR_NORMAL_COMPLETION) {
+ ret = EIO;
+ break;
+ }
+
+ ret = rum_set_power_state(sc, 1);
+ if (ret != 0) {
+ device_printf(sc->sc_dev,
+ "%s: could not move to the SLEEP state: %s\n",
+ __func__, usbd_errstr(uerror));
+ }
+ break;
+ default:
+ break;
+ }
+ RUM_UNLOCK(sc);
+ IEEE80211_LOCK(ic);
+ return (ret == 0 ? rvp->newstate(vap, nstate, arg) : ret);
+}
+
+static void
+rum_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct rum_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211vap *vap;
+ struct rum_tx_data *data;
+ struct mbuf *m;
+ struct usb_page_cache *pc;
+ unsigned len;
+ int actlen, sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "transfer complete, %d bytes\n", actlen);
+
+ /* free resources */
+ data = usbd_xfer_get_priv(xfer);
+ rum_tx_free(data, 0);
+ usbd_xfer_set_priv(xfer, NULL);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ data = STAILQ_FIRST(&sc->tx_q);
+ if (data) {
+ STAILQ_REMOVE_HEAD(&sc->tx_q, next);
+ m = data->m;
+
+ if (m->m_pkthdr.len > (int)(MCLBYTES + RT2573_TX_DESC_SIZE)) {
+ DPRINTFN(0, "data overflow, %u bytes\n",
+ m->m_pkthdr.len);
+ m->m_pkthdr.len = (MCLBYTES + RT2573_TX_DESC_SIZE);
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &data->desc, RT2573_TX_DESC_SIZE);
+ usbd_m_copy_in(pc, RT2573_TX_DESC_SIZE, m, 0,
+ m->m_pkthdr.len);
+
+ vap = data->ni->ni_vap;
+ if (ieee80211_radiotap_active_vap(vap)) {
+ struct rum_tx_radiotap_header *tap = &sc->sc_txtap;
+
+ tap->wt_flags = 0;
+ tap->wt_rate = data->rate;
+ tap->wt_antenna = sc->tx_ant;
+
+ ieee80211_radiotap_tx(vap, m);
+ }
+
+ /* align end on a 4-bytes boundary */
+ len = (RT2573_TX_DESC_SIZE + m->m_pkthdr.len + 3) & ~3;
+ if ((len % 64) == 0)
+ len += 4;
+
+ DPRINTFN(11, "sending frame len=%u xferlen=%u\n",
+ m->m_pkthdr.len, len);
+
+ usbd_xfer_set_frame_len(xfer, 0, len);
+ usbd_xfer_set_priv(xfer, data);
+
+ usbd_transfer_submit(xfer);
+ }
+ rum_start(sc);
+ break;
+
+ default: /* Error */
+ DPRINTFN(11, "transfer error, %s\n",
+ usbd_errstr(error));
+
+ counter_u64_add(sc->sc_ic.ic_oerrors, 1);
+ data = usbd_xfer_get_priv(xfer);
+ if (data != NULL) {
+ rum_tx_free(data, error);
+ usbd_xfer_set_priv(xfer, NULL);
+ }
+
+ if (error != USB_ERR_CANCELLED) {
+ if (error == USB_ERR_TIMEOUT)
+ device_printf(sc->sc_dev, "device timeout\n");
+
+ /*
+ * Try to clear stall first, also if other
+ * errors occur, hence clearing stall
+ * introduces a 50 ms delay:
+ */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+rum_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct rum_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_frame_min *wh;
+ struct ieee80211_node *ni;
+ struct mbuf *m = NULL;
+ struct usb_page_cache *pc;
+ uint32_t flags;
+ uint8_t rssi = 0;
+ int len;
+
+ usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ DPRINTFN(15, "rx done, actlen=%d\n", len);
+
+ if (len < RT2573_RX_DESC_SIZE) {
+ DPRINTF("%s: xfer too short %d\n",
+ device_get_nameunit(sc->sc_dev), len);
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto tr_setup;
+ }
+
+ len -= RT2573_RX_DESC_SIZE;
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, &sc->sc_rx_desc, RT2573_RX_DESC_SIZE);
+
+ rssi = rum_get_rssi(sc, sc->sc_rx_desc.rssi);
+ flags = le32toh(sc->sc_rx_desc.flags);
+ sc->last_rx_flags = flags;
+ if (len < ((flags >> 16) & 0xfff)) {
+ DPRINTFN(5, "%s: frame is truncated from %d to %d "
+ "bytes\n", device_get_nameunit(sc->sc_dev),
+ (flags >> 16) & 0xfff, len);
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto tr_setup;
+ }
+ len = (flags >> 16) & 0xfff;
+ if (len < sizeof(struct ieee80211_frame_ack)) {
+ DPRINTFN(5, "%s: frame too short %d\n",
+ device_get_nameunit(sc->sc_dev), len);
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto tr_setup;
+ }
+ if (flags & RT2573_RX_CRC_ERROR) {
+ /*
+ * This should not happen since we did not
+ * request to receive those frames when we
+ * filled RUM_TXRX_CSR2:
+ */
+ DPRINTFN(5, "PHY or CRC error\n");
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto tr_setup;
+ }
+ if ((flags & RT2573_RX_DEC_MASK) != RT2573_RX_DEC_OK) {
+ switch (flags & RT2573_RX_DEC_MASK) {
+ case RT2573_RX_IV_ERROR:
+ DPRINTFN(5, "IV/EIV error\n");
+ break;
+ case RT2573_RX_MIC_ERROR:
+ DPRINTFN(5, "MIC error\n");
+ break;
+ case RT2573_RX_KEY_ERROR:
+ DPRINTFN(5, "Key error\n");
+ break;
+ }
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto tr_setup;
+ }
+
+ m = m_get2(len, M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (m == NULL) {
+ DPRINTF("could not allocate mbuf\n");
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto tr_setup;
+ }
+ usbd_copy_out(pc, RT2573_RX_DESC_SIZE,
+ mtod(m, uint8_t *), len);
+
+ wh = mtod(m, struct ieee80211_frame_min *);
+
+ if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) &&
+ (flags & RT2573_RX_CIP_MASK) !=
+ RT2573_RX_CIP_MODE(RT2573_MODE_NOSEC)) {
+ wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED;
+ m->m_flags |= M_WEP;
+ }
+
+ /* finalize mbuf */
+ m->m_pkthdr.len = m->m_len = len;
+
+ if (ieee80211_radiotap_active(ic)) {
+ struct rum_rx_radiotap_header *tap = &sc->sc_rxtap;
+
+ tap->wr_flags = 0;
+ tap->wr_rate = ieee80211_plcp2rate(sc->sc_rx_desc.rate,
+ (flags & RT2573_RX_OFDM) ?
+ IEEE80211_T_OFDM : IEEE80211_T_CCK);
+ rum_get_tsf(sc, &tap->wr_tsf);
+ tap->wr_antsignal = RT2573_NOISE_FLOOR + rssi;
+ tap->wr_antnoise = RT2573_NOISE_FLOOR;
+ tap->wr_antenna = sc->rx_ant;
+ }
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+
+ /*
+ * At the end of a USB callback it is always safe to unlock
+ * the private mutex of a device! That is why we do the
+ * "ieee80211_input" here, and not some lines up!
+ */
+ RUM_UNLOCK(sc);
+ if (m) {
+ if (m->m_len >= sizeof(struct ieee80211_frame_min))
+ ni = ieee80211_find_rxnode(ic, wh);
+ else
+ ni = NULL;
+
+ if (ni != NULL) {
+ (void) ieee80211_input(ni, m, rssi,
+ RT2573_NOISE_FLOOR);
+ ieee80211_free_node(ni);
+ } else
+ (void) ieee80211_input_all(ic, m, rssi,
+ RT2573_NOISE_FLOOR);
+ }
+ RUM_LOCK(sc);
+ rum_start(sc);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+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 */
+}
+
+/*
+ * Map net80211 cipher to RT2573 security mode.
+ */
+static uint8_t
+rum_crypto_mode(struct rum_softc *sc, u_int cipher, int keylen)
+{
+ switch (cipher) {
+ case IEEE80211_CIPHER_WEP:
+ return (keylen < 8 ? RT2573_MODE_WEP40 : RT2573_MODE_WEP104);
+ case IEEE80211_CIPHER_TKIP:
+ return RT2573_MODE_TKIP;
+ case IEEE80211_CIPHER_AES_CCM:
+ return RT2573_MODE_AES_CCMP;
+ default:
+ device_printf(sc->sc_dev, "unknown cipher %d\n", cipher);
+ return 0;
+ }
+}
+
+static void
+rum_setup_tx_desc(struct rum_softc *sc, struct rum_tx_desc *desc,
+ struct ieee80211_key *k, uint32_t flags, uint8_t xflags, uint8_t qid,
+ int hdrlen, int len, int rate)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct wmeParams *wmep = &sc->wme_params[qid];
+ uint16_t plcp_length;
+ int remainder;
+
+ flags |= RT2573_TX_VALID;
+ flags |= len << 16;
+
+ if (k != NULL && !(k->wk_flags & IEEE80211_KEY_SWCRYPT)) {
+ const struct ieee80211_cipher *cip = k->wk_cipher;
+
+ len += cip->ic_header + cip->ic_trailer + cip->ic_miclen;
+
+ desc->eiv = 0; /* for WEP */
+ cip->ic_setiv(k, (uint8_t *)&desc->iv);
+ }
+
+ /* setup PLCP fields */
+ desc->plcp_signal = rum_plcp_signal(rate);
+ desc->plcp_service = 4;
+
+ len += IEEE80211_CRC_LEN;
+ if (ieee80211_rate2phytype(ic->ic_rt, rate) == IEEE80211_T_OFDM) {
+ flags |= RT2573_TX_OFDM;
+
+ plcp_length = len & 0xfff;
+ desc->plcp_length_hi = plcp_length >> 6;
+ desc->plcp_length_lo = plcp_length & 0x3f;
+ } else {
+ if (rate == 0)
+ rate = 2; /* avoid division by zero */
+ plcp_length = howmany(16 * len, 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;
+ }
+
+ desc->flags = htole32(flags);
+ desc->hdrlen = hdrlen;
+ desc->xflags = xflags;
+
+ desc->wme = htole16(RT2573_QID(qid) |
+ RT2573_AIFSN(wmep->wmep_aifsn) |
+ RT2573_LOGCWMIN(wmep->wmep_logcwmin) |
+ RT2573_LOGCWMAX(wmep->wmep_logcwmax));
+}
+
+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;
+ struct rum_tx_data *data;
+ struct mbuf *mprot;
+ int protrate, flags;
+
+ RUM_LOCK_ASSERT(sc);
+
+ mprot = ieee80211_alloc_prot(ni, m, rate, prot);
+ if (mprot == NULL) {
+ if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1);
+ device_printf(sc->sc_dev,
+ "could not allocate mbuf for protection mode %d\n", prot);
+ return (ENOBUFS);
+ }
+
+ protrate = ieee80211_ctl_rate(ic->ic_rt, rate);
+ flags = 0;
+ if (prot == IEEE80211_PROT_RTSCTS)
+ flags |= RT2573_TX_NEED_ACK;
+
+ data = STAILQ_FIRST(&sc->tx_free);
+ STAILQ_REMOVE_HEAD(&sc->tx_free, next);
+ sc->tx_nfree--;
+
+ data->m = mprot;
+ data->ni = ieee80211_ref_node(ni);
+ data->rate = protrate;
+ rum_setup_tx_desc(sc, &data->desc, NULL, flags, 0, 0, 0,
+ mprot->m_pkthdr.len, protrate);
+
+ STAILQ_INSERT_TAIL(&sc->tx_q, data, next);
+ usbd_transfer_start(sc->sc_xfer[RUM_BULK_WR]);
+
+ return 0;
+}
+
+static uint32_t
+rum_tx_crypto_flags(struct rum_softc *sc, struct ieee80211_node *ni,
+ const struct ieee80211_key *k)
+{
+ struct ieee80211vap *vap = ni->ni_vap;
+ u_int cipher;
+ uint32_t flags = 0;
+ uint8_t mode, pos;
+
+ if (!(k->wk_flags & IEEE80211_KEY_SWCRYPT)) {
+ cipher = k->wk_cipher->ic_cipher;
+ pos = k->wk_keyix;
+ mode = rum_crypto_mode(sc, cipher, k->wk_keylen);
+ if (mode == 0)
+ return 0;
+
+ flags |= RT2573_TX_CIP_MODE(mode);
+
+ /* Do not trust GROUP flag */
+ if (ieee80211_is_key_unicast(vap, k))
+ flags |= RT2573_TX_KEY_PAIR;
+ else
+ pos += 0 * RT2573_SKEY_MAX; /* vap id */
+
+ flags |= RT2573_TX_KEY_ID(pos);
+
+ if (cipher == IEEE80211_CIPHER_TKIP)
+ flags |= RT2573_TX_TKIPMIC;
+ }
+
+ return flags;
+}
+
+static int
+rum_tx_mgt(struct rum_softc *sc, struct mbuf *m0, struct ieee80211_node *ni)
+{
+ const struct ieee80211_txparam *tp = ni->ni_txparms;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct rum_tx_data *data;
+ struct ieee80211_frame *wh;
+ struct ieee80211_key *k = NULL;
+ uint32_t flags = 0;
+ uint16_t dur;
+ uint8_t ac, type, xflags = 0;
+ int hdrlen;
+
+ RUM_LOCK_ASSERT(sc);
+
+ data = STAILQ_FIRST(&sc->tx_free);
+ STAILQ_REMOVE_HEAD(&sc->tx_free, next);
+ sc->tx_nfree--;
+
+ wh = mtod(m0, struct ieee80211_frame *);
+ type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
+ hdrlen = ieee80211_anyhdrsize(wh);
+ ac = M_WME_GETAC(m0);
+
+ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
+ k = ieee80211_crypto_get_txkey(ni, m0);
+ if (k == NULL)
+ return (ENOENT);
+
+ if ((k->wk_flags & IEEE80211_KEY_SWCRYPT) &&
+ !k->wk_cipher->ic_encap(k, m0))
+ return (ENOBUFS);
+
+ wh = mtod(m0, struct ieee80211_frame *);
+ }
+
+ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
+ flags |= RT2573_TX_NEED_ACK;
+
+ dur = ieee80211_ack_duration(ic->ic_rt, tp->mgmtrate,
+ ic->ic_flags & IEEE80211_F_SHPREAMBLE);
+ USETW(wh->i_dur, dur);
+
+ /* tell hardware to add timestamp for probe responses */
+ if (IEEE80211_IS_MGMT_PROBE_RESP(wh))
+ flags |= RT2573_TX_TIMESTAMP;
+ }
+
+ if (type != IEEE80211_FC0_TYPE_CTL && !IEEE80211_QOS_HAS_SEQ(wh))
+ xflags |= RT2573_TX_HWSEQ;
+
+ if (k != NULL)
+ flags |= rum_tx_crypto_flags(sc, ni, k);
+
+ data->m = m0;
+ data->ni = ni;
+ data->rate = tp->mgmtrate;
+
+ rum_setup_tx_desc(sc, &data->desc, k, flags, xflags, ac, hdrlen,
+ m0->m_pkthdr.len, tp->mgmtrate);
+
+ DPRINTFN(10, "sending mgt frame len=%d rate=%d\n",
+ m0->m_pkthdr.len + (int)RT2573_TX_DESC_SIZE, tp->mgmtrate);
+
+ STAILQ_INSERT_TAIL(&sc->tx_q, data, next);
+ usbd_transfer_start(sc->sc_xfer[RUM_BULK_WR]);
+
+ 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 ieee80211com *ic = ni->ni_ic;
+ struct ieee80211_frame *wh;
+ struct rum_tx_data *data;
+ uint32_t flags;
+ uint8_t ac, type, xflags = 0;
+ int rate, error;
+
+ RUM_LOCK_ASSERT(sc);
+
+ wh = mtod(m0, struct ieee80211_frame *);
+ type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
+
+ ac = params->ibp_pri & 3;
+
+ rate = params->ibp_rate0;
+ if (!ieee80211_isratevalid(ic->ic_rt, rate))
+ 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 || sc->tx_nfree == 0)
+ return (ENOBUFS);
+
+ flags |= RT2573_TX_LONG_RETRY | RT2573_TX_IFS_SIFS;
+ }
+
+ if (type != IEEE80211_FC0_TYPE_CTL && !IEEE80211_QOS_HAS_SEQ(wh))
+ xflags |= RT2573_TX_HWSEQ;
+
+ data = STAILQ_FIRST(&sc->tx_free);
+ STAILQ_REMOVE_HEAD(&sc->tx_free, next);
+ sc->tx_nfree--;
+
+ data->m = m0;
+ data->ni = ni;
+ data->rate = rate;
+
+ /* XXX need to setup descriptor ourself */
+ rum_setup_tx_desc(sc, &data->desc, NULL, flags, xflags, ac, 0,
+ m0->m_pkthdr.len, rate);
+
+ DPRINTFN(10, "sending raw frame len=%u rate=%u\n",
+ m0->m_pkthdr.len, rate);
+
+ STAILQ_INSERT_TAIL(&sc->tx_q, data, next);
+ usbd_transfer_start(sc->sc_xfer[RUM_BULK_WR]);
+
+ 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 ieee80211com *ic = &sc->sc_ic;
+ struct rum_tx_data *data;
+ struct ieee80211_frame *wh;
+ const struct ieee80211_txparam *tp = ni->ni_txparms;
+ struct ieee80211_key *k = NULL;
+ uint32_t flags = 0;
+ uint16_t dur;
+ uint8_t ac, type, qos, xflags = 0;
+ int error, hdrlen, rate;
+
+ RUM_LOCK_ASSERT(sc);
+
+ wh = mtod(m0, struct ieee80211_frame *);
+ type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
+ hdrlen = ieee80211_anyhdrsize(wh);
+
+ if (IEEE80211_QOS_HAS_SEQ(wh))
+ qos = ((const struct ieee80211_qosframe *)wh)->i_qos[0];
+ else
+ qos = 0;
+ ac = M_WME_GETAC(m0);
+
+ if (m0->m_flags & M_EAPOL)
+ rate = tp->mgmtrate;
+ else if (IEEE80211_IS_MULTICAST(wh->i_addr1))
+ rate = tp->mcastrate;
+ else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE)
+ rate = tp->ucastrate;
+ else {
+ (void) ieee80211_ratectl_rate(ni, NULL, 0);
+ rate = ieee80211_node_get_txrate_dot11rate(ni);
+ }
+
+ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
+ k = ieee80211_crypto_get_txkey(ni, m0);
+ if (k == NULL) {
+ m_freem(m0);
+ return (ENOENT);
+ }
+ if ((k->wk_flags & IEEE80211_KEY_SWCRYPT) &&
+ !k->wk_cipher->ic_encap(k, m0)) {
+ m_freem(m0);
+ return (ENOBUFS);
+ }
+
+ /* packet header may have moved, reset our local pointer */
+ wh = mtod(m0, struct ieee80211_frame *);
+ }
+
+ if (type != IEEE80211_FC0_TYPE_CTL && !IEEE80211_QOS_HAS_SEQ(wh))
+ xflags |= RT2573_TX_HWSEQ;
+
+ 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(ic->ic_rt, rate) == IEEE80211_T_OFDM)
+ prot = ic->ic_protmode;
+ if (prot != IEEE80211_PROT_NONE) {
+ error = rum_sendprot(sc, m0, ni, prot, rate);
+ if (error || sc->tx_nfree == 0) {
+ m_freem(m0);
+ return ENOBUFS;
+ }
+ flags |= RT2573_TX_LONG_RETRY | RT2573_TX_IFS_SIFS;
+ }
+ }
+
+ if (k != NULL)
+ flags |= rum_tx_crypto_flags(sc, ni, k);
+
+ data = STAILQ_FIRST(&sc->tx_free);
+ STAILQ_REMOVE_HEAD(&sc->tx_free, next);
+ sc->tx_nfree--;
+
+ data->m = m0;
+ data->ni = ni;
+ data->rate = rate;
+
+ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
+ /* Unicast frame, check if an ACK is expected. */
+ if (!qos || (qos & IEEE80211_QOS_ACKPOLICY) !=
+ IEEE80211_QOS_ACKPOLICY_NOACK)
+ flags |= RT2573_TX_NEED_ACK;
+
+ dur = ieee80211_ack_duration(ic->ic_rt, rate,
+ ic->ic_flags & IEEE80211_F_SHPREAMBLE);
+ USETW(wh->i_dur, dur);
+ }
+
+ rum_setup_tx_desc(sc, &data->desc, k, flags, xflags, ac, hdrlen,
+ m0->m_pkthdr.len, rate);
+
+ DPRINTFN(10, "sending frame len=%d rate=%d\n",
+ m0->m_pkthdr.len + (int)RT2573_TX_DESC_SIZE, rate);
+
+ STAILQ_INSERT_TAIL(&sc->tx_q, data, next);
+ usbd_transfer_start(sc->sc_xfer[RUM_BULK_WR]);
+
+ return 0;
+}
+
+static int
+rum_transmit(struct ieee80211com *ic, struct mbuf *m)
+{
+ struct rum_softc *sc = ic->ic_softc;
+ int error;
+
+ RUM_LOCK(sc);
+ if (!sc->sc_running) {
+ RUM_UNLOCK(sc);
+ return (ENXIO);
+ }
+ error = mbufq_enqueue(&sc->sc_snd, m);
+ if (error) {
+ RUM_UNLOCK(sc);
+ return (error);
+ }
+ rum_start(sc);
+ RUM_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+rum_start(struct rum_softc *sc)
+{
+ struct ieee80211_node *ni;
+ struct mbuf *m;
+
+ RUM_LOCK_ASSERT(sc);
+
+ if (!sc->sc_running)
+ return;
+
+ while (sc->tx_nfree >= RUM_TX_MINFREE &&
+ (m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
+ ni = (struct ieee80211_node *) m->m_pkthdr.rcvif;
+ if (rum_tx_data(sc, m, ni) != 0) {
+ if_inc_counter(ni->ni_vap->iv_ifp,
+ IFCOUNTER_OERRORS, 1);
+ ieee80211_free_node(ni);
+ break;
+ }
+ }
+}
+
+static void
+rum_parent(struct ieee80211com *ic)
+{
+ struct rum_softc *sc = ic->ic_softc;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+
+ RUM_LOCK(sc);
+ if (sc->sc_detached) {
+ RUM_UNLOCK(sc);
+ return;
+ }
+ RUM_UNLOCK(sc);
+
+ if (ic->ic_nrunning > 0) {
+ if (rum_init(sc) == 0)
+ ieee80211_start_all(ic);
+ else
+ ieee80211_stop(vap);
+ } else
+ rum_stop(sc);
+}
+
+static void
+rum_eeprom_read(struct rum_softc *sc, uint16_t addr, void *buf, int len)
+{
+ struct usb_device_request req;
+ usb_error_t 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 = rum_do_request(sc, &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)
+{
+ struct usb_device_request req;
+ usb_error_t 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 = rum_do_request(sc, &req, buf);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "could not multi read MAC register: %s\n",
+ usbd_errstr(error));
+ }
+}
+
+static usb_error_t
+rum_write(struct rum_softc *sc, uint16_t reg, uint32_t val)
+{
+ uint32_t tmp = htole32(val);
+
+ return (rum_write_multi(sc, reg, &tmp, sizeof tmp));
+}
+
+static usb_error_t
+rum_write_multi(struct rum_softc *sc, uint16_t reg, void *buf, size_t len)
+{
+ struct usb_device_request req;
+ usb_error_t error;
+ size_t offset;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = RT2573_WRITE_MULTI_MAC;
+ USETW(req.wValue, 0);
+
+ /* write at most 64 bytes at a time */
+ for (offset = 0; offset < len; offset += 64) {
+ USETW(req.wIndex, reg + offset);
+ USETW(req.wLength, MIN(len - offset, 64));
+
+ error = rum_do_request(sc, &req, (char *)buf + offset);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "could not multi write MAC register: %s\n",
+ usbd_errstr(error));
+ return (error);
+ }
+ }
+
+ return (USB_ERR_NORMAL_COMPLETION);
+}
+
+static usb_error_t
+rum_setbits(struct rum_softc *sc, uint16_t reg, uint32_t mask)
+{
+ return (rum_write(sc, reg, rum_read(sc, reg) | mask));
+}
+
+static usb_error_t
+rum_clrbits(struct rum_softc *sc, uint16_t reg, uint32_t mask)
+{
+ return (rum_write(sc, reg, rum_read(sc, reg) & ~mask));
+}
+
+static usb_error_t
+rum_modbits(struct rum_softc *sc, uint16_t reg, uint32_t set, uint32_t unset)
+{
+ return (rum_write(sc, reg, (rum_read(sc, reg) & ~unset) | set));
+}
+
+static int
+rum_bbp_busy(struct rum_softc *sc)
+{
+ int ntries;
+
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (!(rum_read(sc, RT2573_PHY_CSR3) & RT2573_BBP_BUSY))
+ break;
+ if (rum_pause(sc, hz / 100))
+ break;
+ }
+ if (ntries == 100)
+ return (ETIMEDOUT);
+
+ return (0);
+}
+
+static void
+rum_bbp_write(struct rum_softc *sc, uint8_t reg, uint8_t val)
+{
+ uint32_t tmp;
+
+ DPRINTFN(2, "reg=0x%08x\n", reg);
+
+ if (rum_bbp_busy(sc) != 0) {
+ 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;
+
+ DPRINTFN(2, "reg=0x%08x\n", reg);
+
+ if (rum_bbp_busy(sc) != 0) {
+ 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;
+ if (rum_pause(sc, hz / 100))
+ break;
+ }
+
+ 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 < 100; ntries++) {
+ if (!(rum_read(sc, RT2573_PHY_CSR4) & RT2573_RF_BUSY))
+ break;
+ if (rum_pause(sc, hz / 100))
+ break;
+ }
+ if (ntries == 100) {
+ 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 ieee80211com *ic = &sc->sc_ic;
+
+ if (!IEEE80211_IS_CHAN_5GHZ(ic->ic_bsschan)) {
+ rum_setbits(sc, RT2573_TXRX_CSR4,
+ RT2573_MRR_ENABLED | RT2573_MRR_CCK_FALLBACK);
+ } else {
+ rum_modbits(sc, RT2573_TXRX_CSR4,
+ RT2573_MRR_ENABLED, RT2573_MRR_CCK_FALLBACK);
+ }
+}
+
+static void
+rum_set_txpreamble(struct rum_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ if (ic->ic_flags & IEEE80211_F_SHPREAMBLE)
+ rum_setbits(sc, RT2573_TXRX_CSR4, RT2573_SHORT_PREAMBLE);
+ else
+ rum_clrbits(sc, RT2573_TXRX_CSR4, RT2573_SHORT_PREAMBLE);
+}
+
+static void
+rum_set_basicrates(struct rum_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ /* 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;
+
+ /* 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);
+
+ if (IEEE80211_IS_CHAN_2GHZ(c)) {
+ rum_modbits(sc, RT2573_PHY_CSR0, RT2573_PA_PE_2GHZ,
+ RT2573_PA_PE_5GHZ);
+ } else {
+ rum_modbits(sc, RT2573_PHY_CSR0, RT2573_PA_PE_5GHZ,
+ RT2573_PA_PE_2GHZ);
+ }
+}
+
+static void
+rum_set_chan(struct rum_softc *sc, struct ieee80211_channel *c)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ const struct rfprog *rfprog;
+ uint8_t bbp3, bbp94 = RT2573_BBPR94_DEFAULT;
+ int8_t power;
+ 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);
+
+ rum_pause(sc, hz / 100);
+
+ /* 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);
+
+ /* give the chip some extra time to do the switchover */
+ rum_pause(sc, hz / 100);
+}
+
+static void
+rum_set_maxretry(struct rum_softc *sc, struct ieee80211vap *vap)
+{
+ struct ieee80211_node *ni = vap->iv_bss;
+ const struct ieee80211_txparam *tp = ni->ni_txparms;
+ struct rum_vap *rvp = RUM_VAP(vap);
+
+ rvp->maxretry = MIN(tp->maxretry, 0xf);
+
+ rum_modbits(sc, RT2573_TXRX_CSR4, RT2573_SHORT_RETRY(rvp->maxretry) |
+ RT2573_LONG_RETRY(rvp->maxretry),
+ RT2573_SHORT_RETRY_MASK | RT2573_LONG_RETRY_MASK);
+}
+
+/*
+ * Enable TSF synchronization and tell h/w to start sending beacons for IBSS
+ * and HostAP operating modes.
+ */
+static int
+rum_enable_tsf_sync(struct rum_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ uint32_t tmp;
+ uint16_t bintval;
+
+ if (vap->iv_opmode != IEEE80211_M_STA) {
+ /*
+ * Change default 16ms TBTT adjustment to 8ms.
+ * Must be done before enabling beacon generation.
+ */
+ if (rum_write(sc, RT2573_TXRX_CSR10, 1 << 12 | 8) != 0)
+ return EIO;
+ }
+
+ tmp = rum_read(sc, RT2573_TXRX_CSR9) & 0xff000000;
+
+ /* set beacon interval (in 1/16ms unit) */
+ bintval = vap->iv_bss->ni_intval;
+ tmp |= bintval * 16;
+ tmp |= RT2573_TSF_TIMER_EN | RT2573_TBTT_TIMER_EN;
+
+ switch (vap->iv_opmode) {
+ case IEEE80211_M_STA:
+ /*
+ * Local TSF is always updated with remote TSF on beacon
+ * reception.
+ */
+ tmp |= RT2573_TSF_SYNC_MODE(RT2573_TSF_SYNC_MODE_STA);
+ break;
+ case IEEE80211_M_IBSS:
+ /*
+ * Local TSF is updated with remote TSF on beacon reception
+ * only if the remote TSF is greater than local TSF.
+ */
+ tmp |= RT2573_TSF_SYNC_MODE(RT2573_TSF_SYNC_MODE_IBSS);
+ tmp |= RT2573_BCN_TX_EN;
+ break;
+ case IEEE80211_M_HOSTAP:
+ /* SYNC with nobody */
+ tmp |= RT2573_TSF_SYNC_MODE(RT2573_TSF_SYNC_MODE_HOSTAP);
+ tmp |= RT2573_BCN_TX_EN;
+ break;
+ default:
+ device_printf(sc->sc_dev,
+ "Enabling TSF failed. undefined opmode %d\n",
+ vap->iv_opmode);
+ return EINVAL;
+ }
+
+ if (rum_write(sc, RT2573_TXRX_CSR9, tmp) != 0)
+ return EIO;
+
+ /* refresh current sleep time */
+ return (rum_set_sleep_time(sc, bintval));
+}
+
+static void
+rum_enable_tsf(struct rum_softc *sc)
+{
+ rum_modbits(sc, RT2573_TXRX_CSR9, RT2573_TSF_TIMER_EN |
+ RT2573_TSF_SYNC_MODE(RT2573_TSF_SYNC_MODE_DIS), 0x00ffffff);
+}
+
+static void
+rum_abort_tsf_sync(struct rum_softc *sc)
+{
+ rum_clrbits(sc, RT2573_TXRX_CSR9, 0x00ffffff);
+}
+
+static void
+rum_get_tsf(struct rum_softc *sc, uint64_t *buf)
+{
+ rum_read_multi(sc, RT2573_TXRX_CSR12, buf, sizeof (*buf));
+}
+
+static void
+rum_update_slot_cb(struct rum_softc *sc, union sec_param *data, uint8_t rvp_id)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint8_t slottime;
+
+ slottime = IEEE80211_GET_SLOTTIME(ic);
+
+ rum_modbits(sc, RT2573_MAC_CSR9, slottime, 0xff);
+
+ DPRINTF("setting slot time to %uus\n", slottime);
+}
+
+static void
+rum_update_slot(struct ieee80211com *ic)
+{
+ rum_cmd_sleepable(ic->ic_softc, NULL, 0, 0, rum_update_slot_cb);
+}
+
+static int
+rum_wme_update(struct ieee80211com *ic)
+{
+ struct chanAccParams chp;
+ const struct wmeParams *chanp;
+ struct rum_softc *sc = ic->ic_softc;
+ int error = 0;
+
+ ieee80211_wme_ic_getparams(ic, &chp);
+ chanp = chp.cap_wmeParams;
+
+ RUM_LOCK(sc);
+ error = rum_write(sc, RT2573_AIFSN_CSR,
+ chanp[WME_AC_VO].wmep_aifsn << 12 |
+ chanp[WME_AC_VI].wmep_aifsn << 8 |
+ chanp[WME_AC_BK].wmep_aifsn << 4 |
+ chanp[WME_AC_BE].wmep_aifsn);
+ if (error)
+ goto print_err;
+ error = rum_write(sc, RT2573_CWMIN_CSR,
+ chanp[WME_AC_VO].wmep_logcwmin << 12 |
+ chanp[WME_AC_VI].wmep_logcwmin << 8 |
+ chanp[WME_AC_BK].wmep_logcwmin << 4 |
+ chanp[WME_AC_BE].wmep_logcwmin);
+ if (error)
+ goto print_err;
+ error = rum_write(sc, RT2573_CWMAX_CSR,
+ chanp[WME_AC_VO].wmep_logcwmax << 12 |
+ chanp[WME_AC_VI].wmep_logcwmax << 8 |
+ chanp[WME_AC_BK].wmep_logcwmax << 4 |
+ chanp[WME_AC_BE].wmep_logcwmax);
+ if (error)
+ goto print_err;
+ error = rum_write(sc, RT2573_TXOP01_CSR,
+ chanp[WME_AC_BK].wmep_txopLimit << 16 |
+ chanp[WME_AC_BE].wmep_txopLimit);
+ if (error)
+ goto print_err;
+ error = rum_write(sc, RT2573_TXOP23_CSR,
+ chanp[WME_AC_VO].wmep_txopLimit << 16 |
+ chanp[WME_AC_VI].wmep_txopLimit);
+ if (error)
+ goto print_err;
+
+ memcpy(sc->wme_params, chanp, sizeof(*chanp) * WME_NUM_AC);
+
+print_err:
+ RUM_UNLOCK(sc);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "%s: WME update failed, error %d\n",
+ __func__, error);
+ }
+
+ return (error);
+}
+
+static void
+rum_set_bssid(struct rum_softc *sc, const uint8_t *bssid)
+{
+
+ rum_write(sc, RT2573_MAC_CSR4,
+ bssid[0] | bssid[1] << 8 | bssid[2] << 16 | bssid[3] << 24);
+ rum_write(sc, RT2573_MAC_CSR5,
+ bssid[4] | bssid[5] << 8 | RT2573_NUM_BSSID_MSK(1));
+}
+
+static void
+rum_set_macaddr(struct rum_softc *sc, const uint8_t *addr)
+{
+
+ rum_write(sc, RT2573_MAC_CSR2,
+ addr[0] | addr[1] << 8 | addr[2] << 16 | addr[3] << 24);
+ rum_write(sc, RT2573_MAC_CSR3,
+ addr[4] | addr[5] << 8 | 0xff << 16);
+}
+
+static void
+rum_setpromisc(struct rum_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ if (ic->ic_promisc == 0)
+ rum_setbits(sc, RT2573_TXRX_CSR0, RT2573_DROP_NOT_TO_ME);
+ else
+ rum_clrbits(sc, RT2573_TXRX_CSR0, RT2573_DROP_NOT_TO_ME);
+
+ DPRINTF("%s promiscuous mode\n", ic->ic_promisc > 0 ?
+ "entering" : "leaving");
+}
+
+static void
+rum_update_promisc(struct ieee80211com *ic)
+{
+ struct rum_softc *sc = ic->ic_softc;
+
+ RUM_LOCK(sc);
+ if (sc->sc_running)
+ rum_setpromisc(sc);
+ RUM_UNLOCK(sc);
+}
+
+static void
+rum_update_mcast(struct ieee80211com *ic)
+{
+ /* Ignore. */
+}
+
+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)
+{
+ uint16_t val;
+#ifdef RUM_DEBUG
+ int i;
+#endif
+
+ /* read MAC address */
+ rum_eeprom_read(sc, RT2573_EEPROM_ADDRESS, sc->sc_ic.ic_macaddr, 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_wakeup(struct rum_softc *sc)
+{
+ unsigned ntries;
+
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (rum_read(sc, RT2573_MAC_CSR12) & 8)
+ break;
+ rum_write(sc, RT2573_MAC_CSR12, 4); /* force wakeup */
+ if (rum_pause(sc, hz / 100))
+ break;
+ }
+ if (ntries == 100) {
+ device_printf(sc->sc_dev,
+ "timeout waiting for BBP/RF to wakeup\n");
+ return (ETIMEDOUT);
+ }
+
+ return (0);
+}
+
+static int
+rum_bbp_init(struct rum_softc *sc)
+{
+ 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;
+ if (rum_pause(sc, hz / 100))
+ break;
+ }
+ 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 < nitems(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;
+}
+
+static void
+rum_clr_shkey_regs(struct rum_softc *sc)
+{
+ rum_write(sc, RT2573_SEC_CSR0, 0);
+ rum_write(sc, RT2573_SEC_CSR1, 0);
+ rum_write(sc, RT2573_SEC_CSR5, 0);
+}
+
+static int
+rum_init(struct rum_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ uint32_t tmp;
+ int i, ret;
+
+ RUM_LOCK(sc);
+ if (sc->sc_running) {
+ ret = 0;
+ goto end;
+ }
+
+ /* initialize MAC registers to default values */
+ for (i = 0; i < nitems(rum_def_mac); i++)
+ rum_write(sc, rum_def_mac[i].reg, rum_def_mac[i].val);
+
+ /* reset some WME parameters to default values */
+ sc->wme_params[0].wmep_aifsn = 2;
+ sc->wme_params[0].wmep_logcwmin = 4;
+ sc->wme_params[0].wmep_logcwmax = 10;
+
+ /* set host ready */
+ rum_write(sc, RT2573_MAC_CSR1, RT2573_RESET_ASIC | RT2573_RESET_BBP);
+ rum_write(sc, RT2573_MAC_CSR1, 0);
+
+ /* wait for BBP/RF to wakeup */
+ if ((ret = rum_bbp_wakeup(sc)) != 0)
+ goto end;
+
+ if ((ret = rum_bbp_init(sc)) != 0)
+ goto end;
+
+ /* 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);
+
+ /* clear security registers (if required) */
+ if (sc->sc_clr_shkeys == 0) {
+ rum_clr_shkey_regs(sc);
+ sc->sc_clr_shkeys = 1;
+ }
+
+ rum_set_macaddr(sc, vap ? vap->iv_myaddr : ic->ic_macaddr);
+
+ /* initialize ASIC */
+ rum_write(sc, RT2573_MAC_CSR1, RT2573_HOST_READY);
+
+ /*
+ * Allocate Tx and Rx xfer queues.
+ */
+ rum_setup_tx_list(sc);
+
+ /* 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 (ic->ic_promisc == 0)
+ tmp |= RT2573_DROP_NOT_TO_ME;
+ }
+ rum_write(sc, RT2573_TXRX_CSR0, tmp);
+
+ sc->sc_running = 1;
+ usbd_xfer_set_stall(sc->sc_xfer[RUM_BULK_WR]);
+ usbd_transfer_start(sc->sc_xfer[RUM_BULK_RD]);
+
+end: RUM_UNLOCK(sc);
+
+ if (ret != 0)
+ rum_stop(sc);
+
+ return ret;
+}
+
+static void
+rum_stop(struct rum_softc *sc)
+{
+
+ RUM_LOCK(sc);
+ if (!sc->sc_running) {
+ RUM_UNLOCK(sc);
+ return;
+ }
+ sc->sc_running = 0;
+ RUM_UNLOCK(sc);
+
+ /*
+ * Drain the USB transfers, if not already drained:
+ */
+ usbd_transfer_drain(sc->sc_xfer[RUM_BULK_WR]);
+ usbd_transfer_drain(sc->sc_xfer[RUM_BULK_RD]);
+
+ RUM_LOCK(sc);
+ rum_unsetup_tx_list(sc);
+
+ /* disable Rx */
+ rum_setbits(sc, RT2573_TXRX_CSR0, RT2573_DISABLE_RX);
+
+ /* reset ASIC */
+ rum_write(sc, RT2573_MAC_CSR1, RT2573_RESET_ASIC | RT2573_RESET_BBP);
+ rum_write(sc, RT2573_MAC_CSR1, 0);
+ RUM_UNLOCK(sc);
+}
+
+static void
+rum_load_microcode(struct rum_softc *sc, const uint8_t *ucode, size_t size)
+{
+ uint16_t reg = RT2573_MCU_CODE_BASE;
+ usb_error_t err;
+
+ /* copy firmware image into NIC */
+ for (; size >= 4; reg += 4, ucode += 4, size -= 4) {
+ err = rum_write(sc, reg, UGETDW(ucode));
+ if (err) {
+ /* firmware already loaded ? */
+ device_printf(sc->sc_dev, "Firmware load "
+ "failure! (ignored)\n");
+ break;
+ }
+ }
+
+ err = rum_do_mcu_request(sc, RT2573_MCU_RUN);
+ if (err != USB_ERR_NORMAL_COMPLETION) {
+ device_printf(sc->sc_dev, "could not run firmware: %s\n",
+ usbd_errstr(err));
+ }
+
+ /* give the chip some time to boot */
+ rum_pause(sc, hz / 8);
+}
+
+static int
+rum_set_sleep_time(struct rum_softc *sc, uint16_t bintval)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ usb_error_t uerror;
+ int exp, delay;
+
+ RUM_LOCK_ASSERT(sc);
+
+ exp = ic->ic_lintval / bintval;
+ delay = ic->ic_lintval % bintval;
+
+ if (exp > RT2573_TBCN_EXP_MAX)
+ exp = RT2573_TBCN_EXP_MAX;
+ if (delay > RT2573_TBCN_DELAY_MAX)
+ delay = RT2573_TBCN_DELAY_MAX;
+
+ uerror = rum_modbits(sc, RT2573_MAC_CSR11,
+ RT2573_TBCN_EXP(exp) |
+ RT2573_TBCN_DELAY(delay),
+ RT2573_TBCN_EXP(RT2573_TBCN_EXP_MAX) |
+ RT2573_TBCN_DELAY(RT2573_TBCN_DELAY_MAX));
+
+ if (uerror != USB_ERR_NORMAL_COMPLETION)
+ return (EIO);
+
+ sc->sc_sleep_time = IEEE80211_TU_TO_TICKS(exp * bintval + delay);
+
+ return (0);
+}
+
+static int
+rum_reset(struct ieee80211vap *vap, u_long cmd)
+{
+ struct ieee80211com *ic = vap->iv_ic;
+ struct ieee80211_node *ni;
+ struct rum_softc *sc = ic->ic_softc;
+ int error;
+
+ switch (cmd) {
+ case IEEE80211_IOC_POWERSAVE:
+ case IEEE80211_IOC_PROTMODE:
+ case IEEE80211_IOC_RTSTHRESHOLD:
+ error = 0;
+ break;
+ case IEEE80211_IOC_POWERSAVESLEEP:
+ ni = ieee80211_ref_node(vap->iv_bss);
+
+ RUM_LOCK(sc);
+ error = rum_set_sleep_time(sc, ni->ni_intval);
+ if (vap->iv_state == IEEE80211_S_SLEEP) {
+ /* Use new values for wakeup timer. */
+ rum_clrbits(sc, RT2573_MAC_CSR11, RT2573_AUTO_WAKEUP);
+ rum_setbits(sc, RT2573_MAC_CSR11, RT2573_AUTO_WAKEUP);
+ }
+ /* XXX send reassoc */
+ RUM_UNLOCK(sc);
+
+ ieee80211_free_node(ni);
+ break;
+ default:
+ error = ENETRESET;
+ break;
+ }
+
+ return (error);
+}
+
+static int
+rum_set_beacon(struct rum_softc *sc, struct ieee80211vap *vap)
+{
+ struct ieee80211com *ic = vap->iv_ic;
+ struct rum_vap *rvp = RUM_VAP(vap);
+ struct mbuf *m = rvp->bcn_mbuf;
+ const struct ieee80211_txparam *tp;
+ struct rum_tx_desc desc;
+
+ RUM_LOCK_ASSERT(sc);
+
+ if (m == NULL)
+ return EINVAL;
+ if (ic->ic_bsschan == IEEE80211_CHAN_ANYC)
+ return EINVAL;
+
+ tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_bsschan)];
+ rum_setup_tx_desc(sc, &desc, NULL, RT2573_TX_TIMESTAMP,
+ RT2573_TX_HWSEQ, 0, 0, m->m_pkthdr.len, tp->mgmtrate);
+
+ /* copy the Tx descriptor into NIC memory */
+ if (rum_write_multi(sc, RT2573_HW_BCN_BASE(0), (uint8_t *)&desc,
+ RT2573_TX_DESC_SIZE) != 0)
+ return EIO;
+
+ /* copy beacon header and payload into NIC memory */
+ if (rum_write_multi(sc, RT2573_HW_BCN_BASE(0) + RT2573_TX_DESC_SIZE,
+ mtod(m, uint8_t *), m->m_pkthdr.len) != 0)
+ return EIO;
+
+ return 0;
+}
+
+static int
+rum_alloc_beacon(struct rum_softc *sc, struct ieee80211vap *vap)
+{
+ struct rum_vap *rvp = RUM_VAP(vap);
+ struct ieee80211_node *ni = vap->iv_bss;
+ struct mbuf *m;
+
+ if (ni->ni_chan == IEEE80211_CHAN_ANYC)
+ return EINVAL;
+
+ m = ieee80211_beacon_alloc(ni);
+ if (m == NULL)
+ return ENOMEM;
+
+ if (rvp->bcn_mbuf != NULL)
+ m_freem(rvp->bcn_mbuf);
+
+ rvp->bcn_mbuf = m;
+
+ return (rum_set_beacon(sc, vap));
+}
+
+static void
+rum_update_beacon_cb(struct rum_softc *sc, union sec_param *data,
+ uint8_t rvp_id)
+{
+ struct ieee80211vap *vap = data->vap;
+
+ rum_set_beacon(sc, vap);
+}
+
+static void
+rum_update_beacon(struct ieee80211vap *vap, int item)
+{
+ struct ieee80211com *ic = vap->iv_ic;
+ struct rum_softc *sc = ic->ic_softc;
+ struct rum_vap *rvp = RUM_VAP(vap);
+ struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off;
+ struct ieee80211_node *ni = vap->iv_bss;
+ struct mbuf *m = rvp->bcn_mbuf;
+ int mcast = 0;
+
+ RUM_LOCK(sc);
+ if (m == NULL) {
+ m = ieee80211_beacon_alloc(ni);
+ if (m == NULL) {
+ device_printf(sc->sc_dev,
+ "%s: could not allocate beacon frame\n", __func__);
+ RUM_UNLOCK(sc);
+ return;
+ }
+ rvp->bcn_mbuf = m;
+ }
+
+ switch (item) {
+ case IEEE80211_BEACON_ERP:
+ rum_update_slot(ic);
+ break;
+ case IEEE80211_BEACON_TIM:
+ mcast = 1; /*TODO*/
+ break;
+ default:
+ break;
+ }
+ RUM_UNLOCK(sc);
+
+ setbit(bo->bo_flags, item);
+ ieee80211_beacon_update(ni, m, mcast);
+
+ rum_cmd_sleepable(sc, &vap, sizeof(vap), 0, rum_update_beacon_cb);
+}
+
+static int
+rum_common_key_set(struct rum_softc *sc, struct ieee80211_key *k,
+ uint16_t base)
+{
+
+ if (rum_write_multi(sc, base, k->wk_key, k->wk_keylen))
+ return EIO;
+
+ if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_TKIP) {
+ if (rum_write_multi(sc, base + IEEE80211_KEYBUF_SIZE,
+ k->wk_txmic, 8))
+ return EIO;
+ if (rum_write_multi(sc, base + IEEE80211_KEYBUF_SIZE + 8,
+ k->wk_rxmic, 8))
+ return EIO;
+ }
+
+ return 0;
+}
+
+static void
+rum_group_key_set_cb(struct rum_softc *sc, union sec_param *data,
+ uint8_t rvp_id)
+{
+ struct ieee80211_key *k = &data->key;
+ uint8_t mode;
+
+ if (sc->sc_clr_shkeys == 0) {
+ rum_clr_shkey_regs(sc);
+ sc->sc_clr_shkeys = 1;
+ }
+
+ mode = rum_crypto_mode(sc, k->wk_cipher->ic_cipher, k->wk_keylen);
+ if (mode == 0)
+ goto print_err;
+
+ DPRINTFN(1, "setting group key %d for vap %d, mode %d "
+ "(tx %s, rx %s)\n", k->wk_keyix, rvp_id, mode,
+ (k->wk_flags & IEEE80211_KEY_XMIT) ? "on" : "off",
+ (k->wk_flags & IEEE80211_KEY_RECV) ? "on" : "off");
+
+ /* Install the key. */
+ if (rum_common_key_set(sc, k, RT2573_SKEY(rvp_id, k->wk_keyix)) != 0)
+ goto print_err;
+
+ /* Set cipher mode. */
+ if (rum_modbits(sc, rvp_id < 2 ? RT2573_SEC_CSR1 : RT2573_SEC_CSR5,
+ mode << (rvp_id % 2 + k->wk_keyix) * RT2573_SKEY_MAX,
+ RT2573_MODE_MASK << (rvp_id % 2 + k->wk_keyix) * RT2573_SKEY_MAX)
+ != 0)
+ goto print_err;
+
+ /* Mark this key as valid. */
+ if (rum_setbits(sc, RT2573_SEC_CSR0,
+ 1 << (rvp_id * RT2573_SKEY_MAX + k->wk_keyix)) != 0)
+ goto print_err;
+
+ return;
+
+print_err:
+ device_printf(sc->sc_dev, "%s: cannot set group key %d for vap %d\n",
+ __func__, k->wk_keyix, rvp_id);
+}
+
+static void
+rum_group_key_del_cb(struct rum_softc *sc, union sec_param *data,
+ uint8_t rvp_id)
+{
+ struct ieee80211_key *k = &data->key;
+
+ DPRINTF("%s: removing group key %d for vap %d\n", __func__,
+ k->wk_keyix, rvp_id);
+ rum_clrbits(sc,
+ rvp_id < 2 ? RT2573_SEC_CSR1 : RT2573_SEC_CSR5,
+ RT2573_MODE_MASK << (rvp_id % 2 + k->wk_keyix) * RT2573_SKEY_MAX);
+ rum_clrbits(sc, RT2573_SEC_CSR0,
+ rvp_id * RT2573_SKEY_MAX + k->wk_keyix);
+}
+
+static void
+rum_pair_key_set_cb(struct rum_softc *sc, union sec_param *data,
+ uint8_t rvp_id)
+{
+ struct ieee80211_key *k = &data->key;
+ uint8_t buf[IEEE80211_ADDR_LEN + 1];
+ uint8_t mode;
+
+ mode = rum_crypto_mode(sc, k->wk_cipher->ic_cipher, k->wk_keylen);
+ if (mode == 0)
+ goto print_err;
+
+ DPRINTFN(1, "setting pairwise key %d for vap %d, mode %d "
+ "(tx %s, rx %s)\n", k->wk_keyix, rvp_id, mode,
+ (k->wk_flags & IEEE80211_KEY_XMIT) ? "on" : "off",
+ (k->wk_flags & IEEE80211_KEY_RECV) ? "on" : "off");
+
+ /* Install the key. */
+ if (rum_common_key_set(sc, k, RT2573_PKEY(k->wk_keyix)) != 0)
+ goto print_err;
+
+ IEEE80211_ADDR_COPY(buf, k->wk_macaddr);
+ buf[IEEE80211_ADDR_LEN] = mode;
+
+ /* Set transmitter address and cipher mode. */
+ if (rum_write_multi(sc, RT2573_ADDR_ENTRY(k->wk_keyix),
+ buf, sizeof buf) != 0)
+ goto print_err;
+
+ /* Enable key table lookup for this vap. */
+ if (sc->vap_key_count[rvp_id]++ == 0)
+ if (rum_setbits(sc, RT2573_SEC_CSR4, 1 << rvp_id) != 0)
+ goto print_err;
+
+ /* Mark this key as valid. */
+ if (rum_setbits(sc,
+ k->wk_keyix < 32 ? RT2573_SEC_CSR2 : RT2573_SEC_CSR3,
+ 1 << (k->wk_keyix % 32)) != 0)
+ goto print_err;
+
+ return;
+
+print_err:
+ device_printf(sc->sc_dev,
+ "%s: cannot set pairwise key %d, vap %d\n", __func__, k->wk_keyix,
+ rvp_id);
+}
+
+static void
+rum_pair_key_del_cb(struct rum_softc *sc, union sec_param *data,
+ uint8_t rvp_id)
+{
+ struct ieee80211_key *k = &data->key;
+
+ DPRINTF("%s: removing key %d\n", __func__, k->wk_keyix);
+ rum_clrbits(sc, (k->wk_keyix < 32) ? RT2573_SEC_CSR2 : RT2573_SEC_CSR3,
+ 1 << (k->wk_keyix % 32));
+ sc->keys_bmap &= ~(1ULL << k->wk_keyix);
+ if (--sc->vap_key_count[rvp_id] == 0)
+ rum_clrbits(sc, RT2573_SEC_CSR4, 1 << rvp_id);
+}
+
+static int
+rum_key_alloc(struct ieee80211vap *vap, struct ieee80211_key *k,
+ ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix)
+{
+ struct rum_softc *sc = vap->iv_ic->ic_softc;
+ uint8_t i;
+
+ if (ieee80211_is_key_unicast(vap, k)) {
+ if (!(k->wk_flags & IEEE80211_KEY_SWCRYPT)) {
+ RUM_LOCK(sc);
+ for (i = 0; i < RT2573_ADDR_MAX; i++) {
+ if ((sc->keys_bmap & (1ULL << i)) == 0) {
+ sc->keys_bmap |= (1ULL << i);
+ *keyix = i;
+ break;
+ }
+ }
+ RUM_UNLOCK(sc);
+ if (i == RT2573_ADDR_MAX) {
+ device_printf(sc->sc_dev,
+ "%s: no free space in the key table\n",
+ __func__);
+ return 0;
+ }
+ } else
+ *keyix = 0;
+ } else {
+ *keyix = ieee80211_crypto_get_key_wepidx(vap, k);
+ }
+ *rxkeyix = *keyix;
+ return 1;
+}
+
+static int
+rum_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k)
+{
+ struct rum_softc *sc = vap->iv_ic->ic_softc;
+ int group;
+
+ if (k->wk_flags & IEEE80211_KEY_SWCRYPT) {
+ /* Not for us. */
+ return 1;
+ }
+
+ group = ieee80211_is_key_global(vap, k);
+
+ return !rum_cmd_sleepable(sc, k, sizeof(*k), 0,
+ group ? rum_group_key_set_cb : rum_pair_key_set_cb);
+}
+
+static int
+rum_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k)
+{
+ struct rum_softc *sc = vap->iv_ic->ic_softc;
+ int group;
+
+ if (k->wk_flags & IEEE80211_KEY_SWCRYPT) {
+ /* Not for us. */
+ return 1;
+ }
+
+ group = ieee80211_is_key_global(vap, k);
+
+ return !rum_cmd_sleepable(sc, k, sizeof(*k), 0,
+ group ? rum_group_key_del_cb : rum_pair_key_del_cb);
+}
+
+static int
+rum_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
+ const struct ieee80211_bpf_params *params)
+{
+ struct rum_softc *sc = ni->ni_ic->ic_softc;
+ int ret;
+
+ RUM_LOCK(sc);
+ /* prevent management frames from being sent if we're not ready */
+ if (!sc->sc_running) {
+ ret = ENETDOWN;
+ goto bad;
+ }
+ if (sc->tx_nfree < RUM_TX_MINFREE) {
+ ret = EIO;
+ goto bad;
+ }
+
+ if (params == NULL) {
+ /*
+ * Legacy path; interpret frame contents to decide
+ * precisely how to send the frame.
+ */
+ if ((ret = rum_tx_mgt(sc, m, ni)) != 0)
+ goto bad;
+ } else {
+ /*
+ * Caller supplied explicit parameters to use in
+ * sending the frame.
+ */
+ if ((ret = rum_tx_raw(sc, m, ni, params)) != 0)
+ goto bad;
+ }
+ RUM_UNLOCK(sc);
+
+ return 0;
+bad:
+ RUM_UNLOCK(sc);
+ m_freem(m);
+ return ret;
+}
+
+static void
+rum_ratectl_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);
+
+ usb_callout_reset(&rvp->ratectl_ch, hz, rum_ratectl_timeout, rvp);
+}
+
+static void
+rum_ratectl_timeout(void *arg)
+{
+ struct rum_vap *rvp = arg;
+ struct ieee80211vap *vap = &rvp->vap;
+ struct ieee80211com *ic = vap->iv_ic;
+
+ ieee80211_runtask(ic, &rvp->ratectl_task);
+}
+
+static void
+rum_ratectl_task(void *arg, int pending)
+{
+ struct rum_vap *rvp = arg;
+ struct ieee80211vap *vap = &rvp->vap;
+ struct rum_softc *sc = vap->iv_ic->ic_softc;
+ struct ieee80211_ratectl_tx_stats *txs = &sc->sc_txs;
+ int ok[3], fail;
+
+ RUM_LOCK(sc);
+ /* read and clear statistic registers (STA_CSR0 to STA_CSR5) */
+ rum_read_multi(sc, RT2573_STA_CSR0, sc->sta, sizeof(sc->sta));
+
+ ok[0] = (le32toh(sc->sta[4]) & 0xffff); /* TX ok w/o retry */
+ ok[1] = (le32toh(sc->sta[4]) >> 16); /* TX ok w/ one retry */
+ ok[2] = (le32toh(sc->sta[5]) & 0xffff); /* TX ok w/ multiple retries */
+ fail = (le32toh(sc->sta[5]) >> 16); /* TX retry-fail count */
+
+ txs->flags = IEEE80211_RATECTL_TX_STATS_RETRIES;
+ txs->nframes = ok[0] + ok[1] + ok[2] + fail;
+ txs->nsuccess = txs->nframes - fail;
+ /* XXX at least */
+ txs->nretries = ok[1] + ok[2] * 2 + fail * (rvp->maxretry + 1);
+
+ if (txs->nframes != 0)
+ ieee80211_ratectl_tx_update(vap, txs);
+
+ /* count TX retry-fail as Tx errors */
+ if_inc_counter(vap->iv_ifp, IFCOUNTER_OERRORS, fail);
+
+ usb_callout_reset(&rvp->ratectl_ch, hz, rum_ratectl_timeout, rvp);
+ RUM_UNLOCK(sc);
+}
+
+static void
+rum_scan_start(struct ieee80211com *ic)
+{
+ struct rum_softc *sc = ic->ic_softc;
+
+ RUM_LOCK(sc);
+ rum_abort_tsf_sync(sc);
+ rum_set_bssid(sc, ieee80211broadcastaddr);
+ RUM_UNLOCK(sc);
+
+}
+
+static void
+rum_scan_end(struct ieee80211com *ic)
+{
+ struct rum_softc *sc = ic->ic_softc;
+
+ if (ic->ic_flags_ext & IEEE80211_FEXT_BGSCAN) {
+ RUM_LOCK(sc);
+ if (ic->ic_opmode != IEEE80211_M_AHDEMO)
+ rum_enable_tsf_sync(sc);
+ else
+ rum_enable_tsf(sc);
+ rum_set_bssid(sc, sc->sc_bssid);
+ RUM_UNLOCK(sc);
+ }
+}
+
+static void
+rum_set_channel(struct ieee80211com *ic)
+{
+ struct rum_softc *sc = ic->ic_softc;
+
+ RUM_LOCK(sc);
+ rum_set_chan(sc, ic->ic_curchan);
+ RUM_UNLOCK(sc);
+}
+
+static void
+rum_getradiocaps(struct ieee80211com *ic,
+ int maxchans, int *nchans, struct ieee80211_channel chans[])
+{
+ struct rum_softc *sc = ic->ic_softc;
+ uint8_t bands[IEEE80211_MODE_BYTES];
+
+ memset(bands, 0, sizeof(bands));
+ setbit(bands, IEEE80211_MODE_11B);
+ setbit(bands, IEEE80211_MODE_11G);
+ ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0);
+
+ if (sc->rf_rev == RT2573_RF_5225 || sc->rf_rev == RT2573_RF_5226) {
+ setbit(bands, IEEE80211_MODE_11A);
+ ieee80211_add_channel_list_5ghz(chans, maxchans, nchans,
+ rum_chan_5ghz, nitems(rum_chan_5ghz), bands, 0);
+ }
+}
+
+static int
+rum_get_rssi(struct rum_softc *sc, uint8_t raw)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ 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 int
+rum_pause(struct rum_softc *sc, int timeout)
+{
+
+ usb_pause_mtx(&sc->sc_mtx, timeout);
+ return (0);
+}
+
+static device_method_t rum_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, rum_match),
+ DEVMETHOD(device_attach, rum_attach),
+ DEVMETHOD(device_detach, rum_detach),
+ DEVMETHOD_END
+};
+
+static driver_t rum_driver = {
+ .name = "rum",
+ .methods = rum_methods,
+ .size = sizeof(struct rum_softc),
+};
+
+DRIVER_MODULE(rum, uhub, rum_driver, NULL, NULL);
+MODULE_DEPEND(rum, wlan, 1, 1, 1);
+MODULE_DEPEND(rum, usb, 1, 1, 1);
+MODULE_VERSION(rum, 1);
+USB_PNP_HOST_INFO(rum_devs);
diff --git a/sys/dev/usb/wlan/if_rumfw.h b/sys/dev/usb/wlan/if_rumfw.h
new file mode 100644
index 000000000000..80895214d823
--- /dev/null
+++ b/sys/dev/usb/wlan/if_rumfw.h
@@ -0,0 +1,212 @@
+
+/*-
+ * 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/dev/usb/wlan/if_rumreg.h b/sys/dev/usb/wlan/if_rumreg.h
new file mode 100644
index 000000000000..348a57582859
--- /dev/null
+++ b/sys/dev/usb/wlan/if_rumreg.h
@@ -0,0 +1,305 @@
+
+/*-
+ * 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
+
+/*
+ * WME registers.
+ */
+#define RT2573_AIFSN_CSR 0x0400
+#define RT2573_CWMIN_CSR 0x0404
+#define RT2573_CWMAX_CSR 0x0408
+#define RT2573_TXOP01_CSR 0x040C
+#define RT2573_TXOP23_CSR 0x0410
+#define RT2573_MCU_CODE_BASE 0x0800
+
+/*
+ * H/w encryption/decryption support
+ */
+#define KEY_SIZE (IEEE80211_KEYBUF_SIZE + IEEE80211_MICBUF_SIZE)
+#define RT2573_ADDR_MAX 64
+#define RT2573_SKEY_MAX 4
+
+#define RT2573_SKEY(vap, kidx) (0x1000 + ((vap) * RT2573_SKEY_MAX + \
+ (kidx)) * KEY_SIZE)
+#define RT2573_PKEY(id) (0x1200 + (id) * KEY_SIZE)
+
+#define RT2573_ADDR_ENTRY(id) (0x1a00 + (id) * 8)
+
+/*
+ * Shared memory area
+ */
+#define RT2573_HW_BCN_BASE(id) (0x2400 + (id) * 0x100)
+
+/*
+ * Control and status registers.
+ */
+#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 values for register RT2573_ADDR_MODE */
+#define RT2573_MODE_MASK 0x7
+#define RT2573_MODE_NOSEC 0
+#define RT2573_MODE_WEP40 1
+#define RT2573_MODE_WEP104 2
+#define RT2573_MODE_TKIP 3
+#define RT2573_MODE_AES_CCMP 4
+#define RT2573_MODE_CKIP40 5
+#define RT2573_MODE_CKIP104 6
+
+/* 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_NUM_BSSID_MSK(n) (((n * 3) & 3) << 16)
+
+/* possible flags for register MAC_CSR11 */
+#define RT2573_AUTO_WAKEUP (1 << 15)
+#define RT2573_TBCN_EXP(n) ((n) << 8)
+#define RT2573_TBCN_EXP_MAX 0x7f
+#define RT2573_TBCN_DELAY(t) (t)
+#define RT2573_TBCN_DELAY_MAX 0xff
+
+/* 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_ACKCTS_PWRMGT (1 << 16)
+#define RT2573_SHORT_PREAMBLE (1 << 18)
+#define RT2573_MRR_ENABLED (1 << 19)
+#define RT2573_MRR_CCK_FALLBACK (1 << 22)
+#define RT2573_LONG_RETRY(max) ((max) << 24)
+#define RT2573_LONG_RETRY_MASK (0xf << 24)
+#define RT2573_SHORT_RETRY(max) ((max) << 28)
+#define RT2573_SHORT_RETRY_MASK (0xf << 28)
+
+/* possible flags for register TXRX_CSR9 */
+#define RT2573_TSF_TIMER_EN (1 << 16)
+#define RT2573_TSF_SYNC_MODE(x) (((x) & 0x3) << 17)
+#define RT2573_TSF_SYNC_MODE_DIS 0
+#define RT2573_TSF_SYNC_MODE_STA 1
+#define RT2573_TSF_SYNC_MODE_IBSS 2
+#define RT2573_TSF_SYNC_MODE_HOSTAP 3
+#define RT2573_TBTT_TIMER_EN (1 << 19)
+#define RT2573_BCN_TX_EN (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 (1U << 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
+
+/* USB vendor requests */
+#define RT2573_MCU_SLEEP 7
+#define RT2573_MCU_RUN 8
+#define RT2573_MCU_WAKEUP 9
+
+#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)
+#define RT2573_TX_TKIPMIC (1 << 8)
+#define RT2573_TX_KEY_PAIR (1 << 9)
+#define RT2573_TX_KEY_ID(id) (((id) & 0x3f) << 10)
+#define RT2573_TX_CIP_MODE(m) ((m) << 29)
+
+ 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)
+
+ uint8_t hdrlen;
+ uint8_t xflags;
+#define RT2573_TX_HWSEQ (1 << 4)
+
+ 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_UC2ME (1 << 2)
+#define RT2573_RX_MC (1 << 3)
+#define RT2573_RX_BC (1 << 4)
+#define RT2573_RX_MYBSS (1 << 5)
+#define RT2573_RX_CRC_ERROR (1 << 6)
+#define RT2573_RX_OFDM (1 << 7)
+
+#define RT2573_RX_DEC_MASK (3 << 8)
+#define RT2573_RX_DEC_OK (0 << 8)
+
+#define RT2573_RX_IV_ERROR (1 << 8)
+#define RT2573_RX_MIC_ERROR (2 << 8)
+#define RT2573_RX_KEY_ERROR (3 << 8)
+
+#define RT2573_RX_KEY_PAIR (1 << 28)
+
+#define RT2573_RX_CIP_MASK (7 << 29)
+#define RT2573_RX_CIP_MODE(m) ((m) << 29)
+
+ 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/dev/usb/wlan/if_rumvar.h b/sys/dev/usb/wlan/if_rumvar.h
new file mode 100644
index 000000000000..40981f6aa46d
--- /dev/null
+++ b/sys/dev/usb/wlan/if_rumvar.h
@@ -0,0 +1,185 @@
+
+/*-
+ * 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_TX_LIST_COUNT 8
+#define RUM_TX_MINFREE 2
+
+struct rum_rx_radiotap_header {
+ struct ieee80211_radiotap_header wr_ihdr;
+ uint64_t wr_tsf;
+ 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;
+ uint8_t wr_antenna;
+} __packed __aligned(8);
+
+#define RT2573_RX_RADIOTAP_PRESENT \
+ ((1 << IEEE80211_RADIOTAP_TSFT) | \
+ (1 << IEEE80211_RADIOTAP_FLAGS) | \
+ (1 << IEEE80211_RADIOTAP_RATE) | \
+ (1 << IEEE80211_RADIOTAP_CHANNEL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \
+ (1 << IEEE80211_RADIOTAP_ANTENNA) | \
+ 0)
+
+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;
+} __packed;
+
+#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 {
+ STAILQ_ENTRY(rum_tx_data) next;
+ struct rum_softc *sc;
+ struct rum_tx_desc desc;
+ struct mbuf *m;
+ struct ieee80211_node *ni;
+ int rate;
+};
+typedef STAILQ_HEAD(, rum_tx_data) rum_txdhead;
+
+union sec_param {
+ struct ieee80211_key key;
+ uint8_t macaddr[IEEE80211_ADDR_LEN];
+ struct ieee80211vap *vap;
+};
+#define CMD_FUNC_PROTO void (*func)(struct rum_softc *, \
+ union sec_param *, uint8_t)
+
+struct rum_cmdq {
+ union sec_param data;
+ uint8_t rvp_id;
+
+ CMD_FUNC_PROTO;
+};
+#define RUM_CMDQ_SIZE 16
+
+struct rum_vap {
+ struct ieee80211vap vap;
+ struct mbuf *bcn_mbuf;
+ struct usb_callout ratectl_ch;
+ struct task ratectl_task;
+ uint8_t maxretry;
+
+ int (*newstate)(struct ieee80211vap *,
+ enum ieee80211_state, int);
+ void (*bmiss)(struct ieee80211vap *);
+ void (*recv_mgmt)(struct ieee80211_node *,
+ struct mbuf *, int,
+ const struct ieee80211_rx_stats *,
+ int, int);
+};
+#define RUM_VAP(vap) ((struct rum_vap *)(vap))
+
+enum {
+ RUM_BULK_WR,
+ RUM_BULK_RD,
+ RUM_N_TRANSFER = 2,
+};
+
+struct rum_softc {
+ struct ieee80211com sc_ic;
+ struct ieee80211_ratectl_tx_stats sc_txs;
+ struct mbufq sc_snd;
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+
+ struct usb_xfer *sc_xfer[RUM_N_TRANSFER];
+
+ uint8_t rf_rev;
+ uint8_t rffreq;
+
+ struct rum_tx_data tx_data[RUM_TX_LIST_COUNT];
+ rum_txdhead tx_q;
+ rum_txdhead tx_free;
+ int tx_nfree;
+ struct rum_rx_desc sc_rx_desc;
+
+ struct mtx sc_mtx;
+
+ int sc_sleep_end;
+ int sc_sleep_time;
+ uint8_t last_rx_flags;
+
+ struct rum_cmdq cmdq[RUM_CMDQ_SIZE];
+ struct mtx cmdq_mtx;
+ struct task cmdq_task;
+ uint8_t cmdq_first;
+ uint8_t cmdq_last;
+
+ uint32_t sta[6];
+ uint32_t rf_regs[4];
+ uint8_t txpow[44];
+ u_int sc_detached:1,
+ sc_running:1,
+ sc_sleeping:1,
+ sc_clr_shkeys:1;
+
+ uint8_t sc_bssid[IEEE80211_ADDR_LEN];
+ struct wmeParams wme_params[WME_NUM_AC];
+
+ uint8_t vap_key_count[1];
+ uint64_t keys_bmap;
+
+ 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;
+ struct rum_tx_radiotap_header sc_txtap;
+};
+
+#define RUM_LOCK_INIT(sc) \
+ mtx_init(&(sc)->sc_mtx, device_get_nameunit((sc)->sc_dev), \
+ MTX_NETWORK_LOCK, MTX_DEF);
+#define RUM_LOCK(sc) mtx_lock(&(sc)->sc_mtx)
+#define RUM_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx)
+#define RUM_LOCK_ASSERT(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED)
+#define RUM_LOCK_DESTROY(sc) mtx_destroy(&(sc)->sc_mtx)
+
+#define RUM_CMDQ_LOCK_INIT(sc) \
+ mtx_init(&(sc)->cmdq_mtx, "cmdq lock", NULL, MTX_DEF)
+#define RUM_CMDQ_LOCK(sc) mtx_lock(&(sc)->cmdq_mtx)
+#define RUM_CMDQ_UNLOCK(sc) mtx_unlock(&(sc)->cmdq_mtx)
+#define RUM_CMDQ_LOCK_DESTROY(sc) mtx_destroy(&(sc)->cmdq_mtx)
diff --git a/sys/dev/usb/wlan/if_run.c b/sys/dev/usb/wlan/if_run.c
new file mode 100644
index 000000000000..97c790dd5b81
--- /dev/null
+++ b/sys/dev/usb/wlan/if_run.c
@@ -0,0 +1,6441 @@
+/*-
+ * Copyright (c) 2008,2010 Damien Bergamini <damien.bergamini@free.fr>
+ * ported to FreeBSD by Akinori Furukoshi <moonlightakkiy@yahoo.ca>
+ * USB Consulting, Hans Petter Selasky <hselasky@freebsd.org>
+ * Copyright (c) 2013-2014 Kevin Lo
+ *
+ * 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.
+ */
+
+/*-
+ * Ralink Technology RT2700U/RT2800U/RT3000U/RT3900E chipset driver.
+ * http://www.ralinktech.com/
+ */
+
+#include "opt_wlan.h"
+
+#include <sys/param.h>
+#include <sys/eventhandler.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/linker.h>
+#include <sys/firmware.h>
+#include <sys/kdb.h>
+
+#include <net/bpf.h>
+#include <net/if.h>
+#include <net/if_var.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 <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_var.h>
+#include <netinet/if_ether.h>
+#include <netinet/ip.h>
+
+#include <net80211/ieee80211_var.h>
+#include <net80211/ieee80211_regdomain.h>
+#include <net80211/ieee80211_radiotap.h>
+#include <net80211/ieee80211_ratectl.h>
+#ifdef IEEE80211_SUPPORT_SUPERG
+#include <net80211/ieee80211_superg.h>
+#endif
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR run_debug
+#include <dev/usb/usb_debug.h>
+#include <dev/usb/usb_msctest.h>
+
+#include <dev/usb/wlan/if_runreg.h>
+#include <dev/usb/wlan/if_runvar.h>
+
+#ifdef USB_DEBUG
+#define RUN_DEBUG
+#endif
+
+#ifdef RUN_DEBUG
+int run_debug = 0;
+static SYSCTL_NODE(_hw_usb, OID_AUTO, run, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB run");
+SYSCTL_INT(_hw_usb_run, OID_AUTO, debug, CTLFLAG_RWTUN, &run_debug, 0,
+ "run debug level");
+
+enum {
+ RUN_DEBUG_XMIT = 0x00000001, /* basic xmit operation */
+ RUN_DEBUG_XMIT_DESC = 0x00000002, /* xmit descriptors */
+ RUN_DEBUG_RECV = 0x00000004, /* basic recv operation */
+ RUN_DEBUG_RECV_DESC = 0x00000008, /* recv descriptors */
+ RUN_DEBUG_STATE = 0x00000010, /* 802.11 state transitions */
+ RUN_DEBUG_RATE = 0x00000020, /* rate adaptation */
+ RUN_DEBUG_USB = 0x00000040, /* usb requests */
+ RUN_DEBUG_FIRMWARE = 0x00000080, /* firmware(9) loading debug */
+ RUN_DEBUG_BEACON = 0x00000100, /* beacon handling */
+ RUN_DEBUG_INTR = 0x00000200, /* ISR */
+ RUN_DEBUG_TEMP = 0x00000400, /* temperature calibration */
+ RUN_DEBUG_ROM = 0x00000800, /* various ROM info */
+ RUN_DEBUG_KEY = 0x00001000, /* crypto keys management */
+ RUN_DEBUG_TXPWR = 0x00002000, /* dump Tx power values */
+ RUN_DEBUG_RSSI = 0x00004000, /* dump RSSI lookups */
+ RUN_DEBUG_RESET = 0x00008000, /* initialization progress */
+ RUN_DEBUG_CALIB = 0x00010000, /* calibration progress */
+ RUN_DEBUG_CMD = 0x00020000, /* command queue */
+ RUN_DEBUG_ANY = 0xffffffff
+};
+
+#define RUN_DPRINTF(_sc, _m, ...) do { \
+ if (run_debug & (_m)) \
+ device_printf((_sc)->sc_dev, __VA_ARGS__); \
+} while(0)
+#else
+#define RUN_DPRINTF(_sc, _m, ...) do { (void) _sc; } while (0)
+#endif
+
+#define IEEE80211_HAS_ADDR4(wh) IEEE80211_IS_DSTODS(wh)
+
+/*
+ * Because of LOR in run_key_delete(), use atomic instead.
+ * '& RUN_CMDQ_MASQ' is to loop cmdq[].
+ */
+#define RUN_CMDQ_GET(c) (atomic_fetchadd_32((c), 1) & RUN_CMDQ_MASQ)
+
+static const STRUCT_USB_HOST_ID run_devs[] = {
+#define RUN_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) }
+#define RUN_DEV_EJECT(v,p) \
+ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, RUN_EJECT) }
+#define RUN_EJECT 1
+ RUN_DEV(ABOCOM, RT2770),
+ RUN_DEV(ABOCOM, RT2870),
+ RUN_DEV(ABOCOM, RT3070),
+ RUN_DEV(ABOCOM, RT3071),
+ RUN_DEV(ABOCOM, RT3072),
+ RUN_DEV(ABOCOM2, RT2870_1),
+ RUN_DEV(ACCTON, RT2770),
+ RUN_DEV(ACCTON, RT2870_1),
+ RUN_DEV(ACCTON, RT2870_2),
+ RUN_DEV(ACCTON, RT2870_3),
+ RUN_DEV(ACCTON, RT2870_4),
+ RUN_DEV(ACCTON, RT2870_5),
+ RUN_DEV(ACCTON, RT3070),
+ RUN_DEV(ACCTON, RT3070_1),
+ RUN_DEV(ACCTON, RT3070_2),
+ RUN_DEV(ACCTON, RT3070_3),
+ RUN_DEV(ACCTON, RT3070_4),
+ RUN_DEV(ACCTON, RT3070_5),
+ RUN_DEV(AIRTIES, RT3070),
+ RUN_DEV(ALLWIN, RT2070),
+ RUN_DEV(ALLWIN, RT2770),
+ RUN_DEV(ALLWIN, RT2870),
+ RUN_DEV(ALLWIN, RT3070),
+ RUN_DEV(ALLWIN, RT3071),
+ RUN_DEV(ALLWIN, RT3072),
+ RUN_DEV(ALLWIN, RT3572),
+ RUN_DEV(AMIGO, RT2870_1),
+ RUN_DEV(AMIGO, RT2870_2),
+ RUN_DEV(AMIT, CGWLUSB2GNR),
+ RUN_DEV(AMIT, RT2870_1),
+ RUN_DEV(AMIT2, RT2870),
+ RUN_DEV(ASUS, RT2870_1),
+ RUN_DEV(ASUS, RT2870_2),
+ RUN_DEV(ASUS, RT2870_3),
+ RUN_DEV(ASUS, RT2870_4),
+ RUN_DEV(ASUS, RT2870_5),
+ RUN_DEV(ASUS, USBN13),
+ RUN_DEV(ASUS, RT3070_1),
+ RUN_DEV(ASUS, USBN66),
+ RUN_DEV(ASUS, USB_N53),
+ RUN_DEV(ASUS, USBN14),
+ RUN_DEV(ASUS2, USBN11),
+ RUN_DEV(AZUREWAVE, RT2870_1),
+ RUN_DEV(AZUREWAVE, RT2870_2),
+ RUN_DEV(AZUREWAVE, RT3070_1),
+ RUN_DEV(AZUREWAVE, RT3070_2),
+ RUN_DEV(AZUREWAVE, RT3070_3),
+ RUN_DEV(BELKIN, F9L1103),
+ RUN_DEV(BELKIN, F5D8053V3),
+ RUN_DEV(BELKIN, F5D8055),
+ RUN_DEV(BELKIN, F5D8055V2),
+ RUN_DEV(BELKIN, F6D4050V1),
+ RUN_DEV(BELKIN, F6D4050V2),
+ RUN_DEV(BELKIN, RT2870_1),
+ RUN_DEV(BELKIN, RT2870_2),
+ RUN_DEV(CISCOLINKSYS, AE1000),
+ RUN_DEV(CISCOLINKSYS2, RT3070),
+ RUN_DEV(CISCOLINKSYS3, RT3070),
+ RUN_DEV(CONCEPTRONIC2, RT2870_1),
+ RUN_DEV(CONCEPTRONIC2, RT2870_2),
+ RUN_DEV(CONCEPTRONIC2, RT2870_3),
+ RUN_DEV(CONCEPTRONIC2, RT2870_4),
+ RUN_DEV(CONCEPTRONIC2, RT2870_5),
+ RUN_DEV(CONCEPTRONIC2, RT2870_6),
+ RUN_DEV(CONCEPTRONIC2, RT2870_7),
+ RUN_DEV(CONCEPTRONIC2, RT2870_8),
+ RUN_DEV(CONCEPTRONIC2, RT3070_1),
+ RUN_DEV(CONCEPTRONIC2, RT3070_2),
+ RUN_DEV(CONCEPTRONIC2, VIGORN61),
+ RUN_DEV(COREGA, CGWLUSB300GNM),
+ RUN_DEV(COREGA, RT2870_1),
+ RUN_DEV(COREGA, RT2870_2),
+ RUN_DEV(COREGA, RT2870_3),
+ RUN_DEV(COREGA, RT3070),
+ RUN_DEV(CYBERTAN, RT2870),
+ RUN_DEV(DLINK, RT2870),
+ RUN_DEV(DLINK, RT3072),
+ RUN_DEV(DLINK, DWA125A3),
+ RUN_DEV(DLINK, DWA127),
+ RUN_DEV(DLINK, DWA140B3),
+ RUN_DEV(DLINK, DWA160B2),
+ RUN_DEV(DLINK, DWA140D1),
+ RUN_DEV(DLINK, DWA130F1),
+ RUN_DEV(DLINK, DWA162),
+ RUN_DEV(DLINK2, DWA130),
+ RUN_DEV(DLINK2, RT2870_1),
+ RUN_DEV(DLINK2, RT2870_2),
+ RUN_DEV(DLINK2, RT3070_1),
+ RUN_DEV(DLINK2, RT3070_2),
+ RUN_DEV(DLINK2, RT3070_3),
+ RUN_DEV(DLINK2, RT3070_4),
+ RUN_DEV(DLINK2, RT3070_5),
+ RUN_DEV(DLINK2, RT3072),
+ RUN_DEV(DLINK2, RT3072_1),
+ RUN_DEV(EDIMAX, EW7717),
+ RUN_DEV(EDIMAX, EW7718),
+ RUN_DEV(EDIMAX, EW7733UND),
+ RUN_DEV(EDIMAX, RT2870_1),
+ RUN_DEV(ENCORE, RT3070_1),
+ RUN_DEV(ENCORE, RT3070_2),
+ RUN_DEV(ENCORE, RT3070_3),
+ RUN_DEV(GIGABYTE, GNWB31N),
+ RUN_DEV(GIGABYTE, GNWB32L),
+ RUN_DEV(GIGABYTE, RT2870_1),
+ RUN_DEV(GIGASET, RT3070_1),
+ RUN_DEV(GIGASET, RT3070_2),
+ RUN_DEV(GUILLEMOT, HWNU300),
+ RUN_DEV(HAWKING, HWUN2),
+ RUN_DEV(HAWKING, RT2870_1),
+ RUN_DEV(HAWKING, RT2870_2),
+ RUN_DEV(HAWKING, RT3070),
+ RUN_DEV(IODATA, RT3072_1),
+ RUN_DEV(IODATA, RT3072_2),
+ RUN_DEV(IODATA, RT3072_3),
+ RUN_DEV(IODATA, RT3072_4),
+ RUN_DEV(LINKSYS4, RT3070),
+ RUN_DEV(LINKSYS4, WUSB100),
+ RUN_DEV(LINKSYS4, WUSB54GCV3),
+ RUN_DEV(LINKSYS4, WUSB600N),
+ RUN_DEV(LINKSYS4, WUSB600NV2),
+ RUN_DEV(LOGITEC, RT2870_1),
+ RUN_DEV(LOGITEC, RT2870_2),
+ RUN_DEV(LOGITEC, RT2870_3),
+ RUN_DEV(LOGITEC, LANW300NU2),
+ RUN_DEV(LOGITEC, LANW150NU2),
+ RUN_DEV(LOGITEC, LANW300NU2S),
+ RUN_DEV(MELCO, WLIUCG300HP),
+ RUN_DEV(MELCO, RT2870_2),
+ RUN_DEV(MELCO, WLIUCAG300N),
+ RUN_DEV(MELCO, WLIUCG300N),
+ RUN_DEV(MELCO, WLIUCG301N),
+ RUN_DEV(MELCO, WLIUCGN),
+ RUN_DEV(MELCO, WLIUCGNM),
+ RUN_DEV(MELCO, WLIUCG300HPV1),
+ RUN_DEV(MELCO, WLIUCGNM2),
+ RUN_DEV(MOTOROLA4, RT2770),
+ RUN_DEV(MOTOROLA4, RT3070),
+ RUN_DEV(MSI, RT3070_1),
+ RUN_DEV(MSI, RT3070_2),
+ RUN_DEV(MSI, RT3070_3),
+ RUN_DEV(MSI, RT3070_4),
+ RUN_DEV(MSI, RT3070_5),
+ RUN_DEV(MSI, RT3070_6),
+ RUN_DEV(MSI, RT3070_7),
+ RUN_DEV(MSI, RT3070_8),
+ RUN_DEV(MSI, RT3070_9),
+ RUN_DEV(MSI, RT3070_10),
+ RUN_DEV(MSI, RT3070_11),
+ RUN_DEV(NETGEAR, WNDA4100),
+ RUN_DEV(OVISLINK, RT3072),
+ RUN_DEV(PARA, RT3070),
+ RUN_DEV(PEGATRON, RT2870),
+ RUN_DEV(PEGATRON, RT3070),
+ RUN_DEV(PEGATRON, RT3070_2),
+ RUN_DEV(PEGATRON, RT3070_3),
+ RUN_DEV(PHILIPS, RT2870),
+ RUN_DEV(PLANEX2, GWUS300MINIS),
+ RUN_DEV(PLANEX2, GWUSMICRON),
+ RUN_DEV(PLANEX2, RT2870),
+ RUN_DEV(PLANEX2, RT3070),
+ RUN_DEV(QCOM, RT2870),
+ RUN_DEV(QUANTA, RT3070),
+ RUN_DEV(RALINK, RT2070),
+ RUN_DEV(RALINK, RT2770),
+ RUN_DEV(RALINK, RT2870),
+ RUN_DEV(RALINK, RT3070),
+ RUN_DEV(RALINK, RT3071),
+ RUN_DEV(RALINK, RT3072),
+ RUN_DEV(RALINK, RT3370),
+ RUN_DEV(RALINK, RT3572),
+ RUN_DEV(RALINK, RT3573),
+ RUN_DEV(RALINK, RT5370),
+ RUN_DEV(RALINK, RT5372),
+ RUN_DEV(RALINK, RT5572),
+ RUN_DEV(RALINK, RT8070),
+ RUN_DEV(SAMSUNG, WIS09ABGN),
+ RUN_DEV(SAMSUNG2, RT2870_1),
+ RUN_DEV(SENAO, RT2870_1),
+ RUN_DEV(SENAO, RT2870_2),
+ RUN_DEV(SENAO, RT2870_3),
+ RUN_DEV(SENAO, RT2870_4),
+ RUN_DEV(SENAO, RT3070),
+ RUN_DEV(SENAO, RT3071),
+ RUN_DEV(SENAO, RT3072_1),
+ RUN_DEV(SENAO, RT3072_2),
+ RUN_DEV(SENAO, RT3072_3),
+ RUN_DEV(SENAO, RT3072_4),
+ RUN_DEV(SENAO, RT3072_5),
+ RUN_DEV(SITECOMEU, RT2770),
+ RUN_DEV(SITECOMEU, RT2870_1),
+ RUN_DEV(SITECOMEU, RT2870_2),
+ RUN_DEV(SITECOMEU, RT2870_3),
+ RUN_DEV(SITECOMEU, RT2870_4),
+ RUN_DEV(SITECOMEU, RT3070),
+ RUN_DEV(SITECOMEU, RT3070_1),
+ RUN_DEV(SITECOMEU, RT3070_2),
+ RUN_DEV(SITECOMEU, RT3070_3),
+ RUN_DEV(SITECOMEU, RT3070_4),
+ RUN_DEV(SITECOMEU, RT3071),
+ RUN_DEV(SITECOMEU, RT3072_1),
+ RUN_DEV(SITECOMEU, RT3072_2),
+ RUN_DEV(SITECOMEU, RT3072_3),
+ RUN_DEV(SITECOMEU, RT3072_4),
+ RUN_DEV(SITECOMEU, RT3072_5),
+ RUN_DEV(SITECOMEU, RT3072_6),
+ RUN_DEV(SITECOMEU, WL608),
+ RUN_DEV(SPARKLAN, RT2870_1),
+ RUN_DEV(SPARKLAN, RT3070),
+ RUN_DEV(SWEEX2, LW153),
+ RUN_DEV(SWEEX2, LW303),
+ RUN_DEV(SWEEX2, LW313),
+ RUN_DEV(TOSHIBA, RT3070),
+ RUN_DEV(UMEDIA, RT2870_1),
+ RUN_DEV(ZCOM, RT2870_1),
+ RUN_DEV(ZCOM, RT2870_2),
+ RUN_DEV(ZINWELL, RT2870_1),
+ RUN_DEV(ZINWELL, RT2870_2),
+ RUN_DEV(ZINWELL, RT3070),
+ RUN_DEV(ZINWELL, RT3072_1),
+ RUN_DEV(ZINWELL, RT3072_2),
+ RUN_DEV(ZYXEL, RT2870_1),
+ RUN_DEV(ZYXEL, RT2870_2),
+ RUN_DEV(ZYXEL, RT3070),
+ RUN_DEV_EJECT(ZYXEL, NWD2705),
+ RUN_DEV_EJECT(RALINK, RT_STOR),
+#undef RUN_DEV_EJECT
+#undef RUN_DEV
+};
+
+static device_probe_t run_match;
+static device_attach_t run_attach;
+static device_detach_t run_detach;
+
+static usb_callback_t run_bulk_rx_callback;
+static usb_callback_t run_bulk_tx_callback0;
+static usb_callback_t run_bulk_tx_callback1;
+static usb_callback_t run_bulk_tx_callback2;
+static usb_callback_t run_bulk_tx_callback3;
+static usb_callback_t run_bulk_tx_callback4;
+static usb_callback_t run_bulk_tx_callback5;
+
+static void run_autoinst(void *, struct usb_device *,
+ struct usb_attach_arg *);
+static int run_driver_loaded(struct module *, int, void *);
+static void run_bulk_tx_callbackN(struct usb_xfer *xfer,
+ usb_error_t error, u_int index);
+static struct ieee80211vap *run_vap_create(struct ieee80211com *,
+ const char [IFNAMSIZ], int, enum ieee80211_opmode, int,
+ const uint8_t [IEEE80211_ADDR_LEN],
+ const uint8_t [IEEE80211_ADDR_LEN]);
+static void run_vap_delete(struct ieee80211vap *);
+static void run_cmdq_cb(void *, int);
+static void run_setup_tx_list(struct run_softc *,
+ struct run_endpoint_queue *);
+static void run_unsetup_tx_list(struct run_softc *,
+ struct run_endpoint_queue *);
+static int run_load_microcode(struct run_softc *);
+static int run_reset(struct run_softc *);
+static usb_error_t run_do_request(struct run_softc *,
+ struct usb_device_request *, void *);
+static int run_read(struct run_softc *, uint16_t, uint32_t *);
+static int run_read_region_1(struct run_softc *, uint16_t, uint8_t *, int);
+static int run_write_2(struct run_softc *, uint16_t, uint16_t);
+static int run_write(struct run_softc *, uint16_t, uint32_t);
+static int run_write_region_1(struct run_softc *, uint16_t,
+ const uint8_t *, int);
+static int run_set_region_4(struct run_softc *, uint16_t, uint32_t, int);
+static int run_efuse_read(struct run_softc *, uint16_t, uint16_t *, int);
+static int run_efuse_read_2(struct run_softc *, uint16_t, uint16_t *);
+static int run_eeprom_read_2(struct run_softc *, uint16_t, uint16_t *);
+static int run_rt2870_rf_write(struct run_softc *, uint32_t);
+static int run_rt3070_rf_read(struct run_softc *, uint8_t, uint8_t *);
+static int run_rt3070_rf_write(struct run_softc *, uint8_t, uint8_t);
+static int run_bbp_read(struct run_softc *, uint8_t, uint8_t *);
+static int run_bbp_write(struct run_softc *, uint8_t, uint8_t);
+static int run_mcu_cmd(struct run_softc *, uint8_t, uint16_t);
+static const char *run_get_rf(uint16_t);
+static void run_rt3593_get_txpower(struct run_softc *);
+static void run_get_txpower(struct run_softc *);
+static int run_read_eeprom(struct run_softc *);
+static struct ieee80211_node *run_node_alloc(struct ieee80211vap *,
+ const uint8_t mac[IEEE80211_ADDR_LEN]);
+static int run_media_change(if_t);
+static int run_newstate(struct ieee80211vap *, enum ieee80211_state, int);
+static int run_wme_update(struct ieee80211com *);
+static void run_key_set_cb(void *);
+static int run_key_set(struct ieee80211vap *, struct ieee80211_key *);
+static void run_key_delete_cb(void *);
+static int run_key_delete(struct ieee80211vap *, struct ieee80211_key *);
+static void run_ratectl_to(void *);
+static void run_ratectl_cb(void *, int);
+static void run_drain_fifo(void *);
+static void run_iter_func(void *, struct ieee80211_node *);
+static void run_newassoc_cb(void *);
+static void run_newassoc(struct ieee80211_node *, int);
+static void run_recv_mgmt(struct ieee80211_node *, struct mbuf *, int,
+ const struct ieee80211_rx_stats *, int, int);
+static void run_rx_frame(struct run_softc *, struct mbuf *, uint32_t);
+static void run_tx_free(struct run_endpoint_queue *pq,
+ struct run_tx_data *, int);
+static void run_set_tx_desc(struct run_softc *, struct run_tx_data *);
+static int run_tx(struct run_softc *, struct mbuf *,
+ struct ieee80211_node *);
+static int run_tx_mgt(struct run_softc *, struct mbuf *,
+ struct ieee80211_node *);
+static int run_sendprot(struct run_softc *, const struct mbuf *,
+ struct ieee80211_node *, int, int);
+static int run_tx_param(struct run_softc *, struct mbuf *,
+ struct ieee80211_node *,
+ const struct ieee80211_bpf_params *);
+static int run_raw_xmit(struct ieee80211_node *, struct mbuf *,
+ const struct ieee80211_bpf_params *);
+static int run_transmit(struct ieee80211com *, struct mbuf *);
+static void run_start(struct run_softc *);
+static void run_parent(struct ieee80211com *);
+static void run_iq_calib(struct run_softc *, u_int);
+static void run_set_agc(struct run_softc *, uint8_t);
+static void run_select_chan_group(struct run_softc *, int);
+static void run_set_rx_antenna(struct run_softc *, int);
+static void run_rt2870_set_chan(struct run_softc *, u_int);
+static void run_rt3070_set_chan(struct run_softc *, u_int);
+static void run_rt3572_set_chan(struct run_softc *, u_int);
+static void run_rt3593_set_chan(struct run_softc *, u_int);
+static void run_rt5390_set_chan(struct run_softc *, u_int);
+static void run_rt5592_set_chan(struct run_softc *, u_int);
+static int run_set_chan(struct run_softc *, struct ieee80211_channel *);
+static void run_set_channel(struct ieee80211com *);
+static void run_getradiocaps(struct ieee80211com *, int, int *,
+ struct ieee80211_channel[]);
+static void run_scan_start(struct ieee80211com *);
+static void run_scan_end(struct ieee80211com *);
+static void run_update_beacon(struct ieee80211vap *, int);
+static void run_update_beacon_cb(void *);
+static void run_updateprot(struct ieee80211com *);
+static void run_updateprot_cb(void *);
+static void run_usb_timeout_cb(void *);
+static void run_reset_livelock(struct run_softc *);
+static void run_enable_tsf_sync(struct run_softc *);
+static void run_enable_tsf(struct run_softc *);
+static void run_disable_tsf(struct run_softc *);
+static void run_get_tsf(struct run_softc *, uint64_t *);
+static void run_enable_mrr(struct run_softc *);
+static void run_set_txpreamble(struct run_softc *);
+static void run_set_basicrates(struct run_softc *);
+static void run_set_leds(struct run_softc *, uint16_t);
+static void run_set_bssid(struct run_softc *, const uint8_t *);
+static void run_set_macaddr(struct run_softc *, const uint8_t *);
+static void run_updateslot(struct ieee80211com *);
+static void run_updateslot_cb(void *);
+static void run_update_mcast(struct ieee80211com *);
+static int8_t run_rssi2dbm(struct run_softc *, uint8_t, uint8_t);
+static void run_update_promisc_locked(struct run_softc *);
+static void run_update_promisc(struct ieee80211com *);
+static void run_rt5390_bbp_init(struct run_softc *);
+static int run_bbp_init(struct run_softc *);
+static int run_rt3070_rf_init(struct run_softc *);
+static void run_rt3593_rf_init(struct run_softc *);
+static void run_rt5390_rf_init(struct run_softc *);
+static int run_rt3070_filter_calib(struct run_softc *, uint8_t, uint8_t,
+ uint8_t *);
+static void run_rt3070_rf_setup(struct run_softc *);
+static void run_rt3593_rf_setup(struct run_softc *);
+static void run_rt5390_rf_setup(struct run_softc *);
+static int run_txrx_enable(struct run_softc *);
+static void run_adjust_freq_offset(struct run_softc *);
+static void run_init_locked(struct run_softc *);
+static void run_stop(void *);
+static void run_delay(struct run_softc *, u_int);
+static void run_update_chw(struct ieee80211com *ic);
+static int run_ampdu_enable(struct ieee80211_node *ni,
+ struct ieee80211_tx_ampdu *tap);
+
+static eventhandler_tag run_etag;
+
+static const struct rt2860_rate {
+ uint8_t rate;
+ uint8_t mcs;
+ enum ieee80211_phytype phy;
+ uint8_t ctl_ridx;
+ uint16_t sp_ack_dur;
+ uint16_t lp_ack_dur;
+} rt2860_rates[] = {
+ /* CCK rates (11b) */
+ { 2, 0, IEEE80211_T_DS, 0, 314, 314 },
+ { 4, 1, IEEE80211_T_DS, 1, 258, 162 },
+ { 11, 2, IEEE80211_T_DS, 2, 223, 127 },
+ { 22, 3, IEEE80211_T_DS, 3, 213, 117 },
+
+ /* OFDM rates (11a / 11g) */
+ { 12, 0, IEEE80211_T_OFDM, 4, 60, 60 },
+ { 18, 1, IEEE80211_T_OFDM, 4, 52, 52 },
+ { 24, 2, IEEE80211_T_OFDM, 6, 48, 48 },
+ { 36, 3, IEEE80211_T_OFDM, 6, 44, 44 },
+ { 48, 4, IEEE80211_T_OFDM, 8, 44, 44 },
+ { 72, 5, IEEE80211_T_OFDM, 8, 40, 40 },
+ { 96, 6, IEEE80211_T_OFDM, 8, 40, 40 },
+ { 108, 7, IEEE80211_T_OFDM, 8, 40, 40 },
+
+ /* MCS - single stream */
+ { 0x80, 0, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x81, 1, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x82, 2, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x83, 3, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x84, 4, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x85, 5, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x86, 6, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x87, 7, IEEE80211_T_HT, 4, 60, 60 },
+
+ /* MCS - 2 streams */
+ { 0x88, 8, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x89, 9, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x8a, 10, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x8b, 11, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x8c, 12, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x8d, 13, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x8e, 14, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x8f, 15, IEEE80211_T_HT, 4, 60, 60 },
+
+ /* MCS - 3 streams */
+ { 0x90, 16, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x91, 17, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x92, 18, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x93, 19, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x94, 20, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x95, 21, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x96, 22, IEEE80211_T_HT, 4, 60, 60 },
+ { 0x97, 23, IEEE80211_T_HT, 4, 60, 60 },
+};
+
+/* These are indexes into the above rt2860_rates[] array */
+#define RT2860_RIDX_CCK1 0
+#define RT2860_RIDX_CCK11 3
+#define RT2860_RIDX_OFDM6 4
+#define RT2860_RIDX_MCS0 12
+#define RT2860_RIDX_MAX 36
+
+static const struct {
+ uint16_t reg;
+ uint32_t val;
+} rt2870_def_mac[] = {
+ RT2870_DEF_MAC
+};
+
+static const struct {
+ uint8_t reg;
+ uint8_t val;
+} rt2860_def_bbp[] = {
+ RT2860_DEF_BBP
+},rt5390_def_bbp[] = {
+ RT5390_DEF_BBP
+},rt5592_def_bbp[] = {
+ RT5592_DEF_BBP
+};
+
+/*
+ * Default values for BBP register R196 for RT5592.
+ */
+static const uint8_t rt5592_bbp_r196[] = {
+ 0xe0, 0x1f, 0x38, 0x32, 0x08, 0x28, 0x19, 0x0a, 0xff, 0x00,
+ 0x16, 0x10, 0x10, 0x0b, 0x36, 0x2c, 0x26, 0x24, 0x42, 0x36,
+ 0x30, 0x2d, 0x4c, 0x46, 0x3d, 0x40, 0x3e, 0x42, 0x3d, 0x40,
+ 0x3c, 0x34, 0x2c, 0x2f, 0x3c, 0x35, 0x2e, 0x2a, 0x49, 0x41,
+ 0x36, 0x31, 0x30, 0x30, 0x0e, 0x0d, 0x28, 0x21, 0x1c, 0x16,
+ 0x50, 0x4a, 0x43, 0x40, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7d, 0x14, 0x32, 0x2c, 0x36, 0x4c, 0x43, 0x2c,
+ 0x2e, 0x36, 0x30, 0x6e
+};
+
+static const struct rfprog {
+ uint8_t chan;
+ uint32_t r1, r2, r3, r4;
+} rt2860_rf2850[] = {
+ RT2860_RF2850
+};
+
+struct {
+ uint8_t n, r, k;
+} rt3070_freqs[] = {
+ RT3070_RF3052
+};
+
+static const struct rt5592_freqs {
+ uint16_t n;
+ uint8_t k, m, r;
+} rt5592_freqs_20mhz[] = {
+ RT5592_RF5592_20MHZ
+},rt5592_freqs_40mhz[] = {
+ RT5592_RF5592_40MHZ
+};
+
+static const struct {
+ uint8_t reg;
+ uint8_t val;
+} rt3070_def_rf[] = {
+ RT3070_DEF_RF
+},rt3572_def_rf[] = {
+ RT3572_DEF_RF
+},rt3593_def_rf[] = {
+ RT3593_DEF_RF
+},rt5390_def_rf[] = {
+ RT5390_DEF_RF
+},rt5392_def_rf[] = {
+ RT5392_DEF_RF
+},rt5592_def_rf[] = {
+ RT5592_DEF_RF
+},rt5592_2ghz_def_rf[] = {
+ RT5592_2GHZ_DEF_RF
+},rt5592_5ghz_def_rf[] = {
+ RT5592_5GHZ_DEF_RF
+};
+
+static const struct {
+ u_int firstchan;
+ u_int lastchan;
+ uint8_t reg;
+ uint8_t val;
+} rt5592_chan_5ghz[] = {
+ RT5592_CHAN_5GHZ
+};
+
+static const struct usb_config run_config[RUN_N_XFER] = {
+ [RUN_BULK_TX_BE] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .ep_index = 0,
+ .direction = UE_DIR_OUT,
+ .bufsize = RUN_MAX_TXSZ,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = run_bulk_tx_callback0,
+ .timeout = 5000, /* ms */
+ },
+ [RUN_BULK_TX_BK] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .ep_index = 1,
+ .bufsize = RUN_MAX_TXSZ,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = run_bulk_tx_callback1,
+ .timeout = 5000, /* ms */
+ },
+ [RUN_BULK_TX_VI] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .ep_index = 2,
+ .bufsize = RUN_MAX_TXSZ,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = run_bulk_tx_callback2,
+ .timeout = 5000, /* ms */
+ },
+ [RUN_BULK_TX_VO] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .ep_index = 3,
+ .bufsize = RUN_MAX_TXSZ,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = run_bulk_tx_callback3,
+ .timeout = 5000, /* ms */
+ },
+ [RUN_BULK_TX_HCCA] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .ep_index = 4,
+ .bufsize = RUN_MAX_TXSZ,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,},
+ .callback = run_bulk_tx_callback4,
+ .timeout = 5000, /* ms */
+ },
+ [RUN_BULK_TX_PRIO] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .ep_index = 5,
+ .bufsize = RUN_MAX_TXSZ,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,.no_pipe_ok = 1,},
+ .callback = run_bulk_tx_callback5,
+ .timeout = 5000, /* ms */
+ },
+ [RUN_BULK_RX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = RUN_MAX_RXSZ,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = run_bulk_rx_callback,
+ }
+};
+
+static void
+run_autoinst(void *arg, struct usb_device *udev,
+ struct usb_attach_arg *uaa)
+{
+ struct usb_interface *iface;
+ struct usb_interface_descriptor *id;
+
+ if (uaa->dev_state != UAA_DEV_READY)
+ return;
+
+ iface = usbd_get_iface(udev, 0);
+ if (iface == NULL)
+ return;
+ id = iface->idesc;
+ if (id == NULL || id->bInterfaceClass != UICLASS_MASS)
+ return;
+ if (usbd_lookup_id_by_uaa(run_devs, sizeof(run_devs), uaa))
+ return;
+
+ if (usb_msc_eject(udev, 0, MSC_EJECT_STOPUNIT) == 0)
+ uaa->dev_state = UAA_DEV_EJECTING;
+}
+
+static int
+run_driver_loaded(struct module *mod, int what, void *arg)
+{
+ switch (what) {
+ case MOD_LOAD:
+ run_etag = EVENTHANDLER_REGISTER(usb_dev_configured,
+ run_autoinst, NULL, EVENTHANDLER_PRI_ANY);
+ break;
+ case MOD_UNLOAD:
+ EVENTHANDLER_DEREGISTER(usb_dev_configured, run_etag);
+ break;
+ default:
+ return (EOPNOTSUPP);
+ }
+ return (0);
+}
+
+static int
+run_match(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != 0)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != RT2860_IFACE_INDEX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(run_devs, sizeof(run_devs), uaa));
+}
+
+static int
+run_attach(device_t self)
+{
+ struct run_softc *sc = device_get_softc(self);
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t ver;
+ uint8_t iface_index;
+ int ntries, error;
+
+ device_set_usb_desc(self);
+ sc->sc_udev = uaa->device;
+ sc->sc_dev = self;
+ if (USB_GET_DRIVER_INFO(uaa) != RUN_EJECT)
+ sc->sc_flags |= RUN_FLAG_FWLOAD_NEEDED;
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev),
+ MTX_NETWORK_LOCK, MTX_DEF);
+ mbufq_init(&sc->sc_snd, ifqmaxlen);
+
+ iface_index = RT2860_IFACE_INDEX;
+
+ error = usbd_transfer_setup(uaa->device, &iface_index,
+ sc->sc_xfer, run_config, RUN_N_XFER, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(self, "could not allocate USB transfers, "
+ "err=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+
+ RUN_LOCK(sc);
+
+ /* wait for the chip to settle */
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (run_read(sc, RT2860_ASIC_VER_ID, &ver) != 0) {
+ RUN_UNLOCK(sc);
+ goto detach;
+ }
+ if (ver != 0 && ver != 0xffffffff)
+ break;
+ run_delay(sc, 10);
+ }
+ if (ntries == 100) {
+ device_printf(sc->sc_dev,
+ "timeout waiting for NIC to initialize\n");
+ RUN_UNLOCK(sc);
+ goto detach;
+ }
+ sc->mac_ver = ver >> 16;
+ sc->mac_rev = ver & 0xffff;
+
+ /* retrieve RF rev. no and various other things from EEPROM */
+ run_read_eeprom(sc);
+
+ device_printf(sc->sc_dev,
+ "MAC/BBP RT%04X (rev 0x%04X), RF %s (MIMO %dT%dR), address %s\n",
+ sc->mac_ver, sc->mac_rev, run_get_rf(sc->rf_rev),
+ sc->ntxchains, sc->nrxchains, ether_sprintf(ic->ic_macaddr));
+
+ RUN_UNLOCK(sc);
+
+ ic->ic_softc = sc;
+ ic->ic_name = device_get_nameunit(self);
+ 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 supported */
+ IEEE80211_C_MONITOR | /* monitor mode supported */
+ IEEE80211_C_IBSS |
+ IEEE80211_C_HOSTAP |
+ IEEE80211_C_WDS | /* 4-address traffic works */
+ IEEE80211_C_MBSS |
+ IEEE80211_C_SHPREAMBLE | /* short preamble supported */
+ IEEE80211_C_SHSLOT | /* short slot time supported */
+ IEEE80211_C_SWAMSDUTX | /* Do software A-MSDU TX */
+ IEEE80211_C_FF | /* Atheros fast-frames */
+ IEEE80211_C_WME | /* WME */
+ IEEE80211_C_WPA; /* WPA1|WPA2(RSN) */
+
+ /*
+ * RF2020 is not an 11n device.
+ */
+ if (sc->rf_rev != RT3070_RF_2020) {
+ device_printf(sc->sc_dev, "[HT] Enabling 802.11n\n");
+ ic->ic_htcaps =
+ IEEE80211_HTC_HT |
+ IEEE80211_HTC_AMPDU |
+ IEEE80211_HTC_AMSDU |
+ IEEE80211_HTCAP_MAXAMSDU_3839 |
+ IEEE80211_HTCAP_SMPS_OFF;
+
+ ic->ic_rxstream = sc->nrxchains;
+ ic->ic_txstream = sc->ntxchains;
+ }
+
+ ic->ic_cryptocaps =
+ IEEE80211_CRYPTO_WEP |
+ IEEE80211_CRYPTO_AES_CCM |
+ IEEE80211_CRYPTO_TKIPMIC |
+ IEEE80211_CRYPTO_TKIP;
+
+ ic->ic_flags |= IEEE80211_F_DATAPAD;
+ ic->ic_flags_ext |= IEEE80211_FEXT_SWBMISS;
+
+ run_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans,
+ ic->ic_channels);
+
+ ieee80211_ifattach(ic);
+
+ ic->ic_scan_start = run_scan_start;
+ ic->ic_scan_end = run_scan_end;
+ ic->ic_set_channel = run_set_channel;
+ ic->ic_getradiocaps = run_getradiocaps;
+ ic->ic_node_alloc = run_node_alloc;
+ ic->ic_newassoc = run_newassoc;
+ ic->ic_updateslot = run_updateslot;
+ ic->ic_update_mcast = run_update_mcast;
+ ic->ic_wme.wme_update = run_wme_update;
+ ic->ic_raw_xmit = run_raw_xmit;
+ ic->ic_update_promisc = run_update_promisc;
+ ic->ic_vap_create = run_vap_create;
+ ic->ic_vap_delete = run_vap_delete;
+ ic->ic_transmit = run_transmit;
+ ic->ic_parent = run_parent;
+ ic->ic_update_chw = run_update_chw;
+ ic->ic_ampdu_enable = run_ampdu_enable;
+
+ ieee80211_radiotap_attach(ic,
+ &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap),
+ RUN_TX_RADIOTAP_PRESENT,
+ &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap),
+ RUN_RX_RADIOTAP_PRESENT);
+
+ TASK_INIT(&sc->cmdq_task, 0, run_cmdq_cb, sc);
+ TASK_INIT(&sc->ratectl_task, 0, run_ratectl_cb, sc);
+ usb_callout_init_mtx(&sc->ratectl_ch, &sc->sc_mtx, 0);
+
+ if (bootverbose)
+ ieee80211_announce(ic);
+
+ return (0);
+
+detach:
+ run_detach(self);
+ return (ENXIO);
+}
+
+static void
+run_drain_mbufq(struct run_softc *sc)
+{
+ struct mbuf *m;
+ struct ieee80211_node *ni;
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+ while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
+ ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
+ m->m_pkthdr.rcvif = NULL;
+ ieee80211_free_node(ni);
+ m_freem(m);
+ }
+}
+
+static int
+run_detach(device_t self)
+{
+ struct run_softc *sc = device_get_softc(self);
+ struct ieee80211com *ic = &sc->sc_ic;
+ int i;
+
+ RUN_LOCK(sc);
+ sc->sc_detached = 1;
+ RUN_UNLOCK(sc);
+
+ /* stop all USB transfers */
+ usbd_transfer_unsetup(sc->sc_xfer, RUN_N_XFER);
+
+ RUN_LOCK(sc);
+ sc->ratectl_run = RUN_RATECTL_OFF;
+ sc->cmdq_run = sc->cmdq_key_set = RUN_CMDQ_ABORT;
+
+ /* free TX list, if any */
+ for (i = 0; i != RUN_EP_QUEUES; i++)
+ run_unsetup_tx_list(sc, &sc->sc_epq[i]);
+
+ /* Free TX queue */
+ run_drain_mbufq(sc);
+ RUN_UNLOCK(sc);
+
+ if (sc->sc_ic.ic_softc == sc) {
+ /* drain tasks */
+ usb_callout_drain(&sc->ratectl_ch);
+ ieee80211_draintask(ic, &sc->cmdq_task);
+ ieee80211_draintask(ic, &sc->ratectl_task);
+ ieee80211_ifdetach(ic);
+ }
+
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static struct ieee80211vap *
+run_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
+ enum ieee80211_opmode opmode, int flags,
+ const uint8_t bssid[IEEE80211_ADDR_LEN],
+ const uint8_t mac[IEEE80211_ADDR_LEN])
+{
+ struct run_softc *sc = ic->ic_softc;
+ struct run_vap *rvp;
+ struct ieee80211vap *vap;
+ int i;
+
+ if (sc->rvp_cnt >= RUN_VAP_MAX) {
+ device_printf(sc->sc_dev, "number of VAPs maxed out\n");
+ return (NULL);
+ }
+
+ switch (opmode) {
+ case IEEE80211_M_STA:
+ /* enable s/w bmiss handling for sta mode */
+ flags |= IEEE80211_CLONE_NOBEACONS;
+ /* fall though */
+ case IEEE80211_M_IBSS:
+ case IEEE80211_M_MONITOR:
+ case IEEE80211_M_HOSTAP:
+ case IEEE80211_M_MBSS:
+ /* other than WDS vaps, only one at a time */
+ if (!TAILQ_EMPTY(&ic->ic_vaps))
+ return (NULL);
+ break;
+ case IEEE80211_M_WDS:
+ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next){
+ if(vap->iv_opmode != IEEE80211_M_HOSTAP)
+ continue;
+ /* WDS vap's always share the local mac address. */
+ flags &= ~IEEE80211_CLONE_BSSID;
+ break;
+ }
+ if (vap == NULL) {
+ device_printf(sc->sc_dev,
+ "wds only supported in ap mode\n");
+ return (NULL);
+ }
+ break;
+ default:
+ device_printf(sc->sc_dev, "unknown opmode %d\n", opmode);
+ return (NULL);
+ }
+
+ rvp = malloc(sizeof(struct run_vap), M_80211_VAP, M_WAITOK | M_ZERO);
+ vap = &rvp->vap;
+
+ if (ieee80211_vap_setup(ic, vap, name, unit, opmode, flags,
+ bssid) != 0) {
+ /* out of memory */
+ free(rvp, M_80211_VAP);
+ return (NULL);
+ }
+
+ vap->iv_update_beacon = run_update_beacon;
+ vap->iv_max_aid = RT2870_WCID_MAX;
+
+ /*
+ * The linux rt2800 driver limits 1 stream devices to a 32KB
+ * RX AMPDU.
+ */
+ if (ic->ic_rxstream > 1)
+ vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_64K;
+ else
+ vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_32K;
+ vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_2; /* 2uS */
+
+ /*
+ * To delete the right key from h/w, we need wcid.
+ * Luckily, there is unused space in ieee80211_key{}, wk_pad,
+ * and matching wcid will be written into there. So, cast
+ * some spells to remove 'const' from ieee80211_key{}
+ */
+ vap->iv_key_delete = (void *)run_key_delete;
+ vap->iv_key_set = (void *)run_key_set;
+
+ /* override state transition machine */
+ rvp->newstate = vap->iv_newstate;
+ vap->iv_newstate = run_newstate;
+ if (opmode == IEEE80211_M_IBSS) {
+ rvp->recv_mgmt = vap->iv_recv_mgmt;
+ vap->iv_recv_mgmt = run_recv_mgmt;
+ }
+
+ ieee80211_ratectl_init(vap);
+ ieee80211_ratectl_setinterval(vap, 1000 /* 1 sec */);
+
+ /* complete setup */
+ ieee80211_vap_attach(vap, run_media_change, ieee80211_media_status,
+ mac);
+
+ /* make sure id is always unique */
+ for (i = 0; i < RUN_VAP_MAX; i++) {
+ if((sc->rvp_bmap & 1 << i) == 0){
+ sc->rvp_bmap |= 1 << i;
+ rvp->rvp_id = i;
+ break;
+ }
+ }
+ if (sc->rvp_cnt++ == 0)
+ ic->ic_opmode = opmode;
+
+ if (opmode == IEEE80211_M_HOSTAP)
+ sc->cmdq_run = RUN_CMDQ_GO;
+
+ RUN_DPRINTF(sc, RUN_DEBUG_STATE, "rvp_id=%d bmap=%x rvp_cnt=%d\n",
+ rvp->rvp_id, sc->rvp_bmap, sc->rvp_cnt);
+
+ return (vap);
+}
+
+static void
+run_vap_delete(struct ieee80211vap *vap)
+{
+ struct run_vap *rvp = RUN_VAP(vap);
+ struct ieee80211com *ic;
+ struct run_softc *sc;
+ uint8_t rvp_id;
+
+ if (vap == NULL)
+ return;
+
+ ic = vap->iv_ic;
+ sc = ic->ic_softc;
+
+ RUN_LOCK(sc);
+
+ m_freem(rvp->beacon_mbuf);
+ rvp->beacon_mbuf = NULL;
+
+ rvp_id = rvp->rvp_id;
+ sc->ratectl_run &= ~(1 << rvp_id);
+ sc->rvp_bmap &= ~(1 << rvp_id);
+ run_set_region_4(sc, RT2860_SKEY(rvp_id, 0), 0, 128);
+ run_set_region_4(sc, RT2860_BCN_BASE(rvp_id), 0, 512);
+ --sc->rvp_cnt;
+
+ RUN_DPRINTF(sc, RUN_DEBUG_STATE,
+ "vap=%p rvp_id=%d bmap=%x rvp_cnt=%d\n",
+ vap, rvp_id, sc->rvp_bmap, sc->rvp_cnt);
+
+ RUN_UNLOCK(sc);
+
+ ieee80211_ratectl_deinit(vap);
+ ieee80211_vap_detach(vap);
+ free(rvp, M_80211_VAP);
+}
+
+/*
+ * There are numbers of functions need to be called in context thread.
+ * Rather than creating taskqueue event for each of those functions,
+ * here is all-for-one taskqueue callback function. This function
+ * guarantees deferred functions are executed in the same order they
+ * were enqueued.
+ * '& RUN_CMDQ_MASQ' is to loop cmdq[].
+ */
+static void
+run_cmdq_cb(void *arg, int pending)
+{
+ struct run_softc *sc = arg;
+ uint8_t i;
+
+ /* call cmdq[].func locked */
+ RUN_LOCK(sc);
+ for (i = sc->cmdq_exec; sc->cmdq[i].func && pending;
+ i = sc->cmdq_exec, pending--) {
+ RUN_DPRINTF(sc, RUN_DEBUG_CMD, "cmdq_exec=%d pending=%d\n",
+ i, pending);
+ if (sc->cmdq_run == RUN_CMDQ_GO) {
+ /*
+ * If arg0 is NULL, callback func needs more
+ * than one arg. So, pass ptr to cmdq struct.
+ */
+ if (sc->cmdq[i].arg0)
+ sc->cmdq[i].func(sc->cmdq[i].arg0);
+ else
+ sc->cmdq[i].func(&sc->cmdq[i]);
+ }
+ sc->cmdq[i].arg0 = NULL;
+ sc->cmdq[i].func = NULL;
+ sc->cmdq_exec++;
+ sc->cmdq_exec &= RUN_CMDQ_MASQ;
+ }
+ RUN_UNLOCK(sc);
+}
+
+static void
+run_setup_tx_list(struct run_softc *sc, struct run_endpoint_queue *pq)
+{
+ struct run_tx_data *data;
+
+ memset(pq, 0, sizeof(*pq));
+
+ STAILQ_INIT(&pq->tx_qh);
+ STAILQ_INIT(&pq->tx_fh);
+
+ for (data = &pq->tx_data[0];
+ data < &pq->tx_data[RUN_TX_RING_COUNT]; data++) {
+ data->sc = sc;
+ STAILQ_INSERT_TAIL(&pq->tx_fh, data, next);
+ }
+ pq->tx_nfree = RUN_TX_RING_COUNT;
+}
+
+static void
+run_unsetup_tx_list(struct run_softc *sc, struct run_endpoint_queue *pq)
+{
+ struct run_tx_data *data;
+
+ /* make sure any subsequent use of the queues will fail */
+ pq->tx_nfree = 0;
+ STAILQ_INIT(&pq->tx_fh);
+ STAILQ_INIT(&pq->tx_qh);
+
+ /* free up all node references and mbufs */
+ for (data = &pq->tx_data[0];
+ data < &pq->tx_data[RUN_TX_RING_COUNT]; data++) {
+ if (data->m != NULL) {
+ m_freem(data->m);
+ data->m = NULL;
+ }
+ if (data->ni != NULL) {
+ ieee80211_free_node(data->ni);
+ data->ni = NULL;
+ }
+ }
+}
+
+static int
+run_load_microcode(struct run_softc *sc)
+{
+ usb_device_request_t req;
+ const struct firmware *fw;
+ const u_char *base;
+ uint32_t tmp;
+ int ntries, error;
+ const uint64_t *temp;
+ uint64_t bytes;
+
+ RUN_UNLOCK(sc);
+ fw = firmware_get("runfw");
+ RUN_LOCK(sc);
+ if (fw == NULL) {
+ device_printf(sc->sc_dev,
+ "failed loadfirmware of file %s\n", "runfw");
+ return ENOENT;
+ }
+
+ if (fw->datasize != 8192) {
+ device_printf(sc->sc_dev,
+ "invalid firmware size (should be 8KB)\n");
+ error = EINVAL;
+ goto fail;
+ }
+
+ /*
+ * RT3071/RT3072 use a different firmware
+ * run-rt2870 (8KB) contains both,
+ * first half (4KB) is for rt2870,
+ * last half is for rt3071.
+ */
+ base = fw->data;
+ if ((sc->mac_ver) != 0x2860 &&
+ (sc->mac_ver) != 0x2872 &&
+ (sc->mac_ver) != 0x3070) {
+ base += 4096;
+ }
+
+ /* cheap sanity check */
+ temp = fw->data;
+ bytes = *temp;
+ if (bytes != be64toh(0xffffff0210280210ULL)) {
+ device_printf(sc->sc_dev, "firmware checksum failed\n");
+ error = EINVAL;
+ goto fail;
+ }
+
+ /* write microcode image */
+ if (sc->sc_flags & RUN_FLAG_FWLOAD_NEEDED) {
+ run_write_region_1(sc, RT2870_FW_BASE, base, 4096);
+ run_write(sc, RT2860_H2M_MAILBOX_CID, 0xffffffff);
+ run_write(sc, RT2860_H2M_MAILBOX_STATUS, 0xffffffff);
+ }
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = RT2870_RESET;
+ USETW(req.wValue, 8);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+ if ((error = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL))
+ != 0) {
+ device_printf(sc->sc_dev, "firmware reset failed\n");
+ goto fail;
+ }
+
+ run_delay(sc, 10);
+
+ run_write(sc, RT2860_H2M_BBPAGENT, 0);
+ run_write(sc, RT2860_H2M_MAILBOX, 0);
+ run_write(sc, RT2860_H2M_INTSRC, 0);
+ if ((error = run_mcu_cmd(sc, RT2860_MCU_CMD_RFRESET, 0)) != 0)
+ goto fail;
+
+ /* wait until microcontroller is ready */
+ for (ntries = 0; ntries < 1000; ntries++) {
+ if ((error = run_read(sc, RT2860_SYS_CTRL, &tmp)) != 0)
+ goto fail;
+ if (tmp & RT2860_MCU_READY)
+ break;
+ run_delay(sc, 10);
+ }
+ if (ntries == 1000) {
+ device_printf(sc->sc_dev,
+ "timeout waiting for MCU to initialize\n");
+ error = ETIMEDOUT;
+ goto fail;
+ }
+ device_printf(sc->sc_dev, "firmware %s ver. %u.%u loaded\n",
+ (base == fw->data) ? "RT2870" : "RT3071",
+ *(base + 4092), *(base + 4093));
+
+fail:
+ firmware_put(fw, FIRMWARE_UNLOAD);
+ return (error);
+}
+
+static int
+run_reset(struct run_softc *sc)
+{
+ usb_device_request_t req;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = RT2870_RESET;
+ USETW(req.wValue, 1);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, 0);
+ return (usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, NULL));
+}
+
+static usb_error_t
+run_do_request(struct run_softc *sc,
+ struct usb_device_request *req, void *data)
+{
+ usb_error_t err;
+ int ntries = 10;
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+
+ while (ntries--) {
+ err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx,
+ req, data, 0, NULL, 250 /* ms */);
+ if (err == 0)
+ break;
+ RUN_DPRINTF(sc, RUN_DEBUG_USB,
+ "Control request failed, %s (retrying)\n",
+ usbd_errstr(err));
+ run_delay(sc, 10);
+ }
+ return (err);
+}
+
+static int
+run_read(struct run_softc *sc, uint16_t reg, uint32_t *val)
+{
+ uint32_t tmp;
+ int error;
+
+ error = run_read_region_1(sc, reg, (uint8_t *)&tmp, sizeof tmp);
+ if (error == 0)
+ *val = le32toh(tmp);
+ else
+ *val = 0xffffffff;
+ return (error);
+}
+
+static int
+run_read_region_1(struct run_softc *sc, uint16_t reg, uint8_t *buf, int len)
+{
+ usb_device_request_t req;
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = RT2870_READ_REGION_1;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, len);
+
+ return (run_do_request(sc, &req, buf));
+}
+
+static int
+run_write_2(struct run_softc *sc, uint16_t reg, uint16_t val)
+{
+ usb_device_request_t req;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = RT2870_WRITE_2;
+ USETW(req.wValue, val);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, 0);
+
+ return (run_do_request(sc, &req, NULL));
+}
+
+static int
+run_write(struct run_softc *sc, uint16_t reg, uint32_t val)
+{
+ int error;
+
+ if ((error = run_write_2(sc, reg, val & 0xffff)) == 0)
+ error = run_write_2(sc, reg + 2, val >> 16);
+ return (error);
+}
+
+static int
+run_write_region_1(struct run_softc *sc, uint16_t reg, const uint8_t *buf,
+ int len)
+{
+#if 1
+ int i, error = 0;
+ /*
+ * NB: the WRITE_REGION_1 command is not stable on RT2860.
+ * We thus issue multiple WRITE_2 commands instead.
+ */
+ KASSERT((len & 1) == 0, ("run_write_region_1: Data too long.\n"));
+ for (i = 0; i < len && error == 0; i += 2)
+ error = run_write_2(sc, reg + i, buf[i] | buf[i + 1] << 8);
+ return (error);
+#else
+ usb_device_request_t req;
+ int error = 0;
+
+ /*
+ * NOTE: It appears the WRITE_REGION_1 command cannot be
+ * passed a huge amount of data, which will crash the
+ * firmware. Limit amount of data passed to 64-bytes at a
+ * time.
+ */
+ while (len > 0) {
+ int delta = 64;
+ if (delta > len)
+ delta = len;
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = RT2870_WRITE_REGION_1;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, reg);
+ USETW(req.wLength, delta);
+ error = run_do_request(sc, &req, __DECONST(uint8_t *, buf));
+ if (error != 0)
+ break;
+ reg += delta;
+ buf += delta;
+ len -= delta;
+ }
+ return (error);
+#endif
+}
+
+static int
+run_set_region_4(struct run_softc *sc, uint16_t reg, uint32_t val, int len)
+{
+ int i, error = 0;
+
+ KASSERT((len & 3) == 0, ("run_set_region_4: Invalid data length.\n"));
+ for (i = 0; i < len && error == 0; i += 4)
+ error = run_write(sc, reg + i, val);
+ return (error);
+}
+
+static int
+run_efuse_read(struct run_softc *sc, uint16_t addr, uint16_t *val, int count)
+{
+ uint32_t tmp;
+ uint16_t reg;
+ int error, ntries;
+
+ if ((error = run_read(sc, RT3070_EFUSE_CTRL, &tmp)) != 0)
+ return (error);
+
+ if (count == 2)
+ addr *= 2;
+ /*-
+ * Read one 16-byte block into registers EFUSE_DATA[0-3]:
+ * DATA0: F E D C
+ * DATA1: B A 9 8
+ * DATA2: 7 6 5 4
+ * DATA3: 3 2 1 0
+ */
+ tmp &= ~(RT3070_EFSROM_MODE_MASK | RT3070_EFSROM_AIN_MASK);
+ tmp |= (addr & ~0xf) << RT3070_EFSROM_AIN_SHIFT | RT3070_EFSROM_KICK;
+ run_write(sc, RT3070_EFUSE_CTRL, tmp);
+ for (ntries = 0; ntries < 100; ntries++) {
+ if ((error = run_read(sc, RT3070_EFUSE_CTRL, &tmp)) != 0)
+ return (error);
+ if (!(tmp & RT3070_EFSROM_KICK))
+ break;
+ run_delay(sc, 2);
+ }
+ if (ntries == 100)
+ return (ETIMEDOUT);
+
+ if ((tmp & RT3070_EFUSE_AOUT_MASK) == RT3070_EFUSE_AOUT_MASK) {
+ *val = 0xffff; /* address not found */
+ return (0);
+ }
+ /* determine to which 32-bit register our 16-bit word belongs */
+ reg = RT3070_EFUSE_DATA3 - (addr & 0xc);
+ if ((error = run_read(sc, reg, &tmp)) != 0)
+ return (error);
+
+ tmp >>= (8 * (addr & 0x3));
+ *val = (addr & 1) ? tmp >> 16 : tmp & 0xffff;
+
+ return (0);
+}
+
+/* Read 16-bit from eFUSE ROM for RT3xxx. */
+static int
+run_efuse_read_2(struct run_softc *sc, uint16_t addr, uint16_t *val)
+{
+ return (run_efuse_read(sc, addr, val, 2));
+}
+
+static int
+run_eeprom_read_2(struct run_softc *sc, uint16_t addr, uint16_t *val)
+{
+ usb_device_request_t req;
+ uint16_t tmp;
+ int error;
+
+ addr *= 2;
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = RT2870_EEPROM_READ;
+ USETW(req.wValue, 0);
+ USETW(req.wIndex, addr);
+ USETW(req.wLength, sizeof(tmp));
+
+ error = usbd_do_request(sc->sc_udev, &sc->sc_mtx, &req, &tmp);
+ if (error == 0)
+ *val = le16toh(tmp);
+ else
+ *val = 0xffff;
+ return (error);
+}
+
+static __inline int
+run_srom_read(struct run_softc *sc, uint16_t addr, uint16_t *val)
+{
+ /* either eFUSE ROM or EEPROM */
+ return sc->sc_srom_read(sc, addr, val);
+}
+
+static int
+run_rt2870_rf_write(struct run_softc *sc, uint32_t val)
+{
+ uint32_t tmp;
+ int error, ntries;
+
+ for (ntries = 0; ntries < 10; ntries++) {
+ if ((error = run_read(sc, RT2860_RF_CSR_CFG0, &tmp)) != 0)
+ return (error);
+ if (!(tmp & RT2860_RF_REG_CTRL))
+ break;
+ }
+ if (ntries == 10)
+ return (ETIMEDOUT);
+
+ return (run_write(sc, RT2860_RF_CSR_CFG0, val));
+}
+
+static int
+run_rt3070_rf_read(struct run_softc *sc, uint8_t reg, uint8_t *val)
+{
+ uint32_t tmp;
+ int error, ntries;
+
+ for (ntries = 0; ntries < 100; ntries++) {
+ if ((error = run_read(sc, RT3070_RF_CSR_CFG, &tmp)) != 0)
+ return (error);
+ if (!(tmp & RT3070_RF_KICK))
+ break;
+ }
+ if (ntries == 100)
+ return (ETIMEDOUT);
+
+ tmp = RT3070_RF_KICK | reg << 8;
+ if ((error = run_write(sc, RT3070_RF_CSR_CFG, tmp)) != 0)
+ return (error);
+
+ for (ntries = 0; ntries < 100; ntries++) {
+ if ((error = run_read(sc, RT3070_RF_CSR_CFG, &tmp)) != 0)
+ return (error);
+ if (!(tmp & RT3070_RF_KICK))
+ break;
+ }
+ if (ntries == 100)
+ return (ETIMEDOUT);
+
+ *val = tmp & 0xff;
+ return (0);
+}
+
+static int
+run_rt3070_rf_write(struct run_softc *sc, uint8_t reg, uint8_t val)
+{
+ uint32_t tmp;
+ int error, ntries;
+
+ for (ntries = 0; ntries < 10; ntries++) {
+ if ((error = run_read(sc, RT3070_RF_CSR_CFG, &tmp)) != 0)
+ return (error);
+ if (!(tmp & RT3070_RF_KICK))
+ break;
+ }
+ if (ntries == 10)
+ return (ETIMEDOUT);
+
+ tmp = RT3070_RF_WRITE | RT3070_RF_KICK | reg << 8 | val;
+ return (run_write(sc, RT3070_RF_CSR_CFG, tmp));
+}
+
+static int
+run_bbp_read(struct run_softc *sc, uint8_t reg, uint8_t *val)
+{
+ uint32_t tmp;
+ int ntries, error;
+
+ for (ntries = 0; ntries < 10; ntries++) {
+ if ((error = run_read(sc, RT2860_BBP_CSR_CFG, &tmp)) != 0)
+ return (error);
+ if (!(tmp & RT2860_BBP_CSR_KICK))
+ break;
+ }
+ if (ntries == 10)
+ return (ETIMEDOUT);
+
+ tmp = RT2860_BBP_CSR_READ | RT2860_BBP_CSR_KICK | reg << 8;
+ if ((error = run_write(sc, RT2860_BBP_CSR_CFG, tmp)) != 0)
+ return (error);
+
+ for (ntries = 0; ntries < 10; ntries++) {
+ if ((error = run_read(sc, RT2860_BBP_CSR_CFG, &tmp)) != 0)
+ return (error);
+ if (!(tmp & RT2860_BBP_CSR_KICK))
+ break;
+ }
+ if (ntries == 10)
+ return (ETIMEDOUT);
+
+ *val = tmp & 0xff;
+ return (0);
+}
+
+static int
+run_bbp_write(struct run_softc *sc, uint8_t reg, uint8_t val)
+{
+ uint32_t tmp;
+ int ntries, error;
+
+ for (ntries = 0; ntries < 10; ntries++) {
+ if ((error = run_read(sc, RT2860_BBP_CSR_CFG, &tmp)) != 0)
+ return (error);
+ if (!(tmp & RT2860_BBP_CSR_KICK))
+ break;
+ }
+ if (ntries == 10)
+ return (ETIMEDOUT);
+
+ tmp = RT2860_BBP_CSR_KICK | reg << 8 | val;
+ return (run_write(sc, RT2860_BBP_CSR_CFG, tmp));
+}
+
+/*
+ * Send a command to the 8051 microcontroller unit.
+ */
+static int
+run_mcu_cmd(struct run_softc *sc, uint8_t cmd, uint16_t arg)
+{
+ uint32_t tmp;
+ int error, ntries;
+
+ for (ntries = 0; ntries < 100; ntries++) {
+ if ((error = run_read(sc, RT2860_H2M_MAILBOX, &tmp)) != 0)
+ return error;
+ if (!(tmp & RT2860_H2M_BUSY))
+ break;
+ }
+ if (ntries == 100)
+ return ETIMEDOUT;
+
+ tmp = RT2860_H2M_BUSY | RT2860_TOKEN_NO_INTR << 16 | arg;
+ if ((error = run_write(sc, RT2860_H2M_MAILBOX, tmp)) == 0)
+ error = run_write(sc, RT2860_HOST_CMD, cmd);
+ return (error);
+}
+
+/*
+ * Add `delta' (signed) to each 4-bit sub-word of a 32-bit word.
+ * Used to adjust per-rate Tx power registers.
+ */
+static __inline uint32_t
+b4inc(uint32_t b32, int8_t delta)
+{
+ int8_t i, b4;
+
+ for (i = 0; i < 8; i++) {
+ b4 = b32 & 0xf;
+ b4 += delta;
+ if (b4 < 0)
+ b4 = 0;
+ else if (b4 > 0xf)
+ b4 = 0xf;
+ b32 = b32 >> 4 | b4 << 28;
+ }
+ return (b32);
+}
+
+static const char *
+run_get_rf(uint16_t rev)
+{
+ switch (rev) {
+ case RT2860_RF_2820: return "RT2820";
+ case RT2860_RF_2850: return "RT2850";
+ case RT2860_RF_2720: return "RT2720";
+ case RT2860_RF_2750: return "RT2750";
+ case RT3070_RF_3020: return "RT3020";
+ case RT3070_RF_2020: return "RT2020";
+ case RT3070_RF_3021: return "RT3021";
+ case RT3070_RF_3022: return "RT3022";
+ case RT3070_RF_3052: return "RT3052";
+ case RT3593_RF_3053: return "RT3053";
+ case RT5592_RF_5592: return "RT5592";
+ case RT5390_RF_5370: return "RT5370";
+ case RT5390_RF_5372: return "RT5372";
+ }
+ return ("unknown");
+}
+
+static void
+run_rt3593_get_txpower(struct run_softc *sc)
+{
+ uint16_t addr, val;
+ int i;
+
+ /* Read power settings for 2GHz channels. */
+ for (i = 0; i < 14; i += 2) {
+ addr = (sc->ntxchains == 3) ? RT3593_EEPROM_PWR2GHZ_BASE1 :
+ RT2860_EEPROM_PWR2GHZ_BASE1;
+ run_srom_read(sc, addr + i / 2, &val);
+ sc->txpow1[i + 0] = (int8_t)(val & 0xff);
+ sc->txpow1[i + 1] = (int8_t)(val >> 8);
+
+ addr = (sc->ntxchains == 3) ? RT3593_EEPROM_PWR2GHZ_BASE2 :
+ RT2860_EEPROM_PWR2GHZ_BASE2;
+ run_srom_read(sc, addr + i / 2, &val);
+ sc->txpow2[i + 0] = (int8_t)(val & 0xff);
+ sc->txpow2[i + 1] = (int8_t)(val >> 8);
+
+ if (sc->ntxchains == 3) {
+ run_srom_read(sc, RT3593_EEPROM_PWR2GHZ_BASE3 + i / 2,
+ &val);
+ sc->txpow3[i + 0] = (int8_t)(val & 0xff);
+ sc->txpow3[i + 1] = (int8_t)(val >> 8);
+ }
+ }
+ /* Fix broken Tx power entries. */
+ for (i = 0; i < 14; i++) {
+ if (sc->txpow1[i] > 31)
+ sc->txpow1[i] = 5;
+ if (sc->txpow2[i] > 31)
+ sc->txpow2[i] = 5;
+ if (sc->ntxchains == 3) {
+ if (sc->txpow3[i] > 31)
+ sc->txpow3[i] = 5;
+ }
+ }
+ /* Read power settings for 5GHz channels. */
+ for (i = 0; i < 40; i += 2) {
+ run_srom_read(sc, RT3593_EEPROM_PWR5GHZ_BASE1 + i / 2, &val);
+ sc->txpow1[i + 14] = (int8_t)(val & 0xff);
+ sc->txpow1[i + 15] = (int8_t)(val >> 8);
+
+ run_srom_read(sc, RT3593_EEPROM_PWR5GHZ_BASE2 + i / 2, &val);
+ sc->txpow2[i + 14] = (int8_t)(val & 0xff);
+ sc->txpow2[i + 15] = (int8_t)(val >> 8);
+
+ if (sc->ntxchains == 3) {
+ run_srom_read(sc, RT3593_EEPROM_PWR5GHZ_BASE3 + i / 2,
+ &val);
+ sc->txpow3[i + 14] = (int8_t)(val & 0xff);
+ sc->txpow3[i + 15] = (int8_t)(val >> 8);
+ }
+ }
+}
+
+static void
+run_get_txpower(struct run_softc *sc)
+{
+ uint16_t val;
+ int i;
+
+ /* Read power settings for 2GHz channels. */
+ for (i = 0; i < 14; i += 2) {
+ run_srom_read(sc, RT2860_EEPROM_PWR2GHZ_BASE1 + i / 2, &val);
+ sc->txpow1[i + 0] = (int8_t)(val & 0xff);
+ sc->txpow1[i + 1] = (int8_t)(val >> 8);
+
+ if (sc->mac_ver != 0x5390) {
+ run_srom_read(sc,
+ RT2860_EEPROM_PWR2GHZ_BASE2 + i / 2, &val);
+ sc->txpow2[i + 0] = (int8_t)(val & 0xff);
+ sc->txpow2[i + 1] = (int8_t)(val >> 8);
+ }
+ }
+ /* Fix broken Tx power entries. */
+ for (i = 0; i < 14; i++) {
+ if (sc->mac_ver >= 0x5390) {
+ if (sc->txpow1[i] < 0 || sc->txpow1[i] > 39)
+ sc->txpow1[i] = 5;
+ } else {
+ if (sc->txpow1[i] < 0 || sc->txpow1[i] > 31)
+ sc->txpow1[i] = 5;
+ }
+ if (sc->mac_ver > 0x5390) {
+ if (sc->txpow2[i] < 0 || sc->txpow2[i] > 39)
+ sc->txpow2[i] = 5;
+ } else if (sc->mac_ver < 0x5390) {
+ if (sc->txpow2[i] < 0 || sc->txpow2[i] > 31)
+ sc->txpow2[i] = 5;
+ }
+ RUN_DPRINTF(sc, RUN_DEBUG_TXPWR,
+ "chan %d: power1=%d, power2=%d\n",
+ rt2860_rf2850[i].chan, sc->txpow1[i], sc->txpow2[i]);
+ }
+ /* Read power settings for 5GHz channels. */
+ for (i = 0; i < 40; i += 2) {
+ run_srom_read(sc, RT2860_EEPROM_PWR5GHZ_BASE1 + i / 2, &val);
+ sc->txpow1[i + 14] = (int8_t)(val & 0xff);
+ sc->txpow1[i + 15] = (int8_t)(val >> 8);
+
+ run_srom_read(sc, RT2860_EEPROM_PWR5GHZ_BASE2 + i / 2, &val);
+ sc->txpow2[i + 14] = (int8_t)(val & 0xff);
+ sc->txpow2[i + 15] = (int8_t)(val >> 8);
+ }
+ /* Fix broken Tx power entries. */
+ for (i = 0; i < 40; i++ ) {
+ if (sc->mac_ver != 0x5592) {
+ if (sc->txpow1[14 + i] < -7 || sc->txpow1[14 + i] > 15)
+ sc->txpow1[14 + i] = 5;
+ if (sc->txpow2[14 + i] < -7 || sc->txpow2[14 + i] > 15)
+ sc->txpow2[14 + i] = 5;
+ }
+ RUN_DPRINTF(sc, RUN_DEBUG_TXPWR,
+ "chan %d: power1=%d, power2=%d\n",
+ rt2860_rf2850[14 + i].chan, sc->txpow1[14 + i],
+ sc->txpow2[14 + i]);
+ }
+}
+
+static int
+run_read_eeprom(struct run_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ int8_t delta_2ghz, delta_5ghz;
+ uint32_t tmp;
+ uint16_t val;
+ int ridx, ant, i;
+
+ /* check whether the ROM is eFUSE ROM or EEPROM */
+ sc->sc_srom_read = run_eeprom_read_2;
+ if (sc->mac_ver >= 0x3070) {
+ run_read(sc, RT3070_EFUSE_CTRL, &tmp);
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM, "EFUSE_CTRL=0x%08x\n", tmp);
+ if ((tmp & RT3070_SEL_EFUSE) || sc->mac_ver == 0x3593)
+ sc->sc_srom_read = run_efuse_read_2;
+ }
+
+ /* read ROM version */
+ run_srom_read(sc, RT2860_EEPROM_VERSION, &val);
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM,
+ "EEPROM rev=%d, FAE=%d\n", val >> 8, val & 0xff);
+
+ /* read MAC address */
+ run_srom_read(sc, RT2860_EEPROM_MAC01, &val);
+ ic->ic_macaddr[0] = val & 0xff;
+ ic->ic_macaddr[1] = val >> 8;
+ run_srom_read(sc, RT2860_EEPROM_MAC23, &val);
+ ic->ic_macaddr[2] = val & 0xff;
+ ic->ic_macaddr[3] = val >> 8;
+ run_srom_read(sc, RT2860_EEPROM_MAC45, &val);
+ ic->ic_macaddr[4] = val & 0xff;
+ ic->ic_macaddr[5] = val >> 8;
+
+ if (sc->mac_ver < 0x3593) {
+ /* read vender BBP settings */
+ for (i = 0; i < 10; i++) {
+ run_srom_read(sc, RT2860_EEPROM_BBP_BASE + i, &val);
+ sc->bbp[i].val = val & 0xff;
+ sc->bbp[i].reg = val >> 8;
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM,
+ "BBP%d=0x%02x\n", sc->bbp[i].reg, sc->bbp[i].val);
+ }
+ if (sc->mac_ver >= 0x3071) {
+ /* read vendor RF settings */
+ for (i = 0; i < 10; i++) {
+ run_srom_read(sc, RT3071_EEPROM_RF_BASE + i,
+ &val);
+ sc->rf[i].val = val & 0xff;
+ sc->rf[i].reg = val >> 8;
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM, "RF%d=0x%02x\n",
+ sc->rf[i].reg, sc->rf[i].val);
+ }
+ }
+ }
+
+ /* read RF frequency offset from EEPROM */
+ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_FREQ_LEDS :
+ RT3593_EEPROM_FREQ, &val);
+ sc->freq = ((val & 0xff) != 0xff) ? val & 0xff : 0;
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM, "EEPROM freq offset %d\n",
+ sc->freq & 0xff);
+
+ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_FREQ_LEDS :
+ RT3593_EEPROM_FREQ_LEDS, &val);
+ if (val >> 8 != 0xff) {
+ /* read LEDs operating mode */
+ sc->leds = val >> 8;
+ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_LED1 :
+ RT3593_EEPROM_LED1, &sc->led[0]);
+ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_LED2 :
+ RT3593_EEPROM_LED2, &sc->led[1]);
+ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_LED3 :
+ RT3593_EEPROM_LED3, &sc->led[2]);
+ } else {
+ /* broken EEPROM, use default settings */
+ sc->leds = 0x01;
+ sc->led[0] = 0x5555;
+ sc->led[1] = 0x2221;
+ sc->led[2] = 0x5627; /* differs from RT2860 */
+ }
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM,
+ "EEPROM LED mode=0x%02x, LEDs=0x%04x/0x%04x/0x%04x\n",
+ sc->leds, sc->led[0], sc->led[1], sc->led[2]);
+
+ /* read RF information */
+ if (sc->mac_ver == 0x5390 || sc->mac_ver ==0x5392)
+ run_srom_read(sc, 0x00, &val);
+ else
+ run_srom_read(sc, RT2860_EEPROM_ANTENNA, &val);
+
+ if (val == 0xffff) {
+ device_printf(sc->sc_dev,
+ "invalid EEPROM antenna info, using default\n");
+ if (sc->mac_ver == 0x3572) {
+ /* default to RF3052 2T2R */
+ sc->rf_rev = RT3070_RF_3052;
+ sc->ntxchains = 2;
+ sc->nrxchains = 2;
+ } else if (sc->mac_ver >= 0x3070) {
+ /* default to RF3020 1T1R */
+ sc->rf_rev = RT3070_RF_3020;
+ sc->ntxchains = 1;
+ sc->nrxchains = 1;
+ } else {
+ /* default to RF2820 1T2R */
+ sc->rf_rev = RT2860_RF_2820;
+ sc->ntxchains = 1;
+ sc->nrxchains = 2;
+ }
+ } else {
+ if (sc->mac_ver == 0x5390 || sc->mac_ver ==0x5392) {
+ sc->rf_rev = val;
+ run_srom_read(sc, RT2860_EEPROM_ANTENNA, &val);
+ } else
+ sc->rf_rev = (val >> 8) & 0xf;
+ sc->ntxchains = (val >> 4) & 0xf;
+ sc->nrxchains = val & 0xf;
+ }
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM, "EEPROM RF rev=0x%04x chains=%dT%dR\n",
+ sc->rf_rev, sc->ntxchains, sc->nrxchains);
+
+ /* check if RF supports automatic Tx access gain control */
+ run_srom_read(sc, RT2860_EEPROM_CONFIG, &val);
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM, "EEPROM CFG 0x%04x\n", val);
+ /* check if driver should patch the DAC issue */
+ if ((val >> 8) != 0xff)
+ sc->patch_dac = (val >> 15) & 1;
+ if ((val & 0xff) != 0xff) {
+ sc->ext_5ghz_lna = (val >> 3) & 1;
+ sc->ext_2ghz_lna = (val >> 2) & 1;
+ /* check if RF supports automatic Tx access gain control */
+ sc->calib_2ghz = sc->calib_5ghz = (val >> 1) & 1;
+ /* check if we have a hardware radio switch */
+ sc->rfswitch = val & 1;
+ }
+
+ /* Read Tx power settings. */
+ if (sc->mac_ver == 0x3593)
+ run_rt3593_get_txpower(sc);
+ else
+ run_get_txpower(sc);
+
+ /* read Tx power compensation for each Tx rate */
+ run_srom_read(sc, RT2860_EEPROM_DELTAPWR, &val);
+ delta_2ghz = delta_5ghz = 0;
+ if ((val & 0xff) != 0xff && (val & 0x80)) {
+ delta_2ghz = val & 0xf;
+ if (!(val & 0x40)) /* negative number */
+ delta_2ghz = -delta_2ghz;
+ }
+ val >>= 8;
+ if ((val & 0xff) != 0xff && (val & 0x80)) {
+ delta_5ghz = val & 0xf;
+ if (!(val & 0x40)) /* negative number */
+ delta_5ghz = -delta_5ghz;
+ }
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM | RUN_DEBUG_TXPWR,
+ "power compensation=%d (2GHz), %d (5GHz)\n", delta_2ghz, delta_5ghz);
+
+ for (ridx = 0; ridx < 5; ridx++) {
+ uint32_t reg;
+
+ run_srom_read(sc, RT2860_EEPROM_RPWR + ridx * 2, &val);
+ reg = val;
+ run_srom_read(sc, RT2860_EEPROM_RPWR + ridx * 2 + 1, &val);
+ reg |= (uint32_t)val << 16;
+
+ sc->txpow20mhz[ridx] = reg;
+ sc->txpow40mhz_2ghz[ridx] = b4inc(reg, delta_2ghz);
+ sc->txpow40mhz_5ghz[ridx] = b4inc(reg, delta_5ghz);
+
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM | RUN_DEBUG_TXPWR,
+ "ridx %d: power 20MHz=0x%08x, 40MHz/2GHz=0x%08x, "
+ "40MHz/5GHz=0x%08x\n", ridx, sc->txpow20mhz[ridx],
+ sc->txpow40mhz_2ghz[ridx], sc->txpow40mhz_5ghz[ridx]);
+ }
+
+ /* Read RSSI offsets and LNA gains from EEPROM. */
+ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_RSSI1_2GHZ :
+ RT3593_EEPROM_RSSI1_2GHZ, &val);
+ sc->rssi_2ghz[0] = val & 0xff; /* Ant A */
+ sc->rssi_2ghz[1] = val >> 8; /* Ant B */
+ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_RSSI2_2GHZ :
+ RT3593_EEPROM_RSSI2_2GHZ, &val);
+ if (sc->mac_ver >= 0x3070) {
+ if (sc->mac_ver == 0x3593) {
+ sc->txmixgain_2ghz = 0;
+ sc->rssi_2ghz[2] = val & 0xff; /* Ant C */
+ } else {
+ /*
+ * On RT3070 chips (limited to 2 Rx chains), this ROM
+ * field contains the Tx mixer gain for the 2GHz band.
+ */
+ if ((val & 0xff) != 0xff)
+ sc->txmixgain_2ghz = val & 0x7;
+ }
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM, "tx mixer gain=%u (2GHz)\n",
+ sc->txmixgain_2ghz);
+ } else
+ sc->rssi_2ghz[2] = val & 0xff; /* Ant C */
+ if (sc->mac_ver == 0x3593)
+ run_srom_read(sc, RT3593_EEPROM_LNA_5GHZ, &val);
+ sc->lna[2] = val >> 8; /* channel group 2 */
+
+ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_RSSI1_5GHZ :
+ RT3593_EEPROM_RSSI1_5GHZ, &val);
+ sc->rssi_5ghz[0] = val & 0xff; /* Ant A */
+ sc->rssi_5ghz[1] = val >> 8; /* Ant B */
+ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_RSSI2_5GHZ :
+ RT3593_EEPROM_RSSI2_5GHZ, &val);
+ if (sc->mac_ver == 0x3572) {
+ /*
+ * On RT3572 chips (limited to 2 Rx chains), this ROM
+ * field contains the Tx mixer gain for the 5GHz band.
+ */
+ if ((val & 0xff) != 0xff)
+ sc->txmixgain_5ghz = val & 0x7;
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM, "tx mixer gain=%u (5GHz)\n",
+ sc->txmixgain_5ghz);
+ } else
+ sc->rssi_5ghz[2] = val & 0xff; /* Ant C */
+ if (sc->mac_ver == 0x3593) {
+ sc->txmixgain_5ghz = 0;
+ run_srom_read(sc, RT3593_EEPROM_LNA_5GHZ, &val);
+ }
+ sc->lna[3] = val >> 8; /* channel group 3 */
+
+ run_srom_read(sc, (sc->mac_ver != 0x3593) ? RT2860_EEPROM_LNA :
+ RT3593_EEPROM_LNA, &val);
+ sc->lna[0] = val & 0xff; /* channel group 0 */
+ sc->lna[1] = val >> 8; /* channel group 1 */
+
+ /* fix broken 5GHz LNA entries */
+ if (sc->lna[2] == 0 || sc->lna[2] == 0xff) {
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM,
+ "invalid LNA for channel group %d\n", 2);
+ sc->lna[2] = sc->lna[1];
+ }
+ if (sc->lna[3] == 0 || sc->lna[3] == 0xff) {
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM,
+ "invalid LNA for channel group %d\n", 3);
+ sc->lna[3] = sc->lna[1];
+ }
+
+ /* fix broken RSSI offset entries */
+ for (ant = 0; ant < 3; ant++) {
+ if (sc->rssi_2ghz[ant] < -10 || sc->rssi_2ghz[ant] > 10) {
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM | RUN_DEBUG_RSSI,
+ "invalid RSSI%d offset: %d (2GHz)\n",
+ ant + 1, sc->rssi_2ghz[ant]);
+ sc->rssi_2ghz[ant] = 0;
+ }
+ if (sc->rssi_5ghz[ant] < -10 || sc->rssi_5ghz[ant] > 10) {
+ RUN_DPRINTF(sc, RUN_DEBUG_ROM | RUN_DEBUG_RSSI,
+ "invalid RSSI%d offset: %d (5GHz)\n",
+ ant + 1, sc->rssi_5ghz[ant]);
+ sc->rssi_5ghz[ant] = 0;
+ }
+ }
+ return (0);
+}
+
+static struct ieee80211_node *
+run_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN])
+{
+ return malloc(sizeof (struct run_node), M_80211_NODE,
+ M_NOWAIT | M_ZERO);
+}
+
+static int
+run_media_change(if_t ifp)
+{
+ struct ieee80211vap *vap = if_getsoftc(ifp);
+ struct ieee80211com *ic = vap->iv_ic;
+ const struct ieee80211_txparam *tp;
+ struct run_softc *sc = ic->ic_softc;
+ uint8_t rate, ridx;
+ int error;
+
+ RUN_LOCK(sc);
+
+ error = ieee80211_media_change(ifp);
+ if (error != 0) {
+ RUN_UNLOCK(sc);
+ return (error);
+ }
+
+ tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)];
+ if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE) {
+ struct ieee80211_node *ni;
+ struct run_node *rn;
+
+ /* XXX TODO: methodize with MCS rates */
+ rate = ic->ic_sup_rates[ic->ic_curmode].
+ rs_rates[tp->ucastrate] & IEEE80211_RATE_VAL;
+ for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++)
+ if (rt2860_rates[ridx].rate == rate)
+ break;
+
+ ni = ieee80211_ref_node(vap->iv_bss);
+ rn = RUN_NODE(ni);
+ rn->fix_ridx = ridx;
+ RUN_DPRINTF(sc, RUN_DEBUG_RATE, "rate=%d, fix_ridx=%d\n",
+ rate, rn->fix_ridx);
+ ieee80211_free_node(ni);
+ }
+
+#if 0
+ if ((if_getflags(ifp) & IFF_UP) &&
+ (if_getdrvflags(ifp) & RUN_RUNNING)){
+ run_init_locked(sc);
+ }
+#endif
+
+ RUN_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+run_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
+{
+ const struct ieee80211_txparam *tp;
+ struct ieee80211com *ic = vap->iv_ic;
+ struct run_softc *sc = ic->ic_softc;
+ struct run_vap *rvp = RUN_VAP(vap);
+ enum ieee80211_state ostate;
+ uint32_t sta[3];
+ uint8_t ratectl;
+ uint8_t restart_ratectl = 0;
+ uint8_t bid = 1 << rvp->rvp_id;
+
+ ostate = vap->iv_state;
+ RUN_DPRINTF(sc, RUN_DEBUG_STATE, "%s -> %s\n",
+ ieee80211_state_name[ostate],
+ ieee80211_state_name[nstate]);
+
+ IEEE80211_UNLOCK(ic);
+ RUN_LOCK(sc);
+
+ ratectl = sc->ratectl_run; /* remember current state */
+ sc->ratectl_run = RUN_RATECTL_OFF;
+ usb_callout_stop(&sc->ratectl_ch);
+
+ if (ostate == IEEE80211_S_RUN) {
+ /* turn link LED off */
+ run_set_leds(sc, RT2860_LED_RADIO);
+ }
+
+ switch (nstate) {
+ case IEEE80211_S_INIT:
+ restart_ratectl = 1;
+
+ if (ostate != IEEE80211_S_RUN)
+ break;
+
+ ratectl &= ~bid;
+ sc->runbmap &= ~bid;
+
+ /* abort TSF synchronization if there is no vap running */
+ if (--sc->running == 0)
+ run_disable_tsf(sc);
+ break;
+
+ case IEEE80211_S_RUN:
+ if (!(sc->runbmap & bid)) {
+ if(sc->running++)
+ restart_ratectl = 1;
+ sc->runbmap |= bid;
+ }
+
+ m_freem(rvp->beacon_mbuf);
+ rvp->beacon_mbuf = NULL;
+
+ switch (vap->iv_opmode) {
+ case IEEE80211_M_HOSTAP:
+ case IEEE80211_M_MBSS:
+ sc->ap_running |= bid;
+ ic->ic_opmode = vap->iv_opmode;
+ run_update_beacon_cb(vap);
+ break;
+ case IEEE80211_M_IBSS:
+ sc->adhoc_running |= bid;
+ if (!sc->ap_running)
+ ic->ic_opmode = vap->iv_opmode;
+ run_update_beacon_cb(vap);
+ break;
+ case IEEE80211_M_STA:
+ sc->sta_running |= bid;
+ if (!sc->ap_running && !sc->adhoc_running)
+ ic->ic_opmode = vap->iv_opmode;
+
+ /* read statistic counters (clear on read) */
+ run_read_region_1(sc, RT2860_TX_STA_CNT0,
+ (uint8_t *)sta, sizeof sta);
+
+ break;
+ default:
+ ic->ic_opmode = vap->iv_opmode;
+ break;
+ }
+
+ if (vap->iv_opmode != IEEE80211_M_MONITOR) {
+ struct ieee80211_node *ni;
+
+ if (ic->ic_bsschan == IEEE80211_CHAN_ANYC) {
+ RUN_UNLOCK(sc);
+ IEEE80211_LOCK(ic);
+ return (-1);
+ }
+ run_updateslot(ic);
+ run_enable_mrr(sc);
+ run_set_txpreamble(sc);
+ run_set_basicrates(sc);
+ ni = ieee80211_ref_node(vap->iv_bss);
+ IEEE80211_ADDR_COPY(sc->sc_bssid, ni->ni_bssid);
+ run_set_bssid(sc, sc->sc_bssid);
+ ieee80211_free_node(ni);
+ run_enable_tsf_sync(sc);
+
+ /* enable automatic rate adaptation */
+ tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)];
+ if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE)
+ ratectl |= bid;
+ } else
+ run_enable_tsf(sc);
+
+ /* turn link LED on */
+ run_set_leds(sc, RT2860_LED_RADIO |
+ (IEEE80211_IS_CHAN_2GHZ(ic->ic_curchan) ?
+ RT2860_LED_LINK_2GHZ : RT2860_LED_LINK_5GHZ));
+
+ break;
+ default:
+ RUN_DPRINTF(sc, RUN_DEBUG_STATE, "undefined state\n");
+ break;
+ }
+
+ /* restart amrr for running VAPs */
+ if ((sc->ratectl_run = ratectl) && restart_ratectl)
+ usb_callout_reset(&sc->ratectl_ch, hz, run_ratectl_to, sc);
+
+ RUN_UNLOCK(sc);
+ IEEE80211_LOCK(ic);
+
+ return(rvp->newstate(vap, nstate, arg));
+}
+
+static int
+run_wme_update(struct ieee80211com *ic)
+{
+ struct chanAccParams chp;
+ struct run_softc *sc = ic->ic_softc;
+ const struct wmeParams *ac;
+ int aci, error = 0;
+
+ ieee80211_wme_ic_getparams(ic, &chp);
+ ac = chp.cap_wmeParams;
+
+ /* update MAC TX configuration registers */
+ RUN_LOCK(sc);
+ for (aci = 0; aci < WME_NUM_AC; aci++) {
+ error = run_write(sc, RT2860_EDCA_AC_CFG(aci),
+ ac[aci].wmep_logcwmax << 16 |
+ ac[aci].wmep_logcwmin << 12 |
+ ac[aci].wmep_aifsn << 8 |
+ ac[aci].wmep_txopLimit);
+ if (error) goto err;
+ }
+
+ /* update SCH/DMA registers too */
+ error = run_write(sc, RT2860_WMM_AIFSN_CFG,
+ ac[WME_AC_VO].wmep_aifsn << 12 |
+ ac[WME_AC_VI].wmep_aifsn << 8 |
+ ac[WME_AC_BK].wmep_aifsn << 4 |
+ ac[WME_AC_BE].wmep_aifsn);
+ if (error) goto err;
+ error = run_write(sc, RT2860_WMM_CWMIN_CFG,
+ ac[WME_AC_VO].wmep_logcwmin << 12 |
+ ac[WME_AC_VI].wmep_logcwmin << 8 |
+ ac[WME_AC_BK].wmep_logcwmin << 4 |
+ ac[WME_AC_BE].wmep_logcwmin);
+ if (error) goto err;
+ error = run_write(sc, RT2860_WMM_CWMAX_CFG,
+ ac[WME_AC_VO].wmep_logcwmax << 12 |
+ ac[WME_AC_VI].wmep_logcwmax << 8 |
+ ac[WME_AC_BK].wmep_logcwmax << 4 |
+ ac[WME_AC_BE].wmep_logcwmax);
+ if (error) goto err;
+ error = run_write(sc, RT2860_WMM_TXOP0_CFG,
+ ac[WME_AC_BK].wmep_txopLimit << 16 |
+ ac[WME_AC_BE].wmep_txopLimit);
+ if (error) goto err;
+ error = run_write(sc, RT2860_WMM_TXOP1_CFG,
+ ac[WME_AC_VO].wmep_txopLimit << 16 |
+ ac[WME_AC_VI].wmep_txopLimit);
+
+err:
+ RUN_UNLOCK(sc);
+ if (error)
+ RUN_DPRINTF(sc, RUN_DEBUG_USB, "WME update failed\n");
+
+ return (error);
+}
+
+static void
+run_key_set_cb(void *arg)
+{
+ struct run_cmdq *cmdq = arg;
+ struct ieee80211vap *vap = cmdq->arg1;
+ struct ieee80211_key *k = cmdq->k;
+ struct ieee80211com *ic = vap->iv_ic;
+ struct run_softc *sc = ic->ic_softc;
+ struct ieee80211_node *ni;
+ u_int cipher = k->wk_cipher->ic_cipher;
+ uint32_t attr;
+ uint16_t base, associd;
+ uint8_t mode, wcid, iv[8];
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (vap->iv_opmode == IEEE80211_M_HOSTAP)
+ ni = ieee80211_find_vap_node(&ic->ic_sta, vap, cmdq->mac);
+ else
+ ni = vap->iv_bss;
+ associd = (ni != NULL) ? ni->ni_associd : 0;
+
+ /* map net80211 cipher to RT2860 security mode */
+ switch (cipher) {
+ case IEEE80211_CIPHER_WEP:
+ if(k->wk_keylen < 8)
+ mode = RT2860_MODE_WEP40;
+ else
+ mode = RT2860_MODE_WEP104;
+ break;
+ case IEEE80211_CIPHER_TKIP:
+ mode = RT2860_MODE_TKIP;
+ break;
+ case IEEE80211_CIPHER_AES_CCM:
+ mode = RT2860_MODE_AES_CCMP;
+ break;
+ default:
+ RUN_DPRINTF(sc, RUN_DEBUG_KEY, "undefined case\n");
+ return;
+ }
+
+ RUN_DPRINTF(sc, RUN_DEBUG_KEY,
+ "associd=%x, keyix=%d, mode=%x, type=%s, tx=%s, rx=%s\n",
+ associd, k->wk_keyix, mode,
+ (k->wk_flags & IEEE80211_KEY_GROUP) ? "group" : "pairwise",
+ (k->wk_flags & IEEE80211_KEY_XMIT) ? "on" : "off",
+ (k->wk_flags & IEEE80211_KEY_RECV) ? "on" : "off");
+
+ if (k->wk_flags & IEEE80211_KEY_GROUP) {
+ wcid = 0; /* NB: update WCID0 for group keys */
+ base = RT2860_SKEY(RUN_VAP(vap)->rvp_id, k->wk_keyix);
+ } else {
+ wcid = (vap->iv_opmode == IEEE80211_M_STA) ?
+ 1 : RUN_AID2WCID(associd);
+ base = RT2860_PKEY(wcid);
+ }
+
+ if (cipher == IEEE80211_CIPHER_TKIP) {
+ if(run_write_region_1(sc, base, k->wk_key, 16))
+ return;
+ if(run_write_region_1(sc, base + 16, &k->wk_key[16], 8)) /* wk_txmic */
+ return;
+ if(run_write_region_1(sc, base + 24, &k->wk_key[24], 8)) /* wk_rxmic */
+ return;
+ } else {
+ /* roundup len to 16-bit: XXX fix write_region_1() instead */
+ if(run_write_region_1(sc, base, k->wk_key, (k->wk_keylen + 1) & ~1))
+ return;
+ }
+
+ if (!(k->wk_flags & IEEE80211_KEY_GROUP) ||
+ (k->wk_flags & (IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV))) {
+ /* set initial packet number in IV+EIV */
+ if (cipher == IEEE80211_CIPHER_WEP) {
+ memset(iv, 0, sizeof iv);
+ iv[3] = vap->iv_def_txkey << 6;
+ } else {
+ if (cipher == IEEE80211_CIPHER_TKIP) {
+ iv[0] = k->wk_keytsc >> 8;
+ iv[1] = (iv[0] | 0x20) & 0x7f;
+ iv[2] = k->wk_keytsc;
+ } else /* CCMP */ {
+ iv[0] = k->wk_keytsc;
+ iv[1] = k->wk_keytsc >> 8;
+ iv[2] = 0;
+ }
+ iv[3] = k->wk_keyix << 6 | IEEE80211_WEP_EXTIV;
+ iv[4] = k->wk_keytsc >> 16;
+ iv[5] = k->wk_keytsc >> 24;
+ iv[6] = k->wk_keytsc >> 32;
+ iv[7] = k->wk_keytsc >> 40;
+ }
+ if (run_write_region_1(sc, RT2860_IVEIV(wcid), iv, 8))
+ return;
+ }
+
+ if (k->wk_flags & IEEE80211_KEY_GROUP) {
+ /* install group key */
+ if (run_read(sc, RT2860_SKEY_MODE_0_7, &attr))
+ return;
+ attr &= ~(0xf << (k->wk_keyix * 4));
+ attr |= mode << (k->wk_keyix * 4);
+ if (run_write(sc, RT2860_SKEY_MODE_0_7, attr))
+ return;
+ } else {
+ /* install pairwise key */
+ if (run_read(sc, RT2860_WCID_ATTR(wcid), &attr))
+ return;
+ attr = (attr & ~0xf) | (mode << 1) | RT2860_RX_PKEY_EN;
+ if (run_write(sc, RT2860_WCID_ATTR(wcid), attr))
+ return;
+ }
+
+ /* TODO create a pass-thru key entry? */
+
+ /* need wcid to delete the right key later */
+ k->wk_pad = wcid;
+}
+
+/*
+ * Don't have to be deferred, but in order to keep order of
+ * execution, i.e. with run_key_delete(), defer this and let
+ * run_cmdq_cb() maintain the order.
+ *
+ * return 0 on error
+ */
+static int
+run_key_set(struct ieee80211vap *vap, struct ieee80211_key *k)
+{
+ struct ieee80211com *ic = vap->iv_ic;
+ struct run_softc *sc = ic->ic_softc;
+ uint32_t i;
+
+ i = RUN_CMDQ_GET(&sc->cmdq_store);
+ RUN_DPRINTF(sc, RUN_DEBUG_KEY, "cmdq_store=%d\n", i);
+ sc->cmdq[i].func = run_key_set_cb;
+ sc->cmdq[i].arg0 = NULL;
+ sc->cmdq[i].arg1 = vap;
+ sc->cmdq[i].k = k;
+ IEEE80211_ADDR_COPY(sc->cmdq[i].mac, k->wk_macaddr);
+ ieee80211_runtask(ic, &sc->cmdq_task);
+
+ /*
+ * To make sure key will be set when hostapd
+ * calls iv_key_set() before if_init().
+ */
+ if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
+ RUN_LOCK(sc);
+ sc->cmdq_key_set = RUN_CMDQ_GO;
+ RUN_UNLOCK(sc);
+ }
+
+ return (1);
+}
+
+/*
+ * If wlan is destroyed without being brought down i.e. without
+ * wlan down or wpa_cli terminate, this function is called after
+ * vap is gone. Don't refer it.
+ */
+static void
+run_key_delete_cb(void *arg)
+{
+ struct run_cmdq *cmdq = arg;
+ struct run_softc *sc = cmdq->arg1;
+ struct ieee80211_key *k = &cmdq->key;
+ uint32_t attr;
+ uint8_t wcid;
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (k->wk_flags & IEEE80211_KEY_GROUP) {
+ /* remove group key */
+ RUN_DPRINTF(sc, RUN_DEBUG_KEY, "removing group key\n");
+ run_read(sc, RT2860_SKEY_MODE_0_7, &attr);
+ attr &= ~(0xf << (k->wk_keyix * 4));
+ run_write(sc, RT2860_SKEY_MODE_0_7, attr);
+ } else {
+ /* remove pairwise key */
+ RUN_DPRINTF(sc, RUN_DEBUG_KEY,
+ "removing key for wcid %x\n", k->wk_pad);
+ /* matching wcid was written to wk_pad in run_key_set() */
+ wcid = k->wk_pad;
+ run_read(sc, RT2860_WCID_ATTR(wcid), &attr);
+ attr &= ~0xf;
+ run_write(sc, RT2860_WCID_ATTR(wcid), attr);
+ run_set_region_4(sc, RT2860_WCID_ENTRY(wcid), 0, 8);
+ }
+
+ k->wk_pad = 0;
+}
+
+/*
+ * return 0 on error
+ */
+static int
+run_key_delete(struct ieee80211vap *vap, struct ieee80211_key *k)
+{
+ struct ieee80211com *ic = vap->iv_ic;
+ struct run_softc *sc = ic->ic_softc;
+ struct ieee80211_key *k0;
+ uint32_t i;
+
+ /*
+ * When called back, key might be gone. So, make a copy
+ * of some values need to delete keys before deferring.
+ * But, because of LOR with node lock, cannot use lock here.
+ * So, use atomic instead.
+ */
+ i = RUN_CMDQ_GET(&sc->cmdq_store);
+ RUN_DPRINTF(sc, RUN_DEBUG_KEY, "cmdq_store=%d\n", i);
+ sc->cmdq[i].func = run_key_delete_cb;
+ sc->cmdq[i].arg0 = NULL;
+ sc->cmdq[i].arg1 = sc;
+ k0 = &sc->cmdq[i].key;
+ k0->wk_flags = k->wk_flags;
+ k0->wk_keyix = k->wk_keyix;
+ /* matching wcid was written to wk_pad in run_key_set() */
+ k0->wk_pad = k->wk_pad;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+ return (1); /* return fake success */
+
+}
+
+static void
+run_ratectl_to(void *arg)
+{
+ struct run_softc *sc = arg;
+
+ /* do it in a process context, so it can go sleep */
+ ieee80211_runtask(&sc->sc_ic, &sc->ratectl_task);
+ /* next timeout will be rescheduled in the callback task */
+}
+
+/* ARGSUSED */
+static void
+run_ratectl_cb(void *arg, int pending)
+{
+ struct run_softc *sc = arg;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+
+ if (vap == NULL)
+ return;
+
+ if (sc->rvp_cnt > 1 || vap->iv_opmode != IEEE80211_M_STA) {
+ /*
+ * run_reset_livelock() doesn't do anything with AMRR,
+ * but Ralink wants us to call it every 1 sec. So, we
+ * piggyback here rather than creating another callout.
+ * Livelock may occur only in HOSTAP or IBSS mode
+ * (when h/w is sending beacons).
+ */
+ RUN_LOCK(sc);
+ run_reset_livelock(sc);
+ /* just in case, there are some stats to drain */
+ run_drain_fifo(sc);
+ RUN_UNLOCK(sc);
+ }
+
+ ieee80211_iterate_nodes(&ic->ic_sta, run_iter_func, sc);
+
+ RUN_LOCK(sc);
+ if(sc->ratectl_run != RUN_RATECTL_OFF)
+ usb_callout_reset(&sc->ratectl_ch, hz, run_ratectl_to, sc);
+ RUN_UNLOCK(sc);
+}
+
+static void
+run_drain_fifo(void *arg)
+{
+ struct run_softc *sc = arg;
+ uint32_t stat;
+ uint16_t (*wstat)[3];
+ uint8_t wcid, mcs, pid;
+ int8_t retry;
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+
+ for (;;) {
+ /* drain Tx status FIFO (maxsize = 16) */
+ run_read(sc, RT2860_TX_STAT_FIFO, &stat);
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "tx stat 0x%08x\n", stat);
+ if (!(stat & RT2860_TXQ_VLD))
+ break;
+
+ wcid = (stat >> RT2860_TXQ_WCID_SHIFT) & 0xff;
+
+ /* if no ACK was requested, no feedback is available */
+ if (!(stat & RT2860_TXQ_ACKREQ) || wcid > RT2870_WCID_MAX ||
+ wcid == 0)
+ continue;
+
+ /*
+ * Even though each stat is Tx-complete-status like format,
+ * the device can poll stats. Because there is no guarantee
+ * that the referring node is still around when read the stats.
+ * So that, if we use ieee80211_ratectl_tx_update(), we will
+ * have hard time not to refer already freed node.
+ *
+ * To eliminate such page faults, we poll stats in softc.
+ * Then, update the rates later with ieee80211_ratectl_tx_update().
+ */
+ wstat = &(sc->wcid_stats[wcid]);
+ (*wstat)[RUN_TXCNT]++;
+ if (stat & RT2860_TXQ_OK)
+ (*wstat)[RUN_SUCCESS]++;
+ else
+ counter_u64_add(sc->sc_ic.ic_oerrors, 1);
+ /*
+ * Check if there were retries, ie if the Tx success rate is
+ * different from the requested rate. Note that it works only
+ * because we do not allow rate fallback from OFDM to CCK.
+ */
+ mcs = (stat >> RT2860_TXQ_MCS_SHIFT) & 0x7f;
+ pid = (stat >> RT2860_TXQ_PID_SHIFT) & 0xf;
+ if ((retry = pid -1 - mcs) > 0) {
+ (*wstat)[RUN_TXCNT] += retry;
+ (*wstat)[RUN_RETRY] += retry;
+ }
+ }
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "count=%d\n", sc->fifo_cnt);
+
+ sc->fifo_cnt = 0;
+}
+
+static void
+run_iter_func(void *arg, struct ieee80211_node *ni)
+{
+ struct run_softc *sc = arg;
+ struct ieee80211_ratectl_tx_stats *txs = &sc->sc_txs;
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct run_node *rn = RUN_NODE(ni);
+ union run_stats sta[2];
+ uint16_t (*wstat)[3];
+ int error, ridx;
+ uint8_t dot11rate;
+
+ RUN_LOCK(sc);
+
+ /* Check for special case */
+ if (sc->rvp_cnt <= 1 && vap->iv_opmode == IEEE80211_M_STA &&
+ ni != vap->iv_bss)
+ goto fail;
+
+ txs->flags = IEEE80211_RATECTL_TX_STATS_NODE |
+ IEEE80211_RATECTL_TX_STATS_RETRIES;
+ txs->ni = ni;
+ if (sc->rvp_cnt <= 1 && (vap->iv_opmode == IEEE80211_M_IBSS ||
+ vap->iv_opmode == IEEE80211_M_STA)) {
+ /* read statistic counters (clear on read) and update AMRR state */
+ error = run_read_region_1(sc, RT2860_TX_STA_CNT0, (uint8_t *)sta,
+ sizeof sta);
+ if (error != 0)
+ goto fail;
+
+ /* count failed TX as errors */
+ if_inc_counter(vap->iv_ifp, IFCOUNTER_OERRORS,
+ le16toh(sta[0].error.fail));
+
+ txs->nretries = le16toh(sta[1].tx.retry);
+ txs->nsuccess = le16toh(sta[1].tx.success);
+ /* nretries??? */
+ txs->nframes = txs->nretries + txs->nsuccess +
+ le16toh(sta[0].error.fail);
+
+ RUN_DPRINTF(sc, RUN_DEBUG_RATE,
+ "retrycnt=%d success=%d failcnt=%d\n",
+ txs->nretries, txs->nsuccess, le16toh(sta[0].error.fail));
+ } else {
+ wstat = &(sc->wcid_stats[RUN_AID2WCID(ni->ni_associd)]);
+
+ if (wstat == &(sc->wcid_stats[0]) ||
+ wstat > &(sc->wcid_stats[RT2870_WCID_MAX]))
+ goto fail;
+
+ txs->nretries = (*wstat)[RUN_RETRY];
+ txs->nsuccess = (*wstat)[RUN_SUCCESS];
+ txs->nframes = (*wstat)[RUN_TXCNT];
+ RUN_DPRINTF(sc, RUN_DEBUG_RATE,
+ "retrycnt=%d txcnt=%d success=%d\n",
+ txs->nretries, txs->nframes, txs->nsuccess);
+
+ memset(wstat, 0, sizeof(*wstat));
+ }
+
+ ieee80211_ratectl_tx_update(vap, txs);
+ ieee80211_ratectl_rate(ni, NULL, 0);
+ /* XXX TODO: methodize with MCS rates */
+ dot11rate = ieee80211_node_get_txrate_dot11rate(ni);
+ for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++)
+ if (rt2860_rates[ridx].rate == dot11rate)
+ break;
+ rn->amrr_ridx = ridx;
+
+fail:
+ RUN_UNLOCK(sc);
+
+ RUN_DPRINTF(sc, RUN_DEBUG_RATE, "rate=0x%02x, ridx=%d\n",
+ ieee80211_node_get_txrate_dot11rate(ni), rn->amrr_ridx);
+}
+
+static void
+run_newassoc_cb(void *arg)
+{
+ struct run_cmdq *cmdq = arg;
+ struct ieee80211_node *ni = cmdq->arg1;
+ struct run_softc *sc = ni->ni_vap->iv_ic->ic_softc;
+ uint8_t wcid = cmdq->wcid;
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+
+ run_write_region_1(sc, RT2860_WCID_ENTRY(wcid),
+ ni->ni_macaddr, IEEE80211_ADDR_LEN);
+
+ memset(&(sc->wcid_stats[wcid]), 0, sizeof(sc->wcid_stats[wcid]));
+}
+
+static void
+run_newassoc(struct ieee80211_node *ni, int isnew)
+{
+ struct run_node *rn = RUN_NODE(ni);
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct ieee80211com *ic = vap->iv_ic;
+ struct run_softc *sc = ic->ic_softc;
+ uint8_t rate;
+ uint8_t ridx;
+ uint8_t wcid;
+
+ wcid = (vap->iv_opmode == IEEE80211_M_STA) ?
+ 1 : RUN_AID2WCID(ni->ni_associd);
+
+ if (wcid > RT2870_WCID_MAX) {
+ device_printf(sc->sc_dev, "wcid=%d out of range\n", wcid);
+ return;
+ }
+
+ /* only interested in true associations */
+ if (isnew && ni->ni_associd != 0) {
+ /*
+ * This function could is called though timeout function.
+ * Need to defer.
+ */
+ uint32_t cnt = RUN_CMDQ_GET(&sc->cmdq_store);
+ RUN_DPRINTF(sc, RUN_DEBUG_STATE, "cmdq_store=%d\n", cnt);
+ sc->cmdq[cnt].func = run_newassoc_cb;
+ sc->cmdq[cnt].arg0 = NULL;
+ sc->cmdq[cnt].arg1 = ni;
+ sc->cmdq[cnt].wcid = wcid;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+ }
+
+ RUN_DPRINTF(sc, RUN_DEBUG_STATE,
+ "new assoc isnew=%d associd=%x addr=%s\n",
+ isnew, ni->ni_associd, ether_sprintf(ni->ni_macaddr));
+
+ rate = vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)].mgmtrate;
+ /* XXX TODO: methodize with MCS rates */
+ for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++)
+ if (rt2860_rates[ridx].rate == rate)
+ break;
+ rn->mgt_ridx = ridx;
+ RUN_DPRINTF(sc, RUN_DEBUG_STATE | RUN_DEBUG_RATE,
+ "rate=%d, mgmt_ridx=%d\n", rate, rn->mgt_ridx);
+
+ RUN_LOCK(sc);
+ if(sc->ratectl_run != RUN_RATECTL_OFF)
+ usb_callout_reset(&sc->ratectl_ch, hz, run_ratectl_to, sc);
+ RUN_UNLOCK(sc);
+}
+
+/*
+ * Return the Rx chain with the highest RSSI for a given frame.
+ */
+static __inline uint8_t
+run_maxrssi_chain(struct run_softc *sc, const struct rt2860_rxwi *rxwi)
+{
+ uint8_t rxchain = 0;
+
+ if (sc->nrxchains > 1) {
+ if (rxwi->rssi[1] > rxwi->rssi[rxchain])
+ rxchain = 1;
+ if (sc->nrxchains > 2)
+ if (rxwi->rssi[2] > rxwi->rssi[rxchain])
+ rxchain = 2;
+ }
+ return (rxchain);
+}
+
+static void
+run_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m, int subtype,
+ const struct ieee80211_rx_stats *rxs, int rssi, int nf)
+{
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct run_softc *sc = vap->iv_ic->ic_softc;
+ struct run_vap *rvp = RUN_VAP(vap);
+ uint64_t ni_tstamp, rx_tstamp;
+
+ rvp->recv_mgmt(ni, m, subtype, rxs, rssi, nf);
+
+ if (vap->iv_state == IEEE80211_S_RUN &&
+ (subtype == IEEE80211_FC0_SUBTYPE_BEACON ||
+ subtype == IEEE80211_FC0_SUBTYPE_PROBE_RESP)) {
+ ni_tstamp = le64toh(ni->ni_tstamp.tsf);
+ RUN_LOCK(sc);
+ run_get_tsf(sc, &rx_tstamp);
+ RUN_UNLOCK(sc);
+ rx_tstamp = le64toh(rx_tstamp);
+
+ if (ni_tstamp >= rx_tstamp) {
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV | RUN_DEBUG_BEACON,
+ "ibss merge, tsf %ju tstamp %ju\n",
+ (uintmax_t)rx_tstamp, (uintmax_t)ni_tstamp);
+ (void) ieee80211_ibss_merge(ni);
+ }
+ }
+}
+
+static void
+run_rx_frame(struct run_softc *sc, struct mbuf *m, uint32_t dmalen)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_frame *wh;
+ struct ieee80211_node *ni;
+ struct rt2870_rxd *rxd;
+ struct rt2860_rxwi *rxwi;
+ uint32_t flags;
+ uint16_t len, rxwisize;
+ uint8_t ant, rssi;
+ int8_t nf;
+
+ rxwisize = sizeof(struct rt2860_rxwi);
+ if (sc->mac_ver == 0x5592)
+ rxwisize += sizeof(uint64_t);
+ else if (sc->mac_ver == 0x3593)
+ rxwisize += sizeof(uint32_t);
+
+ if (__predict_false(dmalen <
+ rxwisize + sizeof(struct ieee80211_frame_ack))) {
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV,
+ "payload is too short: dma length %u < %zu\n",
+ dmalen, rxwisize + sizeof(struct ieee80211_frame_ack));
+ goto fail;
+ }
+
+ rxwi = mtod(m, struct rt2860_rxwi *);
+ len = le16toh(rxwi->len) & 0xfff;
+
+ if (__predict_false(len > dmalen - rxwisize)) {
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV,
+ "bad RXWI length %u > %u\n", len, dmalen);
+ goto fail;
+ }
+
+ /* Rx descriptor is located at the end */
+ rxd = (struct rt2870_rxd *)(mtod(m, caddr_t) + dmalen);
+ flags = le32toh(rxd->flags);
+
+ if (__predict_false(flags & (RT2860_RX_CRCERR | RT2860_RX_ICVERR))) {
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV, "%s error.\n",
+ (flags & RT2860_RX_CRCERR)?"CRC":"ICV");
+ goto fail;
+ }
+
+ if (flags & RT2860_RX_L2PAD) {
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV,
+ "received RT2860_RX_L2PAD frame\n");
+ len += 2;
+ }
+
+ m->m_data += rxwisize;
+ m->m_pkthdr.len = m->m_len = len;
+
+ wh = mtod(m, struct ieee80211_frame *);
+
+ if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) != 0 &&
+ (flags & RT2860_RX_DEC) != 0) {
+ wh->i_fc[1] &= ~IEEE80211_FC1_PROTECTED;
+ m->m_flags |= M_WEP;
+ }
+
+ if (len >= sizeof(struct ieee80211_frame_min)) {
+ ni = ieee80211_find_rxnode(ic,
+ mtod(m, struct ieee80211_frame_min *));
+ } else
+ ni = NULL;
+
+ if(ni && ni->ni_flags & IEEE80211_NODE_HT) {
+ m->m_flags |= M_AMPDU;
+ }
+
+ if (__predict_false(flags & RT2860_RX_MICERR)) {
+ /* report MIC failures to net80211 for TKIP */
+ if (ni != NULL)
+ ieee80211_notify_michael_failure(ni->ni_vap, wh,
+ rxwi->keyidx);
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV,
+ "MIC error. Someone is lying.\n");
+ goto fail;
+ }
+
+ ant = run_maxrssi_chain(sc, rxwi);
+ rssi = rxwi->rssi[ant];
+ nf = run_rssi2dbm(sc, rssi, ant);
+
+ if (__predict_false(ieee80211_radiotap_active(ic))) {
+ struct run_rx_radiotap_header *tap = &sc->sc_rxtap;
+ uint16_t phy;
+
+ tap->wr_flags = 0;
+ if (flags & RT2860_RX_L2PAD)
+ tap->wr_flags |= IEEE80211_RADIOTAP_F_DATAPAD;
+ tap->wr_antsignal = rssi;
+ tap->wr_antenna = ant;
+ tap->wr_dbm_antsignal = run_rssi2dbm(sc, rssi, ant);
+ tap->wr_rate = 2; /* in case it can't be found below */
+ RUN_LOCK(sc);
+ run_get_tsf(sc, &tap->wr_tsf);
+ RUN_UNLOCK(sc);
+ phy = le16toh(rxwi->phy);
+ switch (phy & RT2860_PHY_MODE) {
+ case RT2860_PHY_CCK:
+ switch ((phy & RT2860_PHY_MCS) & ~RT2860_PHY_SHPRE) {
+ case 0: tap->wr_rate = 2; break;
+ case 1: tap->wr_rate = 4; break;
+ case 2: tap->wr_rate = 11; break;
+ case 3: tap->wr_rate = 22; break;
+ }
+ if (phy & RT2860_PHY_SHPRE)
+ tap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE;
+ break;
+ case RT2860_PHY_OFDM:
+ switch (phy & RT2860_PHY_MCS) {
+ case 0: tap->wr_rate = 12; break;
+ case 1: tap->wr_rate = 18; break;
+ case 2: tap->wr_rate = 24; break;
+ case 3: tap->wr_rate = 36; break;
+ case 4: tap->wr_rate = 48; break;
+ case 5: tap->wr_rate = 72; break;
+ case 6: tap->wr_rate = 96; break;
+ case 7: tap->wr_rate = 108; break;
+ }
+ break;
+ }
+ }
+
+ if (ni != NULL) {
+ (void)ieee80211_input(ni, m, rssi, nf);
+ ieee80211_free_node(ni);
+ } else {
+ (void)ieee80211_input_all(ic, m, rssi, nf);
+ }
+
+ return;
+
+fail:
+ m_freem(m);
+ counter_u64_add(ic->ic_ierrors, 1);
+}
+
+static void
+run_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct run_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct mbuf *m = NULL;
+ struct mbuf *m0;
+ uint32_t dmalen, mbuf_len;
+ uint16_t rxwisize;
+ int xferlen;
+
+ rxwisize = sizeof(struct rt2860_rxwi);
+ if (sc->mac_ver == 0x5592)
+ rxwisize += sizeof(uint64_t);
+ else if (sc->mac_ver == 0x3593)
+ rxwisize += sizeof(uint32_t);
+
+ usbd_xfer_status(xfer, &xferlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV,
+ "rx done, actlen=%d\n", xferlen);
+
+ if (xferlen < (int)(sizeof(uint32_t) + rxwisize +
+ sizeof(struct rt2870_rxd))) {
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV_DESC | RUN_DEBUG_USB,
+ "xfer too short %d\n", xferlen);
+ goto tr_setup;
+ }
+
+ m = sc->rx_m;
+ sc->rx_m = NULL;
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ if (sc->rx_m == NULL) {
+ sc->rx_m = m_getjcl(M_NOWAIT, MT_DATA, M_PKTHDR,
+ RUN_MAX_RXSZ);
+ }
+ if (sc->rx_m == NULL) {
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV |
+ RUN_DEBUG_RECV_DESC | RUN_DEBUG_USB,
+ "could not allocate mbuf - idle with stall\n");
+ counter_u64_add(ic->ic_ierrors, 1);
+ usbd_xfer_set_stall(xfer);
+ usbd_xfer_set_frames(xfer, 0);
+ } else {
+ /*
+ * Directly loading a mbuf cluster into DMA to
+ * save some data copying. This works because
+ * there is only one cluster.
+ */
+ usbd_xfer_set_frame_data(xfer, 0,
+ mtod(sc->rx_m, caddr_t), RUN_MAX_RXSZ);
+ usbd_xfer_set_frames(xfer, 1);
+ }
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ if (error == USB_ERR_TIMEOUT)
+ device_printf(sc->sc_dev, "device timeout\n");
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto tr_setup;
+ }
+ if (sc->rx_m != NULL) {
+ m_freem(sc->rx_m);
+ sc->rx_m = NULL;
+ }
+ break;
+ }
+
+ if (m == NULL)
+ return;
+
+ /* inputting all the frames must be last */
+
+ RUN_UNLOCK(sc);
+
+ m->m_pkthdr.len = m->m_len = xferlen;
+
+ /* HW can aggregate multiple 802.11 frames in a single USB xfer */
+ for(;;) {
+ dmalen = le32toh(*mtod(m, uint32_t *)) & 0xffff;
+
+ if ((dmalen >= (uint32_t)-8) || (dmalen == 0) ||
+ ((dmalen & 3) != 0)) {
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV_DESC | RUN_DEBUG_USB,
+ "bad DMA length %u\n", dmalen);
+ break;
+ }
+ if ((dmalen + 8) > (uint32_t)xferlen) {
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV_DESC | RUN_DEBUG_USB,
+ "bad DMA length %u > %d\n",
+ dmalen + 8, xferlen);
+ break;
+ }
+
+ /* If it is the last one or a single frame, we won't copy. */
+ if ((xferlen -= dmalen + 8) <= 8) {
+ /* trim 32-bit DMA-len header */
+ m->m_data += 4;
+ m->m_pkthdr.len = m->m_len -= 4;
+ run_rx_frame(sc, m, dmalen);
+ m = NULL; /* don't free source buffer */
+ break;
+ }
+
+ mbuf_len = dmalen + sizeof(struct rt2870_rxd);
+ if (__predict_false(mbuf_len > MCLBYTES)) {
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV_DESC | RUN_DEBUG_USB,
+ "payload is too big: mbuf_len %u\n", mbuf_len);
+ counter_u64_add(ic->ic_ierrors, 1);
+ break;
+ }
+
+ /* copy aggregated frames to another mbuf */
+ m0 = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (__predict_false(m0 == NULL)) {
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV_DESC,
+ "could not allocate mbuf\n");
+ counter_u64_add(ic->ic_ierrors, 1);
+ break;
+ }
+ m_copydata(m, 4 /* skip 32-bit DMA-len header */,
+ mbuf_len, mtod(m0, caddr_t));
+ m0->m_pkthdr.len = m0->m_len = mbuf_len;
+ run_rx_frame(sc, m0, dmalen);
+
+ /* update data ptr */
+ m->m_data += mbuf_len + 4;
+ m->m_pkthdr.len = m->m_len -= mbuf_len + 4;
+ }
+
+ /* make sure we free the source buffer, if any */
+ m_freem(m);
+
+#ifdef IEEE80211_SUPPORT_SUPERG
+ ieee80211_ff_age_all(ic, 100);
+#endif
+ RUN_LOCK(sc);
+}
+
+static void
+run_tx_free(struct run_endpoint_queue *pq,
+ struct run_tx_data *data, int txerr)
+{
+
+ ieee80211_tx_complete(data->ni, data->m, txerr);
+
+ data->m = NULL;
+ data->ni = NULL;
+
+ STAILQ_INSERT_TAIL(&pq->tx_fh, data, next);
+ pq->tx_nfree++;
+}
+
+static void
+run_bulk_tx_callbackN(struct usb_xfer *xfer, usb_error_t error, u_int index)
+{
+ struct run_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct run_tx_data *data;
+ struct ieee80211vap *vap = NULL;
+ struct usb_page_cache *pc;
+ struct run_endpoint_queue *pq = &sc->sc_epq[index];
+ struct mbuf *m;
+ usb_frlength_t size;
+ int actlen;
+ int sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_USB,
+ "transfer complete: %d bytes @ index %d\n", actlen, index);
+
+ data = usbd_xfer_get_priv(xfer);
+ run_tx_free(pq, data, 0);
+ usbd_xfer_set_priv(xfer, NULL);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ data = STAILQ_FIRST(&pq->tx_qh);
+ if (data == NULL)
+ break;
+
+ STAILQ_REMOVE_HEAD(&pq->tx_qh, next);
+
+ m = data->m;
+ size = (sc->mac_ver == 0x5592) ?
+ sizeof(data->desc) + sizeof(uint32_t) : sizeof(data->desc);
+ if ((m->m_pkthdr.len +
+ size + 3 + 8) > RUN_MAX_TXSZ) {
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT_DESC | RUN_DEBUG_USB,
+ "data overflow, %u bytes\n", m->m_pkthdr.len);
+ run_tx_free(pq, data, 1);
+ goto tr_setup;
+ }
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &data->desc, size);
+ usbd_m_copy_in(pc, size, m, 0, m->m_pkthdr.len);
+ size += m->m_pkthdr.len;
+ /*
+ * Align end on a 4-byte boundary, pad 8 bytes (CRC +
+ * 4-byte padding), and be sure to zero those trailing
+ * bytes:
+ */
+ usbd_frame_zero(pc, size, ((-size) & 3) + 8);
+ size += ((-size) & 3) + 8;
+
+ vap = data->ni->ni_vap;
+ if (ieee80211_radiotap_active_vap(vap)) {
+ const struct ieee80211_frame *wh;
+ struct run_tx_radiotap_header *tap = &sc->sc_txtap;
+ struct rt2860_txwi *txwi =
+ (struct rt2860_txwi *)(&data->desc + sizeof(struct rt2870_txd));
+ int has_l2pad;
+
+ wh = mtod(m, struct ieee80211_frame *);
+ has_l2pad = IEEE80211_HAS_ADDR4(wh) !=
+ IEEE80211_QOS_HAS_SEQ(wh);
+
+ tap->wt_flags = 0;
+ tap->wt_rate = rt2860_rates[data->ridx].rate;
+ tap->wt_hwqueue = index;
+ if (le16toh(txwi->phy) & RT2860_PHY_SHPRE)
+ tap->wt_flags |= IEEE80211_RADIOTAP_F_SHORTPRE;
+ if (has_l2pad)
+ tap->wt_flags |= IEEE80211_RADIOTAP_F_DATAPAD;
+
+ ieee80211_radiotap_tx(vap, m);
+ }
+
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_USB,
+ "sending frame len=%u/%u @ index %d\n",
+ m->m_pkthdr.len, size, index);
+
+ usbd_xfer_set_frame_len(xfer, 0, size);
+ usbd_xfer_set_priv(xfer, data);
+ usbd_transfer_submit(xfer);
+ run_start(sc);
+
+ break;
+
+ default:
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_USB,
+ "USB transfer error, %s\n", usbd_errstr(error));
+
+ data = usbd_xfer_get_priv(xfer);
+
+ if (data != NULL) {
+ if(data->ni != NULL)
+ vap = data->ni->ni_vap;
+ run_tx_free(pq, data, error);
+ usbd_xfer_set_priv(xfer, NULL);
+ }
+
+ if (vap == NULL)
+ vap = TAILQ_FIRST(&ic->ic_vaps);
+
+ if (error != USB_ERR_CANCELLED) {
+ if (error == USB_ERR_TIMEOUT) {
+ device_printf(sc->sc_dev, "device timeout\n");
+ uint32_t i = RUN_CMDQ_GET(&sc->cmdq_store);
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_USB,
+ "cmdq_store=%d\n", i);
+ sc->cmdq[i].func = run_usb_timeout_cb;
+ sc->cmdq[i].arg0 = vap;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+ }
+
+ /*
+ * Try to clear stall first, also if other
+ * errors occur, hence clearing stall
+ * introduces a 50 ms delay:
+ */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+#ifdef IEEE80211_SUPPORT_SUPERG
+ /* XXX TODO: make this deferred rather than unlock/relock */
+ /* XXX TODO: should only do the QoS AC this belongs to */
+ if (pq->tx_nfree >= RUN_TX_RING_COUNT) {
+ RUN_UNLOCK(sc);
+ ieee80211_ff_flush_all(ic);
+ RUN_LOCK(sc);
+ }
+#endif
+}
+
+static void
+run_bulk_tx_callback0(struct usb_xfer *xfer, usb_error_t error)
+{
+ run_bulk_tx_callbackN(xfer, error, 0);
+}
+
+static void
+run_bulk_tx_callback1(struct usb_xfer *xfer, usb_error_t error)
+{
+ run_bulk_tx_callbackN(xfer, error, 1);
+}
+
+static void
+run_bulk_tx_callback2(struct usb_xfer *xfer, usb_error_t error)
+{
+ run_bulk_tx_callbackN(xfer, error, 2);
+}
+
+static void
+run_bulk_tx_callback3(struct usb_xfer *xfer, usb_error_t error)
+{
+ run_bulk_tx_callbackN(xfer, error, 3);
+}
+
+static void
+run_bulk_tx_callback4(struct usb_xfer *xfer, usb_error_t error)
+{
+ run_bulk_tx_callbackN(xfer, error, 4);
+}
+
+static void
+run_bulk_tx_callback5(struct usb_xfer *xfer, usb_error_t error)
+{
+ run_bulk_tx_callbackN(xfer, error, 5);
+}
+
+static void
+run_set_tx_desc(struct run_softc *sc, struct run_tx_data *data)
+{
+ struct mbuf *m = data->m;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = data->ni->ni_vap;
+ struct ieee80211_frame *wh;
+ struct rt2870_txd *txd;
+ struct rt2860_txwi *txwi;
+ uint16_t xferlen, txwisize;
+ uint16_t mcs;
+ uint8_t ridx = data->ridx;
+ uint8_t pad;
+
+ /* get MCS code from rate index */
+ mcs = rt2860_rates[ridx].mcs;
+
+ txwisize = (sc->mac_ver == 0x5592) ?
+ sizeof(*txwi) + sizeof(uint32_t) : sizeof(*txwi);
+ xferlen = txwisize + m->m_pkthdr.len;
+
+ /* roundup to 32-bit alignment */
+ xferlen = (xferlen + 3) & ~3;
+
+ txd = (struct rt2870_txd *)&data->desc;
+ txd->len = htole16(xferlen);
+
+ wh = mtod(m, struct ieee80211_frame *);
+
+ /*
+ * Ether both are true or both are false, the header
+ * are nicely aligned to 32-bit. So, no L2 padding.
+ */
+ if(IEEE80211_HAS_ADDR4(wh) == IEEE80211_QOS_HAS_SEQ(wh))
+ pad = 0;
+ else
+ pad = 2;
+
+ /* setup TX Wireless Information */
+ txwi = (struct rt2860_txwi *)(txd + 1);
+ txwi->len = htole16(m->m_pkthdr.len - pad);
+ if (rt2860_rates[ridx].phy == IEEE80211_T_DS) {
+ mcs |= RT2860_PHY_CCK;
+ if (ridx != RT2860_RIDX_CCK1 &&
+ (ic->ic_flags & IEEE80211_F_SHPREAMBLE))
+ mcs |= RT2860_PHY_SHPRE;
+ } else if (rt2860_rates[ridx].phy == IEEE80211_T_OFDM) {
+ mcs |= RT2860_PHY_OFDM;
+ } else if (rt2860_rates[ridx].phy == IEEE80211_T_HT) {
+ /* XXX TODO: [adrian] set short preamble for MCS? */
+ mcs |= RT2860_PHY_HT_MIX; /* Mixed, not greenfield */
+ }
+ txwi->phy = htole16(mcs);
+
+ /* check if RTS/CTS or CTS-to-self protection is required */
+ if (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
+ ((m->m_pkthdr.len + IEEE80211_CRC_LEN > vap->iv_rtsthreshold) ||
+ ((ic->ic_flags & IEEE80211_F_USEPROT) &&
+ rt2860_rates[ridx].phy == IEEE80211_T_OFDM) ||
+ ((ic->ic_htprotmode == IEEE80211_PROT_RTSCTS) &&
+ rt2860_rates[ridx].phy == IEEE80211_T_HT)))
+ txwi->txop |= RT2860_TX_TXOP_HT;
+ else
+ txwi->txop |= RT2860_TX_TXOP_BACKOFF;
+
+ if (vap->iv_opmode != IEEE80211_M_STA && !IEEE80211_QOS_HAS_SEQ(wh))
+ txwi->xflags |= RT2860_TX_NSEQ;
+}
+
+/* This function must be called locked */
+static int
+run_tx(struct run_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct ieee80211_frame *wh;
+ const struct ieee80211_txparam *tp = ni->ni_txparms;
+ struct run_node *rn = RUN_NODE(ni);
+ struct run_tx_data *data;
+ struct rt2870_txd *txd;
+ struct rt2860_txwi *txwi;
+ uint16_t qos;
+ uint16_t dur;
+ uint16_t qid;
+ uint8_t type;
+ uint8_t tid;
+ uint8_t ridx;
+ uint8_t ctl_ridx;
+ uint8_t qflags;
+ uint8_t xflags = 0;
+ int hasqos;
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+
+ wh = mtod(m, struct ieee80211_frame *);
+
+ type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
+
+ /*
+ * There are 7 bulk endpoints: 1 for RX
+ * and 6 for TX (4 EDCAs + HCCA + Prio).
+ * Update 03-14-2009: some devices like the Planex GW-US300MiniS
+ * seem to have only 4 TX bulk endpoints (Fukaumi Naoki).
+ */
+ if ((hasqos = IEEE80211_QOS_HAS_SEQ(wh))) {
+ uint8_t *frm;
+
+ frm = ieee80211_getqos(wh);
+ qos = le16toh(*(const uint16_t *)frm);
+ tid = qos & IEEE80211_QOS_TID;
+ qid = TID_TO_WME_AC(tid);
+ } else {
+ qos = 0;
+ tid = 0;
+ qid = WME_AC_BE;
+ }
+ qflags = (qid < 4) ? RT2860_TX_QSEL_EDCA : RT2860_TX_QSEL_HCCA;
+
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "qos %d\tqid %d\ttid %d\tqflags %x\n",
+ qos, qid, tid, qflags);
+
+ /* pickup a rate index */
+ if (IEEE80211_IS_MULTICAST(wh->i_addr1) ||
+ type != IEEE80211_FC0_TYPE_DATA || m->m_flags & M_EAPOL) {
+ /* XXX TODO: methodize for 11n; use MCS0 for 11NA/11NG */
+ ridx = (ic->ic_curmode == IEEE80211_MODE_11A || ic->ic_curmode == IEEE80211_MODE_11NA) ?
+ RT2860_RIDX_OFDM6 : RT2860_RIDX_CCK1;
+ ctl_ridx = rt2860_rates[ridx].ctl_ridx;
+ } else {
+ if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE)
+ ridx = rn->fix_ridx;
+ else
+ ridx = rn->amrr_ridx;
+ ctl_ridx = rt2860_rates[ridx].ctl_ridx;
+ }
+
+ if (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
+ (!hasqos || (qos & IEEE80211_QOS_ACKPOLICY) !=
+ IEEE80211_QOS_ACKPOLICY_NOACK)) {
+ xflags |= RT2860_TX_ACK;
+ if (ic->ic_flags & IEEE80211_F_SHPREAMBLE)
+ dur = rt2860_rates[ctl_ridx].sp_ack_dur;
+ else
+ dur = rt2860_rates[ctl_ridx].lp_ack_dur;
+ USETW(wh->i_dur, dur);
+ }
+
+ /* reserve slots for mgmt packets, just in case */
+ if (sc->sc_epq[qid].tx_nfree < 3) {
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "tx ring %d is full\n", qid);
+ return (-1);
+ }
+
+ data = STAILQ_FIRST(&sc->sc_epq[qid].tx_fh);
+ STAILQ_REMOVE_HEAD(&sc->sc_epq[qid].tx_fh, next);
+ sc->sc_epq[qid].tx_nfree--;
+
+ txd = (struct rt2870_txd *)&data->desc;
+ txd->flags = qflags;
+ txwi = (struct rt2860_txwi *)(txd + 1);
+ txwi->xflags = xflags;
+ if (IEEE80211_IS_MULTICAST(wh->i_addr1))
+ txwi->wcid = 0;
+ else
+ txwi->wcid = (vap->iv_opmode == IEEE80211_M_STA) ?
+ 1 : RUN_AID2WCID(ni->ni_associd);
+
+ /* clear leftover garbage bits */
+ txwi->flags = 0;
+ txwi->txop = 0;
+
+ data->m = m;
+ data->ni = ni;
+ data->ridx = ridx;
+
+ run_set_tx_desc(sc, data);
+
+ /*
+ * The chip keeps track of 2 kind of Tx stats,
+ * * TX_STAT_FIFO, for per WCID stats, and
+ * * TX_STA_CNT0 for all-TX-in-one stats.
+ *
+ * To use FIFO stats, we need to store MCS into the driver-private
+ * PacketID field. So that, we can tell whose stats when we read them.
+ * We add 1 to the MCS because setting the PacketID field to 0 means
+ * that we don't want feedback in TX_STAT_FIFO.
+ * And, that's what we want for STA mode, since TX_STA_CNT0 does the job.
+ *
+ * FIFO stats doesn't count Tx with WCID 0xff, so we do this in run_tx().
+ */
+ if (sc->rvp_cnt > 1 || vap->iv_opmode == IEEE80211_M_HOSTAP ||
+ vap->iv_opmode == IEEE80211_M_MBSS) {
+ uint16_t pid = (rt2860_rates[ridx].mcs + 1) & 0xf;
+ txwi->len |= htole16(pid << RT2860_TX_PID_SHIFT);
+
+ /*
+ * Unlike PCI based devices, we don't get any interrupt from
+ * USB devices, so we simulate FIFO-is-full interrupt here.
+ * Ralink recommends to drain FIFO stats every 100 ms, but 16 slots
+ * quickly get fulled. To prevent overflow, increment a counter on
+ * every FIFO stat request, so we know how many slots are left.
+ * We do this only in HOSTAP or multiple vap mode since FIFO stats
+ * are used only in those modes.
+ * We just drain stats. AMRR gets updated every 1 sec by
+ * run_ratectl_cb() via callout.
+ * Call it early. Otherwise overflow.
+ */
+ if (sc->fifo_cnt++ == 10) {
+ /*
+ * With multiple vaps or if_bridge, if_start() is called
+ * with a non-sleepable lock, tcpinp. So, need to defer.
+ */
+ uint32_t i = RUN_CMDQ_GET(&sc->cmdq_store);
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "cmdq_store=%d\n", i);
+ sc->cmdq[i].func = run_drain_fifo;
+ sc->cmdq[i].arg0 = sc;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+ }
+ }
+
+ STAILQ_INSERT_TAIL(&sc->sc_epq[qid].tx_qh, data, next);
+
+ usbd_transfer_start(sc->sc_xfer[qid]);
+
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT,
+ "sending data frame len=%d rate=%d qid=%d\n",
+ m->m_pkthdr.len + (int)(sizeof(struct rt2870_txd) +
+ sizeof(struct rt2860_txwi)), rt2860_rates[ridx].rate, qid);
+
+ return (0);
+}
+
+static int
+run_tx_mgt(struct run_softc *sc, struct mbuf *m, struct ieee80211_node *ni)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct run_node *rn = RUN_NODE(ni);
+ struct run_tx_data *data;
+ struct ieee80211_frame *wh;
+ struct rt2870_txd *txd;
+ struct rt2860_txwi *txwi;
+ uint16_t dur;
+ uint8_t ridx = rn->mgt_ridx;
+ uint8_t xflags = 0;
+ uint8_t wflags = 0;
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+
+ wh = mtod(m, struct ieee80211_frame *);
+
+ /* tell hardware to add timestamp for probe responses */
+ if (IEEE80211_IS_MGMT_PROBE_RESP(wh))
+ wflags |= RT2860_TX_TS;
+ else if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
+ xflags |= RT2860_TX_ACK;
+
+ dur = ieee80211_ack_duration(ic->ic_rt, rt2860_rates[ridx].rate,
+ ic->ic_flags & IEEE80211_F_SHPREAMBLE);
+ USETW(wh->i_dur, dur);
+ }
+
+ if (sc->sc_epq[0].tx_nfree == 0)
+ /* let caller free mbuf */
+ return (EIO);
+ data = STAILQ_FIRST(&sc->sc_epq[0].tx_fh);
+ STAILQ_REMOVE_HEAD(&sc->sc_epq[0].tx_fh, next);
+ sc->sc_epq[0].tx_nfree--;
+
+ txd = (struct rt2870_txd *)&data->desc;
+ txd->flags = RT2860_TX_QSEL_EDCA;
+ txwi = (struct rt2860_txwi *)(txd + 1);
+ txwi->wcid = 0xff;
+ txwi->flags = wflags;
+ txwi->xflags = xflags;
+ txwi->txop = 0; /* clear leftover garbage bits */
+
+ data->m = m;
+ data->ni = ni;
+ data->ridx = ridx;
+
+ run_set_tx_desc(sc, data);
+
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "sending mgt frame len=%d rate=%d\n",
+ m->m_pkthdr.len + (int)(sizeof(struct rt2870_txd) +
+ sizeof(struct rt2860_txwi)), rt2860_rates[ridx].rate);
+
+ STAILQ_INSERT_TAIL(&sc->sc_epq[0].tx_qh, data, next);
+
+ usbd_transfer_start(sc->sc_xfer[0]);
+
+ return (0);
+}
+
+static int
+run_sendprot(struct run_softc *sc,
+ const struct mbuf *m, struct ieee80211_node *ni, int prot, int rate)
+{
+ struct ieee80211com *ic = ni->ni_ic;
+ struct run_tx_data *data;
+ struct rt2870_txd *txd;
+ struct rt2860_txwi *txwi;
+ struct mbuf *mprot;
+ int ridx;
+ int protrate;
+ uint8_t wflags = 0;
+ uint8_t xflags = 0;
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+
+ /* check that there are free slots before allocating the mbuf */
+ if (sc->sc_epq[0].tx_nfree == 0)
+ /* let caller free mbuf */
+ return (ENOBUFS);
+
+ mprot = ieee80211_alloc_prot(ni, m, rate, prot);
+ if (mprot == NULL) {
+ if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1);
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "could not allocate mbuf\n");
+ return (ENOBUFS);
+ }
+
+ protrate = ieee80211_ctl_rate(ic->ic_rt, rate);
+ wflags = RT2860_TX_FRAG;
+ xflags = 0;
+ if (prot == IEEE80211_PROT_RTSCTS)
+ xflags |= RT2860_TX_ACK;
+
+ data = STAILQ_FIRST(&sc->sc_epq[0].tx_fh);
+ STAILQ_REMOVE_HEAD(&sc->sc_epq[0].tx_fh, next);
+ sc->sc_epq[0].tx_nfree--;
+
+ txd = (struct rt2870_txd *)&data->desc;
+ txd->flags = RT2860_TX_QSEL_EDCA;
+ txwi = (struct rt2860_txwi *)(txd + 1);
+ txwi->wcid = 0xff;
+ txwi->flags = wflags;
+ txwi->xflags = xflags;
+ txwi->txop = 0; /* clear leftover garbage bits */
+
+ data->m = mprot;
+ data->ni = ieee80211_ref_node(ni);
+
+ /* XXX TODO: methodize with MCS rates */
+ for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++)
+ if (rt2860_rates[ridx].rate == protrate)
+ break;
+ data->ridx = ridx;
+
+ run_set_tx_desc(sc, data);
+
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "sending prot len=%u rate=%u\n",
+ m->m_pkthdr.len, rate);
+
+ STAILQ_INSERT_TAIL(&sc->sc_epq[0].tx_qh, data, next);
+
+ usbd_transfer_start(sc->sc_xfer[0]);
+
+ return (0);
+}
+
+static int
+run_tx_param(struct run_softc *sc, struct mbuf *m, struct ieee80211_node *ni,
+ const struct ieee80211_bpf_params *params)
+{
+ struct ieee80211com *ic = ni->ni_ic;
+ struct run_tx_data *data;
+ struct rt2870_txd *txd;
+ struct rt2860_txwi *txwi;
+ uint8_t ridx;
+ uint8_t rate;
+ uint8_t opflags = 0;
+ uint8_t xflags = 0;
+ int error;
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+
+ KASSERT(params != NULL, ("no raw xmit params"));
+
+ rate = params->ibp_rate0;
+ if (!ieee80211_isratevalid(ic->ic_rt, rate)) {
+ /* let caller free mbuf */
+ return (EINVAL);
+ }
+
+ if ((params->ibp_flags & IEEE80211_BPF_NOACK) == 0)
+ xflags |= RT2860_TX_ACK;
+ if (params->ibp_flags & (IEEE80211_BPF_RTS|IEEE80211_BPF_CTS)) {
+ error = run_sendprot(sc, m, ni,
+ params->ibp_flags & IEEE80211_BPF_RTS ?
+ IEEE80211_PROT_RTSCTS : IEEE80211_PROT_CTSONLY,
+ rate);
+ if (error) {
+ /* let caller free mbuf */
+ return error;
+ }
+ opflags |= /*XXX RT2573_TX_LONG_RETRY |*/ RT2860_TX_TXOP_SIFS;
+ }
+
+ if (sc->sc_epq[0].tx_nfree == 0) {
+ /* let caller free mbuf */
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT,
+ "sending raw frame, but tx ring is full\n");
+ return (EIO);
+ }
+ data = STAILQ_FIRST(&sc->sc_epq[0].tx_fh);
+ STAILQ_REMOVE_HEAD(&sc->sc_epq[0].tx_fh, next);
+ sc->sc_epq[0].tx_nfree--;
+
+ txd = (struct rt2870_txd *)&data->desc;
+ txd->flags = RT2860_TX_QSEL_EDCA;
+ txwi = (struct rt2860_txwi *)(txd + 1);
+ txwi->wcid = 0xff;
+ txwi->xflags = xflags;
+ txwi->txop = opflags;
+ txwi->flags = 0; /* clear leftover garbage bits */
+
+ data->m = m;
+ data->ni = ni;
+ /* XXX TODO: methodize with MCS rates */
+ for (ridx = 0; ridx < RT2860_RIDX_MAX; ridx++)
+ if (rt2860_rates[ridx].rate == rate)
+ break;
+ data->ridx = ridx;
+
+ run_set_tx_desc(sc, data);
+
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "sending raw frame len=%u rate=%u\n",
+ m->m_pkthdr.len, rate);
+
+ STAILQ_INSERT_TAIL(&sc->sc_epq[0].tx_qh, data, next);
+
+ usbd_transfer_start(sc->sc_xfer[0]);
+
+ return (0);
+}
+
+static int
+run_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
+ const struct ieee80211_bpf_params *params)
+{
+ struct run_softc *sc = ni->ni_ic->ic_softc;
+ int error = 0;
+
+ RUN_LOCK(sc);
+
+ /* prevent management frames from being sent if we're not ready */
+ if (!(sc->sc_flags & RUN_RUNNING)) {
+ error = ENETDOWN;
+ goto done;
+ }
+
+ if (params == NULL) {
+ /* tx mgt packet */
+ if ((error = run_tx_mgt(sc, m, ni)) != 0) {
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "mgt tx failed\n");
+ goto done;
+ }
+ } else {
+ /* tx raw packet with param */
+ if ((error = run_tx_param(sc, m, ni, params)) != 0) {
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT, "tx with param failed\n");
+ goto done;
+ }
+ }
+
+done:
+ RUN_UNLOCK(sc);
+
+ if (error != 0) {
+ if(m != NULL)
+ m_freem(m);
+ }
+
+ return (error);
+}
+
+static int
+run_transmit(struct ieee80211com *ic, struct mbuf *m)
+{
+ struct run_softc *sc = ic->ic_softc;
+ int error;
+
+ RUN_LOCK(sc);
+ if ((sc->sc_flags & RUN_RUNNING) == 0) {
+ RUN_UNLOCK(sc);
+ return (ENXIO);
+ }
+ error = mbufq_enqueue(&sc->sc_snd, m);
+ if (error) {
+ RUN_UNLOCK(sc);
+ return (error);
+ }
+ run_start(sc);
+ RUN_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+run_start(struct run_softc *sc)
+{
+ struct ieee80211_node *ni;
+ struct mbuf *m;
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+
+ if ((sc->sc_flags & RUN_RUNNING) == 0)
+ return;
+
+ while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
+ ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
+ if (run_tx(sc, m, ni) != 0) {
+ mbufq_prepend(&sc->sc_snd, m);
+ break;
+ }
+ }
+}
+
+static void
+run_parent(struct ieee80211com *ic)
+{
+ struct run_softc *sc = ic->ic_softc;
+ int startall = 0;
+
+ RUN_LOCK(sc);
+ if (sc->sc_detached) {
+ RUN_UNLOCK(sc);
+ return;
+ }
+
+ if (ic->ic_nrunning > 0) {
+ if (!(sc->sc_flags & RUN_RUNNING)) {
+ startall = 1;
+ run_init_locked(sc);
+ } else
+ run_update_promisc_locked(sc);
+ } else if ((sc->sc_flags & RUN_RUNNING) && sc->rvp_cnt <= 1)
+ run_stop(sc);
+ RUN_UNLOCK(sc);
+ if (startall)
+ ieee80211_start_all(ic);
+}
+
+static void
+run_iq_calib(struct run_softc *sc, u_int chan)
+{
+ uint16_t val;
+
+ /* Tx0 IQ gain. */
+ run_bbp_write(sc, 158, 0x2c);
+ if (chan <= 14)
+ run_efuse_read(sc, RT5390_EEPROM_IQ_GAIN_CAL_TX0_2GHZ, &val, 1);
+ else if (chan <= 64) {
+ run_efuse_read(sc,
+ RT5390_EEPROM_IQ_GAIN_CAL_TX0_CH36_TO_CH64_5GHZ,
+ &val, 1);
+ } else if (chan <= 138) {
+ run_efuse_read(sc,
+ RT5390_EEPROM_IQ_GAIN_CAL_TX0_CH100_TO_CH138_5GHZ,
+ &val, 1);
+ } else if (chan <= 165) {
+ run_efuse_read(sc,
+ RT5390_EEPROM_IQ_GAIN_CAL_TX0_CH140_TO_CH165_5GHZ,
+ &val, 1);
+ } else
+ val = 0;
+ run_bbp_write(sc, 159, val);
+
+ /* Tx0 IQ phase. */
+ run_bbp_write(sc, 158, 0x2d);
+ if (chan <= 14) {
+ run_efuse_read(sc, RT5390_EEPROM_IQ_PHASE_CAL_TX0_2GHZ,
+ &val, 1);
+ } else if (chan <= 64) {
+ run_efuse_read(sc,
+ RT5390_EEPROM_IQ_PHASE_CAL_TX0_CH36_TO_CH64_5GHZ,
+ &val, 1);
+ } else if (chan <= 138) {
+ run_efuse_read(sc,
+ RT5390_EEPROM_IQ_PHASE_CAL_TX0_CH100_TO_CH138_5GHZ,
+ &val, 1);
+ } else if (chan <= 165) {
+ run_efuse_read(sc,
+ RT5390_EEPROM_IQ_PHASE_CAL_TX0_CH140_TO_CH165_5GHZ,
+ &val, 1);
+ } else
+ val = 0;
+ run_bbp_write(sc, 159, val);
+
+ /* Tx1 IQ gain. */
+ run_bbp_write(sc, 158, 0x4a);
+ if (chan <= 14) {
+ run_efuse_read(sc, RT5390_EEPROM_IQ_GAIN_CAL_TX1_2GHZ,
+ &val, 1);
+ } else if (chan <= 64) {
+ run_efuse_read(sc,
+ RT5390_EEPROM_IQ_GAIN_CAL_TX1_CH36_TO_CH64_5GHZ,
+ &val, 1);
+ } else if (chan <= 138) {
+ run_efuse_read(sc,
+ RT5390_EEPROM_IQ_GAIN_CAL_TX1_CH100_TO_CH138_5GHZ,
+ &val, 1);
+ } else if (chan <= 165) {
+ run_efuse_read(sc,
+ RT5390_EEPROM_IQ_GAIN_CAL_TX1_CH140_TO_CH165_5GHZ,
+ &val, 1);
+ } else
+ val = 0;
+ run_bbp_write(sc, 159, val);
+
+ /* Tx1 IQ phase. */
+ run_bbp_write(sc, 158, 0x4b);
+ if (chan <= 14) {
+ run_efuse_read(sc, RT5390_EEPROM_IQ_PHASE_CAL_TX1_2GHZ,
+ &val, 1);
+ } else if (chan <= 64) {
+ run_efuse_read(sc,
+ RT5390_EEPROM_IQ_PHASE_CAL_TX1_CH36_TO_CH64_5GHZ,
+ &val, 1);
+ } else if (chan <= 138) {
+ run_efuse_read(sc,
+ RT5390_EEPROM_IQ_PHASE_CAL_TX1_CH100_TO_CH138_5GHZ,
+ &val, 1);
+ } else if (chan <= 165) {
+ run_efuse_read(sc,
+ RT5390_EEPROM_IQ_PHASE_CAL_TX1_CH140_TO_CH165_5GHZ,
+ &val, 1);
+ } else
+ val = 0;
+ run_bbp_write(sc, 159, val);
+
+ /* RF IQ compensation control. */
+ run_bbp_write(sc, 158, 0x04);
+ run_efuse_read(sc, RT5390_EEPROM_RF_IQ_COMPENSATION_CTL,
+ &val, 1);
+ run_bbp_write(sc, 159, val);
+
+ /* RF IQ imbalance compensation control. */
+ run_bbp_write(sc, 158, 0x03);
+ run_efuse_read(sc,
+ RT5390_EEPROM_RF_IQ_IMBALANCE_COMPENSATION_CTL, &val, 1);
+ run_bbp_write(sc, 159, val);
+}
+
+static void
+run_set_agc(struct run_softc *sc, uint8_t agc)
+{
+ uint8_t bbp;
+
+ if (sc->mac_ver == 0x3572) {
+ run_bbp_read(sc, 27, &bbp);
+ bbp &= ~(0x3 << 5);
+ run_bbp_write(sc, 27, bbp | 0 << 5); /* select Rx0 */
+ run_bbp_write(sc, 66, agc);
+ run_bbp_write(sc, 27, bbp | 1 << 5); /* select Rx1 */
+ run_bbp_write(sc, 66, agc);
+ } else
+ run_bbp_write(sc, 66, agc);
+}
+
+static void
+run_select_chan_group(struct run_softc *sc, int group)
+{
+ uint32_t tmp;
+ uint8_t agc;
+
+ run_bbp_write(sc, 62, 0x37 - sc->lna[group]);
+ run_bbp_write(sc, 63, 0x37 - sc->lna[group]);
+ run_bbp_write(sc, 64, 0x37 - sc->lna[group]);
+ if (sc->mac_ver < 0x3572)
+ run_bbp_write(sc, 86, 0x00);
+
+ if (sc->mac_ver == 0x3593) {
+ run_bbp_write(sc, 77, 0x98);
+ run_bbp_write(sc, 83, (group == 0) ? 0x8a : 0x9a);
+ }
+
+ if (group == 0) {
+ if (sc->ext_2ghz_lna) {
+ if (sc->mac_ver >= 0x5390)
+ run_bbp_write(sc, 75, 0x52);
+ else {
+ run_bbp_write(sc, 82, 0x62);
+ run_bbp_write(sc, 75, 0x46);
+ }
+ } else {
+ if (sc->mac_ver == 0x5592) {
+ run_bbp_write(sc, 79, 0x1c);
+ run_bbp_write(sc, 80, 0x0e);
+ run_bbp_write(sc, 81, 0x3a);
+ run_bbp_write(sc, 82, 0x62);
+
+ run_bbp_write(sc, 195, 0x80);
+ run_bbp_write(sc, 196, 0xe0);
+ run_bbp_write(sc, 195, 0x81);
+ run_bbp_write(sc, 196, 0x1f);
+ run_bbp_write(sc, 195, 0x82);
+ run_bbp_write(sc, 196, 0x38);
+ run_bbp_write(sc, 195, 0x83);
+ run_bbp_write(sc, 196, 0x32);
+ run_bbp_write(sc, 195, 0x85);
+ run_bbp_write(sc, 196, 0x28);
+ run_bbp_write(sc, 195, 0x86);
+ run_bbp_write(sc, 196, 0x19);
+ } else if (sc->mac_ver >= 0x5390)
+ run_bbp_write(sc, 75, 0x50);
+ else {
+ run_bbp_write(sc, 82,
+ (sc->mac_ver == 0x3593) ? 0x62 : 0x84);
+ run_bbp_write(sc, 75, 0x50);
+ }
+ }
+ } else {
+ if (sc->mac_ver == 0x5592) {
+ run_bbp_write(sc, 79, 0x18);
+ run_bbp_write(sc, 80, 0x08);
+ run_bbp_write(sc, 81, 0x38);
+ run_bbp_write(sc, 82, 0x92);
+
+ run_bbp_write(sc, 195, 0x80);
+ run_bbp_write(sc, 196, 0xf0);
+ run_bbp_write(sc, 195, 0x81);
+ run_bbp_write(sc, 196, 0x1e);
+ run_bbp_write(sc, 195, 0x82);
+ run_bbp_write(sc, 196, 0x28);
+ run_bbp_write(sc, 195, 0x83);
+ run_bbp_write(sc, 196, 0x20);
+ run_bbp_write(sc, 195, 0x85);
+ run_bbp_write(sc, 196, 0x7f);
+ run_bbp_write(sc, 195, 0x86);
+ run_bbp_write(sc, 196, 0x7f);
+ } else if (sc->mac_ver == 0x3572)
+ run_bbp_write(sc, 82, 0x94);
+ else
+ run_bbp_write(sc, 82,
+ (sc->mac_ver == 0x3593) ? 0x82 : 0xf2);
+ if (sc->ext_5ghz_lna)
+ run_bbp_write(sc, 75, 0x46);
+ else
+ run_bbp_write(sc, 75, 0x50);
+ }
+
+ run_read(sc, RT2860_TX_BAND_CFG, &tmp);
+ tmp &= ~(RT2860_5G_BAND_SEL_N | RT2860_5G_BAND_SEL_P);
+ tmp |= (group == 0) ? RT2860_5G_BAND_SEL_N : RT2860_5G_BAND_SEL_P;
+ run_write(sc, RT2860_TX_BAND_CFG, tmp);
+
+ /* enable appropriate Power Amplifiers and Low Noise Amplifiers */
+ tmp = RT2860_RFTR_EN | RT2860_TRSW_EN | RT2860_LNA_PE0_EN;
+ if (sc->mac_ver == 0x3593)
+ tmp |= 1 << 29 | 1 << 28;
+ if (sc->nrxchains > 1)
+ tmp |= RT2860_LNA_PE1_EN;
+ if (group == 0) { /* 2GHz */
+ tmp |= RT2860_PA_PE_G0_EN;
+ if (sc->ntxchains > 1)
+ tmp |= RT2860_PA_PE_G1_EN;
+ if (sc->mac_ver == 0x3593) {
+ if (sc->ntxchains > 2)
+ tmp |= 1 << 25;
+ }
+ } else { /* 5GHz */
+ tmp |= RT2860_PA_PE_A0_EN;
+ if (sc->ntxchains > 1)
+ tmp |= RT2860_PA_PE_A1_EN;
+ }
+ if (sc->mac_ver == 0x3572) {
+ run_rt3070_rf_write(sc, 8, 0x00);
+ run_write(sc, RT2860_TX_PIN_CFG, tmp);
+ run_rt3070_rf_write(sc, 8, 0x80);
+ } else
+ run_write(sc, RT2860_TX_PIN_CFG, tmp);
+
+ if (sc->mac_ver == 0x5592) {
+ run_bbp_write(sc, 195, 0x8d);
+ run_bbp_write(sc, 196, 0x1a);
+ }
+
+ if (sc->mac_ver == 0x3593) {
+ run_read(sc, RT2860_GPIO_CTRL, &tmp);
+ tmp &= ~0x01010000;
+ if (group == 0)
+ tmp |= 0x00010000;
+ tmp = (tmp & ~0x00009090) | 0x00000090;
+ run_write(sc, RT2860_GPIO_CTRL, tmp);
+ }
+
+ /* set initial AGC value */
+ if (group == 0) { /* 2GHz band */
+ if (sc->mac_ver >= 0x3070)
+ agc = 0x1c + sc->lna[0] * 2;
+ else
+ agc = 0x2e + sc->lna[0];
+ } else { /* 5GHz band */
+ if (sc->mac_ver == 0x5592)
+ agc = 0x24 + sc->lna[group] * 2;
+ else if (sc->mac_ver == 0x3572 || sc->mac_ver == 0x3593)
+ agc = 0x22 + (sc->lna[group] * 5) / 3;
+ else
+ agc = 0x32 + (sc->lna[group] * 5) / 3;
+ }
+ run_set_agc(sc, agc);
+}
+
+static void
+run_rt2870_set_chan(struct run_softc *sc, u_int chan)
+{
+ const struct rfprog *rfprog = rt2860_rf2850;
+ uint32_t r2, r3, r4;
+ int8_t txpow1, txpow2;
+ int i;
+
+ /* find the settings for this channel (we know it exists) */
+ for (i = 0; rfprog[i].chan != chan; i++);
+
+ r2 = rfprog[i].r2;
+ if (sc->ntxchains == 1)
+ r2 |= 1 << 14; /* 1T: disable Tx chain 2 */
+ if (sc->nrxchains == 1)
+ r2 |= 1 << 17 | 1 << 6; /* 1R: disable Rx chains 2 & 3 */
+ else if (sc->nrxchains == 2)
+ r2 |= 1 << 6; /* 2R: disable Rx chain 3 */
+
+ /* use Tx power values from EEPROM */
+ txpow1 = sc->txpow1[i];
+ txpow2 = sc->txpow2[i];
+
+ /* Initialize RF R3 and R4. */
+ r3 = rfprog[i].r3 & 0xffffc1ff;
+ r4 = (rfprog[i].r4 & ~(0x001f87c0)) | (sc->freq << 15);
+ if (chan > 14) {
+ if (txpow1 >= 0) {
+ txpow1 = (txpow1 > 0xf) ? (0xf) : (txpow1);
+ r3 |= (txpow1 << 10) | (1 << 9);
+ } else {
+ txpow1 += 7;
+
+ /* txpow1 is not possible larger than 15. */
+ r3 |= (txpow1 << 10);
+ }
+ if (txpow2 >= 0) {
+ txpow2 = (txpow2 > 0xf) ? (0xf) : (txpow2);
+ r4 |= (txpow2 << 7) | (1 << 6);
+ } else {
+ txpow2 += 7;
+ r4 |= (txpow2 << 7);
+ }
+ } else {
+ /* Set Tx0 power. */
+ r3 |= (txpow1 << 9);
+
+ /* Set frequency offset and Tx1 power. */
+ r4 |= (txpow2 << 6);
+ }
+
+ run_rt2870_rf_write(sc, rfprog[i].r1);
+ run_rt2870_rf_write(sc, r2);
+ run_rt2870_rf_write(sc, r3 & ~(1 << 2));
+ run_rt2870_rf_write(sc, r4);
+
+ run_delay(sc, 10);
+
+ run_rt2870_rf_write(sc, rfprog[i].r1);
+ run_rt2870_rf_write(sc, r2);
+ run_rt2870_rf_write(sc, r3 | (1 << 2));
+ run_rt2870_rf_write(sc, r4);
+
+ run_delay(sc, 10);
+
+ run_rt2870_rf_write(sc, rfprog[i].r1);
+ run_rt2870_rf_write(sc, r2);
+ run_rt2870_rf_write(sc, r3 & ~(1 << 2));
+ run_rt2870_rf_write(sc, r4);
+}
+
+static void
+run_rt3070_set_chan(struct run_softc *sc, u_int chan)
+{
+ int8_t txpow1, txpow2;
+ uint8_t rf;
+ int i;
+
+ /* find the settings for this channel (we know it exists) */
+ for (i = 0; rt2860_rf2850[i].chan != chan; i++);
+
+ /* use Tx power values from EEPROM */
+ txpow1 = sc->txpow1[i];
+ txpow2 = sc->txpow2[i];
+
+ run_rt3070_rf_write(sc, 2, rt3070_freqs[i].n);
+
+ /* RT3370/RT3390: RF R3 [7:4] is not reserved bits. */
+ run_rt3070_rf_read(sc, 3, &rf);
+ rf = (rf & ~0x0f) | rt3070_freqs[i].k;
+ run_rt3070_rf_write(sc, 3, rf);
+
+ run_rt3070_rf_read(sc, 6, &rf);
+ rf = (rf & ~0x03) | rt3070_freqs[i].r;
+ run_rt3070_rf_write(sc, 6, rf);
+
+ /* set Tx0 power */
+ run_rt3070_rf_read(sc, 12, &rf);
+ rf = (rf & ~0x1f) | txpow1;
+ run_rt3070_rf_write(sc, 12, rf);
+
+ /* set Tx1 power */
+ run_rt3070_rf_read(sc, 13, &rf);
+ rf = (rf & ~0x1f) | txpow2;
+ run_rt3070_rf_write(sc, 13, rf);
+
+ run_rt3070_rf_read(sc, 1, &rf);
+ rf &= ~0xfc;
+ if (sc->ntxchains == 1)
+ rf |= 1 << 7 | 1 << 5; /* 1T: disable Tx chains 2 & 3 */
+ else if (sc->ntxchains == 2)
+ rf |= 1 << 7; /* 2T: disable Tx chain 3 */
+ if (sc->nrxchains == 1)
+ rf |= 1 << 6 | 1 << 4; /* 1R: disable Rx chains 2 & 3 */
+ else if (sc->nrxchains == 2)
+ rf |= 1 << 6; /* 2R: disable Rx chain 3 */
+ run_rt3070_rf_write(sc, 1, rf);
+
+ /* set RF offset */
+ run_rt3070_rf_read(sc, 23, &rf);
+ rf = (rf & ~0x7f) | sc->freq;
+ run_rt3070_rf_write(sc, 23, rf);
+
+ /* program RF filter */
+ run_rt3070_rf_read(sc, 24, &rf); /* Tx */
+ rf = (rf & ~0x3f) | sc->rf24_20mhz;
+ run_rt3070_rf_write(sc, 24, rf);
+ run_rt3070_rf_read(sc, 31, &rf); /* Rx */
+ rf = (rf & ~0x3f) | sc->rf24_20mhz;
+ run_rt3070_rf_write(sc, 31, rf);
+
+ /* enable RF tuning */
+ run_rt3070_rf_read(sc, 7, &rf);
+ run_rt3070_rf_write(sc, 7, rf | 0x01);
+}
+
+static void
+run_rt3572_set_chan(struct run_softc *sc, u_int chan)
+{
+ int8_t txpow1, txpow2;
+ uint32_t tmp;
+ uint8_t rf;
+ int i;
+
+ /* find the settings for this channel (we know it exists) */
+ for (i = 0; rt2860_rf2850[i].chan != chan; i++);
+
+ /* use Tx power values from EEPROM */
+ txpow1 = sc->txpow1[i];
+ txpow2 = sc->txpow2[i];
+
+ if (chan <= 14) {
+ run_bbp_write(sc, 25, sc->bbp25);
+ run_bbp_write(sc, 26, sc->bbp26);
+ } else {
+ /* enable IQ phase correction */
+ run_bbp_write(sc, 25, 0x09);
+ run_bbp_write(sc, 26, 0xff);
+ }
+
+ run_rt3070_rf_write(sc, 2, rt3070_freqs[i].n);
+ run_rt3070_rf_write(sc, 3, rt3070_freqs[i].k);
+ run_rt3070_rf_read(sc, 6, &rf);
+ rf = (rf & ~0x0f) | rt3070_freqs[i].r;
+ rf |= (chan <= 14) ? 0x08 : 0x04;
+ run_rt3070_rf_write(sc, 6, rf);
+
+ /* set PLL mode */
+ run_rt3070_rf_read(sc, 5, &rf);
+ rf &= ~(0x08 | 0x04);
+ rf |= (chan <= 14) ? 0x04 : 0x08;
+ run_rt3070_rf_write(sc, 5, rf);
+
+ /* set Tx power for chain 0 */
+ if (chan <= 14)
+ rf = 0x60 | txpow1;
+ else
+ rf = 0xe0 | (txpow1 & 0xc) << 1 | (txpow1 & 0x3);
+ run_rt3070_rf_write(sc, 12, rf);
+
+ /* set Tx power for chain 1 */
+ if (chan <= 14)
+ rf = 0x60 | txpow2;
+ else
+ rf = 0xe0 | (txpow2 & 0xc) << 1 | (txpow2 & 0x3);
+ run_rt3070_rf_write(sc, 13, rf);
+
+ /* set Tx/Rx streams */
+ run_rt3070_rf_read(sc, 1, &rf);
+ rf &= ~0xfc;
+ if (sc->ntxchains == 1)
+ rf |= 1 << 7 | 1 << 5; /* 1T: disable Tx chains 2 & 3 */
+ else if (sc->ntxchains == 2)
+ rf |= 1 << 7; /* 2T: disable Tx chain 3 */
+ if (sc->nrxchains == 1)
+ rf |= 1 << 6 | 1 << 4; /* 1R: disable Rx chains 2 & 3 */
+ else if (sc->nrxchains == 2)
+ rf |= 1 << 6; /* 2R: disable Rx chain 3 */
+ run_rt3070_rf_write(sc, 1, rf);
+
+ /* set RF offset */
+ run_rt3070_rf_read(sc, 23, &rf);
+ rf = (rf & ~0x7f) | sc->freq;
+ run_rt3070_rf_write(sc, 23, rf);
+
+ /* program RF filter */
+ rf = sc->rf24_20mhz;
+ run_rt3070_rf_write(sc, 24, rf); /* Tx */
+ run_rt3070_rf_write(sc, 31, rf); /* Rx */
+
+ /* enable RF tuning */
+ run_rt3070_rf_read(sc, 7, &rf);
+ rf = (chan <= 14) ? 0xd8 : ((rf & ~0xc8) | 0x14);
+ run_rt3070_rf_write(sc, 7, rf);
+
+ /* TSSI */
+ rf = (chan <= 14) ? 0xc3 : 0xc0;
+ run_rt3070_rf_write(sc, 9, rf);
+
+ /* set loop filter 1 */
+ run_rt3070_rf_write(sc, 10, 0xf1);
+ /* set loop filter 2 */
+ run_rt3070_rf_write(sc, 11, (chan <= 14) ? 0xb9 : 0x00);
+
+ /* set tx_mx2_ic */
+ run_rt3070_rf_write(sc, 15, (chan <= 14) ? 0x53 : 0x43);
+ /* set tx_mx1_ic */
+ if (chan <= 14)
+ rf = 0x48 | sc->txmixgain_2ghz;
+ else
+ rf = 0x78 | sc->txmixgain_5ghz;
+ run_rt3070_rf_write(sc, 16, rf);
+
+ /* set tx_lo1 */
+ run_rt3070_rf_write(sc, 17, 0x23);
+ /* set tx_lo2 */
+ if (chan <= 14)
+ rf = 0x93;
+ else if (chan <= 64)
+ rf = 0xb7;
+ else if (chan <= 128)
+ rf = 0x74;
+ else
+ rf = 0x72;
+ run_rt3070_rf_write(sc, 19, rf);
+
+ /* set rx_lo1 */
+ if (chan <= 14)
+ rf = 0xb3;
+ else if (chan <= 64)
+ rf = 0xf6;
+ else if (chan <= 128)
+ rf = 0xf4;
+ else
+ rf = 0xf3;
+ run_rt3070_rf_write(sc, 20, rf);
+
+ /* set pfd_delay */
+ if (chan <= 14)
+ rf = 0x15;
+ else if (chan <= 64)
+ rf = 0x3d;
+ else
+ rf = 0x01;
+ run_rt3070_rf_write(sc, 25, rf);
+
+ /* set rx_lo2 */
+ run_rt3070_rf_write(sc, 26, (chan <= 14) ? 0x85 : 0x87);
+ /* set ldo_rf_vc */
+ run_rt3070_rf_write(sc, 27, (chan <= 14) ? 0x00 : 0x01);
+ /* set drv_cc */
+ run_rt3070_rf_write(sc, 29, (chan <= 14) ? 0x9b : 0x9f);
+
+ run_read(sc, RT2860_GPIO_CTRL, &tmp);
+ tmp &= ~0x8080;
+ if (chan <= 14)
+ tmp |= 0x80;
+ run_write(sc, RT2860_GPIO_CTRL, tmp);
+
+ /* enable RF tuning */
+ run_rt3070_rf_read(sc, 7, &rf);
+ run_rt3070_rf_write(sc, 7, rf | 0x01);
+
+ run_delay(sc, 2);
+}
+
+static void
+run_rt3593_set_chan(struct run_softc *sc, u_int chan)
+{
+ int8_t txpow1, txpow2, txpow3;
+ uint8_t h20mhz, rf;
+ int i;
+
+ /* find the settings for this channel (we know it exists) */
+ for (i = 0; rt2860_rf2850[i].chan != chan; i++);
+
+ /* use Tx power values from EEPROM */
+ txpow1 = sc->txpow1[i];
+ txpow2 = sc->txpow2[i];
+ txpow3 = (sc->ntxchains == 3) ? sc->txpow3[i] : 0;
+
+ if (chan <= 14) {
+ run_bbp_write(sc, 25, sc->bbp25);
+ run_bbp_write(sc, 26, sc->bbp26);
+ } else {
+ /* Enable IQ phase correction. */
+ run_bbp_write(sc, 25, 0x09);
+ run_bbp_write(sc, 26, 0xff);
+ }
+
+ run_rt3070_rf_write(sc, 8, rt3070_freqs[i].n);
+ run_rt3070_rf_write(sc, 9, rt3070_freqs[i].k & 0x0f);
+ run_rt3070_rf_read(sc, 11, &rf);
+ rf = (rf & ~0x03) | (rt3070_freqs[i].r & 0x03);
+ run_rt3070_rf_write(sc, 11, rf);
+
+ /* Set pll_idoh. */
+ run_rt3070_rf_read(sc, 11, &rf);
+ rf &= ~0x4c;
+ rf |= (chan <= 14) ? 0x44 : 0x48;
+ run_rt3070_rf_write(sc, 11, rf);
+
+ if (chan <= 14)
+ rf = txpow1 & 0x1f;
+ else
+ rf = 0x40 | ((txpow1 & 0x18) << 1) | (txpow1 & 0x07);
+ run_rt3070_rf_write(sc, 53, rf);
+
+ if (chan <= 14)
+ rf = txpow2 & 0x1f;
+ else
+ rf = 0x40 | ((txpow2 & 0x18) << 1) | (txpow2 & 0x07);
+ run_rt3070_rf_write(sc, 55, rf);
+
+ if (chan <= 14)
+ rf = txpow3 & 0x1f;
+ else
+ rf = 0x40 | ((txpow3 & 0x18) << 1) | (txpow3 & 0x07);
+ run_rt3070_rf_write(sc, 54, rf);
+
+ rf = RT3070_RF_BLOCK | RT3070_PLL_PD;
+ if (sc->ntxchains == 3)
+ rf |= RT3070_TX0_PD | RT3070_TX1_PD | RT3070_TX2_PD;
+ else
+ rf |= RT3070_TX0_PD | RT3070_TX1_PD;
+ rf |= RT3070_RX0_PD | RT3070_RX1_PD | RT3070_RX2_PD;
+ run_rt3070_rf_write(sc, 1, rf);
+
+ run_adjust_freq_offset(sc);
+
+ run_rt3070_rf_write(sc, 31, (chan <= 14) ? 0xa0 : 0x80);
+
+ h20mhz = (sc->rf24_20mhz & 0x20) >> 5;
+ run_rt3070_rf_read(sc, 30, &rf);
+ rf = (rf & ~0x06) | (h20mhz << 1) | (h20mhz << 2);
+ run_rt3070_rf_write(sc, 30, rf);
+
+ run_rt3070_rf_read(sc, 36, &rf);
+ if (chan <= 14)
+ rf |= 0x80;
+ else
+ rf &= ~0x80;
+ run_rt3070_rf_write(sc, 36, rf);
+
+ /* Set vcolo_bs. */
+ run_rt3070_rf_write(sc, 34, (chan <= 14) ? 0x3c : 0x20);
+ /* Set pfd_delay. */
+ run_rt3070_rf_write(sc, 12, (chan <= 14) ? 0x1a : 0x12);
+
+ /* Set vco bias current control. */
+ run_rt3070_rf_read(sc, 6, &rf);
+ rf &= ~0xc0;
+ if (chan <= 14)
+ rf |= 0x40;
+ else if (chan <= 128)
+ rf |= 0x80;
+ else
+ rf |= 0x40;
+ run_rt3070_rf_write(sc, 6, rf);
+
+ run_rt3070_rf_read(sc, 30, &rf);
+ rf = (rf & ~0x18) | 0x10;
+ run_rt3070_rf_write(sc, 30, rf);
+
+ run_rt3070_rf_write(sc, 10, (chan <= 14) ? 0xd3 : 0xd8);
+ run_rt3070_rf_write(sc, 13, (chan <= 14) ? 0x12 : 0x23);
+
+ run_rt3070_rf_read(sc, 51, &rf);
+ rf = (rf & ~0x03) | 0x01;
+ run_rt3070_rf_write(sc, 51, rf);
+ /* Set tx_mx1_cc. */
+ run_rt3070_rf_read(sc, 51, &rf);
+ rf &= ~0x1c;
+ rf |= (chan <= 14) ? 0x14 : 0x10;
+ run_rt3070_rf_write(sc, 51, rf);
+ /* Set tx_mx1_ic. */
+ run_rt3070_rf_read(sc, 51, &rf);
+ rf &= ~0xe0;
+ rf |= (chan <= 14) ? 0x60 : 0x40;
+ run_rt3070_rf_write(sc, 51, rf);
+ /* Set tx_lo1_ic. */
+ run_rt3070_rf_read(sc, 49, &rf);
+ rf &= ~0x1c;
+ rf |= (chan <= 14) ? 0x0c : 0x08;
+ run_rt3070_rf_write(sc, 49, rf);
+ /* Set tx_lo1_en. */
+ run_rt3070_rf_read(sc, 50, &rf);
+ run_rt3070_rf_write(sc, 50, rf & ~0x20);
+ /* Set drv_cc. */
+ run_rt3070_rf_read(sc, 57, &rf);
+ rf &= ~0xfc;
+ rf |= (chan <= 14) ? 0x6c : 0x3c;
+ run_rt3070_rf_write(sc, 57, rf);
+ /* Set rx_mix1_ic, rxa_lnactr, lna_vc, lna_inbias_en and lna_en. */
+ run_rt3070_rf_write(sc, 44, (chan <= 14) ? 0x93 : 0x9b);
+ /* Set drv_gnd_a, tx_vga_cc_a and tx_mx2_gain. */
+ run_rt3070_rf_write(sc, 52, (chan <= 14) ? 0x45 : 0x05);
+ /* Enable VCO calibration. */
+ run_rt3070_rf_read(sc, 3, &rf);
+ rf &= ~RT5390_VCOCAL;
+ rf |= (chan <= 14) ? RT5390_VCOCAL : 0xbe;
+ run_rt3070_rf_write(sc, 3, rf);
+
+ if (chan <= 14)
+ rf = 0x23;
+ else if (chan <= 64)
+ rf = 0x36;
+ else if (chan <= 128)
+ rf = 0x32;
+ else
+ rf = 0x30;
+ run_rt3070_rf_write(sc, 39, rf);
+ if (chan <= 14)
+ rf = 0xbb;
+ else if (chan <= 64)
+ rf = 0xeb;
+ else if (chan <= 128)
+ rf = 0xb3;
+ else
+ rf = 0x9b;
+ run_rt3070_rf_write(sc, 45, rf);
+
+ /* Set FEQ/AEQ control. */
+ run_bbp_write(sc, 105, 0x34);
+}
+
+static void
+run_rt5390_set_chan(struct run_softc *sc, u_int chan)
+{
+ int8_t txpow1, txpow2;
+ uint8_t rf;
+ int i;
+
+ /* find the settings for this channel (we know it exists) */
+ for (i = 0; rt2860_rf2850[i].chan != chan; i++);
+
+ /* use Tx power values from EEPROM */
+ txpow1 = sc->txpow1[i];
+ txpow2 = sc->txpow2[i];
+
+ run_rt3070_rf_write(sc, 8, rt3070_freqs[i].n);
+ run_rt3070_rf_write(sc, 9, rt3070_freqs[i].k & 0x0f);
+ run_rt3070_rf_read(sc, 11, &rf);
+ rf = (rf & ~0x03) | (rt3070_freqs[i].r & 0x03);
+ run_rt3070_rf_write(sc, 11, rf);
+
+ run_rt3070_rf_read(sc, 49, &rf);
+ rf = (rf & ~0x3f) | (txpow1 & 0x3f);
+ /* The valid range of the RF R49 is 0x00 to 0x27. */
+ if ((rf & 0x3f) > 0x27)
+ rf = (rf & ~0x3f) | 0x27;
+ run_rt3070_rf_write(sc, 49, rf);
+
+ if (sc->mac_ver == 0x5392) {
+ run_rt3070_rf_read(sc, 50, &rf);
+ rf = (rf & ~0x3f) | (txpow2 & 0x3f);
+ /* The valid range of the RF R50 is 0x00 to 0x27. */
+ if ((rf & 0x3f) > 0x27)
+ rf = (rf & ~0x3f) | 0x27;
+ run_rt3070_rf_write(sc, 50, rf);
+ }
+
+ run_rt3070_rf_read(sc, 1, &rf);
+ rf |= RT3070_RF_BLOCK | RT3070_PLL_PD | RT3070_RX0_PD | RT3070_TX0_PD;
+ if (sc->mac_ver == 0x5392)
+ rf |= RT3070_RX1_PD | RT3070_TX1_PD;
+ run_rt3070_rf_write(sc, 1, rf);
+
+ if (sc->mac_ver != 0x5392) {
+ run_rt3070_rf_read(sc, 2, &rf);
+ rf |= 0x80;
+ run_rt3070_rf_write(sc, 2, rf);
+ run_delay(sc, 10);
+ rf &= 0x7f;
+ run_rt3070_rf_write(sc, 2, rf);
+ }
+
+ run_adjust_freq_offset(sc);
+
+ if (sc->mac_ver == 0x5392) {
+ /* Fix for RT5392C. */
+ if (sc->mac_rev >= 0x0223) {
+ if (chan <= 4)
+ rf = 0x0f;
+ else if (chan >= 5 && chan <= 7)
+ rf = 0x0e;
+ else
+ rf = 0x0d;
+ run_rt3070_rf_write(sc, 23, rf);
+
+ if (chan <= 4)
+ rf = 0x0c;
+ else if (chan == 5)
+ rf = 0x0b;
+ else if (chan >= 6 && chan <= 7)
+ rf = 0x0a;
+ else if (chan >= 8 && chan <= 10)
+ rf = 0x09;
+ else
+ rf = 0x08;
+ run_rt3070_rf_write(sc, 59, rf);
+ } else {
+ if (chan <= 11)
+ rf = 0x0f;
+ else
+ rf = 0x0b;
+ run_rt3070_rf_write(sc, 59, rf);
+ }
+ } else {
+ /* Fix for RT5390F. */
+ if (sc->mac_rev >= 0x0502) {
+ if (chan <= 11)
+ rf = 0x43;
+ else
+ rf = 0x23;
+ run_rt3070_rf_write(sc, 55, rf);
+
+ if (chan <= 11)
+ rf = 0x0f;
+ else if (chan == 12)
+ rf = 0x0d;
+ else
+ rf = 0x0b;
+ run_rt3070_rf_write(sc, 59, rf);
+ } else {
+ run_rt3070_rf_write(sc, 55, 0x44);
+ run_rt3070_rf_write(sc, 59, 0x8f);
+ }
+ }
+
+ /* Enable VCO calibration. */
+ run_rt3070_rf_read(sc, 3, &rf);
+ rf |= RT5390_VCOCAL;
+ run_rt3070_rf_write(sc, 3, rf);
+}
+
+static void
+run_rt5592_set_chan(struct run_softc *sc, u_int chan)
+{
+ const struct rt5592_freqs *freqs;
+ uint32_t tmp;
+ uint8_t reg, rf, txpow_bound;
+ int8_t txpow1, txpow2;
+ int i;
+
+ run_read(sc, RT5592_DEBUG_INDEX, &tmp);
+ freqs = (tmp & RT5592_SEL_XTAL) ?
+ rt5592_freqs_40mhz : rt5592_freqs_20mhz;
+
+ /* find the settings for this channel (we know it exists) */
+ for (i = 0; rt2860_rf2850[i].chan != chan; i++, freqs++);
+
+ /* use Tx power values from EEPROM */
+ txpow1 = sc->txpow1[i];
+ txpow2 = sc->txpow2[i];
+
+ run_read(sc, RT3070_LDO_CFG0, &tmp);
+ tmp &= ~0x1c000000;
+ if (chan > 14)
+ tmp |= 0x14000000;
+ run_write(sc, RT3070_LDO_CFG0, tmp);
+
+ /* N setting. */
+ run_rt3070_rf_write(sc, 8, freqs->n & 0xff);
+ run_rt3070_rf_read(sc, 9, &rf);
+ rf &= ~(1 << 4);
+ rf |= ((freqs->n & 0x0100) >> 8) << 4;
+ run_rt3070_rf_write(sc, 9, rf);
+
+ /* K setting. */
+ run_rt3070_rf_read(sc, 9, &rf);
+ rf &= ~0x0f;
+ rf |= (freqs->k & 0x0f);
+ run_rt3070_rf_write(sc, 9, rf);
+
+ /* Mode setting. */
+ run_rt3070_rf_read(sc, 11, &rf);
+ rf &= ~0x0c;
+ rf |= ((freqs->m - 0x8) & 0x3) << 2;
+ run_rt3070_rf_write(sc, 11, rf);
+ run_rt3070_rf_read(sc, 9, &rf);
+ rf &= ~(1 << 7);
+ rf |= (((freqs->m - 0x8) & 0x4) >> 2) << 7;
+ run_rt3070_rf_write(sc, 9, rf);
+
+ /* R setting. */
+ run_rt3070_rf_read(sc, 11, &rf);
+ rf &= ~0x03;
+ rf |= (freqs->r - 0x1);
+ run_rt3070_rf_write(sc, 11, rf);
+
+ if (chan <= 14) {
+ /* Initialize RF registers for 2GHZ. */
+ for (i = 0; i < nitems(rt5592_2ghz_def_rf); i++) {
+ run_rt3070_rf_write(sc, rt5592_2ghz_def_rf[i].reg,
+ rt5592_2ghz_def_rf[i].val);
+ }
+
+ rf = (chan <= 10) ? 0x07 : 0x06;
+ run_rt3070_rf_write(sc, 23, rf);
+ run_rt3070_rf_write(sc, 59, rf);
+
+ run_rt3070_rf_write(sc, 55, 0x43);
+
+ /*
+ * RF R49/R50 Tx power ALC code.
+ * G-band bit<7:6>=1:0, bit<5:0> range from 0x0 ~ 0x27.
+ */
+ reg = 2;
+ txpow_bound = 0x27;
+ } else {
+ /* Initialize RF registers for 5GHZ. */
+ for (i = 0; i < nitems(rt5592_5ghz_def_rf); i++) {
+ run_rt3070_rf_write(sc, rt5592_5ghz_def_rf[i].reg,
+ rt5592_5ghz_def_rf[i].val);
+ }
+ for (i = 0; i < nitems(rt5592_chan_5ghz); i++) {
+ if (chan >= rt5592_chan_5ghz[i].firstchan &&
+ chan <= rt5592_chan_5ghz[i].lastchan) {
+ run_rt3070_rf_write(sc, rt5592_chan_5ghz[i].reg,
+ rt5592_chan_5ghz[i].val);
+ }
+ }
+
+ /*
+ * RF R49/R50 Tx power ALC code.
+ * A-band bit<7:6>=1:1, bit<5:0> range from 0x0 ~ 0x2b.
+ */
+ reg = 3;
+ txpow_bound = 0x2b;
+ }
+
+ /* RF R49 ch0 Tx power ALC code. */
+ run_rt3070_rf_read(sc, 49, &rf);
+ rf &= ~0xc0;
+ rf |= (reg << 6);
+ rf = (rf & ~0x3f) | (txpow1 & 0x3f);
+ if ((rf & 0x3f) > txpow_bound)
+ rf = (rf & ~0x3f) | txpow_bound;
+ run_rt3070_rf_write(sc, 49, rf);
+
+ /* RF R50 ch1 Tx power ALC code. */
+ run_rt3070_rf_read(sc, 50, &rf);
+ rf &= ~(1 << 7 | 1 << 6);
+ rf |= (reg << 6);
+ rf = (rf & ~0x3f) | (txpow2 & 0x3f);
+ if ((rf & 0x3f) > txpow_bound)
+ rf = (rf & ~0x3f) | txpow_bound;
+ run_rt3070_rf_write(sc, 50, rf);
+
+ /* Enable RF_BLOCK, PLL_PD, RX0_PD, and TX0_PD. */
+ run_rt3070_rf_read(sc, 1, &rf);
+ rf |= (RT3070_RF_BLOCK | RT3070_PLL_PD | RT3070_RX0_PD | RT3070_TX0_PD);
+ if (sc->ntxchains > 1)
+ rf |= RT3070_TX1_PD;
+ if (sc->nrxchains > 1)
+ rf |= RT3070_RX1_PD;
+ run_rt3070_rf_write(sc, 1, rf);
+
+ run_rt3070_rf_write(sc, 6, 0xe4);
+
+ run_rt3070_rf_write(sc, 30, 0x10);
+ run_rt3070_rf_write(sc, 31, 0x80);
+ run_rt3070_rf_write(sc, 32, 0x80);
+
+ run_adjust_freq_offset(sc);
+
+ /* Enable VCO calibration. */
+ run_rt3070_rf_read(sc, 3, &rf);
+ rf |= RT5390_VCOCAL;
+ run_rt3070_rf_write(sc, 3, rf);
+}
+
+static void
+run_set_rx_antenna(struct run_softc *sc, int aux)
+{
+ uint32_t tmp;
+ uint8_t bbp152;
+
+ if (aux) {
+ if (sc->rf_rev == RT5390_RF_5370) {
+ run_bbp_read(sc, 152, &bbp152);
+ run_bbp_write(sc, 152, bbp152 & ~0x80);
+ } else {
+ run_mcu_cmd(sc, RT2860_MCU_CMD_ANTSEL, 0);
+ run_read(sc, RT2860_GPIO_CTRL, &tmp);
+ run_write(sc, RT2860_GPIO_CTRL, (tmp & ~0x0808) | 0x08);
+ }
+ } else {
+ if (sc->rf_rev == RT5390_RF_5370) {
+ run_bbp_read(sc, 152, &bbp152);
+ run_bbp_write(sc, 152, bbp152 | 0x80);
+ } else {
+ run_mcu_cmd(sc, RT2860_MCU_CMD_ANTSEL, 1);
+ run_read(sc, RT2860_GPIO_CTRL, &tmp);
+ run_write(sc, RT2860_GPIO_CTRL, tmp & ~0x0808);
+ }
+ }
+}
+
+static int
+run_set_chan(struct run_softc *sc, struct ieee80211_channel *c)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ u_int chan, group;
+
+ chan = ieee80211_chan2ieee(ic, c);
+ if (chan == 0 || chan == IEEE80211_CHAN_ANY)
+ return (EINVAL);
+
+ if (sc->mac_ver == 0x5592)
+ run_rt5592_set_chan(sc, chan);
+ else if (sc->mac_ver >= 0x5390)
+ run_rt5390_set_chan(sc, chan);
+ else if (sc->mac_ver == 0x3593)
+ run_rt3593_set_chan(sc, chan);
+ else if (sc->mac_ver == 0x3572)
+ run_rt3572_set_chan(sc, chan);
+ else if (sc->mac_ver >= 0x3070)
+ run_rt3070_set_chan(sc, chan);
+ else
+ run_rt2870_set_chan(sc, chan);
+
+ /* determine channel group */
+ if (chan <= 14)
+ group = 0;
+ else if (chan <= 64)
+ group = 1;
+ else if (chan <= 128)
+ group = 2;
+ else
+ group = 3;
+
+ /* XXX necessary only when group has changed! */
+ run_select_chan_group(sc, group);
+
+ run_delay(sc, 10);
+
+ /* Perform IQ calibration. */
+ if (sc->mac_ver >= 0x5392)
+ run_iq_calib(sc, chan);
+
+ return (0);
+}
+
+static void
+run_set_channel(struct ieee80211com *ic)
+{
+ struct run_softc *sc = ic->ic_softc;
+
+ RUN_LOCK(sc);
+ run_set_chan(sc, ic->ic_curchan);
+ RUN_UNLOCK(sc);
+
+ return;
+}
+
+static void
+run_getradiocaps(struct ieee80211com *ic,
+ int maxchans, int *nchans, struct ieee80211_channel chans[])
+{
+ struct run_softc *sc = ic->ic_softc;
+ uint8_t bands[IEEE80211_MODE_BYTES];
+
+ memset(bands, 0, sizeof(bands));
+ setbit(bands, IEEE80211_MODE_11B);
+ setbit(bands, IEEE80211_MODE_11G);
+ if (sc->rf_rev != RT3070_RF_2020)
+ setbit(bands, IEEE80211_MODE_11NG);
+
+ /* Note: for now, only support HT20 channels */
+ ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0);
+
+ if (sc->rf_rev == RT2860_RF_2750 || sc->rf_rev == RT2860_RF_2850 ||
+ sc->rf_rev == RT3070_RF_3052 || sc->rf_rev == RT3593_RF_3053 ||
+ sc->rf_rev == RT5592_RF_5592) {
+ setbit(bands, IEEE80211_MODE_11A);
+ if (sc->rf_rev != RT3070_RF_2020)
+ setbit(bands, IEEE80211_MODE_11NA);
+ /* Note: for now, only support HT20 channels */
+ ieee80211_add_channel_list_5ghz(chans, maxchans, nchans,
+ run_chan_5ghz, nitems(run_chan_5ghz), bands, 0);
+ }
+}
+
+static void
+run_scan_start(struct ieee80211com *ic)
+{
+ struct run_softc *sc = ic->ic_softc;
+
+ RUN_LOCK(sc);
+
+ /* abort TSF synchronization */
+ run_disable_tsf(sc);
+ run_set_bssid(sc, ieee80211broadcastaddr);
+
+ RUN_UNLOCK(sc);
+
+ return;
+}
+
+static void
+run_scan_end(struct ieee80211com *ic)
+{
+ struct run_softc *sc = ic->ic_softc;
+
+ RUN_LOCK(sc);
+
+ run_enable_tsf_sync(sc);
+ run_set_bssid(sc, sc->sc_bssid);
+
+ RUN_UNLOCK(sc);
+
+ return;
+}
+
+/*
+ * Could be called from ieee80211_node_timeout()
+ * (non-sleepable thread)
+ */
+static void
+run_update_beacon(struct ieee80211vap *vap, int item)
+{
+ struct ieee80211com *ic = vap->iv_ic;
+ struct ieee80211_beacon_offsets *bo = &vap->iv_bcn_off;
+ struct ieee80211_node *ni = vap->iv_bss;
+ struct run_softc *sc = ic->ic_softc;
+ struct run_vap *rvp = RUN_VAP(vap);
+ int mcast = 0;
+ uint32_t i;
+
+ switch (item) {
+ case IEEE80211_BEACON_ERP:
+ run_updateslot(ic);
+ break;
+ case IEEE80211_BEACON_HTINFO:
+ run_updateprot(ic);
+ break;
+ case IEEE80211_BEACON_TIM:
+ mcast = 1; /*TODO*/
+ break;
+ default:
+ break;
+ }
+
+ setbit(bo->bo_flags, item);
+ if (rvp->beacon_mbuf == NULL) {
+ rvp->beacon_mbuf = ieee80211_beacon_alloc(ni);
+ if (rvp->beacon_mbuf == NULL)
+ return;
+ }
+ ieee80211_beacon_update(ni, rvp->beacon_mbuf, mcast);
+
+ i = RUN_CMDQ_GET(&sc->cmdq_store);
+ RUN_DPRINTF(sc, RUN_DEBUG_BEACON, "cmdq_store=%d\n", i);
+ sc->cmdq[i].func = run_update_beacon_cb;
+ sc->cmdq[i].arg0 = vap;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+
+ return;
+}
+
+static void
+run_update_beacon_cb(void *arg)
+{
+ struct ieee80211vap *vap = arg;
+ struct ieee80211_node *ni = vap->iv_bss;
+ struct run_vap *rvp = RUN_VAP(vap);
+ struct ieee80211com *ic = vap->iv_ic;
+ struct run_softc *sc = ic->ic_softc;
+ struct rt2860_txwi txwi;
+ struct mbuf *m;
+ uint16_t txwisize;
+ uint8_t ridx;
+
+ if (ni->ni_chan == IEEE80211_CHAN_ANYC)
+ return;
+ if (ic->ic_bsschan == IEEE80211_CHAN_ANYC)
+ return;
+
+ /*
+ * No need to call ieee80211_beacon_update(), run_update_beacon()
+ * is taking care of appropriate calls.
+ */
+ if (rvp->beacon_mbuf == NULL) {
+ rvp->beacon_mbuf = ieee80211_beacon_alloc(ni);
+ if (rvp->beacon_mbuf == NULL)
+ return;
+ }
+ m = rvp->beacon_mbuf;
+
+ memset(&txwi, 0, sizeof(txwi));
+ txwi.wcid = 0xff;
+ txwi.len = htole16(m->m_pkthdr.len);
+
+ /* send beacons at the lowest available rate */
+ ridx = (ic->ic_curmode == IEEE80211_MODE_11A) ?
+ RT2860_RIDX_OFDM6 : RT2860_RIDX_CCK1;
+ txwi.phy = htole16(rt2860_rates[ridx].mcs);
+ if (rt2860_rates[ridx].phy == IEEE80211_T_OFDM)
+ txwi.phy |= htole16(RT2860_PHY_OFDM);
+ txwi.txop = RT2860_TX_TXOP_HT;
+ txwi.flags = RT2860_TX_TS;
+ txwi.xflags = RT2860_TX_NSEQ;
+
+ txwisize = (sc->mac_ver == 0x5592) ?
+ sizeof(txwi) + sizeof(uint32_t) : sizeof(txwi);
+ run_write_region_1(sc, RT2860_BCN_BASE(rvp->rvp_id), (uint8_t *)&txwi,
+ txwisize);
+ run_write_region_1(sc, RT2860_BCN_BASE(rvp->rvp_id) + txwisize,
+ mtod(m, uint8_t *), (m->m_pkthdr.len + 1) & ~1);
+}
+
+static void
+run_updateprot(struct ieee80211com *ic)
+{
+ struct run_softc *sc = ic->ic_softc;
+ uint32_t i;
+
+ i = RUN_CMDQ_GET(&sc->cmdq_store);
+ RUN_DPRINTF(sc, RUN_DEBUG_BEACON, "cmdq_store=%d\n", i);
+ sc->cmdq[i].func = run_updateprot_cb;
+ sc->cmdq[i].arg0 = ic;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+}
+
+static void
+run_updateprot_cb(void *arg)
+{
+ struct ieee80211com *ic = arg;
+ struct run_softc *sc = ic->ic_softc;
+ uint32_t tmp;
+
+ tmp = RT2860_RTSTH_EN | RT2860_PROT_NAV_SHORT | RT2860_TXOP_ALLOW_ALL;
+ /* setup protection frame rate (MCS code) */
+ tmp |= (ic->ic_curmode == IEEE80211_MODE_11A) ?
+ rt2860_rates[RT2860_RIDX_OFDM6].mcs | RT2860_PHY_OFDM :
+ rt2860_rates[RT2860_RIDX_CCK11].mcs;
+
+ /* CCK frames don't require protection */
+ run_write(sc, RT2860_CCK_PROT_CFG, tmp);
+ if (ic->ic_flags & IEEE80211_F_USEPROT) {
+ if (ic->ic_protmode == IEEE80211_PROT_RTSCTS)
+ tmp |= RT2860_PROT_CTRL_RTS_CTS;
+ else if (ic->ic_protmode == IEEE80211_PROT_CTSONLY)
+ tmp |= RT2860_PROT_CTRL_CTS;
+ }
+ run_write(sc, RT2860_OFDM_PROT_CFG, tmp);
+}
+
+static void
+run_usb_timeout_cb(void *arg)
+{
+ struct ieee80211vap *vap = arg;
+ struct run_softc *sc = vap->iv_ic->ic_softc;
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+
+ if(vap->iv_state == IEEE80211_S_RUN &&
+ vap->iv_opmode != IEEE80211_M_STA)
+ run_reset_livelock(sc);
+ else if (vap->iv_state == IEEE80211_S_SCAN) {
+ RUN_DPRINTF(sc, RUN_DEBUG_USB | RUN_DEBUG_STATE,
+ "timeout caused by scan\n");
+ /* cancel bgscan */
+ ieee80211_cancel_scan(vap);
+ } else
+ RUN_DPRINTF(sc, RUN_DEBUG_USB | RUN_DEBUG_STATE,
+ "timeout by unknown cause\n");
+}
+
+static void
+run_reset_livelock(struct run_softc *sc)
+{
+ uint32_t tmp;
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+
+ /*
+ * In IBSS or HostAP modes (when the hardware sends beacons), the MAC
+ * can run into a livelock and start sending CTS-to-self frames like
+ * crazy if protection is enabled. Reset MAC/BBP for a while
+ */
+ run_read(sc, RT2860_DEBUG, &tmp);
+ RUN_DPRINTF(sc, RUN_DEBUG_RESET, "debug reg %08x\n", tmp);
+ if ((tmp & (1 << 29)) && (tmp & (1 << 7 | 1 << 5))) {
+ RUN_DPRINTF(sc, RUN_DEBUG_RESET,
+ "CTS-to-self livelock detected\n");
+ run_write(sc, RT2860_MAC_SYS_CTRL, RT2860_MAC_SRST);
+ run_delay(sc, 1);
+ run_write(sc, RT2860_MAC_SYS_CTRL,
+ RT2860_MAC_RX_EN | RT2860_MAC_TX_EN);
+ }
+}
+
+static void
+run_update_promisc_locked(struct run_softc *sc)
+{
+ uint32_t tmp;
+
+ run_read(sc, RT2860_RX_FILTR_CFG, &tmp);
+
+ tmp |= RT2860_DROP_UC_NOME;
+ if (sc->sc_ic.ic_promisc > 0)
+ tmp &= ~RT2860_DROP_UC_NOME;
+
+ run_write(sc, RT2860_RX_FILTR_CFG, tmp);
+
+ RUN_DPRINTF(sc, RUN_DEBUG_RECV, "%s promiscuous mode\n",
+ (sc->sc_ic.ic_promisc > 0) ? "entering" : "leaving");
+}
+
+static void
+run_update_promisc(struct ieee80211com *ic)
+{
+ struct run_softc *sc = ic->ic_softc;
+
+ if ((sc->sc_flags & RUN_RUNNING) == 0)
+ return;
+
+ RUN_LOCK(sc);
+ run_update_promisc_locked(sc);
+ RUN_UNLOCK(sc);
+}
+
+static void
+run_enable_tsf_sync(struct run_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ uint32_t tmp;
+
+ RUN_DPRINTF(sc, RUN_DEBUG_BEACON, "rvp_id=%d ic_opmode=%d\n",
+ RUN_VAP(vap)->rvp_id, ic->ic_opmode);
+
+ run_read(sc, RT2860_BCN_TIME_CFG, &tmp);
+ tmp &= ~0x1fffff;
+ tmp |= vap->iv_bss->ni_intval * 16;
+ tmp |= RT2860_TSF_TIMER_EN | RT2860_TBTT_TIMER_EN;
+
+ if (ic->ic_opmode == IEEE80211_M_STA) {
+ /*
+ * Local TSF is always updated with remote TSF on beacon
+ * reception.
+ */
+ tmp |= 1 << RT2860_TSF_SYNC_MODE_SHIFT;
+ } else if (ic->ic_opmode == IEEE80211_M_IBSS) {
+ tmp |= RT2860_BCN_TX_EN;
+ /*
+ * Local TSF is updated with remote TSF on beacon reception
+ * only if the remote TSF is greater than local TSF.
+ */
+ tmp |= 2 << RT2860_TSF_SYNC_MODE_SHIFT;
+ } else if (ic->ic_opmode == IEEE80211_M_HOSTAP ||
+ ic->ic_opmode == IEEE80211_M_MBSS) {
+ tmp |= RT2860_BCN_TX_EN;
+ /* SYNC with nobody */
+ tmp |= 3 << RT2860_TSF_SYNC_MODE_SHIFT;
+ } else {
+ RUN_DPRINTF(sc, RUN_DEBUG_BEACON,
+ "Enabling TSF failed. undefined opmode\n");
+ return;
+ }
+
+ run_write(sc, RT2860_BCN_TIME_CFG, tmp);
+}
+
+static void
+run_enable_tsf(struct run_softc *sc)
+{
+ uint32_t tmp;
+
+ if (run_read(sc, RT2860_BCN_TIME_CFG, &tmp) == 0) {
+ tmp &= ~(RT2860_BCN_TX_EN | RT2860_TBTT_TIMER_EN);
+ tmp |= RT2860_TSF_TIMER_EN;
+ run_write(sc, RT2860_BCN_TIME_CFG, tmp);
+ }
+}
+
+static void
+run_disable_tsf(struct run_softc *sc)
+{
+ uint32_t tmp;
+
+ if (run_read(sc, RT2860_BCN_TIME_CFG, &tmp) == 0) {
+ tmp &= ~(RT2860_BCN_TX_EN | RT2860_TSF_TIMER_EN |
+ RT2860_TBTT_TIMER_EN);
+ run_write(sc, RT2860_BCN_TIME_CFG, tmp);
+ }
+}
+
+static void
+run_get_tsf(struct run_softc *sc, uint64_t *buf)
+{
+ run_read_region_1(sc, RT2860_TSF_TIMER_DW0, (uint8_t *)buf,
+ sizeof(*buf));
+}
+
+static void
+run_enable_mrr(struct run_softc *sc)
+{
+#define CCK(mcs) (mcs)
+#define OFDM(mcs) (1 << 3 | (mcs))
+ run_write(sc, RT2860_LG_FBK_CFG0,
+ OFDM(6) << 28 | /* 54->48 */
+ OFDM(5) << 24 | /* 48->36 */
+ OFDM(4) << 20 | /* 36->24 */
+ OFDM(3) << 16 | /* 24->18 */
+ OFDM(2) << 12 | /* 18->12 */
+ OFDM(1) << 8 | /* 12-> 9 */
+ OFDM(0) << 4 | /* 9-> 6 */
+ OFDM(0)); /* 6-> 6 */
+
+ run_write(sc, RT2860_LG_FBK_CFG1,
+ CCK(2) << 12 | /* 11->5.5 */
+ CCK(1) << 8 | /* 5.5-> 2 */
+ CCK(0) << 4 | /* 2-> 1 */
+ CCK(0)); /* 1-> 1 */
+#undef OFDM
+#undef CCK
+}
+
+static void
+run_set_txpreamble(struct run_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t tmp;
+
+ run_read(sc, RT2860_AUTO_RSP_CFG, &tmp);
+ if (ic->ic_flags & IEEE80211_F_SHPREAMBLE)
+ tmp |= RT2860_CCK_SHORT_EN;
+ else
+ tmp &= ~RT2860_CCK_SHORT_EN;
+ run_write(sc, RT2860_AUTO_RSP_CFG, tmp);
+}
+
+static void
+run_set_basicrates(struct run_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ /* set basic rates mask */
+ if (ic->ic_curmode == IEEE80211_MODE_11B)
+ run_write(sc, RT2860_LEGACY_BASIC_RATE, 0x003);
+ else if (ic->ic_curmode == IEEE80211_MODE_11A)
+ run_write(sc, RT2860_LEGACY_BASIC_RATE, 0x150);
+ else /* 11g */
+ run_write(sc, RT2860_LEGACY_BASIC_RATE, 0x15f);
+}
+
+static void
+run_set_leds(struct run_softc *sc, uint16_t which)
+{
+ (void)run_mcu_cmd(sc, RT2860_MCU_CMD_LEDS,
+ which | (sc->leds & 0x7f));
+}
+
+static void
+run_set_bssid(struct run_softc *sc, const uint8_t *bssid)
+{
+ run_write(sc, RT2860_MAC_BSSID_DW0,
+ bssid[0] | bssid[1] << 8 | bssid[2] << 16 | bssid[3] << 24);
+ run_write(sc, RT2860_MAC_BSSID_DW1,
+ bssid[4] | bssid[5] << 8);
+}
+
+static void
+run_set_macaddr(struct run_softc *sc, const uint8_t *addr)
+{
+ run_write(sc, RT2860_MAC_ADDR_DW0,
+ addr[0] | addr[1] << 8 | addr[2] << 16 | addr[3] << 24);
+ run_write(sc, RT2860_MAC_ADDR_DW1,
+ addr[4] | addr[5] << 8 | 0xff << 16);
+}
+
+static void
+run_updateslot(struct ieee80211com *ic)
+{
+ struct run_softc *sc = ic->ic_softc;
+ uint32_t i;
+
+ i = RUN_CMDQ_GET(&sc->cmdq_store);
+ RUN_DPRINTF(sc, RUN_DEBUG_BEACON, "cmdq_store=%d\n", i);
+ sc->cmdq[i].func = run_updateslot_cb;
+ sc->cmdq[i].arg0 = ic;
+ ieee80211_runtask(ic, &sc->cmdq_task);
+
+ return;
+}
+
+/* ARGSUSED */
+static void
+run_updateslot_cb(void *arg)
+{
+ struct ieee80211com *ic = arg;
+ struct run_softc *sc = ic->ic_softc;
+ uint32_t tmp;
+
+ run_read(sc, RT2860_BKOFF_SLOT_CFG, &tmp);
+ tmp &= ~0xff;
+ tmp |= IEEE80211_GET_SLOTTIME(ic);
+ run_write(sc, RT2860_BKOFF_SLOT_CFG, tmp);
+}
+
+static void
+run_update_mcast(struct ieee80211com *ic)
+{
+}
+
+static int8_t
+run_rssi2dbm(struct run_softc *sc, uint8_t rssi, uint8_t rxchain)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_channel *c = ic->ic_curchan;
+ int delta;
+
+ if (IEEE80211_IS_CHAN_5GHZ(c)) {
+ u_int chan = ieee80211_chan2ieee(ic, c);
+ delta = sc->rssi_5ghz[rxchain];
+
+ /* determine channel group */
+ if (chan <= 64)
+ delta -= sc->lna[1];
+ else if (chan <= 128)
+ delta -= sc->lna[2];
+ else
+ delta -= sc->lna[3];
+ } else
+ delta = sc->rssi_2ghz[rxchain] - sc->lna[0];
+
+ return (-12 - delta - rssi);
+}
+
+static void
+run_rt5390_bbp_init(struct run_softc *sc)
+{
+ u_int i;
+ uint8_t bbp;
+
+ /* Apply maximum likelihood detection for 2 stream case. */
+ run_bbp_read(sc, 105, &bbp);
+ if (sc->nrxchains > 1)
+ run_bbp_write(sc, 105, bbp | RT5390_MLD);
+
+ /* Avoid data lost and CRC error. */
+ run_bbp_read(sc, 4, &bbp);
+ run_bbp_write(sc, 4, bbp | RT5390_MAC_IF_CTRL);
+
+ if (sc->mac_ver == 0x5592) {
+ for (i = 0; i < nitems(rt5592_def_bbp); i++) {
+ run_bbp_write(sc, rt5592_def_bbp[i].reg,
+ rt5592_def_bbp[i].val);
+ }
+ for (i = 0; i < nitems(rt5592_bbp_r196); i++) {
+ run_bbp_write(sc, 195, i + 0x80);
+ run_bbp_write(sc, 196, rt5592_bbp_r196[i]);
+ }
+ } else {
+ for (i = 0; i < nitems(rt5390_def_bbp); i++) {
+ run_bbp_write(sc, rt5390_def_bbp[i].reg,
+ rt5390_def_bbp[i].val);
+ }
+ }
+ if (sc->mac_ver == 0x5392) {
+ run_bbp_write(sc, 88, 0x90);
+ run_bbp_write(sc, 95, 0x9a);
+ run_bbp_write(sc, 98, 0x12);
+ run_bbp_write(sc, 106, 0x12);
+ run_bbp_write(sc, 134, 0xd0);
+ run_bbp_write(sc, 135, 0xf6);
+ run_bbp_write(sc, 148, 0x84);
+ }
+
+ run_bbp_read(sc, 152, &bbp);
+ run_bbp_write(sc, 152, bbp | 0x80);
+
+ /* Fix BBP254 for RT5592C. */
+ if (sc->mac_ver == 0x5592 && sc->mac_rev >= 0x0221) {
+ run_bbp_read(sc, 254, &bbp);
+ run_bbp_write(sc, 254, bbp | 0x80);
+ }
+
+ /* Disable hardware antenna diversity. */
+ if (sc->mac_ver == 0x5390)
+ run_bbp_write(sc, 154, 0);
+
+ /* Initialize Rx CCK/OFDM frequency offset report. */
+ run_bbp_write(sc, 142, 1);
+ run_bbp_write(sc, 143, 57);
+}
+
+static int
+run_bbp_init(struct run_softc *sc)
+{
+ int i, error, ntries;
+ uint8_t bbp0;
+
+ /* wait for BBP to wake up */
+ for (ntries = 0; ntries < 20; ntries++) {
+ if ((error = run_bbp_read(sc, 0, &bbp0)) != 0)
+ return error;
+ if (bbp0 != 0 && bbp0 != 0xff)
+ break;
+ }
+ if (ntries == 20)
+ return (ETIMEDOUT);
+
+ /* initialize BBP registers to default values */
+ if (sc->mac_ver >= 0x5390)
+ run_rt5390_bbp_init(sc);
+ else {
+ for (i = 0; i < nitems(rt2860_def_bbp); i++) {
+ run_bbp_write(sc, rt2860_def_bbp[i].reg,
+ rt2860_def_bbp[i].val);
+ }
+ }
+
+ if (sc->mac_ver == 0x3593) {
+ run_bbp_write(sc, 79, 0x13);
+ run_bbp_write(sc, 80, 0x05);
+ run_bbp_write(sc, 81, 0x33);
+ run_bbp_write(sc, 86, 0x46);
+ run_bbp_write(sc, 137, 0x0f);
+ }
+
+ /* fix BBP84 for RT2860E */
+ if (sc->mac_ver == 0x2860 && sc->mac_rev != 0x0101)
+ run_bbp_write(sc, 84, 0x19);
+
+ if (sc->mac_ver >= 0x3070 && (sc->mac_ver != 0x3593 &&
+ sc->mac_ver != 0x5592)) {
+ run_bbp_write(sc, 79, 0x13);
+ run_bbp_write(sc, 80, 0x05);
+ run_bbp_write(sc, 81, 0x33);
+ } else if (sc->mac_ver == 0x2860 && sc->mac_rev == 0x0100) {
+ run_bbp_write(sc, 69, 0x16);
+ run_bbp_write(sc, 73, 0x12);
+ }
+ return (0);
+}
+
+static int
+run_rt3070_rf_init(struct run_softc *sc)
+{
+ uint32_t tmp;
+ uint8_t bbp4, mingain, rf, target;
+ u_int i;
+
+ run_rt3070_rf_read(sc, 30, &rf);
+ /* toggle RF R30 bit 7 */
+ run_rt3070_rf_write(sc, 30, rf | 0x80);
+ run_delay(sc, 10);
+ run_rt3070_rf_write(sc, 30, rf & ~0x80);
+
+ /* initialize RF registers to default value */
+ if (sc->mac_ver == 0x3572) {
+ for (i = 0; i < nitems(rt3572_def_rf); i++) {
+ run_rt3070_rf_write(sc, rt3572_def_rf[i].reg,
+ rt3572_def_rf[i].val);
+ }
+ } else {
+ for (i = 0; i < nitems(rt3070_def_rf); i++) {
+ run_rt3070_rf_write(sc, rt3070_def_rf[i].reg,
+ rt3070_def_rf[i].val);
+ }
+ }
+
+ if (sc->mac_ver == 0x3070 && sc->mac_rev < 0x0201) {
+ /*
+ * Change voltage from 1.2V to 1.35V for RT3070.
+ * The DAC issue (RT3070_LDO_CFG0) has been fixed
+ * in RT3070(F).
+ */
+ run_read(sc, RT3070_LDO_CFG0, &tmp);
+ tmp = (tmp & ~0x0f000000) | 0x0d000000;
+ run_write(sc, RT3070_LDO_CFG0, tmp);
+
+ } else if (sc->mac_ver == 0x3071) {
+ run_rt3070_rf_read(sc, 6, &rf);
+ run_rt3070_rf_write(sc, 6, rf | 0x40);
+ run_rt3070_rf_write(sc, 31, 0x14);
+
+ run_read(sc, RT3070_LDO_CFG0, &tmp);
+ tmp &= ~0x1f000000;
+ if (sc->mac_rev < 0x0211)
+ tmp |= 0x0d000000; /* 1.3V */
+ else
+ tmp |= 0x01000000; /* 1.2V */
+ run_write(sc, RT3070_LDO_CFG0, tmp);
+
+ /* patch LNA_PE_G1 */
+ run_read(sc, RT3070_GPIO_SWITCH, &tmp);
+ run_write(sc, RT3070_GPIO_SWITCH, tmp & ~0x20);
+
+ } else if (sc->mac_ver == 0x3572) {
+ run_rt3070_rf_read(sc, 6, &rf);
+ run_rt3070_rf_write(sc, 6, rf | 0x40);
+
+ /* increase voltage from 1.2V to 1.35V */
+ run_read(sc, RT3070_LDO_CFG0, &tmp);
+ tmp = (tmp & ~0x1f000000) | 0x0d000000;
+ run_write(sc, RT3070_LDO_CFG0, tmp);
+
+ if (sc->mac_rev < 0x0211 || !sc->patch_dac) {
+ run_delay(sc, 1); /* wait for 1msec */
+ /* decrease voltage back to 1.2V */
+ tmp = (tmp & ~0x1f000000) | 0x01000000;
+ run_write(sc, RT3070_LDO_CFG0, tmp);
+ }
+ }
+
+ /* select 20MHz bandwidth */
+ run_rt3070_rf_read(sc, 31, &rf);
+ run_rt3070_rf_write(sc, 31, rf & ~0x20);
+
+ /* calibrate filter for 20MHz bandwidth */
+ sc->rf24_20mhz = 0x1f; /* default value */
+ target = (sc->mac_ver < 0x3071) ? 0x16 : 0x13;
+ run_rt3070_filter_calib(sc, 0x07, target, &sc->rf24_20mhz);
+
+ /* select 40MHz bandwidth */
+ run_bbp_read(sc, 4, &bbp4);
+ run_bbp_write(sc, 4, (bbp4 & ~0x18) | 0x10);
+ run_rt3070_rf_read(sc, 31, &rf);
+ run_rt3070_rf_write(sc, 31, rf | 0x20);
+
+ /* calibrate filter for 40MHz bandwidth */
+ sc->rf24_40mhz = 0x2f; /* default value */
+ target = (sc->mac_ver < 0x3071) ? 0x19 : 0x15;
+ run_rt3070_filter_calib(sc, 0x27, target, &sc->rf24_40mhz);
+
+ /* go back to 20MHz bandwidth */
+ run_bbp_read(sc, 4, &bbp4);
+ run_bbp_write(sc, 4, bbp4 & ~0x18);
+
+ if (sc->mac_ver == 0x3572) {
+ /* save default BBP registers 25 and 26 values */
+ run_bbp_read(sc, 25, &sc->bbp25);
+ run_bbp_read(sc, 26, &sc->bbp26);
+ } else if (sc->mac_rev < 0x0201 || sc->mac_rev < 0x0211)
+ run_rt3070_rf_write(sc, 27, 0x03);
+
+ run_read(sc, RT3070_OPT_14, &tmp);
+ run_write(sc, RT3070_OPT_14, tmp | 1);
+
+ if (sc->mac_ver == 0x3070 || sc->mac_ver == 0x3071) {
+ run_rt3070_rf_read(sc, 17, &rf);
+ rf &= ~RT3070_TX_LO1;
+ if ((sc->mac_ver == 0x3070 ||
+ (sc->mac_ver == 0x3071 && sc->mac_rev >= 0x0211)) &&
+ !sc->ext_2ghz_lna)
+ rf |= 0x20; /* fix for long range Rx issue */
+ mingain = (sc->mac_ver == 0x3070) ? 1 : 2;
+ if (sc->txmixgain_2ghz >= mingain)
+ rf = (rf & ~0x7) | sc->txmixgain_2ghz;
+ run_rt3070_rf_write(sc, 17, rf);
+ }
+
+ if (sc->mac_ver == 0x3071) {
+ run_rt3070_rf_read(sc, 1, &rf);
+ rf &= ~(RT3070_RX0_PD | RT3070_TX0_PD);
+ rf |= RT3070_RF_BLOCK | RT3070_RX1_PD | RT3070_TX1_PD;
+ run_rt3070_rf_write(sc, 1, rf);
+
+ run_rt3070_rf_read(sc, 15, &rf);
+ run_rt3070_rf_write(sc, 15, rf & ~RT3070_TX_LO2);
+
+ run_rt3070_rf_read(sc, 20, &rf);
+ run_rt3070_rf_write(sc, 20, rf & ~RT3070_RX_LO1);
+
+ run_rt3070_rf_read(sc, 21, &rf);
+ run_rt3070_rf_write(sc, 21, rf & ~RT3070_RX_LO2);
+ }
+
+ if (sc->mac_ver == 0x3070 || sc->mac_ver == 0x3071) {
+ /* fix Tx to Rx IQ glitch by raising RF voltage */
+ run_rt3070_rf_read(sc, 27, &rf);
+ rf &= ~0x77;
+ if (sc->mac_rev < 0x0211)
+ rf |= 0x03;
+ run_rt3070_rf_write(sc, 27, rf);
+ }
+ return (0);
+}
+
+static void
+run_rt3593_rf_init(struct run_softc *sc)
+{
+ uint32_t tmp;
+ uint8_t rf;
+ u_int i;
+
+ /* Disable the GPIO bits 4 and 7 for LNA PE control. */
+ run_read(sc, RT3070_GPIO_SWITCH, &tmp);
+ tmp &= ~(1 << 4 | 1 << 7);
+ run_write(sc, RT3070_GPIO_SWITCH, tmp);
+
+ /* Initialize RF registers to default value. */
+ for (i = 0; i < nitems(rt3593_def_rf); i++) {
+ run_rt3070_rf_write(sc, rt3593_def_rf[i].reg,
+ rt3593_def_rf[i].val);
+ }
+
+ /* Toggle RF R2 to initiate calibration. */
+ run_rt3070_rf_write(sc, 2, RT5390_RESCAL);
+
+ /* Initialize RF frequency offset. */
+ run_adjust_freq_offset(sc);
+
+ run_rt3070_rf_read(sc, 18, &rf);
+ run_rt3070_rf_write(sc, 18, rf | RT3593_AUTOTUNE_BYPASS);
+
+ /*
+ * Increase voltage from 1.2V to 1.35V, wait for 1 msec to
+ * decrease voltage back to 1.2V.
+ */
+ run_read(sc, RT3070_LDO_CFG0, &tmp);
+ tmp = (tmp & ~0x1f000000) | 0x0d000000;
+ run_write(sc, RT3070_LDO_CFG0, tmp);
+ run_delay(sc, 1);
+ tmp = (tmp & ~0x1f000000) | 0x01000000;
+ run_write(sc, RT3070_LDO_CFG0, tmp);
+
+ sc->rf24_20mhz = 0x1f;
+ sc->rf24_40mhz = 0x2f;
+
+ /* Save default BBP registers 25 and 26 values. */
+ run_bbp_read(sc, 25, &sc->bbp25);
+ run_bbp_read(sc, 26, &sc->bbp26);
+
+ run_read(sc, RT3070_OPT_14, &tmp);
+ run_write(sc, RT3070_OPT_14, tmp | 1);
+}
+
+static void
+run_rt5390_rf_init(struct run_softc *sc)
+{
+ uint32_t tmp;
+ uint8_t rf;
+ u_int i;
+
+ /* Toggle RF R2 to initiate calibration. */
+ if (sc->mac_ver == 0x5390) {
+ run_rt3070_rf_read(sc, 2, &rf);
+ run_rt3070_rf_write(sc, 2, rf | RT5390_RESCAL);
+ run_delay(sc, 10);
+ run_rt3070_rf_write(sc, 2, rf & ~RT5390_RESCAL);
+ } else {
+ run_rt3070_rf_write(sc, 2, RT5390_RESCAL);
+ run_delay(sc, 10);
+ }
+
+ /* Initialize RF registers to default value. */
+ if (sc->mac_ver == 0x5592) {
+ for (i = 0; i < nitems(rt5592_def_rf); i++) {
+ run_rt3070_rf_write(sc, rt5592_def_rf[i].reg,
+ rt5592_def_rf[i].val);
+ }
+ /* Initialize RF frequency offset. */
+ run_adjust_freq_offset(sc);
+ } else if (sc->mac_ver == 0x5392) {
+ for (i = 0; i < nitems(rt5392_def_rf); i++) {
+ run_rt3070_rf_write(sc, rt5392_def_rf[i].reg,
+ rt5392_def_rf[i].val);
+ }
+ if (sc->mac_rev >= 0x0223) {
+ run_rt3070_rf_write(sc, 23, 0x0f);
+ run_rt3070_rf_write(sc, 24, 0x3e);
+ run_rt3070_rf_write(sc, 51, 0x32);
+ run_rt3070_rf_write(sc, 53, 0x22);
+ run_rt3070_rf_write(sc, 56, 0xc1);
+ run_rt3070_rf_write(sc, 59, 0x0f);
+ }
+ } else {
+ for (i = 0; i < nitems(rt5390_def_rf); i++) {
+ run_rt3070_rf_write(sc, rt5390_def_rf[i].reg,
+ rt5390_def_rf[i].val);
+ }
+ if (sc->mac_rev >= 0x0502) {
+ run_rt3070_rf_write(sc, 6, 0xe0);
+ run_rt3070_rf_write(sc, 25, 0x80);
+ run_rt3070_rf_write(sc, 46, 0x73);
+ run_rt3070_rf_write(sc, 53, 0x00);
+ run_rt3070_rf_write(sc, 56, 0x42);
+ run_rt3070_rf_write(sc, 61, 0xd1);
+ }
+ }
+
+ sc->rf24_20mhz = 0x1f; /* default value */
+ sc->rf24_40mhz = (sc->mac_ver == 0x5592) ? 0 : 0x2f;
+
+ if (sc->mac_rev < 0x0211)
+ run_rt3070_rf_write(sc, 27, 0x3);
+
+ run_read(sc, RT3070_OPT_14, &tmp);
+ run_write(sc, RT3070_OPT_14, tmp | 1);
+}
+
+static int
+run_rt3070_filter_calib(struct run_softc *sc, uint8_t init, uint8_t target,
+ uint8_t *val)
+{
+ uint8_t rf22, rf24;
+ uint8_t bbp55_pb, bbp55_sb, delta;
+ int ntries;
+
+ /* program filter */
+ run_rt3070_rf_read(sc, 24, &rf24);
+ rf24 = (rf24 & 0xc0) | init; /* initial filter value */
+ run_rt3070_rf_write(sc, 24, rf24);
+
+ /* enable baseband loopback mode */
+ run_rt3070_rf_read(sc, 22, &rf22);
+ run_rt3070_rf_write(sc, 22, rf22 | 0x01);
+
+ /* set power and frequency of passband test tone */
+ run_bbp_write(sc, 24, 0x00);
+ for (ntries = 0; ntries < 100; ntries++) {
+ /* transmit test tone */
+ run_bbp_write(sc, 25, 0x90);
+ run_delay(sc, 10);
+ /* read received power */
+ run_bbp_read(sc, 55, &bbp55_pb);
+ if (bbp55_pb != 0)
+ break;
+ }
+ if (ntries == 100)
+ return (ETIMEDOUT);
+
+ /* set power and frequency of stopband test tone */
+ run_bbp_write(sc, 24, 0x06);
+ for (ntries = 0; ntries < 100; ntries++) {
+ /* transmit test tone */
+ run_bbp_write(sc, 25, 0x90);
+ run_delay(sc, 10);
+ /* read received power */
+ run_bbp_read(sc, 55, &bbp55_sb);
+
+ delta = bbp55_pb - bbp55_sb;
+ if (delta > target)
+ break;
+
+ /* reprogram filter */
+ rf24++;
+ run_rt3070_rf_write(sc, 24, rf24);
+ }
+ if (ntries < 100) {
+ if (rf24 != init)
+ rf24--; /* backtrack */
+ *val = rf24;
+ run_rt3070_rf_write(sc, 24, rf24);
+ }
+
+ /* restore initial state */
+ run_bbp_write(sc, 24, 0x00);
+
+ /* disable baseband loopback mode */
+ run_rt3070_rf_read(sc, 22, &rf22);
+ run_rt3070_rf_write(sc, 22, rf22 & ~0x01);
+
+ return (0);
+}
+
+static void
+run_rt3070_rf_setup(struct run_softc *sc)
+{
+ uint8_t bbp, rf;
+ int i;
+
+ if (sc->mac_ver == 0x3572) {
+ /* enable DC filter */
+ if (sc->mac_rev >= 0x0201)
+ run_bbp_write(sc, 103, 0xc0);
+
+ run_bbp_read(sc, 138, &bbp);
+ if (sc->ntxchains == 1)
+ bbp |= 0x20; /* turn off DAC1 */
+ if (sc->nrxchains == 1)
+ bbp &= ~0x02; /* turn off ADC1 */
+ run_bbp_write(sc, 138, bbp);
+
+ if (sc->mac_rev >= 0x0211) {
+ /* improve power consumption */
+ run_bbp_read(sc, 31, &bbp);
+ run_bbp_write(sc, 31, bbp & ~0x03);
+ }
+
+ run_rt3070_rf_read(sc, 16, &rf);
+ rf = (rf & ~0x07) | sc->txmixgain_2ghz;
+ run_rt3070_rf_write(sc, 16, rf);
+
+ } else if (sc->mac_ver == 0x3071) {
+ if (sc->mac_rev >= 0x0211) {
+ /* enable DC filter */
+ run_bbp_write(sc, 103, 0xc0);
+
+ /* improve power consumption */
+ run_bbp_read(sc, 31, &bbp);
+ run_bbp_write(sc, 31, bbp & ~0x03);
+ }
+
+ run_bbp_read(sc, 138, &bbp);
+ if (sc->ntxchains == 1)
+ bbp |= 0x20; /* turn off DAC1 */
+ if (sc->nrxchains == 1)
+ bbp &= ~0x02; /* turn off ADC1 */
+ run_bbp_write(sc, 138, bbp);
+
+ run_write(sc, RT2860_TX_SW_CFG1, 0);
+ if (sc->mac_rev < 0x0211) {
+ run_write(sc, RT2860_TX_SW_CFG2,
+ sc->patch_dac ? 0x2c : 0x0f);
+ } else
+ run_write(sc, RT2860_TX_SW_CFG2, 0);
+
+ } else if (sc->mac_ver == 0x3070) {
+ if (sc->mac_rev >= 0x0201) {
+ /* enable DC filter */
+ run_bbp_write(sc, 103, 0xc0);
+
+ /* improve power consumption */
+ run_bbp_read(sc, 31, &bbp);
+ run_bbp_write(sc, 31, bbp & ~0x03);
+ }
+
+ if (sc->mac_rev < 0x0201) {
+ run_write(sc, RT2860_TX_SW_CFG1, 0);
+ run_write(sc, RT2860_TX_SW_CFG2, 0x2c);
+ } else
+ run_write(sc, RT2860_TX_SW_CFG2, 0);
+ }
+
+ /* initialize RF registers from ROM for >=RT3071*/
+ if (sc->mac_ver >= 0x3071) {
+ for (i = 0; i < 10; i++) {
+ if (sc->rf[i].reg == 0 || sc->rf[i].reg == 0xff)
+ continue;
+ run_rt3070_rf_write(sc, sc->rf[i].reg, sc->rf[i].val);
+ }
+ }
+}
+
+static void
+run_rt3593_rf_setup(struct run_softc *sc)
+{
+ uint8_t bbp, rf;
+
+ if (sc->mac_rev >= 0x0211) {
+ /* Enable DC filter. */
+ run_bbp_write(sc, 103, 0xc0);
+ }
+ run_write(sc, RT2860_TX_SW_CFG1, 0);
+ if (sc->mac_rev < 0x0211) {
+ run_write(sc, RT2860_TX_SW_CFG2,
+ sc->patch_dac ? 0x2c : 0x0f);
+ } else
+ run_write(sc, RT2860_TX_SW_CFG2, 0);
+
+ run_rt3070_rf_read(sc, 50, &rf);
+ run_rt3070_rf_write(sc, 50, rf & ~RT3593_TX_LO2);
+
+ run_rt3070_rf_read(sc, 51, &rf);
+ rf = (rf & ~(RT3593_TX_LO1 | 0x0c)) |
+ ((sc->txmixgain_2ghz & 0x07) << 2);
+ run_rt3070_rf_write(sc, 51, rf);
+
+ run_rt3070_rf_read(sc, 38, &rf);
+ run_rt3070_rf_write(sc, 38, rf & ~RT5390_RX_LO1);
+
+ run_rt3070_rf_read(sc, 39, &rf);
+ run_rt3070_rf_write(sc, 39, rf & ~RT5390_RX_LO2);
+
+ run_rt3070_rf_read(sc, 1, &rf);
+ run_rt3070_rf_write(sc, 1, rf & ~(RT3070_RF_BLOCK | RT3070_PLL_PD));
+
+ run_rt3070_rf_read(sc, 30, &rf);
+ rf = (rf & ~0x18) | 0x10;
+ run_rt3070_rf_write(sc, 30, rf);
+
+ /* Apply maximum likelihood detection for 2 stream case. */
+ run_bbp_read(sc, 105, &bbp);
+ if (sc->nrxchains > 1)
+ run_bbp_write(sc, 105, bbp | RT5390_MLD);
+
+ /* Avoid data lost and CRC error. */
+ run_bbp_read(sc, 4, &bbp);
+ run_bbp_write(sc, 4, bbp | RT5390_MAC_IF_CTRL);
+
+ run_bbp_write(sc, 92, 0x02);
+ run_bbp_write(sc, 82, 0x82);
+ run_bbp_write(sc, 106, 0x05);
+ run_bbp_write(sc, 104, 0x92);
+ run_bbp_write(sc, 88, 0x90);
+ run_bbp_write(sc, 148, 0xc8);
+ run_bbp_write(sc, 47, 0x48);
+ run_bbp_write(sc, 120, 0x50);
+
+ run_bbp_write(sc, 163, 0x9d);
+
+ /* SNR mapping. */
+ run_bbp_write(sc, 142, 0x06);
+ run_bbp_write(sc, 143, 0xa0);
+ run_bbp_write(sc, 142, 0x07);
+ run_bbp_write(sc, 143, 0xa1);
+ run_bbp_write(sc, 142, 0x08);
+ run_bbp_write(sc, 143, 0xa2);
+
+ run_bbp_write(sc, 31, 0x08);
+ run_bbp_write(sc, 68, 0x0b);
+ run_bbp_write(sc, 105, 0x04);
+}
+
+static void
+run_rt5390_rf_setup(struct run_softc *sc)
+{
+ uint8_t bbp, rf;
+
+ if (sc->mac_rev >= 0x0211) {
+ /* Enable DC filter. */
+ run_bbp_write(sc, 103, 0xc0);
+
+ if (sc->mac_ver != 0x5592) {
+ /* Improve power consumption. */
+ run_bbp_read(sc, 31, &bbp);
+ run_bbp_write(sc, 31, bbp & ~0x03);
+ }
+ }
+
+ run_bbp_read(sc, 138, &bbp);
+ if (sc->ntxchains == 1)
+ bbp |= 0x20; /* turn off DAC1 */
+ if (sc->nrxchains == 1)
+ bbp &= ~0x02; /* turn off ADC1 */
+ run_bbp_write(sc, 138, bbp);
+
+ run_rt3070_rf_read(sc, 38, &rf);
+ run_rt3070_rf_write(sc, 38, rf & ~RT5390_RX_LO1);
+
+ run_rt3070_rf_read(sc, 39, &rf);
+ run_rt3070_rf_write(sc, 39, rf & ~RT5390_RX_LO2);
+
+ /* Avoid data lost and CRC error. */
+ run_bbp_read(sc, 4, &bbp);
+ run_bbp_write(sc, 4, bbp | RT5390_MAC_IF_CTRL);
+
+ run_rt3070_rf_read(sc, 30, &rf);
+ rf = (rf & ~0x18) | 0x10;
+ run_rt3070_rf_write(sc, 30, rf);
+
+ if (sc->mac_ver != 0x5592) {
+ run_write(sc, RT2860_TX_SW_CFG1, 0);
+ if (sc->mac_rev < 0x0211) {
+ run_write(sc, RT2860_TX_SW_CFG2,
+ sc->patch_dac ? 0x2c : 0x0f);
+ } else
+ run_write(sc, RT2860_TX_SW_CFG2, 0);
+ }
+}
+
+static int
+run_txrx_enable(struct run_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t tmp;
+ int error, ntries;
+
+ run_write(sc, RT2860_MAC_SYS_CTRL, RT2860_MAC_TX_EN);
+ for (ntries = 0; ntries < 200; ntries++) {
+ if ((error = run_read(sc, RT2860_WPDMA_GLO_CFG, &tmp)) != 0)
+ return (error);
+ if ((tmp & (RT2860_TX_DMA_BUSY | RT2860_RX_DMA_BUSY)) == 0)
+ break;
+ run_delay(sc, 50);
+ }
+ if (ntries == 200)
+ return (ETIMEDOUT);
+
+ run_delay(sc, 50);
+
+ tmp |= RT2860_RX_DMA_EN | RT2860_TX_DMA_EN | RT2860_TX_WB_DDONE;
+ run_write(sc, RT2860_WPDMA_GLO_CFG, tmp);
+
+ /* enable Rx bulk aggregation (set timeout and limit) */
+ tmp = RT2860_USB_TX_EN | RT2860_USB_RX_EN | RT2860_USB_RX_AGG_EN |
+ RT2860_USB_RX_AGG_TO(128) | RT2860_USB_RX_AGG_LMT(2);
+ run_write(sc, RT2860_USB_DMA_CFG, tmp);
+
+ /* set Rx filter */
+ tmp = RT2860_DROP_CRC_ERR | RT2860_DROP_PHY_ERR;
+ if (ic->ic_opmode != IEEE80211_M_MONITOR) {
+ tmp |= RT2860_DROP_UC_NOME | RT2860_DROP_DUPL |
+ RT2860_DROP_CTS | RT2860_DROP_BA | RT2860_DROP_ACK |
+ RT2860_DROP_VER_ERR | RT2860_DROP_CTRL_RSV |
+ RT2860_DROP_CFACK | RT2860_DROP_CFEND;
+ if (ic->ic_opmode == IEEE80211_M_STA)
+ tmp |= RT2860_DROP_RTS | RT2860_DROP_PSPOLL;
+ }
+ run_write(sc, RT2860_RX_FILTR_CFG, tmp);
+
+ run_write(sc, RT2860_MAC_SYS_CTRL,
+ RT2860_MAC_RX_EN | RT2860_MAC_TX_EN);
+
+ return (0);
+}
+
+static void
+run_adjust_freq_offset(struct run_softc *sc)
+{
+ uint8_t rf, tmp;
+
+ run_rt3070_rf_read(sc, 17, &rf);
+ tmp = rf;
+ rf = (rf & ~0x7f) | (sc->freq & 0x7f);
+ rf = MIN(rf, 0x5f);
+
+ if (tmp != rf)
+ run_mcu_cmd(sc, 0x74, (tmp << 8 ) | rf);
+}
+
+static void
+run_init_locked(struct run_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ uint32_t tmp;
+ uint8_t bbp1, bbp3;
+ int i;
+ int ridx;
+ int ntries;
+
+ if (ic->ic_nrunning > 1)
+ return;
+
+ run_stop(sc);
+
+ if (run_load_microcode(sc) != 0) {
+ device_printf(sc->sc_dev, "could not load 8051 microcode\n");
+ goto fail;
+ }
+
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (run_read(sc, RT2860_ASIC_VER_ID, &tmp) != 0)
+ goto fail;
+ if (tmp != 0 && tmp != 0xffffffff)
+ break;
+ run_delay(sc, 10);
+ }
+ if (ntries == 100)
+ goto fail;
+
+ for (i = 0; i != RUN_EP_QUEUES; i++)
+ run_setup_tx_list(sc, &sc->sc_epq[i]);
+
+ run_set_macaddr(sc, vap ? vap->iv_myaddr : ic->ic_macaddr);
+
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (run_read(sc, RT2860_WPDMA_GLO_CFG, &tmp) != 0)
+ goto fail;
+ if ((tmp & (RT2860_TX_DMA_BUSY | RT2860_RX_DMA_BUSY)) == 0)
+ break;
+ run_delay(sc, 10);
+ }
+ if (ntries == 100) {
+ device_printf(sc->sc_dev, "timeout waiting for DMA engine\n");
+ goto fail;
+ }
+ tmp &= 0xff0;
+ tmp |= RT2860_TX_WB_DDONE;
+ run_write(sc, RT2860_WPDMA_GLO_CFG, tmp);
+
+ /* turn off PME_OEN to solve high-current issue */
+ run_read(sc, RT2860_SYS_CTRL, &tmp);
+ run_write(sc, RT2860_SYS_CTRL, tmp & ~RT2860_PME_OEN);
+
+ run_write(sc, RT2860_MAC_SYS_CTRL,
+ RT2860_BBP_HRST | RT2860_MAC_SRST);
+ run_write(sc, RT2860_USB_DMA_CFG, 0);
+
+ if (run_reset(sc) != 0) {
+ device_printf(sc->sc_dev, "could not reset chipset\n");
+ goto fail;
+ }
+
+ run_write(sc, RT2860_MAC_SYS_CTRL, 0);
+
+ /* init Tx power for all Tx rates (from EEPROM) */
+ for (ridx = 0; ridx < 5; ridx++) {
+ if (sc->txpow20mhz[ridx] == 0xffffffff)
+ continue;
+ run_write(sc, RT2860_TX_PWR_CFG(ridx), sc->txpow20mhz[ridx]);
+ }
+
+ for (i = 0; i < nitems(rt2870_def_mac); i++)
+ run_write(sc, rt2870_def_mac[i].reg, rt2870_def_mac[i].val);
+ run_write(sc, RT2860_WMM_AIFSN_CFG, 0x00002273);
+ run_write(sc, RT2860_WMM_CWMIN_CFG, 0x00002344);
+ run_write(sc, RT2860_WMM_CWMAX_CFG, 0x000034aa);
+
+ if (sc->mac_ver >= 0x5390) {
+ run_write(sc, RT2860_TX_SW_CFG0,
+ 4 << RT2860_DLY_PAPE_EN_SHIFT | 4);
+ if (sc->mac_ver >= 0x5392) {
+ run_write(sc, RT2860_MAX_LEN_CFG, 0x00002fff);
+ if (sc->mac_ver == 0x5592) {
+ run_write(sc, RT2860_HT_FBK_CFG1, 0xedcba980);
+ run_write(sc, RT2860_TXOP_HLDR_ET, 0x00000082);
+ } else {
+ run_write(sc, RT2860_HT_FBK_CFG1, 0xedcb4980);
+ run_write(sc, RT2860_LG_FBK_CFG0, 0xedcba322);
+ }
+ }
+ } else if (sc->mac_ver == 0x3593) {
+ run_write(sc, RT2860_TX_SW_CFG0,
+ 4 << RT2860_DLY_PAPE_EN_SHIFT | 2);
+ } else if (sc->mac_ver >= 0x3070) {
+ /* set delay of PA_PE assertion to 1us (unit of 0.25us) */
+ run_write(sc, RT2860_TX_SW_CFG0,
+ 4 << RT2860_DLY_PAPE_EN_SHIFT);
+ }
+
+ /* wait while MAC is busy */
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (run_read(sc, RT2860_MAC_STATUS_REG, &tmp) != 0)
+ goto fail;
+ if (!(tmp & (RT2860_RX_STATUS_BUSY | RT2860_TX_STATUS_BUSY)))
+ break;
+ run_delay(sc, 10);
+ }
+ if (ntries == 100)
+ goto fail;
+
+ /* clear Host to MCU mailbox */
+ run_write(sc, RT2860_H2M_BBPAGENT, 0);
+ run_write(sc, RT2860_H2M_MAILBOX, 0);
+ run_delay(sc, 10);
+
+ if (run_bbp_init(sc) != 0) {
+ device_printf(sc->sc_dev, "could not initialize BBP\n");
+ goto fail;
+ }
+
+ /* abort TSF synchronization */
+ run_disable_tsf(sc);
+
+ /* clear RX WCID search table */
+ run_set_region_4(sc, RT2860_WCID_ENTRY(0), 0, 512);
+ /* clear WCID attribute table */
+ run_set_region_4(sc, RT2860_WCID_ATTR(0), 0, 8 * 32);
+
+ /* hostapd sets a key before init. So, don't clear it. */
+ if (sc->cmdq_key_set != RUN_CMDQ_GO) {
+ /* clear shared key table */
+ run_set_region_4(sc, RT2860_SKEY(0, 0), 0, 8 * 32);
+ /* clear shared key mode */
+ run_set_region_4(sc, RT2860_SKEY_MODE_0_7, 0, 4);
+ }
+
+ run_read(sc, RT2860_US_CYC_CNT, &tmp);
+ tmp = (tmp & ~0xff) | 0x1e;
+ run_write(sc, RT2860_US_CYC_CNT, tmp);
+
+ if (sc->mac_rev != 0x0101)
+ run_write(sc, RT2860_TXOP_CTRL_CFG, 0x0000583f);
+
+ run_write(sc, RT2860_WMM_TXOP0_CFG, 0);
+ run_write(sc, RT2860_WMM_TXOP1_CFG, 48 << 16 | 96);
+
+ /* write vendor-specific BBP values (from EEPROM) */
+ if (sc->mac_ver < 0x3593) {
+ for (i = 0; i < 10; i++) {
+ if (sc->bbp[i].reg == 0 || sc->bbp[i].reg == 0xff)
+ continue;
+ run_bbp_write(sc, sc->bbp[i].reg, sc->bbp[i].val);
+ }
+ }
+
+ /* select Main antenna for 1T1R devices */
+ if (sc->rf_rev == RT3070_RF_3020 || sc->rf_rev == RT5390_RF_5370)
+ run_set_rx_antenna(sc, 0);
+
+ /* send LEDs operating mode to microcontroller */
+ (void)run_mcu_cmd(sc, RT2860_MCU_CMD_LED1, sc->led[0]);
+ (void)run_mcu_cmd(sc, RT2860_MCU_CMD_LED2, sc->led[1]);
+ (void)run_mcu_cmd(sc, RT2860_MCU_CMD_LED3, sc->led[2]);
+
+ if (sc->mac_ver >= 0x5390)
+ run_rt5390_rf_init(sc);
+ else if (sc->mac_ver == 0x3593)
+ run_rt3593_rf_init(sc);
+ else if (sc->mac_ver >= 0x3070)
+ run_rt3070_rf_init(sc);
+
+ /* disable non-existing Rx chains */
+ run_bbp_read(sc, 3, &bbp3);
+ bbp3 &= ~(1 << 3 | 1 << 4);
+ if (sc->nrxchains == 2)
+ bbp3 |= 1 << 3;
+ else if (sc->nrxchains == 3)
+ bbp3 |= 1 << 4;
+ run_bbp_write(sc, 3, bbp3);
+
+ /* disable non-existing Tx chains */
+ run_bbp_read(sc, 1, &bbp1);
+ if (sc->ntxchains == 1)
+ bbp1 &= ~(1 << 3 | 1 << 4);
+ run_bbp_write(sc, 1, bbp1);
+
+ if (sc->mac_ver >= 0x5390)
+ run_rt5390_rf_setup(sc);
+ else if (sc->mac_ver == 0x3593)
+ run_rt3593_rf_setup(sc);
+ else if (sc->mac_ver >= 0x3070)
+ run_rt3070_rf_setup(sc);
+
+ /* select default channel */
+ run_set_chan(sc, ic->ic_curchan);
+
+ /* setup initial protection mode */
+ run_updateprot_cb(ic);
+
+ /* turn radio LED on */
+ run_set_leds(sc, RT2860_LED_RADIO);
+
+ /* Set up AUTO_RSP_CFG register for auto response */
+ run_write(sc, RT2860_AUTO_RSP_CFG, RT2860_AUTO_RSP_EN |
+ RT2860_BAC_ACKPOLICY_EN | RT2860_CTS_40M_MODE_EN);
+
+ sc->sc_flags |= RUN_RUNNING;
+ sc->cmdq_run = RUN_CMDQ_GO;
+
+ for (i = 0; i != RUN_N_XFER; i++)
+ usbd_xfer_set_stall(sc->sc_xfer[i]);
+
+ usbd_transfer_start(sc->sc_xfer[RUN_BULK_RX]);
+
+ if (run_txrx_enable(sc) != 0)
+ goto fail;
+
+ return;
+
+fail:
+ run_stop(sc);
+}
+
+static void
+run_stop(void *arg)
+{
+ struct run_softc *sc = (struct run_softc *)arg;
+ uint32_t tmp;
+ int i;
+ int ntries;
+
+ RUN_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (sc->sc_flags & RUN_RUNNING)
+ run_set_leds(sc, 0); /* turn all LEDs off */
+
+ sc->sc_flags &= ~RUN_RUNNING;
+
+ sc->ratectl_run = RUN_RATECTL_OFF;
+ sc->cmdq_run = sc->cmdq_key_set;
+
+ RUN_UNLOCK(sc);
+
+ for(i = 0; i < RUN_N_XFER; i++)
+ usbd_transfer_drain(sc->sc_xfer[i]);
+
+ RUN_LOCK(sc);
+
+ run_drain_mbufq(sc);
+
+ if (sc->rx_m != NULL) {
+ m_free(sc->rx_m);
+ sc->rx_m = NULL;
+ }
+
+ /* Disable Tx/Rx DMA. */
+ if (run_read(sc, RT2860_WPDMA_GLO_CFG, &tmp) != 0)
+ return;
+ tmp &= ~(RT2860_RX_DMA_EN | RT2860_TX_DMA_EN);
+ run_write(sc, RT2860_WPDMA_GLO_CFG, tmp);
+
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (run_read(sc, RT2860_WPDMA_GLO_CFG, &tmp) != 0)
+ return;
+ if ((tmp & (RT2860_TX_DMA_BUSY | RT2860_RX_DMA_BUSY)) == 0)
+ break;
+ run_delay(sc, 10);
+ }
+ if (ntries == 100) {
+ device_printf(sc->sc_dev, "timeout waiting for DMA engine\n");
+ return;
+ }
+
+ /* disable Tx/Rx */
+ run_read(sc, RT2860_MAC_SYS_CTRL, &tmp);
+ tmp &= ~(RT2860_MAC_RX_EN | RT2860_MAC_TX_EN);
+ run_write(sc, RT2860_MAC_SYS_CTRL, tmp);
+
+ /* wait for pending Tx to complete */
+ for (ntries = 0; ntries < 100; ntries++) {
+ if (run_read(sc, RT2860_TXRXQ_PCNT, &tmp) != 0) {
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_RESET,
+ "Cannot read Tx queue count\n");
+ break;
+ }
+ if ((tmp & RT2860_TX2Q_PCNT_MASK) == 0) {
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_RESET,
+ "All Tx cleared\n");
+ break;
+ }
+ run_delay(sc, 10);
+ }
+ if (ntries >= 100)
+ RUN_DPRINTF(sc, RUN_DEBUG_XMIT | RUN_DEBUG_RESET,
+ "There are still pending Tx\n");
+ run_delay(sc, 10);
+ run_write(sc, RT2860_USB_DMA_CFG, 0);
+
+ run_write(sc, RT2860_MAC_SYS_CTRL, RT2860_BBP_HRST | RT2860_MAC_SRST);
+ run_write(sc, RT2860_MAC_SYS_CTRL, 0);
+
+ for (i = 0; i != RUN_EP_QUEUES; i++)
+ run_unsetup_tx_list(sc, &sc->sc_epq[i]);
+}
+
+static void
+run_delay(struct run_softc *sc, u_int ms)
+{
+ usb_pause_mtx(mtx_owned(&sc->sc_mtx) ?
+ &sc->sc_mtx : NULL, USB_MS_TO_TICKS(ms));
+}
+
+static void
+run_update_chw(struct ieee80211com *ic)
+{
+
+ printf("%s: TODO\n", __func__);
+}
+
+static int
+run_ampdu_enable(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap)
+{
+
+ /* For now, no A-MPDU TX support in the driver */
+ return (0);
+}
+
+static device_method_t run_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, run_match),
+ DEVMETHOD(device_attach, run_attach),
+ DEVMETHOD(device_detach, run_detach),
+ DEVMETHOD_END
+};
+
+static driver_t run_driver = {
+ .name = "run",
+ .methods = run_methods,
+ .size = sizeof(struct run_softc)
+};
+
+DRIVER_MODULE(run, uhub, run_driver, run_driver_loaded, NULL);
+MODULE_DEPEND(run, wlan, 1, 1, 1);
+MODULE_DEPEND(run, usb, 1, 1, 1);
+MODULE_DEPEND(run, firmware, 1, 1, 1);
+MODULE_VERSION(run, 1);
+USB_PNP_HOST_INFO(run_devs);
diff --git a/sys/dev/usb/wlan/if_runreg.h b/sys/dev/usb/wlan/if_runreg.h
new file mode 100644
index 000000000000..87ea0ba86034
--- /dev/null
+++ b/sys/dev/usb/wlan/if_runreg.h
@@ -0,0 +1,1658 @@
+/* $OpenBSD: rt2860reg.h,v 1.19 2009/05/18 19:25:07 damien Exp $ */
+
+/*-
+ * Copyright (c) 2007
+ * 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.
+ */
+
+#ifndef _IF_RUNREG_H_
+#define _IF_RUNREG_H_
+
+#define RT2860_CONFIG_NO 1
+#define RT2860_IFACE_INDEX 0
+
+#define RT3070_OPT_14 0x0114
+
+/* SCH/DMA registers */
+#define RT2860_INT_STATUS 0x0200
+#define RT2860_INT_MASK 0x0204
+#define RT2860_WPDMA_GLO_CFG 0x0208
+#define RT2860_WPDMA_RST_IDX 0x020c
+#define RT2860_DELAY_INT_CFG 0x0210
+#define RT2860_WMM_AIFSN_CFG 0x0214
+#define RT2860_WMM_CWMIN_CFG 0x0218
+#define RT2860_WMM_CWMAX_CFG 0x021c
+#define RT2860_WMM_TXOP0_CFG 0x0220
+#define RT2860_WMM_TXOP1_CFG 0x0224
+#define RT2860_GPIO_CTRL 0x0228
+#define RT2860_MCU_CMD_REG 0x022c
+#define RT2860_TX_BASE_PTR(qid) (0x0230 + (qid) * 16)
+#define RT2860_TX_MAX_CNT(qid) (0x0234 + (qid) * 16)
+#define RT2860_TX_CTX_IDX(qid) (0x0238 + (qid) * 16)
+#define RT2860_TX_DTX_IDX(qid) (0x023c + (qid) * 16)
+#define RT2860_RX_BASE_PTR 0x0290
+#define RT2860_RX_MAX_CNT 0x0294
+#define RT2860_RX_CALC_IDX 0x0298
+#define RT2860_FS_DRX_IDX 0x029c
+#define RT2860_USB_DMA_CFG 0x02a0 /* RT2870 only */
+#define RT2860_US_CYC_CNT 0x02a4
+
+/* PBF registers */
+#define RT2860_SYS_CTRL 0x0400
+#define RT2860_HOST_CMD 0x0404
+#define RT2860_PBF_CFG 0x0408
+#define RT2860_MAX_PCNT 0x040c
+#define RT2860_BUF_CTRL 0x0410
+#define RT2860_MCU_INT_STA 0x0414
+#define RT2860_MCU_INT_ENA 0x0418
+#define RT2860_TXQ_IO(qid) (0x041c + (qid) * 4)
+#define RT2860_RX0Q_IO 0x0424
+#define RT2860_BCN_OFFSET0 0x042c
+#define RT2860_BCN_OFFSET1 0x0430
+#define RT2860_TXRXQ_STA 0x0434
+#define RT2860_TXRXQ_PCNT 0x0438
+#define RT2860_PBF_DBG 0x043c
+#define RT2860_CAP_CTRL 0x0440
+
+/* RT3070 registers */
+#define RT3070_RF_CSR_CFG 0x0500
+#define RT3070_EFUSE_CTRL 0x0580
+#define RT3070_EFUSE_DATA0 0x0590
+#define RT3070_EFUSE_DATA1 0x0594
+#define RT3070_EFUSE_DATA2 0x0598
+#define RT3070_EFUSE_DATA3 0x059c
+#define RT3070_LDO_CFG0 0x05d4
+#define RT3070_GPIO_SWITCH 0x05dc
+
+/* RT5592 registers */
+#define RT5592_DEBUG_INDEX 0x05e8
+
+/* MAC registers */
+#define RT2860_ASIC_VER_ID 0x1000
+#define RT2860_MAC_SYS_CTRL 0x1004
+#define RT2860_MAC_ADDR_DW0 0x1008
+#define RT2860_MAC_ADDR_DW1 0x100c
+#define RT2860_MAC_BSSID_DW0 0x1010
+#define RT2860_MAC_BSSID_DW1 0x1014
+#define RT2860_MAX_LEN_CFG 0x1018
+#define RT2860_BBP_CSR_CFG 0x101c
+#define RT2860_RF_CSR_CFG0 0x1020
+#define RT2860_RF_CSR_CFG1 0x1024
+#define RT2860_RF_CSR_CFG2 0x1028
+#define RT2860_LED_CFG 0x102c
+
+/* undocumented registers */
+#define RT2860_DEBUG 0x10f4
+
+/* MAC Timing control registers */
+#define RT2860_XIFS_TIME_CFG 0x1100
+#define RT2860_BKOFF_SLOT_CFG 0x1104
+#define RT2860_NAV_TIME_CFG 0x1108
+#define RT2860_CH_TIME_CFG 0x110c
+#define RT2860_PBF_LIFE_TIMER 0x1110
+#define RT2860_BCN_TIME_CFG 0x1114
+#define RT2860_TBTT_SYNC_CFG 0x1118
+#define RT2860_TSF_TIMER_DW0 0x111c
+#define RT2860_TSF_TIMER_DW1 0x1120
+#define RT2860_TBTT_TIMER 0x1124
+#define RT2860_INT_TIMER_CFG 0x1128
+#define RT2860_INT_TIMER_EN 0x112c
+#define RT2860_CH_IDLE_TIME 0x1130
+
+/* MAC Power Save configuration registers */
+#define RT2860_MAC_STATUS_REG 0x1200
+#define RT2860_PWR_PIN_CFG 0x1204
+#define RT2860_AUTO_WAKEUP_CFG 0x1208
+
+/* MAC TX configuration registers */
+#define RT2860_EDCA_AC_CFG(aci) (0x1300 + (aci) * 4)
+#define RT2860_EDCA_TID_AC_MAP 0x1310
+#define RT2860_TX_PWR_CFG(ridx) (0x1314 + (ridx) * 4)
+#define RT2860_TX_PIN_CFG 0x1328
+#define RT2860_TX_BAND_CFG 0x132c
+#define RT2860_TX_SW_CFG0 0x1330
+#define RT2860_TX_SW_CFG1 0x1334
+#define RT2860_TX_SW_CFG2 0x1338
+#define RT2860_TXOP_THRES_CFG 0x133c
+#define RT2860_TXOP_CTRL_CFG 0x1340
+#define RT2860_TX_RTS_CFG 0x1344
+#define RT2860_TX_TIMEOUT_CFG 0x1348
+#define RT2860_TX_RTY_CFG 0x134c
+#define RT2860_TX_LINK_CFG 0x1350
+#define RT2860_HT_FBK_CFG0 0x1354
+#define RT2860_HT_FBK_CFG1 0x1358
+#define RT2860_LG_FBK_CFG0 0x135c
+#define RT2860_LG_FBK_CFG1 0x1360
+#define RT2860_CCK_PROT_CFG 0x1364
+#define RT2860_OFDM_PROT_CFG 0x1368
+#define RT2860_MM20_PROT_CFG 0x136c
+#define RT2860_MM40_PROT_CFG 0x1370
+#define RT2860_GF20_PROT_CFG 0x1374
+#define RT2860_GF40_PROT_CFG 0x1378
+#define RT2860_EXP_CTS_TIME 0x137c
+#define RT2860_EXP_ACK_TIME 0x1380
+
+/* MAC RX configuration registers */
+#define RT2860_RX_FILTR_CFG 0x1400
+#define RT2860_AUTO_RSP_CFG 0x1404
+#define RT2860_LEGACY_BASIC_RATE 0x1408
+#define RT2860_HT_BASIC_RATE 0x140c
+#define RT2860_HT_CTRL_CFG 0x1410
+#define RT2860_SIFS_COST_CFG 0x1414
+#define RT2860_RX_PARSER_CFG 0x1418
+
+/* MAC Security configuration registers */
+#define RT2860_TX_SEC_CNT0 0x1500
+#define RT2860_RX_SEC_CNT0 0x1504
+#define RT2860_CCMP_FC_MUTE 0x1508
+
+/* MAC HCCA/PSMP configuration registers */
+#define RT2860_TXOP_HLDR_ADDR0 0x1600
+#define RT2860_TXOP_HLDR_ADDR1 0x1604
+#define RT2860_TXOP_HLDR_ET 0x1608
+#define RT2860_QOS_CFPOLL_RA_DW0 0x160c
+#define RT2860_QOS_CFPOLL_A1_DW1 0x1610
+#define RT2860_QOS_CFPOLL_QC 0x1614
+
+/* MAC Statistics Counters */
+#define RT2860_RX_STA_CNT0 0x1700
+#define RT2860_RX_STA_CNT1 0x1704
+#define RT2860_RX_STA_CNT2 0x1708
+#define RT2860_TX_STA_CNT0 0x170c
+#define RT2860_TX_STA_CNT1 0x1710
+#define RT2860_TX_STA_CNT2 0x1714
+#define RT2860_TX_STAT_FIFO 0x1718
+
+/* RX WCID search table */
+#define RT2860_WCID_ENTRY(wcid) (0x1800 + (wcid) * 8)
+
+#define RT2860_FW_BASE 0x2000
+#define RT2870_FW_BASE 0x3000
+
+/* Pair-wise key table */
+#define RT2860_PKEY(wcid) (0x4000 + (wcid) * 32)
+
+/* IV/EIV table */
+#define RT2860_IVEIV(wcid) (0x6000 + (wcid) * 8)
+
+/* WCID attribute table */
+#define RT2860_WCID_ATTR(wcid) (0x6800 + (wcid) * 4)
+
+/* Shared Key Table */
+#define RT2860_SKEY(vap, kidx) (0x6c00 + (vap) * 128 + (kidx) * 32)
+
+/* Shared Key Mode */
+#define RT2860_SKEY_MODE_0_7 0x7000
+#define RT2860_SKEY_MODE_8_15 0x7004
+#define RT2860_SKEY_MODE_16_23 0x7008
+#define RT2860_SKEY_MODE_24_31 0x700c
+
+/* Shared Memory between MCU and host */
+#define RT2860_H2M_MAILBOX 0x7010
+#define RT2860_H2M_MAILBOX_CID 0x7014
+#define RT2860_H2M_MAILBOX_STATUS 0x701c
+#define RT2860_H2M_INTSRC 0x7024
+#define RT2860_H2M_BBPAGENT 0x7028
+#define RT2860_BCN_BASE(vap) (0x7800 + (vap) * 512)
+
+/* possible flags for register RT2860_PCI_EECTRL */
+#define RT2860_C (1 << 0)
+#define RT2860_S (1 << 1)
+#define RT2860_D (1 << 2)
+#define RT2860_SHIFT_D 2
+#define RT2860_Q (1 << 3)
+#define RT2860_SHIFT_Q 3
+
+/* possible flags for registers INT_STATUS/INT_MASK */
+#define RT2860_TX_COHERENT (1 << 17)
+#define RT2860_RX_COHERENT (1 << 16)
+#define RT2860_MAC_INT_4 (1 << 15)
+#define RT2860_MAC_INT_3 (1 << 14)
+#define RT2860_MAC_INT_2 (1 << 13)
+#define RT2860_MAC_INT_1 (1 << 12)
+#define RT2860_MAC_INT_0 (1 << 11)
+#define RT2860_TX_RX_COHERENT (1 << 10)
+#define RT2860_MCU_CMD_INT (1 << 9)
+#define RT2860_TX_DONE_INT5 (1 << 8)
+#define RT2860_TX_DONE_INT4 (1 << 7)
+#define RT2860_TX_DONE_INT3 (1 << 6)
+#define RT2860_TX_DONE_INT2 (1 << 5)
+#define RT2860_TX_DONE_INT1 (1 << 4)
+#define RT2860_TX_DONE_INT0 (1 << 3)
+#define RT2860_RX_DONE_INT (1 << 2)
+#define RT2860_TX_DLY_INT (1 << 1)
+#define RT2860_RX_DLY_INT (1 << 0)
+
+/* possible flags for register WPDMA_GLO_CFG */
+#define RT2860_HDR_SEG_LEN_SHIFT 8
+#define RT2860_BIG_ENDIAN (1 << 7)
+#define RT2860_TX_WB_DDONE (1 << 6)
+#define RT2860_WPDMA_BT_SIZE_SHIFT 4
+#define RT2860_WPDMA_BT_SIZE16 0
+#define RT2860_WPDMA_BT_SIZE32 1
+#define RT2860_WPDMA_BT_SIZE64 2
+#define RT2860_WPDMA_BT_SIZE128 3
+#define RT2860_RX_DMA_BUSY (1 << 3)
+#define RT2860_RX_DMA_EN (1 << 2)
+#define RT2860_TX_DMA_BUSY (1 << 1)
+#define RT2860_TX_DMA_EN (1 << 0)
+
+/* possible flags for register DELAY_INT_CFG */
+#define RT2860_TXDLY_INT_EN (1U << 31)
+#define RT2860_TXMAX_PINT_SHIFT 24
+#define RT2860_TXMAX_PTIME_SHIFT 16
+#define RT2860_RXDLY_INT_EN (1 << 15)
+#define RT2860_RXMAX_PINT_SHIFT 8
+#define RT2860_RXMAX_PTIME_SHIFT 0
+
+/* possible flags for register GPIO_CTRL */
+#define RT2860_GPIO_D_SHIFT 8
+#define RT2860_GPIO_O_SHIFT 0
+
+/* possible flags for register USB_DMA_CFG */
+#define RT2860_USB_TX_BUSY (1U << 31)
+#define RT2860_USB_RX_BUSY (1 << 30)
+#define RT2860_USB_EPOUT_VLD_SHIFT 24
+#define RT2860_USB_TX_EN (1 << 23)
+#define RT2860_USB_RX_EN (1 << 22)
+#define RT2860_USB_RX_AGG_EN (1 << 21)
+#define RT2860_USB_TXOP_HALT (1 << 20)
+#define RT2860_USB_TX_CLEAR (1 << 19)
+#define RT2860_USB_PHY_WD_EN (1 << 16)
+#define RT2860_USB_RX_AGG_LMT(x) ((x) << 8) /* in unit of 1KB */
+#define RT2860_USB_RX_AGG_TO(x) ((x) & 0xff) /* in unit of 33ns */
+
+/* possible flags for register US_CYC_CNT */
+#define RT2860_TEST_EN (1 << 24)
+#define RT2860_TEST_SEL_SHIFT 16
+#define RT2860_BT_MODE_EN (1 << 8)
+#define RT2860_US_CYC_CNT_SHIFT 0
+
+/* possible flags for register SYS_CTRL */
+#define RT2860_HST_PM_SEL (1 << 16)
+#define RT2860_CAP_MODE (1 << 14)
+#define RT2860_PME_OEN (1 << 13)
+#define RT2860_CLKSELECT (1 << 12)
+#define RT2860_PBF_CLK_EN (1 << 11)
+#define RT2860_MAC_CLK_EN (1 << 10)
+#define RT2860_DMA_CLK_EN (1 << 9)
+#define RT2860_MCU_READY (1 << 7)
+#define RT2860_ASY_RESET (1 << 4)
+#define RT2860_PBF_RESET (1 << 3)
+#define RT2860_MAC_RESET (1 << 2)
+#define RT2860_DMA_RESET (1 << 1)
+#define RT2860_MCU_RESET (1 << 0)
+
+/* possible values for register HOST_CMD */
+#define RT2860_MCU_CMD_SLEEP 0x30
+#define RT2860_MCU_CMD_WAKEUP 0x31
+#define RT2860_MCU_CMD_LEDS 0x50
+#define RT2860_MCU_CMD_LED_RSSI 0x51
+#define RT2860_MCU_CMD_LED1 0x52
+#define RT2860_MCU_CMD_LED2 0x53
+#define RT2860_MCU_CMD_LED3 0x54
+#define RT2860_MCU_CMD_RFRESET 0x72
+#define RT2860_MCU_CMD_ANTSEL 0x73
+#define RT2860_MCU_CMD_BBP 0x80
+#define RT2860_MCU_CMD_PSLEVEL 0x83
+
+/* possible flags for register PBF_CFG */
+#define RT2860_TX1Q_NUM_SHIFT 21
+#define RT2860_TX2Q_NUM_SHIFT 16
+#define RT2860_NULL0_MODE (1 << 15)
+#define RT2860_NULL1_MODE (1 << 14)
+#define RT2860_RX_DROP_MODE (1 << 13)
+#define RT2860_TX0Q_MANUAL (1 << 12)
+#define RT2860_TX1Q_MANUAL (1 << 11)
+#define RT2860_TX2Q_MANUAL (1 << 10)
+#define RT2860_RX0Q_MANUAL (1 << 9)
+#define RT2860_HCCA_EN (1 << 8)
+#define RT2860_TX0Q_EN (1 << 4)
+#define RT2860_TX1Q_EN (1 << 3)
+#define RT2860_TX2Q_EN (1 << 2)
+#define RT2860_RX0Q_EN (1 << 1)
+
+/* possible flags for register BUF_CTRL */
+#define RT2860_WRITE_TXQ(qid) (1 << (11 - (qid)))
+#define RT2860_NULL0_KICK (1 << 7)
+#define RT2860_NULL1_KICK (1 << 6)
+#define RT2860_BUF_RESET (1 << 5)
+#define RT2860_READ_TXQ(qid) (1 << (3 - (qid))
+#define RT2860_READ_RX0Q (1 << 0)
+
+/* possible flags for registers MCU_INT_STA/MCU_INT_ENA */
+#define RT2860_MCU_MAC_INT_8 (1 << 24)
+#define RT2860_MCU_MAC_INT_7 (1 << 23)
+#define RT2860_MCU_MAC_INT_6 (1 << 22)
+#define RT2860_MCU_MAC_INT_4 (1 << 20)
+#define RT2860_MCU_MAC_INT_3 (1 << 19)
+#define RT2860_MCU_MAC_INT_2 (1 << 18)
+#define RT2860_MCU_MAC_INT_1 (1 << 17)
+#define RT2860_MCU_MAC_INT_0 (1 << 16)
+#define RT2860_DTX0_INT (1 << 11)
+#define RT2860_DTX1_INT (1 << 10)
+#define RT2860_DTX2_INT (1 << 9)
+#define RT2860_DRX0_INT (1 << 8)
+#define RT2860_HCMD_INT (1 << 7)
+#define RT2860_N0TX_INT (1 << 6)
+#define RT2860_N1TX_INT (1 << 5)
+#define RT2860_BCNTX_INT (1 << 4)
+#define RT2860_MTX0_INT (1 << 3)
+#define RT2860_MTX1_INT (1 << 2)
+#define RT2860_MTX2_INT (1 << 1)
+#define RT2860_MRX0_INT (1 << 0)
+
+/* possible flags for register TXRXQ_PCNT */
+#define RT2860_RX0Q_PCNT_MASK 0xff000000
+#define RT2860_TX2Q_PCNT_MASK 0x00ff0000
+#define RT2860_TX1Q_PCNT_MASK 0x0000ff00
+#define RT2860_TX0Q_PCNT_MASK 0x000000ff
+
+/* possible flags for register CAP_CTRL */
+#define RT2860_CAP_ADC_FEQ (1U << 31)
+#define RT2860_CAP_START (1 << 30)
+#define RT2860_MAN_TRIG (1 << 29)
+#define RT2860_TRIG_OFFSET_SHIFT 16
+#define RT2860_START_ADDR_SHIFT 0
+
+/* possible flags for register RF_CSR_CFG */
+#define RT3070_RF_KICK (1 << 17)
+#define RT3070_RF_WRITE (1 << 16)
+
+/* possible flags for register EFUSE_CTRL */
+#define RT3070_SEL_EFUSE (1U << 31)
+#define RT3070_EFSROM_KICK (1 << 30)
+#define RT3070_EFSROM_AIN_MASK 0x03ff0000
+#define RT3070_EFSROM_AIN_SHIFT 16
+#define RT3070_EFSROM_MODE_MASK 0x000000c0
+#define RT3070_EFUSE_AOUT_MASK 0x0000003f
+
+/* possible flag for register DEBUG_INDEX */
+#define RT5592_SEL_XTAL (1U << 31)
+
+/* possible flags for register MAC_SYS_CTRL */
+#define RT2860_RX_TS_EN (1 << 7)
+#define RT2860_WLAN_HALT_EN (1 << 6)
+#define RT2860_PBF_LOOP_EN (1 << 5)
+#define RT2860_CONT_TX_TEST (1 << 4)
+#define RT2860_MAC_RX_EN (1 << 3)
+#define RT2860_MAC_TX_EN (1 << 2)
+#define RT2860_BBP_HRST (1 << 1)
+#define RT2860_MAC_SRST (1 << 0)
+
+/* possible flags for register MAC_BSSID_DW1 */
+#define RT2860_MULTI_BCN_NUM_SHIFT 18
+#define RT2860_MULTI_BSSID_MODE_SHIFT 16
+
+/* possible flags for register MAX_LEN_CFG */
+#define RT2860_MIN_MPDU_LEN_SHIFT 16
+#define RT2860_MAX_PSDU_LEN_SHIFT 12
+#define RT2860_MAX_PSDU_LEN8K 0
+#define RT2860_MAX_PSDU_LEN16K 1
+#define RT2860_MAX_PSDU_LEN32K 2
+#define RT2860_MAX_PSDU_LEN64K 3
+#define RT2860_MAX_MPDU_LEN_SHIFT 0
+
+/* possible flags for registers BBP_CSR_CFG/H2M_BBPAGENT */
+#define RT2860_BBP_RW_PARALLEL (1 << 19)
+#define RT2860_BBP_PAR_DUR_112_5 (1 << 18)
+#define RT2860_BBP_CSR_KICK (1 << 17)
+#define RT2860_BBP_CSR_READ (1 << 16)
+#define RT2860_BBP_ADDR_SHIFT 8
+#define RT2860_BBP_DATA_SHIFT 0
+
+/* possible flags for register RF_CSR_CFG0 */
+#define RT2860_RF_REG_CTRL (1U << 31)
+#define RT2860_RF_LE_SEL1 (1 << 30)
+#define RT2860_RF_LE_STBY (1 << 29)
+#define RT2860_RF_REG_WIDTH_SHIFT 24
+#define RT2860_RF_REG_0_SHIFT 0
+
+/* possible flags for register RF_CSR_CFG1 */
+#define RT2860_RF_DUR_5 (1 << 24)
+#define RT2860_RF_REG_1_SHIFT 0
+
+/* possible flags for register LED_CFG */
+#define RT2860_LED_POL (1 << 30)
+#define RT2860_Y_LED_MODE_SHIFT 28
+#define RT2860_G_LED_MODE_SHIFT 26
+#define RT2860_R_LED_MODE_SHIFT 24
+#define RT2860_LED_MODE_OFF 0
+#define RT2860_LED_MODE_BLINK_TX 1
+#define RT2860_LED_MODE_SLOW_BLINK 2
+#define RT2860_LED_MODE_ON 3
+#define RT2860_SLOW_BLK_TIME_SHIFT 16
+#define RT2860_LED_OFF_TIME_SHIFT 8
+#define RT2860_LED_ON_TIME_SHIFT 0
+
+/* possible flags for register XIFS_TIME_CFG */
+#define RT2860_BB_RXEND_EN (1 << 29)
+#define RT2860_EIFS_TIME_SHIFT 20
+#define RT2860_OFDM_XIFS_TIME_SHIFT 16
+#define RT2860_OFDM_SIFS_TIME_SHIFT 8
+#define RT2860_CCK_SIFS_TIME_SHIFT 0
+
+/* possible flags for register BKOFF_SLOT_CFG */
+#define RT2860_CC_DELAY_TIME_SHIFT 8
+#define RT2860_SLOT_TIME 0
+
+/* possible flags for register NAV_TIME_CFG */
+#define RT2860_NAV_UPD (1U << 31)
+#define RT2860_NAV_UPD_VAL_SHIFT 16
+#define RT2860_NAV_CLR_EN (1 << 15)
+#define RT2860_NAV_TIMER_SHIFT 0
+
+/* possible flags for register CH_TIME_CFG */
+#define RT2860_EIFS_AS_CH_BUSY (1 << 4)
+#define RT2860_NAV_AS_CH_BUSY (1 << 3)
+#define RT2860_RX_AS_CH_BUSY (1 << 2)
+#define RT2860_TX_AS_CH_BUSY (1 << 1)
+#define RT2860_CH_STA_TIMER_EN (1 << 0)
+
+/* possible values for register BCN_TIME_CFG */
+#define RT2860_TSF_INS_COMP_SHIFT 24
+#define RT2860_BCN_TX_EN (1 << 20)
+#define RT2860_TBTT_TIMER_EN (1 << 19)
+#define RT2860_TSF_SYNC_MODE_SHIFT 17
+#define RT2860_TSF_SYNC_MODE_DIS 0
+#define RT2860_TSF_SYNC_MODE_STA 1
+#define RT2860_TSF_SYNC_MODE_IBSS 2
+#define RT2860_TSF_SYNC_MODE_HOSTAP 3
+#define RT2860_TSF_TIMER_EN (1 << 16)
+#define RT2860_BCN_INTVAL_SHIFT 0
+
+/* possible flags for register TBTT_SYNC_CFG */
+#define RT2860_BCN_CWMIN_SHIFT 20
+#define RT2860_BCN_AIFSN_SHIFT 16
+#define RT2860_BCN_EXP_WIN_SHIFT 8
+#define RT2860_TBTT_ADJUST_SHIFT 0
+
+/* possible flags for register INT_TIMER_CFG */
+#define RT2860_GP_TIMER_SHIFT 16
+#define RT2860_PRE_TBTT_TIMER_SHIFT 0
+
+/* possible flags for register INT_TIMER_EN */
+#define RT2860_GP_TIMER_EN (1 << 1)
+#define RT2860_PRE_TBTT_INT_EN (1 << 0)
+
+/* possible flags for register MAC_STATUS_REG */
+#define RT2860_RX_STATUS_BUSY (1 << 1)
+#define RT2860_TX_STATUS_BUSY (1 << 0)
+
+/* possible flags for register PWR_PIN_CFG */
+#define RT2860_IO_ADDA_PD (1 << 3)
+#define RT2860_IO_PLL_PD (1 << 2)
+#define RT2860_IO_RA_PE (1 << 1)
+#define RT2860_IO_RF_PE (1 << 0)
+
+/* possible flags for register AUTO_WAKEUP_CFG */
+#define RT2860_AUTO_WAKEUP_EN (1 << 15)
+#define RT2860_SLEEP_TBTT_NUM_SHIFT 8
+#define RT2860_WAKEUP_LEAD_TIME_SHIFT 0
+
+/* possible flags for register TX_PIN_CFG */
+#define RT2860_TRSW_POL (1 << 19)
+#define RT2860_TRSW_EN (1 << 18)
+#define RT2860_RFTR_POL (1 << 17)
+#define RT2860_RFTR_EN (1 << 16)
+#define RT2860_LNA_PE_G1_POL (1 << 15)
+#define RT2860_LNA_PE_A1_POL (1 << 14)
+#define RT2860_LNA_PE_G0_POL (1 << 13)
+#define RT2860_LNA_PE_A0_POL (1 << 12)
+#define RT2860_LNA_PE_G1_EN (1 << 11)
+#define RT2860_LNA_PE_A1_EN (1 << 10)
+#define RT2860_LNA_PE1_EN (RT2860_LNA_PE_A1_EN | RT2860_LNA_PE_G1_EN)
+#define RT2860_LNA_PE_G0_EN (1 << 9)
+#define RT2860_LNA_PE_A0_EN (1 << 8)
+#define RT2860_LNA_PE0_EN (RT2860_LNA_PE_A0_EN | RT2860_LNA_PE_G0_EN)
+#define RT2860_PA_PE_G1_POL (1 << 7)
+#define RT2860_PA_PE_A1_POL (1 << 6)
+#define RT2860_PA_PE_G0_POL (1 << 5)
+#define RT2860_PA_PE_A0_POL (1 << 4)
+#define RT2860_PA_PE_G1_EN (1 << 3)
+#define RT2860_PA_PE_A1_EN (1 << 2)
+#define RT2860_PA_PE_G0_EN (1 << 1)
+#define RT2860_PA_PE_A0_EN (1 << 0)
+
+/* possible flags for register TX_BAND_CFG */
+#define RT2860_5G_BAND_SEL_N (1 << 2)
+#define RT2860_5G_BAND_SEL_P (1 << 1)
+#define RT2860_TX_BAND_SEL (1 << 0)
+
+/* possible flags for register TX_SW_CFG0 */
+#define RT2860_DLY_RFTR_EN_SHIFT 24
+#define RT2860_DLY_TRSW_EN_SHIFT 16
+#define RT2860_DLY_PAPE_EN_SHIFT 8
+#define RT2860_DLY_TXPE_EN_SHIFT 0
+
+/* possible flags for register TX_SW_CFG1 */
+#define RT2860_DLY_RFTR_DIS_SHIFT 16
+#define RT2860_DLY_TRSW_DIS_SHIFT 8
+#define RT2860_DLY_PAPE_DIS SHIFT 0
+
+/* possible flags for register TX_SW_CFG2 */
+#define RT2860_DLY_LNA_EN_SHIFT 24
+#define RT2860_DLY_LNA_DIS_SHIFT 16
+#define RT2860_DLY_DAC_EN_SHIFT 8
+#define RT2860_DLY_DAC_DIS_SHIFT 0
+
+/* possible flags for register TXOP_THRES_CFG */
+#define RT2860_TXOP_REM_THRES_SHIFT 24
+#define RT2860_CF_END_THRES_SHIFT 16
+#define RT2860_RDG_IN_THRES 8
+#define RT2860_RDG_OUT_THRES 0
+
+/* possible flags for register TXOP_CTRL_CFG */
+#define RT2860_EXT_CW_MIN_SHIFT 16
+#define RT2860_EXT_CCA_DLY_SHIFT 8
+#define RT2860_EXT_CCA_EN (1 << 7)
+#define RT2860_LSIG_TXOP_EN (1 << 6)
+#define RT2860_TXOP_TRUN_EN_MIMOPS (1 << 4)
+#define RT2860_TXOP_TRUN_EN_TXOP (1 << 3)
+#define RT2860_TXOP_TRUN_EN_RATE (1 << 2)
+#define RT2860_TXOP_TRUN_EN_AC (1 << 1)
+#define RT2860_TXOP_TRUN_EN_TIMEOUT (1 << 0)
+
+/* possible flags for register TX_RTS_CFG */
+#define RT2860_RTS_FBK_EN (1 << 24)
+#define RT2860_RTS_THRES_SHIFT 8
+#define RT2860_RTS_RTY_LIMIT_SHIFT 0
+
+/* possible flags for register TX_TIMEOUT_CFG */
+#define RT2860_TXOP_TIMEOUT_SHIFT 16
+#define RT2860_RX_ACK_TIMEOUT_SHIFT 8
+#define RT2860_MPDU_LIFE_TIME_SHIFT 4
+
+/* possible flags for register TX_RTY_CFG */
+#define RT2860_TX_AUTOFB_EN (1 << 30)
+#define RT2860_AGG_RTY_MODE_TIMER (1 << 29)
+#define RT2860_NAG_RTY_MODE_TIMER (1 << 28)
+#define RT2860_LONG_RTY_THRES_SHIFT 16
+#define RT2860_LONG_RTY_LIMIT_SHIFT 8
+#define RT2860_SHORT_RTY_LIMIT_SHIFT 0
+
+/* possible flags for register TX_LINK_CFG */
+#define RT2860_REMOTE_MFS_SHIFT 24
+#define RT2860_REMOTE_MFB_SHIFT 16
+#define RT2860_TX_CFACK_EN (1 << 12)
+#define RT2860_TX_RDG_EN (1 << 11)
+#define RT2860_TX_MRQ_EN (1 << 10)
+#define RT2860_REMOTE_UMFS_EN (1 << 9)
+#define RT2860_TX_MFB_EN (1 << 8)
+#define RT2860_REMOTE_MFB_LT_SHIFT 0
+
+/* possible flags for registers *_PROT_CFG */
+#define RT2860_RTSTH_EN (1 << 26)
+#define RT2860_TXOP_ALLOW_GF40 (1 << 25)
+#define RT2860_TXOP_ALLOW_GF20 (1 << 24)
+#define RT2860_TXOP_ALLOW_MM40 (1 << 23)
+#define RT2860_TXOP_ALLOW_MM20 (1 << 22)
+#define RT2860_TXOP_ALLOW_OFDM (1 << 21)
+#define RT2860_TXOP_ALLOW_CCK (1 << 20)
+#define RT2860_TXOP_ALLOW_ALL (0x3f << 20)
+#define RT2860_PROT_NAV_SHORT (1 << 18)
+#define RT2860_PROT_NAV_LONG (2 << 18)
+#define RT2860_PROT_CTRL_RTS_CTS (1 << 16)
+#define RT2860_PROT_CTRL_CTS (2 << 16)
+
+/* possible flags for registers EXP_{CTS,ACK}_TIME */
+#define RT2860_EXP_OFDM_TIME_SHIFT 16
+#define RT2860_EXP_CCK_TIME_SHIFT 0
+
+/* possible flags for register RX_FILTR_CFG */
+#define RT2860_DROP_CTRL_RSV (1 << 16)
+#define RT2860_DROP_BAR (1 << 15)
+#define RT2860_DROP_BA (1 << 14)
+#define RT2860_DROP_PSPOLL (1 << 13)
+#define RT2860_DROP_RTS (1 << 12)
+#define RT2860_DROP_CTS (1 << 11)
+#define RT2860_DROP_ACK (1 << 10)
+#define RT2860_DROP_CFEND (1 << 9)
+#define RT2860_DROP_CFACK (1 << 8)
+#define RT2860_DROP_DUPL (1 << 7)
+#define RT2860_DROP_BC (1 << 6)
+#define RT2860_DROP_MC (1 << 5)
+#define RT2860_DROP_VER_ERR (1 << 4)
+#define RT2860_DROP_NOT_MYBSS (1 << 3)
+#define RT2860_DROP_UC_NOME (1 << 2)
+#define RT2860_DROP_PHY_ERR (1 << 1)
+#define RT2860_DROP_CRC_ERR (1 << 0)
+
+/* possible flags for register AUTO_RSP_CFG */
+#define RT2860_CTRL_PWR_BIT (1 << 7)
+#define RT2860_BAC_ACK_POLICY (1 << 6)
+#define RT2860_CCK_SHORT_EN (1 << 4)
+#define RT2860_CTS_40M_REF_EN (1 << 3)
+#define RT2860_CTS_40M_MODE_EN (1 << 2)
+#define RT2860_BAC_ACKPOLICY_EN (1 << 1)
+#define RT2860_AUTO_RSP_EN (1 << 0)
+
+/* possible flags for register SIFS_COST_CFG */
+#define RT2860_OFDM_SIFS_COST_SHIFT 8
+#define RT2860_CCK_SIFS_COST_SHIFT 0
+
+/* possible flags for register TXOP_HLDR_ET */
+#define RT2860_TXOP_ETM1_EN (1 << 25)
+#define RT2860_TXOP_ETM0_EN (1 << 24)
+#define RT2860_TXOP_ETM_THRES_SHIFT 16
+#define RT2860_TXOP_ETO_EN (1 << 8)
+#define RT2860_TXOP_ETO_THRES_SHIFT 1
+#define RT2860_PER_RX_RST_EN (1 << 0)
+
+/* possible flags for register TX_STAT_FIFO */
+#define RT2860_TXQ_MCS_SHIFT 16
+#define RT2860_TXQ_WCID_SHIFT 8
+#define RT2860_TXQ_ACKREQ (1 << 7)
+#define RT2860_TXQ_AGG (1 << 6)
+#define RT2860_TXQ_OK (1 << 5)
+#define RT2860_TXQ_PID_SHIFT 1
+#define RT2860_TXQ_VLD (1 << 0)
+
+/* possible flags for register WCID_ATTR */
+#define RT2860_MODE_NOSEC 0
+#define RT2860_MODE_WEP40 1
+#define RT2860_MODE_WEP104 2
+#define RT2860_MODE_TKIP 3
+#define RT2860_MODE_AES_CCMP 4
+#define RT2860_MODE_CKIP40 5
+#define RT2860_MODE_CKIP104 6
+#define RT2860_MODE_CKIP128 7
+#define RT2860_RX_PKEY_EN (1 << 0)
+
+/* possible flags for register H2M_MAILBOX */
+#define RT2860_H2M_BUSY (1 << 24)
+#define RT2860_TOKEN_NO_INTR 0xff
+
+/* possible flags for MCU command RT2860_MCU_CMD_LEDS */
+#define RT2860_LED_RADIO (1 << 13)
+#define RT2860_LED_LINK_2GHZ (1 << 14)
+#define RT2860_LED_LINK_5GHZ (1 << 15)
+
+/* possible flags for RT3020 RF register 1 */
+#define RT3070_RF_BLOCK (1 << 0)
+#define RT3070_PLL_PD (1 << 1)
+#define RT3070_RX0_PD (1 << 2)
+#define RT3070_TX0_PD (1 << 3)
+#define RT3070_RX1_PD (1 << 4)
+#define RT3070_TX1_PD (1 << 5)
+#define RT3070_RX2_PD (1 << 6)
+#define RT3070_TX2_PD (1 << 7)
+
+/* possible flags for RT3020 RF register 15 */
+#define RT3070_TX_LO2 (1 << 3)
+
+/* possible flags for RT3020 RF register 17 */
+#define RT3070_TX_LO1 (1 << 3)
+
+/* possible flags for RT3020 RF register 20 */
+#define RT3070_RX_LO1 (1 << 3)
+
+/* possible flags for RT3020 RF register 21 */
+#define RT3070_RX_LO2 (1 << 3)
+
+/* possible flags for RT3053 RF register 18 */
+#define RT3593_AUTOTUNE_BYPASS (1 << 6)
+
+/* possible flags for RT3053 RF register 50 */
+#define RT3593_TX_LO2 (1 << 4)
+
+/* possible flags for RT3053 RF register 51 */
+#define RT3593_TX_LO1 (1 << 4)
+
+/* Possible flags for RT5390 RF register 2. */
+#define RT5390_RESCAL (1 << 7)
+
+/* Possible flags for RT5390 RF register 3. */
+#define RT5390_VCOCAL (1 << 7)
+
+/* Possible flags for RT5390 RF register 38. */
+#define RT5390_RX_LO1 (1 << 5)
+
+/* Possible flags for RT5390 RF register 39. */
+#define RT5390_RX_LO2 (1 << 7)
+
+/* Possible flags for RT5390 BBP register 4. */
+#define RT5390_MAC_IF_CTRL (1 << 6)
+
+/* Possible flags for RT5390 BBP register 105. */
+#define RT5390_MLD (1 << 2)
+#define RT5390_EN_SIG_MODULATION (1 << 3)
+
+/* RT2860 TX descriptor */
+struct rt2860_txd {
+ uint32_t sdp0; /* Segment Data Pointer 0 */
+ uint16_t sdl1; /* Segment Data Length 1 */
+#define RT2860_TX_BURST (1 << 15)
+#define RT2860_TX_LS1 (1 << 14) /* SDP1 is the last segment */
+
+ uint16_t sdl0; /* Segment Data Length 0 */
+#define RT2860_TX_DDONE (1 << 15)
+#define RT2860_TX_LS0 (1 << 14) /* SDP0 is the last segment */
+
+ uint32_t sdp1; /* Segment Data Pointer 1 */
+ uint8_t reserved[3];
+ uint8_t flags;
+#define RT2860_TX_QSEL_SHIFT 1
+#define RT2860_TX_QSEL_MGMT (0 << 1)
+#define RT2860_TX_QSEL_HCCA (1 << 1)
+#define RT2860_TX_QSEL_EDCA (2 << 1)
+#define RT2860_TX_WIV (1 << 0)
+} __packed;
+
+/* RT2870 TX descriptor */
+struct rt2870_txd {
+ uint16_t len;
+ uint8_t pad;
+ uint8_t flags;
+} __packed;
+
+/* TX Wireless Information */
+struct rt2860_txwi {
+ uint8_t flags;
+#define RT2860_TX_MPDU_DSITY_SHIFT 5
+#define RT2860_TX_AMPDU (1 << 4)
+#define RT2860_TX_TS (1 << 3)
+#define RT2860_TX_CFACK (1 << 2)
+#define RT2860_TX_MMPS (1 << 1)
+#define RT2860_TX_FRAG (1 << 0)
+
+ uint8_t txop;
+#define RT2860_TX_TXOP_HT 0
+#define RT2860_TX_TXOP_PIFS 1
+#define RT2860_TX_TXOP_SIFS 2
+#define RT2860_TX_TXOP_BACKOFF 3
+
+ uint16_t phy;
+#define RT2860_PHY_MODE 0xc000
+#define RT2860_PHY_CCK (0 << 14)
+#define RT2860_PHY_OFDM (1 << 14)
+#define RT2860_PHY_HT_MIX (2 << 14)
+#define RT2860_PHY_HT_GF (3 << 14)
+#define RT2860_PHY_SGI (1 << 8)
+#define RT2860_PHY_BW40 (1 << 7)
+#define RT2860_PHY_MCS 0x7f
+#define RT2860_PHY_SHPRE (1 << 3)
+
+ uint8_t xflags;
+#define RT2860_TX_BAWINSIZE_SHIFT 2
+#define RT2860_TX_NSEQ (1 << 1)
+#define RT2860_TX_ACK (1 << 0)
+
+ uint8_t wcid; /* Wireless Client ID */
+ uint16_t len;
+#define RT2860_TX_PID_SHIFT 12
+
+ uint32_t iv;
+ uint32_t eiv;
+} __packed;
+
+/* RT2860 RX descriptor */
+struct rt2860_rxd {
+ uint32_t sdp0;
+ uint16_t sdl1; /* unused */
+ uint16_t sdl0;
+#define RT2860_RX_DDONE (1 << 15)
+#define RT2860_RX_LS0 (1 << 14)
+
+ uint32_t sdp1; /* unused */
+ uint32_t flags;
+#define RT2860_RX_DEC (1 << 16)
+#define RT2860_RX_AMPDU (1 << 15)
+#define RT2860_RX_L2PAD (1 << 14)
+#define RT2860_RX_RSSI (1 << 13)
+#define RT2860_RX_HTC (1 << 12)
+#define RT2860_RX_AMSDU (1 << 11)
+#define RT2860_RX_MICERR (1 << 10)
+#define RT2860_RX_ICVERR (1 << 9)
+#define RT2860_RX_CRCERR (1 << 8)
+#define RT2860_RX_MYBSS (1 << 7)
+#define RT2860_RX_BC (1 << 6)
+#define RT2860_RX_MC (1 << 5)
+#define RT2860_RX_UC2ME (1 << 4)
+#define RT2860_RX_FRAG (1 << 3)
+#define RT2860_RX_NULL (1 << 2)
+#define RT2860_RX_DATA (1 << 1)
+#define RT2860_RX_BA (1 << 0)
+} __packed;
+
+/* RT2870 RX descriptor */
+struct rt2870_rxd {
+ /* single 32-bit field */
+ uint32_t flags;
+} __packed;
+
+/* RX Wireless Information */
+struct rt2860_rxwi {
+ uint8_t wcid;
+ uint8_t keyidx;
+#define RT2860_RX_UDF_SHIFT 5
+#define RT2860_RX_BSS_IDX_SHIFT 2
+
+ uint16_t len;
+#define RT2860_RX_TID_SHIFT 12
+
+ uint16_t seq;
+ uint16_t phy;
+ uint8_t rssi[3];
+ uint8_t reserved1;
+ uint8_t snr[2];
+ uint16_t reserved2;
+} __packed;
+
+#define RT2860_RF_2820 0x0001 /* 2T3R */
+#define RT2860_RF_2850 0x0002 /* dual-band 2T3R */
+#define RT2860_RF_2720 0x0003 /* 1T2R */
+#define RT2860_RF_2750 0x0004 /* dual-band 1T2R */
+#define RT3070_RF_3020 0x0005 /* 1T1R */
+#define RT3070_RF_2020 0x0006 /* b/g */
+#define RT3070_RF_3021 0x0007 /* 1T2R */
+#define RT3070_RF_3022 0x0008 /* 2T2R */
+#define RT3070_RF_3052 0x0009 /* dual-band 2T2R */
+#define RT3593_RF_3053 0x000d /* dual-band 3T3R */
+#define RT5592_RF_5592 0x000f /* dual-band 2T2R */
+#define RT5390_RF_5370 0x5370 /* 1T1R */
+#define RT5390_RF_5372 0x5372 /* 2T2R */
+
+/* USB commands for RT2870 only */
+#define RT2870_RESET 1
+#define RT2870_WRITE_2 2
+#define RT2870_WRITE_REGION_1 6
+#define RT2870_READ_REGION_1 7
+#define RT2870_EEPROM_READ 9
+
+#define RT2860_EEPROM_DELAY 1 /* minimum hold time (microsecond) */
+
+#define RT2860_EEPROM_VERSION 0x01
+#define RT2860_EEPROM_MAC01 0x02
+#define RT2860_EEPROM_MAC23 0x03
+#define RT2860_EEPROM_MAC45 0x04
+#define RT2860_EEPROM_PCIE_PSLEVEL 0x11
+#define RT2860_EEPROM_REV 0x12
+#define RT2860_EEPROM_ANTENNA 0x1a
+#define RT2860_EEPROM_CONFIG 0x1b
+#define RT2860_EEPROM_COUNTRY 0x1c
+#define RT2860_EEPROM_FREQ_LEDS 0x1d
+#define RT2860_EEPROM_LED1 0x1e
+#define RT2860_EEPROM_LED2 0x1f
+#define RT2860_EEPROM_LED3 0x20
+#define RT2860_EEPROM_LNA 0x22
+#define RT2860_EEPROM_RSSI1_2GHZ 0x23
+#define RT2860_EEPROM_RSSI2_2GHZ 0x24
+#define RT2860_EEPROM_RSSI1_5GHZ 0x25
+#define RT2860_EEPROM_RSSI2_5GHZ 0x26
+#define RT2860_EEPROM_DELTAPWR 0x28
+#define RT2860_EEPROM_PWR2GHZ_BASE1 0x29
+#define RT2860_EEPROM_PWR2GHZ_BASE2 0x30
+#define RT2860_EEPROM_TSSI1_2GHZ 0x37
+#define RT2860_EEPROM_TSSI2_2GHZ 0x38
+#define RT2860_EEPROM_TSSI3_2GHZ 0x39
+#define RT2860_EEPROM_TSSI4_2GHZ 0x3a
+#define RT2860_EEPROM_TSSI5_2GHZ 0x3b
+#define RT2860_EEPROM_PWR5GHZ_BASE1 0x3c
+#define RT2860_EEPROM_PWR5GHZ_BASE2 0x53
+#define RT2860_EEPROM_TSSI1_5GHZ 0x6a
+#define RT2860_EEPROM_TSSI2_5GHZ 0x6b
+#define RT2860_EEPROM_TSSI3_5GHZ 0x6c
+#define RT2860_EEPROM_TSSI4_5GHZ 0x6d
+#define RT2860_EEPROM_TSSI5_5GHZ 0x6e
+#define RT2860_EEPROM_RPWR 0x6f
+#define RT2860_EEPROM_BBP_BASE 0x78
+#define RT3071_EEPROM_RF_BASE 0x82
+
+/* EEPROM registers for RT3593. */
+#define RT3593_EEPROM_FREQ_LEDS 0x21
+#define RT3593_EEPROM_FREQ 0x22
+#define RT3593_EEPROM_LED1 0x22
+#define RT3593_EEPROM_LED2 0x23
+#define RT3593_EEPROM_LED3 0x24
+#define RT3593_EEPROM_LNA 0x26
+#define RT3593_EEPROM_LNA_5GHZ 0x27
+#define RT3593_EEPROM_RSSI1_2GHZ 0x28
+#define RT3593_EEPROM_RSSI2_2GHZ 0x29
+#define RT3593_EEPROM_RSSI1_5GHZ 0x2a
+#define RT3593_EEPROM_RSSI2_5GHZ 0x2b
+#define RT3593_EEPROM_PWR2GHZ_BASE1 0x30
+#define RT3593_EEPROM_PWR2GHZ_BASE2 0x37
+#define RT3593_EEPROM_PWR2GHZ_BASE3 0x3e
+#define RT3593_EEPROM_PWR5GHZ_BASE1 0x4b
+#define RT3593_EEPROM_PWR5GHZ_BASE2 0x65
+#define RT3593_EEPROM_PWR5GHZ_BASE3 0x7f
+
+/*
+ * EEPROM IQ calibration.
+ */
+#define RT5390_EEPROM_IQ_GAIN_CAL_TX0_2GHZ 0x130
+#define RT5390_EEPROM_IQ_PHASE_CAL_TX0_2GHZ 0x131
+#define RT5390_EEPROM_IQ_GAIN_CAL_TX1_2GHZ 0x133
+#define RT5390_EEPROM_IQ_PHASE_CAL_TX1_2GHZ 0x134
+#define RT5390_EEPROM_RF_IQ_COMPENSATION_CTL 0x13c
+#define RT5390_EEPROM_RF_IQ_IMBALANCE_COMPENSATION_CTL 0x13d
+#define RT5390_EEPROM_IQ_GAIN_CAL_TX0_CH36_TO_CH64_5GHZ 0x144
+#define RT5390_EEPROM_IQ_PHASE_CAL_TX0_CH36_TO_CH64_5GHZ 0x145
+#define RT5390_EEPROM_IQ_GAIN_CAL_TX0_CH100_TO_CH138_5GHZ 0x146
+#define RT5390_EEPROM_IQ_PHASE_CAL_TX0_CH100_TO_CH138_5GHZ 0x147
+#define RT5390_EEPROM_IQ_GAIN_CAL_TX0_CH140_TO_CH165_5GHZ 0x148
+#define RT5390_EEPROM_IQ_PHASE_CAL_TX0_CH140_TO_CH165_5GHZ 0x149
+#define RT5390_EEPROM_IQ_GAIN_CAL_TX1_CH36_TO_CH64_5GHZ 0x14a
+#define RT5390_EEPROM_IQ_PHASE_CAL_TX1_CH36_TO_CH64_5GHZ 0x14b
+#define RT5390_EEPROM_IQ_GAIN_CAL_TX1_CH100_TO_CH138_5GHZ 0x14c
+#define RT5390_EEPROM_IQ_PHASE_CAL_TX1_CH100_TO_CH138_5GHZ 0x14d
+#define RT5390_EEPROM_IQ_GAIN_CAL_TX1_CH140_TO_CH165_5GHZ 0x14e
+#define RT5390_EEPROM_IQ_PHASE_CAL_TX1_CH140_TO_CH165_5GHZ 0x14f
+
+/*
+ * EEPROM access macro.
+ */
+#define RT2860_EEPROM_CTL(sc, val) do { \
+ RAL_WRITE((sc), RT2860_PCI_EECTRL, (val)); \
+ RAL_BARRIER_READ_WRITE((sc)); \
+ DELAY(RT2860_EEPROM_DELAY); \
+} while (/* CONSTCOND */0)
+
+/*
+ * Default values for MAC registers; values taken from the reference driver.
+ */
+#define RT2870_DEF_MAC \
+ { RT2860_BCN_OFFSET0, 0xf8f0e8e0 }, \
+ { RT2860_BCN_OFFSET1, 0x6f77d0c8 }, \
+ { RT2860_LEGACY_BASIC_RATE, 0x0000013f }, \
+ { RT2860_HT_BASIC_RATE, 0x00008003 }, \
+ { RT2860_MAC_SYS_CTRL, 0x00000000 }, \
+ { RT2860_BKOFF_SLOT_CFG, 0x00000209 }, \
+ { RT2860_TX_SW_CFG0, 0x00000000 }, \
+ { RT2860_TX_SW_CFG1, 0x00080606 }, \
+ { RT2860_TX_LINK_CFG, 0x00001020 }, \
+ { RT2860_TX_TIMEOUT_CFG, 0x000a2090 }, \
+ { RT2860_MAX_LEN_CFG, 0x00001f00 }, \
+ { RT2860_LED_CFG, 0x7f031e46 }, \
+ { RT2860_WMM_AIFSN_CFG, 0x00002273 }, \
+ { RT2860_WMM_CWMIN_CFG, 0x00002344 }, \
+ { RT2860_WMM_CWMAX_CFG, 0x000034aa }, \
+ { RT2860_MAX_PCNT, 0x1f3fbf9f }, \
+ { RT2860_TX_RTY_CFG, 0x47d01f0f }, \
+ { RT2860_AUTO_RSP_CFG, 0x00000013 }, \
+ { RT2860_CCK_PROT_CFG, 0x05740003 }, \
+ { RT2860_OFDM_PROT_CFG, 0x05740003 }, \
+ { RT2860_PBF_CFG, 0x00f40006 }, \
+ { RT2860_WPDMA_GLO_CFG, 0x00000030 }, \
+ { RT2860_GF20_PROT_CFG, 0x01744004 }, \
+ { RT2860_GF40_PROT_CFG, 0x03f44084 }, \
+ { RT2860_MM20_PROT_CFG, 0x01744004 }, \
+ { RT2860_MM40_PROT_CFG, 0x03f44084 }, \
+ { RT2860_TXOP_CTRL_CFG, 0x0000583f }, \
+ { RT2860_TXOP_HLDR_ET, 0x00000002 }, \
+ { RT2860_TX_RTS_CFG, 0x00092b20 }, \
+ { RT2860_EXP_ACK_TIME, 0x002400ca }, \
+ { RT2860_XIFS_TIME_CFG, 0x33a41010 }, \
+ { RT2860_PWR_PIN_CFG, 0x00000003 }
+
+/*
+ * Default values for BBP registers; values taken from the reference driver.
+ */
+#define RT2860_DEF_BBP \
+ { 65, 0x2c }, \
+ { 66, 0x38 }, \
+ { 68, 0x0b }, \
+ { 69, 0x12 }, \
+ { 70, 0x0a }, \
+ { 73, 0x10 }, \
+ { 81, 0x37 }, \
+ { 82, 0x62 }, \
+ { 83, 0x6a }, \
+ { 84, 0x99 }, \
+ { 86, 0x00 }, \
+ { 91, 0x04 }, \
+ { 92, 0x00 }, \
+ { 103, 0x00 }, \
+ { 105, 0x05 }, \
+ { 106, 0x35 }
+
+#define RT5390_DEF_BBP \
+ { 31, 0x08 }, \
+ { 65, 0x2c }, \
+ { 66, 0x38 }, \
+ { 68, 0x0b }, \
+ { 69, 0x0d }, \
+ { 70, 0x06 }, \
+ { 73, 0x13 }, \
+ { 75, 0x46 }, \
+ { 76, 0x28 }, \
+ { 77, 0x59 }, \
+ { 81, 0x37 }, \
+ { 82, 0x62 }, \
+ { 83, 0x7a }, \
+ { 84, 0x9a }, \
+ { 86, 0x38 }, \
+ { 91, 0x04 }, \
+ { 92, 0x02 }, \
+ { 103, 0xc0 }, \
+ { 104, 0x92 }, \
+ { 105, 0x3c }, \
+ { 106, 0x03 }, \
+ { 128, 0x12 }
+
+#define RT5592_DEF_BBP \
+ { 20, 0x06 }, \
+ { 31, 0x08 }, \
+ { 65, 0x2c }, \
+ { 66, 0x38 }, \
+ { 68, 0xdd }, \
+ { 69, 0x1a }, \
+ { 70, 0x05 }, \
+ { 73, 0x13 }, \
+ { 74, 0x0f }, \
+ { 75, 0x4f }, \
+ { 76, 0x28 }, \
+ { 77, 0x59 }, \
+ { 81, 0x37 }, \
+ { 82, 0x62 }, \
+ { 83, 0x6a }, \
+ { 84, 0x9a }, \
+ { 86, 0x38 }, \
+ { 88, 0x90 }, \
+ { 91, 0x04 }, \
+ { 92, 0x02 }, \
+ { 95, 0x9a }, \
+ { 98, 0x12 }, \
+ { 103, 0xc0 }, \
+ { 104, 0x92 }, \
+ { 105, 0x3c }, \
+ { 106, 0x35 }, \
+ { 128, 0x12 }, \
+ { 134, 0xd0 }, \
+ { 135, 0xf6 }, \
+ { 137, 0x0f }
+
+/*
+ * Channel map for run(4) driver; taken from the table below.
+ */
+static const uint8_t run_chan_5ghz[] =
+ { 36, 38, 40, 44, 46, 48, 52, 54, 56, 60, 62, 64, 100, 102, 104,
+ 108, 110, 112, 116, 118, 120, 124, 126, 128, 132, 134, 136, 140,
+ 149, 151, 153, 157, 159, 161, 165, 167, 169, 171, 173,
+ 184, 188, 192, 196, 208, 212, 216 };
+
+/*
+ * Default settings for RF registers; values derived from the reference driver.
+ */
+#define RT2860_RF2850 \
+ { 1, 0x98402ecc, 0x984c0786, 0x9816b455, 0x9800510b }, \
+ { 2, 0x98402ecc, 0x984c0786, 0x98168a55, 0x9800519f }, \
+ { 3, 0x98402ecc, 0x984c078a, 0x98168a55, 0x9800518b }, \
+ { 4, 0x98402ecc, 0x984c078a, 0x98168a55, 0x9800519f }, \
+ { 5, 0x98402ecc, 0x984c078e, 0x98168a55, 0x9800518b }, \
+ { 6, 0x98402ecc, 0x984c078e, 0x98168a55, 0x9800519f }, \
+ { 7, 0x98402ecc, 0x984c0792, 0x98168a55, 0x9800518b }, \
+ { 8, 0x98402ecc, 0x984c0792, 0x98168a55, 0x9800519f }, \
+ { 9, 0x98402ecc, 0x984c0796, 0x98168a55, 0x9800518b }, \
+ { 10, 0x98402ecc, 0x984c0796, 0x98168a55, 0x9800519f }, \
+ { 11, 0x98402ecc, 0x984c079a, 0x98168a55, 0x9800518b }, \
+ { 12, 0x98402ecc, 0x984c079a, 0x98168a55, 0x9800519f }, \
+ { 13, 0x98402ecc, 0x984c079e, 0x98168a55, 0x9800518b }, \
+ { 14, 0x98402ecc, 0x984c07a2, 0x98168a55, 0x98005193 }, \
+ { 36, 0x98402ecc, 0x984c099a, 0x98158a55, 0x980ed1a3 }, \
+ { 38, 0x98402ecc, 0x984c099e, 0x98158a55, 0x980ed193 }, \
+ { 40, 0x98402ec8, 0x984c0682, 0x98158a55, 0x980ed183 }, \
+ { 44, 0x98402ec8, 0x984c0682, 0x98158a55, 0x980ed1a3 }, \
+ { 46, 0x98402ec8, 0x984c0686, 0x98158a55, 0x980ed18b }, \
+ { 48, 0x98402ec8, 0x984c0686, 0x98158a55, 0x980ed19b }, \
+ { 52, 0x98402ec8, 0x984c068a, 0x98158a55, 0x980ed193 }, \
+ { 54, 0x98402ec8, 0x984c068a, 0x98158a55, 0x980ed1a3 }, \
+ { 56, 0x98402ec8, 0x984c068e, 0x98158a55, 0x980ed18b }, \
+ { 60, 0x98402ec8, 0x984c0692, 0x98158a55, 0x980ed183 }, \
+ { 62, 0x98402ec8, 0x984c0692, 0x98158a55, 0x980ed193 }, \
+ { 64, 0x98402ec8, 0x984c0692, 0x98158a55, 0x980ed1a3 }, \
+ { 100, 0x98402ec8, 0x984c06b2, 0x98178a55, 0x980ed783 }, \
+ { 102, 0x98402ec8, 0x985c06b2, 0x98578a55, 0x980ed793 }, \
+ { 104, 0x98402ec8, 0x985c06b2, 0x98578a55, 0x980ed1a3 }, \
+ { 108, 0x98402ecc, 0x985c0a32, 0x98578a55, 0x980ed193 }, \
+ { 110, 0x98402ecc, 0x984c0a36, 0x98178a55, 0x980ed183 }, \
+ { 112, 0x98402ecc, 0x984c0a36, 0x98178a55, 0x980ed19b }, \
+ { 116, 0x98402ecc, 0x984c0a3a, 0x98178a55, 0x980ed1a3 }, \
+ { 118, 0x98402ecc, 0x984c0a3e, 0x98178a55, 0x980ed193 }, \
+ { 120, 0x98402ec4, 0x984c0382, 0x98178a55, 0x980ed183 }, \
+ { 124, 0x98402ec4, 0x984c0382, 0x98178a55, 0x980ed193 }, \
+ { 126, 0x98402ec4, 0x984c0382, 0x98178a55, 0x980ed15b }, \
+ { 128, 0x98402ec4, 0x984c0382, 0x98178a55, 0x980ed1a3 }, \
+ { 132, 0x98402ec4, 0x984c0386, 0x98178a55, 0x980ed18b }, \
+ { 134, 0x98402ec4, 0x984c0386, 0x98178a55, 0x980ed193 }, \
+ { 136, 0x98402ec4, 0x984c0386, 0x98178a55, 0x980ed19b }, \
+ { 140, 0x98402ec4, 0x984c038a, 0x98178a55, 0x980ed183 }, \
+ { 149, 0x98402ec4, 0x984c038a, 0x98178a55, 0x980ed1a7 }, \
+ { 151, 0x98402ec4, 0x984c038e, 0x98178a55, 0x980ed187 }, \
+ { 153, 0x98402ec4, 0x984c038e, 0x98178a55, 0x980ed18f }, \
+ { 157, 0x98402ec4, 0x984c038e, 0x98178a55, 0x980ed19f }, \
+ { 159, 0x98402ec4, 0x984c038e, 0x98178a55, 0x980ed1a7 }, \
+ { 161, 0x98402ec4, 0x984c0392, 0x98178a55, 0x980ed187 }, \
+ { 165, 0x98402ec4, 0x984c0392, 0x98178a55, 0x980ed197 }, \
+ { 167, 0x98402ec4, 0x984c03d2, 0x98179855, 0x9815531f }, \
+ { 169, 0x98402ec4, 0x984c03d2, 0x98179855, 0x98155327 }, \
+ { 171, 0x98402ec4, 0x984c03d6, 0x98179855, 0x98155307 }, \
+ { 173, 0x98402ec4, 0x984c03d6, 0x98179855, 0x9815530f }, \
+ { 184, 0x95002ccc, 0x9500491e, 0x9509be55, 0x950c0a0b }, \
+ { 188, 0x95002ccc, 0x95004922, 0x9509be55, 0x950c0a13 }, \
+ { 192, 0x95002ccc, 0x95004926, 0x9509be55, 0x950c0a1b }, \
+ { 196, 0x95002ccc, 0x9500492a, 0x9509be55, 0x950c0a23 }, \
+ { 208, 0x95002ccc, 0x9500493a, 0x9509be55, 0x950c0a13 }, \
+ { 212, 0x95002ccc, 0x9500493e, 0x9509be55, 0x950c0a1b }, \
+ { 216, 0x95002ccc, 0x95004982, 0x9509be55, 0x950c0a23 }
+
+#define RT3070_RF3052 \
+ { 0xf1, 2, 2 }, \
+ { 0xf1, 2, 7 }, \
+ { 0xf2, 2, 2 }, \
+ { 0xf2, 2, 7 }, \
+ { 0xf3, 2, 2 }, \
+ { 0xf3, 2, 7 }, \
+ { 0xf4, 2, 2 }, \
+ { 0xf4, 2, 7 }, \
+ { 0xf5, 2, 2 }, \
+ { 0xf5, 2, 7 }, \
+ { 0xf6, 2, 2 }, \
+ { 0xf6, 2, 7 }, \
+ { 0xf7, 2, 2 }, \
+ { 0xf8, 2, 4 }, \
+ { 0x56, 0, 4 }, \
+ { 0x56, 0, 6 }, \
+ { 0x56, 0, 8 }, \
+ { 0x57, 0, 0 }, \
+ { 0x57, 0, 2 }, \
+ { 0x57, 0, 4 }, \
+ { 0x57, 0, 8 }, \
+ { 0x57, 0, 10 }, \
+ { 0x58, 0, 0 }, \
+ { 0x58, 0, 4 }, \
+ { 0x58, 0, 6 }, \
+ { 0x58, 0, 8 }, \
+ { 0x5b, 0, 8 }, \
+ { 0x5b, 0, 10 }, \
+ { 0x5c, 0, 0 }, \
+ { 0x5c, 0, 4 }, \
+ { 0x5c, 0, 6 }, \
+ { 0x5c, 0, 8 }, \
+ { 0x5d, 0, 0 }, \
+ { 0x5d, 0, 2 }, \
+ { 0x5d, 0, 4 }, \
+ { 0x5d, 0, 8 }, \
+ { 0x5d, 0, 10 }, \
+ { 0x5e, 0, 0 }, \
+ { 0x5e, 0, 4 }, \
+ { 0x5e, 0, 6 }, \
+ { 0x5e, 0, 8 }, \
+ { 0x5f, 0, 0 }, \
+ { 0x5f, 0, 9 }, \
+ { 0x5f, 0, 11 }, \
+ { 0x60, 0, 1 }, \
+ { 0x60, 0, 5 }, \
+ { 0x60, 0, 7 }, \
+ { 0x60, 0, 9 }, \
+ { 0x61, 0, 1 }, \
+ { 0x61, 0, 3 }, \
+ { 0x61, 0, 5 }, \
+ { 0x61, 0, 7 }, \
+ { 0x61, 0, 9 }
+
+#define RT5592_RF5592_20MHZ \
+ { 0x1e2, 4, 10, 3 }, \
+ { 0x1e3, 4, 10, 3 }, \
+ { 0x1e4, 4, 10, 3 }, \
+ { 0x1e5, 4, 10, 3 }, \
+ { 0x1e6, 4, 10, 3 }, \
+ { 0x1e7, 4, 10, 3 }, \
+ { 0x1e8, 4, 10, 3 }, \
+ { 0x1e9, 4, 10, 3 }, \
+ { 0x1ea, 4, 10, 3 }, \
+ { 0x1eb, 4, 10, 3 }, \
+ { 0x1ec, 4, 10, 3 }, \
+ { 0x1ed, 4, 10, 3 }, \
+ { 0x1ee, 4, 10, 3 }, \
+ { 0x1f0, 8, 10, 3 }, \
+ { 0xac, 8, 12, 1 }, \
+ { 0xad, 0, 12, 1 }, \
+ { 0xad, 4, 12, 1 }, \
+ { 0xae, 0, 12, 1 }, \
+ { 0xae, 4, 12, 1 }, \
+ { 0xae, 8, 12, 1 }, \
+ { 0xaf, 4, 12, 1 }, \
+ { 0xaf, 8, 12, 1 }, \
+ { 0xb0, 0, 12, 1 }, \
+ { 0xb0, 8, 12, 1 }, \
+ { 0xb1, 0, 12, 1 }, \
+ { 0xb1, 4, 12, 1 }, \
+ { 0xb7, 4, 12, 1 }, \
+ { 0xb7, 8, 12, 1 }, \
+ { 0xb8, 0, 12, 1 }, \
+ { 0xb8, 8, 12, 1 }, \
+ { 0xb9, 0, 12, 1 }, \
+ { 0xb9, 4, 12, 1 }, \
+ { 0xba, 0, 12, 1 }, \
+ { 0xba, 4, 12, 1 }, \
+ { 0xba, 8, 12, 1 }, \
+ { 0xbb, 4, 12, 1 }, \
+ { 0xbb, 8, 12, 1 }, \
+ { 0xbc, 0, 12, 1 }, \
+ { 0xbc, 8, 12, 1 }, \
+ { 0xbd, 0, 12, 1 }, \
+ { 0xbd, 4, 12, 1 }, \
+ { 0xbe, 0, 12, 1 }, \
+ { 0xbf, 6, 12, 1 }, \
+ { 0xbf, 10, 12, 1 }, \
+ { 0xc0, 2, 12, 1 }, \
+ { 0xc0, 10, 12, 1 }, \
+ { 0xc1, 2, 12, 1 }, \
+ { 0xc1, 6, 12, 1 }, \
+ { 0xc2, 2, 12, 1 }, \
+ { 0xa4, 0, 12, 1 }, \
+ { 0xa4, 4, 12, 1 }, \
+ { 0xa5, 8, 12, 1 }, \
+ { 0xa6, 0, 12, 1 }
+
+#define RT5592_RF5592_40MHZ \
+ { 0xf1, 2, 10, 3 }, \
+ { 0xf1, 7, 10, 3 }, \
+ { 0xf2, 2, 10, 3 }, \
+ { 0xf2, 7, 10, 3 }, \
+ { 0xf3, 2, 10, 3 }, \
+ { 0xf3, 7, 10, 3 }, \
+ { 0xf4, 2, 10, 3 }, \
+ { 0xf4, 7, 10, 3 }, \
+ { 0xf5, 2, 10, 3 }, \
+ { 0xf5, 7, 10, 3 }, \
+ { 0xf6, 2, 10, 3 }, \
+ { 0xf6, 7, 10, 3 }, \
+ { 0xf7, 2, 10, 3 }, \
+ { 0xf8, 4, 10, 3 }, \
+ { 0x56, 4, 12, 1 }, \
+ { 0x56, 6, 12, 1 }, \
+ { 0x56, 8, 12, 1 }, \
+ { 0x57, 0, 12, 1 }, \
+ { 0x57, 2, 12, 1 }, \
+ { 0x57, 4, 12, 1 }, \
+ { 0x57, 8, 12, 1 }, \
+ { 0x57, 10, 12, 1 }, \
+ { 0x58, 0, 12, 1 }, \
+ { 0x58, 4, 12, 1 }, \
+ { 0x58, 6, 12, 1 }, \
+ { 0x58, 8, 12, 1 }, \
+ { 0x5b, 8, 12, 1 }, \
+ { 0x5b, 10, 12, 1 }, \
+ { 0x5c, 0, 12, 1 }, \
+ { 0x5c, 4, 12, 1 }, \
+ { 0x5c, 6, 12, 1 }, \
+ { 0x5c, 8, 12, 1 }, \
+ { 0x5d, 0, 12, 1 }, \
+ { 0x5d, 2, 12, 1 }, \
+ { 0x5d, 4, 12, 1 }, \
+ { 0x5d, 8, 12, 1 }, \
+ { 0x5d, 10, 12, 1 }, \
+ { 0x5e, 0, 12, 1 }, \
+ { 0x5e, 4, 12, 1 }, \
+ { 0x5e, 6, 12, 1 }, \
+ { 0x5e, 8, 12, 1 }, \
+ { 0x5f, 0, 12, 1 }, \
+ { 0x5f, 9, 12, 1 }, \
+ { 0x5f, 11, 12, 1 }, \
+ { 0x60, 1, 12, 1 }, \
+ { 0x60, 5, 12, 1 }, \
+ { 0x60, 7, 12, 1 }, \
+ { 0x60, 9, 12, 1 }, \
+ { 0x61, 1, 12, 1 }, \
+ { 0x52, 0, 12, 1 }, \
+ { 0x52, 4, 12, 1 }, \
+ { 0x52, 8, 12, 1 }, \
+ { 0x53, 0, 12, 1 }
+
+#define RT3070_DEF_RF \
+ { 4, 0x40 }, \
+ { 5, 0x03 }, \
+ { 6, 0x02 }, \
+ { 7, 0x60 }, \
+ { 9, 0x0f }, \
+ { 10, 0x41 }, \
+ { 11, 0x21 }, \
+ { 12, 0x7b }, \
+ { 14, 0x90 }, \
+ { 15, 0x58 }, \
+ { 16, 0xb3 }, \
+ { 17, 0x92 }, \
+ { 18, 0x2c }, \
+ { 19, 0x02 }, \
+ { 20, 0xba }, \
+ { 21, 0xdb }, \
+ { 24, 0x16 }, \
+ { 25, 0x03 }, \
+ { 29, 0x1f }
+
+#define RT3572_DEF_RF \
+ { 0, 0x70 }, \
+ { 1, 0x81 }, \
+ { 2, 0xf1 }, \
+ { 3, 0x02 }, \
+ { 4, 0x4c }, \
+ { 5, 0x05 }, \
+ { 6, 0x4a }, \
+ { 7, 0xd8 }, \
+ { 9, 0xc3 }, \
+ { 10, 0xf1 }, \
+ { 11, 0xb9 }, \
+ { 12, 0x70 }, \
+ { 13, 0x65 }, \
+ { 14, 0xa0 }, \
+ { 15, 0x53 }, \
+ { 16, 0x4c }, \
+ { 17, 0x23 }, \
+ { 18, 0xac }, \
+ { 19, 0x93 }, \
+ { 20, 0xb3 }, \
+ { 21, 0xd0 }, \
+ { 22, 0x00 }, \
+ { 23, 0x3c }, \
+ { 24, 0x16 }, \
+ { 25, 0x15 }, \
+ { 26, 0x85 }, \
+ { 27, 0x00 }, \
+ { 28, 0x00 }, \
+ { 29, 0x9b }, \
+ { 30, 0x09 }, \
+ { 31, 0x10 }
+
+#define RT3593_DEF_RF \
+ { 1, 0x03 }, \
+ { 3, 0x80 }, \
+ { 5, 0x00 }, \
+ { 6, 0x40 }, \
+ { 8, 0xf1 }, \
+ { 9, 0x02 }, \
+ { 10, 0xd3 }, \
+ { 11, 0x40 }, \
+ { 12, 0x4e }, \
+ { 13, 0x12 }, \
+ { 18, 0x40 }, \
+ { 22, 0x20 }, \
+ { 30, 0x10 }, \
+ { 31, 0x80 }, \
+ { 32, 0x78 }, \
+ { 33, 0x3b }, \
+ { 34, 0x3c }, \
+ { 35, 0xe0 }, \
+ { 38, 0x86 }, \
+ { 39, 0x23 }, \
+ { 44, 0xd3 }, \
+ { 45, 0xbb }, \
+ { 46, 0x60 }, \
+ { 49, 0x81 }, \
+ { 50, 0x86 }, \
+ { 51, 0x75 }, \
+ { 52, 0x45 }, \
+ { 53, 0x18 }, \
+ { 54, 0x18 }, \
+ { 55, 0x18 }, \
+ { 56, 0xdb }, \
+ { 57, 0x6e }
+
+#define RT5390_DEF_RF \
+ { 1, 0x0f }, \
+ { 2, 0x80 }, \
+ { 3, 0x88 }, \
+ { 5, 0x10 }, \
+ { 6, 0xa0 }, \
+ { 7, 0x00 }, \
+ { 10, 0x53 }, \
+ { 11, 0x4a }, \
+ { 12, 0x46 }, \
+ { 13, 0x9f }, \
+ { 14, 0x00 }, \
+ { 15, 0x00 }, \
+ { 16, 0x00 }, \
+ { 18, 0x03 }, \
+ { 19, 0x00 }, \
+ { 20, 0x00 }, \
+ { 21, 0x00 }, \
+ { 22, 0x20 }, \
+ { 23, 0x00 }, \
+ { 24, 0x00 }, \
+ { 25, 0xc0 }, \
+ { 26, 0x00 }, \
+ { 27, 0x09 }, \
+ { 28, 0x00 }, \
+ { 29, 0x10 }, \
+ { 30, 0x10 }, \
+ { 31, 0x80 }, \
+ { 32, 0x80 }, \
+ { 33, 0x00 }, \
+ { 34, 0x07 }, \
+ { 35, 0x12 }, \
+ { 36, 0x00 }, \
+ { 37, 0x08 }, \
+ { 38, 0x85 }, \
+ { 39, 0x1b }, \
+ { 40, 0x0b }, \
+ { 41, 0xbb }, \
+ { 42, 0xd2 }, \
+ { 43, 0x9a }, \
+ { 44, 0x0e }, \
+ { 45, 0xa2 }, \
+ { 46, 0x7b }, \
+ { 47, 0x00 }, \
+ { 48, 0x10 }, \
+ { 49, 0x94 }, \
+ { 52, 0x38 }, \
+ { 53, 0x84 }, \
+ { 54, 0x78 }, \
+ { 55, 0x44 }, \
+ { 56, 0x22 }, \
+ { 57, 0x80 }, \
+ { 58, 0x7f }, \
+ { 59, 0x8f }, \
+ { 60, 0x45 }, \
+ { 61, 0xdd }, \
+ { 62, 0x00 }, \
+ { 63, 0x00 }
+
+#define RT5392_DEF_RF \
+ { 1, 0x17 }, \
+ { 3, 0x88 }, \
+ { 5, 0x10 }, \
+ { 6, 0xe0 }, \
+ { 7, 0x00 }, \
+ { 10, 0x53 }, \
+ { 11, 0x4a }, \
+ { 12, 0x46 }, \
+ { 13, 0x9f }, \
+ { 14, 0x00 }, \
+ { 15, 0x00 }, \
+ { 16, 0x00 }, \
+ { 18, 0x03 }, \
+ { 19, 0x4d }, \
+ { 20, 0x00 }, \
+ { 21, 0x8d }, \
+ { 22, 0x20 }, \
+ { 23, 0x0b }, \
+ { 24, 0x44 }, \
+ { 25, 0x80 }, \
+ { 26, 0x82 }, \
+ { 27, 0x09 }, \
+ { 28, 0x00 }, \
+ { 29, 0x10 }, \
+ { 30, 0x10 }, \
+ { 31, 0x80 }, \
+ { 32, 0x20 }, \
+ { 33, 0xc0 }, \
+ { 34, 0x07 }, \
+ { 35, 0x12 }, \
+ { 36, 0x00 }, \
+ { 37, 0x08 }, \
+ { 38, 0x89 }, \
+ { 39, 0x1b }, \
+ { 40, 0x0f }, \
+ { 41, 0xbb }, \
+ { 42, 0xd5 }, \
+ { 43, 0x9b }, \
+ { 44, 0x0e }, \
+ { 45, 0xa2 }, \
+ { 46, 0x73 }, \
+ { 47, 0x0c }, \
+ { 48, 0x10 }, \
+ { 49, 0x94 }, \
+ { 50, 0x94 }, \
+ { 51, 0x3a }, \
+ { 52, 0x48 }, \
+ { 53, 0x44 }, \
+ { 54, 0x38 }, \
+ { 55, 0x43 }, \
+ { 56, 0xa1 }, \
+ { 57, 0x00 }, \
+ { 58, 0x39 }, \
+ { 59, 0x07 }, \
+ { 60, 0x45 }, \
+ { 61, 0x91 }, \
+ { 62, 0x39 }, \
+ { 63, 0x07 }
+
+#define RT5592_DEF_RF \
+ { 1, 0x3f }, \
+ { 3, 0x08 }, \
+ { 5, 0x10 }, \
+ { 6, 0xe4 }, \
+ { 7, 0x00 }, \
+ { 14, 0x00 }, \
+ { 15, 0x00 }, \
+ { 16, 0x00 }, \
+ { 18, 0x03 }, \
+ { 19, 0x4d }, \
+ { 20, 0x10 }, \
+ { 21, 0x8d }, \
+ { 26, 0x82 }, \
+ { 28, 0x00 }, \
+ { 29, 0x10 }, \
+ { 33, 0xc0 }, \
+ { 34, 0x07 }, \
+ { 35, 0x12 }, \
+ { 47, 0x0c }, \
+ { 53, 0x22 }, \
+ { 63, 0x07 }
+
+#define RT5592_2GHZ_DEF_RF \
+ { 10, 0x90 }, \
+ { 11, 0x4a }, \
+ { 12, 0x52 }, \
+ { 13, 0x42 }, \
+ { 22, 0x40 }, \
+ { 24, 0x4a }, \
+ { 25, 0x80 }, \
+ { 27, 0x42 }, \
+ { 36, 0x80 }, \
+ { 37, 0x08 }, \
+ { 38, 0x89 }, \
+ { 39, 0x1b }, \
+ { 40, 0x0d }, \
+ { 41, 0x9b }, \
+ { 42, 0xd5 }, \
+ { 43, 0x72 }, \
+ { 44, 0x0e }, \
+ { 45, 0xa2 }, \
+ { 46, 0x6b }, \
+ { 48, 0x10 }, \
+ { 51, 0x3e }, \
+ { 52, 0x48 }, \
+ { 54, 0x38 }, \
+ { 56, 0xa1 }, \
+ { 57, 0x00 }, \
+ { 58, 0x39 }, \
+ { 60, 0x45 }, \
+ { 61, 0x91 }, \
+ { 62, 0x39 }
+
+#define RT5592_5GHZ_DEF_RF \
+ { 10, 0x97 }, \
+ { 11, 0x40 }, \
+ { 25, 0xbf }, \
+ { 27, 0x42 }, \
+ { 36, 0x00 }, \
+ { 37, 0x04 }, \
+ { 38, 0x85 }, \
+ { 40, 0x42 }, \
+ { 41, 0xbb }, \
+ { 42, 0xd7 }, \
+ { 45, 0x41 }, \
+ { 48, 0x00 }, \
+ { 57, 0x77 }, \
+ { 60, 0x05 }, \
+ { 61, 0x01 }
+
+#define RT5592_CHAN_5GHZ \
+ { 36, 64, 12, 0x2e }, \
+ { 100, 165, 12, 0x0e }, \
+ { 36, 64, 13, 0x22 }, \
+ { 100, 165, 13, 0x42 }, \
+ { 36, 64, 22, 0x60 }, \
+ { 100, 165, 22, 0x40 }, \
+ { 36, 64, 23, 0x7f }, \
+ { 100, 153, 23, 0x3c }, \
+ { 155, 165, 23, 0x38 }, \
+ { 36, 50, 24, 0x09 }, \
+ { 52, 64, 24, 0x07 }, \
+ { 100, 153, 24, 0x06 }, \
+ { 155, 165, 24, 0x05 }, \
+ { 36, 64, 39, 0x1c }, \
+ { 100, 138, 39, 0x1a }, \
+ { 140, 165, 39, 0x18 }, \
+ { 36, 64, 43, 0x5b }, \
+ { 100, 138, 43, 0x3b }, \
+ { 140, 165, 43, 0x1b }, \
+ { 36, 64, 44, 0x40 }, \
+ { 100, 138, 44, 0x20 }, \
+ { 140, 165, 44, 0x10 }, \
+ { 36, 64, 46, 0x00 }, \
+ { 100, 138, 46, 0x18 }, \
+ { 140, 165, 46, 0x08 }, \
+ { 36, 64, 51, 0xfe }, \
+ { 100, 124, 51, 0xfc }, \
+ { 126, 165, 51, 0xec }, \
+ { 36, 64, 52, 0x0c }, \
+ { 100, 138, 52, 0x06 }, \
+ { 140, 165, 52, 0x06 }, \
+ { 36, 64, 54, 0xf8 }, \
+ { 100, 165, 54, 0xeb }, \
+ { 36, 50, 55, 0x06 }, \
+ { 52, 64, 55, 0x04 }, \
+ { 100, 138, 55, 0x01 }, \
+ { 140, 165, 55, 0x00 }, \
+ { 36, 50, 56, 0xd3 }, \
+ { 52, 128, 56, 0xbb }, \
+ { 130, 165, 56, 0xab }, \
+ { 36, 64, 58, 0x15 }, \
+ { 100, 116, 58, 0x1d }, \
+ { 118, 165, 58, 0x15 }, \
+ { 36, 64, 59, 0x7f }, \
+ { 100, 138, 59, 0x3f }, \
+ { 140, 165, 59, 0x7c }, \
+ { 36, 64, 62, 0x15 }, \
+ { 100, 116, 62, 0x1d }, \
+ { 118, 165, 62, 0x15 }
+
+union run_stats {
+ uint32_t raw;
+ struct {
+ uint16_t fail;
+ uint16_t pad;
+ } error;
+ struct {
+ uint16_t success;
+ uint16_t retry;
+ } tx;
+} __aligned(4);
+
+#endif /* _IF_RUNREG_H_ */
diff --git a/sys/dev/usb/wlan/if_runvar.h b/sys/dev/usb/wlan/if_runvar.h
new file mode 100644
index 000000000000..c81af76870b2
--- /dev/null
+++ b/sys/dev/usb/wlan/if_runvar.h
@@ -0,0 +1,266 @@
+/* $OpenBSD: if_runvar.h,v 1.3 2009/03/26 20:17:27 damien Exp $ */
+
+/*-
+ * Copyright (c) 2008,2009 Damien Bergamini <damien.bergamini@free.fr>
+ * ported to FreeBSD by Akinori Furukoshi <moonlightakkiy@yahoo.ca>
+ * USB Consulting, Hans Petter Selasky <hselasky@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.
+ */
+
+#ifndef _IF_RUNVAR_H_
+#define _IF_RUNVAR_H_
+
+/* Support up to 4KB frames - useful for A-MSDU/FF. */
+#define RUN_MAX_RXSZ \
+ MIN(4096, MJUMPAGESIZE)
+
+/* Support up to 8KB frames - useful for A-MSDU/FF. */
+#define RUN_MAX_TXSZ \
+ (sizeof (struct rt2870_txd) + \
+ sizeof (struct rt2860_txwi) + \
+ 8192 + 11)
+
+#define RUN_TX_TIMEOUT 5000 /* ms */
+
+/* Tx ring count was 8/endpoint, now 32 for all 4 (or 6) endpoints. */
+#define RUN_TX_RING_COUNT 32
+#define RUN_RX_RING_COUNT 1
+
+#define RT2870_WCID_MAX 64
+#define RUN_AID2WCID(aid) ((aid) & 0xff)
+
+#define RUN_VAP_MAX 8
+
+struct run_rx_radiotap_header {
+ struct ieee80211_radiotap_header wr_ihdr;
+ uint64_t wr_tsf;
+ uint8_t wr_flags;
+ uint8_t wr_rate;
+ uint16_t wr_chan_freq;
+ uint16_t wr_chan_flags;
+ int8_t wr_dbm_antsignal;
+ uint8_t wr_antenna;
+ uint8_t wr_antsignal;
+} __packed __aligned(8);
+
+#define RUN_RX_RADIOTAP_PRESENT \
+ (1 << IEEE80211_RADIOTAP_TSFT | \
+ 1 << IEEE80211_RADIOTAP_FLAGS | \
+ 1 << IEEE80211_RADIOTAP_RATE | \
+ 1 << IEEE80211_RADIOTAP_CHANNEL | \
+ 1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL | \
+ 1 << IEEE80211_RADIOTAP_ANTENNA | \
+ 1 << IEEE80211_RADIOTAP_DB_ANTSIGNAL)
+
+struct run_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_hwqueue;
+} __packed;
+
+#define IEEE80211_RADIOTAP_HWQUEUE 15
+
+#define RUN_TX_RADIOTAP_PRESENT \
+ (1 << IEEE80211_RADIOTAP_FLAGS | \
+ 1 << IEEE80211_RADIOTAP_RATE | \
+ 1 << IEEE80211_RADIOTAP_CHANNEL | \
+ 1 << IEEE80211_RADIOTAP_HWQUEUE)
+
+struct run_softc;
+
+struct run_tx_data {
+ STAILQ_ENTRY(run_tx_data) next;
+ struct run_softc *sc;
+ struct mbuf *m;
+ struct ieee80211_node *ni;
+ uint32_t align[0]; /* dummy field */
+ uint8_t desc[sizeof(struct rt2870_txd) +
+ sizeof(struct rt2860_txwi)];
+ uint8_t ridx;
+};
+STAILQ_HEAD(run_tx_data_head, run_tx_data);
+
+struct run_node {
+ struct ieee80211_node ni;
+ uint8_t amrr_ridx;
+ uint8_t mgt_ridx;
+ uint8_t fix_ridx;
+};
+#define RUN_NODE(ni) ((struct run_node *)(ni))
+
+struct run_cmdq {
+ void *arg0;
+ void *arg1;
+ void (*func)(void *);
+ struct ieee80211_key *k;
+ struct ieee80211_key key;
+ uint8_t mac[IEEE80211_ADDR_LEN];
+ uint8_t wcid;
+};
+
+struct run_vap {
+ struct ieee80211vap vap;
+ struct mbuf *beacon_mbuf;
+
+ int (*newstate)(struct ieee80211vap *,
+ enum ieee80211_state, int);
+ void (*recv_mgmt)(struct ieee80211_node *,
+ struct mbuf *, int,
+ const struct ieee80211_rx_stats *,
+ int, int);
+
+ uint8_t rvp_id;
+};
+#define RUN_VAP(vap) ((struct run_vap *)(vap))
+
+/*
+ * There are 7 bulk endpoints: 1 for RX
+ * and 6 for TX (4 EDCAs + HCCA + Prio).
+ * Update 03-14-2009: some devices like the Planex GW-US300MiniS
+ * seem to have only 4 TX bulk endpoints (Fukaumi Naoki).
+ */
+enum {
+ RUN_BULK_TX_BE, /* = WME_AC_BE */
+ RUN_BULK_TX_BK, /* = WME_AC_BK */
+ RUN_BULK_TX_VI, /* = WME_AC_VI */
+ RUN_BULK_TX_VO, /* = WME_AC_VO */
+ RUN_BULK_TX_HCCA,
+ RUN_BULK_TX_PRIO,
+ RUN_BULK_RX,
+ RUN_N_XFER,
+};
+
+#define RUN_EP_QUEUES RUN_BULK_RX
+
+struct run_endpoint_queue {
+ struct run_tx_data tx_data[RUN_TX_RING_COUNT];
+ struct run_tx_data_head tx_qh;
+ struct run_tx_data_head tx_fh;
+ uint32_t tx_nfree;
+};
+
+struct run_softc {
+ struct mtx sc_mtx;
+ struct ieee80211com sc_ic;
+ struct ieee80211_ratectl_tx_stats sc_txs;
+ struct mbufq sc_snd;
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+ int sc_need_fwload;
+
+ int sc_flags;
+#define RUN_FLAG_FWLOAD_NEEDED 0x01
+#define RUN_RUNNING 0x02
+
+ uint16_t wcid_stats[RT2870_WCID_MAX + 1][3];
+#define RUN_TXCNT 0
+#define RUN_SUCCESS 1
+#define RUN_RETRY 2
+
+ int (*sc_srom_read)(struct run_softc *,
+ uint16_t, uint16_t *);
+
+ uint16_t mac_ver;
+ uint16_t mac_rev;
+ uint16_t rf_rev;
+ uint8_t freq;
+ uint8_t ntxchains;
+ uint8_t nrxchains;
+
+ uint8_t bbp25;
+ uint8_t bbp26;
+ uint8_t rf24_20mhz;
+ uint8_t rf24_40mhz;
+ uint8_t patch_dac;
+ uint8_t rfswitch;
+ uint8_t ext_2ghz_lna;
+ uint8_t ext_5ghz_lna;
+ uint8_t calib_2ghz;
+ uint8_t calib_5ghz;
+ uint8_t txmixgain_2ghz;
+ uint8_t txmixgain_5ghz;
+ int8_t txpow1[54];
+ int8_t txpow2[54];
+ int8_t txpow3[54];
+ int8_t rssi_2ghz[3];
+ int8_t rssi_5ghz[3];
+ uint8_t lna[4];
+
+ struct {
+ uint8_t reg;
+ uint8_t val;
+ } bbp[10], rf[10];
+ uint8_t leds;
+ uint16_t led[3];
+ uint32_t txpow20mhz[5];
+ uint32_t txpow40mhz_2ghz[5];
+ uint32_t txpow40mhz_5ghz[5];
+
+ struct run_endpoint_queue sc_epq[RUN_EP_QUEUES];
+
+ struct task ratectl_task;
+ struct usb_callout ratectl_ch;
+ uint8_t ratectl_run;
+#define RUN_RATECTL_OFF 0
+
+/* need to be power of 2, otherwise RUN_CMDQ_GET fails */
+#define RUN_CMDQ_MAX 16
+#define RUN_CMDQ_MASQ (RUN_CMDQ_MAX - 1)
+ struct run_cmdq cmdq[RUN_CMDQ_MAX];
+ struct task cmdq_task;
+ uint32_t cmdq_store;
+ uint8_t cmdq_exec;
+ uint8_t cmdq_run;
+ uint8_t cmdq_key_set;
+#define RUN_CMDQ_ABORT 0
+#define RUN_CMDQ_GO 1
+
+ struct usb_xfer *sc_xfer[RUN_N_XFER];
+
+ struct mbuf *rx_m;
+
+ uint8_t fifo_cnt;
+
+ uint8_t running;
+ uint8_t runbmap;
+ uint8_t ap_running;
+ uint8_t adhoc_running;
+ uint8_t sta_running;
+ uint8_t rvp_cnt;
+ uint8_t rvp_bmap;
+ uint8_t sc_detached;
+
+ uint8_t sc_bssid[IEEE80211_ADDR_LEN];
+
+ union {
+ struct run_rx_radiotap_header th;
+ uint8_t pad[64];
+ } sc_rxtapu;
+#define sc_rxtap sc_rxtapu.th
+
+ union {
+ struct run_tx_radiotap_header th;
+ uint8_t pad[64];
+ } sc_txtapu;
+#define sc_txtap sc_txtapu.th
+};
+
+#define RUN_LOCK(sc) mtx_lock(&(sc)->sc_mtx)
+#define RUN_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx)
+#define RUN_LOCK_ASSERT(sc, t) mtx_assert(&(sc)->sc_mtx, t)
+
+#endif /* _IF_RUNVAR_H_ */
diff --git a/sys/dev/usb/wlan/if_uath.c b/sys/dev/usb/wlan/if_uath.c
new file mode 100644
index 000000000000..b49c75032d77
--- /dev/null
+++ b/sys/dev/usb/wlan/if_uath.c
@@ -0,0 +1,2886 @@
+/*-
+ * SPDX-License-Identifier: (BSD-2-Clause AND BSD-1-Clause)
+ *
+ * Copyright (c) 2006 Sam Leffler, Errno Consulting
+ * Copyright (c) 2008-2009 Weongyo Jeong <weongyo@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.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
+ * redistribution must be conditioned upon including a substantially
+ * similar Disclaimer requirement for further binary redistribution.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
+ */
+
+/*
+ * This driver is distantly derived from a driver of the same name
+ * by Damien Bergamini. The original copyright is included below:
+ *
+ * Copyright (c) 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.
+ */
+
+/*-
+ * Driver for Atheros AR5523 USB parts.
+ *
+ * The driver requires firmware to be loaded into the device. This
+ * is done on device discovery from a user application (uathload)
+ * that is launched by devd when a device with suitable product ID
+ * is recognized. Once firmware has been loaded the device will
+ * reset the USB port and re-attach with the original product ID+1
+ * and this driver will be attached. The firmware is licensed for
+ * general use (royalty free) and may be incorporated in products.
+ * Note that the firmware normally packaged with the NDIS drivers
+ * for these devices does not work in this way and so does not work
+ * with this driver.
+ */
+
+#include "opt_wlan.h"
+
+#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 <net/bpf.h>
+#include <net/if.h>
+#include <net/if_var.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_input.h>
+#include <net80211/ieee80211_regdomain.h>
+#include <net80211/ieee80211_radiotap.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include "usbdevs.h"
+
+#include <dev/usb/wlan/if_uathreg.h>
+#include <dev/usb/wlan/if_uathvar.h>
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uath, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB Atheros");
+
+static int uath_countrycode = CTRY_DEFAULT; /* country code */
+SYSCTL_INT(_hw_usb_uath, OID_AUTO, countrycode, CTLFLAG_RWTUN, &uath_countrycode,
+ 0, "country code");
+static int uath_regdomain = 0; /* regulatory domain */
+SYSCTL_INT(_hw_usb_uath, OID_AUTO, regdomain, CTLFLAG_RD, &uath_regdomain,
+ 0, "regulatory domain");
+
+#ifdef UATH_DEBUG
+int uath_debug = 0;
+SYSCTL_INT(_hw_usb_uath, OID_AUTO, debug, CTLFLAG_RWTUN, &uath_debug, 0,
+ "uath debug level");
+enum {
+ UATH_DEBUG_XMIT = 0x00000001, /* basic xmit operation */
+ UATH_DEBUG_XMIT_DUMP = 0x00000002, /* xmit dump */
+ UATH_DEBUG_RECV = 0x00000004, /* basic recv operation */
+ UATH_DEBUG_TX_PROC = 0x00000008, /* tx ISR proc */
+ UATH_DEBUG_RX_PROC = 0x00000010, /* rx ISR proc */
+ UATH_DEBUG_RECV_ALL = 0x00000020, /* trace all frames (beacons) */
+ UATH_DEBUG_INIT = 0x00000040, /* initialization of dev */
+ UATH_DEBUG_DEVCAP = 0x00000080, /* dev caps */
+ UATH_DEBUG_CMDS = 0x00000100, /* commands */
+ UATH_DEBUG_CMDS_DUMP = 0x00000200, /* command buffer dump */
+ UATH_DEBUG_RESET = 0x00000400, /* reset processing */
+ UATH_DEBUG_STATE = 0x00000800, /* 802.11 state transitions */
+ UATH_DEBUG_MULTICAST = 0x00001000, /* multicast */
+ UATH_DEBUG_WME = 0x00002000, /* WME */
+ UATH_DEBUG_CHANNEL = 0x00004000, /* channel */
+ UATH_DEBUG_RATES = 0x00008000, /* rates */
+ UATH_DEBUG_CRYPTO = 0x00010000, /* crypto */
+ UATH_DEBUG_LED = 0x00020000, /* LED */
+ UATH_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
+
+/* recognized device vendors/products */
+static const STRUCT_USB_HOST_ID uath_devs[] = {
+#define UATH_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) }
+ UATH_DEV(ACCTON, SMCWUSBTG2),
+ UATH_DEV(ATHEROS, AR5523),
+ UATH_DEV(ATHEROS2, AR5523_1),
+ UATH_DEV(ATHEROS2, AR5523_2),
+ UATH_DEV(ATHEROS2, AR5523_3),
+ UATH_DEV(CONCEPTRONIC, AR5523_1),
+ UATH_DEV(CONCEPTRONIC, AR5523_2),
+ UATH_DEV(DLINK, DWLAG122),
+ UATH_DEV(DLINK, DWLAG132),
+ UATH_DEV(DLINK, DWLG132),
+ UATH_DEV(DLINK2, DWA120),
+ UATH_DEV(GIGASET, AR5523),
+ UATH_DEV(GIGASET, SMCWUSBTG),
+ UATH_DEV(GLOBALSUN, AR5523_1),
+ UATH_DEV(GLOBALSUN, AR5523_2),
+ UATH_DEV(NETGEAR, WG111U),
+ UATH_DEV(NETGEAR3, WG111T),
+ UATH_DEV(NETGEAR3, WPN111),
+ UATH_DEV(NETGEAR3, WPN111_2),
+ UATH_DEV(UMEDIA, TEW444UBEU),
+ UATH_DEV(UMEDIA, AR5523_2),
+ UATH_DEV(WISTRONNEWEB, AR5523_1),
+ UATH_DEV(WISTRONNEWEB, AR5523_2),
+ UATH_DEV(WISTRONNEWEB, AR5523_2_ALT),
+ UATH_DEV(ZCOM, AR5523)
+#undef UATH_DEV
+};
+
+static usb_callback_t uath_intr_rx_callback;
+static usb_callback_t uath_intr_tx_callback;
+static usb_callback_t uath_bulk_rx_callback;
+static usb_callback_t uath_bulk_tx_callback;
+
+static const struct usb_config uath_usbconfig[UATH_N_XFERS] = {
+ [UATH_INTR_RX] = {
+ .type = UE_BULK,
+ .endpoint = 0x1,
+ .direction = UE_DIR_IN,
+ .bufsize = UATH_MAX_CMDSZ,
+ .flags = {
+ .pipe_bof = 1,
+ .short_xfer_ok = 1
+ },
+ .callback = uath_intr_rx_callback
+ },
+ [UATH_INTR_TX] = {
+ .type = UE_BULK,
+ .endpoint = 0x1,
+ .direction = UE_DIR_OUT,
+ .bufsize = UATH_MAX_CMDSZ * UATH_CMD_LIST_COUNT,
+ .flags = {
+ .force_short_xfer = 1,
+ .pipe_bof = 1,
+ },
+ .callback = uath_intr_tx_callback,
+ .timeout = UATH_CMD_TIMEOUT
+ },
+ [UATH_BULK_RX] = {
+ .type = UE_BULK,
+ .endpoint = 0x2,
+ .direction = UE_DIR_IN,
+ .bufsize = MCLBYTES,
+ .flags = {
+ .ext_buffer = 1,
+ .pipe_bof = 1,
+ .short_xfer_ok = 1
+ },
+ .callback = uath_bulk_rx_callback
+ },
+ [UATH_BULK_TX] = {
+ .type = UE_BULK,
+ .endpoint = 0x2,
+ .direction = UE_DIR_OUT,
+ .bufsize = UATH_MAX_TXBUFSZ * UATH_TX_DATA_LIST_COUNT,
+ .flags = {
+ .force_short_xfer = 1,
+ .pipe_bof = 1
+ },
+ .callback = uath_bulk_tx_callback,
+ .timeout = UATH_DATA_TIMEOUT
+ }
+};
+
+static struct ieee80211vap *uath_vap_create(struct ieee80211com *,
+ const char [IFNAMSIZ], int, enum ieee80211_opmode, int,
+ const uint8_t [IEEE80211_ADDR_LEN],
+ const uint8_t [IEEE80211_ADDR_LEN]);
+static void uath_vap_delete(struct ieee80211vap *);
+static int uath_alloc_cmd_list(struct uath_softc *, struct uath_cmd []);
+static void uath_free_cmd_list(struct uath_softc *, struct uath_cmd []);
+static int uath_host_available(struct uath_softc *);
+static int uath_get_capability(struct uath_softc *, uint32_t, uint32_t *);
+static int uath_get_devcap(struct uath_softc *);
+static struct uath_cmd *
+ uath_get_cmdbuf(struct uath_softc *);
+static int uath_cmd_read(struct uath_softc *, uint32_t, const void *,
+ int, void *, int, int);
+static int uath_cmd_write(struct uath_softc *, uint32_t, const void *,
+ int, int);
+static void uath_stat(void *);
+#ifdef UATH_DEBUG
+static void uath_dump_cmd(const uint8_t *, int, char);
+static const char *
+ uath_codename(int);
+#endif
+static int uath_get_devstatus(struct uath_softc *,
+ uint8_t macaddr[IEEE80211_ADDR_LEN]);
+static int uath_get_status(struct uath_softc *, uint32_t, void *, int);
+static int uath_alloc_rx_data_list(struct uath_softc *);
+static int uath_alloc_tx_data_list(struct uath_softc *);
+static void uath_free_rx_data_list(struct uath_softc *);
+static void uath_free_tx_data_list(struct uath_softc *);
+static int uath_init(struct uath_softc *);
+static void uath_stop(struct uath_softc *);
+static void uath_parent(struct ieee80211com *);
+static int uath_transmit(struct ieee80211com *, struct mbuf *);
+static void uath_start(struct uath_softc *);
+static int uath_raw_xmit(struct ieee80211_node *, struct mbuf *,
+ const struct ieee80211_bpf_params *);
+static void uath_scan_start(struct ieee80211com *);
+static void uath_scan_end(struct ieee80211com *);
+static void uath_set_channel(struct ieee80211com *);
+static void uath_update_mcast(struct ieee80211com *);
+static void uath_update_promisc(struct ieee80211com *);
+static int uath_config(struct uath_softc *, uint32_t, uint32_t);
+static int uath_config_multi(struct uath_softc *, uint32_t, const void *,
+ int);
+static int uath_switch_channel(struct uath_softc *,
+ struct ieee80211_channel *);
+static int uath_set_rxfilter(struct uath_softc *, uint32_t, uint32_t);
+static void uath_watchdog(void *);
+static void uath_abort_xfers(struct uath_softc *);
+static int uath_dataflush(struct uath_softc *);
+static int uath_cmdflush(struct uath_softc *);
+static int uath_flush(struct uath_softc *);
+static int uath_set_ledstate(struct uath_softc *, int);
+static int uath_set_chan(struct uath_softc *, struct ieee80211_channel *);
+static int uath_reset_tx_queues(struct uath_softc *);
+static int uath_wme_init(struct uath_softc *);
+static struct uath_data *
+ uath_getbuf(struct uath_softc *);
+static int uath_newstate(struct ieee80211vap *, enum ieee80211_state,
+ int);
+static int uath_set_key(struct uath_softc *,
+ const struct ieee80211_key *, int);
+static int uath_set_keys(struct uath_softc *, struct ieee80211vap *);
+static void uath_sysctl_node(struct uath_softc *);
+
+static int
+uath_match(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != UATH_CONFIG_INDEX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != UATH_IFACE_INDEX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(uath_devs, sizeof(uath_devs), uaa));
+}
+
+static int
+uath_attach(device_t dev)
+{
+ struct uath_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint8_t bands[IEEE80211_MODE_BYTES];
+ uint8_t iface_index = UATH_IFACE_INDEX; /* XXX */
+ usb_error_t error;
+
+ sc->sc_dev = dev;
+ sc->sc_udev = uaa->device;
+#ifdef UATH_DEBUG
+ sc->sc_debug = uath_debug;
+#endif
+ device_set_usb_desc(dev);
+
+ /*
+ * Only post-firmware devices here.
+ */
+ mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev), MTX_NETWORK_LOCK,
+ MTX_DEF);
+ callout_init(&sc->stat_ch, 0);
+ callout_init_mtx(&sc->watchdog_ch, &sc->sc_mtx, 0);
+ mbufq_init(&sc->sc_snd, ifqmaxlen);
+
+ error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer,
+ uath_usbconfig, UATH_N_XFERS, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "could not allocate USB transfers, "
+ "err=%s\n", usbd_errstr(error));
+ goto fail;
+ }
+
+ sc->sc_cmd_dma_buf =
+ usbd_xfer_get_frame_buffer(sc->sc_xfer[UATH_INTR_TX], 0);
+ sc->sc_tx_dma_buf =
+ usbd_xfer_get_frame_buffer(sc->sc_xfer[UATH_BULK_TX], 0);
+
+ /*
+ * Setup buffers for firmware commands.
+ */
+ error = uath_alloc_cmd_list(sc, sc->sc_cmd);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "could not allocate Tx command list\n");
+ goto fail1;
+ }
+
+ /*
+ * We're now ready to send+receive firmware commands.
+ */
+ UATH_LOCK(sc);
+ error = uath_host_available(sc);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "could not initialize adapter\n");
+ goto fail2;
+ }
+ error = uath_get_devcap(sc);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "could not get device capabilities\n");
+ goto fail2;
+ }
+ UATH_UNLOCK(sc);
+
+ /* Create device sysctl node. */
+ uath_sysctl_node(sc);
+
+ UATH_LOCK(sc);
+ error = uath_get_devstatus(sc, ic->ic_macaddr);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "could not get device status\n");
+ goto fail2;
+ }
+
+ /*
+ * Allocate xfers for Rx/Tx data pipes.
+ */
+ error = uath_alloc_rx_data_list(sc);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "could not allocate Rx data list\n");
+ goto fail2;
+ }
+ error = uath_alloc_tx_data_list(sc);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "could not allocate Tx data list\n");
+ goto fail2;
+ }
+ UATH_UNLOCK(sc);
+
+ ic->ic_softc = sc;
+ ic->ic_name = device_get_nameunit(dev);
+ 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_WPA | /* 802.11i */
+ IEEE80211_C_BGSCAN | /* capable of bg scanning */
+ IEEE80211_C_TXFRAG; /* handle tx frags */
+
+ /* put a regulatory domain to reveal informations. */
+ uath_regdomain = sc->sc_devcap.regDomain;
+
+ memset(bands, 0, sizeof(bands));
+ setbit(bands, IEEE80211_MODE_11B);
+ setbit(bands, IEEE80211_MODE_11G);
+ if ((sc->sc_devcap.analog5GhzRevision & 0xf0) == 0x30)
+ setbit(bands, IEEE80211_MODE_11A);
+ /* XXX turbo */
+ ieee80211_init_channels(ic, NULL, bands);
+
+ ieee80211_ifattach(ic);
+
+ /* Note: this has to happen AFTER ieee80211_ifattach() */
+ ieee80211_set_software_ciphers(ic,
+ IEEE80211_CRYPTO_WEP | IEEE80211_CRYPTO_TKIP |
+ IEEE80211_CRYPTO_AES_CCM | IEEE80211_CRYPTO_AES_GCM_128);
+
+ ic->ic_raw_xmit = uath_raw_xmit;
+ ic->ic_scan_start = uath_scan_start;
+ ic->ic_scan_end = uath_scan_end;
+ ic->ic_set_channel = uath_set_channel;
+ ic->ic_vap_create = uath_vap_create;
+ ic->ic_vap_delete = uath_vap_delete;
+ ic->ic_update_mcast = uath_update_mcast;
+ ic->ic_update_promisc = uath_update_promisc;
+ ic->ic_transmit = uath_transmit;
+ ic->ic_parent = uath_parent;
+
+ ieee80211_radiotap_attach(ic,
+ &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap),
+ UATH_TX_RADIOTAP_PRESENT,
+ &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap),
+ UATH_RX_RADIOTAP_PRESENT);
+
+ if (bootverbose)
+ ieee80211_announce(ic);
+
+ return (0);
+
+fail2: UATH_UNLOCK(sc);
+ uath_free_cmd_list(sc, sc->sc_cmd);
+fail1: usbd_transfer_unsetup(sc->sc_xfer, UATH_N_XFERS);
+fail:
+ return (error);
+}
+
+static int
+uath_detach(device_t dev)
+{
+ struct uath_softc *sc = device_get_softc(dev);
+ struct ieee80211com *ic = &sc->sc_ic;
+ unsigned x;
+
+ /*
+ * Prevent further allocations from RX/TX/CMD
+ * data lists and ioctls
+ */
+ UATH_LOCK(sc);
+ sc->sc_flags |= UATH_FLAG_INVALID;
+
+ STAILQ_INIT(&sc->sc_rx_active);
+ STAILQ_INIT(&sc->sc_rx_inactive);
+
+ STAILQ_INIT(&sc->sc_tx_active);
+ STAILQ_INIT(&sc->sc_tx_inactive);
+ STAILQ_INIT(&sc->sc_tx_pending);
+
+ STAILQ_INIT(&sc->sc_cmd_active);
+ STAILQ_INIT(&sc->sc_cmd_pending);
+ STAILQ_INIT(&sc->sc_cmd_waiting);
+ STAILQ_INIT(&sc->sc_cmd_inactive);
+
+ uath_stop(sc);
+ UATH_UNLOCK(sc);
+
+ callout_drain(&sc->stat_ch);
+ callout_drain(&sc->watchdog_ch);
+
+ /* drain USB transfers */
+ for (x = 0; x != UATH_N_XFERS; x++)
+ usbd_transfer_drain(sc->sc_xfer[x]);
+
+ /* free data buffers */
+ UATH_LOCK(sc);
+ uath_free_rx_data_list(sc);
+ uath_free_tx_data_list(sc);
+ uath_free_cmd_list(sc, sc->sc_cmd);
+ UATH_UNLOCK(sc);
+
+ /* free USB transfers and some data buffers */
+ usbd_transfer_unsetup(sc->sc_xfer, UATH_N_XFERS);
+
+ ieee80211_ifdetach(ic);
+ mbufq_drain(&sc->sc_snd);
+ mtx_destroy(&sc->sc_mtx);
+ return (0);
+}
+
+static void
+uath_free_cmd_list(struct uath_softc *sc, struct uath_cmd cmds[])
+{
+ int i;
+
+ for (i = 0; i != UATH_CMD_LIST_COUNT; i++)
+ cmds[i].buf = NULL;
+}
+
+static int
+uath_alloc_cmd_list(struct uath_softc *sc, struct uath_cmd cmds[])
+{
+ int i;
+
+ STAILQ_INIT(&sc->sc_cmd_active);
+ STAILQ_INIT(&sc->sc_cmd_pending);
+ STAILQ_INIT(&sc->sc_cmd_waiting);
+ STAILQ_INIT(&sc->sc_cmd_inactive);
+
+ for (i = 0; i != UATH_CMD_LIST_COUNT; i++) {
+ struct uath_cmd *cmd = &cmds[i];
+
+ cmd->sc = sc; /* backpointer for callbacks */
+ cmd->msgid = i;
+ cmd->buf = ((uint8_t *)sc->sc_cmd_dma_buf) +
+ (i * UATH_MAX_CMDSZ);
+ STAILQ_INSERT_TAIL(&sc->sc_cmd_inactive, cmd, next);
+ UATH_STAT_INC(sc, st_cmd_inactive);
+ }
+ return (0);
+}
+
+static int
+uath_host_available(struct uath_softc *sc)
+{
+ struct uath_cmd_host_available setup;
+
+ UATH_ASSERT_LOCKED(sc);
+
+ /* inform target the host is available */
+ setup.sw_ver_major = htobe32(ATH_SW_VER_MAJOR);
+ setup.sw_ver_minor = htobe32(ATH_SW_VER_MINOR);
+ setup.sw_ver_patch = htobe32(ATH_SW_VER_PATCH);
+ setup.sw_ver_build = htobe32(ATH_SW_VER_BUILD);
+ return uath_cmd_read(sc, WDCMSG_HOST_AVAILABLE,
+ &setup, sizeof setup, NULL, 0, 0);
+}
+
+#ifdef UATH_DEBUG
+static void
+uath_dump_cmd(const uint8_t *buf, int len, char prefix)
+{
+ const char *sep = "";
+ int i;
+
+ for (i = 0; i < len; i++) {
+ if ((i % 16) == 0) {
+ printf("%s%c ", sep, prefix);
+ sep = "\n";
+ }
+ else if ((i % 4) == 0)
+ printf(" ");
+ printf("%02x", buf[i]);
+ }
+ printf("\n");
+}
+
+static const char *
+uath_codename(int code)
+{
+ static const char *names[] = {
+ "0x00",
+ "HOST_AVAILABLE",
+ "BIND",
+ "TARGET_RESET",
+ "TARGET_GET_CAPABILITY",
+ "TARGET_SET_CONFIG",
+ "TARGET_GET_STATUS",
+ "TARGET_GET_STATS",
+ "TARGET_START",
+ "TARGET_STOP",
+ "TARGET_ENABLE",
+ "TARGET_DISABLE",
+ "CREATE_CONNECTION",
+ "UPDATE_CONNECT_ATTR",
+ "DELETE_CONNECT",
+ "SEND",
+ "FLUSH",
+ "STATS_UPDATE",
+ "BMISS",
+ "DEVICE_AVAIL",
+ "SEND_COMPLETE",
+ "DATA_AVAIL",
+ "SET_PWR_MODE",
+ "BMISS_ACK",
+ "SET_LED_STEADY",
+ "SET_LED_BLINK",
+ "SETUP_BEACON_DESC",
+ "BEACON_INIT",
+ "RESET_KEY_CACHE",
+ "RESET_KEY_CACHE_ENTRY",
+ "SET_KEY_CACHE_ENTRY",
+ "SET_DECOMP_MASK",
+ "SET_REGULATORY_DOMAIN",
+ "SET_LED_STATE",
+ "WRITE_ASSOCID",
+ "SET_STA_BEACON_TIMERS",
+ "GET_TSF",
+ "RESET_TSF",
+ "SET_ADHOC_MODE",
+ "SET_BASIC_RATE",
+ "MIB_CONTROL",
+ "GET_CHANNEL_DATA",
+ "GET_CUR_RSSI",
+ "SET_ANTENNA_SWITCH",
+ "0x2c", "0x2d", "0x2e",
+ "USE_SHORT_SLOT_TIME",
+ "SET_POWER_MODE",
+ "SETUP_PSPOLL_DESC",
+ "SET_RX_MULTICAST_FILTER",
+ "RX_FILTER",
+ "PER_CALIBRATION",
+ "RESET",
+ "DISABLE",
+ "PHY_DISABLE",
+ "SET_TX_POWER_LIMIT",
+ "SET_TX_QUEUE_PARAMS",
+ "SETUP_TX_QUEUE",
+ "RELEASE_TX_QUEUE",
+ };
+ static char buf[8];
+
+ if (code < nitems(names))
+ return names[code];
+ if (code == WDCMSG_SET_DEFAULT_KEY)
+ return "SET_DEFAULT_KEY";
+ snprintf(buf, sizeof(buf), "0x%02x", code);
+ return buf;
+}
+#endif
+
+/*
+ * Low-level function to send read or write commands to the firmware.
+ */
+static int
+uath_cmdsend(struct uath_softc *sc, uint32_t code, const void *idata, int ilen,
+ void *odata, int olen, int flags)
+{
+ struct uath_cmd_hdr *hdr;
+ struct uath_cmd *cmd;
+ int error;
+
+ UATH_ASSERT_LOCKED(sc);
+
+ /* grab a xfer */
+ cmd = uath_get_cmdbuf(sc);
+ if (cmd == NULL) {
+ device_printf(sc->sc_dev, "%s: empty inactive queue\n",
+ __func__);
+ return (ENOBUFS);
+ }
+ cmd->flags = flags;
+ /* always bulk-out a multiple of 4 bytes */
+ cmd->buflen = roundup2(sizeof(struct uath_cmd_hdr) + ilen, 4);
+
+ hdr = (struct uath_cmd_hdr *)cmd->buf;
+ memset(hdr, 0, sizeof(struct uath_cmd_hdr));
+ hdr->len = htobe32(cmd->buflen);
+ hdr->code = htobe32(code);
+ hdr->msgid = cmd->msgid; /* don't care about endianness */
+ hdr->magic = htobe32((cmd->flags & UATH_CMD_FLAG_MAGIC) ? 1 << 24 : 0);
+ memcpy((uint8_t *)(hdr + 1), idata, ilen);
+
+#ifdef UATH_DEBUG
+ if (sc->sc_debug & UATH_DEBUG_CMDS) {
+ printf("%s: send %s [flags 0x%x] olen %d\n",
+ __func__, uath_codename(code), cmd->flags, olen);
+ if (sc->sc_debug & UATH_DEBUG_CMDS_DUMP)
+ uath_dump_cmd(cmd->buf, cmd->buflen, '+');
+ }
+#endif
+ cmd->odata = odata;
+ KASSERT(odata == NULL ||
+ olen < UATH_MAX_CMDSZ - sizeof(*hdr) + sizeof(uint32_t),
+ ("odata %p olen %u", odata, olen));
+ cmd->olen = olen;
+
+ STAILQ_INSERT_TAIL(&sc->sc_cmd_pending, cmd, next);
+ UATH_STAT_INC(sc, st_cmd_pending);
+ usbd_transfer_start(sc->sc_xfer[UATH_INTR_TX]);
+
+ if (cmd->flags & UATH_CMD_FLAG_READ) {
+ usbd_transfer_start(sc->sc_xfer[UATH_INTR_RX]);
+
+ /* wait at most two seconds for command reply */
+ error = mtx_sleep(cmd, &sc->sc_mtx, 0, "uathcmd", 2 * hz);
+ cmd->odata = NULL; /* in case reply comes too late */
+ if (error != 0) {
+ device_printf(sc->sc_dev, "timeout waiting for reply "
+ "to cmd 0x%x (%u)\n", code, code);
+ } else if (cmd->olen != olen) {
+ device_printf(sc->sc_dev, "unexpected reply data count "
+ "to cmd 0x%x (%u), got %u, expected %u\n",
+ code, code, cmd->olen, olen);
+ error = EINVAL;
+ }
+ return (error);
+ }
+ return (0);
+}
+
+static int
+uath_cmd_read(struct uath_softc *sc, uint32_t code, const void *idata,
+ int ilen, void *odata, int olen, int flags)
+{
+
+ flags |= UATH_CMD_FLAG_READ;
+ return uath_cmdsend(sc, code, idata, ilen, odata, olen, flags);
+}
+
+static int
+uath_cmd_write(struct uath_softc *sc, uint32_t code, const void *data, int len,
+ int flags)
+{
+
+ flags &= ~UATH_CMD_FLAG_READ;
+ return uath_cmdsend(sc, code, data, len, NULL, 0, flags);
+}
+
+static struct uath_cmd *
+uath_get_cmdbuf(struct uath_softc *sc)
+{
+ struct uath_cmd *uc;
+
+ UATH_ASSERT_LOCKED(sc);
+
+ uc = STAILQ_FIRST(&sc->sc_cmd_inactive);
+ if (uc != NULL) {
+ STAILQ_REMOVE_HEAD(&sc->sc_cmd_inactive, next);
+ UATH_STAT_DEC(sc, st_cmd_inactive);
+ } else
+ uc = NULL;
+ if (uc == NULL)
+ DPRINTF(sc, UATH_DEBUG_XMIT, "%s: %s\n", __func__,
+ "out of command xmit buffers");
+ return (uc);
+}
+
+/*
+ * This function is called periodically (every second) when associated to
+ * query device statistics.
+ */
+static void
+uath_stat(void *arg)
+{
+ struct uath_softc *sc = arg;
+ int error;
+
+ UATH_LOCK(sc);
+ /*
+ * Send request for statistics asynchronously. The timer will be
+ * restarted when we'll get the stats notification.
+ */
+ error = uath_cmd_write(sc, WDCMSG_TARGET_GET_STATS, NULL, 0,
+ UATH_CMD_FLAG_ASYNC);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "could not query stats, error %d\n", error);
+ }
+ UATH_UNLOCK(sc);
+}
+
+static int
+uath_get_capability(struct uath_softc *sc, uint32_t cap, uint32_t *val)
+{
+ int error;
+
+ cap = htobe32(cap);
+ error = uath_cmd_read(sc, WDCMSG_TARGET_GET_CAPABILITY,
+ &cap, sizeof cap, val, sizeof(uint32_t), UATH_CMD_FLAG_MAGIC);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "could not read capability %u\n",
+ be32toh(cap));
+ return (error);
+ }
+ *val = be32toh(*val);
+ return (error);
+}
+
+static int
+uath_get_devcap(struct uath_softc *sc)
+{
+#define GETCAP(x, v) do { \
+ error = uath_get_capability(sc, x, &v); \
+ if (error != 0) \
+ return (error); \
+ DPRINTF(sc, UATH_DEBUG_DEVCAP, \
+ "%s: %s=0x%08x\n", __func__, #x, v); \
+} while (0)
+ struct uath_devcap *cap = &sc->sc_devcap;
+ int error;
+
+ /* collect device capabilities */
+ GETCAP(CAP_TARGET_VERSION, cap->targetVersion);
+ GETCAP(CAP_TARGET_REVISION, cap->targetRevision);
+ GETCAP(CAP_MAC_VERSION, cap->macVersion);
+ GETCAP(CAP_MAC_REVISION, cap->macRevision);
+ GETCAP(CAP_PHY_REVISION, cap->phyRevision);
+ GETCAP(CAP_ANALOG_5GHz_REVISION, cap->analog5GhzRevision);
+ GETCAP(CAP_ANALOG_2GHz_REVISION, cap->analog2GhzRevision);
+
+ GETCAP(CAP_REG_DOMAIN, cap->regDomain);
+ GETCAP(CAP_REG_CAP_BITS, cap->regCapBits);
+#if 0
+ /* NB: not supported in rev 1.5 */
+ GETCAP(CAP_COUNTRY_CODE, cap->countryCode);
+#endif
+ GETCAP(CAP_WIRELESS_MODES, cap->wirelessModes);
+ GETCAP(CAP_CHAN_SPREAD_SUPPORT, cap->chanSpreadSupport);
+ GETCAP(CAP_COMPRESS_SUPPORT, cap->compressSupport);
+ GETCAP(CAP_BURST_SUPPORT, cap->burstSupport);
+ GETCAP(CAP_FAST_FRAMES_SUPPORT, cap->fastFramesSupport);
+ GETCAP(CAP_CHAP_TUNING_SUPPORT, cap->chapTuningSupport);
+ GETCAP(CAP_TURBOG_SUPPORT, cap->turboGSupport);
+ GETCAP(CAP_TURBO_PRIME_SUPPORT, cap->turboPrimeSupport);
+ GETCAP(CAP_DEVICE_TYPE, cap->deviceType);
+ GETCAP(CAP_WME_SUPPORT, cap->wmeSupport);
+ GETCAP(CAP_TOTAL_QUEUES, cap->numTxQueues);
+ GETCAP(CAP_CONNECTION_ID_MAX, cap->connectionIdMax);
+
+ GETCAP(CAP_LOW_5GHZ_CHAN, cap->low5GhzChan);
+ GETCAP(CAP_HIGH_5GHZ_CHAN, cap->high5GhzChan);
+ GETCAP(CAP_LOW_2GHZ_CHAN, cap->low2GhzChan);
+ GETCAP(CAP_HIGH_2GHZ_CHAN, cap->high2GhzChan);
+ GETCAP(CAP_TWICE_ANTENNAGAIN_5G, cap->twiceAntennaGain5G);
+ GETCAP(CAP_TWICE_ANTENNAGAIN_2G, cap->twiceAntennaGain2G);
+
+ GETCAP(CAP_CIPHER_AES_CCM, cap->supportCipherAES_CCM);
+ GETCAP(CAP_CIPHER_TKIP, cap->supportCipherTKIP);
+ GETCAP(CAP_MIC_TKIP, cap->supportMicTKIP);
+
+ cap->supportCipherWEP = 1; /* NB: always available */
+
+ return (0);
+}
+
+static int
+uath_get_devstatus(struct uath_softc *sc, uint8_t macaddr[IEEE80211_ADDR_LEN])
+{
+ int error;
+
+ /* retrieve MAC address */
+ error = uath_get_status(sc, ST_MAC_ADDR, macaddr, IEEE80211_ADDR_LEN);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "could not read MAC address\n");
+ return (error);
+ }
+
+ error = uath_get_status(sc, ST_SERIAL_NUMBER,
+ &sc->sc_serial[0], sizeof(sc->sc_serial));
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "could not read device serial number\n");
+ return (error);
+ }
+ return (0);
+}
+
+static int
+uath_get_status(struct uath_softc *sc, uint32_t which, void *odata, int olen)
+{
+ int error;
+
+ which = htobe32(which);
+ error = uath_cmd_read(sc, WDCMSG_TARGET_GET_STATUS,
+ &which, sizeof(which), odata, olen, UATH_CMD_FLAG_MAGIC);
+ if (error != 0)
+ device_printf(sc->sc_dev,
+ "could not read EEPROM offset 0x%02x\n", be32toh(which));
+ return (error);
+}
+
+static void
+uath_free_data_list(struct uath_softc *sc, struct uath_data data[], int ndata,
+ int fillmbuf)
+{
+ int i;
+
+ for (i = 0; i < ndata; i++) {
+ struct uath_data *dp = &data[i];
+
+ if (fillmbuf == 1) {
+ if (dp->m != NULL) {
+ m_freem(dp->m);
+ dp->m = NULL;
+ dp->buf = NULL;
+ }
+ } else {
+ dp->buf = NULL;
+ }
+ if (dp->ni != NULL) {
+ ieee80211_free_node(dp->ni);
+ dp->ni = NULL;
+ }
+ }
+}
+
+static int
+uath_alloc_data_list(struct uath_softc *sc, struct uath_data data[],
+ int ndata, int maxsz, void *dma_buf)
+{
+ int i, error;
+
+ for (i = 0; i < ndata; i++) {
+ struct uath_data *dp = &data[i];
+
+ dp->sc = sc;
+ if (dma_buf == NULL) {
+ /* XXX check maxsz */
+ dp->m = m_getcl(M_NOWAIT, 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 = ((uint8_t *)dma_buf) + (i * maxsz);
+ }
+ dp->ni = NULL;
+ }
+
+ return (0);
+
+fail: uath_free_data_list(sc, data, ndata, 1 /* free mbufs */);
+ return (error);
+}
+
+static int
+uath_alloc_rx_data_list(struct uath_softc *sc)
+{
+ int error, i;
+
+ /* XXX is it enough to store the RX packet with MCLBYTES bytes? */
+ error = uath_alloc_data_list(sc,
+ sc->sc_rx, UATH_RX_DATA_LIST_COUNT, MCLBYTES,
+ NULL /* setup mbufs */);
+ if (error != 0)
+ return (error);
+
+ STAILQ_INIT(&sc->sc_rx_active);
+ STAILQ_INIT(&sc->sc_rx_inactive);
+
+ for (i = 0; i < UATH_RX_DATA_LIST_COUNT; i++) {
+ STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i],
+ next);
+ UATH_STAT_INC(sc, st_rx_inactive);
+ }
+
+ return (0);
+}
+
+static int
+uath_alloc_tx_data_list(struct uath_softc *sc)
+{
+ int error, i;
+
+ error = uath_alloc_data_list(sc,
+ sc->sc_tx, UATH_TX_DATA_LIST_COUNT, UATH_MAX_TXBUFSZ,
+ sc->sc_tx_dma_buf);
+ if (error != 0)
+ return (error);
+
+ STAILQ_INIT(&sc->sc_tx_active);
+ STAILQ_INIT(&sc->sc_tx_inactive);
+ STAILQ_INIT(&sc->sc_tx_pending);
+
+ for (i = 0; i < UATH_TX_DATA_LIST_COUNT; i++) {
+ STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i],
+ next);
+ UATH_STAT_INC(sc, st_tx_inactive);
+ }
+
+ return (0);
+}
+
+static void
+uath_free_rx_data_list(struct uath_softc *sc)
+{
+ uath_free_data_list(sc, sc->sc_rx, UATH_RX_DATA_LIST_COUNT,
+ 1 /* free mbufs */);
+}
+
+static void
+uath_free_tx_data_list(struct uath_softc *sc)
+{
+ uath_free_data_list(sc, sc->sc_tx, UATH_TX_DATA_LIST_COUNT,
+ 0 /* no mbufs */);
+}
+
+static struct ieee80211vap *
+uath_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
+ enum ieee80211_opmode opmode, int flags,
+ const uint8_t bssid[IEEE80211_ADDR_LEN],
+ const uint8_t mac[IEEE80211_ADDR_LEN])
+{
+ struct uath_vap *uvp;
+ struct ieee80211vap *vap;
+
+ if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */
+ return (NULL);
+ uvp = malloc(sizeof(struct uath_vap), M_80211_VAP, M_WAITOK | M_ZERO);
+ vap = &uvp->vap;
+ /* enable s/w bmiss handling for sta mode */
+
+ if (ieee80211_vap_setup(ic, vap, name, unit, opmode,
+ flags | IEEE80211_CLONE_NOBEACONS, bssid) != 0) {
+ /* out of memory */
+ free(uvp, M_80211_VAP);
+ return (NULL);
+ }
+
+ /* override state transition machine */
+ uvp->newstate = vap->iv_newstate;
+ vap->iv_newstate = uath_newstate;
+
+ /* complete setup */
+ ieee80211_vap_attach(vap, ieee80211_media_change,
+ ieee80211_media_status, mac);
+ ic->ic_opmode = opmode;
+ return (vap);
+}
+
+static void
+uath_vap_delete(struct ieee80211vap *vap)
+{
+ struct uath_vap *uvp = UATH_VAP(vap);
+
+ ieee80211_vap_detach(vap);
+ free(uvp, M_80211_VAP);
+}
+
+static int
+uath_init(struct uath_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ uint32_t val;
+ int error;
+
+ UATH_ASSERT_LOCKED(sc);
+
+ if (sc->sc_flags & UATH_FLAG_INITDONE)
+ uath_stop(sc);
+
+ /* reset variables */
+ sc->sc_intrx_nextnum = sc->sc_msgid = 0;
+
+ val = htobe32(0);
+ uath_cmd_write(sc, WDCMSG_BIND, &val, sizeof val, 0);
+
+ /* set MAC address */
+ uath_config_multi(sc, CFG_MAC_ADDR,
+ vap ? vap->iv_myaddr : ic->ic_macaddr, IEEE80211_ADDR_LEN);
+
+ /* XXX honor net80211 state */
+ uath_config(sc, CFG_RATE_CONTROL_ENABLE, 0x00000001);
+ uath_config(sc, CFG_DIVERSITY_CTL, 0x00000001);
+ uath_config(sc, CFG_ABOLT, 0x0000003f);
+ uath_config(sc, CFG_WME_ENABLED, 0x00000001);
+
+ uath_config(sc, CFG_SERVICE_TYPE, 1);
+ uath_config(sc, CFG_TP_SCALE, 0x00000000);
+ uath_config(sc, CFG_TPC_HALF_DBM5, 0x0000003c);
+ uath_config(sc, CFG_TPC_HALF_DBM2, 0x0000003c);
+ uath_config(sc, CFG_OVERRD_TX_POWER, 0x00000000);
+ uath_config(sc, CFG_GMODE_PROTECTION, 0x00000000);
+ uath_config(sc, CFG_GMODE_PROTECT_RATE_INDEX, 0x00000003);
+ uath_config(sc, CFG_PROTECTION_TYPE, 0x00000000);
+ uath_config(sc, CFG_MODE_CTS, 0x00000002);
+
+ error = uath_cmd_read(sc, WDCMSG_TARGET_START, NULL, 0,
+ &val, sizeof(val), UATH_CMD_FLAG_MAGIC);
+ if (error) {
+ device_printf(sc->sc_dev,
+ "could not start target, error %d\n", error);
+ goto fail;
+ }
+ DPRINTF(sc, UATH_DEBUG_INIT, "%s returns handle: 0x%x\n",
+ uath_codename(WDCMSG_TARGET_START), be32toh(val));
+
+ /* set default channel */
+ error = uath_switch_channel(sc, ic->ic_curchan);
+ if (error) {
+ device_printf(sc->sc_dev,
+ "could not switch channel, error %d\n", error);
+ goto fail;
+ }
+
+ val = htobe32(TARGET_DEVICE_AWAKE);
+ uath_cmd_write(sc, WDCMSG_SET_PWR_MODE, &val, sizeof val, 0);
+ /* XXX? check */
+ uath_cmd_write(sc, WDCMSG_RESET_KEY_CACHE, NULL, 0, 0);
+
+ usbd_transfer_start(sc->sc_xfer[UATH_BULK_RX]);
+ /* enable Rx */
+ uath_set_rxfilter(sc, 0x0, UATH_FILTER_OP_INIT);
+ uath_set_rxfilter(sc,
+ UATH_FILTER_RX_UCAST | UATH_FILTER_RX_MCAST |
+ UATH_FILTER_RX_BCAST | UATH_FILTER_RX_BEACON,
+ UATH_FILTER_OP_SET);
+
+ sc->sc_flags |= UATH_FLAG_INITDONE;
+
+ callout_reset(&sc->watchdog_ch, hz, uath_watchdog, sc);
+
+ return (0);
+
+fail:
+ uath_stop(sc);
+ return (error);
+}
+
+static void
+uath_stop(struct uath_softc *sc)
+{
+
+ UATH_ASSERT_LOCKED(sc);
+
+ sc->sc_flags &= ~UATH_FLAG_INITDONE;
+
+ callout_stop(&sc->stat_ch);
+ callout_stop(&sc->watchdog_ch);
+ sc->sc_tx_timer = 0;
+ /* abort pending transmits */
+ uath_abort_xfers(sc);
+ /* flush data & control requests into the target */
+ (void)uath_flush(sc);
+ /* set a LED status to the disconnected. */
+ uath_set_ledstate(sc, 0);
+ /* stop the target */
+ uath_cmd_write(sc, WDCMSG_TARGET_STOP, NULL, 0, 0);
+}
+
+static int
+uath_config(struct uath_softc *sc, uint32_t reg, uint32_t val)
+{
+ struct uath_write_mac write;
+ int error;
+
+ write.reg = htobe32(reg);
+ write.len = htobe32(0); /* 0 = single write */
+ *(uint32_t *)write.data = htobe32(val);
+
+ error = uath_cmd_write(sc, WDCMSG_TARGET_SET_CONFIG, &write,
+ 3 * sizeof (uint32_t), 0);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "could not write register 0x%02x\n",
+ reg);
+ }
+ return (error);
+}
+
+static int
+uath_config_multi(struct uath_softc *sc, uint32_t reg, const void *data,
+ int len)
+{
+ struct uath_write_mac write;
+ int error;
+
+ write.reg = htobe32(reg);
+ write.len = htobe32(len);
+ bcopy(data, write.data, len);
+
+ /* properly handle the case where len is zero (reset) */
+ error = uath_cmd_write(sc, WDCMSG_TARGET_SET_CONFIG, &write,
+ (len == 0) ? sizeof (uint32_t) : 2 * sizeof (uint32_t) + len, 0);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "could not write %d bytes to register 0x%02x\n", len, reg);
+ }
+ return (error);
+}
+
+static int
+uath_switch_channel(struct uath_softc *sc, struct ieee80211_channel *c)
+{
+ int error;
+
+ UATH_ASSERT_LOCKED(sc);
+
+ /* set radio frequency */
+ error = uath_set_chan(sc, c);
+ if (error) {
+ device_printf(sc->sc_dev,
+ "could not set channel, error %d\n", error);
+ goto failed;
+ }
+ /* reset Tx rings */
+ error = uath_reset_tx_queues(sc);
+ if (error) {
+ device_printf(sc->sc_dev,
+ "could not reset Tx queues, error %d\n", error);
+ goto failed;
+ }
+ /* set Tx rings WME properties */
+ error = uath_wme_init(sc);
+ if (error) {
+ device_printf(sc->sc_dev,
+ "could not init Tx queues, error %d\n", error);
+ goto failed;
+ }
+ error = uath_set_ledstate(sc, 0);
+ if (error) {
+ device_printf(sc->sc_dev,
+ "could not set led state, error %d\n", error);
+ goto failed;
+ }
+ error = uath_flush(sc);
+ if (error) {
+ device_printf(sc->sc_dev,
+ "could not flush pipes, error %d\n", error);
+ goto failed;
+ }
+failed:
+ return (error);
+}
+
+static int
+uath_set_rxfilter(struct uath_softc *sc, uint32_t bits, uint32_t op)
+{
+ struct uath_cmd_rx_filter rxfilter;
+
+ rxfilter.bits = htobe32(bits);
+ rxfilter.op = htobe32(op);
+
+ DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL,
+ "setting Rx filter=0x%x flags=0x%x\n", bits, op);
+ return uath_cmd_write(sc, WDCMSG_RX_FILTER, &rxfilter,
+ sizeof rxfilter, 0);
+}
+
+static void
+uath_watchdog(void *arg)
+{
+ struct uath_softc *sc = arg;
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ if (sc->sc_tx_timer > 0) {
+ if (--sc->sc_tx_timer == 0) {
+ device_printf(sc->sc_dev, "device timeout\n");
+ counter_u64_add(ic->ic_oerrors, 1);
+ ieee80211_restart_all(ic);
+ return;
+ }
+ callout_reset(&sc->watchdog_ch, hz, uath_watchdog, sc);
+ }
+}
+
+static void
+uath_abort_xfers(struct uath_softc *sc)
+{
+ int i;
+
+ UATH_ASSERT_LOCKED(sc);
+ /* abort any pending transfers */
+ for (i = 0; i < UATH_N_XFERS; i++)
+ usbd_transfer_stop(sc->sc_xfer[i]);
+}
+
+static int
+uath_flush(struct uath_softc *sc)
+{
+ int error;
+
+ error = uath_dataflush(sc);
+ if (error != 0)
+ goto failed;
+
+ error = uath_cmdflush(sc);
+ if (error != 0)
+ goto failed;
+
+failed:
+ return (error);
+}
+
+static int
+uath_cmdflush(struct uath_softc *sc)
+{
+
+ return uath_cmd_write(sc, WDCMSG_FLUSH, NULL, 0, 0);
+}
+
+static int
+uath_dataflush(struct uath_softc *sc)
+{
+ struct uath_data *data;
+ struct uath_chunk *chunk;
+ struct uath_tx_desc *desc;
+
+ UATH_ASSERT_LOCKED(sc);
+
+ data = uath_getbuf(sc);
+ if (data == NULL)
+ return (ENOBUFS);
+ data->buflen = sizeof(struct uath_chunk) + sizeof(struct uath_tx_desc);
+ data->m = NULL;
+ data->ni = NULL;
+ chunk = (struct uath_chunk *)data->buf;
+ desc = (struct uath_tx_desc *)(chunk + 1);
+
+ /* one chunk only */
+ chunk->seqnum = 0;
+ chunk->flags = UATH_CFLAGS_FINAL;
+ chunk->length = htobe16(sizeof (struct uath_tx_desc));
+
+ memset(desc, 0, sizeof(struct uath_tx_desc));
+ desc->msglen = htobe32(sizeof(struct uath_tx_desc));
+ desc->msgid = (sc->sc_msgid++) + 1; /* don't care about endianness */
+ desc->type = htobe32(WDCMSG_FLUSH);
+ desc->txqid = htobe32(0);
+ desc->connid = htobe32(0);
+ desc->flags = htobe32(0);
+
+#ifdef UATH_DEBUG
+ if (sc->sc_debug & UATH_DEBUG_CMDS) {
+ DPRINTF(sc, UATH_DEBUG_RESET, "send flush ix %d\n",
+ desc->msgid);
+ if (sc->sc_debug & UATH_DEBUG_CMDS_DUMP)
+ uath_dump_cmd(data->buf, data->buflen, '+');
+ }
+#endif
+
+ STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next);
+ UATH_STAT_INC(sc, st_tx_pending);
+ sc->sc_tx_timer = 5;
+ usbd_transfer_start(sc->sc_xfer[UATH_BULK_TX]);
+
+ return (0);
+}
+
+static struct uath_data *
+_uath_getbuf(struct uath_softc *sc)
+{
+ struct uath_data *bf;
+
+ bf = STAILQ_FIRST(&sc->sc_tx_inactive);
+ if (bf != NULL) {
+ STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next);
+ UATH_STAT_DEC(sc, st_tx_inactive);
+ } else
+ bf = NULL;
+ if (bf == NULL)
+ DPRINTF(sc, UATH_DEBUG_XMIT, "%s: %s\n", __func__,
+ "out of xmit buffers");
+ return (bf);
+}
+
+static struct uath_data *
+uath_getbuf(struct uath_softc *sc)
+{
+ struct uath_data *bf;
+
+ UATH_ASSERT_LOCKED(sc);
+
+ bf = _uath_getbuf(sc);
+ if (bf == NULL)
+ DPRINTF(sc, UATH_DEBUG_XMIT, "%s: stop queue\n", __func__);
+ return (bf);
+}
+
+static int
+uath_set_ledstate(struct uath_softc *sc, int connected)
+{
+
+ DPRINTF(sc, UATH_DEBUG_LED,
+ "set led state %sconnected\n", connected ? "" : "!");
+ connected = htobe32(connected);
+ return uath_cmd_write(sc, WDCMSG_SET_LED_STATE,
+ &connected, sizeof connected, 0);
+}
+
+static int
+uath_set_chan(struct uath_softc *sc, struct ieee80211_channel *c)
+{
+#ifdef UATH_DEBUG
+ struct ieee80211com *ic = &sc->sc_ic;
+#endif
+ struct uath_cmd_reset reset;
+
+ memset(&reset, 0, sizeof(reset));
+ if (IEEE80211_IS_CHAN_2GHZ(c))
+ reset.flags |= htobe32(UATH_CHAN_2GHZ);
+ if (IEEE80211_IS_CHAN_5GHZ(c))
+ reset.flags |= htobe32(UATH_CHAN_5GHZ);
+ /* NB: 11g =>'s 11b so don't specify both OFDM and CCK */
+ if (IEEE80211_IS_CHAN_OFDM(c))
+ reset.flags |= htobe32(UATH_CHAN_OFDM);
+ else if (IEEE80211_IS_CHAN_CCK(c))
+ reset.flags |= htobe32(UATH_CHAN_CCK);
+ /* turbo can be used in either 2GHz or 5GHz */
+ if (c->ic_flags & IEEE80211_CHAN_TURBO)
+ reset.flags |= htobe32(UATH_CHAN_TURBO);
+ reset.freq = htobe32(c->ic_freq);
+ reset.maxrdpower = htobe32(50); /* XXX */
+ reset.channelchange = htobe32(1);
+ reset.keeprccontent = htobe32(0);
+
+ DPRINTF(sc, UATH_DEBUG_CHANNEL, "set channel %d, flags 0x%x freq %u\n",
+ ieee80211_chan2ieee(ic, c),
+ be32toh(reset.flags), be32toh(reset.freq));
+ return uath_cmd_write(sc, WDCMSG_RESET, &reset, sizeof reset, 0);
+}
+
+static int
+uath_reset_tx_queues(struct uath_softc *sc)
+{
+ int ac, error;
+
+ DPRINTF(sc, UATH_DEBUG_RESET, "%s: reset Tx queues\n", __func__);
+ for (ac = 0; ac < 4; ac++) {
+ const uint32_t qid = htobe32(ac);
+
+ error = uath_cmd_write(sc, WDCMSG_RELEASE_TX_QUEUE, &qid,
+ sizeof qid, 0);
+ if (error != 0)
+ break;
+ }
+ return (error);
+}
+
+static int
+uath_wme_init(struct uath_softc *sc)
+{
+ /* XXX get from net80211 */
+ static const struct uath_wme_settings uath_wme_11g[4] = {
+ { 7, 4, 10, 0, 0 }, /* Background */
+ { 3, 4, 10, 0, 0 }, /* Best-Effort */
+ { 3, 3, 4, 26, 0 }, /* Video */
+ { 2, 2, 3, 47, 0 } /* Voice */
+ };
+ struct uath_cmd_txq_setup qinfo;
+ int ac, error;
+
+ DPRINTF(sc, UATH_DEBUG_WME, "%s: setup Tx queues\n", __func__);
+ for (ac = 0; ac < 4; ac++) {
+ qinfo.qid = htobe32(ac);
+ qinfo.len = htobe32(sizeof(qinfo.attr));
+ qinfo.attr.priority = htobe32(ac); /* XXX */
+ qinfo.attr.aifs = htobe32(uath_wme_11g[ac].aifsn);
+ qinfo.attr.logcwmin = htobe32(uath_wme_11g[ac].logcwmin);
+ qinfo.attr.logcwmax = htobe32(uath_wme_11g[ac].logcwmax);
+ qinfo.attr.bursttime = htobe32(IEEE80211_TXOP_TO_US(
+ uath_wme_11g[ac].txop));
+ qinfo.attr.mode = htobe32(uath_wme_11g[ac].acm);/*XXX? */
+ qinfo.attr.qflags = htobe32(1); /* XXX? */
+
+ error = uath_cmd_write(sc, WDCMSG_SETUP_TX_QUEUE, &qinfo,
+ sizeof qinfo, 0);
+ if (error != 0)
+ break;
+ }
+ return (error);
+}
+
+static void
+uath_parent(struct ieee80211com *ic)
+{
+ struct uath_softc *sc = ic->ic_softc;
+ int startall = 0;
+
+ UATH_LOCK(sc);
+ if (sc->sc_flags & UATH_FLAG_INVALID) {
+ UATH_UNLOCK(sc);
+ return;
+ }
+
+ if (ic->ic_nrunning > 0) {
+ if (!(sc->sc_flags & UATH_FLAG_INITDONE)) {
+ uath_init(sc);
+ startall = 1;
+ }
+ } else if (sc->sc_flags & UATH_FLAG_INITDONE)
+ uath_stop(sc);
+ UATH_UNLOCK(sc);
+ if (startall)
+ ieee80211_start_all(ic);
+}
+
+static int
+uath_tx_start(struct uath_softc *sc, struct mbuf *m0, struct ieee80211_node *ni,
+ struct uath_data *data)
+{
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct uath_chunk *chunk;
+ struct uath_tx_desc *desc;
+ const struct ieee80211_frame *wh;
+ struct ieee80211_key *k;
+ int framelen, msglen;
+
+ UATH_ASSERT_LOCKED(sc);
+
+ data->ni = ni;
+ data->m = m0;
+ chunk = (struct uath_chunk *)data->buf;
+ desc = (struct uath_tx_desc *)(chunk + 1);
+
+ if (ieee80211_radiotap_active_vap(vap)) {
+ struct uath_tx_radiotap_header *tap = &sc->sc_txtap;
+
+ tap->wt_flags = 0;
+ if (m0->m_flags & M_FRAG)
+ tap->wt_flags |= IEEE80211_RADIOTAP_F_FRAG;
+
+ ieee80211_radiotap_tx(vap, m0);
+ }
+
+ wh = mtod(m0, struct ieee80211_frame *);
+ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
+ 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 *);
+ }
+ m_copydata(m0, 0, m0->m_pkthdr.len, (uint8_t *)(desc + 1));
+
+ framelen = m0->m_pkthdr.len + IEEE80211_CRC_LEN;
+ msglen = framelen + sizeof (struct uath_tx_desc);
+ data->buflen = msglen + sizeof (struct uath_chunk);
+
+ /* one chunk only for now */
+ chunk->seqnum = sc->sc_seqnum++;
+ chunk->flags = (m0->m_flags & M_FRAG) ? 0 : UATH_CFLAGS_FINAL;
+ if (m0->m_flags & M_LASTFRAG)
+ chunk->flags |= UATH_CFLAGS_FINAL;
+ chunk->flags = UATH_CFLAGS_FINAL;
+ chunk->length = htobe16(msglen);
+
+ /* fill Tx descriptor */
+ desc->msglen = htobe32(msglen);
+ /* NB: to get UATH_TX_NOTIFY reply, `msgid' must be larger than 0 */
+ desc->msgid = (sc->sc_msgid++) + 1; /* don't care about endianness */
+ desc->type = htobe32(WDCMSG_SEND);
+ switch (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) {
+ case IEEE80211_FC0_TYPE_CTL:
+ case IEEE80211_FC0_TYPE_MGT:
+ /* NB: force all management frames to highest queue */
+ if (ni->ni_flags & IEEE80211_NODE_QOS) {
+ /* NB: force all management frames to highest queue */
+ desc->txqid = htobe32(WME_AC_VO | UATH_TXQID_MINRATE);
+ } else
+ desc->txqid = htobe32(WME_AC_BE | UATH_TXQID_MINRATE);
+ break;
+ case IEEE80211_FC0_TYPE_DATA:
+ /* XXX multicast frames should honor mcastrate */
+ desc->txqid = htobe32(M_WME_GETAC(m0));
+ break;
+ default:
+ device_printf(sc->sc_dev, "bogus frame type 0x%x (%s)\n",
+ wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK, __func__);
+ m_freem(m0);
+ return (EIO);
+ }
+ if (vap->iv_state == IEEE80211_S_AUTH ||
+ vap->iv_state == IEEE80211_S_ASSOC ||
+ vap->iv_state == IEEE80211_S_RUN)
+ desc->connid = htobe32(UATH_ID_BSS);
+ else
+ desc->connid = htobe32(UATH_ID_INVALID);
+ desc->flags = htobe32(0 /* no UATH_TX_NOTIFY */);
+ desc->buflen = htobe32(m0->m_pkthdr.len);
+
+#ifdef UATH_DEBUG
+ DPRINTF(sc, UATH_DEBUG_XMIT,
+ "send frame ix %u framelen %d msglen %d connid 0x%x txqid 0x%x\n",
+ desc->msgid, framelen, msglen, be32toh(desc->connid),
+ be32toh(desc->txqid));
+ if (sc->sc_debug & UATH_DEBUG_XMIT_DUMP)
+ uath_dump_cmd(data->buf, data->buflen, '+');
+#endif
+
+ STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next);
+ UATH_STAT_INC(sc, st_tx_pending);
+ usbd_transfer_start(sc->sc_xfer[UATH_BULK_TX]);
+
+ return (0);
+}
+
+/*
+ * Cleanup driver resources when we run out of buffers while processing
+ * fragments; return the tx buffers allocated and drop node references.
+ */
+static void
+uath_txfrag_cleanup(struct uath_softc *sc,
+ uath_datahead *frags, struct ieee80211_node *ni)
+{
+ struct uath_data *bf, *next;
+
+ UATH_ASSERT_LOCKED(sc);
+
+ STAILQ_FOREACH_SAFE(bf, frags, next, next) {
+ /* NB: bf assumed clean */
+ STAILQ_REMOVE_HEAD(frags, next);
+ STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next);
+ UATH_STAT_INC(sc, st_tx_inactive);
+ ieee80211_node_decref(ni);
+ }
+}
+
+/*
+ * Setup xmit of a fragmented frame. Allocate a buffer for each frag and bump
+ * the node reference count to reflect the held reference to be setup by
+ * uath_tx_start.
+ */
+static int
+uath_txfrag_setup(struct uath_softc *sc, uath_datahead *frags,
+ struct mbuf *m0, struct ieee80211_node *ni)
+{
+ struct mbuf *m;
+ struct uath_data *bf;
+
+ UATH_ASSERT_LOCKED(sc);
+ for (m = m0->m_nextpkt; m != NULL; m = m->m_nextpkt) {
+ bf = uath_getbuf(sc);
+ if (bf == NULL) { /* out of buffers, cleanup */
+ uath_txfrag_cleanup(sc, frags, ni);
+ break;
+ }
+ (void) ieee80211_ref_node(ni);
+ STAILQ_INSERT_TAIL(frags, bf, next);
+ }
+
+ return !STAILQ_EMPTY(frags);
+}
+
+static int
+uath_transmit(struct ieee80211com *ic, struct mbuf *m)
+{
+ struct uath_softc *sc = ic->ic_softc;
+ int error;
+
+ UATH_LOCK(sc);
+ if ((sc->sc_flags & UATH_FLAG_INITDONE) == 0) {
+ UATH_UNLOCK(sc);
+ return (ENXIO);
+ }
+ error = mbufq_enqueue(&sc->sc_snd, m);
+ if (error) {
+ UATH_UNLOCK(sc);
+ return (error);
+ }
+ uath_start(sc);
+ UATH_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+uath_start(struct uath_softc *sc)
+{
+ struct uath_data *bf;
+ struct ieee80211_node *ni;
+ struct mbuf *m, *next;
+ uath_datahead frags;
+
+ UATH_ASSERT_LOCKED(sc);
+
+ if ((sc->sc_flags & UATH_FLAG_INITDONE) == 0 ||
+ (sc->sc_flags & UATH_FLAG_INVALID))
+ return;
+
+ while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
+ bf = uath_getbuf(sc);
+ if (bf == NULL) {
+ mbufq_prepend(&sc->sc_snd, m);
+ break;
+ }
+
+ ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
+ m->m_pkthdr.rcvif = NULL;
+
+ /*
+ * Check for fragmentation. If this frame has been broken up
+ * verify we have enough buffers to send all the fragments
+ * so all go out or none...
+ */
+ STAILQ_INIT(&frags);
+ if ((m->m_flags & M_FRAG) &&
+ !uath_txfrag_setup(sc, &frags, m, ni)) {
+ DPRINTF(sc, UATH_DEBUG_XMIT,
+ "%s: out of txfrag buffers\n", __func__);
+ ieee80211_free_mbuf(m);
+ goto bad;
+ }
+ sc->sc_seqnum = 0;
+ nextfrag:
+ /*
+ * Pass the frame to the h/w for transmission.
+ * Fragmented frames have each frag chained together
+ * with m_nextpkt. We know there are sufficient uath_data's
+ * to send all the frags because of work done by
+ * uath_txfrag_setup.
+ */
+ next = m->m_nextpkt;
+ if (uath_tx_start(sc, m, ni, bf) != 0) {
+ bad:
+ if_inc_counter(ni->ni_vap->iv_ifp,
+ IFCOUNTER_OERRORS, 1);
+ reclaim:
+ STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next);
+ UATH_STAT_INC(sc, st_tx_inactive);
+ uath_txfrag_cleanup(sc, &frags, ni);
+ ieee80211_free_node(ni);
+ continue;
+ }
+
+ if (next != NULL) {
+ /*
+ * Beware of state changing between frags.
+ XXX check sta power-save state?
+ */
+ if (ni->ni_vap->iv_state != IEEE80211_S_RUN) {
+ DPRINTF(sc, UATH_DEBUG_XMIT,
+ "%s: flush fragmented packet, state %s\n",
+ __func__,
+ ieee80211_state_name[ni->ni_vap->iv_state]);
+ ieee80211_free_mbuf(next);
+ goto reclaim;
+ }
+ m = next;
+ bf = STAILQ_FIRST(&frags);
+ KASSERT(bf != NULL, ("no buf for txfrag"));
+ STAILQ_REMOVE_HEAD(&frags, next);
+ goto nextfrag;
+ }
+
+ sc->sc_tx_timer = 5;
+ }
+}
+
+static int
+uath_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
+ const struct ieee80211_bpf_params *params)
+{
+ struct ieee80211com *ic = ni->ni_ic;
+ struct uath_data *bf;
+ struct uath_softc *sc = ic->ic_softc;
+
+ UATH_LOCK(sc);
+ /* prevent management frames from being sent if we're not ready */
+ if ((sc->sc_flags & UATH_FLAG_INVALID) ||
+ !(sc->sc_flags & UATH_FLAG_INITDONE)) {
+ m_freem(m);
+ UATH_UNLOCK(sc);
+ return (ENETDOWN);
+ }
+
+ /* grab a TX buffer */
+ bf = uath_getbuf(sc);
+ if (bf == NULL) {
+ m_freem(m);
+ UATH_UNLOCK(sc);
+ return (ENOBUFS);
+ }
+
+ sc->sc_seqnum = 0;
+ if (uath_tx_start(sc, m, ni, bf) != 0) {
+ STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next);
+ UATH_STAT_INC(sc, st_tx_inactive);
+ UATH_UNLOCK(sc);
+ return (EIO);
+ }
+ UATH_UNLOCK(sc);
+
+ sc->sc_tx_timer = 5;
+ return (0);
+}
+
+static void
+uath_scan_start(struct ieee80211com *ic)
+{
+ /* do nothing */
+}
+
+static void
+uath_scan_end(struct ieee80211com *ic)
+{
+ /* do nothing */
+}
+
+static void
+uath_set_channel(struct ieee80211com *ic)
+{
+ struct uath_softc *sc = ic->ic_softc;
+
+ UATH_LOCK(sc);
+ if ((sc->sc_flags & UATH_FLAG_INVALID) ||
+ (sc->sc_flags & UATH_FLAG_INITDONE) == 0) {
+ UATH_UNLOCK(sc);
+ return;
+ }
+ /* flush data & control requests into the target */
+ (void)uath_flush(sc);
+ (void)uath_switch_channel(sc, ic->ic_curchan);
+ UATH_UNLOCK(sc);
+}
+
+static int
+uath_set_rxmulti_filter(struct uath_softc *sc)
+{
+ /* XXX broken */
+ return (0);
+}
+static void
+uath_update_mcast(struct ieee80211com *ic)
+{
+ struct uath_softc *sc = ic->ic_softc;
+
+ UATH_LOCK(sc);
+ if ((sc->sc_flags & UATH_FLAG_INVALID) ||
+ (sc->sc_flags & UATH_FLAG_INITDONE) == 0) {
+ UATH_UNLOCK(sc);
+ return;
+ }
+ /*
+ * this is for avoiding the race condition when we're try to
+ * connect to the AP with WPA.
+ */
+ if (sc->sc_flags & UATH_FLAG_INITDONE)
+ (void)uath_set_rxmulti_filter(sc);
+ UATH_UNLOCK(sc);
+}
+
+static void
+uath_update_promisc(struct ieee80211com *ic)
+{
+ struct uath_softc *sc = ic->ic_softc;
+
+ UATH_LOCK(sc);
+ if ((sc->sc_flags & UATH_FLAG_INVALID) ||
+ (sc->sc_flags & UATH_FLAG_INITDONE) == 0) {
+ UATH_UNLOCK(sc);
+ return;
+ }
+ if (sc->sc_flags & UATH_FLAG_INITDONE) {
+ uath_set_rxfilter(sc,
+ UATH_FILTER_RX_UCAST | UATH_FILTER_RX_MCAST |
+ UATH_FILTER_RX_BCAST | UATH_FILTER_RX_BEACON |
+ UATH_FILTER_RX_PROM, UATH_FILTER_OP_SET);
+ }
+ UATH_UNLOCK(sc);
+}
+
+static int
+uath_create_connection(struct uath_softc *sc, uint32_t connid)
+{
+ const struct ieee80211_rateset *rs;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ struct ieee80211_node *ni;
+ struct uath_cmd_create_connection create;
+
+ ni = ieee80211_ref_node(vap->iv_bss);
+ memset(&create, 0, sizeof(create));
+ create.connid = htobe32(connid);
+ create.bssid = htobe32(0);
+ /* XXX packed or not? */
+ create.size = htobe32(sizeof(struct uath_cmd_rateset));
+
+ rs = &ni->ni_rates;
+ create.connattr.rateset.length = rs->rs_nrates;
+ bcopy(rs->rs_rates, &create.connattr.rateset.set[0],
+ rs->rs_nrates);
+
+ /* XXX turbo */
+ if (IEEE80211_IS_CHAN_A(ni->ni_chan))
+ create.connattr.wlanmode = htobe32(WLAN_MODE_11a);
+ else if (IEEE80211_IS_CHAN_ANYG(ni->ni_chan))
+ create.connattr.wlanmode = htobe32(WLAN_MODE_11g);
+ else
+ create.connattr.wlanmode = htobe32(WLAN_MODE_11b);
+ ieee80211_free_node(ni);
+
+ return uath_cmd_write(sc, WDCMSG_CREATE_CONNECTION, &create,
+ sizeof create, 0);
+}
+
+static int
+uath_set_rates(struct uath_softc *sc, const struct ieee80211_rateset *rs)
+{
+ struct uath_cmd_rates rates;
+
+ memset(&rates, 0, sizeof(rates));
+ rates.connid = htobe32(UATH_ID_BSS); /* XXX */
+ rates.size = htobe32(sizeof(struct uath_cmd_rateset));
+ /* XXX bounds check rs->rs_nrates */
+ rates.rateset.length = rs->rs_nrates;
+ bcopy(rs->rs_rates, &rates.rateset.set[0], rs->rs_nrates);
+
+ DPRINTF(sc, UATH_DEBUG_RATES,
+ "setting supported rates nrates=%d\n", rs->rs_nrates);
+ return uath_cmd_write(sc, WDCMSG_SET_BASIC_RATE,
+ &rates, sizeof rates, 0);
+}
+
+static int
+uath_write_associd(struct uath_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ struct ieee80211_node *ni;
+ struct uath_cmd_set_associd associd;
+
+ ni = ieee80211_ref_node(vap->iv_bss);
+ memset(&associd, 0, sizeof(associd));
+ associd.defaultrateix = htobe32(1); /* XXX */
+ associd.associd = htobe32(ni->ni_associd);
+ associd.timoffset = htobe32(0x3b); /* XXX */
+ IEEE80211_ADDR_COPY(associd.bssid, ni->ni_bssid);
+ ieee80211_free_node(ni);
+ return uath_cmd_write(sc, WDCMSG_WRITE_ASSOCID, &associd,
+ sizeof associd, 0);
+}
+
+static int
+uath_set_ledsteady(struct uath_softc *sc, int lednum, int ledmode)
+{
+ struct uath_cmd_ledsteady led;
+
+ led.lednum = htobe32(lednum);
+ led.ledmode = htobe32(ledmode);
+
+ DPRINTF(sc, UATH_DEBUG_LED, "set %s led %s (steady)\n",
+ (lednum == UATH_LED_LINK) ? "link" : "activity",
+ ledmode ? "on" : "off");
+ return uath_cmd_write(sc, WDCMSG_SET_LED_STEADY, &led, sizeof led, 0);
+}
+
+static int
+uath_set_ledblink(struct uath_softc *sc, int lednum, int ledmode,
+ int blinkrate, int slowmode)
+{
+ struct uath_cmd_ledblink led;
+
+ led.lednum = htobe32(lednum);
+ led.ledmode = htobe32(ledmode);
+ led.blinkrate = htobe32(blinkrate);
+ led.slowmode = htobe32(slowmode);
+
+ DPRINTF(sc, UATH_DEBUG_LED, "set %s led %s (blink)\n",
+ (lednum == UATH_LED_LINK) ? "link" : "activity",
+ ledmode ? "on" : "off");
+ return uath_cmd_write(sc, WDCMSG_SET_LED_BLINK, &led, sizeof led, 0);
+}
+
+static int
+uath_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
+{
+ enum ieee80211_state ostate = vap->iv_state;
+ int error;
+ struct ieee80211_node *ni;
+ struct ieee80211com *ic = vap->iv_ic;
+ struct uath_softc *sc = ic->ic_softc;
+ struct uath_vap *uvp = UATH_VAP(vap);
+
+ DPRINTF(sc, UATH_DEBUG_STATE,
+ "%s: %s -> %s\n", __func__, ieee80211_state_name[vap->iv_state],
+ ieee80211_state_name[nstate]);
+
+ IEEE80211_UNLOCK(ic);
+ UATH_LOCK(sc);
+ callout_stop(&sc->stat_ch);
+ callout_stop(&sc->watchdog_ch);
+ ni = ieee80211_ref_node(vap->iv_bss);
+
+ switch (nstate) {
+ case IEEE80211_S_INIT:
+ if (ostate == IEEE80211_S_RUN) {
+ /* turn link and activity LEDs off */
+ uath_set_ledstate(sc, 0);
+ }
+ break;
+
+ case IEEE80211_S_SCAN:
+ break;
+
+ case IEEE80211_S_AUTH:
+ /* flush data & control requests into the target */
+ (void)uath_flush(sc);
+ /* XXX good place? set RTS threshold */
+ uath_config(sc, CFG_USER_RTS_THRESHOLD, vap->iv_rtsthreshold);
+ /* XXX bad place */
+ error = uath_set_keys(sc, vap);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "could not set crypto keys, error %d\n", error);
+ break;
+ }
+ if (uath_switch_channel(sc, ni->ni_chan) != 0) {
+ device_printf(sc->sc_dev, "could not switch channel\n");
+ break;
+ }
+ if (uath_create_connection(sc, UATH_ID_BSS) != 0) {
+ device_printf(sc->sc_dev,
+ "could not create connection\n");
+ break;
+ }
+ break;
+
+ case IEEE80211_S_ASSOC:
+ if (uath_set_rates(sc, &ni->ni_rates) != 0) {
+ device_printf(sc->sc_dev,
+ "could not set negotiated rate set\n");
+ break;
+ }
+ break;
+
+ case IEEE80211_S_RUN:
+ /* XXX monitor mode doesn't be tested */
+ if (ic->ic_opmode == IEEE80211_M_MONITOR) {
+ uath_set_ledstate(sc, 1);
+ break;
+ }
+
+ /*
+ * Tx rate is controlled by firmware, report the maximum
+ * negotiated rate in ifconfig output.
+ */
+ ieee80211_node_set_txrate_dot11rate(ni,
+ ni->ni_rates.rs_rates[ni->ni_rates.rs_nrates-1]);
+
+ if (uath_write_associd(sc) != 0) {
+ device_printf(sc->sc_dev,
+ "could not write association id\n");
+ break;
+ }
+ /* turn link LED on */
+ uath_set_ledsteady(sc, UATH_LED_LINK, UATH_LED_ON);
+ /* make activity LED blink */
+ uath_set_ledblink(sc, UATH_LED_ACTIVITY, UATH_LED_ON, 1, 2);
+ /* set state to associated */
+ uath_set_ledstate(sc, 1);
+
+ /* start statistics timer */
+ callout_reset(&sc->stat_ch, hz, uath_stat, sc);
+ break;
+ default:
+ break;
+ }
+ ieee80211_free_node(ni);
+ UATH_UNLOCK(sc);
+ IEEE80211_LOCK(ic);
+ return (uvp->newstate(vap, nstate, arg));
+}
+
+static int
+uath_set_key(struct uath_softc *sc, const struct ieee80211_key *wk,
+ int index)
+{
+#if 0
+ struct uath_cmd_crypto crypto;
+ int i;
+
+ memset(&crypto, 0, sizeof(crypto));
+ crypto.keyidx = htobe32(index);
+ crypto.magic1 = htobe32(1);
+ crypto.size = htobe32(368);
+ crypto.mask = htobe32(0xffff);
+ crypto.flags = htobe32(0x80000068);
+ if (index != UATH_DEFAULT_KEY)
+ crypto.flags |= htobe32(index << 16);
+ memset(crypto.magic2, 0xff, sizeof(crypto.magic2));
+
+ /*
+ * Each byte of the key must be XOR'ed with 10101010 before being
+ * transmitted to the firmware.
+ */
+ for (i = 0; i < wk->wk_keylen; i++)
+ crypto.key[i] = wk->wk_key[i] ^ 0xaa;
+
+ DPRINTF(sc, UATH_DEBUG_CRYPTO,
+ "setting crypto key index=%d len=%d\n", index, wk->wk_keylen);
+ return uath_cmd_write(sc, WDCMSG_SET_KEY_CACHE_ENTRY, &crypto,
+ sizeof crypto, 0);
+#else
+ /* XXX support H/W cryto */
+ return (0);
+#endif
+}
+
+static int
+uath_set_keys(struct uath_softc *sc, struct ieee80211vap *vap)
+{
+ int i, error;
+
+ error = 0;
+ for (i = 0; i < IEEE80211_WEP_NKID; i++) {
+ const struct ieee80211_key *wk = &vap->iv_nw_keys[i];
+
+ if (wk->wk_flags & (IEEE80211_KEY_XMIT|IEEE80211_KEY_RECV)) {
+ error = uath_set_key(sc, wk, i);
+ if (error)
+ return (error);
+ }
+ }
+ if (vap->iv_def_txkey != IEEE80211_KEYIX_NONE) {
+ error = uath_set_key(sc, &vap->iv_nw_keys[vap->iv_def_txkey],
+ UATH_DEFAULT_KEY);
+ }
+ return (error);
+}
+
+#define UATH_SYSCTL_STAT_ADD32(c, h, n, p, d) \
+ SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d)
+
+static void
+uath_sysctl_node(struct uath_softc *sc)
+{
+ struct sysctl_ctx_list *ctx;
+ struct sysctl_oid_list *child;
+ struct sysctl_oid *tree;
+ struct uath_stat *stats;
+
+ stats = &sc->sc_stat;
+ ctx = device_get_sysctl_ctx(sc->sc_dev);
+ child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev));
+
+ tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats",
+ CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "UATH statistics");
+ child = SYSCTL_CHILDREN(tree);
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "badchunkseqnum",
+ &stats->st_badchunkseqnum, "Bad chunk sequence numbers");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "invalidlen", &stats->st_invalidlen,
+ "Invalid length");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "multichunk", &stats->st_multichunk,
+ "Multi chunks");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "toobigrxpkt",
+ &stats->st_toobigrxpkt, "Too big rx packets");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "stopinprogress",
+ &stats->st_stopinprogress, "Stop in progress");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "crcerrs", &stats->st_crcerr,
+ "CRC errors");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "phyerr", &stats->st_phyerr,
+ "PHY errors");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "decrypt_crcerr",
+ &stats->st_decrypt_crcerr, "Decryption CRC errors");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "decrypt_micerr",
+ &stats->st_decrypt_micerr, "Decryption Misc errors");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "decomperr", &stats->st_decomperr,
+ "Decomp errors");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "keyerr", &stats->st_keyerr,
+ "Key errors");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "err", &stats->st_err,
+ "Unknown errors");
+
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_active",
+ &stats->st_cmd_active, "Active numbers in Command queue");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_inactive",
+ &stats->st_cmd_inactive, "Inactive numbers in Command queue");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_pending",
+ &stats->st_cmd_pending, "Pending numbers in Command queue");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "cmd_waiting",
+ &stats->st_cmd_waiting, "Waiting numbers in Command queue");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "rx_active",
+ &stats->st_rx_active, "Active numbers in RX queue");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "rx_inactive",
+ &stats->st_rx_inactive, "Inactive numbers in RX queue");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "tx_active",
+ &stats->st_tx_active, "Active numbers in TX queue");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "tx_inactive",
+ &stats->st_tx_inactive, "Inactive numbers in TX queue");
+ UATH_SYSCTL_STAT_ADD32(ctx, child, "tx_pending",
+ &stats->st_tx_pending, "Pending numbers in TX queue");
+}
+
+#undef UATH_SYSCTL_STAT_ADD32
+
+CTASSERT(sizeof(u_int) >= sizeof(uint32_t));
+
+static void
+uath_cmdeof(struct uath_softc *sc, struct uath_cmd *cmd)
+{
+ struct uath_cmd_hdr *hdr;
+ uint32_t dlen;
+
+ hdr = (struct uath_cmd_hdr *)cmd->buf;
+ /* NB: msgid is passed thru w/o byte swapping */
+#ifdef UATH_DEBUG
+ if (sc->sc_debug & UATH_DEBUG_CMDS) {
+ uint32_t len = be32toh(hdr->len);
+ printf("%s: %s [ix %u] len %u status %u\n",
+ __func__, uath_codename(be32toh(hdr->code)),
+ hdr->msgid, len, be32toh(hdr->magic));
+ if (sc->sc_debug & UATH_DEBUG_CMDS_DUMP)
+ uath_dump_cmd(cmd->buf,
+ len > UATH_MAX_CMDSZ ? sizeof(*hdr) : len, '-');
+ }
+#endif
+ hdr->code = be32toh(hdr->code);
+ hdr->len = be32toh(hdr->len);
+ hdr->magic = be32toh(hdr->magic); /* target status on return */
+
+ switch (hdr->code & 0xff) {
+ /* reply to a read command */
+ default:
+ DPRINTF(sc, UATH_DEBUG_RX_PROC | UATH_DEBUG_RECV_ALL,
+ "%s: code %d hdr len %u\n",
+ __func__, hdr->code & 0xff, hdr->len);
+ /*
+ * The first response from the target after the
+ * HOST_AVAILABLE has an invalid msgid so we must
+ * treat it specially.
+ */
+ if (hdr->msgid < UATH_CMD_LIST_COUNT) {
+ uint32_t *rp = (uint32_t *)(hdr+1);
+ u_int olen;
+
+ if (sizeof(*hdr) > hdr->len ||
+ hdr->len > UATH_MAX_CMDSZ) {
+ device_printf(sc->sc_dev,
+ "%s: invalid WDC msg length %u; "
+ "msg ignored\n", __func__, hdr->len);
+ return;
+ }
+ /*
+ * Calculate return/receive payload size; the
+ * first word, if present, always gives the
+ * number of bytes--unless it's 0 in which
+ * case a single 32-bit word should be present.
+ */
+ dlen = hdr->len - sizeof(*hdr);
+ if (dlen >= sizeof(uint32_t)) {
+ olen = be32toh(rp[0]);
+ dlen -= sizeof(uint32_t);
+ if (olen == 0) {
+ /* convention is 0 =>'s one word */
+ olen = sizeof(uint32_t);
+ /* XXX KASSERT(olen == dlen ) */
+ }
+ } else
+ olen = 0;
+ if (cmd->odata != NULL) {
+ /* NB: cmd->olen validated in uath_cmd */
+ if (olen > (u_int)cmd->olen) {
+ /* XXX complain? */
+ device_printf(sc->sc_dev,
+ "%s: cmd 0x%x olen %u cmd olen %u\n",
+ __func__, hdr->code, olen,
+ cmd->olen);
+ olen = cmd->olen;
+ }
+ if (olen > dlen) {
+ /* XXX complain, shouldn't happen */
+ device_printf(sc->sc_dev,
+ "%s: cmd 0x%x olen %u dlen %u\n",
+ __func__, hdr->code, olen, dlen);
+ olen = dlen;
+ }
+ /* XXX have submitter do this */
+ /* copy answer into caller's supplied buffer */
+ bcopy(&rp[1], cmd->odata, olen);
+ cmd->olen = olen;
+ }
+ }
+ wakeup_one(cmd); /* wake up caller */
+ break;
+
+ case WDCMSG_TARGET_START:
+ if (hdr->msgid >= UATH_CMD_LIST_COUNT) {
+ /* XXX */
+ return;
+ }
+ dlen = hdr->len - sizeof(*hdr);
+ if (dlen != sizeof(uint32_t)) {
+ device_printf(sc->sc_dev,
+ "%s: dlen (%u) != %zu!\n",
+ __func__, dlen, sizeof(uint32_t));
+ return;
+ }
+ if (cmd->odata != NULL) {
+ /* XXX have submitter do this */
+ /* copy answer into caller's supplied buffer */
+ bcopy(hdr+1, cmd->odata, sizeof(uint32_t));
+ cmd->olen = sizeof(uint32_t);
+ }
+ wakeup_one(cmd); /* wake up caller */
+ break;
+
+ case WDCMSG_SEND_COMPLETE:
+ /* this notification is sent when UATH_TX_NOTIFY is set */
+ DPRINTF(sc, UATH_DEBUG_RX_PROC | UATH_DEBUG_RECV_ALL,
+ "%s: received Tx notification\n", __func__);
+ break;
+
+ case WDCMSG_TARGET_GET_STATS:
+ DPRINTF(sc, UATH_DEBUG_RX_PROC | UATH_DEBUG_RECV_ALL,
+ "%s: received device statistics\n", __func__);
+ callout_reset(&sc->stat_ch, hz, uath_stat, sc);
+ break;
+ }
+}
+
+static void
+uath_intr_rx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uath_softc *sc = usbd_xfer_softc(xfer);
+ struct uath_cmd *cmd;
+ struct uath_cmd_hdr *hdr;
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ UATH_ASSERT_LOCKED(sc);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ cmd = STAILQ_FIRST(&sc->sc_cmd_waiting);
+ if (cmd == NULL)
+ goto setup;
+ STAILQ_REMOVE_HEAD(&sc->sc_cmd_waiting, next);
+ UATH_STAT_DEC(sc, st_cmd_waiting);
+ STAILQ_INSERT_TAIL(&sc->sc_cmd_inactive, cmd, next);
+ UATH_STAT_INC(sc, st_cmd_inactive);
+
+ if (actlen < sizeof(struct uath_cmd_hdr)) {
+ device_printf(sc->sc_dev,
+ "%s: short xfer error (actlen %d)\n",
+ __func__, actlen);
+ goto setup;
+ }
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, cmd->buf, actlen);
+
+ hdr = (struct uath_cmd_hdr *)cmd->buf;
+ if (be32toh(hdr->len) > (uint32_t)actlen) {
+ device_printf(sc->sc_dev,
+ "%s: truncated xfer (len %u, actlen %d)\n",
+ __func__, be32toh(hdr->len), actlen);
+ goto setup;
+ }
+
+ uath_cmdeof(sc, cmd);
+ case USB_ST_SETUP:
+setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto setup;
+ }
+ break;
+ }
+}
+
+static void
+uath_intr_tx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uath_softc *sc = usbd_xfer_softc(xfer);
+ struct uath_cmd *cmd;
+
+ UATH_ASSERT_LOCKED(sc);
+
+ cmd = STAILQ_FIRST(&sc->sc_cmd_active);
+ if (cmd != NULL && USB_GET_STATE(xfer) != USB_ST_SETUP) {
+ STAILQ_REMOVE_HEAD(&sc->sc_cmd_active, next);
+ UATH_STAT_DEC(sc, st_cmd_active);
+ STAILQ_INSERT_TAIL((cmd->flags & UATH_CMD_FLAG_READ) ?
+ &sc->sc_cmd_waiting : &sc->sc_cmd_inactive, cmd, next);
+ if (cmd->flags & UATH_CMD_FLAG_READ)
+ UATH_STAT_INC(sc, st_cmd_waiting);
+ else
+ UATH_STAT_INC(sc, st_cmd_inactive);
+ }
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ case USB_ST_SETUP:
+setup:
+ cmd = STAILQ_FIRST(&sc->sc_cmd_pending);
+ if (cmd == NULL) {
+ DPRINTF(sc, UATH_DEBUG_XMIT, "%s: empty pending queue\n",
+ __func__);
+ return;
+ }
+ STAILQ_REMOVE_HEAD(&sc->sc_cmd_pending, next);
+ UATH_STAT_DEC(sc, st_cmd_pending);
+ STAILQ_INSERT_TAIL((cmd->flags & UATH_CMD_FLAG_ASYNC) ?
+ &sc->sc_cmd_inactive : &sc->sc_cmd_active, cmd, next);
+ if (cmd->flags & UATH_CMD_FLAG_ASYNC)
+ UATH_STAT_INC(sc, st_cmd_inactive);
+ else
+ UATH_STAT_INC(sc, st_cmd_active);
+
+ usbd_xfer_set_frame_data(xfer, 0, cmd->buf, cmd->buflen);
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto setup;
+ }
+ break;
+ }
+}
+
+static void
+uath_update_rxstat(struct uath_softc *sc, uint32_t status)
+{
+
+ switch (status) {
+ case UATH_STATUS_STOP_IN_PROGRESS:
+ UATH_STAT_INC(sc, st_stopinprogress);
+ break;
+ case UATH_STATUS_CRC_ERR:
+ UATH_STAT_INC(sc, st_crcerr);
+ break;
+ case UATH_STATUS_PHY_ERR:
+ UATH_STAT_INC(sc, st_phyerr);
+ break;
+ case UATH_STATUS_DECRYPT_CRC_ERR:
+ UATH_STAT_INC(sc, st_decrypt_crcerr);
+ break;
+ case UATH_STATUS_DECRYPT_MIC_ERR:
+ UATH_STAT_INC(sc, st_decrypt_micerr);
+ break;
+ case UATH_STATUS_DECOMP_ERR:
+ UATH_STAT_INC(sc, st_decomperr);
+ break;
+ case UATH_STATUS_KEY_ERR:
+ UATH_STAT_INC(sc, st_keyerr);
+ break;
+ case UATH_STATUS_ERR:
+ UATH_STAT_INC(sc, st_err);
+ break;
+ default:
+ break;
+ }
+}
+
+CTASSERT(UATH_MIN_RXBUFSZ >= sizeof(struct uath_chunk));
+
+static struct mbuf *
+uath_data_rxeof(struct usb_xfer *xfer, struct uath_data *data,
+ struct uath_rx_desc **pdesc)
+{
+ struct uath_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct uath_chunk *chunk;
+ struct uath_rx_desc *desc;
+ struct mbuf *m = data->m, *mnew, *mp;
+ uint16_t chunklen;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ if (actlen < (int)UATH_MIN_RXBUFSZ) {
+ DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL,
+ "%s: wrong xfer size (len=%d)\n", __func__, actlen);
+ counter_u64_add(ic->ic_ierrors, 1);
+ return (NULL);
+ }
+
+ chunk = (struct uath_chunk *)data->buf;
+ chunklen = be16toh(chunk->length);
+ if (chunk->seqnum == 0 && chunk->flags == 0 && chunklen == 0) {
+ device_printf(sc->sc_dev, "%s: strange response\n", __func__);
+ counter_u64_add(ic->ic_ierrors, 1);
+ UATH_RESET_INTRX(sc);
+ return (NULL);
+ }
+
+ if (chunklen > actlen) {
+ device_printf(sc->sc_dev,
+ "%s: invalid chunk length (len %u > actlen %d)\n",
+ __func__, chunklen, actlen);
+ counter_u64_add(ic->ic_ierrors, 1);
+ /* XXX cleanup? */
+ UATH_RESET_INTRX(sc);
+ return (NULL);
+ }
+
+ if (chunk->seqnum != sc->sc_intrx_nextnum) {
+ DPRINTF(sc, UATH_DEBUG_XMIT, "invalid seqnum %d, expected %d\n",
+ chunk->seqnum, sc->sc_intrx_nextnum);
+ UATH_STAT_INC(sc, st_badchunkseqnum);
+ if (sc->sc_intrx_head != NULL)
+ m_freem(sc->sc_intrx_head);
+ UATH_RESET_INTRX(sc);
+ return (NULL);
+ }
+
+ /* check multi-chunk frames */
+ if ((chunk->seqnum == 0 && !(chunk->flags & UATH_CFLAGS_FINAL)) ||
+ (chunk->seqnum != 0 && (chunk->flags & UATH_CFLAGS_FINAL)) ||
+ chunk->flags & UATH_CFLAGS_RXMSG)
+ UATH_STAT_INC(sc, st_multichunk);
+
+ if (chunk->flags & UATH_CFLAGS_FINAL) {
+ if (chunklen < sizeof(struct uath_rx_desc)) {
+ device_printf(sc->sc_dev,
+ "%s: invalid chunk length %d\n",
+ __func__, chunklen);
+ counter_u64_add(ic->ic_ierrors, 1);
+ if (sc->sc_intrx_head != NULL)
+ m_freem(sc->sc_intrx_head);
+ UATH_RESET_INTRX(sc);
+ return (NULL);
+ }
+ chunklen -= sizeof(struct uath_rx_desc);
+ }
+
+ if (chunklen > 0 &&
+ (!(chunk->flags & UATH_CFLAGS_FINAL) || !(chunk->seqnum == 0))) {
+ /* we should use intermediate RX buffer */
+ if (chunk->seqnum == 0)
+ UATH_RESET_INTRX(sc);
+ if ((sc->sc_intrx_len + sizeof(struct uath_rx_desc) +
+ chunklen) > UATH_MAX_INTRX_SIZE) {
+ UATH_STAT_INC(sc, st_invalidlen);
+ counter_u64_add(ic->ic_ierrors, 1);
+ if (sc->sc_intrx_head != NULL)
+ m_freem(sc->sc_intrx_head);
+ UATH_RESET_INTRX(sc);
+ return (NULL);
+ }
+
+ m->m_len = chunklen;
+ m->m_data += sizeof(struct uath_chunk);
+
+ if (sc->sc_intrx_head == NULL) {
+ sc->sc_intrx_head = m;
+ sc->sc_intrx_tail = m;
+ } else {
+ m->m_flags &= ~M_PKTHDR;
+ sc->sc_intrx_tail->m_next = m;
+ sc->sc_intrx_tail = m;
+ }
+ }
+ sc->sc_intrx_len += chunklen;
+
+ mnew = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (mnew == NULL) {
+ DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL,
+ "%s: can't get new mbuf, drop frame\n", __func__);
+ counter_u64_add(ic->ic_ierrors, 1);
+ if (sc->sc_intrx_head != NULL)
+ m_freem(sc->sc_intrx_head);
+ UATH_RESET_INTRX(sc);
+ return (NULL);
+ }
+
+ data->m = mnew;
+ data->buf = mtod(mnew, uint8_t *);
+
+ /* if the frame is not final continue the transfer */
+ if (!(chunk->flags & UATH_CFLAGS_FINAL)) {
+ sc->sc_intrx_nextnum++;
+ UATH_RESET_INTRX(sc);
+ return (NULL);
+ }
+
+ /*
+ * if the frame is not set UATH_CFLAGS_RXMSG, then rx descriptor is
+ * located at the end, 32-bit aligned
+ */
+ desc = (chunk->flags & UATH_CFLAGS_RXMSG) ?
+ (struct uath_rx_desc *)(chunk + 1) :
+ (struct uath_rx_desc *)(((uint8_t *)chunk) +
+ sizeof(struct uath_chunk) + be16toh(chunk->length) -
+ sizeof(struct uath_rx_desc));
+ if ((uint8_t *)chunk + actlen - sizeof(struct uath_rx_desc) <
+ (uint8_t *)desc) {
+ device_printf(sc->sc_dev,
+ "%s: wrong Rx descriptor pointer "
+ "(desc %p chunk %p actlen %d)\n",
+ __func__, desc, chunk, actlen);
+ counter_u64_add(ic->ic_ierrors, 1);
+ if (sc->sc_intrx_head != NULL)
+ m_freem(sc->sc_intrx_head);
+ UATH_RESET_INTRX(sc);
+ return (NULL);
+ }
+
+ *pdesc = desc;
+
+ DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL,
+ "%s: frame len %u code %u status %u rate %u antenna %u "
+ "rssi %d channel %u phyerror %u connix %u decrypterror %u "
+ "keycachemiss %u\n", __func__, be32toh(desc->framelen)
+ , be32toh(desc->code), be32toh(desc->status), be32toh(desc->rate)
+ , be32toh(desc->antenna), be32toh(desc->rssi), be32toh(desc->channel)
+ , be32toh(desc->phyerror), be32toh(desc->connix)
+ , be32toh(desc->decrypterror), be32toh(desc->keycachemiss));
+
+ if (be32toh(desc->len) > MCLBYTES) {
+ DPRINTF(sc, UATH_DEBUG_RECV | UATH_DEBUG_RECV_ALL,
+ "%s: bad descriptor (len=%d)\n", __func__,
+ be32toh(desc->len));
+ counter_u64_add(ic->ic_ierrors, 1);
+ UATH_STAT_INC(sc, st_toobigrxpkt);
+ if (sc->sc_intrx_head != NULL)
+ m_freem(sc->sc_intrx_head);
+ UATH_RESET_INTRX(sc);
+ return (NULL);
+ }
+
+ uath_update_rxstat(sc, be32toh(desc->status));
+
+ /* finalize mbuf */
+ if (sc->sc_intrx_head == NULL) {
+ uint32_t framelen;
+
+ if (be32toh(desc->framelen) < UATH_RX_DUMMYSIZE) {
+ device_printf(sc->sc_dev,
+ "%s: framelen too small (%u)\n",
+ __func__, be32toh(desc->framelen));
+ counter_u64_add(ic->ic_ierrors, 1);
+ if (sc->sc_intrx_head != NULL)
+ m_freem(sc->sc_intrx_head);
+ UATH_RESET_INTRX(sc);
+ return (NULL);
+ }
+
+ framelen = be32toh(desc->framelen) - UATH_RX_DUMMYSIZE;
+ if (framelen > actlen - sizeof(struct uath_chunk) ||
+ framelen < sizeof(struct ieee80211_frame_ack)) {
+ device_printf(sc->sc_dev,
+ "%s: wrong frame length (%u, actlen %d)!\n",
+ __func__, framelen, actlen);
+ counter_u64_add(ic->ic_ierrors, 1);
+ if (sc->sc_intrx_head != NULL)
+ m_freem(sc->sc_intrx_head);
+ UATH_RESET_INTRX(sc);
+ return (NULL);
+ }
+
+ m->m_pkthdr.len = m->m_len = framelen;
+ m->m_data += sizeof(struct uath_chunk);
+ } else {
+ mp = sc->sc_intrx_head;
+ mp->m_flags |= M_PKTHDR;
+ mp->m_pkthdr.len = sc->sc_intrx_len;
+ m = mp;
+ }
+
+ /* there are a lot more fields in the RX descriptor */
+ if ((sc->sc_flags & UATH_FLAG_INVALID) == 0 &&
+ ieee80211_radiotap_active(ic)) {
+ struct uath_rx_radiotap_header *tap = &sc->sc_rxtap;
+ uint32_t tsf_hi = be32toh(desc->tstamp_high);
+ uint32_t tsf_lo = be32toh(desc->tstamp_low);
+
+ /* XXX only get low order 24bits of tsf from h/w */
+ tap->wr_tsf = htole64(((uint64_t)tsf_hi << 32) | tsf_lo);
+ tap->wr_flags = 0;
+ if (be32toh(desc->status) == UATH_STATUS_CRC_ERR)
+ tap->wr_flags |= IEEE80211_RADIOTAP_F_BADFCS;
+ /* XXX map other status to BADFCS? */
+ /* XXX ath h/w rate code, need to map */
+ tap->wr_rate = be32toh(desc->rate);
+ tap->wr_antenna = be32toh(desc->antenna);
+ tap->wr_antsignal = -95 + be32toh(desc->rssi);
+ tap->wr_antnoise = -95;
+ }
+
+ UATH_RESET_INTRX(sc);
+
+ return (m);
+}
+
+static void
+uath_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uath_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_frame *wh;
+ struct ieee80211_node *ni;
+ struct mbuf *m = NULL;
+ struct uath_data *data;
+ struct uath_rx_desc *desc = NULL;
+ int8_t nf;
+
+ UATH_ASSERT_LOCKED(sc);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ data = STAILQ_FIRST(&sc->sc_rx_active);
+ if (data == NULL)
+ goto setup;
+ STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next);
+ UATH_STAT_DEC(sc, st_rx_active);
+ m = uath_data_rxeof(xfer, data, &desc);
+ STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next);
+ UATH_STAT_INC(sc, st_rx_inactive);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+setup:
+ data = STAILQ_FIRST(&sc->sc_rx_inactive);
+ if (data == NULL)
+ return;
+ STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next);
+ UATH_STAT_DEC(sc, st_rx_inactive);
+ STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next);
+ UATH_STAT_INC(sc, st_rx_active);
+ usbd_xfer_set_frame_data(xfer, 0, data->buf, MCLBYTES);
+ usbd_transfer_submit(xfer);
+
+ /*
+ * To avoid LOR we should unlock our private mutex here to call
+ * ieee80211_input() because here is at the end of a USB
+ * callback and safe to unlock.
+ */
+ if (sc->sc_flags & UATH_FLAG_INVALID) {
+ if (m != NULL)
+ m_freem(m);
+ return;
+ }
+ UATH_UNLOCK(sc);
+ if (m != NULL && desc != NULL) {
+ wh = mtod(m, struct ieee80211_frame *);
+ ni = ieee80211_find_rxnode(ic,
+ (struct ieee80211_frame_min *)wh);
+ nf = -95; /* XXX */
+ if (ni != NULL) {
+ (void) ieee80211_input(ni, m,
+ (int)be32toh(desc->rssi), nf);
+ /* node is no longer needed */
+ ieee80211_free_node(ni);
+ } else
+ (void) ieee80211_input_all(ic, m,
+ (int)be32toh(desc->rssi), nf);
+ m = NULL;
+ desc = NULL;
+ }
+ UATH_LOCK(sc);
+ uath_start(sc);
+ break;
+ default:
+ /* needs it to the inactive queue due to a error. */
+ data = STAILQ_FIRST(&sc->sc_rx_active);
+ if (data != NULL) {
+ STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next);
+ UATH_STAT_DEC(sc, st_rx_active);
+ STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next);
+ UATH_STAT_INC(sc, st_rx_inactive);
+ }
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto setup;
+ }
+ break;
+ }
+}
+
+static void
+uath_data_txeof(struct usb_xfer *xfer, struct uath_data *data)
+{
+ struct uath_softc *sc = usbd_xfer_softc(xfer);
+
+ UATH_ASSERT_LOCKED(sc);
+
+ if (data->m) {
+ /* XXX status? */
+ ieee80211_tx_complete(data->ni, data->m, 0);
+ data->m = NULL;
+ data->ni = NULL;
+ }
+ sc->sc_tx_timer = 0;
+}
+
+static void
+uath_bulk_tx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct uath_softc *sc = usbd_xfer_softc(xfer);
+ struct uath_data *data;
+
+ UATH_ASSERT_LOCKED(sc);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ data = STAILQ_FIRST(&sc->sc_tx_active);
+ if (data == NULL)
+ goto setup;
+ STAILQ_REMOVE_HEAD(&sc->sc_tx_active, next);
+ UATH_STAT_DEC(sc, st_tx_active);
+ uath_data_txeof(xfer, data);
+ STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next);
+ UATH_STAT_INC(sc, st_tx_inactive);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+setup:
+ data = STAILQ_FIRST(&sc->sc_tx_pending);
+ if (data == NULL) {
+ DPRINTF(sc, UATH_DEBUG_XMIT, "%s: empty pending queue\n",
+ __func__);
+ return;
+ }
+ STAILQ_REMOVE_HEAD(&sc->sc_tx_pending, next);
+ UATH_STAT_DEC(sc, st_tx_pending);
+ STAILQ_INSERT_TAIL(&sc->sc_tx_active, data, next);
+ UATH_STAT_INC(sc, st_tx_active);
+
+ usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen);
+ usbd_transfer_submit(xfer);
+
+ uath_start(sc);
+ break;
+ default:
+ data = STAILQ_FIRST(&sc->sc_tx_active);
+ if (data == NULL)
+ goto setup;
+ if (data->ni != NULL) {
+ if_inc_counter(data->ni->ni_vap->iv_ifp,
+ IFCOUNTER_OERRORS, 1);
+ if ((sc->sc_flags & UATH_FLAG_INVALID) == 0)
+ ieee80211_free_node(data->ni);
+ data->ni = NULL;
+ }
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto setup;
+ }
+ break;
+ }
+}
+
+static device_method_t uath_methods[] = {
+ DEVMETHOD(device_probe, uath_match),
+ DEVMETHOD(device_attach, uath_attach),
+ DEVMETHOD(device_detach, uath_detach),
+ DEVMETHOD_END
+};
+
+static driver_t uath_driver = {
+ .name = "uath",
+ .methods = uath_methods,
+ .size = sizeof(struct uath_softc)
+};
+
+DRIVER_MODULE(uath, uhub, uath_driver, NULL, NULL);
+MODULE_DEPEND(uath, wlan, 1, 1, 1);
+MODULE_DEPEND(uath, usb, 1, 1, 1);
+MODULE_VERSION(uath, 1);
+USB_PNP_HOST_INFO(uath_devs);
diff --git a/sys/dev/usb/wlan/if_uathreg.h b/sys/dev/usb/wlan/if_uathreg.h
new file mode 100644
index 000000000000..91e5f04bc902
--- /dev/null
+++ b/sys/dev/usb/wlan/if_uathreg.h
@@ -0,0 +1,600 @@
+/* $OpenBSD: if_uathreg.h,v 1.2 2006/09/18 16:34:23 damien Exp $ */
+
+/*-
+ * Copyright (c) 2006
+ * Damien Bergamini <damien.bergamini@free.fr>
+ * Copyright (c) 2006 Sam Leffler, Errno Consulting
+ *
+ * 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 UATH_CONFIG_INDEX 0
+#define UATH_IFACE_INDEX 0
+
+/* all fields are big endian */
+struct uath_fwblock {
+ uint32_t flags;
+#define UATH_WRITE_BLOCK (1 << 4)
+
+ uint32_t len;
+#define UATH_MAX_FWBLOCK_SIZE 2048
+
+ uint32_t total;
+ uint32_t remain;
+ uint32_t rxtotal;
+ uint32_t pad[123];
+} __packed;
+
+#define UATH_MAX_CMDSZ 512
+
+/*
+ * Messages are passed in Target Endianness. All fixed-size
+ * fields of a WDS Control Message are treated as 32-bit
+ * values and Control Msgs are guaranteed to be 32-bit aligned.
+ *
+ * The format of a WDS Control Message is as follows:
+ * Message Length 32 bits
+ * Message Opcode 32 bits
+ * Message ID 32 bits
+ * parameter 1
+ * parameter 2
+ * ...
+ *
+ * A variable-length parameter, or a parameter that is larger than
+ * 32 bits is passed as <length, data> pair, where length is a
+ * 32-bit quantity and data is padded to 32 bits.
+ */
+struct uath_cmd_hdr {
+ uint32_t len; /* msg length including header */
+ uint32_t code; /* operation code */
+/* NB: these are defined for rev 1.5 firmware; rev 1.6 is different */
+/* messages from Host -> Target */
+#define WDCMSG_HOST_AVAILABLE 0x01
+#define WDCMSG_BIND 0x02
+#define WDCMSG_TARGET_RESET 0x03
+#define WDCMSG_TARGET_GET_CAPABILITY 0x04
+#define WDCMSG_TARGET_SET_CONFIG 0x05
+#define WDCMSG_TARGET_GET_STATUS 0x06
+#define WDCMSG_TARGET_GET_STATS 0x07
+#define WDCMSG_TARGET_START 0x08
+#define WDCMSG_TARGET_STOP 0x09
+#define WDCMSG_TARGET_ENABLE 0x0a
+#define WDCMSG_TARGET_DISABLE 0x0b
+#define WDCMSG_CREATE_CONNECTION 0x0c
+#define WDCMSG_UPDATE_CONNECT_ATTR 0x0d
+#define WDCMSG_DELETE_CONNECT 0x0e
+#define WDCMSG_SEND 0x0f
+#define WDCMSG_FLUSH 0x10
+/* messages from Target -> Host */
+#define WDCMSG_STATS_UPDATE 0x11
+#define WDCMSG_BMISS 0x12
+#define WDCMSG_DEVICE_AVAIL 0x13
+#define WDCMSG_SEND_COMPLETE 0x14
+#define WDCMSG_DATA_AVAIL 0x15
+#define WDCMSG_SET_PWR_MODE 0x16
+#define WDCMSG_BMISS_ACK 0x17
+#define WDCMSG_SET_LED_STEADY 0x18
+#define WDCMSG_SET_LED_BLINK 0x19
+/* more messages */
+#define WDCMSG_SETUP_BEACON_DESC 0x1a
+#define WDCMSG_BEACON_INIT 0x1b
+#define WDCMSG_RESET_KEY_CACHE 0x1c
+#define WDCMSG_RESET_KEY_CACHE_ENTRY 0x1d
+#define WDCMSG_SET_KEY_CACHE_ENTRY 0x1e
+#define WDCMSG_SET_DECOMP_MASK 0x1f
+#define WDCMSG_SET_REGULATORY_DOMAIN 0x20
+#define WDCMSG_SET_LED_STATE 0x21
+#define WDCMSG_WRITE_ASSOCID 0x22
+#define WDCMSG_SET_STA_BEACON_TIMERS 0x23
+#define WDCMSG_GET_TSF 0x24
+#define WDCMSG_RESET_TSF 0x25
+#define WDCMSG_SET_ADHOC_MODE 0x26
+#define WDCMSG_SET_BASIC_RATE 0x27
+#define WDCMSG_MIB_CONTROL 0x28
+#define WDCMSG_GET_CHANNEL_DATA 0x29
+#define WDCMSG_GET_CUR_RSSI 0x2a
+#define WDCMSG_SET_ANTENNA_SWITCH 0x2b
+#define WDCMSG_USE_SHORT_SLOT_TIME 0x2f
+#define WDCMSG_SET_POWER_MODE 0x30
+#define WDCMSG_SETUP_PSPOLL_DESC 0x31
+#define WDCMSG_SET_RX_MULTICAST_FILTER 0x32
+#define WDCMSG_RX_FILTER 0x33
+#define WDCMSG_PER_CALIBRATION 0x34
+#define WDCMSG_RESET 0x35
+#define WDCMSG_DISABLE 0x36
+#define WDCMSG_PHY_DISABLE 0x37
+#define WDCMSG_SET_TX_POWER_LIMIT 0x38
+#define WDCMSG_SET_TX_QUEUE_PARAMS 0x39
+#define WDCMSG_SETUP_TX_QUEUE 0x3a
+#define WDCMSG_RELEASE_TX_QUEUE 0x3b
+#define WDCMSG_SET_DEFAULT_KEY 0x43
+ uint32_t msgid; /* msg id (supplied by host) */
+ uint32_t magic; /* response desired/target status */
+ uint32_t debug[4]; /* debug data area */
+ /* msg data follows */
+} __packed;
+
+struct uath_chunk {
+ uint8_t seqnum; /* sequence number for ordering */
+ uint8_t flags;
+#define UATH_CFLAGS_FINAL 0x01 /* final chunk of a msg */
+#define UATH_CFLAGS_RXMSG 0x02 /* chunk contains rx completion */
+#define UATH_CFLAGS_DEBUG 0x04 /* for debugging */
+ uint16_t length; /* chunk size in bytes */
+ /* chunk data follows */
+} __packed;
+
+#define UATH_RX_DUMMYSIZE 4
+
+/*
+ * Message format for a WDCMSG_DATA_AVAIL message from Target to Host.
+ */
+struct uath_rx_desc {
+ uint32_t len; /* msg length including header */
+ uint32_t code; /* WDCMSG_DATA_AVAIL */
+ uint32_t gennum; /* generation number */
+ uint32_t status; /* start of RECEIVE_INFO */
+#define UATH_STATUS_OK 0
+#define UATH_STATUS_STOP_IN_PROGRESS 1
+#define UATH_STATUS_CRC_ERR 2
+#define UATH_STATUS_PHY_ERR 3
+#define UATH_STATUS_DECRYPT_CRC_ERR 4
+#define UATH_STATUS_DECRYPT_MIC_ERR 5
+#define UATH_STATUS_DECOMP_ERR 6
+#define UATH_STATUS_KEY_ERR 7
+#define UATH_STATUS_ERR 8
+ uint32_t tstamp_low; /* low-order 32-bits of rx timestamp */
+ uint32_t tstamp_high; /* high-order 32-bits of rx timestamp */
+ uint32_t framelen; /* frame length */
+ uint32_t rate; /* rx rate code */
+ uint32_t antenna;
+ int32_t rssi;
+ uint32_t channel;
+ uint32_t phyerror;
+ uint32_t connix; /* key table ix for bss traffic */
+ uint32_t decrypterror;
+ uint32_t keycachemiss;
+ uint32_t pad; /* XXX? */
+} __packed;
+
+struct uath_tx_desc {
+ uint32_t msglen;
+ uint32_t msgid; /* msg id (supplied by host) */
+ uint32_t type; /* opcode: WDMSG_SEND or WDCMSG_FLUSH */
+ uint32_t txqid; /* tx queue id and flags */
+#define UATH_TXQID_MASK 0x0f
+#define UATH_TXQID_MINRATE 0x10 /* use min tx rate */
+#define UATH_TXQID_FF 0x20 /* content is fast frame */
+ uint32_t connid; /* tx connection id */
+#define UATH_ID_INVALID 0xffffffff /* for sending prior to connection */
+ uint32_t flags; /* non-zero if response desired */
+#define UATH_TX_NOTIFY (1 << 24) /* f/w will send a UATH_NOTIF_TX */
+ uint32_t buflen; /* payload length */
+} __packed;
+
+struct uath_cmd_host_available {
+ uint32_t sw_ver_major;
+ uint32_t sw_ver_minor;
+ uint32_t sw_ver_patch;
+ uint32_t sw_ver_build;
+} __packed;
+#define ATH_SW_VER_MAJOR 1
+#define ATH_SW_VER_MINOR 5
+#define ATH_SW_VER_PATCH 0
+#define ATH_SW_VER_BUILD 9999
+
+struct uath_cmd_bind {
+ uint32_t targethandle;
+ uint32_t hostapiversion;
+} __packed;
+
+/* structure for command WDCMSG_RESET */
+struct uath_cmd_reset {
+ uint32_t flags; /* channel flags */
+#define UATH_CHAN_TURBO 0x0100
+#define UATH_CHAN_CCK 0x0200
+#define UATH_CHAN_OFDM 0x0400
+#define UATH_CHAN_2GHZ 0x1000
+#define UATH_CHAN_5GHZ 0x2000
+ uint32_t freq; /* channel frequency */
+ uint32_t maxrdpower;
+ uint32_t cfgctl;
+ uint32_t twiceantennareduction;
+ uint32_t channelchange;
+ uint32_t keeprccontent;
+} __packed;
+
+/* structure for commands UATH_CMD_READ_MAC and UATH_CMD_READ_EEPROM */
+struct uath_read_mac {
+ uint32_t len;
+ uint8_t data[32];
+} __packed;
+
+/* structure for command UATH_CMD_WRITE_MAC */
+struct uath_write_mac {
+ uint32_t reg;
+ uint32_t len;
+ uint8_t data[32];
+} __packed;
+
+/* structure for command UATH_CMD_STA_JOIN */
+struct uath_cmd_join_bss {
+ uint32_t bssid; /* NB: use zero */
+ uint32_t bssmac[2]; /* bssid mac address */
+ uint32_t bsstype;
+ uint32_t wlanmode;
+ uint32_t beaconinterval;
+ uint32_t dtiminterval;
+ uint32_t cfpinterval;
+ uint32_t atimwindow;
+ uint32_t defaultrateix;
+ uint32_t shortslottime11g;
+ uint32_t sleepduration;
+ uint32_t bmissthreshold;
+ uint32_t tcppowerlimit;
+ uint32_t quietduration;
+ uint32_t quietoffset;
+ uint32_t quietackctsallow;
+ uint32_t bssdefaultkey; /* XXX? */
+} __packed;
+
+struct uath_cmd_assoc_bss {
+ uint32_t bssid;
+ uint32_t associd;
+} __packed;
+
+struct uath_cmd_start_bss {
+ uint32_t bssid;
+} __packed;
+
+/* structure for command UATH_CMD_0C */
+struct uath_cmd_0c {
+ uint32_t magic1;
+ uint32_t magic2;
+ uint32_t magic3;
+} __packed;
+
+struct uath_cmd_ledsteady { /* WDCMSG_SET_LED_STEADY */
+ uint32_t lednum;
+#define UATH_LED_LINK 0
+#define UATH_LED_ACTIVITY 1
+ uint32_t ledmode;
+#define UATH_LED_OFF 0
+#define UATH_LED_ON 1
+} __packed;
+
+struct uath_cmd_ledblink { /* WDCMSG_SET_LED_BLINK */
+ uint32_t lednum;
+ uint32_t ledmode;
+ uint32_t blinkrate;
+ uint32_t slowmode;
+} __packed;
+
+struct uath_cmd_ledstate { /* WDCMSG_SET_LED_STATE */
+ uint32_t connected;
+} __packed;
+
+struct uath_connkey_rec {
+ uint8_t bssid[IEEE80211_ADDR_LEN];
+ uint32_t keyiv;
+ uint32_t extkeyiv;
+ uint16_t keyflags;
+ uint16_t keylen;
+ uint16_t keytype; /* WEP, TKIP or AES */
+ /* As far as I know, MIPS 4Kp is 32-bit processor */
+ uint32_t priv;
+ uint8_t keyval[32];
+ uint16_t aes_keylen;
+ uint8_t aes_keyval[16];
+ uint8_t mic_txkeyval[8];
+ uint8_t mic_rxkeyval[8];
+ int64_t keyrsc[17];
+ int32_t keytsc[17];
+ int32_t keyexttsc[17];
+} __packed;
+
+/* structure for command UATH_CMD_CRYPTO */
+struct uath_cmd_crypto {
+ uint32_t keyidx;
+#define UATH_DEFAULT_KEY 6
+ uint32_t xorkey;
+ uint32_t size;
+ struct uath_connkey_rec rec;
+} __packed;
+
+struct uath_cmd_rateset {
+ uint8_t length;
+#define UATH_MAX_NRATES 32
+ uint8_t set[UATH_MAX_NRATES];
+};
+
+/* structure for command WDCMSG_SET_BASIC_RATE */
+struct uath_cmd_rates {
+ uint32_t connid;
+ uint32_t keeprccontent;
+ uint32_t size;
+ struct uath_cmd_rateset rateset;
+} __packed;
+
+enum {
+ WLAN_MODE_NONE = 0,
+ WLAN_MODE_11b,
+ WLAN_MODE_11a,
+ WLAN_MODE_11g,
+ WLAN_MODE_11a_TURBO,
+ WLAN_MODE_11g_TURBO,
+ WLAN_MODE_11a_TURBO_PRIME,
+ WLAN_MODE_11g_TURBO_PRIME,
+ WLAN_MODE_11a_XR,
+ WLAN_MODE_11g_XR,
+};
+
+struct uath_cmd_connection_attr {
+ uint32_t longpreambleonly;
+ struct uath_cmd_rateset rateset;
+ uint32_t wlanmode;
+} __packed;
+
+/* structure for command WDCMSG_CREATE_CONNECTION */
+struct uath_cmd_create_connection {
+ uint32_t connid;
+ uint32_t bssid;
+ uint32_t size;
+ struct uath_cmd_connection_attr connattr;
+} __packed;
+
+struct uath_cmd_txq_setparams { /* WDCMSG_SET_TX_QUEUE_PARAMS */
+ uint32_t qnum;
+ uint32_t aifs;
+ uint32_t logcwmin;
+ uint32_t logcwmax;
+ uint32_t bursttime;
+ uint32_t qflags;
+} __packed;
+
+struct uath_cmd_txq_attr {
+ uint32_t priority;
+ uint32_t aifs;
+ uint32_t logcwmin;
+ uint32_t logcwmax;
+ uint32_t bursttime;
+ uint32_t mode;
+ uint32_t qflags;
+} __packed;
+
+struct uath_cmd_txq_setup { /* WDCMSG_SETUP_TX_QUEUE */
+ uint32_t qid;
+ uint32_t len;
+ struct uath_cmd_txq_attr attr;
+} __packed;
+
+struct uath_cmd_stoptxdma { /* WDCMSG_STOP_TX_DMA */
+ uint32_t qnum;
+ uint32_t msec;
+} __packed;
+
+/* structure for command UATH_CMD_31 */
+struct uath_cmd_31 {
+ uint32_t magic1;
+ uint32_t magic2;
+} __packed;
+
+struct uath_cmd_rx_filter { /* WDCMSG_RX_FILTER */
+ uint32_t bits;
+#define UATH_FILTER_RX_UCAST 0x00000001
+#define UATH_FILTER_RX_MCAST 0x00000002
+#define UATH_FILTER_RX_BCAST 0x00000004
+#define UATH_FILTER_RX_CONTROL 0x00000008
+#define UATH_FILTER_RX_BEACON 0x00000010 /* beacon frames */
+#define UATH_FILTER_RX_PROM 0x00000020 /* promiscuous mode */
+#define UATH_FILTER_RX_PHY_ERR 0x00000040 /* phy errors */
+#define UATH_FILTER_RX_PHY_RADAR 0x00000080 /* radar phy errors */
+#define UATH_FILTER_RX_XR_POOL 0x00000400 /* XR group polls */
+#define UATH_FILTER_RX_PROBE_REQ 0x00000800
+ uint32_t op;
+#define UATH_FILTER_OP_INIT 0x0
+#define UATH_FILTER_OP_SET 0x1
+#define UATH_FILTER_OP_CLEAR 0x2
+#define UATH_FILTER_OP_TEMP 0x3
+#define UATH_FILTER_OP_RESTORE 0x4
+} __packed;
+
+struct uath_cmd_rx_mcast_filter { /* WDCMSG_SET_RX_MCAST_FILTER */
+ uint32_t filter0;
+ uint32_t filter1;
+} __packed;
+
+struct uath_cmd_set_associd { /* WDCMSG_WRITE_ASSOCID */
+ uint32_t defaultrateix;
+ uint32_t associd;
+ uint32_t timoffset;
+ uint32_t turboprime;
+ uint32_t bssid[2];
+} __packed;
+
+struct uath_cmd_set_stabeacon_timers { /* WDCMSG_SET_STA_BEACON_TIMERS */
+ uint32_t nexttbtt;
+ uint32_t nextdtim;
+ uint32_t nextcfp;
+ uint32_t beaconperiod;
+ uint32_t dtimperiod;
+ uint32_t cfpperiod;
+ uint32_t cfpduration;
+ uint32_t sleepduration;
+ uint32_t bsmissthreshold;
+} __packed;
+
+enum {
+ CFG_NONE, /* Sentinal to indicate "no config" */
+ CFG_REG_DOMAIN, /* Regulatory Domain */
+ CFG_RATE_CONTROL_ENABLE,
+ CFG_DEF_XMIT_DATA_RATE, /* NB: if rate control is not enabled */
+ CFG_HW_TX_RETRIES,
+ CFG_SW_TX_RETRIES,
+ CFG_SLOW_CLOCK_ENABLE,
+ CFG_COMP_PROC,
+ CFG_USER_RTS_THRESHOLD,
+ CFG_XR2NORM_RATE_THRESHOLD,
+ CFG_XRMODE_SWITCH_COUNT,
+ CFG_PROTECTION_TYPE,
+ CFG_BURST_SEQ_THRESHOLD,
+ CFG_ABOLT,
+ CFG_IQ_LOG_COUNT_MAX,
+ CFG_MODE_CTS,
+ CFG_WME_ENABLED,
+ CFG_GPRS_CBR_PERIOD,
+ CFG_SERVICE_TYPE,
+ /* MAC Address to use. Overrides EEPROM */
+ CFG_MAC_ADDR,
+ CFG_DEBUG_EAR,
+ CFG_INIT_REGS,
+ /* An ID for use in error & debug messages */
+ CFG_DEBUG_ID,
+ CFG_COMP_WIN_SZ,
+ CFG_DIVERSITY_CTL,
+ CFG_TP_SCALE,
+ CFG_TPC_HALF_DBM5,
+ CFG_TPC_HALF_DBM2,
+ CFG_OVERRD_TX_POWER,
+ CFG_USE_32KHZ_CLOCK,
+ CFG_GMODE_PROTECTION,
+ CFG_GMODE_PROTECT_RATE_INDEX,
+ CFG_GMODE_NON_ERP_PREAMBLE,
+ CFG_WDC_TRANSPORT_CHUNK_SIZE,
+};
+
+enum {
+ /* Sentinal to indicate "no capability" */
+ CAP_NONE,
+ CAP_ALL, /* ALL capabilities */
+ CAP_TARGET_VERSION,
+ CAP_TARGET_REVISION,
+ CAP_MAC_VERSION,
+ CAP_MAC_REVISION,
+ CAP_PHY_REVISION,
+ CAP_ANALOG_5GHz_REVISION,
+ CAP_ANALOG_2GHz_REVISION,
+ /* Target supports WDC message debug features */
+ CAP_DEBUG_WDCMSG_SUPPORT,
+
+ CAP_REG_DOMAIN,
+ CAP_COUNTRY_CODE,
+ CAP_REG_CAP_BITS,
+
+ CAP_WIRELESS_MODES,
+ CAP_CHAN_SPREAD_SUPPORT,
+ CAP_SLEEP_AFTER_BEACON_BROKEN,
+ CAP_COMPRESS_SUPPORT,
+ CAP_BURST_SUPPORT,
+ CAP_FAST_FRAMES_SUPPORT,
+ CAP_CHAP_TUNING_SUPPORT,
+ CAP_TURBOG_SUPPORT,
+ CAP_TURBO_PRIME_SUPPORT,
+ CAP_DEVICE_TYPE,
+ CAP_XR_SUPPORT,
+ CAP_WME_SUPPORT,
+ CAP_TOTAL_QUEUES,
+ CAP_CONNECTION_ID_MAX, /* Should absorb CAP_KEY_CACHE_SIZE */
+
+ CAP_LOW_5GHZ_CHAN,
+ CAP_HIGH_5GHZ_CHAN,
+ CAP_LOW_2GHZ_CHAN,
+ CAP_HIGH_2GHZ_CHAN,
+
+ CAP_MIC_AES_CCM,
+ CAP_MIC_CKIP,
+ CAP_MIC_TKIP,
+ CAP_MIC_TKIP_WME,
+ CAP_CIPHER_AES_CCM,
+ CAP_CIPHER_CKIP,
+ CAP_CIPHER_TKIP,
+
+ CAP_TWICE_ANTENNAGAIN_5G,
+ CAP_TWICE_ANTENNAGAIN_2G,
+};
+
+enum {
+ ST_NONE, /* Sentinal to indicate "no status" */
+ ST_ALL,
+ ST_SERVICE_TYPE,
+ ST_WLAN_MODE,
+ ST_FREQ,
+ ST_BAND,
+ ST_LAST_RSSI,
+ ST_PS_FRAMES_DROPPED,
+ ST_CACHED_DEF_ANT,
+ ST_COUNT_OTHER_RX_ANT,
+ ST_USE_FAST_DIVERSITY,
+ ST_MAC_ADDR,
+ ST_RX_GENERATION_NUM,
+ ST_TX_QUEUE_DEPTH,
+ ST_SERIAL_NUMBER,
+ ST_WDC_TRANSPORT_CHUNK_SIZE,
+};
+
+enum {
+ BSS_ATTR_BEACON_INTERVAL,
+ BSS_ATTR_DTIM_INTERVAL,
+ BSS_ATTR_CFP_INTERVAL,
+ BSS_ATTR_CFP_MAX_DURATION,
+ BSS_ATTR_ATIM_WINDOW,
+ BSS_ATTR_DEFAULT_RATE_INDEX,
+ BSS_ATTR_SHORT_SLOT_TIME_11g,
+ BSS_ATTR_SLEEP_DURATION,
+ BSS_ATTR_BMISS_THRESHOLD,
+ BSS_ATTR_TPC_POWER_LIMIT,
+ BSS_ATTR_BSS_KEY_UPDATE,
+};
+
+struct uath_cmd_update_bss_attribute {
+ uint32_t bssid;
+ uint32_t attribute; /* BSS_ATTR_BEACON_INTERVAL, et al. */
+ uint32_t cfgsize; /* should be zero 0 */
+ uint32_t cfgdata;
+};
+
+struct uath_cmd_update_bss_attribute_key {
+ uint32_t bssid;
+ uint32_t attribute; /* BSS_ATTR_BSS_KEY_UPDATE */
+ uint32_t cfgsize; /* size of remaining data */
+ uint32_t bsskeyix;
+ uint32_t isdefaultkey;
+ uint32_t keyiv; /* IV generation control */
+ uint32_t extkeyiv; /* extended IV for TKIP & CCM */
+ uint32_t keyflags;
+ uint32_t keytype;
+ uint32_t initvalue; /* XXX */
+ uint32_t keyval[4];
+ uint32_t mictxkeyval[2];
+ uint32_t micrxkeyval[2];
+ uint32_t keyrsc[2];
+};
+
+enum {
+ TARGET_DEVICE_AWAKE,
+ TARGET_DEVICE_SLEEP,
+ TARGET_DEVICE_PWRDN,
+ TARGET_DEVICE_PWRSAVE,
+ TARGET_DEVICE_SUSPEND,
+ TARGET_DEVICE_RESUME,
+};
+
+#define UATH_MAX_TXBUFSZ \
+ (sizeof(struct uath_chunk) + sizeof(struct uath_tx_desc) + \
+ IEEE80211_MAX_LEN)
+
+/*
+ * it's not easy to measure how the chunk is passed into the host if the target
+ * passed the multi-chunks so just we check a minimal size we can imagine.
+ */
+#define UATH_MIN_RXBUFSZ (sizeof(struct uath_chunk))
diff --git a/sys/dev/usb/wlan/if_uathvar.h b/sys/dev/usb/wlan/if_uathvar.h
new file mode 100644
index 000000000000..ce380667c8ee
--- /dev/null
+++ b/sys/dev/usb/wlan/if_uathvar.h
@@ -0,0 +1,245 @@
+/* $OpenBSD: if_uathvar.h,v 1.3 2006/09/20 19:47:17 damien Exp $ */
+
+/*-
+ * Copyright (c) 2006
+ * Damien Bergamini <damien.bergamini@free.fr>
+ * Copyright (c) 2006 Sam Leffler, Errno Consulting
+ * Copyright (c) 2008-2009 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.
+ */
+
+enum {
+ UATH_INTR_RX,
+ UATH_INTR_TX,
+ UATH_BULK_RX,
+ UATH_BULK_TX,
+ UATH_N_XFERS = 4,
+};
+
+#define UATH_ID_BSS 2 /* Connection ID */
+
+#define UATH_RX_DATA_LIST_COUNT 128
+#define UATH_TX_DATA_LIST_COUNT 16
+#define UATH_CMD_LIST_COUNT 60
+
+#define UATH_DATA_TIMEOUT 10000
+#define UATH_CMD_TIMEOUT 1000
+
+/* flags for sending firmware commands */
+#define UATH_CMD_FLAG_ASYNC (1 << 0)
+#define UATH_CMD_FLAG_READ (1 << 1)
+#define UATH_CMD_FLAG_MAGIC (1 << 2)
+
+struct uath_rx_radiotap_header {
+ struct ieee80211_radiotap_header wr_ihdr;
+ u_int64_t wr_tsf;
+ u_int8_t wr_flags;
+ u_int8_t wr_rate;
+ uint16_t wr_chan_freq;
+ uint16_t wr_chan_flags;
+ int8_t wr_antsignal;
+ int8_t wr_antnoise;
+ u_int8_t wr_antenna;
+} __packed __aligned(8);
+
+#define UATH_RX_RADIOTAP_PRESENT ( \
+ (1 << IEEE80211_RADIOTAP_TSFT) | \
+ (1 << IEEE80211_RADIOTAP_FLAGS) | \
+ (1 << IEEE80211_RADIOTAP_RATE) | \
+ (1 << IEEE80211_RADIOTAP_ANTENNA) | \
+ (1 << IEEE80211_RADIOTAP_CHANNEL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \
+ 0)
+
+struct uath_tx_radiotap_header {
+ struct ieee80211_radiotap_header wt_ihdr;
+ uint8_t wt_flags;
+ uint8_t wt_pad;
+ uint16_t wt_chan_freq;
+ uint16_t wt_chan_flags;
+} __packed;
+
+#define UATH_TX_RADIOTAP_PRESENT \
+ ((1 << IEEE80211_RADIOTAP_FLAGS) | \
+ (1 << IEEE80211_RADIOTAP_CHANNEL))
+
+struct uath_data {
+ struct uath_softc *sc;
+ uint8_t *buf;
+ uint16_t buflen;
+ struct mbuf *m;
+ struct ieee80211_node *ni; /* NB: tx only */
+ STAILQ_ENTRY(uath_data) next;
+};
+typedef STAILQ_HEAD(, uath_data) uath_datahead;
+
+struct uath_cmd {
+ struct uath_softc *sc;
+ uint32_t flags;
+ uint32_t msgid;
+ uint8_t *buf;
+ uint16_t buflen;
+ void *odata; /* NB: tx only */
+ int olen; /* space in odata */
+ STAILQ_ENTRY(uath_cmd) next;
+};
+typedef STAILQ_HEAD(, uath_cmd) uath_cmdhead;
+
+struct uath_wme_settings {
+ uint8_t aifsn;
+ uint8_t logcwmin;
+ uint8_t logcwmax;
+ uint16_t txop;
+ uint8_t acm;
+};
+
+struct uath_devcap {
+ uint32_t targetVersion;
+ uint32_t targetRevision;
+ uint32_t macVersion;
+ uint32_t macRevision;
+ uint32_t phyRevision;
+ uint32_t analog5GhzRevision;
+ uint32_t analog2GhzRevision;
+ uint32_t regDomain;
+ uint32_t regCapBits;
+ uint32_t countryCode;
+ uint32_t keyCacheSize;
+ uint32_t numTxQueues;
+ uint32_t connectionIdMax;
+ uint32_t wirelessModes;
+#define UATH_WIRELESS_MODE_11A 0x01
+#define UATH_WIRELESS_MODE_TURBO 0x02
+#define UATH_WIRELESS_MODE_11B 0x04
+#define UATH_WIRELESS_MODE_11G 0x08
+#define UATH_WIRELESS_MODE_108G 0x10
+ uint32_t chanSpreadSupport;
+ uint32_t compressSupport;
+ uint32_t burstSupport;
+ uint32_t fastFramesSupport;
+ uint32_t chapTuningSupport;
+ uint32_t turboGSupport;
+ uint32_t turboPrimeSupport;
+ uint32_t deviceType;
+ uint32_t wmeSupport;
+ uint32_t low2GhzChan;
+ uint32_t high2GhzChan;
+ uint32_t low5GhzChan;
+ uint32_t high5GhzChan;
+ uint32_t supportCipherWEP;
+ uint32_t supportCipherAES_CCM;
+ uint32_t supportCipherTKIP;
+ uint32_t supportCipherMicAES_CCM;
+ uint32_t supportMicTKIP;
+ uint32_t twiceAntennaGain5G;
+ uint32_t twiceAntennaGain2G;
+};
+
+struct uath_stat {
+ uint32_t st_badchunkseqnum;
+ uint32_t st_invalidlen;
+ uint32_t st_multichunk;
+ uint32_t st_toobigrxpkt;
+ uint32_t st_stopinprogress;
+ uint32_t st_crcerr;
+ uint32_t st_phyerr;
+ uint32_t st_decrypt_crcerr;
+ uint32_t st_decrypt_micerr;
+ uint32_t st_decomperr;
+ uint32_t st_keyerr;
+ uint32_t st_err;
+ /* CMD/RX/TX queues */
+ uint32_t st_cmd_active;
+ uint32_t st_cmd_inactive;
+ uint32_t st_cmd_pending;
+ uint32_t st_cmd_waiting;
+ uint32_t st_rx_active;
+ uint32_t st_rx_inactive;
+ uint32_t st_tx_active;
+ uint32_t st_tx_inactive;
+ uint32_t st_tx_pending;
+};
+#define UATH_STAT_INC(sc, var) (sc)->sc_stat.var++
+#define UATH_STAT_DEC(sc, var) (sc)->sc_stat.var--
+
+struct uath_vap {
+ struct ieee80211vap vap;
+ int (*newstate)(struct ieee80211vap *,
+ enum ieee80211_state, int);
+};
+#define UATH_VAP(vap) ((struct uath_vap *)(vap))
+
+struct uath_softc {
+ struct ieee80211com sc_ic;
+ struct mbufq sc_snd;
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+ void *sc_cmd_dma_buf;
+ void *sc_tx_dma_buf;
+ struct mtx sc_mtx;
+ uint32_t sc_debug;
+
+ struct uath_stat sc_stat;
+ int (*sc_newstate)(struct ieee80211com *,
+ enum ieee80211_state, int);
+
+ struct usb_xfer *sc_xfer[UATH_N_XFERS];
+ struct uath_cmd sc_cmd[UATH_CMD_LIST_COUNT];
+ uath_cmdhead sc_cmd_active;
+ uath_cmdhead sc_cmd_inactive;
+ uath_cmdhead sc_cmd_pending;
+ uath_cmdhead sc_cmd_waiting;
+ struct uath_data sc_rx[UATH_RX_DATA_LIST_COUNT];
+ uath_datahead sc_rx_active;
+ uath_datahead sc_rx_inactive;
+ struct uath_data sc_tx[UATH_TX_DATA_LIST_COUNT];
+ uath_datahead sc_tx_active;
+ uath_datahead sc_tx_inactive;
+ uath_datahead sc_tx_pending;
+
+ uint32_t sc_msgid;
+ uint32_t sc_seqnum;
+ int sc_tx_timer;
+ struct callout watchdog_ch;
+ struct callout stat_ch;
+ /* multi-chunked support */
+ struct mbuf *sc_intrx_head;
+ struct mbuf *sc_intrx_tail;
+ uint8_t sc_intrx_nextnum;
+ uint32_t sc_intrx_len;
+#define UATH_MAX_INTRX_SIZE 3616
+
+ struct uath_devcap sc_devcap;
+ uint8_t sc_serial[16];
+
+ /* unsorted */
+ uint32_t sc_flags;
+#define UATH_FLAG_INVALID (1 << 1)
+#define UATH_FLAG_INITDONE (1 << 2)
+
+ struct uath_rx_radiotap_header sc_rxtap;
+ struct uath_tx_radiotap_header sc_txtap;
+};
+
+#define UATH_LOCK(sc) mtx_lock(&(sc)->sc_mtx)
+#define UATH_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx)
+#define UATH_ASSERT_LOCKED(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED)
+
+#define UATH_RESET_INTRX(sc) do { \
+ (sc)->sc_intrx_head = NULL; \
+ (sc)->sc_intrx_tail = NULL; \
+ (sc)->sc_intrx_nextnum = 0; \
+ (sc)->sc_intrx_len = 0; \
+} while (0)
diff --git a/sys/dev/usb/wlan/if_upgt.c b/sys/dev/usb/wlan/if_upgt.c
new file mode 100644
index 000000000000..642631ae34b7
--- /dev/null
+++ b/sys/dev/usb/wlan/if_upgt.c
@@ -0,0 +1,2343 @@
+/* $OpenBSD: if_upgt.c,v 1.35 2008/04/16 18:32:15 damien Exp $ */
+
+/*
+ * 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 "opt_wlan.h"
+
+#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_var.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 <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 "usbdevs.h"
+
+#include <dev/usb/wlan/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>.
+ */
+
+static SYSCTL_NODE(_hw, OID_AUTO, upgt, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
+ "USB PrismGT GW3887 driver parameters");
+
+#ifdef UPGT_DEBUG
+int upgt_debug = 0;
+SYSCTL_INT(_hw_upgt, OID_AUTO, debug, CTLFLAG_RWTUN, &upgt_debug,
+ 0, "control debugging printfs");
+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_device_reset(struct upgt_softc *);
+static void upgt_bulk_tx(struct upgt_softc *, struct upgt_data *);
+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 struct mbuf *
+ upgt_rxeof(struct usb_xfer *, struct upgt_data *, int *);
+static struct mbuf *
+ upgt_rx(struct upgt_softc *, uint8_t *, int, int *);
+static void upgt_txeof(struct usb_xfer *, struct upgt_data *);
+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_init(struct upgt_softc *);
+static void upgt_parent(struct ieee80211com *);
+static int upgt_transmit(struct ieee80211com *, struct mbuf *);
+static void upgt_start(struct upgt_softc *);
+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 [IFNAMSIZ], int, enum ieee80211_opmode, int,
+ const uint8_t [IEEE80211_ADDR_LEN],
+ const uint8_t [IEEE80211_ADDR_LEN]);
+static void upgt_vap_delete(struct ieee80211vap *);
+static void upgt_update_mcast(struct ieee80211com *);
+static uint8_t upgt_rx_rate(struct upgt_softc *, const int);
+static void upgt_set_multi(void *);
+static void upgt_stop(struct upgt_softc *);
+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_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_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_watchdog(void *);
+static void upgt_abort_xfers(struct upgt_softc *);
+static void upgt_abort_xfers_locked(struct upgt_softc *);
+static void upgt_sysctl_node(struct upgt_softc *);
+static struct upgt_data *
+ upgt_getbuf(struct upgt_softc *);
+static struct upgt_data *
+ upgt_gettxbuf(struct upgt_softc *);
+static int upgt_tx_start(struct upgt_softc *, struct mbuf *,
+ struct ieee80211_node *, struct upgt_data *);
+
+static const char *upgt_fwname = "upgt-gw3887";
+
+static const STRUCT_USB_HOST_ID upgt_devs[] = {
+#define UPGT_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) }
+ /* version 2 devices */
+ UPGT_DEV(ACCTON, PRISM_GT),
+ UPGT_DEV(BELKIN, F5D7050),
+ UPGT_DEV(CISCOLINKSYS, WUSB54AG),
+ UPGT_DEV(CONCEPTRONIC, PRISM_GT),
+ UPGT_DEV(DELL, PRISM_GT_1),
+ UPGT_DEV(DELL, PRISM_GT_2),
+ UPGT_DEV(FSC, E5400),
+ UPGT_DEV(GLOBESPAN, PRISM_GT_1),
+ UPGT_DEV(GLOBESPAN, PRISM_GT_2),
+ UPGT_DEV(NETGEAR, WG111V1_2),
+ UPGT_DEV(INTERSIL, PRISM_GT),
+ UPGT_DEV(SMC, 2862WG),
+ UPGT_DEV(USR, USR5422),
+ UPGT_DEV(WISTRONNEWEB, UR045G),
+ UPGT_DEV(XYRATEX, PRISM_GT_1),
+ UPGT_DEV(XYRATEX, PRISM_GT_2),
+ UPGT_DEV(ZCOM, XG703A),
+ UPGT_DEV(ZCOM, XM142)
+};
+
+static usb_callback_t upgt_bulk_rx_callback;
+static usb_callback_t upgt_bulk_tx_callback;
+
+static const struct usb_config upgt_config[UPGT_N_XFERS] = {
+ [UPGT_BULK_TX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = MCLBYTES * UPGT_TX_MAXCOUNT,
+ .flags = {
+ .force_short_xfer = 1,
+ .pipe_bof = 1
+ },
+ .callback = upgt_bulk_tx_callback,
+ .timeout = UPGT_USB_TIMEOUT, /* ms */
+ },
+ [UPGT_BULK_RX] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = MCLBYTES * UPGT_RX_MAXCOUNT,
+ .flags = {
+ .pipe_bof = 1,
+ .short_xfer_ok = 1
+ },
+ .callback = upgt_bulk_rx_callback,
+ },
+};
+
+static int
+upgt_match(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != UPGT_CONFIG_INDEX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != UPGT_IFACE_INDEX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(upgt_devs, sizeof(upgt_devs), uaa));
+}
+
+static int
+upgt_attach(device_t dev)
+{
+ struct upgt_softc *sc = device_get_softc(dev);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ uint8_t bands[IEEE80211_MODE_BYTES];
+ uint8_t iface_index = UPGT_IFACE_INDEX;
+ int error;
+
+ sc->sc_dev = dev;
+ sc->sc_udev = uaa->device;
+#ifdef UPGT_DEBUG
+ sc->sc_debug = upgt_debug;
+#endif
+ device_set_usb_desc(dev);
+
+ 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);
+ mbufq_init(&sc->sc_snd, ifqmaxlen);
+
+ error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer,
+ upgt_config, UPGT_N_XFERS, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "could not allocate USB transfers, "
+ "err=%s\n", usbd_errstr(error));
+ goto fail1;
+ }
+
+ sc->sc_rx_dma_buf = usbd_xfer_get_frame_buffer(
+ sc->sc_xfer[UPGT_BULK_RX], 0);
+ sc->sc_tx_dma_buf = usbd_xfer_get_frame_buffer(
+ sc->sc_xfer[UPGT_BULK_TX], 0);
+
+ /* Setup TX and RX buffers */
+ error = upgt_alloc_tx(sc);
+ if (error)
+ goto fail2;
+ error = upgt_alloc_rx(sc);
+ if (error)
+ goto fail3;
+
+ /* Initialize the device. */
+ error = upgt_device_reset(sc);
+ if (error)
+ goto fail4;
+ /* Verify the firmware. */
+ error = upgt_fw_verify(sc);
+ if (error)
+ goto fail4;
+ /* 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");
+ error = EIO;
+ goto fail4;
+ }
+ 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. */
+ error = upgt_fw_load(sc);
+ if (error)
+ goto fail4;
+
+ /* Read the whole EEPROM content and parse it. */
+ error = upgt_eeprom_read(sc);
+ if (error)
+ goto fail4;
+ error = upgt_eeprom_parse(sc);
+ if (error)
+ goto fail4;
+
+ /* all works related with the device have done here. */
+ upgt_abort_xfers(sc);
+
+ ic->ic_softc = sc;
+ ic->ic_name = device_get_nameunit(dev);
+ 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 */
+ ;
+
+ memset(bands, 0, sizeof(bands));
+ 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;
+ ic->ic_transmit = upgt_transmit;
+ ic->ic_parent = upgt_parent;
+
+ ieee80211_radiotap_attach(ic,
+ &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap),
+ UPGT_TX_RADIOTAP_PRESENT,
+ &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap),
+ UPGT_RX_RADIOTAP_PRESENT);
+
+ upgt_sysctl_node(sc);
+
+ if (bootverbose)
+ ieee80211_announce(ic);
+
+ return (0);
+
+fail4: upgt_free_rx(sc);
+fail3: upgt_free_tx(sc);
+fail2: usbd_transfer_unsetup(sc->sc_xfer, UPGT_N_XFERS);
+fail1: mtx_destroy(&sc->sc_mtx);
+
+ return (error);
+}
+
+static void
+upgt_txeof(struct usb_xfer *xfer, struct upgt_data *data)
+{
+
+ if (data->m) {
+ /* XXX status? */
+ ieee80211_tx_complete(data->ni, data->m, 0);
+ data->m = NULL;
+ data->ni = NULL;
+ }
+}
+
+static void
+upgt_get_stats(struct upgt_softc *sc)
+{
+ struct upgt_data *data_cmd;
+ struct upgt_lmac_mem *mem;
+ struct upgt_lmac_stats *stats;
+
+ data_cmd = upgt_getbuf(sc);
+ if (data_cmd == NULL) {
+ device_printf(sc->sc_dev, "%s: out of buffers.\n", __func__);
+ return;
+ }
+
+ /*
+ * Transmit the URB containing the CMD data.
+ */
+ memset(data_cmd->buf, 0, 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;
+
+ data_cmd->buflen = sizeof(*mem) + sizeof(*stats);
+
+ mem->chksum = upgt_chksum_le((uint32_t *)stats,
+ data_cmd->buflen - sizeof(*mem));
+
+ upgt_bulk_tx(sc, data_cmd);
+}
+
+static void
+upgt_parent(struct ieee80211com *ic)
+{
+ struct upgt_softc *sc = ic->ic_softc;
+ int startall = 0;
+
+ UPGT_LOCK(sc);
+ if (sc->sc_flags & UPGT_FLAG_DETACHED) {
+ UPGT_UNLOCK(sc);
+ return;
+ }
+ if (ic->ic_nrunning > 0) {
+ if (sc->sc_flags & UPGT_FLAG_INITDONE) {
+ if (ic->ic_allmulti > 0 || ic->ic_promisc > 0)
+ upgt_set_multi(sc);
+ } else {
+ upgt_init(sc);
+ startall = 1;
+ }
+ } else if (sc->sc_flags & UPGT_FLAG_INITDONE)
+ upgt_stop(sc);
+ UPGT_UNLOCK(sc);
+ if (startall)
+ ieee80211_start_all(ic);
+}
+
+static void
+upgt_stop(struct upgt_softc *sc)
+{
+
+ UPGT_ASSERT_LOCKED(sc);
+
+ if (sc->sc_flags & UPGT_FLAG_INITDONE)
+ upgt_set_macfilter(sc, IEEE80211_S_INIT);
+ upgt_abort_xfers_locked(sc);
+ /* device down */
+ sc->sc_tx_timer = 0;
+ sc->sc_flags &= ~UPGT_FLAG_INITDONE;
+}
+
+static void
+upgt_set_led(struct upgt_softc *sc, int action)
+{
+ struct upgt_data *data_cmd;
+ struct upgt_lmac_mem *mem;
+ struct upgt_lmac_led *led;
+
+ data_cmd = upgt_getbuf(sc);
+ if (data_cmd == NULL) {
+ device_printf(sc->sc_dev, "%s: out of buffers.\n", __func__);
+ return;
+ }
+
+ /*
+ * Transmit the URB containing the CMD data.
+ */
+ memset(data_cmd->buf, 0, 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) {
+ STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data_cmd, next);
+ return;
+ }
+ if (sc->sc_led_blink) {
+ /* previous blink was not finished */
+ STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data_cmd, next);
+ 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:
+ STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data_cmd, next);
+ return;
+ }
+
+ data_cmd->buflen = sizeof(*mem) + sizeof(*led);
+
+ mem->chksum = upgt_chksum_le((uint32_t *)led,
+ data_cmd->buflen - sizeof(*mem));
+
+ upgt_bulk_tx(sc, data_cmd);
+}
+
+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(struct upgt_softc *sc)
+{
+
+ UPGT_ASSERT_LOCKED(sc);
+
+ if (sc->sc_flags & UPGT_FLAG_INITDONE)
+ upgt_stop(sc);
+
+ usbd_transfer_start(sc->sc_xfer[UPGT_BULK_RX]);
+
+ (void)upgt_set_macfilter(sc, IEEE80211_S_SCAN);
+
+ sc->sc_flags |= UPGT_FLAG_INITDONE;
+
+ callout_reset(&sc->sc_watchdog_ch, hz, upgt_watchdog, sc);
+}
+
+static int
+upgt_set_macfilter(struct upgt_softc *sc, uint8_t state)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ struct ieee80211_node *ni;
+ struct upgt_data *data_cmd;
+ struct upgt_lmac_mem *mem;
+ struct upgt_lmac_filter *filter;
+
+ UPGT_ASSERT_LOCKED(sc);
+
+ data_cmd = upgt_getbuf(sc);
+ if (data_cmd == NULL) {
+ device_printf(sc->sc_dev, "out of TX buffers.\n");
+ return (ENOBUFS);
+ }
+
+ /*
+ * Transmit the URB containing the CMD data.
+ */
+ memset(data_cmd->buf, 0, 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(ieee80211broadcastaddr));
+ filter->type = htole16(UPGT_FILTER_TYPE_NONE);
+ IEEE80211_ADDR_COPY(filter->dst,
+ vap ? vap->iv_myaddr : ic->ic_macaddr);
+ IEEE80211_ADDR_COPY(filter->src, ieee80211broadcastaddr);
+ 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:
+ ni = ieee80211_ref_node(vap->iv_bss);
+ /* 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,
+ vap ? vap->iv_myaddr : ic->ic_macaddr);
+ 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,
+ vap ? vap->iv_myaddr : ic->ic_macaddr);
+ 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);
+ }
+ ieee80211_free_node(ni);
+ break;
+ default:
+ device_printf(sc->sc_dev,
+ "MAC filter does not know that state\n");
+ break;
+ }
+
+ data_cmd->buflen = sizeof(*mem) + sizeof(*filter);
+
+ mem->chksum = upgt_chksum_le((uint32_t *)filter,
+ data_cmd->buflen - sizeof(*mem));
+
+ upgt_bulk_tx(sc, data_cmd);
+
+ return (0);
+}
+
+static void
+upgt_setup_rates(struct ieee80211vap *vap, struct ieee80211com *ic)
+{
+ struct upgt_softc *sc = ic->ic_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)
+ memcpy(sc->sc_cur_rateset, rateset_auto_11b,
+ sizeof(sc->sc_cur_rateset));
+ if (ic->ic_curmode == IEEE80211_MODE_11G ||
+ ic->ic_curmode == IEEE80211_MODE_AUTO)
+ memcpy(sc->sc_cur_rateset, rateset_auto_11g,
+ 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)
+{
+
+ /* XXX don't know how to set a device. Lack of docs. */
+}
+
+static int
+upgt_transmit(struct ieee80211com *ic, struct mbuf *m)
+{
+ struct upgt_softc *sc = ic->ic_softc;
+ int error;
+
+ UPGT_LOCK(sc);
+ if ((sc->sc_flags & UPGT_FLAG_INITDONE) == 0) {
+ UPGT_UNLOCK(sc);
+ return (ENXIO);
+ }
+ error = mbufq_enqueue(&sc->sc_snd, m);
+ if (error) {
+ UPGT_UNLOCK(sc);
+ return (error);
+ }
+ upgt_start(sc);
+ UPGT_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+upgt_start(struct upgt_softc *sc)
+{
+ struct upgt_data *data_tx;
+ struct ieee80211_node *ni;
+ struct mbuf *m;
+
+ UPGT_ASSERT_LOCKED(sc);
+
+ if ((sc->sc_flags & UPGT_FLAG_INITDONE) == 0)
+ return;
+
+ while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
+ data_tx = upgt_gettxbuf(sc);
+ if (data_tx == NULL) {
+ mbufq_prepend(&sc->sc_snd, m);
+ break;
+ }
+
+ ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
+ m->m_pkthdr.rcvif = NULL;
+
+ if (upgt_tx_start(sc, m, ni, data_tx) != 0) {
+ if_inc_counter(ni->ni_vap->iv_ifp,
+ IFCOUNTER_OERRORS, 1);
+ STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, data_tx, next);
+ UPGT_STAT_INC(sc, st_tx_inactive);
+ ieee80211_free_node(ni);
+ continue;
+ }
+ sc->sc_tx_timer = 5;
+ }
+}
+
+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 upgt_softc *sc = ic->ic_softc;
+ struct upgt_data *data_tx = NULL;
+
+ UPGT_LOCK(sc);
+ /* prevent management frames from being sent if we're not ready */
+ if (!(sc->sc_flags & UPGT_FLAG_INITDONE)) {
+ m_freem(m);
+ UPGT_UNLOCK(sc);
+ return ENETDOWN;
+ }
+
+ data_tx = upgt_gettxbuf(sc);
+ if (data_tx == NULL) {
+ m_freem(m);
+ UPGT_UNLOCK(sc);
+ return (ENOBUFS);
+ }
+
+ if (upgt_tx_start(sc, m, ni, data_tx) != 0) {
+ STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, data_tx, next);
+ UPGT_STAT_INC(sc, st_tx_inactive);
+ UPGT_UNLOCK(sc);
+ return (EIO);
+ }
+ UPGT_UNLOCK(sc);
+
+ sc->sc_tx_timer = 5;
+ return (0);
+}
+
+static void
+upgt_watchdog(void *arg)
+{
+ struct upgt_softc *sc = arg;
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ if (sc->sc_tx_timer > 0) {
+ if (--sc->sc_tx_timer == 0) {
+ device_printf(sc->sc_dev, "watchdog timeout\n");
+ /* upgt_init(sc); XXX needs a process context ? */
+ counter_u64_add(ic->ic_oerrors, 1);
+ 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_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_softc;
+
+ UPGT_LOCK(sc);
+ upgt_set_chan(sc, ic->ic_curchan);
+ UPGT_UNLOCK(sc);
+}
+
+static void
+upgt_set_chan(struct upgt_softc *sc, struct ieee80211_channel *c)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct upgt_data *data_cmd;
+ struct upgt_lmac_mem *mem;
+ struct upgt_lmac_channel *chan;
+ int channel;
+
+ UPGT_ASSERT_LOCKED(sc);
+
+ 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);
+
+ data_cmd = upgt_getbuf(sc);
+ if (data_cmd == NULL) {
+ device_printf(sc->sc_dev, "%s: out of buffers.\n", __func__);
+ return;
+ }
+ /*
+ * Transmit the URB containing the CMD data.
+ */
+ memset(data_cmd->buf, 0, 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;
+
+ memcpy(chan->freq3_1, &sc->sc_eeprom_freq3[channel].data,
+ sizeof(chan->freq3_1));
+ memcpy(chan->freq4, &sc->sc_eeprom_freq4[channel],
+ sizeof(sc->sc_eeprom_freq4[channel]));
+ memcpy(chan->freq3_2, &sc->sc_eeprom_freq3[channel].data,
+ sizeof(chan->freq3_2));
+
+ data_cmd->buflen = sizeof(*mem) + sizeof(*chan);
+
+ mem->chksum = upgt_chksum_le((uint32_t *)chan,
+ data_cmd->buflen - sizeof(*mem));
+
+ upgt_bulk_tx(sc, data_cmd);
+}
+
+static struct ieee80211vap *
+upgt_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
+ enum ieee80211_opmode 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 = malloc(sizeof(struct upgt_vap), M_80211_VAP, M_WAITOK | M_ZERO);
+ vap = &uvp->vap;
+ /* enable s/w bmiss handling for sta mode */
+
+ if (ieee80211_vap_setup(ic, vap, name, unit, opmode,
+ flags | IEEE80211_CLONE_NOBEACONS, bssid) != 0) {
+ /* out of memory */
+ free(uvp, M_80211_VAP);
+ return (NULL);
+ }
+
+ /* 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, mac);
+ 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_softc;
+
+ /* do it in a process context */
+ sc->sc_state = nstate;
+
+ IEEE80211_UNLOCK(ic);
+ UPGT_LOCK(sc);
+ callout_stop(&sc->sc_led_ch);
+ callout_stop(&sc->sc_watchdog_ch);
+
+ switch (nstate) {
+ case IEEE80211_S_INIT:
+ /* do not accept any frames if the device is down */
+ (void)upgt_set_macfilter(sc, sc->sc_state);
+ 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_set_macfilter(sc, sc->sc_state);
+ upgt_set_led(sc, UPGT_LED_ON);
+ break;
+ default:
+ break;
+ }
+ UPGT_UNLOCK(sc);
+ IEEE80211_LOCK(ic);
+ return (uvp->newstate(vap, nstate, arg));
+}
+
+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 ieee80211com *ic)
+{
+ struct upgt_softc *sc = ic->ic_softc;
+
+ upgt_set_multi(sc);
+}
+
+static int
+upgt_eeprom_parse(struct upgt_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ 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) {
+ /* sanity check */
+ if (eeprom_option >= (struct upgt_eeprom_option *)
+ (sc->sc_eeprom + UPGT_EEPROM_SIZE)) {
+ return (EINVAL);
+ }
+
+ /* the eeprom option length is stored in words */
+ option_len =
+ (le16toh(eeprom_option->len) - 1) * sizeof(uint16_t);
+ option_type =
+ le16toh(eeprom_option->type);
+
+ /* sanity check */
+ if (option_len == 0 || option_len >= UPGT_EEPROM_SIZE)
+ return (EINVAL);
+
+ 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_macaddr,
+ 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;
+ int elements;
+ unsigned channel;
+
+ freq3_header = (struct upgt_eeprom_freq3_header *)data;
+ freq3 = (struct upgt_lmac_freq3 *)(freq3_header + 1);
+
+ elements = freq3_header->elements;
+
+ DPRINTF(sc, UPGT_DEBUG_FW, "flags=0x%02x elements=%d\n",
+ freq3_header->flags, elements);
+
+ if (elements >= (int)(UPGT_EEPROM_SIZE / sizeof(freq3[0])))
+ return;
+
+ for (i = 0; i < elements; i++) {
+ channel = ieee80211_mhz2ieee(le16toh(freq3[i].freq), 0);
+ if (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;
+ int j;
+ int elements;
+ int settings;
+ unsigned channel;
+
+ freq4_header = (struct upgt_eeprom_freq4_header *)data;
+ freq4_1 = (struct upgt_eeprom_freq4_1 *)(freq4_header + 1);
+ 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",
+ freq4_header->flags, elements, settings);
+
+ if (elements >= (int)(UPGT_EEPROM_SIZE / sizeof(freq4_1[0])))
+ return;
+
+ for (i = 0; i < elements; i++) {
+ channel = ieee80211_mhz2ieee(le16toh(freq4_1[i].freq), 0);
+ if (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;
+ int 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);
+
+ if (elements >= (int)(UPGT_EEPROM_SIZE / sizeof(freq6[0])))
+ return;
+
+ for (i = 0; i < elements; i++) {
+ channel = ieee80211_mhz2ieee(le16toh(freq6[i].freq), 0);
+ if (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;
+ struct upgt_lmac_mem *mem;
+ struct upgt_lmac_eeprom *eeprom;
+ int block, error, offset;
+
+ UPGT_LOCK(sc);
+ usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(100));
+
+ 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);
+
+ data_cmd = upgt_getbuf(sc);
+ if (data_cmd == NULL) {
+ UPGT_UNLOCK(sc);
+ return (ENOBUFS);
+ }
+
+ /*
+ * Transmit the URB containing the CMD data.
+ */
+ memset(data_cmd->buf, 0, 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);
+
+ data_cmd->buflen = sizeof(*mem) + sizeof(*eeprom) + block;
+
+ mem->chksum = upgt_chksum_le((uint32_t *)eeprom,
+ data_cmd->buflen - sizeof(*mem));
+ upgt_bulk_tx(sc, data_cmd);
+
+ error = mtx_sleep(sc, &sc->sc_mtx, 0, "eeprom_request", hz);
+ if (error != 0) {
+ device_printf(sc->sc_dev,
+ "timeout while waiting for EEPROM data\n");
+ UPGT_UNLOCK(sc);
+ return (EIO);
+ }
+
+ offset += block;
+ if (UPGT_EEPROM_SIZE - offset < block)
+ block = UPGT_EEPROM_SIZE - offset;
+ }
+
+ UPGT_UNLOCK(sc);
+ return (0);
+}
+
+/*
+ * When a rx data came in the function returns a mbuf and a rssi values.
+ */
+static struct mbuf *
+upgt_rxeof(struct usb_xfer *xfer, struct upgt_data *data, int *rssi)
+{
+ struct mbuf *m = NULL;
+ struct upgt_softc *sc = usbd_xfer_softc(xfer);
+ struct upgt_lmac_header *header;
+ struct upgt_lmac_eeprom *eeprom;
+ uint8_t h1_type;
+ uint16_t h2_type;
+ int actlen, sumlen;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
+ UPGT_ASSERT_LOCKED(sc);
+
+ if (actlen < 1)
+ return (NULL);
+
+ /* Check only at the very beginning. */
+ if (!(sc->sc_flags & UPGT_FLAG_FWLOADED) &&
+ (memcmp(data->buf, "OK", 2) == 0)) {
+ sc->sc_flags |= UPGT_FLAG_FWLOADED;
+ wakeup_one(sc);
+ return (NULL);
+ }
+
+ if (actlen < (int)UPGT_RX_MINSZ)
+ return (NULL);
+
+ /*
+ * Check what type of frame came in.
+ */
+ header = (struct upgt_lmac_header *)(data->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->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);
+
+ memcpy(sc->sc_eeprom + eeprom_offset,
+ data->buf + sizeof(struct upgt_lmac_eeprom) + 4,
+ eeprom_len);
+
+ /* EEPROM data has arrived in time, wakeup. */
+ 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->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__);
+ m = upgt_rx(sc, data->buf + 4, le16toh(header->header1.len),
+ rssi);
+ } 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);
+ }
+ return (m);
+}
+
+/*
+ * 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)
+{
+ size_t 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 struct mbuf *
+upgt_rx(struct upgt_softc *sc, uint8_t *data, int pkglen, int *rssi)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct upgt_lmac_rx_desc *rxdesc;
+ struct mbuf *m;
+
+ /*
+ * don't pass packets to the ieee80211 framework if the driver isn't
+ * RUNNING.
+ */
+ if (!(sc->sc_flags & UPGT_FLAG_INITDONE))
+ return (NULL);
+
+ /* 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_NOWAIT, MT_DATA, M_PKTHDR);
+ if (m == NULL) {
+ device_printf(sc->sc_dev, "could not create RX mbuf\n");
+ return (NULL);
+ }
+ m_adj(m, ETHER_ALIGN);
+ memcpy(mtod(m, char *), rxdesc->data, pkglen);
+ /* trim FCS */
+ m->m_len = m->m_pkthdr.len = pkglen - IEEE80211_CRC_LEN;
+
+ if (ieee80211_radiotap_active(ic)) {
+ struct upgt_rx_radiotap_header *tap = &sc->sc_rxtap;
+
+ tap->wr_flags = 0;
+ tap->wr_rate = upgt_rx_rate(sc, rxdesc->rate);
+ tap->wr_antsignal = rxdesc->rssi;
+ }
+
+ DPRINTF(sc, UPGT_DEBUG_RX_PROC, "%s: RX done\n", __func__);
+ *rssi = rxdesc->rssi;
+ return (m);
+}
+
+static uint8_t
+upgt_rx_rate(struct upgt_softc *sc, const int rate)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ 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 upgt_lmac_tx_done_desc *desc;
+ int i, freed = 0;
+
+ UPGT_ASSERT_LOCKED(sc);
+
+ desc = (struct upgt_lmac_tx_done_desc *)data;
+
+ for (i = 0; i < UPGT_TX_MAXCOUNT; i++) {
+ struct upgt_data *data_tx = &sc->sc_tx_data[i];
+
+ if (data_tx->addr == le32toh(desc->header2.reqid)) {
+ upgt_mem_free(sc, data_tx->addr);
+ data_tx->ni = NULL;
+ data_tx->addr = 0;
+ data_tx->m = NULL;
+
+ 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));
+
+ freed++;
+ }
+ }
+
+ if (freed != 0) {
+ UPGT_UNLOCK(sc);
+ sc->sc_tx_timer = 0;
+ upgt_start(sc);
+ UPGT_LOCK(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;
+ struct upgt_fw_x2_header *x2;
+ char start_fwload_cmd[] = { 0x3c, 0x0d };
+ int error = 0;
+ size_t offset;
+ int bsize;
+ int n;
+ 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);
+ }
+
+ UPGT_LOCK(sc);
+
+ /* send firmware start load command */
+ data_cmd = upgt_getbuf(sc);
+ if (data_cmd == NULL) {
+ error = ENOBUFS;
+ goto fail;
+ }
+ data_cmd->buflen = sizeof(start_fwload_cmd);
+ memcpy(data_cmd->buf, start_fwload_cmd, data_cmd->buflen);
+ upgt_bulk_tx(sc, data_cmd);
+
+ /* send X2 header */
+ data_cmd = upgt_getbuf(sc);
+ if (data_cmd == NULL) {
+ error = ENOBUFS;
+ goto fail;
+ }
+ data_cmd->buflen = sizeof(struct upgt_fw_x2_header);
+ x2 = (struct upgt_fw_x2_header *)data_cmd->buf;
+ memcpy(x2->signature, UPGT_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));
+ upgt_bulk_tx(sc, data_cmd);
+
+ /* 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;
+
+ data_cmd = upgt_getbuf(sc);
+ if (data_cmd == NULL) {
+ error = ENOBUFS;
+ goto fail;
+ }
+ n = upgt_fw_copy((const uint8_t *)fw->data + offset,
+ data_cmd->buf, bsize);
+ data_cmd->buflen = bsize;
+ upgt_bulk_tx(sc, data_cmd);
+
+ DPRINTF(sc, UPGT_DEBUG_FW, "FW offset=%zu, read=%d, sent=%d\n",
+ offset, n, bsize);
+ bsize = n;
+ }
+ DPRINTF(sc, UPGT_DEBUG_FW, "%s: firmware downloaded\n", __func__);
+
+ /* load firmware */
+ data_cmd = upgt_getbuf(sc);
+ if (data_cmd == NULL) {
+ error = ENOBUFS;
+ goto fail;
+ }
+ 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';
+ data_cmd->buflen = 6;
+ upgt_bulk_tx(sc, data_cmd);
+
+ /* waiting 'OK' response. */
+ usbd_transfer_start(sc->sc_xfer[UPGT_BULK_RX]);
+ error = mtx_sleep(sc, &sc->sc_mtx, 0, "upgtfw", 2 * hz);
+ if (error != 0) {
+ device_printf(sc->sc_dev, "firmware load failed\n");
+ error = EIO;
+ }
+
+ DPRINTF(sc, UPGT_DEBUG_FW, "%s: firmware loaded\n", __func__);
+fail:
+ UPGT_UNLOCK(sc);
+ 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;
+
+ 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;
+ size_t offset;
+ int bra_end = 0;
+ int 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 %zu\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 void
+upgt_bulk_tx(struct upgt_softc *sc, struct upgt_data *data)
+{
+
+ UPGT_ASSERT_LOCKED(sc);
+
+ STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next);
+ UPGT_STAT_INC(sc, st_tx_pending);
+ usbd_transfer_start(sc->sc_xfer[UPGT_BULK_TX]);
+}
+
+static int
+upgt_device_reset(struct upgt_softc *sc)
+{
+ struct upgt_data *data;
+ char init_cmd[] = { 0x7e, 0x7e, 0x7e, 0x7e };
+
+ UPGT_LOCK(sc);
+
+ data = upgt_getbuf(sc);
+ if (data == NULL) {
+ UPGT_UNLOCK(sc);
+ return (ENOBUFS);
+ }
+ memcpy(data->buf, init_cmd, sizeof(init_cmd));
+ data->buflen = sizeof(init_cmd);
+ upgt_bulk_tx(sc, data);
+ usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(100));
+
+ UPGT_UNLOCK(sc);
+ DPRINTF(sc, UPGT_DEBUG_FW, "%s: device initialized\n", __func__);
+ return (0);
+}
+
+static int
+upgt_alloc_tx(struct upgt_softc *sc)
+{
+ int i;
+
+ STAILQ_INIT(&sc->sc_tx_active);
+ STAILQ_INIT(&sc->sc_tx_inactive);
+ STAILQ_INIT(&sc->sc_tx_pending);
+
+ for (i = 0; i < UPGT_TX_MAXCOUNT; i++) {
+ struct upgt_data *data = &sc->sc_tx_data[i];
+ data->buf = ((uint8_t *)sc->sc_tx_dma_buf) + (i * MCLBYTES);
+ STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next);
+ UPGT_STAT_INC(sc, st_tx_inactive);
+ }
+
+ return (0);
+}
+
+static int
+upgt_alloc_rx(struct upgt_softc *sc)
+{
+ int i;
+
+ STAILQ_INIT(&sc->sc_rx_active);
+ STAILQ_INIT(&sc->sc_rx_inactive);
+
+ for (i = 0; i < UPGT_RX_MAXCOUNT; i++) {
+ struct upgt_data *data = &sc->sc_rx_data[i];
+ data->buf = ((uint8_t *)sc->sc_rx_dma_buf) + (i * MCLBYTES);
+ STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next);
+ }
+ return (0);
+}
+
+static int
+upgt_detach(device_t dev)
+{
+ struct upgt_softc *sc = device_get_softc(dev);
+ struct ieee80211com *ic = &sc->sc_ic;
+ unsigned x;
+
+ /*
+ * Prevent further allocations from RX/TX/CMD
+ * data lists and ioctls
+ */
+ UPGT_LOCK(sc);
+ sc->sc_flags |= UPGT_FLAG_DETACHED;
+
+ STAILQ_INIT(&sc->sc_tx_active);
+ STAILQ_INIT(&sc->sc_tx_inactive);
+ STAILQ_INIT(&sc->sc_tx_pending);
+
+ STAILQ_INIT(&sc->sc_rx_active);
+ STAILQ_INIT(&sc->sc_rx_inactive);
+
+ upgt_stop(sc);
+ UPGT_UNLOCK(sc);
+
+ callout_drain(&sc->sc_led_ch);
+ callout_drain(&sc->sc_watchdog_ch);
+
+ /* drain USB transfers */
+ for (x = 0; x != UPGT_N_XFERS; x++)
+ usbd_transfer_drain(sc->sc_xfer[x]);
+
+ /* free data buffers */
+ UPGT_LOCK(sc);
+ upgt_free_rx(sc);
+ upgt_free_tx(sc);
+ UPGT_UNLOCK(sc);
+
+ /* free USB transfers and some data buffers */
+ usbd_transfer_unsetup(sc->sc_xfer, UPGT_N_XFERS);
+
+ ieee80211_ifdetach(ic);
+ mbufq_drain(&sc->sc_snd);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static void
+upgt_free_rx(struct upgt_softc *sc)
+{
+ int i;
+
+ for (i = 0; i < UPGT_RX_MAXCOUNT; i++) {
+ struct upgt_data *data = &sc->sc_rx_data[i];
+
+ data->buf = NULL;
+ data->ni = NULL;
+ }
+}
+
+static void
+upgt_free_tx(struct upgt_softc *sc)
+{
+ int i;
+
+ for (i = 0; i < UPGT_TX_MAXCOUNT; i++) {
+ struct upgt_data *data = &sc->sc_tx_data[i];
+
+ if (data->ni != NULL)
+ ieee80211_free_node(data->ni);
+
+ data->buf = NULL;
+ data->ni = NULL;
+ }
+}
+
+static void
+upgt_abort_xfers_locked(struct upgt_softc *sc)
+{
+ int i;
+
+ UPGT_ASSERT_LOCKED(sc);
+ /* abort any pending transfers */
+ for (i = 0; i < UPGT_N_XFERS; i++)
+ usbd_transfer_stop(sc->sc_xfer[i]);
+}
+
+static void
+upgt_abort_xfers(struct upgt_softc *sc)
+{
+
+ UPGT_LOCK(sc);
+ upgt_abort_xfers_locked(sc);
+ UPGT_UNLOCK(sc);
+}
+
+#define UPGT_SYSCTL_STAT_ADD32(c, h, n, p, d) \
+ SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d)
+
+static void
+upgt_sysctl_node(struct upgt_softc *sc)
+{
+ struct sysctl_ctx_list *ctx;
+ struct sysctl_oid_list *child;
+ struct sysctl_oid *tree;
+ struct upgt_stat *stats;
+
+ stats = &sc->sc_stat;
+ ctx = device_get_sysctl_ctx(sc->sc_dev);
+ child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev));
+
+ tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats",
+ CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "UPGT statistics");
+ child = SYSCTL_CHILDREN(tree);
+ UPGT_SYSCTL_STAT_ADD32(ctx, child, "tx_active",
+ &stats->st_tx_active, "Active numbers in TX queue");
+ UPGT_SYSCTL_STAT_ADD32(ctx, child, "tx_inactive",
+ &stats->st_tx_inactive, "Inactive numbers in TX queue");
+ UPGT_SYSCTL_STAT_ADD32(ctx, child, "tx_pending",
+ &stats->st_tx_pending, "Pending numbers in TX queue");
+}
+
+#undef UPGT_SYSCTL_STAT_ADD32
+
+static struct upgt_data *
+_upgt_getbuf(struct upgt_softc *sc)
+{
+ struct upgt_data *bf;
+
+ bf = STAILQ_FIRST(&sc->sc_tx_inactive);
+ if (bf != NULL) {
+ STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next);
+ UPGT_STAT_DEC(sc, st_tx_inactive);
+ } else
+ bf = NULL;
+ if (bf == NULL)
+ DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: %s\n", __func__,
+ "out of xmit buffers");
+ return (bf);
+}
+
+static struct upgt_data *
+upgt_getbuf(struct upgt_softc *sc)
+{
+ struct upgt_data *bf;
+
+ UPGT_ASSERT_LOCKED(sc);
+
+ bf = _upgt_getbuf(sc);
+ if (bf == NULL)
+ DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: stop queue\n", __func__);
+
+ return (bf);
+}
+
+static struct upgt_data *
+upgt_gettxbuf(struct upgt_softc *sc)
+{
+ struct upgt_data *bf;
+
+ UPGT_ASSERT_LOCKED(sc);
+
+ bf = upgt_getbuf(sc);
+ if (bf == NULL)
+ return (NULL);
+
+ bf->addr = upgt_mem_alloc(sc);
+ if (bf->addr == 0) {
+ DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: no free prism memory!\n",
+ __func__);
+ STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next);
+ UPGT_STAT_INC(sc, st_tx_inactive);
+ return (NULL);
+ }
+ return (bf);
+}
+
+static int
+upgt_tx_start(struct upgt_softc *sc, struct mbuf *m, struct ieee80211_node *ni,
+ struct upgt_data *data)
+{
+ struct ieee80211vap *vap = ni->ni_vap;
+ int error = 0, len;
+ struct ieee80211_frame *wh;
+ struct ieee80211_key *k;
+ struct upgt_lmac_mem *mem;
+ struct upgt_lmac_tx_desc *txdesc;
+
+ UPGT_ASSERT_LOCKED(sc);
+
+ upgt_set_led(sc, UPGT_LED_BLINK);
+
+ /*
+ * Software crypto.
+ */
+ wh = mtod(m, struct ieee80211_frame *);
+ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
+ k = ieee80211_crypto_encap(ni, m);
+ if (k == NULL) {
+ device_printf(sc->sc_dev,
+ "ieee80211_crypto_encap returns NULL.\n");
+ error = EIO;
+ goto done;
+ }
+
+ /* in case packet header moved, reset pointer */
+ wh = mtod(m, struct ieee80211_frame *);
+ }
+
+ /* Transmit the URB containing the TX data. */
+ memset(data->buf, 0, MCLBYTES);
+ mem = (struct upgt_lmac_mem *)data->buf;
+ mem->addr = htole32(data->addr);
+ txdesc = (struct upgt_lmac_tx_desc *)(mem + 1);
+
+ if (IEEE80211_IS_MGMT(wh)) {
+ /* 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;
+ memcpy(txdesc->rates, sc->sc_cur_rateset, sizeof(txdesc->rates));
+ }
+ txdesc->header1.type = UPGT_H1_TYPE_TX_DATA;
+ txdesc->header1.len = htole16(m->m_pkthdr.len);
+ txdesc->header2.reqid = htole32(data->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 (ieee80211_radiotap_active_vap(vap)) {
+ struct upgt_tx_radiotap_header *tap = &sc->sc_txtap;
+
+ tap->wt_flags = 0;
+ tap->wt_rate = 0; /* XXX where to get from? */
+
+ ieee80211_radiotap_tx(vap, m);
+ }
+
+ /* copy frame below our TX descriptor header */
+ m_copydata(m, 0, m->m_pkthdr.len,
+ data->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));
+ data->ni = ni;
+ data->m = m;
+ data->buflen = len;
+
+ DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: TX start data sending (%d bytes)\n",
+ __func__, len);
+ KASSERT(len <= MCLBYTES, ("mbuf is small for saving data"));
+
+ upgt_bulk_tx(sc, data);
+done:
+ /*
+ * 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*
+ */
+ if (!(if_getcounter(vap->iv_ifp, IFCOUNTER_OPACKETS) %
+ UPGT_TX_STAT_INTERVAL))
+ upgt_get_stats(sc);
+
+ return (error);
+}
+
+static void
+upgt_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct upgt_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_frame *wh;
+ struct ieee80211_node *ni;
+ struct mbuf *m = NULL;
+ struct upgt_data *data;
+ int8_t nf;
+ int rssi = -1;
+
+ UPGT_ASSERT_LOCKED(sc);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ data = STAILQ_FIRST(&sc->sc_rx_active);
+ if (data == NULL)
+ goto setup;
+ STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next);
+ m = upgt_rxeof(xfer, data, &rssi);
+ STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+setup:
+ data = STAILQ_FIRST(&sc->sc_rx_inactive);
+ if (data == NULL)
+ return;
+ STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next);
+ STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next);
+ usbd_xfer_set_frame_data(xfer, 0, data->buf, MCLBYTES);
+ usbd_transfer_submit(xfer);
+
+ /*
+ * To avoid LOR we should unlock our private mutex here to call
+ * ieee80211_input() because here is at the end of a USB
+ * callback and safe to unlock.
+ */
+ UPGT_UNLOCK(sc);
+ if (m != NULL) {
+ wh = mtod(m, struct ieee80211_frame *);
+ ni = ieee80211_find_rxnode(ic,
+ (struct ieee80211_frame_min *)wh);
+ nf = -95; /* XXX */
+ if (ni != NULL) {
+ (void) ieee80211_input(ni, m, rssi, nf);
+ /* node is no longer needed */
+ ieee80211_free_node(ni);
+ } else
+ (void) ieee80211_input_all(ic, m, rssi, nf);
+ m = NULL;
+ }
+ UPGT_LOCK(sc);
+ upgt_start(sc);
+ break;
+ default:
+ /* needs it to the inactive queue due to a error. */
+ data = STAILQ_FIRST(&sc->sc_rx_active);
+ if (data != NULL) {
+ STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next);
+ STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next);
+ }
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto setup;
+ }
+ break;
+ }
+}
+
+static void
+upgt_bulk_tx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct upgt_softc *sc = usbd_xfer_softc(xfer);
+ struct upgt_data *data;
+
+ UPGT_ASSERT_LOCKED(sc);
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ data = STAILQ_FIRST(&sc->sc_tx_active);
+ if (data == NULL)
+ goto setup;
+ STAILQ_REMOVE_HEAD(&sc->sc_tx_active, next);
+ UPGT_STAT_DEC(sc, st_tx_active);
+ upgt_txeof(xfer, data);
+ STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next);
+ UPGT_STAT_INC(sc, st_tx_inactive);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+setup:
+ data = STAILQ_FIRST(&sc->sc_tx_pending);
+ if (data == NULL) {
+ DPRINTF(sc, UPGT_DEBUG_XMIT, "%s: empty pending queue\n",
+ __func__);
+ return;
+ }
+ STAILQ_REMOVE_HEAD(&sc->sc_tx_pending, next);
+ UPGT_STAT_DEC(sc, st_tx_pending);
+ STAILQ_INSERT_TAIL(&sc->sc_tx_active, data, next);
+ UPGT_STAT_INC(sc, st_tx_active);
+
+ usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen);
+ usbd_transfer_submit(xfer);
+ upgt_start(sc);
+ break;
+ default:
+ data = STAILQ_FIRST(&sc->sc_tx_active);
+ if (data == NULL)
+ goto setup;
+ if (data->ni != NULL) {
+ if_inc_counter(data->ni->ni_vap->iv_ifp,
+ IFCOUNTER_OERRORS, 1);
+ ieee80211_free_node(data->ni);
+ data->ni = NULL;
+ }
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto setup;
+ }
+ break;
+ }
+}
+
+static device_method_t upgt_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, upgt_match),
+ DEVMETHOD(device_attach, upgt_attach),
+ DEVMETHOD(device_detach, upgt_detach),
+ DEVMETHOD_END
+};
+
+static driver_t upgt_driver = {
+ .name = "upgt",
+ .methods = upgt_methods,
+ .size = sizeof(struct upgt_softc)
+};
+
+DRIVER_MODULE(if_upgt, uhub, upgt_driver, NULL, NULL);
+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);
+USB_PNP_HOST_INFO(upgt_devs);
diff --git a/sys/dev/usb/wlan/if_upgtvar.h b/sys/dev/usb/wlan/if_upgtvar.h
new file mode 100644
index 000000000000..a751737674ff
--- /dev/null
+++ b/sys/dev/usb/wlan/if_upgtvar.h
@@ -0,0 +1,479 @@
+/* $OpenBSD: if_upgtvar.h,v 1.14 2008/02/02 13:48:44 mglocker Exp $ */
+
+/*
+ * 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.
+ */
+enum {
+ UPGT_BULK_RX,
+ UPGT_BULK_TX,
+ UPGT_N_XFERS = 2,
+};
+
+#define UPGT_CONFIG_INDEX 0
+#define UPGT_IFACE_INDEX 0
+#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_RX_MAXCOUNT 6
+#define UPGT_TX_MAXCOUNT 128
+#define UPGT_TX_STAT_INTERVAL 5
+#define UPGT_RX_MINSZ (sizeof(struct upgt_lmac_header) + 4)
+
+/* 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 256
+
+#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 {
+ uint8_t *buf;
+ uint32_t buflen;
+ struct ieee80211_node *ni;
+ struct mbuf *m;
+ uint32_t addr;
+ STAILQ_ENTRY(upgt_data) next;
+};
+typedef STAILQ_HEAD(, upgt_data) upgt_datahead;
+
+/*
+ * 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 __aligned(8);
+
+#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_stat {
+ uint32_t st_tx_active;
+ uint32_t st_tx_inactive;
+ uint32_t st_tx_pending;
+};
+
+#define UPGT_STAT_INC(sc, var) (sc)->sc_stat.var++
+#define UPGT_STAT_DEC(sc, var) (sc)->sc_stat.var--
+
+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 {
+ struct ieee80211com sc_ic;
+ struct mbufq sc_snd;
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+ void *sc_rx_dma_buf;
+ void *sc_tx_dma_buf;
+ struct mtx sc_mtx;
+ struct upgt_stat sc_stat;
+ int sc_flags;
+#define UPGT_FLAG_FWLOADED (1 << 0)
+#define UPGT_FLAG_INITDONE (1 << 1)
+#define UPGT_FLAG_DETACHED (1 << 2)
+ int sc_debug;
+
+ 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[2 * UPGT_EEPROM_SIZE] __aligned(4);
+ 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 */
+ struct usb_xfer *sc_xfer[UPGT_N_XFERS];
+ int sc_rx_no;
+ int sc_tx_no;
+ struct upgt_data sc_rx_data[UPGT_RX_MAXCOUNT];
+ upgt_datahead sc_rx_active;
+ upgt_datahead sc_rx_inactive;
+ struct upgt_data sc_tx_data[UPGT_TX_MAXCOUNT];
+ upgt_datahead sc_tx_active;
+ upgt_datahead sc_tx_inactive;
+ upgt_datahead sc_tx_pending;
+
+ /* BPF */
+ struct upgt_rx_radiotap_header sc_rxtap;
+ struct upgt_tx_radiotap_header sc_txtap;
+};
+
+#define UPGT_LOCK(sc) mtx_lock(&(sc)->sc_mtx)
+#define UPGT_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx)
+#define UPGT_ASSERT_LOCKED(sc) mtx_assert(&(sc)->sc_mtx, MA_OWNED)
diff --git a/sys/dev/usb/wlan/if_ural.c b/sys/dev/usb/wlan/if_ural.c
new file mode 100644
index 000000000000..260d75a9821d
--- /dev/null
+++ b/sys/dev/usb/wlan/if_ural.c
@@ -0,0 +1,2213 @@
+
+/*-
+ * Copyright (c) 2005, 2006
+ * Damien Bergamini <damien.bergamini@free.fr>
+ *
+ * Copyright (c) 2006, 2008
+ * Hans Petter Selasky <hselasky@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.
+ */
+
+/*-
+ * Ralink Technology RT2500USB chipset driver
+ * http://www.ralinktech.com/
+ */
+
+#include "opt_wlan.h"
+
+#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 <net/bpf.h>
+#include <net/if.h>
+#include <net/if_var.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 <net80211/ieee80211_ratectl.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include "usbdevs.h"
+
+#define USB_DEBUG_VAR ural_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/wlan/if_uralreg.h>
+#include <dev/usb/wlan/if_uralvar.h>
+
+#ifdef USB_DEBUG
+static int ural_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ural, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB ural");
+SYSCTL_INT(_hw_usb_ural, OID_AUTO, debug, CTLFLAG_RWTUN, &ural_debug, 0,
+ "Debug level");
+#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_HOST_ID ural_devs[] = {
+#define URAL_DEV(v,p) { USB_VP(USB_VENDOR_##v, USB_PRODUCT_##v##_##p) }
+ URAL_DEV(ASUS, WL167G),
+ URAL_DEV(ASUS, RT2570),
+ URAL_DEV(BELKIN, F5D7050),
+ URAL_DEV(BELKIN, F5D7051),
+ URAL_DEV(CISCOLINKSYS, HU200TS),
+ URAL_DEV(CISCOLINKSYS, WUSB54G),
+ URAL_DEV(CISCOLINKSYS, WUSB54GP),
+ URAL_DEV(CONCEPTRONIC2, C54RU),
+ URAL_DEV(DLINK, DWLG122),
+ URAL_DEV(GIGABYTE, GN54G),
+ URAL_DEV(GIGABYTE, GNWBKG),
+ URAL_DEV(GUILLEMOT, HWGUSB254),
+ URAL_DEV(MELCO, KG54),
+ URAL_DEV(MELCO, KG54AI),
+ URAL_DEV(MELCO, KG54YB),
+ URAL_DEV(MELCO, NINWIFI),
+ URAL_DEV(MSI, RT2570),
+ URAL_DEV(MSI, RT2570_2),
+ URAL_DEV(MSI, RT2570_3),
+ URAL_DEV(NOVATECH, NV902),
+ URAL_DEV(RALINK, RT2570),
+ URAL_DEV(RALINK, RT2570_2),
+ URAL_DEV(RALINK, RT2570_3),
+ URAL_DEV(SIEMENS2, WL54G),
+ URAL_DEV(SMC, 2862WG),
+ URAL_DEV(SPHAIRON, UB801R),
+ URAL_DEV(SURECOM, RT2570),
+ URAL_DEV(VTECH, RT2570),
+ URAL_DEV(ZINWELL, RT2570),
+#undef URAL_DEV
+};
+
+static usb_callback_t ural_bulk_read_callback;
+static usb_callback_t ural_bulk_write_callback;
+
+static usb_error_t ural_do_request(struct ural_softc *sc,
+ struct usb_device_request *req, void *data);
+static struct ieee80211vap *ural_vap_create(struct ieee80211com *,
+ const char [IFNAMSIZ], int, enum ieee80211_opmode,
+ int, const uint8_t [IEEE80211_ADDR_LEN],
+ const uint8_t [IEEE80211_ADDR_LEN]);
+static void ural_vap_delete(struct ieee80211vap *);
+static void ural_tx_free(struct ural_tx_data *, int);
+static void ural_setup_tx_list(struct ural_softc *);
+static void ural_unsetup_tx_list(struct ural_softc *);
+static int ural_newstate(struct ieee80211vap *,
+ enum ieee80211_state, int);
+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 int ural_transmit(struct ieee80211com *, struct mbuf *);
+static void ural_start(struct ural_softc *);
+static void ural_parent(struct ieee80211com *);
+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 void ural_scan_start(struct ieee80211com *);
+static void ural_scan_end(struct ieee80211com *);
+static void ural_getradiocaps(struct ieee80211com *, int, int *,
+ struct ieee80211_channel[]);
+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_enable_tsf(struct ural_softc *);
+static void ural_update_slot(struct ural_softc *);
+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 *, const uint8_t *);
+static void ural_update_promisc(struct ieee80211com *);
+static void ural_setpromisc(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(struct ural_softc *);
+static void ural_stop(struct ural_softc *);
+static int ural_raw_xmit(struct ieee80211_node *, struct mbuf *,
+ const struct ieee80211_bpf_params *);
+static void ural_ratectl_start(struct ural_softc *,
+ struct ieee80211_node *);
+static void ural_ratectl_timeout(void *);
+static void ural_ratectl_task(void *, int);
+static int ural_pause(struct ural_softc *sc, int timeout);
+
+/*
+ * 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, RAL_FRAME_SIZE },
+ { 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 const uint8_t ural_chan_5ghz[] =
+ { 36, 40, 44, 48, 52, 56, 60, 64,
+ 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140,
+ 149, 153, 157, 161 };
+
+static const struct usb_config ural_config[URAL_N_TRANSFER] = {
+ [URAL_BULK_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = (RAL_FRAME_SIZE + RAL_TX_DESC_SIZE + 4),
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = ural_bulk_write_callback,
+ .timeout = 5000, /* ms */
+ },
+ [URAL_BULK_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = (RAL_FRAME_SIZE + RAL_RX_DESC_SIZE),
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = ural_bulk_read_callback,
+ },
+};
+
+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),
+ DEVMETHOD_END
+};
+
+static driver_t ural_driver = {
+ .name = "ural",
+ .methods = ural_methods,
+ .size = sizeof(struct ural_softc),
+};
+
+DRIVER_MODULE(ural, uhub, ural_driver, NULL, NULL);
+MODULE_DEPEND(ural, usb, 1, 1, 1);
+MODULE_DEPEND(ural, wlan, 1, 1, 1);
+MODULE_VERSION(ural, 1);
+USB_PNP_HOST_INFO(ural_devs);
+
+static int
+ural_match(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != 0)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != RAL_IFACE_INDEX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(ural_devs, sizeof(ural_devs), uaa));
+}
+
+static int
+ural_attach(device_t self)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(self);
+ struct ural_softc *sc = device_get_softc(self);
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint8_t iface_index;
+ int error;
+
+ device_set_usb_desc(self);
+ sc->sc_udev = uaa->device;
+ sc->sc_dev = self;
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(self),
+ MTX_NETWORK_LOCK, MTX_DEF);
+ mbufq_init(&sc->sc_snd, ifqmaxlen);
+
+ iface_index = RAL_IFACE_INDEX;
+ error = usbd_transfer_setup(uaa->device,
+ &iface_index, sc->sc_xfer, ural_config,
+ URAL_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(self, "could not allocate USB transfers, "
+ "err=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+
+ RAL_LOCK(sc);
+ /* 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);
+ RAL_UNLOCK(sc);
+
+ device_printf(self, "MAC/BBP RT2570 (rev 0x%02x), RF %s\n",
+ sc->asic_rev, ural_get_rf(sc->rf_rev));
+
+ ic->ic_softc = sc;
+ ic->ic_name = device_get_nameunit(self);
+ 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 */
+ ;
+
+ ural_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans,
+ ic->ic_channels);
+
+ ieee80211_ifattach(ic);
+ ic->ic_update_promisc = ural_update_promisc;
+ ic->ic_raw_xmit = ural_raw_xmit;
+ ic->ic_scan_start = ural_scan_start;
+ ic->ic_scan_end = ural_scan_end;
+ ic->ic_getradiocaps = ural_getradiocaps;
+ ic->ic_set_channel = ural_set_channel;
+ ic->ic_parent = ural_parent;
+ ic->ic_transmit = ural_transmit;
+ ic->ic_vap_create = ural_vap_create;
+ ic->ic_vap_delete = ural_vap_delete;
+
+ ieee80211_radiotap_attach(ic,
+ &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap),
+ RAL_TX_RADIOTAP_PRESENT,
+ &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap),
+ RAL_RX_RADIOTAP_PRESENT);
+
+ if (bootverbose)
+ ieee80211_announce(ic);
+
+ return (0);
+
+detach:
+ ural_detach(self);
+ return (ENXIO); /* failure */
+}
+
+static int
+ural_detach(device_t self)
+{
+ struct ural_softc *sc = device_get_softc(self);
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ /* prevent further ioctls */
+ RAL_LOCK(sc);
+ sc->sc_detached = 1;
+ RAL_UNLOCK(sc);
+
+ /* stop all USB transfers */
+ usbd_transfer_unsetup(sc->sc_xfer, URAL_N_TRANSFER);
+
+ /* free TX list, if any */
+ RAL_LOCK(sc);
+ ural_unsetup_tx_list(sc);
+ RAL_UNLOCK(sc);
+
+ if (ic->ic_softc == sc)
+ ieee80211_ifdetach(ic);
+ mbufq_drain(&sc->sc_snd);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static usb_error_t
+ural_do_request(struct ural_softc *sc,
+ struct usb_device_request *req, void *data)
+{
+ usb_error_t err;
+ int ntries = 10;
+
+ while (ntries--) {
+ err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx,
+ req, data, 0, NULL, 250 /* ms */);
+ if (err == 0)
+ break;
+
+ DPRINTFN(1, "Control request failed, %s (retrying)\n",
+ usbd_errstr(err));
+ if (ural_pause(sc, hz / 100))
+ break;
+ }
+ return (err);
+}
+
+static struct ieee80211vap *
+ural_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
+ enum ieee80211_opmode opmode, int flags,
+ const uint8_t bssid[IEEE80211_ADDR_LEN],
+ const uint8_t mac[IEEE80211_ADDR_LEN])
+{
+ struct ural_softc *sc = ic->ic_softc;
+ struct ural_vap *uvp;
+ struct ieee80211vap *vap;
+
+ if (!TAILQ_EMPTY(&ic->ic_vaps)) /* only one at a time */
+ return NULL;
+ uvp = malloc(sizeof(struct ural_vap), M_80211_VAP, M_WAITOK | M_ZERO);
+ vap = &uvp->vap;
+ /* enable s/w bmiss handling for sta mode */
+
+ if (ieee80211_vap_setup(ic, vap, name, unit, opmode,
+ flags | IEEE80211_CLONE_NOBEACONS, bssid) != 0) {
+ /* out of memory */
+ free(uvp, M_80211_VAP);
+ return (NULL);
+ }
+
+ /* override state transition machine */
+ uvp->newstate = vap->iv_newstate;
+ vap->iv_newstate = ural_newstate;
+
+ usb_callout_init_mtx(&uvp->ratectl_ch, &sc->sc_mtx, 0);
+ TASK_INIT(&uvp->ratectl_task, 0, ural_ratectl_task, uvp);
+ ieee80211_ratectl_init(vap);
+ ieee80211_ratectl_setinterval(vap, 1000 /* 1 sec */);
+
+ /* complete setup */
+ ieee80211_vap_attach(vap, ieee80211_media_change,
+ ieee80211_media_status, mac);
+ ic->ic_opmode = opmode;
+ return vap;
+}
+
+static void
+ural_vap_delete(struct ieee80211vap *vap)
+{
+ struct ural_vap *uvp = URAL_VAP(vap);
+ struct ieee80211com *ic = vap->iv_ic;
+
+ usb_callout_drain(&uvp->ratectl_ch);
+ ieee80211_draintask(ic, &uvp->ratectl_task);
+ ieee80211_ratectl_deinit(vap);
+ ieee80211_vap_detach(vap);
+ free(uvp, M_80211_VAP);
+}
+
+static void
+ural_tx_free(struct ural_tx_data *data, int txerr)
+{
+ struct ural_softc *sc = data->sc;
+
+ if (data->m != NULL) {
+ ieee80211_tx_complete(data->ni, data->m, txerr);
+ data->m = NULL;
+ data->ni = NULL;
+ }
+ STAILQ_INSERT_TAIL(&sc->tx_free, data, next);
+ sc->tx_nfree++;
+}
+
+static void
+ural_setup_tx_list(struct ural_softc *sc)
+{
+ struct ural_tx_data *data;
+ int i;
+
+ sc->tx_nfree = 0;
+ STAILQ_INIT(&sc->tx_q);
+ STAILQ_INIT(&sc->tx_free);
+
+ for (i = 0; i < RAL_TX_LIST_COUNT; i++) {
+ data = &sc->tx_data[i];
+
+ data->sc = sc;
+ STAILQ_INSERT_TAIL(&sc->tx_free, data, next);
+ sc->tx_nfree++;
+ }
+}
+
+static void
+ural_unsetup_tx_list(struct ural_softc *sc)
+{
+ struct ural_tx_data *data;
+ int i;
+
+ /* make sure any subsequent use of the queues will fail */
+ sc->tx_nfree = 0;
+ STAILQ_INIT(&sc->tx_q);
+ STAILQ_INIT(&sc->tx_free);
+
+ /* free up all node references and mbufs */
+ for (i = 0; i < RAL_TX_LIST_COUNT; i++) {
+ data = &sc->tx_data[i];
+
+ if (data->m != NULL) {
+ m_freem(data->m);
+ data->m = NULL;
+ }
+ if (data->ni != NULL) {
+ ieee80211_free_node(data->ni);
+ data->ni = NULL;
+ }
+ }
+}
+
+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_softc;
+ const struct ieee80211_txparam *tp;
+ struct ieee80211_node *ni;
+ struct mbuf *m;
+
+ DPRINTF("%s -> %s\n",
+ ieee80211_state_name[vap->iv_state],
+ ieee80211_state_name[nstate]);
+
+ IEEE80211_UNLOCK(ic);
+ RAL_LOCK(sc);
+ usb_callout_stop(&uvp->ratectl_ch);
+
+ switch (nstate) {
+ case IEEE80211_S_INIT:
+ if (vap->iv_state == 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 = ieee80211_ref_node(vap->iv_bss);
+
+ if (vap->iv_opmode != IEEE80211_M_MONITOR) {
+ if (ic->ic_bsschan == IEEE80211_CHAN_ANYC)
+ goto fail;
+
+ ural_update_slot(sc);
+ ural_set_txpreamble(sc);
+ ural_set_basicrates(sc, ic->ic_bsschan);
+ IEEE80211_ADDR_COPY(sc->sc_bssid, ni->ni_bssid);
+ ural_set_bssid(sc, sc->sc_bssid);
+ }
+
+ if (vap->iv_opmode == IEEE80211_M_HOSTAP ||
+ vap->iv_opmode == IEEE80211_M_IBSS) {
+ m = ieee80211_beacon_alloc(ni);
+ if (m == NULL) {
+ device_printf(sc->sc_dev,
+ "could not allocate beacon\n");
+ goto fail;
+ }
+ ieee80211_ref_node(ni);
+ if (ural_tx_bcn(sc, m, ni) != 0) {
+ device_printf(sc->sc_dev,
+ "could not send beacon\n");
+ goto fail;
+ }
+ }
+
+ /* 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);
+ else
+ ural_enable_tsf(sc);
+
+ /* enable automatic rate adaptation */
+ /* XXX should use ic_bsschan but not valid until after newstate call below */
+ tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_curchan)];
+ if (tp->ucastrate == IEEE80211_FIXED_RATE_NONE)
+ ural_ratectl_start(sc, ni);
+ ieee80211_free_node(ni);
+ break;
+
+ default:
+ break;
+ }
+ RAL_UNLOCK(sc);
+ IEEE80211_LOCK(ic);
+ return (uvp->newstate(vap, nstate, arg));
+
+fail:
+ RAL_UNLOCK(sc);
+ IEEE80211_LOCK(ic);
+ ieee80211_free_node(ni);
+ return (-1);
+}
+
+static void
+ural_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ural_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211vap *vap;
+ struct ural_tx_data *data;
+ struct mbuf *m;
+ struct usb_page_cache *pc;
+ int len;
+
+ usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTFN(11, "transfer complete, %d bytes\n", len);
+
+ /* free resources */
+ data = usbd_xfer_get_priv(xfer);
+ ural_tx_free(data, 0);
+ usbd_xfer_set_priv(xfer, NULL);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ data = STAILQ_FIRST(&sc->tx_q);
+ if (data) {
+ STAILQ_REMOVE_HEAD(&sc->tx_q, next);
+ m = data->m;
+
+ if (m->m_pkthdr.len > (int)(RAL_FRAME_SIZE + RAL_TX_DESC_SIZE)) {
+ DPRINTFN(0, "data overflow, %u bytes\n",
+ m->m_pkthdr.len);
+ m->m_pkthdr.len = (RAL_FRAME_SIZE + RAL_TX_DESC_SIZE);
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &data->desc, RAL_TX_DESC_SIZE);
+ usbd_m_copy_in(pc, RAL_TX_DESC_SIZE, m, 0,
+ m->m_pkthdr.len);
+
+ vap = data->ni->ni_vap;
+ if (ieee80211_radiotap_active_vap(vap)) {
+ struct ural_tx_radiotap_header *tap = &sc->sc_txtap;
+
+ tap->wt_flags = 0;
+ tap->wt_rate = data->rate;
+ tap->wt_antenna = sc->tx_ant;
+
+ ieee80211_radiotap_tx(vap, m);
+ }
+
+ /* xfer length needs to be a multiple of two! */
+ len = (RAL_TX_DESC_SIZE + m->m_pkthdr.len + 1) & ~1;
+ if ((len % 64) == 0)
+ len += 2;
+
+ DPRINTFN(11, "sending frame len=%u xferlen=%u\n",
+ m->m_pkthdr.len, len);
+
+ usbd_xfer_set_frame_len(xfer, 0, len);
+ usbd_xfer_set_priv(xfer, data);
+
+ usbd_transfer_submit(xfer);
+ }
+ ural_start(sc);
+ break;
+
+ default: /* Error */
+ DPRINTFN(11, "transfer error, %s\n",
+ usbd_errstr(error));
+
+ data = usbd_xfer_get_priv(xfer);
+ if (data != NULL) {
+ ural_tx_free(data, error);
+ usbd_xfer_set_priv(xfer, NULL);
+ }
+
+ if (error == USB_ERR_STALLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ if (error == USB_ERR_TIMEOUT)
+ device_printf(sc->sc_dev, "device timeout\n");
+ break;
+ }
+}
+
+static void
+ural_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct ural_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_node *ni;
+ struct mbuf *m = NULL;
+ struct usb_page_cache *pc;
+ uint32_t flags;
+ int8_t rssi = 0, nf = 0;
+ int len;
+
+ usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ DPRINTFN(15, "rx done, actlen=%d\n", len);
+
+ if (len < (int)(RAL_RX_DESC_SIZE + IEEE80211_MIN_LEN)) {
+ DPRINTF("%s: xfer too short %d\n",
+ device_get_nameunit(sc->sc_dev), len);
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto tr_setup;
+ }
+
+ len -= RAL_RX_DESC_SIZE;
+ /* rx descriptor is located at the end */
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, len, &sc->sc_rx_desc, RAL_RX_DESC_SIZE);
+
+ rssi = URAL_RSSI(sc->sc_rx_desc.rssi);
+ nf = RAL_NOISE_FLOOR;
+ flags = le32toh(sc->sc_rx_desc.flags);
+ if (flags & (RAL_RX_PHY_ERROR | 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");
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto tr_setup;
+ }
+
+ m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (m == NULL) {
+ DPRINTF("could not allocate mbuf\n");
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto tr_setup;
+ }
+ usbd_copy_out(pc, 0, mtod(m, uint8_t *), len);
+
+ /* finalize mbuf */
+ m->m_pkthdr.len = m->m_len = (flags >> 16) & 0xfff;
+
+ if (ieee80211_radiotap_active(ic)) {
+ struct ural_rx_radiotap_header *tap = &sc->sc_rxtap;
+
+ /* XXX set once */
+ tap->wr_flags = 0;
+ tap->wr_rate = ieee80211_plcp2rate(sc->sc_rx_desc.rate,
+ (flags & RAL_RX_OFDM) ?
+ IEEE80211_T_OFDM : IEEE80211_T_CCK);
+ tap->wr_antenna = sc->rx_ant;
+ tap->wr_antsignal = nf + rssi;
+ tap->wr_antnoise = nf;
+ }
+ /* Strip trailing 802.11 MAC FCS. */
+ m_adj(m, -IEEE80211_CRC_LEN);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+
+ /*
+ * At the end of a USB callback it is always safe to unlock
+ * the private mutex of a device! That is why we do the
+ * "ieee80211_input" here, and not some lines up!
+ */
+ RAL_UNLOCK(sc);
+ if (m) {
+ ni = ieee80211_find_rxnode(ic,
+ mtod(m, struct ieee80211_frame_min *));
+ if (ni != NULL) {
+ (void) ieee80211_input(ni, m, rssi, nf);
+ ieee80211_free_node(ni);
+ } else
+ (void) ieee80211_input_all(ic, m, rssi, nf);
+ }
+ RAL_LOCK(sc);
+ ural_start(sc);
+ return;
+
+ default: /* Error */
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ return;
+ }
+}
+
+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 ieee80211com *ic = &sc->sc_ic;
+ 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(ic->ic_rt, 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 {
+ if (rate == 0)
+ rate = 2; /* avoid division by zero */
+ plcp_length = howmany(16 * len, 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_data *data;
+
+ if (sc->tx_nfree == 0) {
+ m_freem(m0);
+ ieee80211_free_node(ni);
+ return (EIO);
+ }
+ if (ic->ic_bsschan == IEEE80211_CHAN_ANYC) {
+ m_freem(m0);
+ ieee80211_free_node(ni);
+ return (ENXIO);
+ }
+ data = STAILQ_FIRST(&sc->tx_free);
+ STAILQ_REMOVE_HEAD(&sc->tx_free, next);
+ sc->tx_nfree--;
+ tp = &vap->iv_txparms[ieee80211_chan2mode(ic->ic_bsschan)];
+
+ data->m = m0;
+ data->ni = ni;
+ data->rate = tp->mgmtrate;
+
+ ural_setup_tx_desc(sc, &data->desc,
+ RAL_TX_IFS_NEWBACKOFF | RAL_TX_TIMESTAMP, m0->m_pkthdr.len,
+ tp->mgmtrate);
+
+ DPRINTFN(10, "sending beacon frame len=%u rate=%u\n",
+ m0->m_pkthdr.len, tp->mgmtrate);
+
+ STAILQ_INSERT_TAIL(&sc->tx_q, data, next);
+ usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]);
+
+ return (0);
+}
+
+static int
+ural_tx_mgt(struct ural_softc *sc, struct mbuf *m0, struct ieee80211_node *ni)
+{
+ const struct ieee80211_txparam *tp = ni->ni_txparms;
+ struct ieee80211com *ic = ni->ni_ic;
+ struct ural_tx_data *data;
+ struct ieee80211_frame *wh;
+ struct ieee80211_key *k;
+ uint32_t flags;
+ uint16_t dur;
+
+ RAL_LOCK_ASSERT(sc, MA_OWNED);
+
+ data = STAILQ_FIRST(&sc->tx_free);
+ STAILQ_REMOVE_HEAD(&sc->tx_free, next);
+ sc->tx_nfree--;
+
+ wh = mtod(m0, struct ieee80211_frame *);
+ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
+ 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;
+ data->rate = tp->mgmtrate;
+
+ flags = 0;
+ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
+ flags |= RAL_TX_ACK;
+
+ dur = ieee80211_ack_duration(ic->ic_rt, tp->mgmtrate,
+ ic->ic_flags & IEEE80211_F_SHPREAMBLE);
+ USETW(wh->i_dur, dur);
+
+ /* tell hardware to add timestamp for probe responses */
+ if (IEEE80211_IS_MGMT_PROBE_RESP(wh))
+ flags |= RAL_TX_TIMESTAMP;
+ }
+
+ ural_setup_tx_desc(sc, &data->desc, flags, m0->m_pkthdr.len, tp->mgmtrate);
+
+ DPRINTFN(10, "sending mgt frame len=%u rate=%u\n",
+ m0->m_pkthdr.len, tp->mgmtrate);
+
+ STAILQ_INSERT_TAIL(&sc->tx_q, data, next);
+ usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]);
+
+ 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;
+ struct ural_tx_data *data;
+ struct mbuf *mprot;
+ int protrate, flags;
+
+ mprot = ieee80211_alloc_prot(ni, m, rate, prot);
+ if (mprot == NULL) {
+ if_inc_counter(ni->ni_vap->iv_ifp, IFCOUNTER_OERRORS, 1);
+ device_printf(sc->sc_dev,
+ "could not allocate mbuf for protection mode %d\n", prot);
+ return ENOBUFS;
+ }
+
+ protrate = ieee80211_ctl_rate(ic->ic_rt, rate);
+ flags = RAL_TX_RETRY(7);
+ if (prot == IEEE80211_PROT_RTSCTS)
+ flags |= RAL_TX_ACK;
+
+ data = STAILQ_FIRST(&sc->tx_free);
+ STAILQ_REMOVE_HEAD(&sc->tx_free, next);
+ sc->tx_nfree--;
+
+ data->m = mprot;
+ data->ni = ieee80211_ref_node(ni);
+ data->rate = protrate;
+ ural_setup_tx_desc(sc, &data->desc, flags, mprot->m_pkthdr.len, protrate);
+
+ STAILQ_INSERT_TAIL(&sc->tx_q, data, next);
+ usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]);
+
+ 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 ieee80211com *ic = ni->ni_ic;
+ struct ural_tx_data *data;
+ uint32_t flags;
+ int error;
+ int rate;
+
+ RAL_LOCK_ASSERT(sc, MA_OWNED);
+ KASSERT(params != NULL, ("no raw xmit params"));
+
+ rate = params->ibp_rate0;
+ if (!ieee80211_isratevalid(ic->ic_rt, rate)) {
+ 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 || sc->tx_nfree == 0) {
+ m_freem(m0);
+ return ENOBUFS;
+ }
+ flags |= RAL_TX_IFS_SIFS;
+ }
+
+ data = STAILQ_FIRST(&sc->tx_free);
+ STAILQ_REMOVE_HEAD(&sc->tx_free, next);
+ sc->tx_nfree--;
+
+ data->m = m0;
+ data->ni = ni;
+ data->rate = rate;
+
+ /* XXX need to setup descriptor ourself */
+ ural_setup_tx_desc(sc, &data->desc, flags, m0->m_pkthdr.len, rate);
+
+ DPRINTFN(10, "sending raw frame len=%u rate=%u\n",
+ m0->m_pkthdr.len, rate);
+
+ STAILQ_INSERT_TAIL(&sc->tx_q, data, next);
+ usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]);
+
+ 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 ural_tx_data *data;
+ struct ieee80211_frame *wh;
+ const struct ieee80211_txparam *tp = ni->ni_txparms;
+ struct ieee80211_key *k;
+ uint32_t flags = 0;
+ uint16_t dur;
+ int error, rate;
+
+ RAL_LOCK_ASSERT(sc, MA_OWNED);
+
+ wh = mtod(m0, struct ieee80211_frame *);
+
+ if (m0->m_flags & M_EAPOL)
+ rate = tp->mgmtrate;
+ else if (IEEE80211_IS_MULTICAST(wh->i_addr1))
+ rate = tp->mcastrate;
+ else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE)
+ rate = tp->ucastrate;
+ else {
+ (void) ieee80211_ratectl_rate(ni, NULL, 0);
+ rate = ieee80211_node_get_txrate_dot11rate(ni);
+ }
+
+ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
+ 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(ic->ic_rt, rate) == IEEE80211_T_OFDM)
+ prot = ic->ic_protmode;
+ if (prot != IEEE80211_PROT_NONE) {
+ error = ural_sendprot(sc, m0, ni, prot, rate);
+ if (error || sc->tx_nfree == 0) {
+ m_freem(m0);
+ return ENOBUFS;
+ }
+ flags |= RAL_TX_IFS_SIFS;
+ }
+ }
+
+ data = STAILQ_FIRST(&sc->tx_free);
+ STAILQ_REMOVE_HEAD(&sc->tx_free, next);
+ sc->tx_nfree--;
+
+ data->m = m0;
+ data->ni = ni;
+ data->rate = rate;
+
+ if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
+ flags |= RAL_TX_ACK;
+ flags |= RAL_TX_RETRY(7);
+
+ dur = ieee80211_ack_duration(ic->ic_rt, rate,
+ ic->ic_flags & IEEE80211_F_SHPREAMBLE);
+ USETW(wh->i_dur, dur);
+ }
+
+ ural_setup_tx_desc(sc, &data->desc, flags, m0->m_pkthdr.len, rate);
+
+ DPRINTFN(10, "sending data frame len=%u rate=%u\n",
+ m0->m_pkthdr.len, rate);
+
+ STAILQ_INSERT_TAIL(&sc->tx_q, data, next);
+ usbd_transfer_start(sc->sc_xfer[URAL_BULK_WR]);
+
+ return 0;
+}
+
+static int
+ural_transmit(struct ieee80211com *ic, struct mbuf *m)
+{
+ struct ural_softc *sc = ic->ic_softc;
+ int error;
+
+ RAL_LOCK(sc);
+ if (!sc->sc_running) {
+ RAL_UNLOCK(sc);
+ return (ENXIO);
+ }
+ error = mbufq_enqueue(&sc->sc_snd, m);
+ if (error) {
+ RAL_UNLOCK(sc);
+ return (error);
+ }
+ ural_start(sc);
+ RAL_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+ural_start(struct ural_softc *sc)
+{
+ struct ieee80211_node *ni;
+ struct mbuf *m;
+
+ RAL_LOCK_ASSERT(sc, MA_OWNED);
+
+ if (sc->sc_running == 0)
+ return;
+
+ while (sc->tx_nfree >= RAL_TX_MINFREE &&
+ (m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
+ ni = (struct ieee80211_node *) m->m_pkthdr.rcvif;
+ if (ural_tx_data(sc, m, ni) != 0) {
+ if_inc_counter(ni->ni_vap->iv_ifp,
+ IFCOUNTER_OERRORS, 1);
+ ieee80211_free_node(ni);
+ break;
+ }
+ }
+}
+
+static void
+ural_parent(struct ieee80211com *ic)
+{
+ struct ural_softc *sc = ic->ic_softc;
+ int startall = 0;
+
+ RAL_LOCK(sc);
+ if (sc->sc_detached) {
+ RAL_UNLOCK(sc);
+ return;
+ }
+ if (ic->ic_nrunning > 0) {
+ if (sc->sc_running == 0) {
+ ural_init(sc);
+ startall = 1;
+ } else
+ ural_setpromisc(sc);
+ } else if (sc->sc_running)
+ ural_stop(sc);
+ RAL_UNLOCK(sc);
+ if (startall)
+ ieee80211_start_all(ic);
+}
+
+static void
+ural_set_testmode(struct ural_softc *sc)
+{
+ struct usb_device_request req;
+ usb_error_t 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 = ural_do_request(sc, &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)
+{
+ struct usb_device_request req;
+ usb_error_t 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 = ural_do_request(sc, &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)
+{
+ struct usb_device_request req;
+ usb_error_t 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 = ural_do_request(sc, &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)
+{
+ struct usb_device_request req;
+ usb_error_t 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 = ural_do_request(sc, &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)
+{
+ struct usb_device_request req;
+ usb_error_t 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 = ural_do_request(sc, &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)
+{
+ struct usb_device_request req;
+ usb_error_t 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 = ural_do_request(sc, &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 < 100; ntries++) {
+ if (!(ural_read(sc, RAL_PHY_CSR8) & RAL_BBP_BUSY))
+ break;
+ if (ural_pause(sc, hz / 100))
+ break;
+ }
+ if (ntries == 100) {
+ 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 < 100; ntries++) {
+ if (!(ural_read(sc, RAL_PHY_CSR8) & RAL_BBP_BUSY))
+ break;
+ if (ural_pause(sc, hz / 100))
+ break;
+ }
+ if (ntries == 100) {
+ 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 < 100; ntries++) {
+ if (!(ural_read(sc, RAL_PHY_CSR10) & RAL_RF_LOBUSY))
+ break;
+ if (ural_pause(sc, hz / 100))
+ break;
+ }
+ if (ntries == 100) {
+ 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);
+}
+
+static void
+ural_scan_start(struct ieee80211com *ic)
+{
+ struct ural_softc *sc = ic->ic_softc;
+
+ RAL_LOCK(sc);
+ ural_write(sc, RAL_TXRX_CSR19, 0);
+ ural_set_bssid(sc, ieee80211broadcastaddr);
+ RAL_UNLOCK(sc);
+}
+
+static void
+ural_scan_end(struct ieee80211com *ic)
+{
+ struct ural_softc *sc = ic->ic_softc;
+
+ RAL_LOCK(sc);
+ ural_enable_tsf_sync(sc);
+ ural_set_bssid(sc, sc->sc_bssid);
+ RAL_UNLOCK(sc);
+
+}
+
+static void
+ural_getradiocaps(struct ieee80211com *ic,
+ int maxchans, int *nchans, struct ieee80211_channel chans[])
+{
+ struct ural_softc *sc = ic->ic_softc;
+ uint8_t bands[IEEE80211_MODE_BYTES];
+
+ memset(bands, 0, sizeof(bands));
+ setbit(bands, IEEE80211_MODE_11B);
+ setbit(bands, IEEE80211_MODE_11G);
+ ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0);
+
+ if (sc->rf_rev == RAL_RF_5222) {
+ setbit(bands, IEEE80211_MODE_11A);
+ ieee80211_add_channel_list_5ghz(chans, maxchans, nchans,
+ ural_chan_5ghz, nitems(ural_chan_5ghz), bands, 0);
+ }
+}
+
+static void
+ural_set_channel(struct ieee80211com *ic)
+{
+ struct ural_softc *sc = ic->ic_softc;
+
+ RAL_LOCK(sc);
+ ural_set_chan(sc, ic->ic_curchan);
+ RAL_UNLOCK(sc);
+}
+
+static void
+ural_set_chan(struct ural_softc *sc, struct ieee80211_channel *c)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint8_t power, tmp;
+ 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);
+
+ ural_pause(sc, hz / 100);
+ ural_disable_rf_tune(sc);
+ }
+
+ /* XXX doesn't belong here */
+ /* update basic rate set */
+ ural_set_basicrates(sc, c);
+
+ /* give the hardware some time to do the switchover */
+ ural_pause(sc, hz / 100);
+}
+
+/*
+ * 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 ieee80211com *ic = &sc->sc_ic;
+ 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_enable_tsf(struct ural_softc *sc)
+{
+ /* first, disable TSF synchronization */
+ ural_write(sc, RAL_TXRX_CSR19, 0);
+ ural_write(sc, RAL_TXRX_CSR19, RAL_ENABLE_TSF | RAL_ENABLE_TSF_SYNC(2));
+}
+
+#define RAL_RXTX_TURNAROUND 5 /* us */
+static void
+ural_update_slot(struct ural_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint16_t slottime, sifs, eifs;
+
+ slottime = IEEE80211_GET_SLOTTIME(ic);
+
+ /*
+ * 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 ieee80211com *ic = &sc->sc_ic;
+ 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, const 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_setpromisc(struct ural_softc *sc)
+{
+ uint32_t tmp;
+
+ tmp = ural_read(sc, RAL_TXRX_CSR2);
+
+ tmp &= ~RAL_DROP_NOT_TO_ME;
+ if (sc->sc_ic.ic_promisc == 0)
+ tmp |= RAL_DROP_NOT_TO_ME;
+
+ ural_write(sc, RAL_TXRX_CSR2, tmp);
+
+ DPRINTF("%s promiscuous mode\n", sc->sc_ic.ic_promisc ?
+ "entering" : "leaving");
+}
+
+static void
+ural_update_promisc(struct ieee80211com *ic)
+{
+ struct ural_softc *sc = ic->ic_softc;
+
+ RAL_LOCK(sc);
+ if (sc->sc_running)
+ ural_setpromisc(sc);
+ RAL_UNLOCK(sc);
+}
+
+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 ieee80211com *ic = &sc->sc_ic;
+ 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_macaddr, 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)
+{
+ 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;
+ if (ural_pause(sc, hz / 100))
+ break;
+ }
+ 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 < nitems(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;
+}
+
+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(struct ural_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ uint16_t tmp;
+ int i, ntries;
+
+ RAL_LOCK_ASSERT(sc, MA_OWNED);
+
+ ural_set_testmode(sc);
+ ural_write(sc, 0x308, 0x00f0); /* XXX magic */
+
+ ural_stop(sc);
+
+ /* initialize MAC registers to default values */
+ for (i = 0; i < nitems(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;
+ if (ural_pause(sc, hz / 100))
+ break;
+ }
+ 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);
+
+ ural_set_macaddr(sc, vap ? vap->iv_myaddr : ic->ic_macaddr);
+
+ /*
+ * Allocate Tx and Rx xfer queues.
+ */
+ ural_setup_tx_list(sc);
+
+ /* 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 (ic->ic_promisc == 0)
+ tmp |= RAL_DROP_NOT_TO_ME;
+ }
+ ural_write(sc, RAL_TXRX_CSR2, tmp);
+
+ sc->sc_running = 1;
+ usbd_xfer_set_stall(sc->sc_xfer[URAL_BULK_WR]);
+ usbd_transfer_start(sc->sc_xfer[URAL_BULK_RD]);
+ return;
+
+fail: ural_stop(sc);
+}
+
+static void
+ural_stop(struct ural_softc *sc)
+{
+
+ RAL_LOCK_ASSERT(sc, MA_OWNED);
+
+ sc->sc_running = 0;
+
+ /*
+ * Drain all the transfers, if not already drained:
+ */
+ RAL_UNLOCK(sc);
+ usbd_transfer_drain(sc->sc_xfer[URAL_BULK_WR]);
+ usbd_transfer_drain(sc->sc_xfer[URAL_BULK_RD]);
+ RAL_LOCK(sc);
+
+ ural_unsetup_tx_list(sc);
+
+ /* 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);
+ /* wait a little */
+ ural_pause(sc, hz / 10);
+ ural_write(sc, RAL_MAC_CSR1, 0);
+ /* wait a little */
+ ural_pause(sc, hz / 10);
+}
+
+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 ural_softc *sc = ic->ic_softc;
+
+ RAL_LOCK(sc);
+ /* prevent management frames from being sent if we're not ready */
+ if (!sc->sc_running) {
+ RAL_UNLOCK(sc);
+ m_freem(m);
+ return ENETDOWN;
+ }
+ if (sc->tx_nfree < RAL_TX_MINFREE) {
+ RAL_UNLOCK(sc);
+ m_freem(m);
+ return EIO;
+ }
+
+ 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;
+ }
+ RAL_UNLOCK(sc);
+ return 0;
+bad:
+ RAL_UNLOCK(sc);
+ return EIO; /* XXX */
+}
+
+static void
+ural_ratectl_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);
+
+ usb_callout_reset(&uvp->ratectl_ch, hz, ural_ratectl_timeout, uvp);
+}
+
+static void
+ural_ratectl_timeout(void *arg)
+{
+ struct ural_vap *uvp = arg;
+ struct ieee80211vap *vap = &uvp->vap;
+ struct ieee80211com *ic = vap->iv_ic;
+
+ ieee80211_runtask(ic, &uvp->ratectl_task);
+}
+
+static void
+ural_ratectl_task(void *arg, int pending)
+{
+ struct ural_vap *uvp = arg;
+ struct ieee80211vap *vap = &uvp->vap;
+ struct ural_softc *sc = vap->iv_ic->ic_softc;
+ struct ieee80211_ratectl_tx_stats *txs = &sc->sc_txs;
+ int fail;
+
+ RAL_LOCK(sc);
+ /* read and clear statistic registers (STA_CSR0 to STA_CSR10) */
+ ural_read_multi(sc, RAL_STA_CSR0, sc->sta, sizeof(sc->sta));
+
+ txs->flags = IEEE80211_RATECTL_TX_STATS_RETRIES;
+ txs->nsuccess = sc->sta[7] + /* TX ok w/o retry */
+ sc->sta[8]; /* TX ok w/ retry */
+ fail = sc->sta[9]; /* TX retry-fail count */
+ txs->nframes = txs->nsuccess + fail;
+ /* XXX fail * maxretry */
+ txs->nretries = sc->sta[8] + fail;
+
+ ieee80211_ratectl_tx_update(vap, txs);
+
+ /* count TX retry-fail as Tx errors */
+ if_inc_counter(vap->iv_ifp, IFCOUNTER_OERRORS, fail);
+
+ usb_callout_reset(&uvp->ratectl_ch, hz, ural_ratectl_timeout, uvp);
+ RAL_UNLOCK(sc);
+}
+
+static int
+ural_pause(struct ural_softc *sc, int timeout)
+{
+
+ usb_pause_mtx(&sc->sc_mtx, timeout);
+ return (0);
+}
diff --git a/sys/dev/usb/wlan/if_uralreg.h b/sys/dev/usb/wlan/if_uralreg.h
new file mode 100644
index 000000000000..ece13de17192
--- /dev/null
+++ b/sys/dev/usb/wlan/if_uralreg.h
@@ -0,0 +1,209 @@
+
+/*-
+ * 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_FRAME_SIZE 0x780 /* NOTE: using 0x980 does not work */
+
+#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 (1U << 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/dev/usb/wlan/if_uralvar.h b/sys/dev/usb/wlan/if_uralvar.h
new file mode 100644
index 000000000000..c2b9074fc9b5
--- /dev/null
+++ b/sys/dev/usb/wlan/if_uralvar.h
@@ -0,0 +1,134 @@
+
+/*-
+ * 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_TX_LIST_COUNT 8
+#define RAL_TX_MINFREE 2
+
+#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;
+ int8_t wr_antsignal;
+ int8_t wr_antnoise;
+ uint8_t wr_antenna;
+} __packed __aligned(8);
+
+#define RAL_RX_RADIOTAP_PRESENT \
+ ((1 << IEEE80211_RADIOTAP_FLAGS) | \
+ (1 << IEEE80211_RADIOTAP_RATE) | \
+ (1 << IEEE80211_RADIOTAP_CHANNEL) | \
+ (1 << IEEE80211_RADIOTAP_ANTENNA) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \
+ (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE))
+
+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;
+} __packed;
+
+#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 {
+ STAILQ_ENTRY(ural_tx_data) next;
+ struct ural_softc *sc;
+ struct ural_tx_desc desc;
+ struct mbuf *m;
+ struct ieee80211_node *ni;
+ int rate;
+};
+typedef STAILQ_HEAD(, ural_tx_data) ural_txdhead;
+
+struct ural_vap {
+ struct ieee80211vap vap;
+
+ struct usb_callout ratectl_ch;
+ struct task ratectl_task;
+
+ int (*newstate)(struct ieee80211vap *,
+ enum ieee80211_state, int);
+};
+#define URAL_VAP(vap) ((struct ural_vap *)(vap))
+
+enum {
+ URAL_BULK_WR,
+ URAL_BULK_RD,
+ URAL_N_TRANSFER = 2,
+};
+
+struct ural_softc {
+ struct ieee80211com sc_ic;
+ struct ieee80211_ratectl_tx_stats sc_txs;
+ struct mbufq sc_snd;
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+
+ uint32_t asic_rev;
+ uint8_t rf_rev;
+
+ struct usb_xfer *sc_xfer[URAL_N_TRANSFER];
+
+ struct ural_tx_data tx_data[RAL_TX_LIST_COUNT];
+ ural_txdhead tx_q;
+ ural_txdhead tx_free;
+ int tx_nfree;
+ struct ural_rx_desc sc_rx_desc;
+
+ struct mtx sc_mtx;
+
+ uint16_t sta[11];
+ uint32_t rf_regs[4];
+ uint8_t txpow[14];
+ u_int sc_detached:1,
+ sc_running:1;
+
+ uint8_t sc_bssid[IEEE80211_ADDR_LEN];
+
+ 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;
+ struct ural_tx_radiotap_header sc_txtap;
+};
+
+#define RAL_LOCK(sc) mtx_lock(&(sc)->sc_mtx)
+#define RAL_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx)
+#define RAL_LOCK_ASSERT(sc, t) mtx_assert(&(sc)->sc_mtx, t)
diff --git a/sys/dev/usb/wlan/if_urtw.c b/sys/dev/usb/wlan/if_urtw.c
new file mode 100644
index 000000000000..439faeefc408
--- /dev/null
+++ b/sys/dev/usb/wlan/if_urtw.c
@@ -0,0 +1,4430 @@
+/*-
+ * 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 "opt_wlan.h"
+
+#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 <net/if.h>
+#include <net/if_var.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 "usbdevs.h"
+
+#include <dev/usb/wlan/if_urtwreg.h>
+#include <dev/usb/wlan/if_urtwvar.h>
+
+/* copy some rate indices from if_rtwn_ridx.h */
+#define URTW_RIDX_CCK5 2
+#define URTW_RIDX_CCK11 3
+#define URTW_RIDX_OFDM6 4
+#define URTW_RIDX_OFDM24 8
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, urtw, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB Realtek 8187L");
+#ifdef URTW_DEBUG
+int urtw_debug = 0;
+SYSCTL_INT(_hw_usb_urtw, OID_AUTO, debug, CTLFLAG_RWTUN, &urtw_debug, 0,
+ "control debugging printfs");
+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_INIT = 0x00000080, /* initialization of dev */
+ URTW_DEBUG_TXSTATUS = 0x00000100, /* tx status */
+ 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
+static int urtw_preamble_mode = URTW_PREAMBLE_MODE_LONG;
+SYSCTL_INT(_hw_usb_urtw, OID_AUTO, preamble_mode, CTLFLAG_RWTUN,
+ &urtw_preamble_mode, 0, "set the preable mode (long or short)");
+
+/* recognized device vendors/products */
+#define urtw_lookup(v, p) \
+ ((const struct urtw_type *)usb_lookup(urtw_devs, v, p))
+#define URTW_DEV_B(v,p) \
+ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, URTW_REV_RTL8187B) }
+#define URTW_DEV_L(v,p) \
+ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, URTW_REV_RTL8187L) }
+#define URTW_REV_RTL8187B 0
+#define URTW_REV_RTL8187L 1
+static const STRUCT_USB_HOST_ID urtw_devs[] = {
+ URTW_DEV_B(NETGEAR, WG111V3),
+ URTW_DEV_B(REALTEK, RTL8187B_0),
+ URTW_DEV_B(REALTEK, RTL8187B_1),
+ URTW_DEV_B(REALTEK, RTL8187B_2),
+ URTW_DEV_B(SITECOMEU, WL168V4),
+ URTW_DEV_L(ASUS, P5B_WIFI),
+ URTW_DEV_L(BELKIN, F5D7050E),
+ URTW_DEV_L(LINKSYS4, WUSB54GCV2),
+ URTW_DEV_L(NETGEAR, WG111V2),
+ URTW_DEV_L(REALTEK, RTL8187),
+ URTW_DEV_L(SITECOMEU, WL168V1),
+ URTW_DEV_L(SURECOM, EP9001G2A),
+ { USB_VPI(USB_VENDOR_OVISLINK, 0x8187, URTW_REV_RTL8187L) },
+ { USB_VPI(USB_VENDOR_DICKSMITH, 0x9401, URTW_REV_RTL8187L) },
+ { USB_VPI(USB_VENDOR_HP, 0xca02, URTW_REV_RTL8187L) },
+ { USB_VPI(USB_VENDOR_LOGITEC, 0x010c, URTW_REV_RTL8187L) },
+ { USB_VPI(USB_VENDOR_NETGEAR, 0x6100, URTW_REV_RTL8187L) },
+ { USB_VPI(USB_VENDOR_SPHAIRON, 0x0150, URTW_REV_RTL8187L) },
+ { USB_VPI(USB_VENDOR_QCOM, 0x6232, URTW_REV_RTL8187L) },
+#undef URTW_DEV_L
+#undef URTW_DEV_B
+};
+
+#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 uint8_t urtw_8225z2_agc[] = {
+ 0x5e, 0x5e, 0x5e, 0x5e, 0x5d, 0x5b, 0x59, 0x57, 0x55, 0x53, 0x51,
+ 0x4f, 0x4d, 0x4b, 0x49, 0x47, 0x45, 0x43, 0x41, 0x3f, 0x3d, 0x3b,
+ 0x39, 0x37, 0x35, 0x33, 0x31, 0x2f, 0x2d, 0x2b, 0x29, 0x27, 0x25,
+ 0x23, 0x21, 0x1f, 0x1d, 0x1b, 0x19, 0x17, 0x15, 0x13, 0x11, 0x0f,
+ 0x0d, 0x0b, 0x09, 0x07, 0x05, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x19, 0x19,
+ 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x20, 0x21, 0x22, 0x23,
+ 0x24, 0x25, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28, 0x29, 0x2a, 0x2a,
+ 0x2a, 0x2b, 0x2b, 0x2b, 0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d,
+ 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f, 0x30, 0x30, 0x31, 0x31,
+ 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+ 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31
+};
+
+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_8225v2b_rf_part0[] = {
+ { 0x00, 0x00b7 }, { 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_8225v2b_rf_part1[] = {
+ {0x0f0, 0x32}, {0x0f1, 0x32}, {0x0f2, 0x00},
+ {0x0f3, 0x00}, {0x0f4, 0x32}, {0x0f5, 0x43},
+ {0x0f6, 0x00}, {0x0f7, 0x00}, {0x0f8, 0x46},
+ {0x0f9, 0xa4}, {0x0fa, 0x00}, {0x0fb, 0x00},
+ {0x0fc, 0x96}, {0x0fd, 0xa4}, {0x0fe, 0x00},
+ {0x0ff, 0x00}, {0x158, 0x4b}, {0x159, 0x00},
+ {0x15a, 0x4b}, {0x15b, 0x00}, {0x160, 0x4b},
+ {0x161, 0x09}, {0x162, 0x4b}, {0x163, 0x09},
+ {0x1ce, 0x0f}, {0x1cf, 0x00}, {0x1e0, 0xff},
+ {0x1e1, 0x0f}, {0x1e2, 0x00}, {0x1f0, 0x4e},
+ {0x1f1, 0x01}, {0x1f2, 0x02}, {0x1f3, 0x03},
+ {0x1f4, 0x04}, {0x1f5, 0x05}, {0x1f6, 0x06},
+ {0x1f7, 0x07}, {0x1f8, 0x08}, {0x24e, 0x00},
+ {0x20c, 0x04}, {0x221, 0x61}, {0x222, 0x68},
+ {0x223, 0x6f}, {0x224, 0x76}, {0x225, 0x7d},
+ {0x226, 0x84}, {0x227, 0x8d}, {0x24d, 0x08},
+ {0x250, 0x05}, {0x251, 0xf5}, {0x252, 0x04},
+ {0x253, 0xa0}, {0x254, 0x1f}, {0x255, 0x23},
+ {0x256, 0x45}, {0x257, 0x67}, {0x258, 0x08},
+ {0x259, 0x08}, {0x25a, 0x08}, {0x25b, 0x08},
+ {0x260, 0x08}, {0x261, 0x08}, {0x262, 0x08},
+ {0x263, 0x08}, {0x264, 0xcf}, {0x272, 0x56},
+ {0x273, 0x9a}, {0x034, 0xf0}, {0x035, 0x0f},
+ {0x05b, 0x40}, {0x084, 0x88}, {0x085, 0x24},
+ {0x088, 0x54}, {0x08b, 0xb8}, {0x08c, 0x07},
+ {0x08d, 0x00}, {0x094, 0x1b}, {0x095, 0x12},
+ {0x096, 0x00}, {0x097, 0x06}, {0x09d, 0x1a},
+ {0x09f, 0x10}, {0x0b4, 0x22}, {0x0be, 0x80},
+ {0x0db, 0x00}, {0x0ee, 0x00}, {0x091, 0x03},
+ {0x24c, 0x00}, {0x39f, 0x00}, {0x08c, 0x01},
+ {0x08d, 0x10}, {0x08e, 0x08}, {0x08f, 0x00}
+};
+
+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_8225v2b_rf_part2[] = {
+ { 0x00, 0x10 }, { 0x01, 0x0d }, { 0x02, 0x01 }, { 0x03, 0x00 },
+ { 0x04, 0x14 }, { 0x05, 0xfb }, { 0x06, 0xfb }, { 0x07, 0x60 },
+ { 0x08, 0x00 }, { 0x09, 0x60 }, { 0x0a, 0x00 }, { 0x0b, 0x00 },
+ { 0x0c, 0x00 }, { 0x0d, 0x5c }, { 0x0e, 0x00 }, { 0x0f, 0x00 },
+ { 0x10, 0x40 }, { 0x11, 0x00 }, { 0x12, 0x40 }, { 0x13, 0x00 },
+ { 0x14, 0x00 }, { 0x15, 0x00 }, { 0x16, 0xa8 }, { 0x17, 0x26 },
+ { 0x18, 0x32 }, { 0x19, 0x33 }, { 0x1a, 0x07 }, { 0x1b, 0xa5 },
+ { 0x1c, 0x6f }, { 0x1d, 0x55 }, { 0x1e, 0xc8 }, { 0x1f, 0xb3 },
+ { 0x20, 0x0a }, { 0x21, 0xe1 }, { 0x22, 0x2C }, { 0x23, 0x8a },
+ { 0x24, 0x86 }, { 0x25, 0x83 }, { 0x26, 0x34 }, { 0x27, 0x0f },
+ { 0x28, 0x4f }, { 0x29, 0x24 }, { 0x2a, 0x6f }, { 0x2b, 0xc2 },
+ { 0x2c, 0x6b }, { 0x2d, 0x40 }, { 0x2e, 0x80 }, { 0x2f, 0x00 },
+ { 0x30, 0xc0 }, { 0x31, 0xc1 }, { 0x32, 0x58 }, { 0x33, 0xf1 },
+ { 0x34, 0x00 }, { 0x35, 0xe4 }, { 0x36, 0x90 }, { 0x37, 0x3e },
+ { 0x38, 0x6d }, { 0x39, 0x3c }, { 0x3a, 0xfb }, { 0x3b, 0x07 }
+};
+
+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 uint16_t urtw_8225v2b_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,
+ 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 uint8_t urtw_8225v2b_txpwr_cck[] = {
+ 0x36, 0x35, 0x2e, 0x25, 0x1c, 0x12, 0x09, 0x04,
+ 0x30, 0x2f, 0x29, 0x21, 0x19, 0x10, 0x08, 0x03,
+ 0x2b, 0x2a, 0x25, 0x1e, 0x16, 0x0e, 0x07, 0x03,
+ 0x26, 0x25, 0x21, 0x1b, 0x14, 0x0d, 0x06, 0x03
+};
+
+static uint8_t urtw_8225v2b_txpwr_cck_ch14[] = {
+ 0x36, 0x35, 0x2e, 0x1b, 0x00, 0x00, 0x00, 0x00,
+ 0x30, 0x2f, 0x29, 0x15, 0x00, 0x00, 0x00, 0x00,
+ 0x30, 0x2f, 0x29, 0x15, 0x00, 0x00, 0x00, 0x00,
+ 0x30, 0x2f, 0x29, 0x15, 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 }
+};
+
+#if 0
+static const uint8_t urtw_8187b_reg_table[][3] = {
+ { 0xf0, 0x32, 0 }, { 0xf1, 0x32, 0 }, { 0xf2, 0x00, 0 },
+ { 0xf3, 0x00, 0 }, { 0xf4, 0x32, 0 }, { 0xf5, 0x43, 0 },
+ { 0xf6, 0x00, 0 }, { 0xf7, 0x00, 0 }, { 0xf8, 0x46, 0 },
+ { 0xf9, 0xa4, 0 }, { 0xfa, 0x00, 0 }, { 0xfb, 0x00, 0 },
+ { 0xfc, 0x96, 0 }, { 0xfd, 0xa4, 0 }, { 0xfe, 0x00, 0 },
+ { 0xff, 0x00, 0 }, { 0x58, 0x4b, 1 }, { 0x59, 0x00, 1 },
+ { 0x5a, 0x4b, 1 }, { 0x5b, 0x00, 1 }, { 0x60, 0x4b, 1 },
+ { 0x61, 0x09, 1 }, { 0x62, 0x4b, 1 }, { 0x63, 0x09, 1 },
+ { 0xce, 0x0f, 1 }, { 0xcf, 0x00, 1 }, { 0xe0, 0xff, 1 },
+ { 0xe1, 0x0f, 1 }, { 0xe2, 0x00, 1 }, { 0xf0, 0x4e, 1 },
+ { 0xf1, 0x01, 1 }, { 0xf2, 0x02, 1 }, { 0xf3, 0x03, 1 },
+ { 0xf4, 0x04, 1 }, { 0xf5, 0x05, 1 }, { 0xf6, 0x06, 1 },
+ { 0xf7, 0x07, 1 }, { 0xf8, 0x08, 1 }, { 0x4e, 0x00, 2 },
+ { 0x0c, 0x04, 2 }, { 0x21, 0x61, 2 }, { 0x22, 0x68, 2 },
+ { 0x23, 0x6f, 2 }, { 0x24, 0x76, 2 }, { 0x25, 0x7d, 2 },
+ { 0x26, 0x84, 2 }, { 0x27, 0x8d, 2 }, { 0x4d, 0x08, 2 },
+ { 0x50, 0x05, 2 }, { 0x51, 0xf5, 2 }, { 0x52, 0x04, 2 },
+ { 0x53, 0xa0, 2 }, { 0x54, 0x1f, 2 }, { 0x55, 0x23, 2 },
+ { 0x56, 0x45, 2 }, { 0x57, 0x67, 2 }, { 0x58, 0x08, 2 },
+ { 0x59, 0x08, 2 }, { 0x5a, 0x08, 2 }, { 0x5b, 0x08, 2 },
+ { 0x60, 0x08, 2 }, { 0x61, 0x08, 2 }, { 0x62, 0x08, 2 },
+ { 0x63, 0x08, 2 }, { 0x64, 0xcf, 2 }, { 0x72, 0x56, 2 },
+ { 0x73, 0x9a, 2 }, { 0x34, 0xf0, 0 }, { 0x35, 0x0f, 0 },
+ { 0x5b, 0x40, 0 }, { 0x84, 0x88, 0 }, { 0x85, 0x24, 0 },
+ { 0x88, 0x54, 0 }, { 0x8b, 0xb8, 0 }, { 0x8c, 0x07, 0 },
+ { 0x8d, 0x00, 0 }, { 0x94, 0x1b, 0 }, { 0x95, 0x12, 0 },
+ { 0x96, 0x00, 0 }, { 0x97, 0x06, 0 }, { 0x9d, 0x1a, 0 },
+ { 0x9f, 0x10, 0 }, { 0xb4, 0x22, 0 }, { 0xbe, 0x80, 0 },
+ { 0xdb, 0x00, 0 }, { 0xee, 0x00, 0 }, { 0x91, 0x03, 0 },
+ { 0x4c, 0x00, 2 }, { 0x9f, 0x00, 3 }, { 0x8c, 0x01, 0 },
+ { 0x8d, 0x10, 0 }, { 0x8e, 0x08, 0 }, { 0x8f, 0x00, 0 }
+};
+#endif
+
+static usb_callback_t urtw_bulk_rx_callback;
+static usb_callback_t urtw_bulk_tx_callback;
+static usb_callback_t urtw_bulk_tx_status_callback;
+
+static const struct usb_config urtw_8187b_usbconfig[URTW_8187B_N_XFERS] = {
+ [URTW_8187B_BULK_RX] = {
+ .type = UE_BULK,
+ .endpoint = 0x83,
+ .direction = UE_DIR_IN,
+ .bufsize = MCLBYTES,
+ .flags = {
+ .ext_buffer = 1,
+ .pipe_bof = 1,
+ .short_xfer_ok = 1
+ },
+ .callback = urtw_bulk_rx_callback
+ },
+ [URTW_8187B_BULK_TX_STATUS] = {
+ .type = UE_BULK,
+ .endpoint = 0x89,
+ .direction = UE_DIR_IN,
+ .bufsize = sizeof(uint64_t),
+ .flags = {
+ .pipe_bof = 1,
+ .short_xfer_ok = 1
+ },
+ .callback = urtw_bulk_tx_status_callback
+ },
+ [URTW_8187B_BULK_TX_BE] = {
+ .type = UE_BULK,
+ .endpoint = URTW_8187B_TXPIPE_BE,
+ .direction = UE_DIR_OUT,
+ .bufsize = URTW_TX_MAXSIZE * URTW_TX_DATA_LIST_COUNT,
+ .flags = {
+ .force_short_xfer = 1,
+ .pipe_bof = 1,
+ },
+ .callback = urtw_bulk_tx_callback,
+ .timeout = URTW_DATA_TIMEOUT
+ },
+ [URTW_8187B_BULK_TX_BK] = {
+ .type = UE_BULK,
+ .endpoint = URTW_8187B_TXPIPE_BK,
+ .direction = UE_DIR_OUT,
+ .bufsize = URTW_TX_MAXSIZE,
+ .flags = {
+ .ext_buffer = 1,
+ .force_short_xfer = 1,
+ .pipe_bof = 1,
+ },
+ .callback = urtw_bulk_tx_callback,
+ .timeout = URTW_DATA_TIMEOUT
+ },
+ [URTW_8187B_BULK_TX_VI] = {
+ .type = UE_BULK,
+ .endpoint = URTW_8187B_TXPIPE_VI,
+ .direction = UE_DIR_OUT,
+ .bufsize = URTW_TX_MAXSIZE,
+ .flags = {
+ .ext_buffer = 1,
+ .force_short_xfer = 1,
+ .pipe_bof = 1,
+ },
+ .callback = urtw_bulk_tx_callback,
+ .timeout = URTW_DATA_TIMEOUT
+ },
+ [URTW_8187B_BULK_TX_VO] = {
+ .type = UE_BULK,
+ .endpoint = URTW_8187B_TXPIPE_VO,
+ .direction = UE_DIR_OUT,
+ .bufsize = URTW_TX_MAXSIZE,
+ .flags = {
+ .ext_buffer = 1,
+ .force_short_xfer = 1,
+ .pipe_bof = 1,
+ },
+ .callback = urtw_bulk_tx_callback,
+ .timeout = URTW_DATA_TIMEOUT
+ },
+ [URTW_8187B_BULK_TX_EP12] = {
+ .type = UE_BULK,
+ .endpoint = 0xc,
+ .direction = UE_DIR_OUT,
+ .bufsize = URTW_TX_MAXSIZE,
+ .flags = {
+ .ext_buffer = 1,
+ .force_short_xfer = 1,
+ .pipe_bof = 1,
+ },
+ .callback = urtw_bulk_tx_callback,
+ .timeout = URTW_DATA_TIMEOUT
+ }
+};
+
+static const struct usb_config urtw_8187l_usbconfig[URTW_8187L_N_XFERS] = {
+ [URTW_8187L_BULK_RX] = {
+ .type = UE_BULK,
+ .endpoint = 0x81,
+ .direction = UE_DIR_IN,
+ .bufsize = MCLBYTES,
+ .flags = {
+ .ext_buffer = 1,
+ .pipe_bof = 1,
+ .short_xfer_ok = 1
+ },
+ .callback = urtw_bulk_rx_callback
+ },
+ [URTW_8187L_BULK_TX_LOW] = {
+ .type = UE_BULK,
+ .endpoint = 0x2,
+ .direction = UE_DIR_OUT,
+ .bufsize = URTW_TX_MAXSIZE * URTW_TX_DATA_LIST_COUNT,
+ .flags = {
+ .force_short_xfer = 1,
+ .pipe_bof = 1,
+ },
+ .callback = urtw_bulk_tx_callback,
+ .timeout = URTW_DATA_TIMEOUT
+ },
+ [URTW_8187L_BULK_TX_NORMAL] = {
+ .type = UE_BULK,
+ .endpoint = 0x3,
+ .direction = UE_DIR_OUT,
+ .bufsize = URTW_TX_MAXSIZE,
+ .flags = {
+ .ext_buffer = 1,
+ .force_short_xfer = 1,
+ .pipe_bof = 1,
+ },
+ .callback = urtw_bulk_tx_callback,
+ .timeout = URTW_DATA_TIMEOUT
+ },
+};
+
+static struct ieee80211vap *urtw_vap_create(struct ieee80211com *,
+ const char [IFNAMSIZ], int, enum ieee80211_opmode,
+ int, const uint8_t [IEEE80211_ADDR_LEN],
+ const uint8_t [IEEE80211_ADDR_LEN]);
+static void urtw_vap_delete(struct ieee80211vap *);
+static void urtw_init(struct urtw_softc *);
+static void urtw_stop(struct urtw_softc *);
+static void urtw_parent(struct ieee80211com *);
+static int urtw_transmit(struct ieee80211com *, struct mbuf *);
+static void urtw_start(struct urtw_softc *);
+static int urtw_alloc_rx_data_list(struct urtw_softc *);
+static int urtw_alloc_tx_data_list(struct urtw_softc *);
+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_getradiocaps(struct ieee80211com *, int, int *,
+ struct ieee80211_channel[]);
+static void urtw_set_channel(struct ieee80211com *);
+static void urtw_update_promisc(struct ieee80211com *);
+static void urtw_update_mcast(struct ieee80211com *);
+static int urtw_tx_start(struct urtw_softc *,
+ struct ieee80211_node *, struct mbuf *,
+ struct urtw_data *, int);
+static int urtw_newstate(struct ieee80211vap *,
+ enum ieee80211_state, int);
+static void urtw_led_ch(void *);
+static void urtw_ledtask(void *, int);
+static void urtw_watchdog(void *);
+static void urtw_set_multi(void *);
+static int urtw_isbmode(uint16_t);
+static uint16_t urtw_rtl2rate(uint32_t);
+static usb_error_t urtw_set_rate(struct urtw_softc *);
+static usb_error_t urtw_update_msr(struct urtw_softc *);
+static usb_error_t urtw_read8_c(struct urtw_softc *, int, uint8_t *);
+static usb_error_t urtw_read16_c(struct urtw_softc *, int, uint16_t *);
+static usb_error_t urtw_read32_c(struct urtw_softc *, int, uint32_t *);
+static usb_error_t urtw_write8_c(struct urtw_softc *, int, uint8_t);
+static usb_error_t urtw_write16_c(struct urtw_softc *, int, uint16_t);
+static usb_error_t urtw_write32_c(struct urtw_softc *, int, uint32_t);
+static usb_error_t urtw_eprom_cs(struct urtw_softc *, int);
+static usb_error_t urtw_eprom_ck(struct urtw_softc *);
+static usb_error_t urtw_eprom_sendbits(struct urtw_softc *, int16_t *,
+ int);
+static usb_error_t urtw_eprom_read32(struct urtw_softc *, uint32_t,
+ uint32_t *);
+static usb_error_t urtw_eprom_readbit(struct urtw_softc *, int16_t *);
+static usb_error_t urtw_eprom_writebit(struct urtw_softc *, int16_t);
+static usb_error_t urtw_get_macaddr(struct urtw_softc *);
+static usb_error_t urtw_get_txpwr(struct urtw_softc *);
+static usb_error_t urtw_get_rfchip(struct urtw_softc *);
+static usb_error_t urtw_led_init(struct urtw_softc *);
+static usb_error_t urtw_8185_rf_pins_enable(struct urtw_softc *);
+static usb_error_t urtw_8185_tx_antenna(struct urtw_softc *, uint8_t);
+static usb_error_t urtw_8187_write_phy(struct urtw_softc *, uint8_t,
+ uint32_t);
+static usb_error_t urtw_8187_write_phy_ofdm_c(struct urtw_softc *,
+ uint8_t, uint32_t);
+static usb_error_t urtw_8187_write_phy_cck_c(struct urtw_softc *, uint8_t,
+ uint32_t);
+static usb_error_t urtw_8225_setgain(struct urtw_softc *, int16_t);
+static usb_error_t urtw_8225_usb_init(struct urtw_softc *);
+static usb_error_t urtw_8225_write_c(struct urtw_softc *, uint8_t,
+ uint16_t);
+static usb_error_t urtw_8225_write_s16(struct urtw_softc *, uint8_t, int,
+ uint16_t *);
+static usb_error_t urtw_8225_read(struct urtw_softc *, uint8_t,
+ uint32_t *);
+static usb_error_t urtw_8225_rf_init(struct urtw_softc *);
+static usb_error_t urtw_8225_rf_set_chan(struct urtw_softc *, int);
+static usb_error_t urtw_8225_rf_set_sens(struct urtw_softc *, int);
+static usb_error_t urtw_8225_set_txpwrlvl(struct urtw_softc *, int);
+static usb_error_t urtw_8225_rf_stop(struct urtw_softc *);
+static usb_error_t urtw_8225v2_rf_init(struct urtw_softc *);
+static usb_error_t urtw_8225v2_rf_set_chan(struct urtw_softc *, int);
+static usb_error_t urtw_8225v2_set_txpwrlvl(struct urtw_softc *, int);
+static usb_error_t urtw_8225v2_setgain(struct urtw_softc *, int16_t);
+static usb_error_t urtw_8225_isv2(struct urtw_softc *, int *);
+static usb_error_t urtw_8225v2b_rf_init(struct urtw_softc *);
+static usb_error_t urtw_8225v2b_rf_set_chan(struct urtw_softc *, int);
+static usb_error_t urtw_read8e(struct urtw_softc *, int, uint8_t *);
+static usb_error_t urtw_write8e(struct urtw_softc *, int, uint8_t);
+static usb_error_t urtw_8180_set_anaparam(struct urtw_softc *, uint32_t);
+static usb_error_t urtw_8185_set_anaparam2(struct urtw_softc *, uint32_t);
+static usb_error_t urtw_intr_enable(struct urtw_softc *);
+static usb_error_t urtw_intr_disable(struct urtw_softc *);
+static usb_error_t urtw_reset(struct urtw_softc *);
+static usb_error_t urtw_led_on(struct urtw_softc *, int);
+static usb_error_t urtw_led_ctl(struct urtw_softc *, int);
+static usb_error_t urtw_led_blink(struct urtw_softc *);
+static usb_error_t urtw_led_mode0(struct urtw_softc *, int);
+static usb_error_t urtw_led_mode1(struct urtw_softc *, int);
+static usb_error_t urtw_led_mode2(struct urtw_softc *, int);
+static usb_error_t urtw_led_mode3(struct urtw_softc *, int);
+static usb_error_t urtw_rx_setconf(struct urtw_softc *);
+static usb_error_t urtw_rx_enable(struct urtw_softc *);
+static usb_error_t urtw_tx_enable(struct urtw_softc *sc);
+static void urtw_free_tx_data_list(struct urtw_softc *);
+static void urtw_free_rx_data_list(struct urtw_softc *);
+static void urtw_free_data_list(struct urtw_softc *,
+ struct urtw_data data[], int, int);
+static usb_error_t urtw_set_macaddr(struct urtw_softc *, const uint8_t *);
+static usb_error_t urtw_adapter_start(struct urtw_softc *);
+static usb_error_t urtw_adapter_start_b(struct urtw_softc *);
+static usb_error_t urtw_set_mode(struct urtw_softc *, uint32_t);
+static usb_error_t urtw_8187b_cmd_reset(struct urtw_softc *);
+static usb_error_t urtw_do_request(struct urtw_softc *,
+ struct usb_device_request *, void *);
+static usb_error_t urtw_8225v2b_set_txpwrlvl(struct urtw_softc *, int);
+static usb_error_t urtw_led_off(struct urtw_softc *, int);
+static void urtw_abort_xfers(struct urtw_softc *);
+static struct urtw_data *
+ urtw_getbuf(struct urtw_softc *sc);
+static int urtw_compute_txtime(uint16_t, uint16_t, uint8_t,
+ uint8_t);
+static void urtw_updateslot(struct ieee80211com *);
+static void urtw_updateslottask(void *, int);
+static void urtw_sysctl_node(struct urtw_softc *);
+
+static int
+urtw_match(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != URTW_CONFIG_INDEX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != URTW_IFACE_INDEX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(urtw_devs, sizeof(urtw_devs), uaa));
+}
+
+static int
+urtw_attach(device_t dev)
+{
+ const struct usb_config *setup_start;
+ int ret = ENXIO;
+ struct urtw_softc *sc = device_get_softc(dev);
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint8_t iface_index = URTW_IFACE_INDEX; /* XXX */
+ uint16_t n_setup;
+ uint32_t data;
+ usb_error_t error;
+
+ device_set_usb_desc(dev);
+
+ sc->sc_dev = dev;
+ sc->sc_udev = uaa->device;
+ if (USB_GET_DRIVER_INFO(uaa) == URTW_REV_RTL8187B)
+ sc->sc_flags |= URTW_RTL8187B;
+#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);
+ usb_callout_init_mtx(&sc->sc_led_ch, &sc->sc_mtx, 0);
+ TASK_INIT(&sc->sc_led_task, 0, urtw_ledtask, sc);
+ TASK_INIT(&sc->sc_updateslot_task, 0, urtw_updateslottask, sc);
+ callout_init(&sc->sc_watchdog_ch, 0);
+ mbufq_init(&sc->sc_snd, ifqmaxlen);
+
+ if (sc->sc_flags & URTW_RTL8187B) {
+ setup_start = urtw_8187b_usbconfig;
+ n_setup = URTW_8187B_N_XFERS;
+ } else {
+ setup_start = urtw_8187l_usbconfig;
+ n_setup = URTW_8187L_N_XFERS;
+ }
+
+ error = usbd_transfer_setup(uaa->device, &iface_index, sc->sc_xfer,
+ setup_start, n_setup, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "could not allocate USB transfers, "
+ "err=%s\n", usbd_errstr(error));
+ ret = ENXIO;
+ goto fail0;
+ }
+
+ if (sc->sc_flags & URTW_RTL8187B) {
+ sc->sc_tx_dma_buf =
+ usbd_xfer_get_frame_buffer(sc->sc_xfer[
+ URTW_8187B_BULK_TX_BE], 0);
+ } else {
+ sc->sc_tx_dma_buf =
+ usbd_xfer_get_frame_buffer(sc->sc_xfer[
+ URTW_8187L_BULK_TX_LOW], 0);
+ }
+
+ URTW_LOCK(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;
+
+ URTW_UNLOCK(sc);
+
+ sc->sc_rts_retry = URTW_DEFAULT_RTS_RETRY;
+ sc->sc_tx_retry = URTW_DEFAULT_TX_RETRY;
+ sc->sc_currate = URTW_RIDX_CCK11;
+ sc->sc_preamble_mode = urtw_preamble_mode;
+
+ ic->ic_softc = sc;
+ ic->ic_name = device_get_nameunit(dev);
+ 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 */
+
+ /* XXX TODO: setup regdomain if URTW_EPROM_CHANPLAN_BY_HW bit is set.*/
+
+ urtw_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans,
+ ic->ic_channels);
+
+ 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_getradiocaps = urtw_getradiocaps;
+ ic->ic_set_channel = urtw_set_channel;
+ ic->ic_updateslot = urtw_updateslot;
+ ic->ic_vap_create = urtw_vap_create;
+ ic->ic_vap_delete = urtw_vap_delete;
+ ic->ic_update_promisc = urtw_update_promisc;
+ ic->ic_update_mcast = urtw_update_mcast;
+ ic->ic_parent = urtw_parent;
+ ic->ic_transmit = urtw_transmit;
+
+ ieee80211_radiotap_attach(ic,
+ &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap),
+ URTW_TX_RADIOTAP_PRESENT,
+ &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap),
+ URTW_RX_RADIOTAP_PRESENT);
+
+ urtw_sysctl_node(sc);
+
+ if (bootverbose)
+ ieee80211_announce(ic);
+ return (0);
+
+fail:
+ URTW_UNLOCK(sc);
+ usbd_transfer_unsetup(sc->sc_xfer, (sc->sc_flags & URTW_RTL8187B) ?
+ URTW_8187B_N_XFERS : URTW_8187L_N_XFERS);
+fail0:
+ return (ret);
+}
+
+static int
+urtw_detach(device_t dev)
+{
+ struct urtw_softc *sc = device_get_softc(dev);
+ struct ieee80211com *ic = &sc->sc_ic;
+ unsigned x;
+ unsigned n_xfers;
+
+ /* Prevent further ioctls */
+ URTW_LOCK(sc);
+ sc->sc_flags |= URTW_DETACHED;
+ urtw_stop(sc);
+ URTW_UNLOCK(sc);
+
+ ieee80211_draintask(ic, &sc->sc_updateslot_task);
+ ieee80211_draintask(ic, &sc->sc_led_task);
+
+ usb_callout_drain(&sc->sc_led_ch);
+ callout_drain(&sc->sc_watchdog_ch);
+
+ n_xfers = (sc->sc_flags & URTW_RTL8187B) ?
+ URTW_8187B_N_XFERS : URTW_8187L_N_XFERS;
+
+ /* prevent further allocations from RX/TX data lists */
+ URTW_LOCK(sc);
+ STAILQ_INIT(&sc->sc_tx_active);
+ STAILQ_INIT(&sc->sc_tx_inactive);
+ STAILQ_INIT(&sc->sc_tx_pending);
+
+ STAILQ_INIT(&sc->sc_rx_active);
+ STAILQ_INIT(&sc->sc_rx_inactive);
+ URTW_UNLOCK(sc);
+
+ /* drain USB transfers */
+ for (x = 0; x != n_xfers; x++)
+ usbd_transfer_drain(sc->sc_xfer[x]);
+
+ /* free data buffers */
+ URTW_LOCK(sc);
+ urtw_free_tx_data_list(sc);
+ urtw_free_rx_data_list(sc);
+ URTW_UNLOCK(sc);
+
+ /* free USB transfers and some data buffers */
+ usbd_transfer_unsetup(sc->sc_xfer, n_xfers);
+
+ ieee80211_ifdetach(ic);
+ mbufq_drain(&sc->sc_snd);
+ mtx_destroy(&sc->sc_mtx);
+ return (0);
+}
+
+static void
+urtw_free_tx_data_list(struct urtw_softc *sc)
+{
+ urtw_free_data_list(sc, sc->sc_tx, URTW_TX_DATA_LIST_COUNT, 0);
+}
+
+static void
+urtw_free_rx_data_list(struct urtw_softc *sc)
+{
+ urtw_free_data_list(sc, sc->sc_rx, URTW_RX_DATA_LIST_COUNT, 1);
+}
+
+static void
+urtw_free_data_list(struct urtw_softc *sc, struct urtw_data data[], int ndata,
+ int fillmbuf)
+{
+ int i;
+
+ for (i = 0; i < ndata; i++) {
+ struct urtw_data *dp = &data[i];
+
+ if (fillmbuf == 1) {
+ if (dp->m != NULL) {
+ m_freem(dp->m);
+ dp->m = NULL;
+ dp->buf = NULL;
+ }
+ } else {
+ dp->buf = NULL;
+ }
+ if (dp->ni != NULL) {
+ ieee80211_free_node(dp->ni);
+ dp->ni = NULL;
+ }
+ }
+}
+
+static struct ieee80211vap *
+urtw_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
+ enum ieee80211_opmode 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 = malloc(sizeof(struct urtw_vap), M_80211_VAP, M_WAITOK | M_ZERO);
+ vap = &uvp->vap;
+ /* enable s/w bmiss handling for sta mode */
+
+ if (ieee80211_vap_setup(ic, vap, name, unit, opmode,
+ flags | IEEE80211_CLONE_NOBEACONS, bssid) != 0) {
+ /* out of memory */
+ free(uvp, M_80211_VAP);
+ return (NULL);
+ }
+
+ /* 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, mac);
+ 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 void
+urtw_init(struct urtw_softc *sc)
+{
+ usb_error_t error;
+ int ret;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ if (sc->sc_flags & URTW_RUNNING)
+ urtw_stop(sc);
+
+ error = (sc->sc_flags & URTW_RTL8187B) ? urtw_adapter_start_b(sc) :
+ urtw_adapter_start(sc);
+ if (error != 0)
+ goto fail;
+
+ /* reset softc variables */
+ sc->sc_txtimer = 0;
+
+ if (!(sc->sc_flags & URTW_INIT_ONCE)) {
+ ret = urtw_alloc_rx_data_list(sc);
+ if (ret != 0)
+ goto fail;
+ ret = urtw_alloc_tx_data_list(sc);
+ if (ret != 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;
+
+ if (sc->sc_flags & URTW_RTL8187B)
+ usbd_transfer_start(sc->sc_xfer[URTW_8187B_BULK_TX_STATUS]);
+
+ sc->sc_flags |= URTW_RUNNING;
+
+ callout_reset(&sc->sc_watchdog_ch, hz, urtw_watchdog, sc);
+fail:
+ return;
+}
+
+static usb_error_t
+urtw_adapter_start_b(struct urtw_softc *sc)
+{
+ uint8_t data8;
+ usb_error_t error;
+
+ error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG);
+ if (error)
+ goto fail;
+
+ urtw_read8_m(sc, URTW_CONFIG3, &data8);
+ urtw_write8_m(sc, URTW_CONFIG3,
+ data8 | URTW_CONFIG3_ANAPARAM_WRITE | URTW_CONFIG3_GNT_SELECT);
+ urtw_write32_m(sc, URTW_ANAPARAM2, URTW_8187B_8225_ANAPARAM2_ON);
+ urtw_write32_m(sc, URTW_ANAPARAM, URTW_8187B_8225_ANAPARAM_ON);
+ urtw_write8_m(sc, URTW_ANAPARAM3, URTW_8187B_8225_ANAPARAM3_ON);
+
+ urtw_write8_m(sc, 0x61, 0x10);
+ urtw_read8_m(sc, 0x62, &data8);
+ urtw_write8_m(sc, 0x62, data8 & ~(1 << 5));
+ urtw_write8_m(sc, 0x62, data8 | (1 << 5));
+
+ urtw_read8_m(sc, URTW_CONFIG3, &data8);
+ data8 &= ~URTW_CONFIG3_ANAPARAM_WRITE;
+ urtw_write8_m(sc, URTW_CONFIG3, data8);
+
+ error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL);
+ if (error)
+ goto fail;
+
+ error = urtw_8187b_cmd_reset(sc);
+ if (error)
+ goto fail;
+
+ error = sc->sc_rf_init(sc);
+ if (error != 0)
+ goto fail;
+ urtw_write8_m(sc, URTW_CMD, URTW_CMD_RX_ENABLE | URTW_CMD_TX_ENABLE);
+
+ /* fix RTL8187B RX stall */
+ error = urtw_intr_enable(sc);
+ if (error)
+ goto fail;
+
+ error = urtw_write8e(sc, 0x41, 0xf4);
+ if (error)
+ goto fail;
+ error = urtw_write8e(sc, 0x40, 0x00);
+ if (error)
+ goto fail;
+ error = urtw_write8e(sc, 0x42, 0x00);
+ if (error)
+ goto fail;
+ error = urtw_write8e(sc, 0x42, 0x01);
+ if (error)
+ goto fail;
+ error = urtw_write8e(sc, 0x40, 0x0f);
+ if (error)
+ goto fail;
+ error = urtw_write8e(sc, 0x42, 0x00);
+ if (error)
+ goto fail;
+ error = urtw_write8e(sc, 0x42, 0x01);
+ if (error)
+ goto fail;
+
+ urtw_read8_m(sc, 0xdb, &data8);
+ urtw_write8_m(sc, 0xdb, data8 | (1 << 2));
+ urtw_write16_m(sc, 0x372, 0x59fa);
+ urtw_write16_m(sc, 0x374, 0x59d2);
+ urtw_write16_m(sc, 0x376, 0x59d2);
+ urtw_write16_m(sc, 0x378, 0x19fa);
+ urtw_write16_m(sc, 0x37a, 0x19fa);
+ urtw_write16_m(sc, 0x37c, 0x00d0);
+ urtw_write8_m(sc, 0x61, 0);
+
+ urtw_write8_m(sc, 0x180, 0x0f);
+ urtw_write8_m(sc, 0x183, 0x03);
+ urtw_write8_m(sc, 0xda, 0x10);
+ urtw_write8_m(sc, 0x24d, 0x08);
+ urtw_write32_m(sc, URTW_HSSI_PARA, 0x0600321b);
+
+ urtw_write16_m(sc, 0x1ec, 0x800); /* RX MAX SIZE */
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_set_macaddr(struct urtw_softc *sc, const uint8_t *macaddr)
+{
+ usb_error_t error;
+
+ urtw_write32_m(sc, URTW_MAC0, ((const uint32_t *)macaddr)[0]);
+ urtw_write16_m(sc, URTW_MAC4, ((const uint32_t *)macaddr)[1] & 0xffff);
+
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_adapter_start(struct urtw_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ const uint8_t *macaddr;
+ usb_error_t 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. */
+ macaddr = vap ? vap->iv_myaddr : ic->ic_macaddr;
+ urtw_set_macaddr(sc, macaddr);
+ if (error)
+ goto fail;
+
+ 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, URTW_RATE_FALLBACK_ENABLE | 0x1);
+ 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 usb_error_t
+urtw_set_mode(struct urtw_softc *sc, uint32_t mode)
+{
+ uint8_t data;
+ usb_error_t 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 void
+urtw_pause_ms(struct urtw_softc *sc, int delay)
+{
+ usb_pause_mtx(&sc->sc_mtx, USB_MS_TO_TICKS(delay));
+}
+
+static usb_error_t
+urtw_8187b_cmd_reset(struct urtw_softc *sc)
+{
+ int i;
+ uint8_t data8;
+ usb_error_t error;
+
+ /* XXX the code can be duplicate with urtw_reset(). */
+ urtw_read8_m(sc, URTW_CMD, &data8);
+ data8 = (data8 & 0x2) | URTW_CMD_RST;
+ urtw_write8_m(sc, URTW_CMD, data8);
+
+ for (i = 0; i < 20; i++) {
+ urtw_pause_ms(sc, 2);
+ urtw_read8_m(sc, URTW_CMD, &data8);
+ if (!(data8 & URTW_CMD_RST))
+ break;
+ }
+ if (i >= 20) {
+ device_printf(sc->sc_dev, "reset timeout\n");
+ goto fail;
+ }
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_do_request(struct urtw_softc *sc,
+ struct usb_device_request *req, void *data)
+{
+ usb_error_t err;
+ int ntries = 10;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ while (ntries--) {
+ err = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx,
+ req, data, 0, NULL, 250 /* ms */);
+ if (err == 0)
+ break;
+
+ DPRINTF(sc, URTW_DEBUG_INIT,
+ "Control request failed, %s (retrying)\n",
+ usbd_errstr(err));
+ urtw_pause_ms(sc, 10);
+ }
+ return (err);
+}
+
+static void
+urtw_stop(struct urtw_softc *sc)
+{
+ uint8_t data8;
+ usb_error_t error;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ sc->sc_flags &= ~URTW_RUNNING;
+
+ error = urtw_intr_disable(sc);
+ if (error)
+ goto fail;
+ urtw_read8_m(sc, URTW_CMD, &data8);
+ data8 &= ~(URTW_CMD_RX_ENABLE | URTW_CMD_TX_ENABLE);
+ urtw_write8_m(sc, URTW_CMD, data8);
+
+ error = sc->sc_rf_stop(sc);
+ if (error != 0)
+ goto fail;
+
+ error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG);
+ if (error)
+ goto fail;
+ urtw_read8_m(sc, URTW_CONFIG4, &data8);
+ urtw_write8_m(sc, URTW_CONFIG4, data8 | URTW_CONFIG4_VCOOFF);
+ error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL);
+ if (error)
+ goto fail;
+fail:
+ if (error)
+ device_printf(sc->sc_dev, "failed to stop (%s)\n",
+ usbd_errstr(error));
+
+ usb_callout_stop(&sc->sc_led_ch);
+ callout_stop(&sc->sc_watchdog_ch);
+
+ urtw_abort_xfers(sc);
+}
+
+static void
+urtw_abort_xfers(struct urtw_softc *sc)
+{
+ int i, max;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ max = (sc->sc_flags & URTW_RTL8187B) ? URTW_8187B_N_XFERS :
+ URTW_8187L_N_XFERS;
+
+ /* abort any pending transfers */
+ for (i = 0; i < max; i++)
+ usbd_transfer_stop(sc->sc_xfer[i]);
+}
+
+static void
+urtw_parent(struct ieee80211com *ic)
+{
+ struct urtw_softc *sc = ic->ic_softc;
+ int startall = 0;
+
+ URTW_LOCK(sc);
+ if (sc->sc_flags & URTW_DETACHED) {
+ URTW_UNLOCK(sc);
+ return;
+ }
+
+ if (ic->ic_nrunning > 0) {
+ if (sc->sc_flags & URTW_RUNNING) {
+ if (ic->ic_promisc > 0 || ic->ic_allmulti > 0)
+ urtw_set_multi(sc);
+ } else {
+ urtw_init(sc);
+ startall = 1;
+ }
+ } else if (sc->sc_flags & URTW_RUNNING)
+ urtw_stop(sc);
+ URTW_UNLOCK(sc);
+ if (startall)
+ ieee80211_start_all(ic);
+}
+
+static int
+urtw_transmit(struct ieee80211com *ic, struct mbuf *m)
+{
+ struct urtw_softc *sc = ic->ic_softc;
+ int error;
+
+ URTW_LOCK(sc);
+ if ((sc->sc_flags & URTW_RUNNING) == 0) {
+ URTW_UNLOCK(sc);
+ return (ENXIO);
+ }
+ error = mbufq_enqueue(&sc->sc_snd, m);
+ if (error) {
+ URTW_UNLOCK(sc);
+ return (error);
+ }
+ urtw_start(sc);
+ URTW_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+urtw_start(struct urtw_softc *sc)
+{
+ struct urtw_data *bf;
+ struct ieee80211_node *ni;
+ struct mbuf *m;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ if ((sc->sc_flags & URTW_RUNNING) == 0)
+ return;
+
+ while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
+ bf = urtw_getbuf(sc);
+ if (bf == NULL) {
+ mbufq_prepend(&sc->sc_snd, m);
+ break;
+ }
+
+ ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
+ m->m_pkthdr.rcvif = NULL;
+
+ if (urtw_tx_start(sc, ni, m, bf, URTW_PRIORITY_NORMAL) != 0) {
+ if_inc_counter(ni->ni_vap->iv_ifp,
+ IFCOUNTER_OERRORS, 1);
+ STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next);
+ ieee80211_free_node(ni);
+ break;
+ }
+
+ sc->sc_txtimer = 5;
+ callout_reset(&sc->sc_watchdog_ch, hz, urtw_watchdog, sc);
+ }
+}
+
+static int
+urtw_alloc_data_list(struct urtw_softc *sc, struct urtw_data data[],
+ int ndata, int maxsz, void *dma_buf)
+{
+ int i, error;
+
+ for (i = 0; i < ndata; i++) {
+ struct urtw_data *dp = &data[i];
+
+ dp->sc = sc;
+ if (dma_buf == NULL) {
+ dp->m = m_getcl(M_NOWAIT, 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 = ((uint8_t *)dma_buf) +
+ (i * maxsz);
+ }
+ dp->ni = NULL;
+ }
+ return (0);
+
+fail: urtw_free_data_list(sc, data, ndata, 1);
+ return (error);
+}
+
+static int
+urtw_alloc_rx_data_list(struct urtw_softc *sc)
+{
+ int error, i;
+
+ error = urtw_alloc_data_list(sc,
+ sc->sc_rx, URTW_RX_DATA_LIST_COUNT,
+ MCLBYTES, NULL /* mbufs */);
+ if (error != 0)
+ return (error);
+
+ STAILQ_INIT(&sc->sc_rx_active);
+ STAILQ_INIT(&sc->sc_rx_inactive);
+
+ for (i = 0; i < URTW_RX_DATA_LIST_COUNT; i++)
+ STAILQ_INSERT_HEAD(&sc->sc_rx_inactive, &sc->sc_rx[i], next);
+
+ return (0);
+}
+
+static int
+urtw_alloc_tx_data_list(struct urtw_softc *sc)
+{
+ int error, i;
+
+ error = urtw_alloc_data_list(sc,
+ sc->sc_tx, URTW_TX_DATA_LIST_COUNT, URTW_TX_MAXSIZE,
+ sc->sc_tx_dma_buf /* no mbufs */);
+ if (error != 0)
+ return (error);
+
+ STAILQ_INIT(&sc->sc_tx_active);
+ STAILQ_INIT(&sc->sc_tx_inactive);
+ STAILQ_INIT(&sc->sc_tx_pending);
+
+ for (i = 0; i < URTW_TX_DATA_LIST_COUNT; i++)
+ STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, &sc->sc_tx[i],
+ next);
+
+ 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 urtw_softc *sc = ic->ic_softc;
+ struct urtw_data *bf;
+
+ /* prevent management frames from being sent if we're not ready */
+ if (!(sc->sc_flags & URTW_RUNNING)) {
+ m_freem(m);
+ return ENETDOWN;
+ }
+ URTW_LOCK(sc);
+ bf = urtw_getbuf(sc);
+ if (bf == NULL) {
+ m_freem(m);
+ URTW_UNLOCK(sc);
+ return (ENOBUFS); /* XXX */
+ }
+
+ if (urtw_tx_start(sc, ni, m, bf, URTW_PRIORITY_LOW) != 0) {
+ STAILQ_INSERT_HEAD(&sc->sc_tx_inactive, bf, next);
+ URTW_UNLOCK(sc);
+ return (EIO);
+ }
+ URTW_UNLOCK(sc);
+
+ sc->sc_txtimer = 5;
+ 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_getradiocaps(struct ieee80211com *ic,
+ int maxchans, int *nchans, struct ieee80211_channel chans[])
+{
+ uint8_t bands[IEEE80211_MODE_BYTES];
+
+ memset(bands, 0, sizeof(bands));
+ setbit(bands, IEEE80211_MODE_11B);
+ setbit(bands, IEEE80211_MODE_11G);
+ ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0);
+}
+
+static void
+urtw_set_channel(struct ieee80211com *ic)
+{
+ struct urtw_softc *sc = ic->ic_softc;
+ uint32_t data, orig;
+ usb_error_t error;
+
+ /*
+ * 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 (!(sc->sc_flags & URTW_RUNNING))
+ return;
+
+ if (sc->sc_curchan != NULL && sc->sc_curchan == ic->ic_curchan)
+ return;
+
+ URTW_LOCK(sc);
+
+ /*
+ * during changing th channel we need to temporarily be disable
+ * TX.
+ */
+ urtw_read32_m(sc, URTW_TX_CONF, &orig);
+ data = orig & ~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;
+ urtw_pause_ms(sc, 10);
+ urtw_write32_m(sc, URTW_TX_CONF, orig);
+
+ urtw_write16_m(sc, URTW_ATIM_WND, 2);
+ urtw_write16_m(sc, URTW_ATIM_TR_ITV, 100);
+ urtw_write16_m(sc, URTW_BEACON_INTERVAL, 100);
+ urtw_write16_m(sc, URTW_BEACON_INTERVAL_TIME, 100);
+
+fail:
+ URTW_UNLOCK(sc);
+
+ sc->sc_curchan = ic->ic_curchan;
+
+ if (error != 0)
+ device_printf(sc->sc_dev, "could not change the channel\n");
+}
+
+static void
+urtw_update_promisc(struct ieee80211com *ic)
+{
+ struct urtw_softc *sc = ic->ic_softc;
+
+ URTW_LOCK(sc);
+ if (sc->sc_flags & URTW_RUNNING)
+ urtw_rx_setconf(sc);
+ URTW_UNLOCK(sc);
+}
+
+static void
+urtw_update_mcast(struct ieee80211com *ic)
+{
+
+ /* XXX do nothing? */
+}
+
+static int
+urtw_tx_start(struct urtw_softc *sc, struct ieee80211_node *ni, struct mbuf *m0,
+ struct urtw_data *data, int prior)
+{
+ struct ieee80211_frame *wh = mtod(m0, struct ieee80211_frame *);
+ struct ieee80211_key *k;
+ const struct ieee80211_txparam *tp = ni->ni_txparms;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct usb_xfer *rtl8187b_pipes[URTW_8187B_TXPIPE_MAX] = {
+ sc->sc_xfer[URTW_8187B_BULK_TX_BE],
+ sc->sc_xfer[URTW_8187B_BULK_TX_BK],
+ sc->sc_xfer[URTW_8187B_BULK_TX_VI],
+ sc->sc_xfer[URTW_8187B_BULK_TX_VO]
+ };
+ struct usb_xfer *xfer;
+ int dur = 0, rtsdur = 0, rtsenable = 0, ctsenable = 0, rate, type,
+ pkttime = 0, txdur = 0, isshort = 0, xferlen, ismcast;
+ uint16_t acktime, rtstime, ctstime;
+ uint32_t flags;
+ usb_error_t error;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1);
+ type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
+
+ /*
+ * Software crypto.
+ */
+ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
+ 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 (ieee80211_radiotap_active_vap(vap)) {
+ struct urtw_tx_radiotap_header *tap = &sc->sc_txtap;
+
+ tap->wt_flags = 0;
+ ieee80211_radiotap_tx(vap, m0);
+ }
+
+ if (IEEE80211_IS_MGMT(wh) || IEEE80211_IS_CTL(wh) ||
+ (m0->m_flags & M_EAPOL) != 0) {
+ rate = tp->mgmtrate;
+ } else {
+ /* for data frames */
+ if (ismcast)
+ rate = tp->mcastrate;
+ else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE)
+ rate = tp->ucastrate;
+ else
+ rate = urtw_rtl2rate(sc->sc_currate);
+ }
+
+ sc->sc_stats.txrates[sc->sc_currate]++;
+
+ if (ismcast)
+ txdur = pkttime = urtw_compute_txtime(m0->m_pkthdr.len +
+ IEEE80211_CRC_LEN, rate, 0, 0);
+ else {
+ acktime = urtw_compute_txtime(14, 2,0, 0);
+ if ((m0->m_pkthdr.len + 4) > vap->iv_rtsthreshold) {
+ rtsenable = 1;
+ ctsenable = 0;
+ rtstime = urtw_compute_txtime(URTW_ACKCTS_LEN, 2, 0, 0);
+ ctstime = urtw_compute_txtime(14, 2, 0, 0);
+ pkttime = urtw_compute_txtime(m0->m_pkthdr.len +
+ IEEE80211_CRC_LEN, rate, 0, isshort);
+ rtsdur = ctstime + pkttime + acktime +
+ 3 * URTW_ASIFS_TIME;
+ txdur = rtstime + rtsdur;
+ } else {
+ rtsenable = ctsenable = rtsdur = 0;
+ pkttime = urtw_compute_txtime(m0->m_pkthdr.len +
+ IEEE80211_CRC_LEN, rate, 0, isshort);
+ txdur = pkttime + URTW_ASIFS_TIME + acktime;
+ }
+
+ if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG)
+ dur = urtw_compute_txtime(m0->m_pkthdr.len +
+ IEEE80211_CRC_LEN, rate, 0, isshort) +
+ 3 * URTW_ASIFS_TIME +
+ 2 * acktime;
+ else
+ dur = URTW_ASIFS_TIME + acktime;
+ }
+ USETW(wh->i_dur, dur);
+
+ xferlen = m0->m_pkthdr.len;
+ xferlen += (sc->sc_flags & URTW_RTL8187B) ? (4 * 8) : (4 * 3);
+ if ((0 == xferlen % 64) || (0 == xferlen % 512))
+ xferlen += 1;
+
+ memset(data->buf, 0, URTW_TX_MAXSIZE);
+ flags = m0->m_pkthdr.len & 0xfff;
+ flags |= URTW_TX_FLAG_NO_ENC;
+ 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))
+ flags |= URTW_TX_FLAG_SPLCP;
+ if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG)
+ flags |= URTW_TX_FLAG_MOREFRAG;
+
+ flags |= (sc->sc_currate & 0xf) << URTW_TX_FLAG_TXRATE_SHIFT;
+
+ if (sc->sc_flags & URTW_RTL8187B) {
+ struct urtw_8187b_txhdr *tx;
+
+ tx = (struct urtw_8187b_txhdr *)data->buf;
+ if (ctsenable)
+ flags |= URTW_TX_FLAG_CTS;
+ if (rtsenable) {
+ flags |= URTW_TX_FLAG_RTS;
+ flags |= URTW_RIDX_CCK5 << URTW_TX_FLAG_RTSRATE_SHIFT;
+ tx->rtsdur = rtsdur;
+ }
+ tx->flag = htole32(flags);
+ tx->txdur = txdur;
+ if (IEEE80211_IS_MGMT_PROBE_RESP(wh))
+ tx->retry = 1;
+ else
+ tx->retry = URTW_TX_MAXRETRY;
+ m_copydata(m0, 0, m0->m_pkthdr.len, (uint8_t *)(tx + 1));
+ } else {
+ struct urtw_8187l_txhdr *tx;
+
+ tx = (struct urtw_8187l_txhdr *)data->buf;
+ if (rtsenable) {
+ flags |= URTW_TX_FLAG_RTS;
+ tx->rtsdur = rtsdur;
+ }
+ flags |= URTW_RIDX_CCK5 << URTW_TX_FLAG_RTSRATE_SHIFT;
+ tx->flag = htole32(flags);
+ tx->retry = 3; /* CW minimum */
+ tx->retry |= 7 << 4; /* CW maximum */
+ tx->retry |= URTW_TX_MAXRETRY << 8; /* retry limitation */
+ m_copydata(m0, 0, m0->m_pkthdr.len, (uint8_t *)(tx + 1));
+ }
+
+ data->buflen = xferlen;
+ data->ni = ni;
+ data->m = m0;
+
+ if (sc->sc_flags & URTW_RTL8187B) {
+ switch (type) {
+ case IEEE80211_FC0_TYPE_CTL:
+ case IEEE80211_FC0_TYPE_MGT:
+ xfer = sc->sc_xfer[URTW_8187B_BULK_TX_EP12];
+ break;
+ default:
+ KASSERT(M_WME_GETAC(m0) < URTW_8187B_TXPIPE_MAX,
+ ("unsupported WME pipe %d", M_WME_GETAC(m0)));
+ xfer = rtl8187b_pipes[M_WME_GETAC(m0)];
+ break;
+ }
+ } else
+ xfer = (prior == URTW_PRIORITY_LOW) ?
+ sc->sc_xfer[URTW_8187L_BULK_TX_LOW] :
+ sc->sc_xfer[URTW_8187L_BULK_TX_NORMAL];
+
+ STAILQ_INSERT_TAIL(&sc->sc_tx_pending, data, next);
+ usbd_transfer_start(xfer);
+
+ error = urtw_led_ctl(sc, URTW_LED_CTL_TX);
+ if (error != 0)
+ device_printf(sc->sc_dev, "could not control LED (%d)\n",
+ error);
+ return (0);
+}
+
+static int
+urtw_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
+{
+ struct ieee80211com *ic = vap->iv_ic;
+ struct urtw_softc *sc = ic->ic_softc;
+ struct urtw_vap *uvp = URTW_VAP(vap);
+ struct ieee80211_node *ni;
+ usb_error_t error = 0;
+
+ DPRINTF(sc, URTW_DEBUG_STATE, "%s: %s -> %s\n", __func__,
+ ieee80211_state_name[vap->iv_state],
+ ieee80211_state_name[nstate]);
+
+ sc->sc_state = nstate;
+
+ IEEE80211_UNLOCK(ic);
+ URTW_LOCK(sc);
+ usb_callout_stop(&sc->sc_led_ch);
+ callout_stop(&sc->sc_watchdog_ch);
+
+ switch (nstate) {
+ case IEEE80211_S_INIT:
+ case IEEE80211_S_SCAN:
+ case IEEE80211_S_AUTH:
+ case IEEE80211_S_ASSOC:
+ break;
+ case IEEE80211_S_RUN:
+ ni = ieee80211_ref_node(vap->iv_bss);
+ /* 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);
+ ieee80211_free_node(ni);
+ break;
+ default:
+ break;
+ }
+fail:
+ URTW_UNLOCK(sc);
+ IEEE80211_LOCK(ic);
+ return (uvp->newstate(vap, nstate, arg));
+}
+
+static void
+urtw_watchdog(void *arg)
+{
+ struct urtw_softc *sc = arg;
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ if (sc->sc_txtimer > 0) {
+ if (--sc->sc_txtimer == 0) {
+ device_printf(sc->sc_dev, "device timeout\n");
+ counter_u64_add(ic->ic_oerrors, 1);
+ ieee80211_restart_all(ic);
+ return;
+ }
+ callout_reset(&sc->sc_watchdog_ch, hz, urtw_watchdog, sc);
+ }
+}
+
+static void
+urtw_set_multi(void *arg)
+{
+ /* XXX don't know how to set a device. Lack of docs. */
+}
+
+static usb_error_t
+urtw_set_rate(struct urtw_softc *sc)
+{
+ int i, basic_rate, min_rr_rate, max_rr_rate;
+ uint16_t data;
+ usb_error_t error;
+
+ basic_rate = URTW_RIDX_OFDM24;
+ min_rr_rate = URTW_RIDX_OFDM6;
+ max_rr_rate = URTW_RIDX_OFDM24;
+
+ 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 uint16_t
+urtw_rtl2rate(uint32_t rate)
+{
+ unsigned i;
+
+ for (i = 0; i < nitems(urtw_ratetable); i++) {
+ if (rate == urtw_ratetable[i].val)
+ return urtw_ratetable[i].reg;
+ }
+
+ return (0);
+}
+
+static usb_error_t
+urtw_update_msr(struct urtw_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint8_t data;
+ usb_error_t 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;
+ if (sc->sc_flags & URTW_RTL8187B)
+ data |= URTW_MSR_LINK_ENEDCA;
+ break;
+ case IEEE80211_M_IBSS:
+ data |= URTW_MSR_LINK_ADHOC;
+ break;
+ case IEEE80211_M_HOSTAP:
+ data |= URTW_MSR_LINK_HOSTAP;
+ break;
+ default:
+ DPRINTF(sc, URTW_DEBUG_STATE,
+ "unsupported operation mode 0x%x\n",
+ ic->ic_opmode);
+ error = USB_ERR_INVAL;
+ goto fail;
+ }
+ } else
+ data |= URTW_MSR_LINK_NONE;
+
+ urtw_write8_m(sc, URTW_MSR, data);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_read8_c(struct urtw_softc *sc, int val, uint8_t *data)
+{
+ struct usb_device_request req;
+ usb_error_t error;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = URTW_8187_GETREGS_REQ;
+ USETW(req.wValue, (val & 0xff) | 0xff00);
+ USETW(req.wIndex, (val >> 8) & 0x3);
+ USETW(req.wLength, sizeof(uint8_t));
+
+ error = urtw_do_request(sc, &req, data);
+ return (error);
+}
+
+static usb_error_t
+urtw_read16_c(struct urtw_softc *sc, int val, uint16_t *data)
+{
+ struct usb_device_request req;
+ usb_error_t error;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = URTW_8187_GETREGS_REQ;
+ USETW(req.wValue, (val & 0xff) | 0xff00);
+ USETW(req.wIndex, (val >> 8) & 0x3);
+ USETW(req.wLength, sizeof(uint16_t));
+
+ error = urtw_do_request(sc, &req, data);
+ return (error);
+}
+
+static usb_error_t
+urtw_read32_c(struct urtw_softc *sc, int val, uint32_t *data)
+{
+ struct usb_device_request req;
+ usb_error_t error;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ req.bmRequestType = UT_READ_VENDOR_DEVICE;
+ req.bRequest = URTW_8187_GETREGS_REQ;
+ USETW(req.wValue, (val & 0xff) | 0xff00);
+ USETW(req.wIndex, (val >> 8) & 0x3);
+ USETW(req.wLength, sizeof(uint32_t));
+
+ error = urtw_do_request(sc, &req, data);
+ return (error);
+}
+
+static usb_error_t
+urtw_write8_c(struct urtw_softc *sc, int val, uint8_t data)
+{
+ struct usb_device_request req;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = URTW_8187_SETREGS_REQ;
+ USETW(req.wValue, (val & 0xff) | 0xff00);
+ USETW(req.wIndex, (val >> 8) & 0x3);
+ USETW(req.wLength, sizeof(uint8_t));
+
+ return (urtw_do_request(sc, &req, &data));
+}
+
+static usb_error_t
+urtw_write16_c(struct urtw_softc *sc, int val, uint16_t data)
+{
+ struct usb_device_request req;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = URTW_8187_SETREGS_REQ;
+ USETW(req.wValue, (val & 0xff) | 0xff00);
+ USETW(req.wIndex, (val >> 8) & 0x3);
+ USETW(req.wLength, sizeof(uint16_t));
+
+ return (urtw_do_request(sc, &req, &data));
+}
+
+static usb_error_t
+urtw_write32_c(struct urtw_softc *sc, int val, uint32_t data)
+{
+ struct usb_device_request req;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+ req.bRequest = URTW_8187_SETREGS_REQ;
+ USETW(req.wValue, (val & 0xff) | 0xff00);
+ USETW(req.wIndex, (val >> 8) & 0x3);
+ USETW(req.wLength, sizeof(uint32_t));
+
+ return (urtw_do_request(sc, &req, &data));
+}
+
+static usb_error_t
+urtw_get_macaddr(struct urtw_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t data;
+ usb_error_t error;
+
+ error = urtw_eprom_read32(sc, URTW_EPROM_MACADDR, &data);
+ if (error != 0)
+ goto fail;
+ ic->ic_macaddr[0] = data & 0xff;
+ ic->ic_macaddr[1] = (data & 0xff00) >> 8;
+ error = urtw_eprom_read32(sc, URTW_EPROM_MACADDR + 1, &data);
+ if (error != 0)
+ goto fail;
+ ic->ic_macaddr[2] = data & 0xff;
+ ic->ic_macaddr[3] = (data & 0xff00) >> 8;
+ error = urtw_eprom_read32(sc, URTW_EPROM_MACADDR + 2, &data);
+ if (error != 0)
+ goto fail;
+ ic->ic_macaddr[4] = data & 0xff;
+ ic->ic_macaddr[5] = (data & 0xff00) >> 8;
+fail:
+ return (error);
+}
+
+static usb_error_t
+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 };
+ usb_error_t 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 usb_error_t
+urtw_eprom_cs(struct urtw_softc *sc, int able)
+{
+ uint8_t data;
+ usb_error_t 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 usb_error_t
+urtw_eprom_ck(struct urtw_softc *sc)
+{
+ uint8_t data;
+ usb_error_t 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 usb_error_t
+urtw_eprom_readbit(struct urtw_softc *sc, int16_t *data)
+{
+ uint8_t data8;
+ usb_error_t error;
+
+ urtw_read8_m(sc, URTW_EPROM_CMD, &data8);
+ *data = (data8 & URTW_EPROM_READBIT) ? 1 : 0;
+ DELAY(URTW_EPROM_DELAY);
+
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_eprom_writebit(struct urtw_softc *sc, int16_t bit)
+{
+ uint8_t data;
+ usb_error_t 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 usb_error_t
+urtw_eprom_sendbits(struct urtw_softc *sc, int16_t *buf, int buflen)
+{
+ int i = 0;
+ usb_error_t 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 usb_error_t
+urtw_get_txpwr(struct urtw_softc *sc)
+{
+ int i, j;
+ uint32_t data;
+ usb_error_t 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;
+ }
+ if (sc->sc_flags & URTW_RTL8187B) {
+ error = urtw_eprom_read32(sc, URTW_EPROM_TXPW2, &data);
+ if (error != 0)
+ goto fail;
+ sc->sc_txpwr_cck[1 + 6 + 4] = data & 0xf;
+ sc->sc_txpwr_ofdm[1 + 6 + 4] = (data & 0xf0) >> 4;
+ error = urtw_eprom_read32(sc, 0x0a, &data);
+ if (error != 0)
+ goto fail;
+ sc->sc_txpwr_cck[2 + 6 + 4] = data & 0xf;
+ sc->sc_txpwr_ofdm[2 + 6 + 4] = (data & 0xf0) >> 4;
+ error = urtw_eprom_read32(sc, 0x1c, &data);
+ if (error != 0)
+ goto fail;
+ sc->sc_txpwr_cck[3 + 6 + 4] = data & 0xf;
+ sc->sc_txpwr_cck[3 + 6 + 4 + 1] = (data & 0xf00) >> 8;
+ sc->sc_txpwr_ofdm[3 + 6 + 4] = (data & 0xf0) >> 4;
+ sc->sc_txpwr_ofdm[3 + 6 + 4 + 1] = (data & 0xf000) >> 12;
+ } else {
+ 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 usb_error_t
+urtw_get_rfchip(struct urtw_softc *sc)
+{
+ int ret;
+ uint8_t data8;
+ uint32_t data;
+ usb_error_t error;
+
+ if (sc->sc_flags & URTW_RTL8187B) {
+ urtw_read8_m(sc, 0xe1, &data8);
+ switch (data8) {
+ case 0:
+ sc->sc_flags |= URTW_RTL8187B_REV_B;
+ break;
+ case 1:
+ sc->sc_flags |= URTW_RTL8187B_REV_D;
+ break;
+ case 2:
+ sc->sc_flags |= URTW_RTL8187B_REV_E;
+ break;
+ default:
+ device_printf(sc->sc_dev, "unknown type: %#x\n", data8);
+ sc->sc_flags |= URTW_RTL8187B_REV_B;
+ break;
+ }
+ } else {
+ urtw_read32_m(sc, URTW_TX_CONF, &data);
+ switch (data & URTW_TX_HWMASK) {
+ case URTW_TX_R8187vD_B:
+ sc->sc_flags |= URTW_RTL8187B;
+ break;
+ case URTW_TX_R8187vD:
+ break;
+ default:
+ device_printf(sc->sc_dev, "unknown RTL8187L type: %#x\n",
+ data & URTW_TX_HWMASK);
+ break;
+ }
+ }
+
+ 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;
+ sc->sc_rf_stop = urtw_8225_rf_stop;
+ } else {
+ sc->sc_rf_init = urtw_8225v2_rf_init;
+ sc->sc_rf_set_chan = urtw_8225v2_rf_set_chan;
+ sc->sc_rf_stop = urtw_8225_rf_stop;
+ }
+ sc->sc_max_sens = URTW_8225_RF_MAX_SENS;
+ sc->sc_sens = URTW_8225_RF_DEF_SENS;
+ break;
+ case URTW_EPROM_RFCHIPID_RTL8225Z2:
+ sc->sc_rf_init = urtw_8225v2b_rf_init;
+ sc->sc_rf_set_chan = urtw_8225v2b_rf_set_chan;
+ sc->sc_max_sens = URTW_8225_RF_MAX_SENS;
+ sc->sc_sens = URTW_8225_RF_DEF_SENS;
+ sc->sc_rf_stop = urtw_8225_rf_stop;
+ break;
+ default:
+ DPRINTF(sc, URTW_DEBUG_STATE,
+ "unsupported RF chip %d\n", data & 0xff);
+ error = USB_ERR_INVAL;
+ goto fail;
+ }
+
+ device_printf(sc->sc_dev, "%s rf %s hwrev %s\n",
+ (sc->sc_flags & URTW_RTL8187B) ? "rtl8187b" : "rtl8187l",
+ ((data & 0xff) == URTW_EPROM_RFCHIPID_RTL8225U) ? "rtl8225u" :
+ "rtl8225z2",
+ (sc->sc_flags & URTW_RTL8187B) ? ((data8 == 0) ? "b" :
+ (data8 == 1) ? "d" : "e") : "none");
+
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_led_init(struct urtw_softc *sc)
+{
+ uint32_t rev;
+ usb_error_t 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);
+}
+
+static usb_error_t
+urtw_8225_rf_init(struct urtw_softc *sc)
+{
+ unsigned i;
+ uint16_t data;
+ usb_error_t 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;
+ urtw_pause_ms(sc, 1000);
+
+ for (i = 0; i < nitems(urtw_8225_rf_part1); i++) {
+ urtw_8225_write(sc, urtw_8225_rf_part1[i].reg,
+ urtw_8225_rf_part1[i].val);
+ urtw_pause_ms(sc, 1);
+ }
+ urtw_pause_ms(sc, 100);
+ urtw_8225_write(sc,
+ URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC1);
+ urtw_pause_ms(sc, 200);
+ urtw_8225_write(sc,
+ URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC2);
+ urtw_pause_ms(sc, 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]);
+ urtw_pause_ms(sc, 1);
+ urtw_8187_write_phy_ofdm(sc, 0xa, (uint8_t)i + 0x80);
+ urtw_pause_ms(sc, 1);
+ }
+
+ for (i = 0; i < nitems(urtw_8225_rf_part2); i++) {
+ urtw_8187_write_phy_ofdm(sc, urtw_8225_rf_part2[i].reg,
+ urtw_8225_rf_part2[i].val);
+ urtw_pause_ms(sc, 1);
+ }
+
+ error = urtw_8225_setgain(sc, 4);
+ if (error)
+ goto fail;
+
+ for (i = 0; i < nitems(urtw_8225_rf_part3); i++) {
+ urtw_8187_write_phy_cck(sc, urtw_8225_rf_part3[i].reg,
+ urtw_8225_rf_part3[i].val);
+ urtw_pause_ms(sc, 1);
+ }
+
+ urtw_write8_m(sc, URTW_TESTR, 0x0d);
+
+ error = urtw_8225_set_txpwrlvl(sc, 1);
+ if (error)
+ goto fail;
+
+ urtw_8187_write_phy_cck(sc, 0x10, 0x9b);
+ urtw_pause_ms(sc, 1);
+ urtw_8187_write_phy_ofdm(sc, 0x26, 0x90);
+ urtw_pause_ms(sc, 1);
+
+ /* TX ant A, 0x0 for B */
+ error = urtw_8185_tx_antenna(sc, 0x3);
+ if (error)
+ goto fail;
+ urtw_write32_m(sc, URTW_HSSI_PARA, 0x3dc00002);
+
+ error = urtw_8225_rf_set_chan(sc, 1);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_8185_rf_pins_enable(struct urtw_softc *sc)
+{
+ usb_error_t error = 0;
+
+ urtw_write16_m(sc, URTW_RF_PINS_ENABLE, 0x1ff7);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_8185_tx_antenna(struct urtw_softc *sc, uint8_t ant)
+{
+ usb_error_t error;
+
+ urtw_write8_m(sc, URTW_TX_ANTENNA, ant);
+ urtw_pause_ms(sc, 1);
+fail:
+ return (error);
+}
+
+static usb_error_t
+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 usb_error_t
+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 usb_error_t
+urtw_8187_write_phy(struct urtw_softc *sc, uint8_t addr, uint32_t data)
+{
+ uint32_t phyw;
+ usb_error_t 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)));
+ urtw_pause_ms(sc, 1);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_8225_setgain(struct urtw_softc *sc, int16_t gain)
+{
+ usb_error_t 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 usb_error_t
+urtw_8225_usb_init(struct urtw_softc *sc)
+{
+ uint8_t data;
+ usb_error_t 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);
+
+ urtw_pause_ms(sc, 500);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_8225_write_c(struct urtw_softc *sc, uint8_t addr, uint16_t data)
+{
+ uint16_t d80, d82, d84;
+ usb_error_t 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);
+ urtw_pause_ms(sc, 2);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_8225_write_s16(struct urtw_softc *sc, uint8_t addr, int index,
+ uint16_t *data)
+{
+ uint8_t buf[2];
+ uint16_t data16;
+ struct usb_device_request req;
+ usb_error_t error = 0;
+
+ data16 = *data;
+
+ 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 = urtw_do_request(sc, &req, buf);
+
+ return (error);
+}
+
+static usb_error_t
+urtw_8225_rf_set_chan(struct urtw_softc *sc, int chan)
+{
+ usb_error_t 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]);
+ urtw_pause_ms(sc, 10);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_8225_rf_set_sens(struct urtw_softc *sc, int sens)
+{
+ usb_error_t 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 usb_error_t
+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;
+ usb_error_t 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]);
+ }
+ urtw_pause_ms(sc, 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]);
+ urtw_pause_ms(sc, 1);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_8225_rf_stop(struct urtw_softc *sc)
+{
+ uint8_t data;
+ usb_error_t error;
+
+ urtw_8225_write(sc, 0x4, 0x1f);
+
+ 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);
+ if (sc->sc_flags & URTW_RTL8187B) {
+ urtw_write32_m(sc, URTW_ANAPARAM2,
+ URTW_8187B_8225_ANAPARAM2_OFF);
+ urtw_write32_m(sc, URTW_ANAPARAM, URTW_8187B_8225_ANAPARAM_OFF);
+ urtw_write32_m(sc, URTW_ANAPARAM3,
+ URTW_8187B_8225_ANAPARAM3_OFF);
+ } else {
+ urtw_write32_m(sc, URTW_ANAPARAM2, URTW_8225_ANAPARAM2_OFF);
+ urtw_write32_m(sc, URTW_ANAPARAM, URTW_8225_ANAPARAM_OFF);
+ }
+
+ 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 usb_error_t
+urtw_8225v2_rf_init(struct urtw_softc *sc)
+{
+ unsigned i;
+ uint16_t data;
+ uint32_t data32;
+ usb_error_t 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;
+
+ urtw_pause_ms(sc, 500);
+
+ for (i = 0; i < nitems(urtw_8225v2_rf_part1); i++) {
+ urtw_8225_write(sc, urtw_8225v2_rf_part1[i].reg,
+ urtw_8225v2_rf_part1[i].val);
+ }
+ urtw_pause_ms(sc, 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);
+ urtw_pause_ms(sc, 100);
+ urtw_8225_write(sc,
+ URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC2);
+ urtw_pause_ms(sc, 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);
+ urtw_pause_ms(sc, 100);
+ urtw_8225_write(sc,
+ URTW_8225_ADDR_2_MAGIC, URTW_8225_ADDR_2_DATA_MAGIC2);
+ urtw_pause_ms(sc, 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");
+ }
+ urtw_pause_ms(sc, 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 < nitems(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 < nitems(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_TESTR, 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_HSSI_PARA, 0x3dc00002);
+
+ error = urtw_8225_rf_set_chan(sc, 1);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_8225v2_rf_set_chan(struct urtw_softc *sc, int chan)
+{
+ usb_error_t 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]);
+ urtw_pause_ms(sc, 10);
+fail:
+ return (error);
+}
+
+static usb_error_t
+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;
+ usb_error_t 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 usb_error_t
+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;
+ usb_error_t 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]);
+ urtw_pause_ms(sc, 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]);
+ urtw_pause_ms(sc, 1);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_8225v2_setgain(struct urtw_softc *sc, int16_t gain)
+{
+ uint8_t *gainp;
+ usb_error_t error;
+
+ /* XXX for A? */
+ gainp = urtw_8225v2_gain_bg;
+ urtw_8187_write_phy_ofdm(sc, 0x0d, gainp[gain * 3]);
+ urtw_pause_ms(sc, 1);
+ urtw_8187_write_phy_ofdm(sc, 0x1b, gainp[gain * 3 + 1]);
+ urtw_pause_ms(sc, 1);
+ urtw_8187_write_phy_ofdm(sc, 0x1d, gainp[gain * 3 + 2]);
+ urtw_pause_ms(sc, 1);
+ urtw_8187_write_phy_ofdm(sc, 0x21, 0x17);
+ urtw_pause_ms(sc, 1);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_8225_isv2(struct urtw_softc *sc, int *ret)
+{
+ uint32_t data;
+ usb_error_t 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);
+ urtw_pause_ms(sc, 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 usb_error_t
+urtw_8225v2b_rf_init(struct urtw_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ const uint8_t *macaddr;
+ unsigned i;
+ uint8_t data8;
+ usb_error_t error;
+
+ error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG);
+ if (error)
+ goto fail;
+
+ /*
+ * initialize extra registers on 8187
+ */
+ urtw_write16_m(sc, URTW_BRSR_8187B, 0xfff);
+
+ /* retry limit */
+ urtw_read8_m(sc, URTW_CW_CONF, &data8);
+ data8 |= URTW_CW_CONF_PERPACKET_RETRY;
+ urtw_write8_m(sc, URTW_CW_CONF, data8);
+
+ /* TX AGC */
+ urtw_read8_m(sc, URTW_TX_AGC_CTL, &data8);
+ data8 |= URTW_TX_AGC_CTL_PERPACKET_GAIN;
+ urtw_write8_m(sc, URTW_TX_AGC_CTL, data8);
+
+ /* Auto Rate Fallback Control */
+#define URTW_ARFR 0x1e0
+ urtw_write16_m(sc, URTW_ARFR, 0xfff);
+ urtw_read8_m(sc, URTW_RATE_FALLBACK, &data8);
+ urtw_write8_m(sc, URTW_RATE_FALLBACK,
+ data8 | URTW_RATE_FALLBACK_ENABLE);
+
+ urtw_read8_m(sc, URTW_MSR, &data8);
+ urtw_write8_m(sc, URTW_MSR, data8 & 0xf3);
+ urtw_read8_m(sc, URTW_MSR, &data8);
+ urtw_write8_m(sc, URTW_MSR, data8 | URTW_MSR_LINK_ENEDCA);
+ urtw_write8_m(sc, URTW_ACM_CONTROL, sc->sc_acmctl);
+
+ urtw_write16_m(sc, URTW_ATIM_WND, 2);
+ urtw_write16_m(sc, URTW_BEACON_INTERVAL, 100);
+#define URTW_FEMR_FOR_8187B 0x1d4
+ urtw_write16_m(sc, URTW_FEMR_FOR_8187B, 0xffff);
+
+ /* led type */
+ urtw_read8_m(sc, URTW_CONFIG1, &data8);
+ data8 = (data8 & 0x3f) | 0x80;
+ urtw_write8_m(sc, URTW_CONFIG1, data8);
+
+ /* applying MAC address again. */
+ macaddr = vap ? vap->iv_myaddr : ic->ic_macaddr;
+ error = urtw_set_macaddr(sc, macaddr);
+ if (error)
+ goto fail;
+
+ error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL);
+ if (error)
+ goto fail;
+
+ urtw_write8_m(sc, URTW_WPA_CONFIG, 0);
+
+ /*
+ * MAC configuration
+ */
+ for (i = 0; i < nitems(urtw_8225v2b_rf_part1); i++)
+ urtw_write8_m(sc, urtw_8225v2b_rf_part1[i].reg,
+ urtw_8225v2b_rf_part1[i].val);
+ urtw_write16_m(sc, URTW_TID_AC_MAP, 0xfa50);
+ urtw_write16_m(sc, URTW_INT_MIG, 0x0000);
+ urtw_write32_m(sc, 0x1f0, 0);
+ urtw_write32_m(sc, 0x1f4, 0);
+ urtw_write8_m(sc, 0x1f8, 0);
+ urtw_write32_m(sc, URTW_RF_TIMING, 0x4001);
+
+#define URTW_RFSW_CTRL 0x272
+ urtw_write16_m(sc, URTW_RFSW_CTRL, 0x569a);
+
+ /*
+ * initialize PHY
+ */
+ error = urtw_set_mode(sc, URTW_EPROM_CMD_CONFIG);
+ if (error)
+ goto fail;
+ urtw_read8_m(sc, URTW_CONFIG3, &data8);
+ urtw_write8_m(sc, URTW_CONFIG3,
+ data8 | URTW_CONFIG3_ANAPARAM_WRITE);
+
+ error = urtw_set_mode(sc, URTW_EPROM_CMD_NORMAL);
+ if (error)
+ goto fail;
+
+ /* setup RFE initial timing */
+ urtw_write16_m(sc, URTW_RF_PINS_OUTPUT, 0x0480);
+ urtw_write16_m(sc, URTW_RF_PINS_SELECT, 0x2488);
+ urtw_write16_m(sc, URTW_RF_PINS_ENABLE, 0x1fff);
+ urtw_pause_ms(sc, 1100);
+
+ for (i = 0; i < nitems(urtw_8225v2b_rf_part0); i++) {
+ urtw_8225_write(sc, urtw_8225v2b_rf_part0[i].reg,
+ urtw_8225v2b_rf_part0[i].val);
+ urtw_pause_ms(sc, 1);
+ }
+ urtw_8225_write(sc, 0x00, 0x01b7);
+
+ for (i = 0; i < 95; i++) {
+ urtw_8225_write(sc, URTW_8225_ADDR_1_MAGIC, (uint8_t)(i + 1));
+ urtw_pause_ms(sc, 1);
+ urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC,
+ urtw_8225v2b_rxgain[i]);
+ urtw_pause_ms(sc, 1);
+ }
+
+ urtw_8225_write(sc, URTW_8225_ADDR_3_MAGIC, 0x080);
+ urtw_pause_ms(sc, 1);
+ urtw_8225_write(sc, URTW_8225_ADDR_5_MAGIC, 0x004);
+ urtw_pause_ms(sc, 1);
+ urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, 0x0b7);
+ urtw_pause_ms(sc, 1);
+ urtw_pause_ms(sc, 3000);
+ urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, 0xc4d);
+ urtw_pause_ms(sc, 2000);
+ urtw_8225_write(sc, URTW_8225_ADDR_2_MAGIC, 0x44d);
+ urtw_pause_ms(sc, 1);
+ urtw_8225_write(sc, URTW_8225_ADDR_0_MAGIC, 0x2bf);
+ urtw_pause_ms(sc, 1);
+
+ urtw_write8_m(sc, URTW_TX_GAIN_CCK, 0x03);
+ urtw_write8_m(sc, URTW_TX_GAIN_OFDM, 0x07);
+ urtw_write8_m(sc, URTW_TX_ANTENNA, 0x03);
+
+ urtw_8187_write_phy_ofdm(sc, 0x80, 0x12);
+ for (i = 0; i < 128; i++) {
+ uint32_t addr, data;
+
+ data = (urtw_8225z2_agc[i] << 8) | 0x0000008f;
+ addr = ((i + 0x80) << 8) | 0x0000008e;
+
+ urtw_8187_write_phy_ofdm(sc, data & 0x7f, (data >> 8) & 0xff);
+ urtw_8187_write_phy_ofdm(sc, addr & 0x7f, (addr >> 8) & 0xff);
+ urtw_8187_write_phy_ofdm(sc, 0x0e, 0x00);
+ }
+ urtw_8187_write_phy_ofdm(sc, 0x80, 0x10);
+
+ for (i = 0; i < nitems(urtw_8225v2b_rf_part2); i++)
+ urtw_8187_write_phy_ofdm(sc, i, urtw_8225v2b_rf_part2[i].val);
+
+ urtw_write32_m(sc, URTW_8187B_AC_VO, (7 << 12) | (3 << 8) | 0x1c);
+ urtw_write32_m(sc, URTW_8187B_AC_VI, (7 << 12) | (3 << 8) | 0x1c);
+ urtw_write32_m(sc, URTW_8187B_AC_BE, (7 << 12) | (3 << 8) | 0x1c);
+ urtw_write32_m(sc, URTW_8187B_AC_BK, (7 << 12) | (3 << 8) | 0x1c);
+
+ urtw_8187_write_phy_ofdm(sc, 0x97, 0x46);
+ urtw_8187_write_phy_ofdm(sc, 0xa4, 0xb6);
+ urtw_8187_write_phy_ofdm(sc, 0x85, 0xfc);
+ urtw_8187_write_phy_cck(sc, 0xc1, 0x88);
+
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_8225v2b_rf_set_chan(struct urtw_softc *sc, int chan)
+{
+ usb_error_t error;
+
+ error = urtw_8225v2b_set_txpwrlvl(sc, chan);
+ if (error)
+ goto fail;
+
+ urtw_8225_write(sc, URTW_8225_ADDR_7_MAGIC, urtw_8225_channel[chan]);
+ urtw_pause_ms(sc, 10);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_8225v2b_set_txpwrlvl(struct urtw_softc *sc, int chan)
+{
+ int i;
+ uint8_t *cck_pwrtable;
+ uint8_t cck_pwrlvl_max = 15;
+ uint8_t cck_pwrlvl = sc->sc_txpwr_cck[chan] & 0xff;
+ uint8_t ofdm_pwrlvl = sc->sc_txpwr_ofdm[chan] & 0xff;
+ usb_error_t error;
+
+ /* CCK power setting */
+ cck_pwrlvl = (cck_pwrlvl > cck_pwrlvl_max) ?
+ ((sc->sc_flags & URTW_RTL8187B_REV_B) ? cck_pwrlvl_max : 22) :
+ (cck_pwrlvl + ((sc->sc_flags & URTW_RTL8187B_REV_B) ? 0 : 7));
+ cck_pwrlvl += sc->sc_txpwr_cck_base;
+ cck_pwrlvl = (cck_pwrlvl > 35) ? 35 : cck_pwrlvl;
+ cck_pwrtable = (chan == 14) ? urtw_8225v2b_txpwr_cck_ch14 :
+ urtw_8225v2b_txpwr_cck;
+
+ if (sc->sc_flags & URTW_RTL8187B_REV_B)
+ cck_pwrtable += (cck_pwrlvl <= 6) ? 0 :
+ ((cck_pwrlvl <= 11) ? 8 : 16);
+ else
+ cck_pwrtable += (cck_pwrlvl <= 5) ? 0 :
+ ((cck_pwrlvl <= 11) ? 8 : ((cck_pwrlvl <= 17) ? 16 : 24));
+
+ 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] << 1);
+ urtw_pause_ms(sc, 1);
+
+ /* OFDM power setting */
+ ofdm_pwrlvl = (ofdm_pwrlvl > 15) ?
+ ((sc->sc_flags & URTW_RTL8187B_REV_B) ? 17 : 25) :
+ (ofdm_pwrlvl + ((sc->sc_flags & URTW_RTL8187B_REV_B) ? 2 : 10));
+ ofdm_pwrlvl += sc->sc_txpwr_ofdm_base;
+ ofdm_pwrlvl = (ofdm_pwrlvl > 35) ? 35 : ofdm_pwrlvl;
+
+ urtw_write8_m(sc, URTW_TX_GAIN_OFDM,
+ urtw_8225v2_tx_gain_cck_ofdm[ofdm_pwrlvl] << 1);
+
+ if (sc->sc_flags & URTW_RTL8187B_REV_B) {
+ if (ofdm_pwrlvl <= 11) {
+ urtw_8187_write_phy_ofdm(sc, 0x87, 0x60);
+ urtw_8187_write_phy_ofdm(sc, 0x89, 0x60);
+ } else {
+ urtw_8187_write_phy_ofdm(sc, 0x87, 0x5c);
+ urtw_8187_write_phy_ofdm(sc, 0x89, 0x5c);
+ }
+ } else {
+ if (ofdm_pwrlvl <= 11) {
+ urtw_8187_write_phy_ofdm(sc, 0x87, 0x5c);
+ urtw_8187_write_phy_ofdm(sc, 0x89, 0x5c);
+ } else if (ofdm_pwrlvl <= 17) {
+ urtw_8187_write_phy_ofdm(sc, 0x87, 0x54);
+ urtw_8187_write_phy_ofdm(sc, 0x89, 0x54);
+ } else {
+ urtw_8187_write_phy_ofdm(sc, 0x87, 0x50);
+ urtw_8187_write_phy_ofdm(sc, 0x89, 0x50);
+ }
+ }
+ urtw_pause_ms(sc, 1);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_read8e(struct urtw_softc *sc, int val, uint8_t *data)
+{
+ struct usb_device_request req;
+ usb_error_t 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 = urtw_do_request(sc, &req, data);
+ return (error);
+}
+
+static usb_error_t
+urtw_write8e(struct urtw_softc *sc, int val, uint8_t data)
+{
+ struct usb_device_request 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 (urtw_do_request(sc, &req, &data));
+}
+
+static usb_error_t
+urtw_8180_set_anaparam(struct urtw_softc *sc, uint32_t val)
+{
+ uint8_t data;
+ usb_error_t 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 usb_error_t
+urtw_8185_set_anaparam2(struct urtw_softc *sc, uint32_t val)
+{
+ uint8_t data;
+ usb_error_t 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 usb_error_t
+urtw_intr_enable(struct urtw_softc *sc)
+{
+ usb_error_t error;
+
+ urtw_write16_m(sc, URTW_INTR_MASK, 0xffff);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_intr_disable(struct urtw_softc *sc)
+{
+ usb_error_t error;
+
+ urtw_write16_m(sc, URTW_INTR_MASK, 0);
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_reset(struct urtw_softc *sc)
+{
+ uint8_t data;
+ usb_error_t 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;
+ urtw_pause_ms(sc, 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;
+ urtw_pause_ms(sc, 100);
+
+ urtw_read8_m(sc, URTW_CMD, &data);
+ data = (data & 0x2) | URTW_CMD_RST;
+ urtw_write8_m(sc, URTW_CMD, data);
+ urtw_pause_ms(sc, 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;
+ urtw_pause_ms(sc, 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 usb_error_t
+urtw_led_ctl(struct urtw_softc *sc, int mode)
+{
+ usb_error_t 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:
+ DPRINTF(sc, URTW_DEBUG_STATE,
+ "unsupported LED mode %d\n", sc->sc_strategy);
+ error = USB_ERR_INVAL;
+ break;
+ }
+
+ return (error);
+}
+
+static usb_error_t
+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:
+ DPRINTF(sc, URTW_DEBUG_STATE,
+ "unsupported LED mode 0x%x", mode);
+ return (USB_ERR_INVAL);
+ }
+
+ 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;
+ usb_callout_reset(&sc->sc_led_ch, hz, urtw_led_ch, sc);
+ break;
+ case URTW_LED_POWER_ON_BLINK:
+ urtw_led_on(sc, URTW_LED_GPIO);
+ urtw_pause_ms(sc, 100);
+ urtw_led_off(sc, URTW_LED_GPIO);
+ break;
+ default:
+ DPRINTF(sc, URTW_DEBUG_STATE,
+ "unknown LED status 0x%x", sc->sc_gpio_ledstate);
+ return (USB_ERR_INVAL);
+ }
+ return (0);
+}
+
+static usb_error_t
+urtw_led_mode1(struct urtw_softc *sc, int mode)
+{
+ return (USB_ERR_INVAL);
+}
+
+static usb_error_t
+urtw_led_mode2(struct urtw_softc *sc, int mode)
+{
+ return (USB_ERR_INVAL);
+}
+
+static usb_error_t
+urtw_led_mode3(struct urtw_softc *sc, int mode)
+{
+ return (USB_ERR_INVAL);
+}
+
+static usb_error_t
+urtw_led_on(struct urtw_softc *sc, int type)
+{
+ usb_error_t 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:
+ DPRINTF(sc, URTW_DEBUG_STATE,
+ "unsupported LED PIN type 0x%x",
+ sc->sc_gpio_ledpin);
+ error = USB_ERR_INVAL;
+ goto fail;
+ }
+ } else {
+ DPRINTF(sc, URTW_DEBUG_STATE,
+ "unsupported LED type 0x%x", type);
+ error = USB_ERR_INVAL;
+ goto fail;
+ }
+
+ sc->sc_gpio_ledon = 1;
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_led_off(struct urtw_softc *sc, int type)
+{
+ usb_error_t 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:
+ DPRINTF(sc, URTW_DEBUG_STATE,
+ "unsupported LED PIN type 0x%x",
+ sc->sc_gpio_ledpin);
+ error = USB_ERR_INVAL;
+ goto fail;
+ }
+ } else {
+ DPRINTF(sc, URTW_DEBUG_STATE,
+ "unsupported LED type 0x%x", type);
+ error = USB_ERR_INVAL;
+ goto fail;
+ }
+
+ sc->sc_gpio_ledon = 0;
+
+fail:
+ return (error);
+}
+
+static void
+urtw_led_ch(void *arg)
+{
+ struct urtw_softc *sc = arg;
+ struct ieee80211com *ic = &sc->sc_ic;
+
+ ieee80211_runtask(ic, &sc->sc_led_task);
+}
+
+static void
+urtw_ledtask(void *arg, int pending)
+{
+ struct urtw_softc *sc = arg;
+
+ if (sc->sc_strategy != URTW_SW_LED_MODE0) {
+ DPRINTF(sc, URTW_DEBUG_STATE,
+ "could not process a LED strategy 0x%x",
+ sc->sc_strategy);
+ return;
+ }
+
+ URTW_LOCK(sc);
+ urtw_led_blink(sc);
+ URTW_UNLOCK(sc);
+}
+
+static usb_error_t
+urtw_led_blink(struct urtw_softc *sc)
+{
+ uint8_t ing = 0;
+
+ if (sc->sc_gpio_blinkstate == URTW_LED_ON)
+ urtw_led_on(sc, URTW_LED_GPIO);
+ else
+ 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)
+ urtw_led_on(sc, URTW_LED_GPIO);
+ else if (sc->sc_gpio_ledstate == URTW_LED_OFF &&
+ sc->sc_gpio_ledon == 1)
+ 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:
+ usb_callout_reset(&sc->sc_led_ch, hz, urtw_led_ch, sc);
+ break;
+ default:
+ DPRINTF(sc, URTW_DEBUG_STATE,
+ "unknown LED status 0x%x",
+ sc->sc_gpio_ledstate);
+ return (USB_ERR_INVAL);
+ }
+ return (0);
+}
+
+static usb_error_t
+urtw_rx_enable(struct urtw_softc *sc)
+{
+ uint8_t data;
+ usb_error_t error;
+
+ usbd_transfer_start((sc->sc_flags & URTW_RTL8187B) ?
+ sc->sc_xfer[URTW_8187B_BULK_RX] : sc->sc_xfer[URTW_8187L_BULK_RX]);
+
+ error = urtw_rx_setconf(sc);
+ if (error != 0)
+ goto fail;
+
+ if ((sc->sc_flags & URTW_RTL8187B) == 0) {
+ urtw_read8_m(sc, URTW_CMD, &data);
+ urtw_write8_m(sc, URTW_CMD, data | URTW_CMD_RX_ENABLE);
+ }
+fail:
+ return (error);
+}
+
+static usb_error_t
+urtw_tx_enable(struct urtw_softc *sc)
+{
+ uint8_t data8;
+ uint32_t data;
+ usb_error_t error;
+
+ if (sc->sc_flags & URTW_RTL8187B) {
+ urtw_read32_m(sc, URTW_TX_CONF, &data);
+ data &= ~URTW_TX_LOOPBACK_MASK;
+ data &= ~(URTW_TX_DPRETRY_MASK | URTW_TX_RTSRETRY_MASK);
+ data &= ~(URTW_TX_NOCRC | URTW_TX_MXDMA_MASK);
+ data &= ~URTW_TX_SWPLCPLEN;
+ data |= URTW_TX_HW_SEQNUM | URTW_TX_DISREQQSIZE |
+ (7 << 8) | /* short retry limit */
+ (7 << 0) | /* long retry limit */
+ (7 << 21); /* MAX TX DMA */
+ urtw_write32_m(sc, URTW_TX_CONF, data);
+
+ urtw_read8_m(sc, URTW_MSR, &data8);
+ data8 |= URTW_MSR_LINK_ENEDCA;
+ urtw_write8_m(sc, URTW_MSR, data8);
+ return (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 usb_error_t
+urtw_rx_setconf(struct urtw_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t data;
+ usb_error_t error;
+
+ urtw_read32_m(sc, URTW_RX, &data);
+ data = data &~ URTW_RX_FILTER_MASK;
+ if (sc->sc_flags & URTW_RTL8187B) {
+ data = data | URTW_RX_FILTER_MNG | URTW_RX_FILTER_DATA |
+ URTW_RX_FILTER_MCAST | URTW_RX_FILTER_BCAST |
+ URTW_RX_FIFO_THRESHOLD_NONE |
+ URTW_MAX_RX_DMA_2048 |
+ URTW_RX_AUTORESETPHY | URTW_RCR_ONLYERLPKT;
+ } else {
+ 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;
+
+ 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;
+ }
+
+ /* XXX allmulti should not be checked here... */
+ if (ic->ic_opmode == IEEE80211_M_MONITOR ||
+ ic->ic_promisc > 0 || ic->ic_allmulti > 0) {
+ data = data | URTW_RX_FILTER_CTL;
+ data = data | URTW_RX_FILTER_ALLMAC;
+ } else {
+ data = data | URTW_RX_FILTER_NICMAC;
+ data = data | URTW_RX_CHECK_BSSID;
+ }
+
+ urtw_write32_m(sc, URTW_RX, data);
+fail:
+ return (error);
+}
+
+static struct mbuf *
+urtw_rxeof(struct usb_xfer *xfer, struct urtw_data *data, int *rssi_p,
+ int8_t *nf_p)
+{
+ int actlen, flen, rssi;
+ struct ieee80211_frame *wh;
+ struct mbuf *m, *mnew;
+ struct urtw_softc *sc = data->sc;
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint8_t noise = 0, rate;
+ uint64_t mactime;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ if (sc->sc_flags & URTW_RTL8187B) {
+ struct urtw_8187b_rxhdr *rx;
+
+ if (actlen < sizeof(*rx) + IEEE80211_ACK_LEN)
+ goto fail;
+
+ rx = (struct urtw_8187b_rxhdr *)(data->buf +
+ (actlen - (sizeof(struct urtw_8187b_rxhdr))));
+ flen = le32toh(rx->flag) & 0xfff;
+ if (flen > actlen - sizeof(*rx))
+ goto fail;
+
+ rate = (le32toh(rx->flag) >> URTW_RX_FLAG_RXRATE_SHIFT) & 0xf;
+ /* XXX correct? */
+ rssi = rx->rssi & URTW_RX_RSSI_MASK;
+ noise = rx->noise;
+
+ if (ieee80211_radiotap_active(ic))
+ mactime = rx->mactime;
+ } else {
+ struct urtw_8187l_rxhdr *rx;
+
+ if (actlen < sizeof(*rx) + IEEE80211_ACK_LEN)
+ goto fail;
+
+ rx = (struct urtw_8187l_rxhdr *)(data->buf +
+ (actlen - (sizeof(struct urtw_8187l_rxhdr))));
+ flen = le32toh(rx->flag) & 0xfff;
+ if (flen > actlen - sizeof(*rx))
+ goto fail;
+
+ rate = (le32toh(rx->flag) >> URTW_RX_FLAG_RXRATE_SHIFT) & 0xf;
+ /* XXX correct? */
+ rssi = rx->rssi & URTW_RX_8187L_RSSI_MASK;
+ noise = rx->noise;
+
+ if (ieee80211_radiotap_active(ic))
+ mactime = rx->mactime;
+ }
+
+ if (flen < IEEE80211_ACK_LEN)
+ goto fail;
+
+ mnew = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ if (mnew == NULL)
+ goto fail;
+
+ m = data->m;
+ data->m = mnew;
+ data->buf = mtod(mnew, uint8_t *);
+
+ /* finalize mbuf */
+ m->m_pkthdr.len = m->m_len = flen - IEEE80211_CRC_LEN;
+
+ if (ieee80211_radiotap_active(ic)) {
+ struct urtw_rx_radiotap_header *tap = &sc->sc_rxtap;
+
+ tap->wr_tsf = mactime;
+ tap->wr_flags = 0;
+ tap->wr_dbm_antsignal = (int8_t)rssi;
+ }
+
+ wh = mtod(m, struct ieee80211_frame *);
+ if (IEEE80211_IS_DATA(wh))
+ sc->sc_currate = (rate > 0) ? rate : sc->sc_currate;
+
+ *rssi_p = rssi;
+ *nf_p = noise; /* XXX correct? */
+
+ return (m);
+
+fail:
+ counter_u64_add(ic->ic_ierrors, 1);
+ return (NULL);
+}
+
+static void
+urtw_bulk_rx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct urtw_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_node *ni;
+ struct mbuf *m = NULL;
+ struct urtw_data *data;
+ int8_t nf = -95;
+ int rssi = 1;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ data = STAILQ_FIRST(&sc->sc_rx_active);
+ if (data == NULL)
+ goto setup;
+ STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next);
+ m = urtw_rxeof(xfer, data, &rssi, &nf);
+ STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+setup:
+ data = STAILQ_FIRST(&sc->sc_rx_inactive);
+ if (data == NULL) {
+ KASSERT(m == NULL, ("mbuf isn't NULL"));
+ return;
+ }
+ STAILQ_REMOVE_HEAD(&sc->sc_rx_inactive, next);
+ STAILQ_INSERT_TAIL(&sc->sc_rx_active, data, next);
+ usbd_xfer_set_frame_data(xfer, 0, data->buf,
+ usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+
+ /*
+ * To avoid LOR we should unlock our private mutex here to call
+ * ieee80211_input() because here is at the end of a USB
+ * callback and safe to unlock.
+ */
+ URTW_UNLOCK(sc);
+ if (m != NULL) {
+ if (m->m_pkthdr.len >=
+ sizeof(struct ieee80211_frame_min)) {
+ ni = ieee80211_find_rxnode(ic,
+ mtod(m, struct ieee80211_frame_min *));
+ } else
+ ni = NULL;
+
+ if (ni != NULL) {
+ (void) ieee80211_input(ni, m, rssi, nf);
+ /* node is no longer needed */
+ ieee80211_free_node(ni);
+ } else
+ (void) ieee80211_input_all(ic, m, rssi, nf);
+ m = NULL;
+ }
+ URTW_LOCK(sc);
+ break;
+ default:
+ /* needs it to the inactive queue due to a error. */
+ data = STAILQ_FIRST(&sc->sc_rx_active);
+ if (data != NULL) {
+ STAILQ_REMOVE_HEAD(&sc->sc_rx_active, next);
+ STAILQ_INSERT_TAIL(&sc->sc_rx_inactive, data, next);
+ }
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto setup;
+ }
+ break;
+ }
+}
+
+#define URTW_STATUS_TYPE_TXCLOSE 1
+#define URTW_STATUS_TYPE_BEACON_INTR 0
+
+static void
+urtw_txstatus_eof(struct usb_xfer *xfer)
+{
+ struct urtw_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ int actlen, type, pktretry;
+ uint64_t val;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ if (actlen != sizeof(uint64_t))
+ return;
+
+ val = le64toh(sc->sc_txstatus);
+ type = (val >> 30) & 0x3;
+ if (type == URTW_STATUS_TYPE_TXCLOSE) {
+ pktretry = val & 0xff;
+ if (pktretry == URTW_TX_MAXRETRY)
+ counter_u64_add(ic->ic_oerrors, 1);
+ DPRINTF(sc, URTW_DEBUG_TXSTATUS, "pktretry %d seq %#x\n",
+ pktretry, (val >> 16) & 0xff);
+ }
+}
+
+static void
+urtw_bulk_tx_status_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct urtw_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ void *dma_buf = usbd_xfer_get_frame_buffer(xfer, 0);
+
+ URTW_ASSERT_LOCKED(sc);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ urtw_txstatus_eof(xfer);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+setup:
+ memcpy(dma_buf, &sc->sc_txstatus, sizeof(uint64_t));
+ usbd_xfer_set_frame_len(xfer, 0, sizeof(uint64_t));
+ usbd_transfer_submit(xfer);
+ break;
+ default:
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ counter_u64_add(ic->ic_ierrors, 1);
+ goto setup;
+ }
+ break;
+ }
+}
+
+static void
+urtw_txeof(struct usb_xfer *xfer, struct urtw_data *data)
+{
+ struct urtw_softc *sc = usbd_xfer_softc(xfer);
+
+ URTW_ASSERT_LOCKED(sc);
+
+ if (data->m) {
+ /* XXX status? */
+ ieee80211_tx_complete(data->ni, data->m, 0);
+ data->m = NULL;
+ data->ni = NULL;
+ }
+ sc->sc_txtimer = 0;
+}
+
+static void
+urtw_bulk_tx_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct urtw_softc *sc = usbd_xfer_softc(xfer);
+ struct urtw_data *data;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ data = STAILQ_FIRST(&sc->sc_tx_active);
+ if (data == NULL)
+ goto setup;
+ STAILQ_REMOVE_HEAD(&sc->sc_tx_active, next);
+ urtw_txeof(xfer, data);
+ STAILQ_INSERT_TAIL(&sc->sc_tx_inactive, data, next);
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+setup:
+ data = STAILQ_FIRST(&sc->sc_tx_pending);
+ if (data == NULL) {
+ DPRINTF(sc, URTW_DEBUG_XMIT,
+ "%s: empty pending queue\n", __func__);
+ return;
+ }
+ STAILQ_REMOVE_HEAD(&sc->sc_tx_pending, next);
+ STAILQ_INSERT_TAIL(&sc->sc_tx_active, data, next);
+
+ usbd_xfer_set_frame_data(xfer, 0, data->buf, data->buflen);
+ usbd_transfer_submit(xfer);
+
+ urtw_start(sc);
+ break;
+ default:
+ data = STAILQ_FIRST(&sc->sc_tx_active);
+ if (data == NULL)
+ goto setup;
+ if (data->ni != NULL) {
+ if_inc_counter(data->ni->ni_vap->iv_ifp,
+ IFCOUNTER_OERRORS, 1);
+ ieee80211_free_node(data->ni);
+ data->ni = NULL;
+ }
+ if (error != USB_ERR_CANCELLED) {
+ usbd_xfer_set_stall(xfer);
+ goto setup;
+ }
+ break;
+ }
+}
+
+static struct urtw_data *
+_urtw_getbuf(struct urtw_softc *sc)
+{
+ struct urtw_data *bf;
+
+ bf = STAILQ_FIRST(&sc->sc_tx_inactive);
+ if (bf != NULL)
+ STAILQ_REMOVE_HEAD(&sc->sc_tx_inactive, next);
+ else
+ bf = NULL;
+ if (bf == NULL)
+ DPRINTF(sc, URTW_DEBUG_XMIT, "%s: %s\n", __func__,
+ "out of xmit buffers");
+ return (bf);
+}
+
+static struct urtw_data *
+urtw_getbuf(struct urtw_softc *sc)
+{
+ struct urtw_data *bf;
+
+ URTW_ASSERT_LOCKED(sc);
+
+ bf = _urtw_getbuf(sc);
+ if (bf == NULL)
+ DPRINTF(sc, URTW_DEBUG_XMIT, "%s: stop queue\n", __func__);
+ return (bf);
+}
+
+static int
+urtw_isbmode(uint16_t rate)
+{
+
+ return ((rate <= 22 && rate != 12 && rate != 18) ||
+ rate == 44) ? (1) : (0);
+}
+
+static uint16_t
+urtw_rate2dbps(uint16_t rate)
+{
+
+ switch(rate) {
+ case 12:
+ case 18:
+ case 24:
+ case 36:
+ case 48:
+ case 72:
+ case 96:
+ case 108:
+ return (rate * 2);
+ default:
+ break;
+ }
+ return (24);
+}
+
+static int
+urtw_compute_txtime(uint16_t framelen, uint16_t rate,
+ uint8_t ismgt, uint8_t isshort)
+{
+ uint16_t ceiling, frametime, n_dbps;
+
+ if (urtw_isbmode(rate)) {
+ if (ismgt || !isshort || rate == 2)
+ frametime = (uint16_t)(144 + 48 +
+ (framelen * 8 / (rate / 2)));
+ else
+ frametime = (uint16_t)(72 + 24 +
+ (framelen * 8 / (rate / 2)));
+ if ((framelen * 8 % (rate / 2)) != 0)
+ frametime++;
+ } else {
+ n_dbps = urtw_rate2dbps(rate);
+ ceiling = (16 + 8 * framelen + 6) / n_dbps
+ + (((16 + 8 * framelen + 6) % n_dbps) ? 1 : 0);
+ frametime = (uint16_t)(16 + 4 + 4 * ceiling + 6);
+ }
+ return (frametime);
+}
+
+/*
+ * Callback from the 802.11 layer to update the
+ * slot time based on the current setting.
+ */
+static void
+urtw_updateslot(struct ieee80211com *ic)
+{
+ struct urtw_softc *sc = ic->ic_softc;
+
+ ieee80211_runtask(ic, &sc->sc_updateslot_task);
+}
+
+static void
+urtw_updateslottask(void *arg, int pending)
+{
+ struct urtw_softc *sc = arg;
+ struct ieee80211com *ic = &sc->sc_ic;
+ int error;
+
+ URTW_LOCK(sc);
+ if ((sc->sc_flags & URTW_RUNNING) == 0) {
+ URTW_UNLOCK(sc);
+ return;
+ }
+ if (sc->sc_flags & URTW_RTL8187B) {
+ urtw_write8_m(sc, URTW_SIFS, 0x22);
+ if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan))
+ urtw_write8_m(sc, URTW_SLOT, IEEE80211_DUR_SHSLOT);
+ else
+ urtw_write8_m(sc, URTW_SLOT, IEEE80211_DUR_SLOT);
+ urtw_write8_m(sc, URTW_8187B_EIFS, 0x5b);
+ urtw_write8_m(sc, URTW_CARRIER_SCOUNT, 0x5b);
+ } else {
+ 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, IEEE80211_DUR_SHSLOT);
+ else
+ urtw_write8_m(sc, URTW_SLOT, IEEE80211_DUR_SLOT);
+ if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan)) {
+ urtw_write8_m(sc, URTW_DIFS, 0x14);
+ urtw_write8_m(sc, URTW_EIFS, 0x5b - 0x14);
+ urtw_write8_m(sc, URTW_CW_VAL, 0x73);
+ } else {
+ urtw_write8_m(sc, URTW_DIFS, 0x24);
+ urtw_write8_m(sc, URTW_EIFS, 0x5b - 0x24);
+ urtw_write8_m(sc, URTW_CW_VAL, 0xa5);
+ }
+ }
+fail:
+ URTW_UNLOCK(sc);
+}
+
+static void
+urtw_sysctl_node(struct urtw_softc *sc)
+{
+#define URTW_SYSCTL_STAT_ADD32(c, h, n, p, d) \
+ SYSCTL_ADD_UINT(c, h, OID_AUTO, n, CTLFLAG_RD, p, 0, d)
+ struct sysctl_ctx_list *ctx;
+ struct sysctl_oid_list *child, *parent;
+ struct sysctl_oid *tree;
+ struct urtw_stats *stats = &sc->sc_stats;
+
+ ctx = device_get_sysctl_ctx(sc->sc_dev);
+ child = SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev));
+
+ tree = SYSCTL_ADD_NODE(ctx, child, OID_AUTO, "stats",
+ CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "URTW statistics");
+ parent = SYSCTL_CHILDREN(tree);
+
+ /* Tx statistics. */
+ tree = SYSCTL_ADD_NODE(ctx, parent, OID_AUTO, "tx",
+ CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "Tx MAC statistics");
+ child = SYSCTL_CHILDREN(tree);
+ URTW_SYSCTL_STAT_ADD32(ctx, child, "1m", &stats->txrates[0],
+ "1 Mbit/s");
+ URTW_SYSCTL_STAT_ADD32(ctx, child, "2m", &stats->txrates[1],
+ "2 Mbit/s");
+ URTW_SYSCTL_STAT_ADD32(ctx, child, "5.5m", &stats->txrates[2],
+ "5.5 Mbit/s");
+ URTW_SYSCTL_STAT_ADD32(ctx, child, "6m", &stats->txrates[4],
+ "6 Mbit/s");
+ URTW_SYSCTL_STAT_ADD32(ctx, child, "9m", &stats->txrates[5],
+ "9 Mbit/s");
+ URTW_SYSCTL_STAT_ADD32(ctx, child, "11m", &stats->txrates[3],
+ "11 Mbit/s");
+ URTW_SYSCTL_STAT_ADD32(ctx, child, "12m", &stats->txrates[6],
+ "12 Mbit/s");
+ URTW_SYSCTL_STAT_ADD32(ctx, child, "18m", &stats->txrates[7],
+ "18 Mbit/s");
+ URTW_SYSCTL_STAT_ADD32(ctx, child, "24m", &stats->txrates[8],
+ "24 Mbit/s");
+ URTW_SYSCTL_STAT_ADD32(ctx, child, "36m", &stats->txrates[9],
+ "36 Mbit/s");
+ URTW_SYSCTL_STAT_ADD32(ctx, child, "48m", &stats->txrates[10],
+ "48 Mbit/s");
+ URTW_SYSCTL_STAT_ADD32(ctx, child, "54m", &stats->txrates[11],
+ "54 Mbit/s");
+#undef URTW_SYSCTL_STAT_ADD32
+}
+
+static device_method_t urtw_methods[] = {
+ DEVMETHOD(device_probe, urtw_match),
+ DEVMETHOD(device_attach, urtw_attach),
+ DEVMETHOD(device_detach, urtw_detach),
+ DEVMETHOD_END
+};
+
+static driver_t urtw_driver = {
+ .name = "urtw",
+ .methods = urtw_methods,
+ .size = sizeof(struct urtw_softc)
+};
+
+DRIVER_MODULE(urtw, uhub, urtw_driver, NULL, NULL);
+MODULE_DEPEND(urtw, wlan, 1, 1, 1);
+MODULE_DEPEND(urtw, usb, 1, 1, 1);
+MODULE_VERSION(urtw, 1);
+USB_PNP_HOST_INFO(urtw_devs);
diff --git a/sys/dev/usb/wlan/if_urtwreg.h b/sys/dev/usb/wlan/if_urtwreg.h
new file mode 100644
index 000000000000..0e7c0f6f1ba8
--- /dev/null
+++ b/sys/dev/usb/wlan/if_urtwreg.h
@@ -0,0 +1,432 @@
+
+/*-
+ * 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_INDEX 0
+#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_MAR 0x0008 /* 6 byte */
+#define URTW_RXFIFO_CNT 0x0010 /* 1 byte */
+#define URTW_TXFIFO_CNT 0x0012 /* 1 byte */
+#define URTW_BQREQ 0x0013 /* 1 byte */
+#define URTW_TSFT 0x0018 /* 6 byte */
+#define URTW_TLPDA 0x0020 /* 4 byte */
+#define URTW_TNPDA 0x0024 /* 4 byte */
+#define URTW_THPDA 0x0028 /* 4 byte */
+#define URTW_BRSR 0x002c /* 2 byte */
+#define URTW_BRSR_MBR_8185 (0x0fff)
+#define URTW_8187B_EIFS 0x002d /* 1 byte for 8187B */
+#define URTW_BSSID 0x002e /* 6 byte */
+#define URTW_BRSR_8187B 0x0034 /* 2 byte for 8187B */
+#define URTW_RESP_RATE 0x0034 /* 1 byte for 8187L */
+#define URTW_RESP_MAX_RATE_SHIFT (4)
+#define URTW_RESP_MIN_RATE_SHIFT (0)
+#define URTW_EIFS 0x0035 /* 1 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_INTR_MASK 0x003c /* 2 byte */
+#define URTW_INTR_STATUS 0x003e /* 2 byte */
+#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_DISCW (1 << 20)
+#define URTW_TX_SWPLCPLEN (1 << 24)
+#define URTW_TX_R8187vD (5 << 25)
+#define URTW_TX_R8187vD_B (6 << 25)
+#define URTW_TX_HWMASK (7 << 25)
+#define URTW_TX_DISREQQSIZE (1 << 28)
+#define URTW_TX_HW_SEQNUM (1 << 30)
+#define URTW_TX_CWMIN (1U << 31)
+#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 (1U << 31)
+#define URTW_INT_TIMEOUT 0x0048 /* 4 byte */
+#define URTW_INT_TBDA 0x004c /* 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_CONFIG0 0x0051 /* 1 byte */
+#define URTW_CONFIG1 0x0052 /* 1 byte */
+#define URTW_CONFIG2 0x0053 /* 1 byte */
+#define URTW_ANAPARAM 0x0054 /* 4 byte */
+#define URTW_8225_ANAPARAM_ON (0xa0000a59)
+#define URTW_8225_ANAPARAM_OFF (0xa00beb59)
+#define URTW_8187B_8225_ANAPARAM_ON (0x45090658)
+#define URTW_8187B_8225_ANAPARAM_OFF (0x55480658)
+#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_MSR_LINK_ENEDCA (1 << 4)
+#define URTW_CONFIG3 0x0059 /* 1 byte */
+#define URTW_CONFIG3_ANAPARAM_WRITE (0x40)
+#define URTW_CONFIG3_GNT_SELECT (0x80)
+#define URTW_CONFIG3_ANAPARAM_W_SHIFT (6)
+#define URTW_CONFIG4 0x005a /* 1 byte */
+#define URTW_CONFIG4_VCOOFF (1 << 7)
+#define URTW_TESTR 0x005b /* 1 byte */
+#define URTW_PSR 0x005e /* 1 byte */
+#define URTW_SECURITY 0x005f /* 1 byte */
+#define URTW_ANAPARAM2 0x0060 /* 4 byte */
+#define URTW_8225_ANAPARAM2_ON (0x860c7312)
+#define URTW_8225_ANAPARAM2_OFF (0x840dec11)
+#define URTW_8187B_8225_ANAPARAM2_ON (0x727f3f52)
+#define URTW_8187B_8225_ANAPARAM2_OFF (0x72003f50)
+#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_DELAY 0x0078 /* 1 byte */
+#define URTW_CARRIER_SCOUNT 0x0079 /* 1 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_HSSI_PARA 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_RATE_FALLBACK_ENABLE (0x80)
+#define URTW_ACM_CONTROL 0x00bf /* 1 byte */
+#define URTW_CONFIG5 0x00d8 /* 1 byte */
+#define URTW_TXDMA_POLLING 0x00d9 /* 1 byte */
+#define URTW_CWR 0x00dc /* 2 byte */
+#define URTW_RETRY_CTR 0x00de /* 1 byte */
+#define URTW_INT_MIG 0x00e2 /* 2 byte */
+#define URTW_RDSAR 0x00e4 /* 4 byte */
+#define URTW_TID_AC_MAP 0x00e8 /* 2 byte */
+#define URTW_ANAPARAM3 0x00ee /* 1 byte */
+#define URTW_8187B_8225_ANAPARAM3_ON (0x0)
+#define URTW_8187B_8225_ANAPARAM3_OFF (0x0)
+#define URTW_8187B_AC_VO 0x00f0 /* 4 byte for 8187B */
+#define URTW_FEMR 0x00f4 /* 2 byte */
+#define URTW_8187B_AC_VI 0x00f4 /* 4 byte for 8187B */
+#define URTW_8187B_AC_BE 0x00f8 /* 4 byte for 8187B */
+#define URTW_TALLY_CNT 0x00fa /* 2 byte */
+#define URTW_TALLY_SEL 0x00fc /* 1 byte */
+#define URTW_8187B_AC_BK 0x00fc /* 4 byte for 8187B */
+#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_CHANPLAN 0x03
+#define URTW_EPROM_CHANPLAN_BY_HW (0x80)
+#define URTW_EPROM_TXPW_BASE 0x05
+#define URTW_EPROM_RFCHIPID 0x06
+#define URTW_EPROM_RFCHIPID_RTL8225U (5)
+#define URTW_EPROM_RFCHIPID_RTL8225Z2 (6)
+#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
+
+#define URTW_ASIFS_TIME 10
+#define URTW_ACKCTS_LEN 14 /* len for ACK and CTS */
+
+struct urtw_8187b_rxhdr {
+ uint32_t flag;
+#define URTW_RX_FLAG_LEN /* 0 ~ 11 bits */
+#define URTW_RX_FLAG_ICV_ERR (1 << 12)
+#define URTW_RX_FLAG_CRC32_ERR (1 << 13)
+#define URTW_RX_FLAG_PM (1 << 14)
+#define URTW_RX_FLAG_RX_ERR (1 << 15)
+#define URTW_RX_FLAG_BCAST (1 << 16)
+#define URTW_RX_FLAG_PAM (1 << 17)
+#define URTW_RX_FLAG_MCAST (1 << 18)
+#define URTW_RX_FLAG_QOS (1 << 19) /* only for RTL8187B */
+#define URTW_RX_FLAG_RXRATE /* 20 ~ 23 bits */
+#define URTW_RX_FLAG_RXRATE_SHIFT 20
+#define URTW_RX_FLAG_TRSW (1 << 24) /* only for RTL8187B */
+#define URTW_RX_FLAG_SPLCP (1 << 25)
+#define URTW_RX_FLAG_FOF (1 << 26)
+#define URTW_RX_FLAG_DMA_FAIL (1 << 27)
+#define URTW_RX_FLAG_LAST (1 << 28)
+#define URTW_RX_FLAG_FIRST (1 << 29)
+#define URTW_RX_FLAG_EOR (1 << 30)
+#define URTW_RX_FLAG_OWN (1U << 31)
+ uint64_t mactime;
+ uint8_t noise;
+ uint8_t rssi;
+#define URTW_RX_RSSI /* 0 ~ 6 bits */
+#define URTW_RX_RSSI_MASK 0x3f
+#define URTW_RX_ANTENNA (1 << 7)
+ uint8_t agc;
+ uint8_t flag2;
+#define URTW_RX_FLAG2_DECRYPTED (1 << 0)
+#define URTW_RX_FLAG2_WAKUP (1 << 1)
+#define URTW_RX_FLAG2_SHIFT (1 << 2)
+#define URTW_RX_FLAG2_RSVD0 /* 3 ~ 7 bits */
+ uint16_t flag3;
+#define URTW_RX_FLAG3_NUMMCSI /* 0 ~ 3 bits */
+#define URTW_RX_FLAG3_SNR_L2E /* 4 ~ 9 bits */
+#define URTW_RX_FLAG3_CFO_BIAS /* 10 ~ 15 bits */
+ int8_t pwdb;
+ uint8_t fot;
+} __packed;
+
+struct urtw_8187b_txhdr {
+ uint32_t flag;
+#define URTW_TX_FLAG_PKTLEN /* 0 ~ 11 bits */
+#define URTW_TX_FLAG_RSVD0 /* 12 ~ 14 bits */
+#define URTW_TX_FLAG_NO_ENC (1 << 15)
+#define URTW_TX_FLAG_SPLCP (1 << 16)
+#define URTW_TX_FLAG_MOREFRAG (1 << 17)
+#define URTW_TX_FLAG_CTS (1 << 18)
+#define URTW_TX_FLAG_RTSRATE /* 19 ~ 22 bits */
+#define URTW_TX_FLAG_RTSRATE_SHIFT 19
+#define URTW_TX_FLAG_RTS (1 << 23)
+#define URTW_TX_FLAG_TXRATE /* 24 ~ 27 bits */
+#define URTW_TX_FLAG_TXRATE_SHIFT 24
+#define URTW_TX_FLAG_LAST (1 << 28)
+#define URTW_TX_FLAG_FIRST (1 << 29)
+#define URTW_TX_FLAG_DMA (1 << 30)
+#define URTW_TX_FLAG_OWN (1U << 31)
+ uint16_t rtsdur;
+ uint16_t len;
+#define URTW_TX_LEN /* 0 ~ 14 bits */
+#define URTW_TX_LEN_EXT (1 << 15)
+ uint32_t bufaddr;
+ uint16_t flag1;
+#define URTW_TX_FLAG1_RXLEN /* 0 ~ 11 bits */
+#define URTW_TX_FLAG1_RSVD0 /* 12 ~ 14 bits */
+#define URTW_TX_FLAG1_MICCAL (1 << 15)
+ uint16_t txdur;
+ uint32_t nextdescaddr;
+ uint8_t rtsagc;
+ uint8_t retry;
+ uint16_t flag2;
+#define URTW_TX_FLAG2_RTDB (1 << 0)
+#define URTW_TX_FLAG2_NOACM (1 << 1)
+#define URTW_TX_FLAG2_PIFS (1 << 2)
+#define URTW_TX_FLAG2_RSVD0 /* 3 ~ 6 bits */
+#define URTW_TX_FLAG2_RTSRATEFALLBACK /* 7 ~ 10 bits */
+#define URTW_TX_FLAG2_RATEFALLBACK /* 11 ~ 15 bits */
+ uint16_t delaybound;
+ uint16_t flag3;
+#define URTW_TX_FLAG3_RSVD0 /* 0 ~ 3 bits */
+#define URTW_TX_FLAG3_AGC /* 4 ~ 11 bits */
+#define URTW_TX_FLAG3_ANTENNA (1 << 12)
+#define URTW_TX_FLAG3_SPC /* 13 ~ 14 bits */
+#define URTW_TX_FLAG3_RSVD1 (1 << 15)
+ uint32_t flag4;
+#define URTW_TX_FLAG4_LENADJUST /* 0 ~ 1 bits */
+#define URTW_TX_FLAG4_RSVD0 (1 << 2)
+#define URTW_TX_FLAG4_TPCDESEN (1 << 3)
+#define URTW_TX_FLAG4_TPCPOLARITY /* 4 ~ 5 bits */
+#define URTW_TX_FLAG4_TPCEN (1 << 6)
+#define URTW_TX_FLAG4_PTEN (1 << 7)
+#define URTW_TX_FLAG4_BCKEY /* 8 ~ 13 bits */
+#define URTW_TX_FLAG4_ENBCKEY (1 << 14)
+#define URTW_TX_FLAG4_ENPMPD (1 << 15)
+#define URTW_TX_FLAG4_FRAGQSZ /* 16 ~ 31 bits */
+} __packed;
+
+struct urtw_8187l_rxhdr {
+ uint32_t flag;
+ uint8_t noise;
+ uint8_t rssi;
+#define URTW_RX_8187L_RSSI /* 0 ~ 6 bits */
+#define URTW_RX_8187L_RSSI_MASK 0x3f
+#define URTW_RX_8187L_ANTENNA (1 << 7)
+ uint8_t agc;
+ uint8_t flag2;
+#define URTW_RX_8187L_DECRYPTED (1 << 0)
+#define URTW_RX_8187L_WAKEUP (1 << 1)
+#define URTW_RX_8187L_SHIFT (1 << 2)
+#define URTW_RX_8187L_RSVD0 /* 3 ~ 7 bits */
+ uint64_t mactime;
+} __packed;
+
+struct urtw_8187l_txhdr {
+ uint32_t flag;
+ uint16_t rtsdur;
+ uint16_t len;
+ uint32_t retry;
+} __packed;
diff --git a/sys/dev/usb/wlan/if_urtwvar.h b/sys/dev/usb/wlan/if_urtwvar.h
new file mode 100644
index 000000000000..3881bc73cf76
--- /dev/null
+++ b/sys/dev/usb/wlan/if_urtwvar.h
@@ -0,0 +1,185 @@
+
+/*-
+ * 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.
+ */
+
+enum {
+ URTW_8187B_BULK_RX,
+ URTW_8187B_BULK_TX_STATUS,
+ URTW_8187B_BULK_TX_BE,
+ URTW_8187B_BULK_TX_BK,
+ URTW_8187B_BULK_TX_VI,
+ URTW_8187B_BULK_TX_VO,
+ URTW_8187B_BULK_TX_EP12,
+ URTW_8187B_N_XFERS = 7
+};
+
+enum {
+ URTW_8187L_BULK_RX,
+ URTW_8187L_BULK_TX_LOW,
+ URTW_8187L_BULK_TX_NORMAL,
+ URTW_8187L_N_XFERS = 3
+};
+
+/* XXX no definition at net80211? */
+#define URTW_MAX_CHANNELS 15
+
+struct urtw_data {
+ struct urtw_softc *sc;
+ uint8_t *buf;
+ uint16_t buflen;
+ struct mbuf *m;
+ struct ieee80211_node *ni; /* NB: tx only */
+ STAILQ_ENTRY(urtw_data) next;
+};
+typedef STAILQ_HEAD(, urtw_data) urtw_datahead;
+
+#define URTW_RX_DATA_LIST_COUNT 4
+#define URTW_TX_DATA_LIST_COUNT 16
+#define URTW_RX_MAXSIZE 0x9c4
+#define URTW_TX_MAXSIZE 0x9c4
+#define URTW_TX_MAXRETRY 11
+
+struct urtw_rx_radiotap_header {
+ struct ieee80211_radiotap_header wr_ihdr;
+ uint64_t wr_tsf;
+ uint8_t wr_flags;
+ uint8_t wr_pad;
+ uint16_t wr_chan_freq;
+ uint16_t wr_chan_flags;
+ int8_t wr_dbm_antsignal;
+} __packed __aligned(8);
+
+#define URTW_RX_RADIOTAP_PRESENT \
+ ((1 << IEEE80211_RADIOTAP_TSFT) | \
+ (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;
+ uint8_t wt_pad;
+ 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_stats {
+ unsigned txrates[12];
+};
+
+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 ieee80211com sc_ic;
+ struct mbufq sc_snd;
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+ struct mtx sc_mtx;
+ void *sc_tx_dma_buf;
+
+ int sc_debug;
+ int sc_flags;
+#define URTW_INIT_ONCE (1 << 1)
+#define URTW_RTL8187B (1 << 2)
+#define URTW_RTL8187B_REV_B (1 << 3)
+#define URTW_RTL8187B_REV_D (1 << 4)
+#define URTW_RTL8187B_REV_E (1 << 5)
+#define URTW_DETACHED (1 << 6)
+#define URTW_RUNNING (1 << 7)
+ enum ieee80211_state sc_state;
+
+ int sc_epromtype;
+#define URTW_EEPROM_93C46 0
+#define URTW_EEPROM_93C56 1
+ uint8_t sc_crcmon;
+
+ struct ieee80211_channel *sc_curchan;
+
+ /* for RF */
+ usb_error_t (*sc_rf_init)(struct urtw_softc *);
+ usb_error_t (*sc_rf_set_chan)(struct urtw_softc *,
+ int);
+ usb_error_t (*sc_rf_set_sens)(struct urtw_softc *,
+ int);
+ usb_error_t (*sc_rf_stop)(struct urtw_softc *);
+ uint8_t sc_rfchip;
+ uint32_t sc_max_sens;
+ uint32_t sc_sens;
+ /* for LED */
+ struct usb_callout sc_led_ch;
+ struct task sc_led_task;
+ 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 */
+ struct usb_xfer *sc_xfer[URTW_8187B_N_XFERS];
+#define URTW_PRIORITY_LOW 0
+#define URTW_PRIORITY_NORMAL 1
+#define URTW_DATA_TIMEOUT 10000 /* 10 sec */
+#define URTW_8187B_TXPIPE_BE 0x6 /* best effort */
+#define URTW_8187B_TXPIPE_BK 0x7 /* background */
+#define URTW_8187B_TXPIPE_VI 0x5 /* video */
+#define URTW_8187B_TXPIPE_VO 0x4 /* voice */
+#define URTW_8187B_TXPIPE_MAX 4
+ struct urtw_data sc_rx[URTW_RX_DATA_LIST_COUNT];
+ urtw_datahead sc_rx_active;
+ urtw_datahead sc_rx_inactive;
+ struct urtw_data sc_tx[URTW_TX_DATA_LIST_COUNT];
+ urtw_datahead sc_tx_active;
+ urtw_datahead sc_tx_inactive;
+ urtw_datahead sc_tx_pending;
+ 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;
+
+ uint8_t sc_acmctl;
+ uint64_t sc_txstatus; /* only for 8187B */
+ struct task sc_updateslot_task;
+
+ struct urtw_stats sc_stats;
+
+ struct urtw_rx_radiotap_header sc_rxtap;
+ struct urtw_tx_radiotap_header sc_txtap;
+};
+
+#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/dev/usb/wlan/if_zyd.c b/sys/dev/usb/wlan/if_zyd.c
new file mode 100644
index 000000000000..1a698caef3c5
--- /dev/null
+++ b/sys/dev/usb/wlan/if_zyd.c
@@ -0,0 +1,2911 @@
+/* $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 $ */
+
+/*-
+ * 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 "opt_wlan.h"
+
+#include <sys/param.h>
+#include <sys/sockio.h>
+#include <sys/sysctl.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.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 <net/bpf.h>
+#include <net/if.h>
+#include <net/if_var.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 <net80211/ieee80211_ratectl.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include "usbdevs.h"
+
+#include <dev/usb/wlan/if_zydreg.h>
+#include <dev/usb/wlan/if_zydfw.h>
+
+#ifdef USB_DEBUG
+static int zyd_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, zyd, CTLFLAG_RW | CTLFLAG_MPSAFE, 0,
+ "USB zyd");
+SYSCTL_INT(_hw_usb_zyd, OID_AUTO, debug, CTLFLAG_RWTUN, &zyd_debug, 0,
+ "zyd debug level");
+
+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_CMD = 0x00000200, /* fw commands */
+ ZYD_DEBUG_ANY = 0xffffffff
+};
+#define DPRINTF(sc, m, fmt, ...) do { \
+ if (zyd_debug & (m)) \
+ printf("%s: " fmt, __func__, ## __VA_ARGS__); \
+} while (0)
+#else
+#define DPRINTF(sc, m, fmt, ...) do { \
+ (void) sc; \
+} while (0)
+#endif
+
+#define zyd_do_request(sc,req,data) \
+ usbd_do_request_flags((sc)->sc_udev, &(sc)->sc_mtx, req, data, 0, NULL, 5000)
+
+static device_probe_t zyd_match;
+static device_attach_t zyd_attach;
+static device_detach_t zyd_detach;
+
+static usb_callback_t zyd_intr_read_callback;
+static usb_callback_t zyd_intr_write_callback;
+static usb_callback_t zyd_bulk_read_callback;
+static usb_callback_t zyd_bulk_write_callback;
+
+static struct ieee80211vap *zyd_vap_create(struct ieee80211com *,
+ const char [IFNAMSIZ], int, enum ieee80211_opmode, int,
+ const uint8_t [IEEE80211_ADDR_LEN],
+ const uint8_t [IEEE80211_ADDR_LEN]);
+static void zyd_vap_delete(struct ieee80211vap *);
+static void zyd_tx_free(struct zyd_tx_data *, int);
+static void zyd_setup_tx_list(struct zyd_softc *);
+static void zyd_unsetup_tx_list(struct zyd_softc *);
+static int zyd_newstate(struct ieee80211vap *, enum ieee80211_state, int);
+static int zyd_cmd(struct zyd_softc *, uint16_t, const void *, int,
+ void *, int, 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(struct zyd_softc *);
+static void zyd_update_mcast(struct ieee80211com *);
+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_rx_data(struct usb_xfer *, int, uint16_t);
+static int zyd_tx_start(struct zyd_softc *, struct mbuf *,
+ struct ieee80211_node *);
+static int zyd_transmit(struct ieee80211com *, struct mbuf *);
+static void zyd_start(struct zyd_softc *);
+static int zyd_raw_xmit(struct ieee80211_node *, struct mbuf *,
+ const struct ieee80211_bpf_params *);
+static void zyd_parent(struct ieee80211com *);
+static void zyd_init_locked(struct zyd_softc *);
+static void zyd_stop(struct zyd_softc *);
+static int zyd_loadfirmware(struct zyd_softc *);
+static void zyd_scan_start(struct ieee80211com *);
+static void zyd_scan_end(struct ieee80211com *);
+static void zyd_getradiocaps(struct ieee80211com *, int, int *,
+ struct ieee80211_channel[]);
+static void zyd_set_channel(struct ieee80211com *);
+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_gct_mode(struct zyd_rf *);
+static int zyd_gct_set_channel_synth(struct zyd_rf *, int, int);
+static int zyd_gct_write(struct zyd_rf *, uint16_t);
+static int zyd_gct_txgain(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 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 0
+#define ZYD_ZD1211B 1
+
+#define ZYD_ZD1211_DEV(v,p) \
+ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, ZYD_ZD1211) }
+#define ZYD_ZD1211B_DEV(v,p) \
+ { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, ZYD_ZD1211B) }
+static const STRUCT_USB_HOST_ID zyd_devs[] = {
+ /* ZYD_ZD1211 */
+ 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_ZD1211B */
+ ZYD_ZD1211B_DEV(ACCTON, SMCWUSBG_NF),
+ 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),
+ 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, G202),
+ ZYD_ZD1211B_DEV(ZYXEL, G220V2)
+};
+
+static const struct usb_config zyd_config[ZYD_N_TRANSFER] = {
+ [ZYD_BULK_WR] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = ZYD_MAX_TXBUFSZ,
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = zyd_bulk_write_callback,
+ .ep_index = 0,
+ .timeout = 10000, /* 10 seconds */
+ },
+ [ZYD_BULK_RD] = {
+ .type = UE_BULK,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = ZYX_MAX_RXBUFSZ,
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = zyd_bulk_read_callback,
+ .ep_index = 0,
+ },
+ [ZYD_INTR_WR] = {
+ .type = UE_BULK_INTR,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = sizeof(struct zyd_cmd),
+ .flags = {.pipe_bof = 1,.force_short_xfer = 1,},
+ .callback = zyd_intr_write_callback,
+ .timeout = 1000, /* 1 second */
+ .ep_index = 1,
+ },
+ [ZYD_INTR_RD] = {
+ .type = UE_INTERRUPT,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = sizeof(struct zyd_cmd),
+ .flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+ .callback = zyd_intr_read_callback,
+ },
+};
+#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 int
+zyd_match(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+ if (uaa->usb_mode != USB_MODE_HOST)
+ return (ENXIO);
+ if (uaa->info.bConfigIndex != ZYD_CONFIG_INDEX)
+ return (ENXIO);
+ if (uaa->info.bIfaceIndex != ZYD_IFACE_INDEX)
+ return (ENXIO);
+
+ return (usbd_lookup_id_by_uaa(zyd_devs, sizeof(zyd_devs), uaa));
+}
+
+static int
+zyd_attach(device_t dev)
+{
+ struct usb_attach_arg *uaa = device_get_ivars(dev);
+ struct zyd_softc *sc = device_get_softc(dev);
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint8_t iface_index;
+ int error;
+
+ if (uaa->info.bcdDevice < 0x4330) {
+ device_printf(dev, "device version mismatch: 0x%X "
+ "(only >= 43.30 supported)\n",
+ uaa->info.bcdDevice);
+ return (EINVAL);
+ }
+
+ device_set_usb_desc(dev);
+ sc->sc_dev = dev;
+ sc->sc_udev = uaa->device;
+ sc->sc_macrev = USB_GET_DRIVER_INFO(uaa);
+
+ mtx_init(&sc->sc_mtx, device_get_nameunit(sc->sc_dev),
+ MTX_NETWORK_LOCK, MTX_DEF);
+ STAILQ_INIT(&sc->sc_rqh);
+ mbufq_init(&sc->sc_snd, ifqmaxlen);
+
+ iface_index = ZYD_IFACE_INDEX;
+ error = usbd_transfer_setup(uaa->device,
+ &iface_index, sc->sc_xfer, zyd_config,
+ ZYD_N_TRANSFER, sc, &sc->sc_mtx);
+ if (error) {
+ device_printf(dev, "could not allocate USB transfers, "
+ "err=%s\n", usbd_errstr(error));
+ goto detach;
+ }
+
+ ZYD_LOCK(sc);
+ if ((error = zyd_get_macaddr(sc)) != 0) {
+ device_printf(sc->sc_dev, "could not read EEPROM\n");
+ ZYD_UNLOCK(sc);
+ goto detach;
+ }
+ ZYD_UNLOCK(sc);
+
+ ic->ic_softc = sc;
+ ic->ic_name = device_get_nameunit(dev);
+ 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 */
+ ;
+
+ zyd_getradiocaps(ic, IEEE80211_CHAN_MAX, &ic->ic_nchans,
+ ic->ic_channels);
+
+ ieee80211_ifattach(ic);
+ ic->ic_raw_xmit = zyd_raw_xmit;
+ ic->ic_scan_start = zyd_scan_start;
+ ic->ic_scan_end = zyd_scan_end;
+ ic->ic_getradiocaps = zyd_getradiocaps;
+ 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;
+ ic->ic_update_promisc = zyd_update_mcast;
+ ic->ic_parent = zyd_parent;
+ ic->ic_transmit = zyd_transmit;
+
+ ieee80211_radiotap_attach(ic,
+ &sc->sc_txtap.wt_ihdr, sizeof(sc->sc_txtap),
+ ZYD_TX_RADIOTAP_PRESENT,
+ &sc->sc_rxtap.wr_ihdr, sizeof(sc->sc_rxtap),
+ ZYD_RX_RADIOTAP_PRESENT);
+
+ if (bootverbose)
+ ieee80211_announce(ic);
+
+ return (0);
+
+detach:
+ zyd_detach(dev);
+ return (ENXIO); /* failure */
+}
+
+static void
+zyd_drain_mbufq(struct zyd_softc *sc)
+{
+ struct mbuf *m;
+ struct ieee80211_node *ni;
+
+ ZYD_LOCK_ASSERT(sc, MA_OWNED);
+ while ((m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
+ ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
+ m->m_pkthdr.rcvif = NULL;
+ ieee80211_free_node(ni);
+ m_freem(m);
+ }
+}
+
+static int
+zyd_detach(device_t dev)
+{
+ struct zyd_softc *sc = device_get_softc(dev);
+ struct ieee80211com *ic = &sc->sc_ic;
+ unsigned x;
+
+ /*
+ * Prevent further allocations from RX/TX data
+ * lists and ioctls:
+ */
+ ZYD_LOCK(sc);
+ sc->sc_flags |= ZYD_FLAG_DETACHED;
+ zyd_drain_mbufq(sc);
+ STAILQ_INIT(&sc->tx_q);
+ STAILQ_INIT(&sc->tx_free);
+ ZYD_UNLOCK(sc);
+
+ /* drain USB transfers */
+ for (x = 0; x != ZYD_N_TRANSFER; x++)
+ usbd_transfer_drain(sc->sc_xfer[x]);
+
+ /* free TX list, if any */
+ ZYD_LOCK(sc);
+ zyd_unsetup_tx_list(sc);
+ ZYD_UNLOCK(sc);
+
+ /* free USB transfers and some data buffers */
+ usbd_transfer_unsetup(sc->sc_xfer, ZYD_N_TRANSFER);
+
+ if (ic->ic_softc == sc)
+ ieee80211_ifdetach(ic);
+ mtx_destroy(&sc->sc_mtx);
+
+ return (0);
+}
+
+static struct ieee80211vap *
+zyd_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
+ enum ieee80211_opmode 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 = malloc(sizeof(struct zyd_vap), M_80211_VAP, M_WAITOK | M_ZERO);
+ vap = &zvp->vap;
+
+ /* enable s/w bmiss handling for sta mode */
+ if (ieee80211_vap_setup(ic, vap, name, unit, opmode,
+ flags | IEEE80211_CLONE_NOBEACONS, bssid) != 0) {
+ /* out of memory */
+ free(zvp, M_80211_VAP);
+ return (NULL);
+ }
+
+ /* override state transition machine */
+ zvp->newstate = vap->iv_newstate;
+ vap->iv_newstate = zyd_newstate;
+
+ ieee80211_ratectl_init(vap);
+ ieee80211_ratectl_setinterval(vap, 1000 /* 1 sec */);
+
+ /* complete setup */
+ ieee80211_vap_attach(vap, ieee80211_media_change,
+ ieee80211_media_status, mac);
+ ic->ic_opmode = opmode;
+ return (vap);
+}
+
+static void
+zyd_vap_delete(struct ieee80211vap *vap)
+{
+ struct zyd_vap *zvp = ZYD_VAP(vap);
+
+ ieee80211_ratectl_deinit(vap);
+ ieee80211_vap_detach(vap);
+ free(zvp, M_80211_VAP);
+}
+
+static void
+zyd_tx_free(struct zyd_tx_data *data, int txerr)
+{
+ struct zyd_softc *sc = data->sc;
+
+ if (data->m != NULL) {
+ ieee80211_tx_complete(data->ni, data->m, txerr);
+ data->m = NULL;
+ data->ni = NULL;
+ }
+ STAILQ_INSERT_TAIL(&sc->tx_free, data, next);
+ sc->tx_nfree++;
+}
+
+static void
+zyd_setup_tx_list(struct zyd_softc *sc)
+{
+ struct zyd_tx_data *data;
+ int i;
+
+ sc->tx_nfree = 0;
+ STAILQ_INIT(&sc->tx_q);
+ STAILQ_INIT(&sc->tx_free);
+
+ for (i = 0; i < ZYD_TX_LIST_CNT; i++) {
+ data = &sc->tx_data[i];
+
+ data->sc = sc;
+ STAILQ_INSERT_TAIL(&sc->tx_free, data, next);
+ sc->tx_nfree++;
+ }
+}
+
+static void
+zyd_unsetup_tx_list(struct zyd_softc *sc)
+{
+ struct zyd_tx_data *data;
+ int i;
+
+ /* make sure any subsequent use of the queues will fail */
+ sc->tx_nfree = 0;
+ STAILQ_INIT(&sc->tx_q);
+ STAILQ_INIT(&sc->tx_free);
+
+ /* free up all node references and mbufs */
+ for (i = 0; i < ZYD_TX_LIST_CNT; i++) {
+ data = &sc->tx_data[i];
+
+ if (data->m != NULL) {
+ m_freem(data->m);
+ data->m = NULL;
+ }
+ if (data->ni != NULL) {
+ ieee80211_free_node(data->ni);
+ data->ni = NULL;
+ }
+ }
+}
+
+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_softc;
+ int error;
+
+ DPRINTF(sc, ZYD_DEBUG_STATE, "%s: %s -> %s\n", __func__,
+ ieee80211_state_name[vap->iv_state],
+ ieee80211_state_name[nstate]);
+
+ IEEE80211_UNLOCK(ic);
+ ZYD_LOCK(sc);
+ switch (nstate) {
+ 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)
+ break;
+
+ /* make data LED blink upon Tx */
+ zyd_write32_m(sc, sc->sc_fwbase + ZYD_FW_LINK_STATUS, 1);
+
+ IEEE80211_ADDR_COPY(sc->sc_bssid, vap->iv_bss->ni_bssid);
+ zyd_set_bssid(sc, sc->sc_bssid);
+ break;
+ default:
+ break;
+ }
+fail:
+ ZYD_UNLOCK(sc);
+ IEEE80211_LOCK(ic);
+ return (zvp->newstate(vap, nstate, arg));
+}
+
+/*
+ * Callback handler for interrupt transfer
+ */
+static void
+zyd_intr_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct zyd_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ struct ieee80211_node *ni;
+ struct zyd_cmd *cmd = &sc->sc_ibuf;
+ struct usb_page_cache *pc;
+ int datalen;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, cmd, sizeof(*cmd));
+
+ switch (le16toh(cmd->code)) {
+ case ZYD_NOTIF_RETRYSTATUS:
+ {
+ struct zyd_notif_retry *retry =
+ (struct zyd_notif_retry *)cmd->data;
+ uint16_t count = le16toh(retry->count);
+
+ 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),
+ count & 0xff, 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) {
+ struct ieee80211_ratectl_tx_status *txs =
+ &sc->sc_txs;
+ int retrycnt = count & 0xff;
+
+ txs->flags =
+ IEEE80211_RATECTL_STATUS_LONG_RETRY;
+ txs->long_retries = retrycnt;
+ if (count & 0x100) {
+ txs->status =
+ IEEE80211_RATECTL_TX_FAIL_LONG;
+ } else {
+ txs->status =
+ IEEE80211_RATECTL_TX_SUCCESS;
+ }
+
+ ieee80211_ratectl_tx_complete(ni, txs);
+ ieee80211_free_node(ni);
+ }
+ if (count & 0x100)
+ /* too many retries */
+ if_inc_counter(vap->iv_ifp, IFCOUNTER_OERRORS,
+ 1);
+ break;
+ }
+ case ZYD_NOTIF_IORD:
+ {
+ struct zyd_rq *rqp;
+
+ if (le16toh(*(uint16_t *)cmd->data) == ZYD_CR_INTERRUPT)
+ break; /* HMAC interrupt */
+
+ datalen = actlen - sizeof(cmd->code);
+ datalen -= 2; /* XXX: padding? */
+
+ STAILQ_FOREACH(rqp, &sc->sc_rqh, rq) {
+ int i;
+ int count;
+
+ if (rqp->olen != datalen)
+ continue;
+ count = rqp->olen / sizeof(struct zyd_pair);
+ for (i = 0; i < count; i++) {
+ if (*(((const uint16_t *)rqp->idata) + i) !=
+ (((struct zyd_pair *)cmd->data) + i)->reg)
+ break;
+ }
+ if (i != count)
+ continue;
+ /* copy answer into caller-supplied buffer */
+ memcpy(rqp->odata, cmd->data, rqp->olen);
+ DPRINTF(sc, ZYD_DEBUG_CMD,
+ "command %p complete, data = %*D \n",
+ rqp, rqp->olen, (char *)rqp->odata, ":");
+ wakeup(rqp); /* wakeup caller */
+ break;
+ }
+ if (rqp == NULL) {
+ device_printf(sc->sc_dev,
+ "unexpected IORD notification %*D\n",
+ datalen, cmd->data, ":");
+ }
+ break;
+ }
+ default:
+ device_printf(sc->sc_dev, "unknown notification %x\n",
+ le16toh(cmd->code));
+ }
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ DPRINTF(sc, ZYD_DEBUG_CMD, "error = %s\n",
+ usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static void
+zyd_intr_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct zyd_softc *sc = usbd_xfer_softc(xfer);
+ struct zyd_rq *rqp, *cmd;
+ struct usb_page_cache *pc;
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ cmd = usbd_xfer_get_priv(xfer);
+ DPRINTF(sc, ZYD_DEBUG_CMD, "command %p transferred\n", cmd);
+ STAILQ_FOREACH(rqp, &sc->sc_rqh, rq) {
+ /* Ensure the cached rq pointer is still valid */
+ if (rqp == cmd &&
+ (rqp->flags & ZYD_CMD_FLAG_READ) == 0)
+ wakeup(rqp); /* wakeup caller */
+ }
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ STAILQ_FOREACH(rqp, &sc->sc_rqh, rq) {
+ if (rqp->flags & ZYD_CMD_FLAG_SENT)
+ continue;
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, rqp->cmd, rqp->ilen);
+
+ usbd_xfer_set_frame_len(xfer, 0, rqp->ilen);
+ usbd_xfer_set_priv(xfer, rqp);
+ rqp->flags |= ZYD_CMD_FLAG_SENT;
+ usbd_transfer_submit(xfer);
+ break;
+ }
+ break;
+
+ default: /* Error */
+ DPRINTF(sc, ZYD_DEBUG_ANY, "error = %s\n",
+ usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static int
+zyd_cmd(struct zyd_softc *sc, uint16_t code, const void *idata, int ilen,
+ void *odata, int olen, int flags)
+{
+ struct zyd_cmd cmd;
+ struct zyd_rq rq;
+ int error;
+
+ if (ilen > (int)sizeof(cmd.data))
+ return (EINVAL);
+
+ cmd.code = htole16(code);
+ memcpy(cmd.data, idata, ilen);
+ DPRINTF(sc, ZYD_DEBUG_CMD, "sending cmd %p = %*D\n",
+ &rq, ilen, idata, ":");
+
+ rq.cmd = &cmd;
+ rq.idata = idata;
+ rq.odata = odata;
+ rq.ilen = sizeof(uint16_t) + ilen;
+ rq.olen = olen;
+ rq.flags = flags;
+ STAILQ_INSERT_TAIL(&sc->sc_rqh, &rq, rq);
+ usbd_transfer_start(sc->sc_xfer[ZYD_INTR_RD]);
+ usbd_transfer_start(sc->sc_xfer[ZYD_INTR_WR]);
+
+ /* wait at most one second for command reply */
+ error = mtx_sleep(&rq, &sc->sc_mtx, 0 , "zydcmd", hz);
+ if (error)
+ device_printf(sc->sc_dev, "command timeout\n");
+ STAILQ_REMOVE(&sc->sc_rqh, &rq, zyd_rq, rq);
+ DPRINTF(sc, ZYD_DEBUG_CMD, "finsihed cmd %p, error = %d \n",
+ &rq, error);
+
+ 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, &reg, 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)
+{
+ 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 < nitems(phyini); i++) {
+ zyd_write16_m(sc, phyini[i].reg, phyini[i].val);
+ }
+
+ /* init RFMD radio */
+ for (i = 0; i < nitems(rfini); i++) {
+ if ((error = zyd_rfwrite(sc, rfini[i])) != 0)
+ return (error);
+ }
+fail:
+ return (error);
+}
+
+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)
+{
+ 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 < nitems(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 < nitems(phy2230s); i++)
+ zyd_write16_m(sc, phy2230s[i].reg, phy2230s[i].val);
+ }
+
+ /* init AL2230 radio */
+ for (i = 0; i < nitems(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 < nitems(rfini2); i++) {
+ error = zyd_rfwrite(sc, rfini2[i]);
+ if (error != 0)
+ goto fail;
+ }
+
+ for (i = 0; i < nitems(phypll); i++)
+ zyd_write16_m(sc, phypll[i].reg, phypll[i].val);
+
+ for (i = 0; i < nitems(rfini3); i++) {
+ error = zyd_rfwrite(sc, rfini3[i]);
+ if (error != 0)
+ goto fail;
+ }
+fail:
+ return (error);
+}
+
+static int
+zyd_al2230_fini(struct zyd_rf *rf)
+{
+ 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 < nitems(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);
+}
+
+static int
+zyd_al2230_init_b(struct zyd_rf *rf)
+{
+ 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 < nitems(phy1); i++)
+ zyd_write16_m(sc, phy1[i].reg, phy1[i].val);
+
+ /* init RF-dependent PHY registers */
+ for (i = 0; i < nitems(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 < nitems(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 < nitems(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 < nitems(rfini_part2); i++) {
+ error = zyd_rfwrite_cr(sc, rfini_part2[i]);
+ if (error != 0)
+ return (error);
+ }
+
+ for (i = 0; i < nitems(phy2); i++)
+ zyd_write16_m(sc, phy2[i].reg, phy2[i].val);
+
+ for (i = 0; i < nitems(rfini_part3); i++) {
+ error = zyd_rfwrite_cr(sc, rfini_part3[i]);
+ if (error != 0)
+ return (error);
+ }
+
+ for (i = 0; i < nitems(phy3); i++)
+ zyd_write16_m(sc, phy3[i].reg, phy3[i].val);
+
+ error = zyd_al2230_fini(rf);
+fail:
+ return (error);
+}
+
+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)
+{
+ 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 < nitems(phy1); i++)
+ zyd_write16_m(sc, phy1[i].reg, phy1[i].val);
+fail:
+ return (error);
+}
+
+static int
+zyd_al2230_set_channel_b(struct zyd_rf *rf, uint8_t chan)
+{
+ 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 < nitems(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);
+}
+
+#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)
+{
+ int error = 0, i;
+ struct zyd_softc *sc = rf->rf_sc;
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct zyd_phy_pair r[] = ZYD_AL2230_PHY_BANDEDGE6;
+ int chan = ieee80211_chan2ieee(ic, c);
+
+ if (chan == 1 || chan == 11)
+ r[0].val = 0x12;
+
+ for (i = 0; i < nitems(r); i++)
+ zyd_write16_m(sc, r[i].reg, r[i].val);
+fail:
+ return (error);
+}
+
+/*
+ * AL7230B RF methods.
+ */
+static int
+zyd_al7230B_init(struct zyd_rf *rf)
+{
+ 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 < nitems(phyini_1); i++)
+ zyd_write16_m(sc, phyini_1[i].reg, phyini_1[i].val);
+
+ /* init AL7230B radio, part one */
+ for (i = 0; i < nitems(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 < nitems(phyini_2); i++)
+ zyd_write16_m(sc, phyini_2[i].reg, phyini_2[i].val);
+
+ /* init AL7230B radio, part two */
+ for (i = 0; i < nitems(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 < nitems(phyini_3); i++)
+ zyd_write16_m(sc, phyini_3[i].reg, phyini_3[i].val);
+fail:
+ return (error);
+}
+
+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)
+{
+ 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 < nitems(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);
+}
+
+/*
+ * AL2210 RF methods.
+ */
+static int
+zyd_al2210_init(struct zyd_rf *rf)
+{
+ 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 < nitems(phyini); i++)
+ zyd_write16_m(sc, phyini[i].reg, phyini[i].val);
+
+ /* init AL2210 radio */
+ for (i = 0; i < nitems(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);
+}
+
+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 ZYD_GCT_INTR_REG 0x85c1
+ 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;
+ static const uint16_t vco[11][7] = ZYD_GCT_VCO;
+ int i, idx = -1, error;
+ uint16_t data;
+
+ /* init RF-dependent PHY registers */
+ for (i = 0; i < nitems(phyini); i++)
+ zyd_write16_m(sc, phyini[i].reg, phyini[i].val);
+
+ /* init cgt radio */
+ for (i = 0; i < nitems(rfini); i++) {
+ if ((error = zyd_rfwrite(sc, rfini[i])) != 0)
+ return (error);
+ }
+
+ error = zyd_gct_mode(rf);
+ if (error != 0)
+ return (error);
+
+ for (i = 0; i < (int)(nitems(vco) - 1); i++) {
+ error = zyd_gct_set_channel_synth(rf, 1, 0);
+ if (error != 0)
+ goto fail;
+ error = zyd_gct_write(rf, vco[i][0]);
+ if (error != 0)
+ goto fail;
+ zyd_write16_m(sc, ZYD_GCT_INTR_REG, 0xf);
+ zyd_read16_m(sc, ZYD_GCT_INTR_REG, &data);
+ if ((data & 0xf) == 0) {
+ idx = i;
+ break;
+ }
+ }
+ if (idx == -1) {
+ error = zyd_gct_set_channel_synth(rf, 1, 1);
+ if (error != 0)
+ goto fail;
+ error = zyd_gct_write(rf, 0x6662);
+ if (error != 0)
+ goto fail;
+ }
+
+ rf->idx = idx;
+ zyd_write16_m(sc, ZYD_CR203, 0x6);
+fail:
+ return (error);
+#undef ZYD_GCT_INTR_REG
+}
+
+static int
+zyd_gct_mode(struct zyd_rf *rf)
+{
+ struct zyd_softc *sc = rf->rf_sc;
+ static const uint32_t mode[] = {
+ 0x25f98, 0x25f9a, 0x25f94, 0x27fd4
+ };
+ int i, error;
+
+ for (i = 0; i < nitems(mode); i++) {
+ if ((error = zyd_rfwrite(sc, mode[i])) != 0)
+ break;
+ }
+ return (error);
+}
+
+static int
+zyd_gct_set_channel_synth(struct zyd_rf *rf, int chan, int acal)
+{
+ int error, idx = chan - 1;
+ struct zyd_softc *sc = rf->rf_sc;
+ static uint32_t acal_synth[] = ZYD_GCT_CHANNEL_ACAL;
+ static uint32_t std_synth[] = ZYD_GCT_CHANNEL_STD;
+ static uint32_t div_synth[] = ZYD_GCT_CHANNEL_DIV;
+
+ error = zyd_rfwrite(sc,
+ (acal == 1) ? acal_synth[idx] : std_synth[idx]);
+ if (error != 0)
+ return (error);
+ return zyd_rfwrite(sc, div_synth[idx]);
+}
+
+static int
+zyd_gct_write(struct zyd_rf *rf, uint16_t value)
+{
+ struct zyd_softc *sc = rf->rf_sc;
+
+ return zyd_rfwrite(sc, 0x300000 | 0x40000 | value);
+}
+
+static int
+zyd_gct_switch_radio(struct zyd_rf *rf, int on)
+{
+ int error;
+ struct zyd_softc *sc = rf->rf_sc;
+
+ error = zyd_rfwrite(sc, on ? 0x25f94 : 0x25f90);
+ if (error != 0)
+ return (error);
+
+ zyd_write16_m(sc, ZYD_CR11, on ? 0x00 : 0x04);
+ zyd_write16_m(sc, ZYD_CR251,
+ on ? ((sc->sc_macrev == ZYD_ZD1211B) ? 0x7f : 0x3f) : 0x2f);
+fail:
+ return (error);
+}
+
+static int
+zyd_gct_set_channel(struct zyd_rf *rf, uint8_t chan)
+{
+ int error, i;
+ struct zyd_softc *sc = rf->rf_sc;
+ static const struct zyd_phy_pair cmd[] = {
+ { ZYD_CR80, 0x30 }, { ZYD_CR81, 0x30 }, { ZYD_CR79, 0x58 },
+ { ZYD_CR12, 0xf0 }, { ZYD_CR77, 0x1b }, { ZYD_CR78, 0x58 },
+ };
+ static const uint16_t vco[11][7] = ZYD_GCT_VCO;
+
+ error = zyd_gct_set_channel_synth(rf, chan, 0);
+ if (error != 0)
+ goto fail;
+ error = zyd_gct_write(rf, (rf->idx == -1) ? 0x6662 :
+ vco[rf->idx][((chan - 1) / 2)]);
+ if (error != 0)
+ goto fail;
+ error = zyd_gct_mode(rf);
+ if (error != 0)
+ return (error);
+ for (i = 0; i < nitems(cmd); i++)
+ zyd_write16_m(sc, cmd[i].reg, cmd[i].val);
+ error = zyd_gct_txgain(rf, chan);
+ if (error != 0)
+ return (error);
+ zyd_write16_m(sc, ZYD_CR203, 0x6);
+fail:
+ return (error);
+}
+
+static int
+zyd_gct_txgain(struct zyd_rf *rf, uint8_t chan)
+{
+ struct zyd_softc *sc = rf->rf_sc;
+ static uint32_t txgain[] = ZYD_GCT_TXGAIN;
+ uint8_t idx = sc->sc_pwrint[chan - 1];
+
+ if (idx >= nitems(txgain)) {
+ device_printf(sc->sc_dev, "could not set TX gain (%d %#x)\n",
+ chan, idx);
+ return 0;
+ }
+
+ return zyd_rfwrite(sc, 0x700000 | txgain[idx]);
+}
+
+/*
+ * Maxim2 RF methods.
+ */
+static int
+zyd_maxim2_init(struct zyd_rf *rf)
+{
+ 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 < nitems(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 < nitems(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);
+}
+
+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)
+{
+ 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 < nitems(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 < nitems(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);
+}
+
+static int
+zyd_rf_attach(struct zyd_softc *sc, uint8_t type)
+{
+ struct zyd_rf *rf = &sc->sc_rf;
+
+ rf->rf_sc = sc;
+ rf->update_pwr = 1;
+
+ 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_MAXIM_NEW:
+ 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 = 24; /* 24-bit RF values */
+ rf->update_pwr = 0;
+ 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)
+{
+ struct usb_device_request req;
+ usb_error_t 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 = zyd_do_request(sc, &req, sc->sc_ic.ic_macaddr);
+ 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 u_int
+zyd_hash_maddr(void *arg, struct sockaddr_dl *sdl, u_int cnt)
+{
+ uint32_t *hash = arg;
+ uint8_t v;
+
+ v = ((uint8_t *)LLADDR(sdl))[5] >> 2;
+ if (v < 32)
+ hash[0] |= 1 << v;
+ else
+ hash[1] |= 1 << (v - 32);
+
+ return (1);
+}
+
+static void
+zyd_set_multi(struct zyd_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ uint32_t hash[2];
+ int error;
+
+ if ((sc->sc_flags & ZYD_FLAG_RUNNING) == 0)
+ return;
+
+ hash[0] = 0x00000000;
+ hash[1] = 0x80000000;
+
+ if (ic->ic_opmode == IEEE80211_M_MONITOR || ic->ic_allmulti > 0 ||
+ ic->ic_promisc > 0) {
+ hash[0] = 0xffffffff;
+ hash[1] = 0xffffffff;
+ } else {
+ struct ieee80211vap *vap;
+
+ TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next)
+ if_foreach_llmaddr(vap->iv_ifp, zyd_hash_maddr, &hash);
+ }
+
+ /* reprogram multicast global hash table */
+ zyd_write32_m(sc, ZYD_MAC_GHTBL, hash[0]);
+ zyd_write32_m(sc, ZYD_MAC_GHTBH, hash[1]);
+fail:
+ if (error != 0)
+ device_printf(sc->sc_dev,
+ "could not set multicast hash table\n");
+}
+
+static void
+zyd_update_mcast(struct ieee80211com *ic)
+{
+ struct zyd_softc *sc = ic->ic_softc;
+
+ ZYD_LOCK(sc);
+ zyd_set_multi(sc);
+ ZYD_UNLOCK(sc);
+}
+
+static int
+zyd_set_rxfilter(struct zyd_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ 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 ieee80211com *ic = &sc->sc_ic;
+ struct zyd_rf *rf = &sc->sc_rf;
+ uint32_t tmp;
+ 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;
+
+ if (rf->update_pwr) {
+ /* 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_rx_data(struct usb_xfer *xfer, int offset, uint16_t len)
+{
+ struct zyd_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct zyd_plcphdr plcp;
+ struct zyd_rx_stat stat;
+ struct usb_page_cache *pc;
+ struct mbuf *m;
+ int rlen, rssi;
+
+ if (len < ZYD_MIN_FRAGSZ) {
+ DPRINTF(sc, ZYD_DEBUG_RECV, "%s: frame too short (length=%d)\n",
+ device_get_nameunit(sc->sc_dev), len);
+ counter_u64_add(ic->ic_ierrors, 1);
+ return;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, offset, &plcp, sizeof(plcp));
+ usbd_copy_out(pc, offset + len - sizeof(stat), &stat, sizeof(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);
+ counter_u64_add(ic->ic_ierrors, 1);
+ 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 > (int)MCLBYTES) {
+ DPRINTF(sc, ZYD_DEBUG_RECV, "%s: frame too long (length=%d)\n",
+ device_get_nameunit(sc->sc_dev), rlen);
+ counter_u64_add(ic->ic_ierrors, 1);
+ return;
+ } else if (rlen > (int)MHLEN)
+ m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
+ else
+ m = m_gethdr(M_NOWAIT, MT_DATA);
+ if (m == NULL) {
+ DPRINTF(sc, ZYD_DEBUG_RECV, "%s: could not allocate rx mbuf\n",
+ device_get_nameunit(sc->sc_dev));
+ counter_u64_add(ic->ic_ierrors, 1);
+ return;
+ }
+ m->m_pkthdr.len = m->m_len = rlen;
+ usbd_copy_out(pc, offset + sizeof(plcp), mtod(m, uint8_t *), rlen);
+
+ if (ieee80211_radiotap_active(ic)) {
+ 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 */
+ }
+ rssi = (stat.rssi > 63) ? 127 : 2 * stat.rssi;
+
+ sc->sc_rx_data[sc->sc_rx_count].rssi = rssi;
+ sc->sc_rx_data[sc->sc_rx_count].m = m;
+ sc->sc_rx_count++;
+}
+
+static void
+zyd_bulk_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct zyd_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211_node *ni;
+ struct zyd_rx_desc desc;
+ struct mbuf *m;
+ struct usb_page_cache *pc;
+ uint32_t offset;
+ uint8_t rssi;
+ int8_t nf;
+ int i;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ sc->sc_rx_count = 0;
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, actlen - sizeof(desc), &desc, sizeof(desc));
+
+ offset = 0;
+ if (UGETW(desc.tag) == ZYD_TAG_MULTIFRAME) {
+ DPRINTF(sc, ZYD_DEBUG_RECV,
+ "%s: received multi-frame transfer\n", __func__);
+
+ for (i = 0; i < ZYD_MAX_RXFRAMECNT; i++) {
+ uint16_t len16 = UGETW(desc.len[i]);
+
+ if (len16 == 0 || len16 > actlen)
+ break;
+
+ zyd_rx_data(xfer, offset, len16);
+
+ /* next frame is aligned on a 32-bit boundary */
+ len16 = (len16 + 3) & ~3;
+ offset += len16;
+ if (len16 > actlen)
+ break;
+ actlen -= len16;
+ }
+ } else {
+ DPRINTF(sc, ZYD_DEBUG_RECV,
+ "%s: received single-frame transfer\n", __func__);
+
+ zyd_rx_data(xfer, 0, actlen);
+ }
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+ usbd_transfer_submit(xfer);
+
+ /*
+ * At the end of a USB callback it is always safe to unlock
+ * the private mutex of a device! That is why we do the
+ * "ieee80211_input" here, and not some lines up!
+ */
+ ZYD_UNLOCK(sc);
+ for (i = 0; i < sc->sc_rx_count; i++) {
+ rssi = sc->sc_rx_data[i].rssi;
+ m = sc->sc_rx_data[i].m;
+ sc->sc_rx_data[i].m = NULL;
+
+ nf = -95; /* XXX */
+
+ ni = ieee80211_find_rxnode(ic,
+ mtod(m, struct ieee80211_frame_min *));
+ if (ni != NULL) {
+ (void)ieee80211_input(ni, m, rssi, nf);
+ ieee80211_free_node(ni);
+ } else
+ (void)ieee80211_input_all(ic, m, rssi, nf);
+ }
+ ZYD_LOCK(sc);
+ zyd_start(sc);
+ break;
+
+ default: /* Error */
+ DPRINTF(sc, ZYD_DEBUG_ANY, "frame error: %s\n", usbd_errstr(error));
+
+ if (error != USB_ERR_CANCELLED) {
+ /* try to clear stall first */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static uint8_t
+zyd_plcp_signal(struct zyd_softc *sc, 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);
+ }
+
+ device_printf(sc->sc_dev, "unsupported rate %d\n", rate);
+ return (0x0);
+}
+
+static void
+zyd_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ struct zyd_softc *sc = usbd_xfer_softc(xfer);
+ struct ieee80211vap *vap;
+ struct zyd_tx_data *data;
+ struct mbuf *m;
+ struct usb_page_cache *pc;
+ int actlen;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+ DPRINTF(sc, ZYD_DEBUG_ANY, "transfer complete, %u bytes\n",
+ actlen);
+
+ /* free resources */
+ data = usbd_xfer_get_priv(xfer);
+ zyd_tx_free(data, 0);
+ usbd_xfer_set_priv(xfer, NULL);
+
+ /* FALLTHROUGH */
+ case USB_ST_SETUP:
+tr_setup:
+ data = STAILQ_FIRST(&sc->tx_q);
+ if (data) {
+ STAILQ_REMOVE_HEAD(&sc->tx_q, next);
+ m = data->m;
+
+ if (m->m_pkthdr.len > (int)ZYD_MAX_TXBUFSZ) {
+ DPRINTF(sc, ZYD_DEBUG_ANY, "data overflow, %u bytes\n",
+ m->m_pkthdr.len);
+ m->m_pkthdr.len = ZYD_MAX_TXBUFSZ;
+ }
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_in(pc, 0, &data->desc, ZYD_TX_DESC_SIZE);
+ usbd_m_copy_in(pc, ZYD_TX_DESC_SIZE, m, 0,
+ m->m_pkthdr.len);
+
+ vap = data->ni->ni_vap;
+ if (ieee80211_radiotap_active_vap(vap)) {
+ struct zyd_tx_radiotap_header *tap = &sc->sc_txtap;
+
+ tap->wt_flags = 0;
+ tap->wt_rate = data->rate;
+
+ ieee80211_radiotap_tx(vap, m);
+ }
+
+ usbd_xfer_set_frame_len(xfer, 0, ZYD_TX_DESC_SIZE + m->m_pkthdr.len);
+ usbd_xfer_set_priv(xfer, data);
+ usbd_transfer_submit(xfer);
+ }
+ zyd_start(sc);
+ break;
+
+ default: /* Error */
+ DPRINTF(sc, ZYD_DEBUG_ANY, "transfer error, %s\n",
+ usbd_errstr(error));
+
+ counter_u64_add(sc->sc_ic.ic_oerrors, 1);
+ data = usbd_xfer_get_priv(xfer);
+ usbd_xfer_set_priv(xfer, NULL);
+ if (data != NULL)
+ zyd_tx_free(data, error);
+
+ if (error != USB_ERR_CANCELLED) {
+ if (error == USB_ERR_TIMEOUT)
+ device_printf(sc->sc_dev, "device timeout\n");
+
+ /*
+ * Try to clear stall first, also if other
+ * errors occur, hence clearing stall
+ * introduces a 50 ms delay:
+ */
+ usbd_xfer_set_stall(xfer);
+ goto tr_setup;
+ }
+ break;
+ }
+}
+
+static int
+zyd_tx_start(struct zyd_softc *sc, struct mbuf *m0, struct ieee80211_node *ni)
+{
+ struct ieee80211vap *vap = ni->ni_vap;
+ struct ieee80211com *ic = ni->ni_ic;
+ struct zyd_tx_desc *desc;
+ struct zyd_tx_data *data;
+ struct ieee80211_frame *wh;
+ const struct ieee80211_txparam *tp = ni->ni_txparms;
+ struct ieee80211_key *k;
+ int rate, totlen, type, ismcast;
+ static const uint8_t ratediv[] = ZYD_TX_RATEDIV;
+ uint8_t phy;
+ uint16_t pktlen;
+ uint32_t bits;
+
+ wh = mtod(m0, struct ieee80211_frame *);
+ data = STAILQ_FIRST(&sc->tx_free);
+ STAILQ_REMOVE_HEAD(&sc->tx_free, next);
+ sc->tx_nfree--;
+
+ ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1);
+ type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
+
+ if (type == IEEE80211_FC0_TYPE_MGT ||
+ type == IEEE80211_FC0_TYPE_CTL ||
+ (m0->m_flags & M_EAPOL) != 0) {
+ rate = tp->mgmtrate;
+ } else {
+ /* for data frames */
+ if (ismcast)
+ rate = tp->mcastrate;
+ else if (tp->ucastrate != IEEE80211_FIXED_RATE_NONE)
+ rate = tp->ucastrate;
+ else {
+ (void) ieee80211_ratectl_rate(ni, NULL, 0);
+ rate = ieee80211_node_get_txrate_dot11rate(ni);
+ }
+ }
+
+ if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
+ k = ieee80211_crypto_encap(ni, m0);
+ if (k == NULL) {
+ return (ENOBUFS);
+ }
+ /* packet header may have moved, reset our local pointer */
+ wh = mtod(m0, struct ieee80211_frame *);
+ }
+
+ data->ni = ni;
+ data->m = m0;
+ data->rate = rate;
+
+ /* fill Tx descriptor */
+ desc = &data->desc;
+ phy = zyd_plcp_signal(sc, rate);
+ desc->phy = phy;
+ 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;
+
+ totlen = m0->m_pkthdr.len + IEEE80211_CRC_LEN;
+ desc->len = htole16(totlen);
+
+ desc->flags = ZYD_TX_FLAG_BACKOFF;
+ if (!ismcast) {
+ /* 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 (IEEE80211_IS_CTL_PS_POLL(wh))
+ desc->flags |= ZYD_TX_FLAG_TYPE(ZYD_TX_TYPE_PS_POLL);
+
+ /* actual transmit length (XXX why +10?) */
+ pktlen = ZYD_TX_DESC_SIZE + 10;
+ if (sc->sc_macrev == ZYD_ZD1211)
+ pktlen += totlen;
+ desc->pktlen = htole16(pktlen);
+
+ bits = (rate == 11) ? (totlen * 16) + 10 :
+ ((rate == 22) ? (totlen * 8) + 10 : (totlen * 8));
+ desc->plcp_length = htole16(bits / ratediv[phy]);
+ desc->plcp_service = 0;
+ if (rate == 22 && (bits % 11) > 0 && (bits % 11) <= 3)
+ desc->plcp_service |= ZYD_PLCP_LENGEXT;
+ desc->nextlen = 0;
+
+ if (ieee80211_radiotap_active_vap(vap)) {
+ struct zyd_tx_radiotap_header *tap = &sc->sc_txtap;
+
+ tap->wt_flags = 0;
+ tap->wt_rate = rate;
+
+ ieee80211_radiotap_tx(vap, m0);
+ }
+
+ DPRINTF(sc, ZYD_DEBUG_XMIT,
+ "%s: sending data frame len=%zu rate=%u\n",
+ device_get_nameunit(sc->sc_dev), (size_t)m0->m_pkthdr.len,
+ rate);
+
+ STAILQ_INSERT_TAIL(&sc->tx_q, data, next);
+ usbd_transfer_start(sc->sc_xfer[ZYD_BULK_WR]);
+
+ return (0);
+}
+
+static int
+zyd_transmit(struct ieee80211com *ic, struct mbuf *m)
+{
+ struct zyd_softc *sc = ic->ic_softc;
+ int error;
+
+ ZYD_LOCK(sc);
+ if ((sc->sc_flags & ZYD_FLAG_RUNNING) == 0) {
+ ZYD_UNLOCK(sc);
+ return (ENXIO);
+ }
+ error = mbufq_enqueue(&sc->sc_snd, m);
+ if (error) {
+ ZYD_UNLOCK(sc);
+ return (error);
+ }
+ zyd_start(sc);
+ ZYD_UNLOCK(sc);
+
+ return (0);
+}
+
+static void
+zyd_start(struct zyd_softc *sc)
+{
+ struct ieee80211_node *ni;
+ struct mbuf *m;
+
+ ZYD_LOCK_ASSERT(sc, MA_OWNED);
+
+ while (sc->tx_nfree > 0 && (m = mbufq_dequeue(&sc->sc_snd)) != NULL) {
+ ni = (struct ieee80211_node *)m->m_pkthdr.rcvif;
+ if (zyd_tx_start(sc, m, ni) != 0) {
+ m_freem(m);
+ if_inc_counter(ni->ni_vap->iv_ifp,
+ IFCOUNTER_OERRORS, 1);
+ ieee80211_free_node(ni);
+ break;
+ }
+ }
+}
+
+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 zyd_softc *sc = ic->ic_softc;
+
+ ZYD_LOCK(sc);
+ /* prevent management frames from being sent if we're not ready */
+ if (!(sc->sc_flags & ZYD_FLAG_RUNNING)) {
+ ZYD_UNLOCK(sc);
+ m_freem(m);
+ return (ENETDOWN);
+ }
+ if (sc->tx_nfree == 0) {
+ ZYD_UNLOCK(sc);
+ m_freem(m);
+ return (ENOBUFS); /* XXX */
+ }
+
+ /*
+ * Legacy path; interpret frame contents to decide
+ * precisely how to send the frame.
+ * XXX raw path
+ */
+ if (zyd_tx_start(sc, m, ni) != 0) {
+ ZYD_UNLOCK(sc);
+ m_freem(m);
+ return (EIO);
+ }
+ ZYD_UNLOCK(sc);
+ return (0);
+}
+
+static void
+zyd_parent(struct ieee80211com *ic)
+{
+ struct zyd_softc *sc = ic->ic_softc;
+ int startall = 0;
+
+ ZYD_LOCK(sc);
+ if (sc->sc_flags & ZYD_FLAG_DETACHED) {
+ ZYD_UNLOCK(sc);
+ return;
+ }
+ if (ic->ic_nrunning > 0) {
+ if ((sc->sc_flags & ZYD_FLAG_RUNNING) == 0) {
+ zyd_init_locked(sc);
+ startall = 1;
+ } else
+ zyd_set_multi(sc);
+ } else if (sc->sc_flags & ZYD_FLAG_RUNNING)
+ zyd_stop(sc);
+ ZYD_UNLOCK(sc);
+ if (startall)
+ ieee80211_start_all(ic);
+}
+
+static void
+zyd_init_locked(struct zyd_softc *sc)
+{
+ struct ieee80211com *ic = &sc->sc_ic;
+ struct ieee80211vap *vap = TAILQ_FIRST(&ic->ic_vaps);
+ struct usb_config_descriptor *cd;
+ int error;
+ uint32_t val;
+
+ ZYD_LOCK_ASSERT(sc, MA_OWNED);
+
+ 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;
+ }
+
+ /* reset device */
+ cd = usbd_get_config_descriptor(sc->sc_udev);
+ error = usbd_req_set_config(sc->sc_udev, &sc->sc_mtx,
+ cd->bConfigurationValue);
+ if (error)
+ device_printf(sc->sc_dev, "reset failed, continuing\n");
+
+ error = zyd_hw_init(sc);
+ if (error) {
+ 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 (sc->sc_flags & ZYD_FLAG_RUNNING)
+ zyd_stop(sc);
+
+ DPRINTF(sc, ZYD_DEBUG_INIT, "setting MAC address to %6D\n",
+ vap ? vap->iv_myaddr : ic->ic_macaddr, ":");
+ error = zyd_set_macaddr(sc, vap ? vap->iv_myaddr : ic->ic_macaddr);
+ 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.
+ */
+ zyd_setup_tx_list(sc);
+
+ /* enable interrupts */
+ zyd_write32_m(sc, ZYD_CR_INTERRUPT, ZYD_HWINT_MASK);
+
+ sc->sc_flags |= ZYD_FLAG_RUNNING;
+ usbd_xfer_set_stall(sc->sc_xfer[ZYD_BULK_WR]);
+ usbd_transfer_start(sc->sc_xfer[ZYD_BULK_RD]);
+ usbd_transfer_start(sc->sc_xfer[ZYD_INTR_RD]);
+
+ return;
+
+fail: zyd_stop(sc);
+ return;
+}
+
+static void
+zyd_stop(struct zyd_softc *sc)
+{
+ int error;
+
+ ZYD_LOCK_ASSERT(sc, MA_OWNED);
+
+ sc->sc_flags &= ~ZYD_FLAG_RUNNING;
+ zyd_drain_mbufq(sc);
+
+ /*
+ * Drain all the transfers, if not already drained:
+ */
+ ZYD_UNLOCK(sc);
+ usbd_transfer_drain(sc->sc_xfer[ZYD_BULK_WR]);
+ usbd_transfer_drain(sc->sc_xfer[ZYD_BULK_RD]);
+ ZYD_LOCK(sc);
+
+ zyd_unsetup_tx_list(sc);
+
+ /* Stop now if the device was never set up */
+ if (!(sc->sc_flags & ZYD_FLAG_INITONCE))
+ return;
+
+ /* 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);
+
+fail:
+ return;
+}
+
+static int
+zyd_loadfirmware(struct zyd_softc *sc)
+{
+ struct usb_device_request 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 (zyd_do_request(sc, &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 (zyd_do_request(sc, &req, &stat) != 0)
+ return (EIO);
+
+ sc->sc_flags |= ZYD_FLAG_FWLOADED;
+
+ return (stat & 0x80) ? (EIO) : (0);
+}
+
+static void
+zyd_scan_start(struct ieee80211com *ic)
+{
+ struct zyd_softc *sc = ic->ic_softc;
+
+ ZYD_LOCK(sc);
+ /* want broadcast address while scanning */
+ zyd_set_bssid(sc, ieee80211broadcastaddr);
+ ZYD_UNLOCK(sc);
+}
+
+static void
+zyd_scan_end(struct ieee80211com *ic)
+{
+ struct zyd_softc *sc = ic->ic_softc;
+
+ ZYD_LOCK(sc);
+ /* restore previous bssid */
+ zyd_set_bssid(sc, sc->sc_bssid);
+ ZYD_UNLOCK(sc);
+}
+
+static void
+zyd_getradiocaps(struct ieee80211com *ic,
+ int maxchans, int *nchans, struct ieee80211_channel chans[])
+{
+ uint8_t bands[IEEE80211_MODE_BYTES];
+
+ memset(bands, 0, sizeof(bands));
+ setbit(bands, IEEE80211_MODE_11B);
+ setbit(bands, IEEE80211_MODE_11G);
+ ieee80211_add_channels_default_2ghz(chans, maxchans, nchans, bands, 0);
+}
+
+static void
+zyd_set_channel(struct ieee80211com *ic)
+{
+ struct zyd_softc *sc = ic->ic_softc;
+
+ ZYD_LOCK(sc);
+ zyd_set_chan(sc, ic->ic_curchan);
+ ZYD_UNLOCK(sc);
+}
+
+static device_method_t zyd_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, zyd_match),
+ DEVMETHOD(device_attach, zyd_attach),
+ DEVMETHOD(device_detach, zyd_detach),
+ DEVMETHOD_END
+};
+
+static driver_t zyd_driver = {
+ .name = "zyd",
+ .methods = zyd_methods,
+ .size = sizeof(struct zyd_softc)
+};
+
+DRIVER_MODULE(zyd, uhub, zyd_driver, NULL, NULL);
+MODULE_DEPEND(zyd, usb, 1, 1, 1);
+MODULE_DEPEND(zyd, wlan, 1, 1, 1);
+MODULE_VERSION(zyd, 1);
+USB_PNP_HOST_INFO(zyd_devs);
diff --git a/sys/dev/usb/wlan/if_zydfw.h b/sys/dev/usb/wlan/if_zydfw.h
new file mode 100644
index 000000000000..92b80744b361
--- /dev/null
+++ b/sys/dev/usb/wlan/if_zydfw.h
@@ -0,0 +1,1145 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * 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.
+ */
+
+
+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/dev/usb/wlan/if_zydreg.h b/sys/dev/usb/wlan/if_zydreg.h
new file mode 100644
index 000000000000..eeb81fe0d5f3
--- /dev/null
+++ b/sys/dev/usb/wlan/if_zydreg.h
@@ -0,0 +1,1313 @@
+/* $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 $ */
+
+/*-
+ * 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
+
+/*
+ * Miscellaneous 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_CR10, 0x89 }, { ZYD_CR15, 0x20 }, { ZYD_CR17, 0x28 }, \
+ { ZYD_CR23, 0x38 }, { ZYD_CR24, 0x20 }, { ZYD_CR26, 0x93 }, \
+ { ZYD_CR27, 0x15 }, { ZYD_CR28, 0x3e }, { ZYD_CR29, 0x00 }, \
+ { ZYD_CR33, 0x28 }, { ZYD_CR34, 0x30 }, { ZYD_CR35, 0x43 }, \
+ { ZYD_CR41, 0x24 }, { ZYD_CR44, 0x32 }, { ZYD_CR46, 0x92 }, \
+ { ZYD_CR47, 0x1e }, { ZYD_CR48, 0x04 }, { ZYD_CR49, 0xfa }, \
+ { 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, 0x28 }, \
+ { ZYD_CR100, 0x02 }, { ZYD_CR101, 0x09 }, { ZYD_CR102, 0x27 }, \
+ { ZYD_CR106, 0x1c }, { ZYD_CR107, 0x1c }, { ZYD_CR109, 0x13 }, \
+ { ZYD_CR110, 0x1f }, { ZYD_CR111, 0x13 }, { ZYD_CR112, 0x1f }, \
+ { ZYD_CR113, 0x27 }, { ZYD_CR114, 0x23 }, { ZYD_CR115, 0x24 }, \
+ { ZYD_CR116, 0x24 }, { ZYD_CR117, 0xfa }, { ZYD_CR118, 0xf0 }, \
+ { ZYD_CR119, 0x1a }, { ZYD_CR120, 0x4f }, { ZYD_CR121, 0x1f }, \
+ { ZYD_CR122, 0xf0 }, { ZYD_CR123, 0x57 }, { ZYD_CR125, 0xad }, \
+ { ZYD_CR126, 0x6c }, { ZYD_CR127, 0x03 }, { ZYD_CR128, 0x14 }, \
+ { ZYD_CR129, 0x12 }, { ZYD_CR130, 0x10 }, { ZYD_CR137, 0x50 }, \
+ { ZYD_CR138, 0xa8 }, { ZYD_CR144, 0xac }, { ZYD_CR146, 0x20 }, \
+ { ZYD_CR252, 0xff }, { ZYD_CR253, 0xff } \
+}
+
+#define ZYD_GCT_RF \
+{ \
+ 0x40002b, 0x519e4f, 0x6f81ad, 0x73fffe, 0x25f9c, 0x100047, \
+ 0x200999, 0x307602, 0x346063, \
+}
+
+#define ZYD_GCT_VCO \
+{ \
+ { 0x664d, 0x604d, 0x6675, 0x6475, 0x6655, 0x6455, 0x6665 }, \
+ { 0x666d, 0x606d, 0x664d, 0x644d, 0x6675, 0x6475, 0x6655 }, \
+ { 0x665d, 0x605d, 0x666d, 0x646d, 0x664d, 0x644d, 0x6675 }, \
+ { 0x667d, 0x607d, 0x665d, 0x645d, 0x666d, 0x646d, 0x664d }, \
+ { 0x6643, 0x6043, 0x667d, 0x647d, 0x665d, 0x645d, 0x666d }, \
+ { 0x6663, 0x6063, 0x6643, 0x6443, 0x667d, 0x647d, 0x665d }, \
+ { 0x6653, 0x6053, 0x6663, 0x6463, 0x6643, 0x6443, 0x667d }, \
+ { 0x6673, 0x6073, 0x6653, 0x6453, 0x6663, 0x6463, 0x6643 }, \
+ { 0x664b, 0x604b, 0x6673, 0x6473, 0x6653, 0x6453, 0x6663 }, \
+ { 0x666b, 0x606b, 0x664b, 0x644b, 0x6673, 0x6473, 0x6653 }, \
+ { 0x665b, 0x605b, 0x666b, 0x646b, 0x664b, 0x644b, 0x6673 } \
+}
+
+#define ZYD_GCT_TXGAIN \
+{ \
+ 0x0e313, 0x0fb13, 0x0e093, 0x0f893, 0x0ea93, 0x1f093, 0x1f493, \
+ 0x1f693, 0x1f393, 0x1f35b, 0x1e6db, 0x1ff3f, 0x1ffff, 0x361d7, \
+ 0x37fbf, 0x3ff8b, 0x3ff33, 0x3fb3f, 0x3ffff \
+}
+
+#define ZYD_GCT_CHANNEL_ACAL \
+{ \
+ 0x106847, 0x106847, 0x106867, 0x106867, 0x106867, 0x106867, \
+ 0x106857, 0x106857, 0x106857, 0x106857, 0x106877, 0x106877, \
+ 0x106877, 0x10684f \
+}
+
+#define ZYD_GCT_CHANNEL_STD \
+{ \
+ 0x100047, 0x100047, 0x100067, 0x100067, 0x100067, 0x100067, \
+ 0x100057, 0x100057, 0x100057, 0x100057, 0x100077, 0x100077, \
+ 0x100077, 0x10004f \
+}
+
+#define ZYD_GCT_CHANNEL_DIV \
+{ \
+ 0x200999, 0x20099b, 0x200998, 0x20099a, 0x200999, 0x20099b, \
+ 0x200998, 0x20099a, 0x200999, 0x20099b, 0x200998, 0x20099a, \
+ 0x200999, 0x200ccc \
+}
+
+#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 } \
+}
+
+#define ZYD_TX_RATEDIV \
+{ \
+ 0x1, 0x2, 0xb, 0xb, 0x1, 0x1, 0x1, 0x1, 0x30, 0x18, 0xc, 0x6, \
+ 0x36, 0x24, 0x12, 0x9 \
+}
+
+/*
+ * 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 (1U << 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_INDEX 0
+#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_TX_DESC_SIZE (sizeof (struct zyd_tx_desc))
+
+#define ZYD_RX_LIST_CNT 1
+#define ZYD_TX_LIST_CNT 5
+#define ZYD_CMD_FLAG_READ (1 << 0)
+#define ZYD_CMD_FLAG_SENT (1 << 1)
+
+/* 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 {
+ STAILQ_ENTRY(zyd_tx_data) next;
+ struct zyd_softc *sc;
+ struct zyd_tx_desc desc;
+ struct mbuf *m;
+ struct ieee80211_node *ni;
+ int rate;
+};
+typedef STAILQ_HEAD(, zyd_tx_data) zyd_txdhead;
+
+struct zyd_rx_data {
+ struct mbuf *m;
+ int rssi;
+};
+
+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 __aligned(8);
+
+#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;
+ int idx; /* for GIT RF */
+ int update_pwr;
+};
+
+struct zyd_rq {
+ struct zyd_cmd *cmd;
+ const uint16_t *idata;
+ struct zyd_pair *odata;
+ int ilen;
+ int olen;
+ int flags;
+ STAILQ_ENTRY(zyd_rq) rq;
+};
+
+struct zyd_vap {
+ struct ieee80211vap vap;
+ int (*newstate)(struct ieee80211vap *,
+ enum ieee80211_state, int);
+};
+#define ZYD_VAP(vap) ((struct zyd_vap *)(vap))
+
+enum {
+ ZYD_BULK_WR,
+ ZYD_BULK_RD,
+ ZYD_INTR_WR,
+ ZYD_INTR_RD,
+ ZYD_N_TRANSFER = 4,
+};
+
+struct zyd_softc {
+ struct ieee80211com sc_ic;
+ struct ieee80211_ratectl_tx_status sc_txs;
+ struct mbufq sc_snd;
+ device_t sc_dev;
+ struct usb_device *sc_udev;
+
+ struct usb_xfer *sc_xfer[ZYD_N_TRANSFER];
+
+ int sc_flags;
+#define ZYD_FLAG_FWLOADED (1 << 0)
+#define ZYD_FLAG_INITONCE (1 << 1)
+#define ZYD_FLAG_INITDONE (1 << 2)
+#define ZYD_FLAG_DETACHED (1 << 3)
+#define ZYD_FLAG_RUNNING (1 << 4)
+
+ struct zyd_rf sc_rf;
+
+ STAILQ_HEAD(, zyd_rq) sc_rtx;
+ STAILQ_HEAD(, zyd_rq) sc_rqh;
+
+ 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];
+ uint8_t sc_bssid[IEEE80211_ADDR_LEN];
+
+ struct mtx sc_mtx;
+ struct zyd_tx_data tx_data[ZYD_TX_LIST_CNT];
+ zyd_txdhead tx_q;
+ zyd_txdhead tx_free;
+ int tx_nfree;
+ struct zyd_rx_desc sc_rx_desc;
+ struct zyd_rx_data sc_rx_data[ZYD_MAX_RXFRAMECNT];
+ int sc_rx_count;
+
+ struct zyd_cmd sc_ibuf;
+
+ struct zyd_rx_radiotap_header sc_rxtap;
+ struct zyd_tx_radiotap_header sc_txtap;
+};
+
+#define ZYD_LOCK(sc) mtx_lock(&(sc)->sc_mtx)
+#define ZYD_UNLOCK(sc) mtx_unlock(&(sc)->sc_mtx)
+#define ZYD_LOCK_ASSERT(sc, t) mtx_assert(&(sc)->sc_mtx, t)