aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/usb/net
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/usb/net')
-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
40 files changed, 31177 insertions, 0 deletions
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_ */