Date: Sun, 12 Feb 2012 06:28:00 +0000 (UTC) From: Oleksandr Tymoshenko <gonzo@FreeBSD.org> To: src-committers@freebsd.org, svn-src-projects@freebsd.org Subject: svn commit: r231546 - in projects/armv6/sys: arm/conf conf dev/usb dev/usb/net Message-ID: <201202120628.q1C6S0iE068646@svn.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: gonzo Date: Sun Feb 12 06:27:59 2012 New Revision: 231546 URL: http://svn.freebsd.org/changeset/base/231546 Log: Add SMSC LAN95XX driver submitted by Ben Gray with some cleanup and EEPROM handling code by me. Before we figure out the way to pass MAC from u-boot to driver, stick with hardcoded address for debugging purposes. Submitted by: Ben Gray <ben.r.gray@gmail.com> Added: projects/armv6/sys/dev/usb/net/if_smsc.c (contents, props changed) projects/armv6/sys/dev/usb/net/if_smscreg.h (contents, props changed) Modified: projects/armv6/sys/arm/conf/PANDABOARD projects/armv6/sys/conf/files projects/armv6/sys/dev/usb/usbdevs Modified: projects/armv6/sys/arm/conf/PANDABOARD ============================================================================== --- projects/armv6/sys/arm/conf/PANDABOARD Sun Feb 12 06:01:49 2012 (r231545) +++ projects/armv6/sys/arm/conf/PANDABOARD Sun Feb 12 06:27:59 2012 (r231546) @@ -124,8 +124,8 @@ device da # Direct Access (disks) # USB Ethernet support, requires miibus device miibus -device axe # ASIX Electronics USB Ethernet -# device smsc # SMSC LAN95xx USB Ethernet +# device axe # ASIX Electronics USB Ethernet +device smsc # SMSC LAN95xx USB Ethernet # Flattened Device Tree options FDT Modified: projects/armv6/sys/conf/files ============================================================================== --- projects/armv6/sys/conf/files Sun Feb 12 06:01:49 2012 (r231545) +++ projects/armv6/sys/conf/files Sun Feb 12 06:27:59 2012 (r231546) @@ -1925,11 +1925,12 @@ dev/usb/net/if_ipheth.c optional ipheth dev/usb/net/if_kue.c optional kue dev/usb/net/if_mos.c optional mos dev/usb/net/if_rue.c optional rue +dev/usb/net/if_smsc.c optional smsc dev/usb/net/if_udav.c optional udav dev/usb/net/if_usie.c optional usie dev/usb/net/ruephy.c optional rue dev/usb/net/usb_ethernet.c optional aue | axe | cdce | cue | kue | mos | \ - rue | udav + rue | smsc | udav dev/usb/net/uhso.c optional uhso # # USB WLAN drivers Added: projects/armv6/sys/dev/usb/net/if_smsc.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ projects/armv6/sys/dev/usb/net/if_smsc.c Sun Feb 12 06:27:59 2012 (r231546) @@ -0,0 +1,1440 @@ +/*- + * Copyright (c) 2011 + * Ben Gray <ben.r.gray@gmail.com>. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the company nor the name of the author may be used to + * endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY BEN GRAY ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL BEN GRAY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * SMSC LAN9xxx devices. + * + */ + +#include <sys/stdint.h> +#include <sys/stddef.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/module.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/condvar.h> +#include <sys/sysctl.h> +#include <sys/sx.h> +#include <sys/unistd.h> +#include <sys/callout.h> +#include <sys/malloc.h> +#include <sys/priv.h> + +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usbdi_util.h> +#include "usbdevs.h" + +#define USB_DEBUG_VAR 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 <dev/usb/usb_device.h> + +/* + * From looking at the Linux SMSC logs I believe the LAN95xx devices have + * the following endpoints: + * Endpoints In 1 Out 2 Int 3 + * + */ +enum { + SMSC_BULK_DT_RD, + SMSC_BULK_DT_WR, + SMSC_INTR_DT_RD, + SMSC_N_TRANSFER, +}; + +#ifdef USB_DEBUG +static int smsc_debug = 0; + +SYSCTL_NODE(_hw_usb, OID_AUTO, smsc, CTLFLAG_RW, 0, "USB smsc"); +SYSCTL_INT(_hw_usb_smsc, OID_AUTO, debug, CTLFLAG_RW, &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(LAN9514_ETH, 0), +#undef SMSC_DEV +}; + +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 (SMSC_REG_MAC_CR) register */ + uint32_t sc_mac_cr; + + 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) + +#define SMSC_TIMEOUT 100 /* 10*ms */ + +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 usb_callback_t smsc_intr_callback; + +static miibus_readreg_t smsc_miibus_readreg; +static miibus_writereg_t smsc_miibus_writereg; +static miibus_statchg_t smsc_miibus_statchg; + +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(struct ifnet *); +static void smsc_ifmedia_sts(struct ifnet *, struct ifmediareq *); + +static int smsc_chip_init(struct smsc_softc *sc); + +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, + .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 = 18944, /* bytes */ + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,}, + .callback = smsc_bulk_read_callback, + .timeout = 0, /* no timeout */ + }, + + [SMSC_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 = smsc_intr_callback, + }, +}; + +static const struct usb_ether_methods smsc_ue_methods = { + .ue_attach_post = smsc_attach_post, + .ue_start = smsc_start, + .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 + * + * + * + * RETURNS: + * Register value or 0 if read failed + */ +static uint32_t +smsc_read_reg(struct smsc_softc *sc, uint32_t off) +{ + 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; + 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) { + device_printf(sc->sc_ue.ue_dev, "Failed to read register 0x%0x, err = %d\n", off, err); + return (0); + } + + return le32toh(buf); +} + +/** + * smsc_write_reg - Reads a 32-bit register on the device + * @sc: driver soft context + * + * + * + * RETURNS: + * Nothing + */ +static void +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; + 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) + device_printf(sc->sc_ue.ue_dev, "Failed to write register 0x%0x, err = %d\n", off, err); + +} + +/** + * smsc_wait_for_bits - Reads data from eeprom + * @sc: driver soft context + * @reg: register number + * @bits: bit to wait for to clear + * + * RETURNS: + * 0 if succeeded, -1 if timed out + */ +static int +smsc_wait_for_bits(struct smsc_softc *sc, uint32_t reg, uint32_t bits) +{ + int i; + uint32_t val; + + for (i = 0; i != SMSC_TIMEOUT; i++) { + val = smsc_read_reg(sc, SMSC_REG_E2P_CMD); + if (!(val & bits)) + break; + if (uether_pause(&sc->sc_ue, hz / 100)) + break; + } + + if (i == SMSC_TIMEOUT) + return (-1); + + return (0); +} + +/** + * smsc_read_eeprom - Reads data from eeprom + * @sc: driver soft context + * @off: EEPROM offset + * @data: memory to read data to + * @length: read length bytes + * + * + * + * RETURNS: + * 0 on success, -1 otherwise + */ +static int +smsc_read_eeprom(struct smsc_softc *sc, uint32_t off, uint8_t *data, int length) +{ + int timedout, i; + uint32_t val; + + if (smsc_wait_for_bits(sc, SMSC_REG_E2P_CMD, E2P_CMD_BUSY)) { + device_printf(sc->sc_ue.ue_dev, "Timed-out waiting for busy EEPROM\n"); + return (-1); + } + + for (i = 0; i < length; i++) { + smsc_write_reg(sc, SMSC_REG_E2P_CMD, + E2P_CMD_BUSY | E2P_CMD_READ | ((off+i) & E2P_CMD_ADDR)); + + timedout = smsc_wait_for_bits(sc, SMSC_REG_E2P_CMD, E2P_CMD_BUSY); + val = smsc_read_reg(sc, SMSC_REG_E2P_CMD); + if (timedout || (val & E2P_CMD_TIMEOUT)) { + device_printf(sc->sc_ue.ue_dev, + "Timed-out reading from EEPROM\n"); + return (-1); + } + + val = smsc_read_reg(sc, SMSC_REG_E2P_DATA); + data[i] = val & E2P_DATA_MASK; + } + + return (0); +} + +#if 0 +/** + * smsc_write_eeprom - Reads data from eeprom + * @sc: driver soft context + * @off: EEPROM offset + * @data: memory to write + * @length: write length bytes + * + * RETURNS: + * 0 on success, -1 otherwise + */ +static int +smsc_write_eeprom(struct smsc_softc *sc, uint32_t off, uint8_t *data, int length) +{ + int timedout, i; + uint32_t val; + + if (smsc_wait_for_bits(sc, SMSC_REG_E2P_CMD, E2P_CMD_BUSY)) { + device_printf(sc->sc_ue.ue_dev, "Timed-out waiting for busy EEPROM\n"); + return (-1); + } + + /* + * Write/Erase + */ + smsc_write_reg(sc, SMSC_REG_E2P_CMD, E2P_CMD_BUSY | E2P_CMD_EWEN); + timedout = smsc_wait_for_bits(sc, SMSC_REG_E2P_CMD, E2P_CMD_BUSY); + val = smsc_read_reg(sc, SMSC_REG_E2P_CMD); + + if (timedout || (val & E2P_CMD_TIMEOUT)) { + device_printf(sc->sc_ue.ue_dev, "Timed-out erasing EEPROM\n"); + return (-1); + } + + for (i = 0; i < length; i++) { + val = data[i]; + smsc_write_reg(sc, SMSC_REG_E2P_DATA, val); + smsc_write_reg(sc, SMSC_REG_E2P_CMD, + E2P_CMD_BUSY | E2P_CMD_WRITE | ((off+i) & E2P_CMD_ADDR)); + + timedout = smsc_wait_for_bits(sc, SMSC_REG_E2P_CMD, E2P_CMD_BUSY); + val = smsc_read_reg(sc, SMSC_REG_E2P_CMD); + + if (timedout || (val & E2P_CMD_TIMEOUT)) { + device_printf(sc->sc_ue.ue_dev, + "Timed-out writing EEPROM %d %x\n", i, val); + return (-1); + } + } + + return (0); +} +#endif + +/** + * smsc_miibus_readreg - Reads a MII/MDIO register + * @dev: usb ether device + * @phy: the number of phy writing to + * @reg: the register address + * @val: the value to write + * + * + * + * RETURNS: + * Returns 0 on success or a negative error code. + */ +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; + int i; + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + SMSC_LOCK(sc); + + addr = (phy << 11) | (reg << 6) | MII_READ; + smsc_write_reg(sc, SMSC_REG_MII_ADDR, addr); + + for (i = 0; i != SMSC_TIMEOUT; i++) { + if (!(smsc_read_reg(sc, SMSC_REG_MII_ADDR) & MII_BUSY)) + break; + if (uether_pause(&sc->sc_ue, hz / 100)) + break; + } + + if (i == SMSC_TIMEOUT) + device_printf(sc->sc_ue.ue_dev, "MII read timed out\n"); + + val = smsc_read_reg(sc, SMS_REG_MII_DATA); + + 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 + * + * + * + * RETURNS: + * 0 + */ +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; + int i; + + if (sc->sc_phyno != phy) + return (0); + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + SMSC_LOCK(sc); + + val = htole32(val); + smsc_write_reg(sc, SMS_REG_MII_DATA, val); + + addr = (phy << 11) | (reg << 6) | MII_WRITE; + smsc_write_reg(sc, SMSC_REG_MII_ADDR, addr); + + for (i = 0; i != SMSC_TIMEOUT; i++) { + if (!(smsc_read_reg(sc, SMSC_REG_MII_ADDR) & MII_BUSY)) + break; + if (uether_pause(&sc->sc_ue, hz / 100)) + break; + } + + if (i == SMSC_TIMEOUT) + device_printf(sc->sc_ue.ue_dev, "MII write timed out\n"); + + if (!locked) + SMSC_UNLOCK(sc); + + return (0); +} + + + +/** + * smsc_miibus_statchg - Called when the MII status changes + * @dev: usb ether device + * + * + * + * RETURNS: + * Returns 0 on success or a negative error code. + */ +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); + struct ifnet *ifp; + int locked; + + locked = mtx_owned(&sc->sc_mtx); + if (!locked) + SMSC_LOCK(sc); + + ifp = uether_getifp(&sc->sc_ue); + if (mii == NULL || ifp == NULL || + (ifp->if_drv_flags & 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) + goto done; + + /* Enable/disable full duplex operation */ + if ((IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) != 0) { + sc->sc_mac_cr &= ~MAC_CR_RCVOWN; + sc->sc_mac_cr |= MAC_CR_FDPX; + } else { + sc->sc_mac_cr &= ~MAC_CR_FDPX; + sc->sc_mac_cr |= MAC_CR_RCVOWN; + } + + smsc_write_reg(sc, SMSC_REG_MAC_CR, sc->sc_mac_cr); + +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. + * + * RETURNS: + * Returns 0 on success or a negative error code. + */ +static int +smsc_ifmedia_upd(struct ifnet *ifp) +{ + struct smsc_softc *sc = ifp->if_softc; + struct mii_data *mii = uether_getmii(&sc->sc_ue); + int err; + + SMSC_LOCK_ASSERT(sc, MA_OWNED); + + 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); +} + +/** + * smsc_ifmedia_sts - Report current media status + * @ifp: + * @ifmr: + * + * Basically boilerplate code that simply calls the mii functions to get the + * media status. + * + * RETURNS: + * Returns 0 on success or a negative error code. + */ +static void +smsc_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr) +{ + struct smsc_softc *sc = ifp->if_softc; + struct mii_data *mii = uether_getmii(&sc->sc_ue); + + SMSC_LOCK(sc); + + mii_pollstat(mii); + + SMSC_UNLOCK(sc); + + ifmr->ifm_active = mii->mii_media_active; + ifmr->ifm_status = mii->mii_media_status; +} + +/** + * smsc_hash - Calculate the hash of a mac address + * @addr: The mac address to calculate the has on + * + * + * RETURNS: + * Returns a value from 0-63 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; +} + +/** + * smsc_setmulti - Setup multicast + * @ue: + * + * + * RETURNS: + * Returns 0 on success or a negative error code. + */ +static void +smsc_setmulti(struct usb_ether *ue) +{ + struct smsc_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + struct ifmultiaddr *ifma; + uint32_t hashtbl[2] = { 0, 0 }; + uint32_t hash; + + SMSC_LOCK_ASSERT(sc, MA_OWNED); + + if (ifp->if_flags & IFF_PROMISC) { + /* Enter promiscuous mode and set the bits accordingly */ + sc->sc_mac_cr |= MAC_CR_PRMS; + sc->sc_mac_cr &= ~(MAC_CR_MCPAS | MAC_CR_HPFILT); + } else if (ifp->if_flags & IFF_ALLMULTI) { + /* Enter multicaste mode and set the bits accordingly */ + sc->sc_mac_cr |= MAC_CR_MCPAS; + sc->sc_mac_cr &= ~(MAC_CR_PRMS | MAC_CR_HPFILT); + + } else { + /* Take the lock of the mac address list before hashing each of them */ + if_maddr_rlock(ifp); + + if (!TAILQ_EMPTY(&ifp->if_multiaddrs)) { + /* 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_cr |= MAC_CR_HPFILT; + sc->sc_mac_cr &= ~(MAC_CR_PRMS | MAC_CR_MCPAS); + + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + if (ifma->ifma_addr->sa_family != AF_LINK) + continue; + + hash = smsc_hash(LLADDR((struct sockaddr_dl *)ifma->ifma_addr)); + hashtbl[hash >> 5] |= 1 << (hash & 0x1F); + } + } else { + /* Only receive packets with destination set to our mac address */ + sc->sc_mac_cr &= ~(MAC_CR_PRMS | MAC_CR_MCPAS | MAC_CR_HPFILT); + } + + if_maddr_runlock(ifp); + } + + /* Write the hash table and mac control registers */ + smsc_write_reg(sc, SMSC_REG_HASHH, hashtbl[1]); + smsc_write_reg(sc, SMSC_REG_HASHL, hashtbl[0]); + smsc_write_reg(sc, SMSC_REG_MAC_CR, sc->sc_mac_cr); +} + +/** + * smsc_setpromisc - Setup promiscuous mode + * @ue: + * + * + * RETURNS: + * Returns 0 on success or a negative error code. + */ +static void +smsc_setpromisc(struct usb_ether *ue) +{ + struct smsc_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + device_printf(sc->sc_ue.ue_dev, "promiscuous mode enabled\n"); + + SMSC_LOCK_ASSERT(sc, MA_OWNED); + + /* Set/clear the promiscuous bit based on setting */ + if (ifp->if_flags & IFF_PROMISC) { + sc->sc_mac_cr |= MAC_CR_PRMS; + } else { + sc->sc_mac_cr &= ~MAC_CR_PRMS; + } + + /* Write mac control registers */ + smsc_write_reg(sc, SMSC_REG_MAC_CR, sc->sc_mac_cr); +} + +/** + * smsc_set_mac_address - 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 this doesn't need to be + * done because typically the MAC is read from the attached EEPROM. + * + * + * RETURNS: + * Returns 0 on success or a negative error code. + */ +static void +smsc_set_mac_address(struct smsc_softc *sc, const uint8_t *addr) +{ + uint32_t tmp; + + /* Program the lower 4 bytes of the MAC */ + tmp = addr[3] << 24 | addr[2] << 16 | addr[1] << 8 | addr[0]; + smsc_write_reg(sc, SMSC_REG_ADDRL, tmp); + + /* Program the upper 2 bytes of the MAC */ + tmp = addr[5] << 8 | addr[4]; + smsc_write_reg(sc, SMSC_REG_ADDRH, tmp); +} + +/** + * smsc_reset - Reset the SMSC interface + * @sc: device soft context + * + * + * + * RETURNS: + * Returns 0 on success or a negative error code. + */ +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) + device_printf(sc->sc_ue.ue_dev, "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 + * + * + * + * RETURNS: + * Returns 0 on success or a negative error code. + */ +static void +smsc_init(struct usb_ether *ue) +{ + struct smsc_softc *sc = uether_getsc(ue); + struct ifnet *ifp = uether_getifp(ue); + + SMSC_LOCK_ASSERT(sc, MA_OWNED); + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) != 0) + return; + + /* Cancel pending I/O */ + smsc_stop(ue); + + /* Reset the ethernet interface */ + smsc_reset(sc); + + /* Set MAC address. */ + smsc_set_mac_address(sc, IF_LLADDR(ifp)); + + /* Load the multicast filter. */ + smsc_setmulti(ue); + + usbd_xfer_set_stall(sc->sc_xfer[SMSC_BULK_DT_WR]); + + /* Indicate we are up and running */ + ifp->if_drv_flags |= IFF_DRV_RUNNING; + + /* Switch to selected media. */ + smsc_ifmedia_upd(ifp); + smsc_start(ue); +} + + +/** + * smsc_intr_callback - Inteerupt callback used to process the USB packet + * @xfer: the USB transfer + * @error: + * + * + * + * RETURNS: + * Returns 0 on success or a negative error code. + */ +static void +smsc_intr_callback(struct usb_xfer *xfer, usb_error_t error) +{ + int actlen; + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); +} + +/** + * smsc_bulk_read_callback - Read callback used to process the USB packet + * @xfer: the USB transfer + * @error: + * + * + * + * RETURNS: + * Returns 0 on success or a negative error code. + */ +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; + struct ifnet *ifp = uether_getifp(ue); + struct usb_page_cache *pc; + uint32_t rxhdr; + uint16_t pktlen; + uint32_t len; + + int actlen; + int frames; + static int count = 0; + + usbd_xfer_status(xfer, &actlen, NULL, &frames, NULL); + count++; + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + /* From looking at the linux driver it appears the received packet + * is prefixed with a 32-bit header, which contains information like + * the status of the received packet. + * + * Also 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. + */ + if (actlen < sizeof(rxhdr) + ETHER_CRC_LEN) { + ifp->if_ierrors++; + printf("DROP\n"); + goto tr_setup; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, &rxhdr, sizeof(rxhdr)); + actlen -= sizeof(rxhdr); + rxhdr = le32toh(rxhdr); + pktlen = (uint16_t)((rxhdr & SMSC_RX_STATUS_FL_MASK) >> SMSC_RX_STATUS_FL_SHIFT); + len = min(pktlen, actlen); + if (rxhdr & SMSC_RX_STATUS_ES) { + ifp->if_ierrors++; + printf("DROP\n"); + goto tr_setup; + } + uether_rxbuf(ue, pc, 2 + sizeof(rxhdr), 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: + device_printf(sc->sc_ue.ue_dev, "bulk read error, %s", usbd_errstr(error)); + if (error != USB_ERR_CANCELLED) { + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + device_printf(sc->sc_ue.ue_dev, "start rx %i", usbd_xfer_max_len(xfer)); + return; + } +} + +/** + * smsc_bulk_write_callback - Write callback used to send ethernet frame + * @xfer: the USB transfer + * @error: + * + * + * + * RETURNS: + * Returns 0 on success or a negative error code. + */ +static void +smsc_bulk_write_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct smsc_softc *sc = usbd_xfer_softc(xfer); + struct ifnet *ifp = uether_getifp(&sc->sc_ue); + struct usb_page_cache *pc; + struct mbuf *m; + uint32_t txhdr; + uint32_t frm_len = 0; + uint32_t csum_prefix = 0; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + /* FALLTHROUGH */ + + case USB_ST_SETUP: +tr_setup: + if ((sc->sc_flags & SMSC_FLAG_LINK) == 0 || + (ifp->if_drv_flags & IFF_DRV_OACTIVE) != 0) { + /* Don't send anything if there is no link or controller is + * busy. + */ + return; + } + + /* Pull the frame of the queue */ + IFQ_DRV_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + return; + + /* Get the frame so we copy in the header and frame data */ + pc = usbd_xfer_get_frame(xfer, 0); + + /* Check if we can use the H/W checksumming */ + if ((ifp->if_capenable & IFCAP_TXCSUM) && + ((m->m_pkthdr.csum_flags & (CSUM_TCP | CSUM_UDP)) != 0)) { + panic("HW checksumming support is not implemented\n"); + } + + /* Each frame is prefixed with two 32-bit values describing the + * length and checksum offloading request. + */ + txhdr = m->m_pkthdr.len | SMSC_TX_CMD_A_FIRST_SEG | SMSC_TX_CMD_A_LAST_SEG; + txhdr = htole32(txhdr); + usbd_copy_in(pc, 0, &txhdr, sizeof(txhdr)); + + txhdr = m->m_pkthdr.len; + if (csum_prefix) + txhdr |= SMSC_TX_CMD_B_CSUM_ENABLE; + 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; + + /* Set the length of the transfer including the header */ + usbd_xfer_set_frame_len(xfer, 0, frm_len); + + /* Update the number of packets sent */ + ifp->if_opackets++; + + /* If there's a BPF listener, bounce a copy of this frame to him */ *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201202120628.q1C6S0iE068646>