summaryrefslogtreecommitdiff
path: root/sys/dev/usb
diff options
context:
space:
mode:
authorJonathan Gray <jsg@cvs.openbsd.org>2012-09-19 16:33:06 +0000
committerJonathan Gray <jsg@cvs.openbsd.org>2012-09-19 16:33:06 +0000
commit40e5a984b4427777a099d28b14e09e3053ee60fc (patch)
tree5d5314583055ea49ce07296b3ed4bc8cc5adac07 /sys/dev/usb
parent6d46f25fab07c68f91999619681d6965ff0c4f48 (diff)
SMSC LAN95xx 10/100 USB Ethernet driver, ported from FreeBSD.
'turbo mode' having multiple rx packets in a single usb transaction similiar to what newer asix chips do is disabled as it seems to cause many rx errors and breaks fragmentation. checksum offloading while apparently supported by the hardware is currently disabled.
Diffstat (limited to 'sys/dev/usb')
-rw-r--r--sys/dev/usb/files.usb7
-rw-r--r--sys/dev/usb/if_smsc.c1454
-rw-r--r--sys/dev/usb/if_smscreg.h307
3 files changed, 1767 insertions, 1 deletions
diff --git a/sys/dev/usb/files.usb b/sys/dev/usb/files.usb
index 07a0673a268..1e4000a5232 100644
--- a/sys/dev/usb/files.usb
+++ b/sys/dev/usb/files.usb
@@ -1,4 +1,4 @@
-# $OpenBSD: files.usb,v 1.100 2012/09/17 12:17:40 yuo Exp $
+# $OpenBSD: files.usb,v 1.101 2012/09/19 16:33:04 jsg Exp $
# $NetBSD: files.usb,v 1.16 2000/02/14 20:29:54 augustss Exp $
#
# Config file and device description for machine-independent USB code.
@@ -186,6 +186,11 @@ device axe: ether, ifnet, mii, ifmedia
attach axe at uhub
file dev/usb/if_axe.c axe
+# SMSC LAN95xx
+device smsc: ether, ifnet, mii, ifmedia
+attach smsc at uhub
+file dev/usb/if_smsc.c smsc
+
# CATC USB-EL1201A
device cue: ether, ifnet, ifmedia
attach cue at uhub
diff --git a/sys/dev/usb/if_smsc.c b/sys/dev/usb/if_smsc.c
new file mode 100644
index 00000000000..3958616f930
--- /dev/null
+++ b/sys/dev/usb/if_smsc.c
@@ -0,0 +1,1454 @@
+/* $OpenBSD: if_smsc.c,v 1.1 2012/09/19 16:33:04 jsg Exp $ */
+/* $FreeBSD: src/sys/dev/usb/net/if_smsc.c,v 1.1 2012/08/15 04:03:55 gonzo Exp $ */
+/*-
+ * 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.
+ */
+
+/*
+ * SMSC LAN9xxx devices (http://www.smsc.com/)
+ *
+ * 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.
+ *
+ * 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 "bpfilter.h"
+#include "vlan.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/sockio.h>
+#include <sys/rwlock.h>
+#include <sys/mbuf.h>
+#include <sys/kernel.h>
+#include <sys/proc.h>
+#include <sys/socket.h>
+
+#include <sys/device.h>
+
+#include <machine/bus.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/if_media.h>
+
+#if NBPFILTER > 0
+#include <net/bpf.h>
+#endif
+
+#ifdef INET
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+#include <netinet/if_ether.h>
+#endif
+
+#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 <dev/usb/usbdivar.h>
+#include <dev/usb/usbdevs.h>
+
+#include "if_smscreg.h"
+
+#ifdef USB_DEBUG
+static int smsc_debug = 0;
+#endif
+
+/*
+ * Various supported device vendors/products.
+ */
+static const struct usb_devno smsc_devs[] = {
+ { USB_VENDOR_SMC2, USB_PRODUCT_SMC2_SMSC9512_14 }
+};
+
+#ifdef USB_DEBUG
+#define smsc_dbg_printf(sc, fmt, args...) \
+ do { \
+ if (smsc_debug > 0) \
+ printf("debug: " fmt, ##args); \
+ } while(0)
+#else
+#define smsc_dbg_printf(sc, fmt, args...)
+#endif
+
+#define smsc_warn_printf(sc, fmt, args...) \
+ printf("%s: warning: " fmt, (sc)->sc_dev.dv_xname, ##args)
+
+#define smsc_err_printf(sc, fmt, args...) \
+ printf("%s: error: " fmt, (sc)->sc_dev.dv_xname, ##args)
+
+int smsc_chip_init(struct smsc_softc *sc);
+int smsc_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data);
+void smsc_setmulti(struct smsc_softc *);
+int smsc_setmacaddress(struct smsc_softc *, const uint8_t *);
+
+int smsc_match(struct device *, void *, void *);
+void smsc_attach(struct device *, struct device *, void *);
+int smsc_detach(struct device *, int);
+int smsc_activate(struct device *, int);
+
+void smsc_init(void *);
+void smsc_stop(struct smsc_softc *);
+void smsc_start(struct ifnet *);
+void smsc_reset(struct smsc_softc *);
+struct mbuf *smsc_newbuf(void);
+
+void smsc_tick(void *);
+void smsc_tick_task(void *);
+void smsc_miibus_statchg(struct device *);
+int smsc_miibus_readreg(struct device *, int, int);
+void smsc_miibus_writereg(struct device *, int, int, int);
+int smsc_ifmedia_upd(struct ifnet *);
+void smsc_ifmedia_sts(struct ifnet *, struct ifmediareq *);
+void smsc_lock_mii(struct smsc_softc *sc);
+void smsc_unlock_mii(struct smsc_softc *sc);
+
+int smsc_tx_list_init(struct smsc_softc *);
+int smsc_rx_list_init(struct smsc_softc *);
+int smsc_encap(struct smsc_softc *, struct mbuf *, int);
+void smsc_rxeof(usbd_xfer_handle, usbd_private_handle, usbd_status);
+void smsc_txeof(usbd_xfer_handle, usbd_private_handle, usbd_status);
+
+int smsc_read_reg(struct smsc_softc *, uint32_t, uint32_t *);
+int smsc_write_reg(struct smsc_softc *, uint32_t, uint32_t);
+int smsc_wait_for_bits(struct smsc_softc *, uint32_t, uint32_t);
+int smsc_sethwcsum(struct smsc_softc *);
+
+struct cfdriver smsc_cd = {
+ NULL, "smsc", DV_IFNET
+};
+
+const struct cfattach smsc_ca = {
+ sizeof(struct smsc_softc),
+ smsc_match,
+ smsc_attach,
+ smsc_detach,
+ smsc_activate
+};
+
+int
+smsc_read_reg(struct smsc_softc *sc, uint32_t off, uint32_t *data)
+{
+ usb_device_request_t req;
+ uint32_t buf;
+ usbd_status err;
+
+ 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 = usbd_do_request(sc->sc_udev, &req, &buf);
+ if (err != 0)
+ smsc_warn_printf(sc, "Failed to read register 0x%0x\n", off);
+
+ *data = letoh32(buf);
+
+ return (err);
+}
+
+int
+smsc_write_reg(struct smsc_softc *sc, uint32_t off, uint32_t data)
+{
+ usb_device_request_t req;
+ uint32_t buf;
+ usbd_status err;
+
+ 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 = usbd_do_request(sc->sc_udev, &req, &buf);
+ if (err != 0)
+ smsc_warn_printf(sc, "Failed to write register 0x%0x\n", off);
+
+ return (err);
+}
+
+int
+smsc_wait_for_bits(struct smsc_softc *sc, uint32_t reg, uint32_t bits)
+{
+ uint32_t val;
+ int err, i;
+
+ for (i = 0; i < 100; i++) {
+ if ((err = smsc_read_reg(sc, reg, &val)) != 0)
+ return (err);
+ if (!(val & bits))
+ return (0);
+ DELAY(5);
+ }
+
+ return (1);
+}
+
+int
+smsc_miibus_readreg(struct device *dev, int phy, int reg)
+{
+ struct smsc_softc *sc = (struct smsc_softc *)dev;
+ uint32_t addr;
+ uint32_t val = 0;
+
+ smsc_lock_mii(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_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);
+ smsc_unlock_mii(sc);
+ val = letoh32(val);
+
+done:
+ return (val & 0xFFFF);
+}
+
+void
+smsc_miibus_writereg(struct device *dev, int phy, int reg, int val)
+{
+ struct smsc_softc *sc = (struct smsc_softc *)dev;
+ uint32_t addr;
+
+ if (sc->sc_phyno != phy)
+ return;
+
+ smsc_lock_mii(sc);
+ if (smsc_wait_for_bits(sc, SMSC_MII_ADDR, SMSC_MII_BUSY) != 0) {
+ smsc_warn_printf(sc, "MII is busy\n");
+ return;
+ }
+
+ val = htole32(val);
+ smsc_write_reg(sc, SMSC_MII_DATA, val);
+
+ addr = (phy << 11) | (reg << 6) | SMSC_MII_WRITE;
+ smsc_write_reg(sc, SMSC_MII_ADDR, addr);
+ smsc_unlock_mii(sc);
+
+ if (smsc_wait_for_bits(sc, SMSC_MII_ADDR, SMSC_MII_BUSY) != 0)
+ smsc_warn_printf(sc, "MII write timeout\n");
+}
+
+void
+smsc_miibus_statchg(struct device *dev)
+{
+ struct smsc_softc *sc = (struct smsc_softc *)dev;
+ struct mii_data *mii = &sc->sc_mii;
+ struct ifnet *ifp = &sc->sc_ac.ac_if;
+ int err;
+ uint32_t flow;
+ uint32_t afc_cfg;
+
+ if (mii == NULL || ifp == NULL ||
+ (ifp->if_flags & IFF_RUNNING) == 0)
+ return;
+
+ /* 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");
+ return;
+ }
+
+ 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);
+ return;
+ }
+
+ /* 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);
+}
+
+int
+smsc_ifmedia_upd(struct ifnet *ifp)
+{
+ struct smsc_softc *sc = ifp->if_softc;
+ struct mii_data *mii = &sc->sc_mii;
+ int err;
+
+ if (mii->mii_instance) {
+ struct mii_softc *miisc;
+
+ LIST_FOREACH(miisc, &mii->mii_phys, mii_list)
+ mii_phy_reset(miisc);
+ }
+ err = mii_mediachg(mii);
+ return (err);
+}
+
+void
+smsc_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr)
+{
+ struct smsc_softc *sc = ifp->if_softc;
+ struct mii_data *mii = &sc->sc_mii;
+
+ mii_pollstat(mii);
+
+ ifmr->ifm_active = mii->mii_media_active;
+ ifmr->ifm_status = mii->mii_media_status;
+}
+
+static inline uint32_t
+smsc_hash(uint8_t addr[ETHER_ADDR_LEN])
+{
+ return (ether_crc32_be(addr, ETHER_ADDR_LEN) >> 26) & 0x3f;
+}
+
+void
+smsc_setmulti(struct smsc_softc *sc)
+{
+ struct ifnet *ifp = &sc->sc_ac.ac_if;
+ struct ether_multi *enm;
+ struct ether_multistep step;
+ uint32_t hashtbl[2] = { 0, 0 };
+ uint32_t hash;
+
+ if (usbd_is_dying(sc->sc_udev))
+ return;
+
+ if (ifp->if_flags & (IFF_ALLMULTI | IFF_PROMISC)) {
+allmulti:
+ 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;
+ smsc_write_reg(sc, SMSC_MAC_CSR, sc->sc_mac_csr);
+ return;
+ } else {
+ sc->sc_mac_csr |= SMSC_MAC_CSR_HPFILT;
+ sc->sc_mac_csr &= ~(SMSC_MAC_CSR_PRMS | SMSC_MAC_CSR_MCPAS);
+ }
+
+ ETHER_FIRST_MULTI(step, &sc->sc_ac, enm);
+ while (enm != NULL) {
+ if (memcmp(enm->enm_addrlo, enm->enm_addrhi,
+ ETHER_ADDR_LEN) != 0)
+ goto allmulti;
+
+ hash = smsc_hash(enm->enm_addrlo);
+ hashtbl[hash >> 5] |= 1 << (hash & 0x1F);
+ ETHER_NEXT_MULTI(step, enm);
+ }
+
+ /* 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 */
+ ifp->if_flags &= ~IFF_ALLMULTI;
+ 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);
+}
+
+int
+smsc_sethwcsum(struct smsc_softc *sc)
+{
+ struct ifnet *ifp = &sc->sc_ac.ac_if;
+ uint32_t val;
+ int err;
+
+ if (!ifp)
+ return (-EIO);
+
+ 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 (ifp->if_capabilities & IFCAP_CSUM_IPv4)
+ val |= SMSC_COE_CTRL_RX_EN;
+ else
+ val &= ~SMSC_COE_CTRL_RX_EN;
+
+ /* Enable/disable the Tx checksum (currently not supported) */
+ if (ifp->if_capabilities & IFCAP_CSUM_IPv4)
+ 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);
+}
+
+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]);
+
+ 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);
+}
+
+void
+smsc_reset(struct smsc_softc *sc)
+{
+ if (usbd_is_dying(sc->sc_udev))
+ return;
+
+ /* Wait a little while for the chip to get its brains in order. */
+ DELAY(1000);
+
+ /* Reinitialize controller to achieve full reset. */
+ smsc_chip_init(sc);
+}
+
+void
+smsc_init(void *xsc)
+{
+ struct smsc_softc *sc = xsc;
+ struct ifnet *ifp = &sc->sc_ac.ac_if;
+ struct smsc_chain *c;
+ usbd_status err;
+ int s, i;
+
+ s = splnet();
+
+ /* Cancel pending I/O */
+ smsc_stop(sc);
+
+ /* Reset the ethernet interface. */
+ smsc_reset(sc);
+
+ /* Init RX ring. */
+ if (smsc_rx_list_init(sc) == ENOBUFS) {
+ printf("%s: rx list init failed\n", sc->sc_dev.dv_xname);
+ splx(s);
+ return;
+ }
+
+ /* Init TX ring. */
+ if (smsc_tx_list_init(sc) == ENOBUFS) {
+ printf("%s: tx list init failed\n", sc->sc_dev.dv_xname);
+ splx(s);
+ return;
+ }
+
+ /* Load the multicast filter. */
+ smsc_setmulti(sc);
+
+ /* Open RX and TX pipes. */
+ err = usbd_open_pipe(sc->sc_iface, sc->sc_ed[SMSC_ENDPT_RX],
+ USBD_EXCLUSIVE_USE, &sc->sc_ep[SMSC_ENDPT_RX]);
+ if (err) {
+ printf("%s: open rx pipe failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err));
+ splx(s);
+ return;
+ }
+
+ err = usbd_open_pipe(sc->sc_iface, sc->sc_ed[SMSC_ENDPT_TX],
+ USBD_EXCLUSIVE_USE, &sc->sc_ep[SMSC_ENDPT_TX]);
+ if (err) {
+ printf("%s: open tx pipe failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err));
+ splx(s);
+ return;
+ }
+
+ /* Start up the receive pipe. */
+ for (i = 0; i < SMSC_RX_LIST_CNT; i++) {
+ c = &sc->sc_cdata.rx_chain[i];
+ usbd_setup_xfer(c->sc_xfer, sc->sc_ep[SMSC_ENDPT_RX],
+ c, c->sc_buf, SMSC_BUFSZ,
+ USBD_SHORT_XFER_OK | USBD_NO_COPY,
+ USBD_NO_TIMEOUT, smsc_rxeof);
+ usbd_transfer(c->sc_xfer);
+ }
+
+ /* TCP/UDP checksum offload engines. */
+ smsc_sethwcsum(sc);
+
+ /* Indicate we are up and running. */
+ ifp->if_flags |= IFF_RUNNING;
+ ifp->if_flags &= ~IFF_OACTIVE;
+
+ timeout_add_sec(&sc->sc_stat_ch, 1);
+
+ splx(s);
+}
+
+void
+smsc_start(struct ifnet *ifp)
+{
+ struct smsc_softc *sc = ifp->if_softc;
+ struct mbuf *m_head = NULL;
+
+ /* Don't send anything if there is no link or controller is busy. */
+ if ((sc->sc_flags & SMSC_FLAG_LINK) == 0 ||
+ (ifp->if_flags & IFF_OACTIVE) != 0) {
+ return;
+ }
+
+ IFQ_POLL(&ifp->if_snd, m_head);
+ if (m_head == NULL)
+ return;
+
+ if (smsc_encap(sc, m_head, 0)) {
+ ifp->if_flags |= IFF_OACTIVE;
+ return;
+ }
+ IFQ_DEQUEUE(&ifp->if_snd, m_head);
+
+#if NBPFILTER > 0
+ if (ifp->if_bpf)
+ bpf_mtap(ifp->if_bpf, m_head, BPF_DIRECTION_OUT);
+#endif
+ ifp->if_flags |= IFF_OACTIVE;
+}
+
+void
+smsc_tick(void *xsc)
+{
+ struct smsc_softc *sc = xsc;
+
+ if (sc == NULL)
+ return;
+
+ if (usbd_is_dying(sc->sc_udev))
+ return;
+
+ usb_add_task(sc->sc_udev, &sc->sc_tick_task);
+}
+
+void
+smsc_stop(struct smsc_softc *sc)
+{
+ usbd_status err;
+ struct ifnet *ifp;
+ int i;
+
+ smsc_reset(sc);
+
+ ifp = &sc->sc_ac.ac_if;
+ ifp->if_timer = 0;
+ ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
+
+ timeout_del(&sc->sc_stat_ch);
+
+ /* Stop transfers. */
+ if (sc->sc_ep[SMSC_ENDPT_RX] != NULL) {
+ err = usbd_abort_pipe(sc->sc_ep[SMSC_ENDPT_RX]);
+ if (err) {
+ printf("%s: abort rx pipe failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err));
+ }
+ err = usbd_close_pipe(sc->sc_ep[SMSC_ENDPT_RX]);
+ if (err) {
+ printf("%s: close rx pipe failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err));
+ }
+ sc->sc_ep[SMSC_ENDPT_RX] = NULL;
+ }
+
+ if (sc->sc_ep[SMSC_ENDPT_TX] != NULL) {
+ err = usbd_abort_pipe(sc->sc_ep[SMSC_ENDPT_TX]);
+ if (err) {
+ printf("%s: abort tx pipe failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err));
+ }
+ err = usbd_close_pipe(sc->sc_ep[SMSC_ENDPT_TX]);
+ if (err) {
+ printf("%s: close tx pipe failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err));
+ }
+ sc->sc_ep[SMSC_ENDPT_TX] = NULL;
+ }
+
+ if (sc->sc_ep[SMSC_ENDPT_INTR] != NULL) {
+ err = usbd_abort_pipe(sc->sc_ep[SMSC_ENDPT_INTR]);
+ if (err) {
+ printf("%s: abort intr pipe failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err));
+ }
+ err = usbd_close_pipe(sc->sc_ep[SMSC_ENDPT_INTR]);
+ if (err) {
+ printf("%s: close intr pipe failed: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(err));
+ }
+ sc->sc_ep[SMSC_ENDPT_INTR] = NULL;
+ }
+
+ /* Free RX resources. */
+ for (i = 0; i < SMSC_RX_LIST_CNT; i++) {
+ if (sc->sc_cdata.rx_chain[i].sc_mbuf != NULL) {
+ m_freem(sc->sc_cdata.rx_chain[i].sc_mbuf);
+ sc->sc_cdata.rx_chain[i].sc_mbuf = NULL;
+ }
+ if (sc->sc_cdata.rx_chain[i].sc_xfer != NULL) {
+ usbd_free_xfer(sc->sc_cdata.rx_chain[i].sc_xfer);
+ sc->sc_cdata.rx_chain[i].sc_xfer = NULL;
+ }
+ }
+
+ /* Free TX resources. */
+ for (i = 0; i < SMSC_TX_LIST_CNT; i++) {
+ if (sc->sc_cdata.tx_chain[i].sc_mbuf != NULL) {
+ m_freem(sc->sc_cdata.tx_chain[i].sc_mbuf);
+ sc->sc_cdata.tx_chain[i].sc_mbuf = NULL;
+ }
+ if (sc->sc_cdata.tx_chain[i].sc_xfer != NULL) {
+ usbd_free_xfer(sc->sc_cdata.tx_chain[i].sc_xfer);
+ sc->sc_cdata.tx_chain[i].sc_xfer = NULL;
+ }
+ }
+}
+
+int
+smsc_chip_init(struct smsc_softc *sc)
+{
+ int err;
+ uint32_t reg_val;
+ int burst_cap;
+
+ /* 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;
+ }
+ usbd_delay_ms(sc->sc_udev, 40);
+
+ /* Set the mac address */
+ if ((err = smsc_setmacaddress(sc, sc->sc_ac.ac_enaddr)) != 0) {
+ smsc_warn_printf(sc, "failed to set the MAC address\n");
+ goto init_failed;
+ }
+
+ /*
+ * Don't know what the HW_CFG_BIR bit is, but following the reset
+ * sequence as used in the Linux driver.
+ */
+ if ((err = smsc_read_reg(sc, SMSC_HW_CFG, &reg_val)) != 0) {
+ smsc_warn_printf(sc, "failed to read HW_CFG: %d\n", err);
+ goto init_failed;
+ }
+ reg_val |= SMSC_HW_CFG_BIR;
+ smsc_write_reg(sc, SMSC_HW_CFG, reg_val);
+
+ /*
+ * There is a so called 'turbo mode' that the linux driver supports, it
+ * seems to allow you to jam multiple frames per Rx transaction.
+ * By default this driver supports that and therefore allows multiple
+ * frames per URB.
+ *
+ * The xfer buffer size needs to reflect this as well, therefore based
+ * on the calculations in the Linux driver the RX bufsize is set to
+ * 18944,
+ * bufsz = (16 * 1024 + 5 * 512)
+ *
+ * Burst capability is the number of URBs that can be in a burst of
+ * data/ethernet frames.
+ */
+#ifdef SMSC_TURBO
+ if (sc->sc_udev->speed == USB_SPEED_HIGH)
+ burst_cap = 37;
+ else
+ burst_cap = 128;
+#else
+ burst_cap = 0;
+#endif
+
+ smsc_write_reg(sc, SMSC_BURST_CAP, burst_cap);
+
+ /* Set the default bulk in delay (magic value from Linux driver) */
+ smsc_write_reg(sc, SMSC_BULK_IN_DLY, 0x00002000);
+
+
+
+ /*
+ * Initialise the RX interface
+ */
+ if ((err = smsc_read_reg(sc, SMSC_HW_CFG, &reg_val)) < 0) {
+ smsc_warn_printf(sc, "failed to read HW_CFG: (err = %d)\n",
+ err);
+ goto init_failed;
+ }
+
+ /*
+ * The following setings are used for 'turbo mode', a.k.a multiple
+ * frames per Rx transaction (again info taken form Linux driver).
+ */
+#ifdef SMSC_TURBO
+ reg_val |= (SMSC_HW_CFG_MEF | SMSC_HW_CFG_BCE);
+#endif
+
+ 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;
+ }
+
+ /* 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);
+
+ /*
+ * 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);
+
+ return (0);
+
+init_failed:
+ smsc_err_printf(sc, "smsc_chip_init failed (err=%d)\n", err);
+ return (err);
+}
+
+int
+smsc_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+ struct smsc_softc *sc = ifp->if_softc;
+ struct ifreq *ifr = (struct ifreq *)data;
+ struct ifaddr *ifa = (struct ifaddr *)data;
+ struct mii_data *mii;
+ int s, error = 0;
+
+ s = splnet();
+
+ switch(cmd) {
+ case SIOCSIFADDR:
+ ifp->if_flags |= IFF_UP;
+ if (!(ifp->if_flags & IFF_RUNNING))
+ smsc_init(sc);
+#ifdef INET
+ if (ifa->ifa_addr->sa_family == AF_INET)
+ arp_ifinit(&sc->sc_ac, ifa);
+#endif
+ break;
+
+ case SIOCSIFFLAGS:
+ if (ifp->if_flags & IFF_UP) {
+ if (ifp->if_flags & IFF_RUNNING &&
+ ifp->if_flags & IFF_PROMISC &&
+ !(sc->sc_if_flags & IFF_PROMISC)) {
+ sc->sc_mac_csr |= SMSC_MAC_CSR_PRMS;
+ smsc_write_reg(sc, SMSC_MAC_CSR,
+ sc->sc_mac_csr);
+ smsc_setmulti(sc);
+ } else if (ifp->if_flags & IFF_RUNNING &&
+ !(ifp->if_flags & IFF_PROMISC) &&
+ sc->sc_if_flags & IFF_PROMISC) {
+ sc->sc_mac_csr &= ~SMSC_MAC_CSR_PRMS;
+ smsc_write_reg(sc, SMSC_MAC_CSR,
+ sc->sc_mac_csr);
+ smsc_setmulti(sc);
+ } else if (!(ifp->if_flags & IFF_RUNNING))
+ smsc_init(sc);
+ } else {
+ if (ifp->if_flags & IFF_RUNNING)
+ smsc_stop(sc);
+ }
+ sc->sc_if_flags = ifp->if_flags;
+ break;
+
+ case SIOCGIFMEDIA:
+ case SIOCSIFMEDIA:
+ mii = &sc->sc_mii;
+ error = ifmedia_ioctl(ifp, ifr, &mii->mii_media, cmd);
+ break;
+
+ default:
+ error = ether_ioctl(ifp, &sc->sc_ac, cmd, data);
+ }
+
+ if (error == ENETRESET) {
+ if (ifp->if_flags & IFF_RUNNING)
+ smsc_setmulti(sc);
+ error = 0;
+ }
+
+ splx(s);
+ return(error);
+}
+
+int
+smsc_match(struct device *parent, void *match, void *aux)
+{
+ struct usb_attach_arg *uaa = aux;
+
+ if (uaa->iface != NULL)
+ return UMATCH_NONE;
+
+ return (usb_lookup(smsc_devs, uaa->vendor, uaa->product) != NULL) ?
+ UMATCH_VENDOR_PRODUCT : UMATCH_NONE;
+}
+
+void
+smsc_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct smsc_softc *sc = (struct smsc_softc *)self;
+ struct usb_attach_arg *uaa = aux;
+ usbd_device_handle dev = uaa->device;
+ usb_interface_descriptor_t *id;
+ usb_endpoint_descriptor_t *ed;
+ struct mii_data *mii;
+ struct ifnet *ifp;
+ int err, s, i;
+ uint32_t mac_h, mac_l;
+
+ sc->sc_udev = dev;
+
+ err = usbd_set_config_no(dev, SMSC_CONFIG_INDEX, 1);
+
+ /* Setup the endpoints for the SMSC LAN95xx device(s) */
+ usb_init_task(&sc->sc_tick_task, smsc_tick_task, sc,
+ USB_TASK_TYPE_GENERIC);
+ rw_init(&sc->sc_mii_lock, "smscmii");
+ usb_init_task(&sc->sc_stop_task, (void (*)(void *))smsc_stop, sc,
+ USB_TASK_TYPE_GENERIC);
+
+ err = usbd_device2interface_handle(dev, SMSC_IFACE_IDX, &sc->sc_iface);
+ if (err) {
+ printf("%s: getting interface handle failed\n",
+ sc->sc_dev.dv_xname);
+ return;
+ }
+
+ id = usbd_get_interface_descriptor(sc->sc_iface);
+
+ /* Find endpoints. */
+ for (i = 0; i < id->bNumEndpoints; i++) {
+ ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i);
+ if (!ed) {
+ printf("%s: couldn't get ep %d\n",
+ sc->sc_dev.dv_xname, i);
+ return;
+ }
+ if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
+ UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
+ sc->sc_ed[SMSC_ENDPT_RX] = ed->bEndpointAddress;
+ } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
+ UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
+ sc->sc_ed[SMSC_ENDPT_TX] = ed->bEndpointAddress;
+ } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
+ UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) {
+ sc->sc_ed[SMSC_ENDPT_INTR] = ed->bEndpointAddress;
+ }
+ }
+
+ s = splnet();
+
+ ifp = &sc->sc_ac.ac_if;
+ ifp->if_softc = sc;
+ strlcpy(ifp->if_xname, sc->sc_dev.dv_xname, IFNAMSIZ);
+ ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+ ifp->if_ioctl = smsc_ioctl;
+ ifp->if_start = smsc_start;
+ ifp->if_capabilities = IFCAP_VLAN_MTU;
+
+ /* 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_ac.ac_enaddr, 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_ac.ac_enaddr[5] = (uint8_t)((mac_h >> 8) & 0xff);
+ sc->sc_ac.ac_enaddr[4] = (uint8_t)((mac_h) & 0xff);
+ sc->sc_ac.ac_enaddr[3] = (uint8_t)((mac_l >> 24) & 0xff);
+ sc->sc_ac.ac_enaddr[2] = (uint8_t)((mac_l >> 16) & 0xff);
+ sc->sc_ac.ac_enaddr[1] = (uint8_t)((mac_l >> 8) & 0xff);
+ sc->sc_ac.ac_enaddr[0] = (uint8_t)((mac_l) & 0xff);
+ }
+
+ printf("%s: address %s\n", sc->sc_dev.dv_xname,
+ ether_sprintf(sc->sc_ac.ac_enaddr));
+
+ /* Initialise the chip for the first time */
+ smsc_chip_init(sc);
+
+ IFQ_SET_READY(&ifp->if_snd);
+
+ /* Initialize MII/media info. */
+ mii = &sc->sc_mii;
+ mii->mii_ifp = ifp;
+ mii->mii_readreg = smsc_miibus_readreg;
+ mii->mii_writereg = smsc_miibus_writereg;
+ mii->mii_statchg = smsc_miibus_statchg;
+ mii->mii_flags = MIIF_AUTOTSLEEP;
+
+ ifmedia_init(&mii->mii_media, 0, smsc_ifmedia_upd, smsc_ifmedia_sts);
+ mii_attach(self, mii, 0xffffffff, MII_PHY_ANY, MII_OFFSET_ANY, 0);
+
+ if (LIST_FIRST(&mii->mii_phys) == NULL) {
+ ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_NONE, 0, NULL);
+ ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_NONE);
+ } else
+ ifmedia_set(&mii->mii_media, IFM_ETHER | IFM_AUTO);
+
+ if_attach(ifp);
+ ether_ifattach(ifp);
+
+ timeout_set(&sc->sc_stat_ch, smsc_tick, sc);
+
+ splx(s);
+}
+
+int
+smsc_detach(struct device *self, int flags)
+{
+ struct smsc_softc *sc = (struct smsc_softc *)self;
+ struct ifnet *ifp = &sc->sc_ac.ac_if;
+ int s;
+
+ if (timeout_initialized(&sc->sc_stat_ch))
+ timeout_del(&sc->sc_stat_ch);
+
+ if (sc->sc_ep[SMSC_ENDPT_TX] != NULL)
+ usbd_abort_pipe(sc->sc_ep[SMSC_ENDPT_TX]);
+ if (sc->sc_ep[SMSC_ENDPT_RX] != NULL)
+ usbd_abort_pipe(sc->sc_ep[SMSC_ENDPT_RX]);
+ if (sc->sc_ep[SMSC_ENDPT_INTR] != NULL)
+ usbd_abort_pipe(sc->sc_ep[SMSC_ENDPT_INTR]);
+
+ /*
+ * Remove any pending tasks. They cannot be executing because they run
+ * in the same thread as detach.
+ */
+ usb_rem_task(sc->sc_udev, &sc->sc_tick_task);
+ usb_rem_task(sc->sc_udev, &sc->sc_stop_task);
+
+ s = splusb();
+
+ if (--sc->sc_refcnt >= 0) {
+ /* Wait for processes to go away */
+ usb_detach_wait(&sc->sc_dev);
+ }
+
+ if (ifp->if_flags & IFF_RUNNING)
+ smsc_stop(sc);
+
+ mii_detach(&sc->sc_mii, MII_PHY_ANY, MII_OFFSET_ANY);
+ ifmedia_delete_instance(&sc->sc_mii.mii_media, IFM_INST_ANY);
+ if (ifp->if_softc != NULL) {
+ ether_ifdetach(ifp);
+ if_detach(ifp);
+ }
+
+#ifdef DIAGNOSTIC
+ if (sc->sc_ep[SMSC_ENDPT_TX] != NULL ||
+ sc->sc_ep[SMSC_ENDPT_RX] != NULL ||
+ sc->sc_ep[SMSC_ENDPT_INTR] != NULL)
+ printf("%s: detach has active endpoints\n",
+ sc->sc_dev.dv_xname);
+#endif
+
+ if (--sc->sc_refcnt >= 0) {
+ /* Wait for processes to go away. */
+ usb_detach_wait(&sc->sc_dev);
+ }
+ splx(s);
+
+ return (0);
+}
+
+void
+smsc_tick_task(void *xsc)
+{
+ int s;
+ struct smsc_softc *sc = xsc;
+ struct ifnet *ifp;
+ struct mii_data *mii;
+
+ if (sc == NULL)
+ return;
+
+ if (usbd_is_dying(sc->sc_udev))
+ return;
+ ifp = &sc->sc_ac.ac_if;
+ mii = &sc->sc_mii;
+ if (mii == NULL)
+ return;
+
+ s = splnet();
+
+ mii_tick(mii);
+ if ((sc->sc_flags & SMSC_FLAG_LINK) == 0)
+ smsc_miibus_statchg(&sc->sc_dev);
+ timeout_add_sec(&sc->sc_stat_ch, 1);
+
+ splx(s);
+}
+
+int
+smsc_activate(struct device *self, int act)
+{
+ struct smsc_softc *sc = (struct smsc_softc *)self;
+
+ switch (act) {
+ case DVACT_DEACTIVATE:
+ usbd_deactivate(sc->sc_udev);
+ break;
+ }
+ return (0);
+}
+
+void
+smsc_lock_mii(struct smsc_softc *sc)
+{
+ sc->sc_refcnt++;
+ rw_enter_write(&sc->sc_mii_lock);
+}
+
+void
+smsc_unlock_mii(struct smsc_softc *sc)
+{
+ rw_exit_write(&sc->sc_mii_lock);
+ if (--sc->sc_refcnt < 0)
+ usb_detach_wakeup(&sc->sc_dev);
+}
+
+void
+smsc_rxeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status)
+{
+ struct smsc_chain *c = (struct smsc_chain *)priv;
+ struct smsc_softc *sc = c->sc_sc;
+ struct ifnet *ifp = &sc->sc_ac.ac_if;
+ u_char *buf = c->sc_buf;
+ uint32_t total_len;
+ uint16_t pktlen = 0;
+ struct mbuf *m;
+ int s;
+ uint32_t rxhdr;
+
+ if (usbd_is_dying(sc->sc_udev))
+ return;
+
+ if (!(ifp->if_flags & IFF_RUNNING))
+ return;
+
+ if (status != USBD_NORMAL_COMPLETION) {
+ if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
+ return;
+ if (usbd_ratecheck(&sc->sc_rx_notice)) {
+ printf("%s: usb errors on rx: %s\n",
+ sc->sc_dev.dv_xname, usbd_errstr(status));
+ }
+ if (status == USBD_STALLED)
+ usbd_clear_endpoint_stall_async(sc->sc_ep[SMSC_ENDPT_RX]);
+ goto done;
+ }
+
+ usbd_get_xfer_status(xfer, NULL, NULL, &total_len, NULL);
+ smsc_dbg_printf(sc, "xfer status total_len %d\n", total_len);
+
+ do {
+ if (total_len < sizeof(rxhdr)) {
+ smsc_dbg_printf(sc, "total_len %d < sizeof(rxhdr) %d\n",
+ total_len, sizeof(rxhdr));
+ ifp->if_ierrors++;
+ goto done;
+ }
+
+ buf += pktlen;
+
+ memcpy(&rxhdr, buf, sizeof(rxhdr));
+ rxhdr = letoh32(rxhdr);
+ total_len -= sizeof(rxhdr);
+
+ if (rxhdr & SMSC_RX_STAT_ERROR) {
+ smsc_dbg_printf(sc, "rx error (hdr 0x%08x)\n", rxhdr);
+ ifp->if_ierrors++;
+ goto done;
+ }
+
+ pktlen = (uint16_t)SMSC_RX_STAT_FRM_LENGTH(rxhdr);
+ smsc_dbg_printf(sc, "rxeof total_len %d pktlen %d rxhdr "
+ "0x%08x\n", total_len, pktlen, rxhdr);
+ if (pktlen > total_len) {
+ smsc_dbg_printf(sc, "pktlen %d > total_len %d\n",
+ pktlen, total_len);
+ ifp->if_ierrors++;
+ goto done;
+ }
+
+ buf += sizeof(rxhdr);
+
+ if ((total_len - pktlen) < 0)
+ total_len = 0;
+ else
+ total_len -= pktlen;
+
+ m = smsc_newbuf();
+ if (m == NULL) {
+ smsc_dbg_printf(sc, "smc_newbuf returned NULL\n");
+ ifp->if_ierrors++;
+ goto done;
+ }
+
+ ifp->if_ipackets++;
+ m->m_pkthdr.rcvif = ifp;
+ m->m_pkthdr.len = m->m_len = pktlen;
+ m_adj(m, ETHER_ALIGN);
+
+ memcpy(mtod(m, char *), buf, pktlen);
+
+ /* push the packet up */
+ s = splnet();
+#if NBPFILTER > 0
+ if (ifp->if_bpf)
+ bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_IN);
+#endif
+ ether_input_mbuf(ifp, m);
+
+ splx(s);
+ } while (total_len > 0);
+
+done:
+ memset(c->sc_buf, 0, SMSC_BUFSZ);
+
+ /* Setup new transfer. */
+ usbd_setup_xfer(xfer, sc->sc_ep[SMSC_ENDPT_RX],
+ c, c->sc_buf, SMSC_BUFSZ,
+ USBD_SHORT_XFER_OK | USBD_NO_COPY,
+ USBD_NO_TIMEOUT, smsc_rxeof);
+ usbd_transfer(xfer);
+
+ return;
+}
+
+void
+smsc_txeof(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status)
+{
+ struct smsc_softc *sc;
+ struct smsc_chain *c;
+ struct ifnet *ifp;
+ int s;
+
+ c = priv;
+ sc = c->sc_sc;
+ ifp = &sc->sc_ac.ac_if;
+
+ if (usbd_is_dying(sc->sc_udev))
+ return;
+
+ s = splnet();
+
+ if (status != USBD_NORMAL_COMPLETION) {
+ if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) {
+ splx(s);
+ return;
+ }
+ ifp->if_oerrors++;
+ printf("%s: usb error on tx: %s\n", sc->sc_dev.dv_xname,
+ usbd_errstr(status));
+ if (status == USBD_STALLED)
+ usbd_clear_endpoint_stall_async(sc->sc_ep[SMSC_ENDPT_TX]);
+ splx(s);
+ return;
+ }
+
+ ifp->if_timer = 0;
+ ifp->if_flags &= ~IFF_OACTIVE;
+
+ m_freem(c->sc_mbuf);
+ c->sc_mbuf = NULL;
+
+ if (IFQ_IS_EMPTY(&ifp->if_snd) == 0)
+ smsc_start(ifp);
+
+ ifp->if_opackets++;
+ splx(s);
+}
+
+int
+smsc_tx_list_init(struct smsc_softc *sc)
+{
+ struct smsc_cdata *cd;
+ struct smsc_chain *c;
+ int i;
+
+ cd = &sc->sc_cdata;
+ for (i = 0; i < SMSC_TX_LIST_CNT; i++) {
+ c = &cd->tx_chain[i];
+ c->sc_sc = sc;
+ c->sc_idx = i;
+ c->sc_mbuf = NULL;
+ if (c->sc_xfer == NULL) {
+ c->sc_xfer = usbd_alloc_xfer(sc->sc_udev);
+ if (c->sc_xfer == NULL)
+ return (ENOBUFS);
+ c->sc_buf = usbd_alloc_buffer(c->sc_xfer,
+ SMSC_BUFSZ);
+ if (c->sc_buf == NULL) {
+ usbd_free_xfer(c->sc_xfer);
+ return (ENOBUFS);
+ }
+ }
+ }
+
+ return (0);
+}
+
+int
+smsc_rx_list_init(struct smsc_softc *sc)
+{
+ struct smsc_cdata *cd;
+ struct smsc_chain *c;
+ int i;
+
+ cd = &sc->sc_cdata;
+ for (i = 0; i < SMSC_RX_LIST_CNT; i++) {
+ c = &cd->rx_chain[i];
+ c->sc_sc = sc;
+ c->sc_idx = i;
+ c->sc_mbuf = NULL;
+ if (c->sc_xfer == NULL) {
+ c->sc_xfer = usbd_alloc_xfer(sc->sc_udev);
+ if (c->sc_xfer == NULL)
+ return (ENOBUFS);
+ c->sc_buf = usbd_alloc_buffer(c->sc_xfer,
+ SMSC_BUFSZ);
+ if (c->sc_buf == NULL) {
+ usbd_free_xfer(c->sc_xfer);
+ return (ENOBUFS);
+ }
+ }
+ }
+
+ return (0);
+}
+
+struct mbuf *
+smsc_newbuf(void)
+{
+ struct mbuf *m;
+
+ MGETHDR(m, M_DONTWAIT, MT_DATA);
+ if (m == NULL)
+ return (NULL);
+
+ MCLGET(m, M_DONTWAIT);
+ if (!(m->m_flags & M_EXT)) {
+ m_freem(m);
+ return (NULL);
+ }
+
+ return (m);
+}
+
+int
+smsc_encap(struct smsc_softc *sc, struct mbuf *m, int idx)
+{
+ struct smsc_chain *c;
+ usbd_status err;
+ uint32_t txhdr;
+ uint32_t frm_len = 0;
+
+ c = &sc->sc_cdata.tx_chain[idx];
+
+ /*
+ * 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);
+ memcpy(c->sc_buf, &txhdr, sizeof(txhdr));
+
+ txhdr = SMSC_TX_CTRL_1_PKT_LENGTH(m->m_pkthdr.len);
+ txhdr = htole32(txhdr);
+ memcpy(c->sc_buf + 4, &txhdr, sizeof(txhdr));
+
+ frm_len += 8;
+
+ /* Next copy in the actual packet */
+ m_copydata(m, 0, m->m_pkthdr.len, c->sc_buf + frm_len);
+ frm_len += m->m_pkthdr.len;
+
+ c->sc_mbuf = m;
+
+ usbd_setup_xfer(c->sc_xfer, sc->sc_ep[SMSC_ENDPT_TX],
+ c, c->sc_buf, frm_len, USBD_FORCE_SHORT_XFER | USBD_NO_COPY,
+ 10000, smsc_txeof);
+
+ err = usbd_transfer(c->sc_xfer);
+ if (err != USBD_IN_PROGRESS) {
+ smsc_stop(sc);
+ return (EIO);
+ }
+
+ sc->sc_cdata.tx_cnt++;
+
+ return (0);
+}
diff --git a/sys/dev/usb/if_smscreg.h b/sys/dev/usb/if_smscreg.h
new file mode 100644
index 00000000000..f642fdefaa8
--- /dev/null
+++ b/sys/dev/usb/if_smscreg.h
@@ -0,0 +1,307 @@
+/* $OpenBSD: if_smscreg.h,v 1.1 2012/09/19 16:33:04 jsg Exp $ */
+/*-
+ * 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.
+ *
+ * $FreeBSD: src/sys/dev/usb/net/if_smscreg.h,v 1.1 2012/08/15 04:03:55 gonzo Exp $
+ */
+#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_RX_LIST_CNT 1
+#define SMSC_TX_LIST_CNT 1
+
+#define SMSC_CONFIG_INDEX 1 /* config number 1 */
+#define SMSC_IFACE_IDX 0
+
+#define SMSC_ENDPT_RX 0
+#define SMSC_ENDPT_TX 1
+#define SMSC_ENDPT_INTR 2
+#define SMSC_ENDPT_MAX 3
+
+struct smsc_chain {
+ struct smsc_softc *sc_sc;
+ usbd_xfer_handle sc_xfer;
+ char *sc_buf;
+ struct mbuf *sc_mbuf;
+ int sc_accum;
+ int sc_idx;
+};
+
+struct smsc_cdata {
+ struct smsc_chain tx_chain[SMSC_TX_LIST_CNT];
+ struct smsc_chain rx_chain[SMSC_RX_LIST_CNT];
+ int tx_prod;
+ int tx_cons;
+ int tx_cnt;
+ int rx_prod;
+};
+
+struct smsc_softc {
+ struct device sc_dev;
+ usbd_device_handle sc_udev;
+ struct arpcom sc_ac;
+ struct mii_data sc_mii;
+ int sc_phyno;
+ usbd_interface_handle sc_iface;
+
+ /*
+ * The following stores the settings in the mac control (MAC_CSR)
+ * register
+ */
+ uint32_t sc_mac_csr;
+ uint32_t sc_rev_id;
+
+ int sc_if_flags;
+ int sc_refcnt;
+
+ struct usb_task sc_tick_task;
+ struct usb_task sc_stop_task;
+
+ int sc_ed[SMSC_ENDPT_MAX];
+ usbd_pipe_handle sc_ep[SMSC_ENDPT_MAX];
+
+ struct rwlock sc_mii_lock;
+
+ struct smsc_cdata sc_cdata;
+ struct timeout sc_stat_ch;
+
+ struct timeval sc_rx_notice;
+
+ uint32_t sc_flags;
+#define SMSC_FLAG_LINK 0x0001
+};
+
+#define SMSC_BUFSZ 18944
+
+#endif /* _IF_SMSCREG_H_ */