svn commit: r234065 - projects/armv6/sys/dev/usb/net
Ben Gray
bgray at FreeBSD.org
Mon Apr 9 17:33:35 UTC 2012
Author: bgray
Date: Mon Apr 9 17:33:35 2012
New Revision: 234065
URL: http://svn.freebsd.org/changeset/base/234065
Log:
Merged in SMSC driver changes, includes support for
H/W checksuming and multiple frames in the RX/TX urbs.
Modified:
projects/armv6/sys/dev/usb/net/if_smsc.c
projects/armv6/sys/dev/usb/net/if_smscreg.h
Modified: projects/armv6/sys/dev/usb/net/if_smsc.c
==============================================================================
--- projects/armv6/sys/dev/usb/net/if_smsc.c Mon Apr 9 17:05:18 2012 (r234064)
+++ projects/armv6/sys/dev/usb/net/if_smsc.c Mon Apr 9 17:33:35 2012 (r234065)
@@ -1,6 +1,6 @@
/*-
- * Copyright (c) 2011
- * Ben Gray <ben.r.gray at gmail.com>.
+ * Copyright (c) 2012
+ * Ben Gray <bgray at freebsd.org>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -11,30 +11,57 @@
* 2. Redistributions in binary form must 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
+ * 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 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.
+ * 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/cdefs.h>
__FBSDID("$FreeBSD$");
/*
- * SMSC LAN9xxx devices.
+ * 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 <sys/stdint.h>
#include <sys/stddef.h>
#include <sys/param.h>
@@ -53,6 +80,7 @@ __FBSDID("$FreeBSD$");
#include <sys/callout.h>
#include <sys/malloc.h>
#include <sys/priv.h>
+#include <sys/random.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
@@ -63,22 +91,9 @@ __FBSDID("$FreeBSD$");
#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,
-};
+#include <dev/usb/net/usb_ethernet.h>
+#include "if_smscreg.h"
#ifdef USB_DEBUG
static int smsc_debug = 0;
@@ -97,26 +112,32 @@ static const struct usb_device_id smsc_d
#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 */
-};
+#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...)
+#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_ZERO(addr) \
+ (!(addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]))
+
+#define ETHER_IS_VALID(addr) \
+ (!ETHER_IS_MULTICAST(addr) && !ETHER_IS_ZERO(addr))
+
-#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;
@@ -124,12 +145,14 @@ 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;
+#if __FreeBSD_version > 1000000
+static int smsc_attach_post_sub(struct usb_ether *ue);
+#endif
static uether_fn_t smsc_attach_post;
static uether_fn_t smsc_init;
static uether_fn_t smsc_stop;
@@ -142,6 +165,7 @@ 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 int smsc_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data);
static const struct usb_config smsc_config[SMSC_N_TRANSFER] = {
@@ -150,7 +174,7 @@ static const struct usb_config smsc_conf
.endpoint = UE_ADDR_ANY,
.direction = UE_DIR_OUT,
.frames = 16,
- .bufsize = 16 * MCLBYTES,
+ .bufsize = 16 * (MCLBYTES + 16),
.flags = {.pipe_bof = 1,.force_short_xfer = 1,},
.callback = smsc_bulk_write_callback,
.timeout = 10000, /* 10 seconds */
@@ -160,25 +184,24 @@ static const struct usb_config smsc_conf
.type = UE_BULK,
.endpoint = UE_ADDR_ANY,
.direction = UE_DIR_IN,
- .bufsize = 18944, /* bytes */
+ .bufsize = 20480, /* 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,
- },
+ /* 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,
+#if __FreeBSD_version > 1000000
+ .ue_attach_post_sub = smsc_attach_post_sub,
+#endif
.ue_start = smsc_start,
+ .ue_ioctl = smsc_ioctl,
.ue_init = smsc_init,
.ue_stop = smsc_stop,
.ue_tick = smsc_tick,
@@ -191,14 +214,17 @@ static const struct usb_ether_methods sm
/**
* 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:
- * Register value or 0 if read failed
+ * 0 on success, a USB_ERR_?? error code on failure.
*/
-static uint32_t
-smsc_read_reg(struct smsc_softc *sc, uint32_t off)
+static int
+smsc_read_reg(struct smsc_softc *sc, uint32_t off, uint32_t *data)
{
struct usb_device_request req;
uint32_t buf;
@@ -207,30 +233,33 @@ smsc_read_reg(struct smsc_softc *sc, uin
SMSC_LOCK_ASSERT(sc, MA_OWNED);
req.bmRequestType = UT_READ_VENDOR_DEVICE;
- req.bRequest = SMSC_UR_READ;
+ 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) {
- device_printf(sc->sc_ue.ue_dev, "Failed to read register 0x%0x, err = %d\n", off, err);
- return (0);
- }
+ if (err != 0)
+ smsc_warn_printf(sc, "Failed to read register 0x%0x\n", off);
- return le32toh(buf);
+ *data = le32toh(buf);
+
+ return (err);
}
/**
- * smsc_write_reg - Reads a 32-bit register on the device
+ * 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:
- * Nothing
+ * 0 on success, a USB_ERR_?? error code on failure.
*/
-static void
+static int
smsc_write_reg(struct smsc_softc *sc, uint32_t off, uint32_t data)
{
struct usb_device_request req;
@@ -242,153 +271,139 @@ smsc_write_reg(struct smsc_softc *sc, ui
buf = htole32(data);
req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
- req.bRequest = SMSC_UR_WRITE;
+ 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)
- device_printf(sc->sc_ue.ue_dev, "Failed to write register 0x%0x, err = %d\n", off, err);
+ smsc_warn_printf(sc, "Failed to write register 0x%0x\n", off);
+ return (err);
}
+
/**
- * smsc_wait_for_bits - Reads data from eeprom
- * @sc: driver soft context
- * @reg: register number
- * @bits: bit to wait for to clear
+ * 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 if succeeded, -1 if timed out
+ * 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)
{
- int i;
+ usb_ticks_t start_ticks;
+ usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000);
uint32_t val;
+ int err;
+
+ SMSC_LOCK_ASSERT(sc, MA_OWNED);
- for (i = 0; i != SMSC_TIMEOUT; i++) {
- val = smsc_read_reg(sc, SMSC_REG_E2P_CMD);
+ start_ticks = (usb_ticks_t)ticks;
+ do {
+ if ((err = smsc_read_reg(sc, reg, &val)) != 0)
+ return (err);
if (!(val & bits))
- break;
- if (uether_pause(&sc->sc_ue, hz / 100))
- break;
- }
-
- if (i == SMSC_TIMEOUT)
- return (-1);
+ return (0);
+
+ uether_pause(&sc->sc_ue, hz / 100);
+ } while ((ticks - start_ticks) < max_ticks);
- return (0);
+ return (USB_ERR_TIMEOUT);
}
/**
- * smsc_read_eeprom - Reads data from eeprom
- * @sc: driver soft context
- * @off: EEPROM offset
- * @data: memory to read data to
- * @length: read length bytes
+ * 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, -1 otherwise
+ * 0 on success, or a USB_ERR_?? error code on failure.
*/
static int
-smsc_read_eeprom(struct smsc_softc *sc, uint32_t off, uint8_t *data, int length)
+smsc_eeprom_read(struct smsc_softc *sc, uint16_t off, uint8_t *buf, uint16_t buflen)
{
- int timedout, i;
+ usb_ticks_t start_ticks;
+ usb_ticks_t max_ticks = USB_MS_TO_TICKS(1000);
+ int err;
+ int locked;
uint32_t val;
+ uint16_t i;
- 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);
- }
+ locked = mtx_owned(&sc->sc_mtx);
+ if (!locked)
+ SMSC_LOCK(sc);
- val = smsc_read_reg(sc, SMSC_REG_E2P_DATA);
- data[i] = val & E2P_DATA_MASK;
+ 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;
}
- 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);
- }
+ /* 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;
- /*
- * 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);
+ uether_pause(&sc->sc_ue, hz / 100);
+ } while ((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 (0);
+ return (err);
}
-#endif
/**
* smsc_miibus_readreg - Reads a MII/MDIO register
* @dev: usb ether device
- * @phy: the number of phy writing to
+ * @phy: the number of phy reading from
* @reg: the register address
- * @val: the value to write
*
+ * Attempts to read a phy register over the MII bus.
*
+ * LOCKING:
+ * Takes and releases the device mutex lock if not already held.
*
* RETURNS:
- * Returns 0 on success or a negative error code.
+ * 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)
@@ -397,27 +412,26 @@ smsc_miibus_readreg(device_t dev, int ph
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 (smsc_wait_for_bits(sc, SMSC_MII_ADDR, SMSC_MII_BUSY) != 0) {
+ smsc_warn_printf(sc, "MII is busy\n");
+ goto done;
}
- if (i == SMSC_TIMEOUT)
- device_printf(sc->sc_ue.ue_dev, "MII read timed out\n");
+ addr = (phy << 11) | (reg << 6) | SMSC_MII_READ;
+ smsc_write_reg(sc, SMSC_MII_ADDR, addr);
- val = smsc_read_reg(sc, SMS_REG_MII_DATA);
+ 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);
@@ -431,10 +445,13 @@ smsc_miibus_readreg(device_t dev, int ph
* @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:
- * 0
+ * Always returns 0 regardless of success or failure.
*/
static int
smsc_miibus_writereg(device_t dev, int phy, int reg, int val)
@@ -442,7 +459,6 @@ smsc_miibus_writereg(device_t dev, int p
struct smsc_softc *sc = device_get_softc(dev);
int locked;
uint32_t addr;
- int i;
if (sc->sc_phyno != phy)
return (0);
@@ -451,38 +467,37 @@ smsc_miibus_writereg(device_t dev, int p
if (!locked)
SMSC_LOCK(sc);
- val = htole32(val);
- smsc_write_reg(sc, SMS_REG_MII_DATA, val);
+ 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) | MII_WRITE;
- smsc_write_reg(sc, SMSC_REG_MII_ADDR, addr);
+ val = htole32(val);
+ smsc_write_reg(sc, SMSC_MII_DATA, val);
- 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;
- }
+ addr = (phy << 11) | (reg << 6) | SMSC_MII_WRITE;
+ smsc_write_reg(sc, SMSC_MII_ADDR, addr);
- if (i == SMSC_TIMEOUT)
- device_printf(sc->sc_ue.ue_dev, "MII write timed out\n");
+ 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 when the MII status changes
+ * 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.
*
- *
- * RETURNS:
- * Returns 0 on success or a negative error code.
+ * LOCKING:
+ * Takes and releases the device mutex lock if not already held.
*/
static void
smsc_miibus_statchg(device_t dev)
@@ -491,6 +506,9 @@ smsc_miibus_statchg(device_t dev)
struct mii_data *mii = uether_getmii(&sc->sc_ue);
struct ifnet *ifp;
int locked;
+ int err;
+ uint32_t flow;
+ uint32_t afc_cfg;
locked = mtx_owned(&sc->sc_mtx);
if (!locked)
@@ -500,7 +518,7 @@ smsc_miibus_statchg(device_t dev)
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)) ==
@@ -517,21 +535,49 @@ smsc_miibus_statchg(device_t dev)
break;
}
}
-
+
/* Lost link, do nothing. */
- if ((sc->sc_flags & SMSC_FLAG_LINK) == 0)
+ if ((sc->sc_flags & SMSC_FLAG_LINK) == 0) {
+ smsc_dbg_printf(sc, "link flag not set\n");
goto done;
+ }
- /* Enable/disable full duplex operation */
+ 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) {
- sc->sc_mac_cr &= ~MAC_CR_RCVOWN;
- sc->sc_mac_cr |= MAC_CR_FDPX;
+ 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 {
- sc->sc_mac_cr &= ~MAC_CR_FDPX;
- sc->sc_mac_cr |= MAC_CR_RCVOWN;
+ 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;
}
-
- smsc_write_reg(sc, SMSC_REG_MAC_CR, sc->sc_mac_cr);
+
+ 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)
@@ -545,6 +591,9 @@ done:
* 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.
*/
@@ -569,14 +618,14 @@ smsc_ifmedia_upd(struct ifnet *ifp)
/**
* smsc_ifmedia_sts - Report current media status
- * @ifp:
- * @ifmr:
+ * @ifp: inet interface pointer
+ * @ifmr: interface media request
*
* Basically boilerplate code that simply calls the mii functions to get the
* media status.
*
- * RETURNS:
- * Returns 0 on success or a negative error code.
+ * LOCKING:
+ * Internally takes and releases the device lock.
*/
static void
smsc_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr)
@@ -596,11 +645,14 @@ smsc_ifmedia_sts(struct ifnet *ifp, stru
/**
* smsc_hash - Calculate the hash of a mac address
- * @addr: The mac address to calculate the has on
+ * @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 which is the hash of the mac address.
+ * 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])
@@ -610,11 +662,13 @@ smsc_hash(uint8_t addr[ETHER_ADDR_LEN])
/**
* smsc_setmulti - Setup multicast
- * @ue:
+ * @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.
*
- * RETURNS:
- * Returns 0 on success or a negative error code.
+ * LOCKING:
+ * Should be called with the SMSC lock held.
*/
static void
smsc_setmulti(struct usb_ether *ue)
@@ -627,14 +681,10 @@ smsc_setmulti(struct usb_ether *ue)
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);
+ if (ifp->if_flags & (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 {
/* Take the lock of the mac address list before hashing each of them */
@@ -644,8 +694,8 @@ smsc_setmulti(struct usb_ether *ue)
/* 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);
+ sc->sc_mac_csr |= SMSC_MAC_CSR_HPFILT;
+ sc->sc_mac_csr &= ~(SMSC_MAC_CSR_PRMS | SMSC_MAC_CSR_MCPAS);
TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
if (ifma->ifma_addr->sa_family != AF_LINK)
@@ -656,25 +706,31 @@ smsc_setmulti(struct usb_ether *ue)
}
} else {
/* Only receive packets with destination set to our mac address */
- sc->sc_mac_cr &= ~(MAC_CR_PRMS | MAC_CR_MCPAS | MAC_CR_HPFILT);
+ sc->sc_mac_csr &= ~(SMSC_MAC_CSR_MCPAS | SMSC_MAC_CSR_HPFILT);
}
if_maddr_runlock(ifp);
+
+ /* 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_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_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 - Setup promiscuous mode
- * @ue:
- *
+ * smsc_setpromisc - Enables/disables promiscuous mode
+ * @ue: usb ethernet device context
*
- * RETURNS:
- * Returns 0 on success or a negative error code.
+ * LOCKING:
+ * Should be called with the SMSC lock held.
*/
static void
smsc_setpromisc(struct usb_ether *ue)
@@ -682,55 +738,111 @@ 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_dbg_printf(sc, "promiscuous mode %sabled\n",
+ (ifp->if_flags & IFF_PROMISC) ? "en" : "dis");
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;
+ if (ifp->if_flags & 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)
+{
+ struct ifnet *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 ((ifp->if_capabilities & ifp->if_capenable) & IFCAP_RXCSUM)
+ 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 & ifp->if_capenable) & 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);
}
- /* Write mac control registers */
- smsc_write_reg(sc, SMSC_REG_MAC_CR, sc->sc_mac_cr);
+ return (0);
}
+
/**
- * smsc_set_mac_address - Sets the mac address in the device
+ * 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 this doesn't need to be
- * done because typically the MAC is read from the attached EEPROM.
+ * 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 void
-smsc_set_mac_address(struct smsc_softc *sc, const uint8_t *addr)
+static int
+smsc_setmacaddress(struct smsc_softc *sc, const uint8_t *addr)
{
- uint32_t tmp;
+ 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);
- /* 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);
+ 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;
- /* Program the upper 2 bytes of the MAC */
- tmp = addr[5] << 8 | addr[4];
- smsc_write_reg(sc, SMSC_REG_ADDRH, tmp);
+ val = (addr[5] << 8) | addr[4];
+ err = smsc_write_reg(sc, SMSC_MAC_ADDRH, val);
+
+done:
+ return (err);
}
/**
- * smsc_reset - Reset the SMSC interface
+ * smsc_reset - Reset the SMSC chip
* @sc: device soft context
*
- *
- *
- * RETURNS:
- * Returns 0 on success or a negative error code.
+ * LOCKING:
+ * Should be called with the SMSC lock held.
*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
More information about the svn-src-projects
mailing list