diff options
Diffstat (limited to 'sys/dev/usb/net')
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, ®_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, ®_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, ¬ification, 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, ®tmp)) { + 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_ */ |