76b0c5e318
- usbnet_enqueue() can set mbuf flags and csum_data - usbnet_input() for non-ethernet based devices (upl, umb) - allow a complete override for ioctl() - remove converted list -- we have compiling and/or working patches for all the devices except for umb(4), will be merged as testing happens hopefully this is the last ABI change, though it may end up being extended for additional smsc(4) support. hello for real netbsd 9.99.3!
1018 lines
26 KiB
C
1018 lines
26 KiB
C
/* $NetBSD: if_axen.c,v 1.55 2019/08/06 00:19:57 mrg Exp $ */
|
|
/* $OpenBSD: if_axen.c,v 1.3 2013/10/21 10:10:22 yuo Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2013 Yojiro UO <yuo@openbsd.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
/*
|
|
* ASIX Electronics AX88178a USB 2.0 ethernet and AX88179 USB 3.0 Ethernet
|
|
* driver.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__KERNEL_RCSID(0, "$NetBSD: if_axen.c,v 1.55 2019/08/06 00:19:57 mrg Exp $");
|
|
|
|
#ifdef _KERNEL_OPT
|
|
#include "opt_usb.h"
|
|
#endif
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/module.h>
|
|
|
|
#include <netinet/in.h> /* XXX for netinet/ip.h */
|
|
#include <netinet/ip.h> /* XXX for IP_MAXPACKET */
|
|
|
|
#include <dev/usb/usbnet.h>
|
|
|
|
#include <dev/usb/if_axenreg.h>
|
|
|
|
#ifdef AXEN_DEBUG
|
|
#define DPRINTF(x) do { if (axendebug) printf x; } while (/*CONSTCOND*/0)
|
|
#define DPRINTFN(n, x) do { if (axendebug >= (n)) printf x; } while (/*CONSTCOND*/0)
|
|
int axendebug = 0;
|
|
#else
|
|
#define DPRINTF(x)
|
|
#define DPRINTFN(n, x)
|
|
#endif
|
|
|
|
struct axen_softc {
|
|
struct usbnet axen_un;
|
|
int axen_rev;
|
|
};
|
|
|
|
struct axen_type {
|
|
struct usb_devno axen_devno;
|
|
uint16_t axen_flags;
|
|
#define AX178A 0x0001 /* AX88178a */
|
|
#define AX179 0x0002 /* AX88179 */
|
|
};
|
|
|
|
/*
|
|
* Various supported device vendors/products.
|
|
*/
|
|
static const struct axen_type axen_devs[] = {
|
|
#if 0 /* not tested */
|
|
{ { USB_VENDOR_ASIX, USB_PRODUCT_ASIX_AX88178A}, AX178A },
|
|
#endif
|
|
{ { USB_VENDOR_ASIX, USB_PRODUCT_ASIX_AX88179}, AX179 },
|
|
{ { USB_VENDOR_DLINK, USB_PRODUCT_DLINK_DUB1312}, AX179 }
|
|
};
|
|
|
|
#define axen_lookup(v, p) ((const struct axen_type *)usb_lookup(axen_devs, v, p))
|
|
|
|
static int axen_match(device_t, cfdata_t, void *);
|
|
static void axen_attach(device_t, device_t, void *);
|
|
|
|
CFATTACH_DECL_NEW(axen, sizeof(struct axen_softc),
|
|
axen_match, axen_attach, usbnet_detach, usbnet_activate);
|
|
|
|
static unsigned axen_tx_prepare(struct usbnet *, struct mbuf *,
|
|
struct usbnet_chain *);
|
|
static int axen_init(struct ifnet *);
|
|
static int axen_cmd(struct axen_softc *, int, int, int, void *);
|
|
static void axen_reset(struct axen_softc *);
|
|
static int axen_get_eaddr(struct axen_softc *, void *);
|
|
static void axen_stop_cb(struct ifnet *, int);
|
|
static void axen_ax88179_init(struct axen_softc *);
|
|
|
|
static usbd_status axen_mii_read_reg(struct usbnet *, int, int, uint16_t *);
|
|
static usbd_status axen_mii_write_reg(struct usbnet *, int, int, uint16_t);
|
|
static void axen_rxeof_loop(struct usbnet *, struct usbd_xfer *,
|
|
struct usbnet_chain *, uint32_t);
|
|
|
|
static int
|
|
axen_cmd(struct axen_softc *sc, int cmd, int index, int val, void *buf)
|
|
{
|
|
struct usbnet * const un = &sc->axen_un;
|
|
usb_device_request_t req;
|
|
usbd_status err;
|
|
|
|
usbnet_isowned_mii(un);
|
|
|
|
if (un->un_dying)
|
|
return 0;
|
|
|
|
if (AXEN_CMD_DIR(cmd))
|
|
req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
|
|
else
|
|
req.bmRequestType = UT_READ_VENDOR_DEVICE;
|
|
req.bRequest = AXEN_CMD_CMD(cmd);
|
|
USETW(req.wValue, val);
|
|
USETW(req.wIndex, index);
|
|
USETW(req.wLength, AXEN_CMD_LEN(cmd));
|
|
|
|
err = usbd_do_request(un->un_udev, &req, buf);
|
|
DPRINTFN(5, ("axen_cmd: cmd 0x%04x val 0x%04x len %d\n",
|
|
cmd, val, AXEN_CMD_LEN(cmd)));
|
|
|
|
if (err) {
|
|
DPRINTF(("%s: cmd: %d, error: %d\n", __func__, cmd, err));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static usbd_status
|
|
axen_mii_read_reg(struct usbnet *un, int phy, int reg, uint16_t *val)
|
|
{
|
|
struct axen_softc * const sc = usbnet_softc(un);
|
|
uint16_t data;
|
|
usbd_status err = axen_cmd(sc, AXEN_CMD_MII_READ_REG, reg, phy, &data);
|
|
|
|
if (!err) {
|
|
*val = le16toh(data);
|
|
|
|
if (reg == MII_BMSR)
|
|
*val &= ~BMSR_EXTCAP;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static usbd_status
|
|
axen_mii_write_reg(struct usbnet *un, int phy, int reg, uint16_t val)
|
|
{
|
|
struct axen_softc * const sc = usbnet_softc(un);
|
|
uint16_t uval = htole16(val);
|
|
|
|
return axen_cmd(sc, AXEN_CMD_MII_WRITE_REG, reg, phy, &uval);
|
|
}
|
|
|
|
static void
|
|
axen_mii_statchg(struct ifnet *ifp)
|
|
{
|
|
struct usbnet * const un = ifp->if_softc;
|
|
struct axen_softc * const sc = usbnet_softc(un);
|
|
struct mii_data * const mii = usbnet_mii(un);
|
|
int err;
|
|
uint16_t val;
|
|
uint16_t wval;
|
|
|
|
if (un->un_dying)
|
|
return;
|
|
|
|
un->un_link = false;
|
|
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:
|
|
un->un_link = true;
|
|
break;
|
|
case IFM_1000_T:
|
|
un->un_link = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Lost link, do nothing. */
|
|
if (!un->un_link)
|
|
return;
|
|
|
|
val = 0;
|
|
if ((mii->mii_media_active & IFM_FDX) != 0)
|
|
val |= AXEN_MEDIUM_FDX;
|
|
|
|
val |= AXEN_MEDIUM_RXFLOW_CTRL_EN | AXEN_MEDIUM_TXFLOW_CTRL_EN |
|
|
AXEN_MEDIUM_RECV_EN;
|
|
switch (IFM_SUBTYPE(mii->mii_media_active)) {
|
|
case IFM_1000_T:
|
|
val |= AXEN_MEDIUM_GIGA | AXEN_MEDIUM_EN_125MHZ;
|
|
break;
|
|
case IFM_100_TX:
|
|
val |= AXEN_MEDIUM_PS;
|
|
break;
|
|
case IFM_10_T:
|
|
/* doesn't need to be handled */
|
|
break;
|
|
}
|
|
|
|
DPRINTF(("%s: val=0x%x\n", __func__, val));
|
|
wval = htole16(val);
|
|
usbnet_lock_mii(un);
|
|
err = axen_cmd(sc, AXEN_CMD_MAC_WRITE2, 2, AXEN_MEDIUM_STATUS, &wval);
|
|
usbnet_unlock_mii(un);
|
|
if (err)
|
|
aprint_error_dev(un->un_dev, "media change failed\n");
|
|
}
|
|
|
|
static void
|
|
axen_setiff_locked(struct usbnet *un)
|
|
{
|
|
struct axen_softc * const sc = usbnet_softc(un);
|
|
struct ifnet * const ifp = usbnet_ifp(un);
|
|
struct ethercom *ec = usbnet_ec(un);
|
|
struct ether_multi *enm;
|
|
struct ether_multistep step;
|
|
uint32_t h = 0;
|
|
uint16_t rxmode;
|
|
uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
uint16_t wval;
|
|
|
|
if (un->un_dying)
|
|
return;
|
|
|
|
usbnet_isowned_mii(un);
|
|
|
|
rxmode = 0;
|
|
|
|
/* Enable receiver, set RX mode */
|
|
axen_cmd(sc, AXEN_CMD_MAC_READ2, 2, AXEN_MAC_RXCTL, &wval);
|
|
rxmode = le16toh(wval);
|
|
rxmode &= ~(AXEN_RXCTL_ACPT_ALL_MCAST | AXEN_RXCTL_PROMISC |
|
|
AXEN_RXCTL_ACPT_MCAST);
|
|
|
|
if (ifp->if_flags & IFF_PROMISC) {
|
|
DPRINTF(("%s: promisc\n", device_xname(un->un_dev)));
|
|
rxmode |= AXEN_RXCTL_PROMISC;
|
|
allmulti:
|
|
ETHER_LOCK(ec);
|
|
ec->ec_flags |= ETHER_F_ALLMULTI;
|
|
ETHER_UNLOCK(ec);
|
|
rxmode |= AXEN_RXCTL_ACPT_ALL_MCAST
|
|
/* | AXEN_RXCTL_ACPT_PHY_MCAST */;
|
|
} else {
|
|
/* now program new ones */
|
|
DPRINTF(("%s: initializing hash table\n",
|
|
device_xname(un->un_dev)));
|
|
ETHER_LOCK(ec);
|
|
ec->ec_flags &= ~ETHER_F_ALLMULTI;
|
|
|
|
ETHER_FIRST_MULTI(step, ec, enm);
|
|
while (enm != NULL) {
|
|
if (memcmp(enm->enm_addrlo, enm->enm_addrhi,
|
|
ETHER_ADDR_LEN)) {
|
|
DPRINTF(("%s: allmulti\n",
|
|
device_xname(un->un_dev)));
|
|
memset(hashtbl, 0, sizeof(hashtbl));
|
|
ETHER_UNLOCK(ec);
|
|
goto allmulti;
|
|
}
|
|
h = ether_crc32_be(enm->enm_addrlo,
|
|
ETHER_ADDR_LEN) >> 26;
|
|
hashtbl[h / 8] |= 1 << (h % 8);
|
|
DPRINTF(("%s: %s added\n",
|
|
device_xname(un->un_dev),
|
|
ether_sprintf(enm->enm_addrlo)));
|
|
ETHER_NEXT_MULTI(step, enm);
|
|
}
|
|
ETHER_UNLOCK(ec);
|
|
rxmode |= AXEN_RXCTL_ACPT_MCAST;
|
|
}
|
|
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE_FILTER, 8, AXEN_FILTER_MULTI, hashtbl);
|
|
wval = htole16(rxmode);
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE2, 2, AXEN_MAC_RXCTL, &wval);
|
|
}
|
|
|
|
static void
|
|
axen_setiff(struct usbnet *un)
|
|
{
|
|
usbnet_lock_mii(un);
|
|
axen_setiff_locked(un);
|
|
usbnet_unlock_mii(un);
|
|
}
|
|
|
|
static void
|
|
axen_reset(struct axen_softc *sc)
|
|
{
|
|
struct usbnet * const un = &sc->axen_un;
|
|
|
|
usbnet_isowned(un);
|
|
if (un->un_dying)
|
|
return;
|
|
/* XXX What to reset? */
|
|
|
|
/* Wait a little while for the chip to get its brains in order. */
|
|
DELAY(1000);
|
|
}
|
|
|
|
static int
|
|
axen_get_eaddr(struct axen_softc *sc, void *addr)
|
|
{
|
|
#if 1
|
|
return axen_cmd(sc, AXEN_CMD_MAC_READ_ETHER, 6, AXEN_CMD_MAC_NODE_ID,
|
|
addr);
|
|
#else
|
|
struct usbnet * const un = &sc->axen_un;
|
|
int i, retry;
|
|
uint8_t eeprom[20];
|
|
uint16_t csum;
|
|
uint16_t buf;
|
|
|
|
for (i = 0; i < 6; i++) {
|
|
/* set eeprom address */
|
|
buf = htole16(i);
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE, 1, AXEN_MAC_EEPROM_ADDR, &buf);
|
|
|
|
/* set eeprom command */
|
|
buf = htole16(AXEN_EEPROM_READ);
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE, 1, AXEN_MAC_EEPROM_CMD, &buf);
|
|
|
|
/* check the value is ready */
|
|
retry = 3;
|
|
do {
|
|
buf = htole16(AXEN_EEPROM_READ);
|
|
usbd_delay_ms(un->un_udev, 10);
|
|
axen_cmd(sc, AXEN_CMD_MAC_READ, 1, AXEN_MAC_EEPROM_CMD,
|
|
&buf);
|
|
retry--;
|
|
if (retry < 0)
|
|
return EINVAL;
|
|
} while ((le16toh(buf) & 0xff) & AXEN_EEPROM_BUSY);
|
|
|
|
/* read data */
|
|
axen_cmd(sc, AXEN_CMD_MAC_READ2, 2, AXEN_EEPROM_READ,
|
|
&eeprom[i * 2]);
|
|
|
|
/* sanity check */
|
|
if ((i == 0) && (eeprom[0] == 0xff))
|
|
return EINVAL;
|
|
}
|
|
|
|
/* check checksum */
|
|
csum = eeprom[6] + eeprom[7] + eeprom[8] + eeprom[9];
|
|
csum = (csum >> 8) + (csum & 0xff) + eeprom[10];
|
|
if (csum != 0xff) {
|
|
printf("eeprom checksum mismatch(0x%02x)\n", csum);
|
|
return EINVAL;
|
|
}
|
|
|
|
memcpy(addr, eeprom, ETHER_ADDR_LEN);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
axen_ax88179_init(struct axen_softc *sc)
|
|
{
|
|
struct usbnet * const un = &sc->axen_un;
|
|
struct axen_qctrl qctrl;
|
|
uint16_t ctl, temp;
|
|
uint16_t wval;
|
|
uint8_t val;
|
|
|
|
usbnet_lock_mii(un);
|
|
|
|
/* XXX: ? */
|
|
axen_cmd(sc, AXEN_CMD_MAC_READ, 1, AXEN_UNK_05, &val);
|
|
DPRINTFN(5, ("AXEN_CMD_MAC_READ(0x05): 0x%02x\n", val));
|
|
|
|
/* check AX88179 version, UA1 / UA2 */
|
|
axen_cmd(sc, AXEN_CMD_MAC_READ, 1, AXEN_GENERAL_STATUS, &val);
|
|
/* UA1 */
|
|
if (!(val & AXEN_GENERAL_STATUS_MASK)) {
|
|
sc->axen_rev = AXEN_REV_UA1;
|
|
DPRINTF(("AX88179 ver. UA1\n"));
|
|
} else {
|
|
sc->axen_rev = AXEN_REV_UA2;
|
|
DPRINTF(("AX88179 ver. UA2\n"));
|
|
}
|
|
|
|
/* power up ethernet PHY */
|
|
wval = htole16(0);
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE2, 2, AXEN_PHYPWR_RSTCTL, &wval);
|
|
|
|
wval = htole16(AXEN_PHYPWR_RSTCTL_IPRL);
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE2, 2, AXEN_PHYPWR_RSTCTL, &wval);
|
|
usbd_delay_ms(un->un_udev, 200);
|
|
|
|
/* set clock mode */
|
|
val = AXEN_PHYCLK_ACS | AXEN_PHYCLK_BCS;
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE, 1, AXEN_PHYCLK, &val);
|
|
usbd_delay_ms(un->un_udev, 100);
|
|
|
|
/* set monitor mode (disable) */
|
|
val = AXEN_MONITOR_NONE;
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE, 1, AXEN_MONITOR_MODE, &val);
|
|
|
|
/* enable auto detach */
|
|
axen_cmd(sc, AXEN_CMD_EEPROM_READ, 2, AXEN_EEPROM_STAT, &wval);
|
|
temp = le16toh(wval);
|
|
DPRINTFN(2,("EEPROM0x43 = 0x%04x\n", temp));
|
|
if (!(temp == 0xffff) && !(temp & 0x0100)) {
|
|
/* Enable auto detach bit */
|
|
val = 0;
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE, 1, AXEN_PHYCLK, &val);
|
|
val = AXEN_PHYCLK_ULR;
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE, 1, AXEN_PHYCLK, &val);
|
|
usbd_delay_ms(un->un_udev, 100);
|
|
|
|
axen_cmd(sc, AXEN_CMD_MAC_READ2, 2, AXEN_PHYPWR_RSTCTL, &wval);
|
|
ctl = le16toh(wval);
|
|
ctl |= AXEN_PHYPWR_RSTCTL_AUTODETACH;
|
|
wval = htole16(ctl);
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE2, 2, AXEN_PHYPWR_RSTCTL, &wval);
|
|
usbd_delay_ms(un->un_udev, 200);
|
|
aprint_error_dev(un->un_dev, "enable auto detach (0x%04x)\n",
|
|
ctl);
|
|
}
|
|
|
|
/* bulkin queue setting */
|
|
axen_cmd(sc, AXEN_CMD_MAC_READ, 1, AXEN_USB_UPLINK, &val);
|
|
switch (val) {
|
|
case AXEN_USB_FS:
|
|
DPRINTF(("uplink: USB1.1\n"));
|
|
qctrl.ctrl = 0x07;
|
|
qctrl.timer_low = 0xcc;
|
|
qctrl.timer_high = 0x4c;
|
|
qctrl.bufsize = AXEN_BUFSZ_LS - 1;
|
|
qctrl.ifg = 0x08;
|
|
break;
|
|
case AXEN_USB_HS:
|
|
DPRINTF(("uplink: USB2.0\n"));
|
|
qctrl.ctrl = 0x07;
|
|
qctrl.timer_low = 0x02;
|
|
qctrl.timer_high = 0xa0;
|
|
qctrl.bufsize = AXEN_BUFSZ_HS - 1;
|
|
qctrl.ifg = 0xff;
|
|
break;
|
|
case AXEN_USB_SS:
|
|
DPRINTF(("uplink: USB3.0\n"));
|
|
qctrl.ctrl = 0x07;
|
|
qctrl.timer_low = 0x4f;
|
|
qctrl.timer_high = 0x00;
|
|
qctrl.bufsize = AXEN_BUFSZ_SS - 1;
|
|
qctrl.ifg = 0xff;
|
|
break;
|
|
default:
|
|
aprint_error_dev(un->un_dev, "unknown uplink bus:0x%02x\n",
|
|
val);
|
|
usbnet_unlock_mii(un);
|
|
return;
|
|
}
|
|
axen_cmd(sc, AXEN_CMD_MAC_SET_RXSR, 5, AXEN_RX_BULKIN_QCTRL, &qctrl);
|
|
|
|
/*
|
|
* set buffer high/low watermark to pause/resume.
|
|
* write 2byte will set high/log simultaneous with AXEN_PAUSE_HIGH.
|
|
* XXX: what is the best value? OSX driver uses 0x3c-0x4c as LOW-HIGH
|
|
* watermark parameters.
|
|
*/
|
|
val = 0x34;
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE, 1, AXEN_PAUSE_LOW_WATERMARK, &val);
|
|
val = 0x52;
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE, 1, AXEN_PAUSE_HIGH_WATERMARK, &val);
|
|
|
|
/* Set RX/TX configuration. */
|
|
/* Set RX control register */
|
|
ctl = AXEN_RXCTL_IPE | AXEN_RXCTL_DROPCRCERR | AXEN_RXCTL_AUTOB;
|
|
wval = htole16(ctl);
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE2, 2, AXEN_MAC_RXCTL, &wval);
|
|
|
|
/* set monitor mode (enable) */
|
|
val = AXEN_MONITOR_PMETYPE | AXEN_MONITOR_PMEPOL | AXEN_MONITOR_RWMP;
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE, 1, AXEN_MONITOR_MODE, &val);
|
|
axen_cmd(sc, AXEN_CMD_MAC_READ, 1, AXEN_MONITOR_MODE, &val);
|
|
DPRINTF(("axen: Monitor mode = 0x%02x\n", val));
|
|
|
|
/* set medium type */
|
|
ctl = AXEN_MEDIUM_GIGA | AXEN_MEDIUM_FDX | AXEN_MEDIUM_EN_125MHZ |
|
|
AXEN_MEDIUM_RXFLOW_CTRL_EN | AXEN_MEDIUM_TXFLOW_CTRL_EN |
|
|
AXEN_MEDIUM_RECV_EN;
|
|
wval = htole16(ctl);
|
|
DPRINTF(("axen: set to medium mode: 0x%04x\n", ctl));
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE2, 2, AXEN_MEDIUM_STATUS, &wval);
|
|
usbd_delay_ms(un->un_udev, 100);
|
|
|
|
axen_cmd(sc, AXEN_CMD_MAC_READ2, 2, AXEN_MEDIUM_STATUS, &wval);
|
|
DPRINTF(("axen: current medium mode: 0x%04x\n", le16toh(wval)));
|
|
|
|
usbnet_unlock_mii(un);
|
|
|
|
#if 0 /* XXX: TBD.... */
|
|
#define GMII_LED_ACTIVE 0x1a
|
|
#define GMII_PHY_PAGE_SEL 0x1e
|
|
#define GMII_PHY_PAGE_SEL 0x1f
|
|
#define GMII_PAGE_EXT 0x0007
|
|
usbnet_miibus_writereg(un->un_dev, un->un_phyno, GMII_PHY_PAGE_SEL,
|
|
GMII_PAGE_EXT);
|
|
usbnet_miibus_writereg(un->un_dev, un->un_phyno, GMII_PHY_PAGE,
|
|
0x002c);
|
|
#endif
|
|
|
|
#if 1 /* XXX: phy hack ? */
|
|
usbnet_miibus_writereg(un->un_dev, un->un_phyno, 0x1F, 0x0005);
|
|
usbnet_miibus_writereg(un->un_dev, un->un_phyno, 0x0C, 0x0000);
|
|
usbnet_miibus_readreg(un->un_dev, un->un_phyno, 0x0001, &wval);
|
|
usbnet_miibus_writereg(un->un_dev, un->un_phyno, 0x01, wval | 0x0080);
|
|
usbnet_miibus_writereg(un->un_dev, un->un_phyno, 0x1F, 0x0000);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
axen_setoe_locked(struct usbnet *un)
|
|
{
|
|
struct axen_softc * const sc = usbnet_softc(un);
|
|
struct ifnet * const ifp = usbnet_ifp(un);
|
|
uint64_t enabled = ifp->if_capenable;
|
|
uint8_t val;
|
|
|
|
usbnet_isowned_mii(un);
|
|
|
|
val = AXEN_RXCOE_OFF;
|
|
if (enabled & IFCAP_CSUM_IPv4_Rx)
|
|
val |= AXEN_RXCOE_IPv4;
|
|
if (enabled & IFCAP_CSUM_TCPv4_Rx)
|
|
val |= AXEN_RXCOE_TCPv4;
|
|
if (enabled & IFCAP_CSUM_UDPv4_Rx)
|
|
val |= AXEN_RXCOE_UDPv4;
|
|
if (enabled & IFCAP_CSUM_TCPv6_Rx)
|
|
val |= AXEN_RXCOE_TCPv6;
|
|
if (enabled & IFCAP_CSUM_UDPv6_Rx)
|
|
val |= AXEN_RXCOE_UDPv6;
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE, 1, AXEN_RX_COE, &val);
|
|
|
|
val = AXEN_TXCOE_OFF;
|
|
if (enabled & IFCAP_CSUM_IPv4_Tx)
|
|
val |= AXEN_TXCOE_IPv4;
|
|
if (enabled & IFCAP_CSUM_TCPv4_Tx)
|
|
val |= AXEN_TXCOE_TCPv4;
|
|
if (enabled & IFCAP_CSUM_UDPv4_Tx)
|
|
val |= AXEN_TXCOE_UDPv4;
|
|
if (enabled & IFCAP_CSUM_TCPv6_Tx)
|
|
val |= AXEN_TXCOE_TCPv6;
|
|
if (enabled & IFCAP_CSUM_UDPv6_Tx)
|
|
val |= AXEN_TXCOE_UDPv6;
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE, 1, AXEN_TX_COE, &val);
|
|
}
|
|
|
|
static void
|
|
axen_setoe(struct usbnet *un)
|
|
{
|
|
|
|
usbnet_lock_mii(un);
|
|
axen_setoe_locked(un);
|
|
usbnet_unlock_mii(un);
|
|
}
|
|
|
|
static int
|
|
axen_ioctl_cb(struct ifnet *ifp, u_long cmd, void *data)
|
|
{
|
|
struct usbnet * const un = ifp->if_softc;
|
|
|
|
switch (cmd) {
|
|
case SIOCSIFFLAGS:
|
|
case SIOCSETHERCAP:
|
|
case SIOCADDMULTI:
|
|
case SIOCDELMULTI:
|
|
axen_setiff(un);
|
|
break;
|
|
case SIOCSIFCAP:
|
|
axen_setoe(un);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
axen_match(device_t parent, cfdata_t match, void *aux)
|
|
{
|
|
struct usb_attach_arg *uaa = aux;
|
|
|
|
return axen_lookup(uaa->uaa_vendor, uaa->uaa_product) != NULL ?
|
|
UMATCH_VENDOR_PRODUCT : UMATCH_NONE;
|
|
}
|
|
|
|
static void
|
|
axen_attach(device_t parent, device_t self, void *aux)
|
|
{
|
|
struct axen_softc * const sc = device_private(self);
|
|
struct usbnet * const un = &sc->axen_un;
|
|
struct usb_attach_arg *uaa = aux;
|
|
struct usbd_device *dev = uaa->uaa_device;
|
|
usbd_status err;
|
|
usb_interface_descriptor_t *id;
|
|
usb_endpoint_descriptor_t *ed;
|
|
char *devinfop;
|
|
uint16_t axen_flags;
|
|
int i;
|
|
|
|
/* Switch to usbnet for device_private() */
|
|
self->dv_private = un;
|
|
|
|
aprint_naive("\n");
|
|
aprint_normal("\n");
|
|
devinfop = usbd_devinfo_alloc(dev, 0);
|
|
aprint_normal_dev(self, "%s\n", devinfop);
|
|
usbd_devinfo_free(devinfop);
|
|
|
|
un->un_dev = self;
|
|
un->un_udev = dev;
|
|
un->un_sc = sc;
|
|
un->un_stop_cb = axen_stop_cb;
|
|
un->un_ioctl_cb = axen_ioctl_cb;
|
|
un->un_read_reg_cb = axen_mii_read_reg;
|
|
un->un_write_reg_cb = axen_mii_write_reg;
|
|
un->un_statchg_cb = axen_mii_statchg;
|
|
un->un_tx_prepare_cb = axen_tx_prepare;
|
|
un->un_rx_loop_cb = axen_rxeof_loop;
|
|
un->un_init_cb = axen_init;
|
|
un->un_rx_xfer_flags = USBD_SHORT_XFER_OK;
|
|
un->un_tx_xfer_flags = USBD_FORCE_SHORT_XFER;
|
|
|
|
err = usbd_set_config_no(dev, AXEN_CONFIG_NO, 1);
|
|
if (err) {
|
|
aprint_error_dev(self, "failed to set configuration"
|
|
", err=%s\n", usbd_errstr(err));
|
|
return;
|
|
}
|
|
|
|
axen_flags = axen_lookup(uaa->uaa_vendor, uaa->uaa_product)->axen_flags;
|
|
|
|
err = usbd_device2interface_handle(dev, AXEN_IFACE_IDX, &un->un_iface);
|
|
if (err) {
|
|
aprint_error_dev(self, "getting interface handle failed\n");
|
|
return;
|
|
}
|
|
|
|
/* decide on what our bufsize will be */
|
|
switch (dev->ud_speed) {
|
|
case USB_SPEED_SUPER:
|
|
un->un_cdata.uncd_rx_bufsz = AXEN_BUFSZ_SS * 1024;
|
|
break;
|
|
case USB_SPEED_HIGH:
|
|
un->un_cdata.uncd_rx_bufsz = AXEN_BUFSZ_HS * 1024;
|
|
break;
|
|
default:
|
|
un->un_cdata.uncd_rx_bufsz = AXEN_BUFSZ_LS * 1024;
|
|
break;
|
|
}
|
|
|
|
un->un_cdata.uncd_tx_bufsz = IP_MAXPACKET +
|
|
ETHER_HDR_LEN + ETHER_CRC_LEN + ETHER_VLAN_ENCAP_LEN +
|
|
sizeof(struct axen_sframe_hdr);
|
|
|
|
/* Find endpoints. */
|
|
id = usbd_get_interface_descriptor(un->un_iface);
|
|
for (i = 0; i < id->bNumEndpoints; i++) {
|
|
ed = usbd_interface2endpoint_descriptor(un->un_iface, i);
|
|
if (!ed) {
|
|
aprint_error_dev(self, "couldn't get ep %d\n", i);
|
|
return;
|
|
}
|
|
if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
|
|
UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
|
|
un->un_ed[USBNET_ENDPT_RX] = ed->bEndpointAddress;
|
|
} else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
|
|
UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
|
|
un->un_ed[USBNET_ENDPT_TX] = ed->bEndpointAddress;
|
|
#if 0 /* not used yet */
|
|
} else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
|
|
UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) {
|
|
un->un_ed[USBNET_ENDPT_INTR] = ed->bEndpointAddress;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* Set these up now for axen_cmd(). */
|
|
usbnet_attach(un, "axendet", AXEN_RX_LIST_CNT, AXEN_TX_LIST_CNT);
|
|
|
|
un->un_phyno = AXEN_PHY_ID;
|
|
DPRINTF(("%s: phyno %d\n", device_xname(self), un->un_phyno));
|
|
|
|
/* Get station address. */
|
|
usbnet_lock_mii(un);
|
|
if (axen_get_eaddr(sc, &un->un_eaddr)) {
|
|
usbnet_unlock_mii(un);
|
|
printf("EEPROM checksum error\n");
|
|
return;
|
|
}
|
|
usbnet_unlock_mii(un);
|
|
|
|
axen_ax88179_init(sc);
|
|
|
|
/* An ASIX chip was detected. Inform the world. */
|
|
if (axen_flags & AX178A)
|
|
aprint_normal_dev(self, "AX88178a\n");
|
|
else if (axen_flags & AX179)
|
|
aprint_normal_dev(self, "AX88179\n");
|
|
else
|
|
aprint_normal_dev(self, "(unknown)\n");
|
|
aprint_normal_dev(self, "Ethernet address %s\n",
|
|
ether_sprintf(un->un_eaddr));
|
|
|
|
struct ethercom *ec = usbnet_ec(un);
|
|
ec->ec_capabilities = ETHERCAP_VLAN_MTU;
|
|
|
|
/* Adapter does not support TSOv6 (They call it LSOv2). */
|
|
struct ifnet *ifp = usbnet_ifp(un);
|
|
ifp->if_capabilities |= IFCAP_TSOv4 |
|
|
IFCAP_CSUM_IPv4_Rx | IFCAP_CSUM_IPv4_Tx |
|
|
IFCAP_CSUM_TCPv4_Rx | IFCAP_CSUM_TCPv4_Tx |
|
|
IFCAP_CSUM_UDPv4_Rx | IFCAP_CSUM_UDPv4_Tx |
|
|
IFCAP_CSUM_TCPv6_Rx | IFCAP_CSUM_TCPv6_Tx |
|
|
IFCAP_CSUM_UDPv6_Rx | IFCAP_CSUM_UDPv6_Tx;
|
|
|
|
usbnet_attach_ifp(un, true, IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST,
|
|
0, 0);
|
|
}
|
|
|
|
static int
|
|
axen_csum_flags_rx(struct ifnet *ifp, uint32_t pkt_hdr)
|
|
{
|
|
int enabled_flags = ifp->if_csum_flags_rx;
|
|
int csum_flags = 0;
|
|
int l3_type, l4_type;
|
|
|
|
if (enabled_flags == 0)
|
|
return 0;
|
|
|
|
l3_type = (pkt_hdr & AXEN_RXHDR_L3_TYPE_MASK) >>
|
|
AXEN_RXHDR_L3_TYPE_OFFSET;
|
|
|
|
if (l3_type == AXEN_RXHDR_L3_TYPE_IPV4)
|
|
csum_flags |= M_CSUM_IPv4;
|
|
|
|
l4_type = (pkt_hdr & AXEN_RXHDR_L4_TYPE_MASK) >>
|
|
AXEN_RXHDR_L4_TYPE_OFFSET;
|
|
|
|
switch (l4_type) {
|
|
case AXEN_RXHDR_L4_TYPE_TCP:
|
|
if (l3_type == AXEN_RXHDR_L3_TYPE_IPV4)
|
|
csum_flags |= M_CSUM_TCPv4;
|
|
else
|
|
csum_flags |= M_CSUM_TCPv6;
|
|
break;
|
|
case AXEN_RXHDR_L4_TYPE_UDP:
|
|
if (l3_type == AXEN_RXHDR_L3_TYPE_IPV4)
|
|
csum_flags |= M_CSUM_UDPv4;
|
|
else
|
|
csum_flags |= M_CSUM_UDPv6;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
csum_flags &= enabled_flags;
|
|
if ((csum_flags & M_CSUM_IPv4) && (pkt_hdr & AXEN_RXHDR_L3CSUM_ERR))
|
|
csum_flags |= M_CSUM_IPv4_BAD;
|
|
if ((csum_flags & ~M_CSUM_IPv4) && (pkt_hdr & AXEN_RXHDR_L4CSUM_ERR))
|
|
csum_flags |= M_CSUM_TCP_UDP_BAD;
|
|
|
|
return csum_flags;
|
|
}
|
|
|
|
static void
|
|
axen_rxeof_loop(struct usbnet *un, struct usbd_xfer *xfer,
|
|
struct usbnet_chain *c, uint32_t total_len)
|
|
{
|
|
struct ifnet *ifp = usbnet_ifp(un);
|
|
uint8_t *buf = c->unc_buf;
|
|
uint32_t rx_hdr, pkt_hdr;
|
|
uint32_t *hdr_p;
|
|
uint16_t hdr_offset, pkt_count;
|
|
size_t pkt_len;
|
|
size_t temp;
|
|
|
|
usbnet_isowned_rx(un);
|
|
|
|
if (total_len < sizeof(pkt_hdr)) {
|
|
aprint_error_dev(un->un_dev, "rxeof: too short transfer\n");
|
|
ifp->if_ierrors++;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* buffer map
|
|
* [packet #0]...[packet #n][pkt hdr#0]..[pkt hdr#n][recv_hdr]
|
|
* each packet has 0xeeee as psuedo header..
|
|
*/
|
|
hdr_p = (uint32_t *)(buf + total_len - sizeof(uint32_t));
|
|
rx_hdr = le32toh(*hdr_p);
|
|
hdr_offset = (uint16_t)(rx_hdr >> 16);
|
|
pkt_count = (uint16_t)(rx_hdr & 0xffff);
|
|
|
|
/* sanity check */
|
|
if (hdr_offset > total_len) {
|
|
aprint_error_dev(un->un_dev,
|
|
"rxeof: invalid hdr offset (%u > %u)\n",
|
|
hdr_offset, total_len);
|
|
ifp->if_ierrors++;
|
|
usbd_delay_ms(un->un_udev, 100);
|
|
return;
|
|
}
|
|
|
|
/* point first packet header */
|
|
hdr_p = (uint32_t *)(buf + hdr_offset);
|
|
|
|
/*
|
|
* ax88179 will pack multiple ip packet to a USB transaction.
|
|
* process all of packets in the buffer
|
|
*/
|
|
|
|
#if 1 /* XXX: paranoiac check. need to remove later */
|
|
#define AXEN_MAX_PACKED_PACKET 200
|
|
if (pkt_count > AXEN_MAX_PACKED_PACKET) {
|
|
DPRINTF(("%s: Too many packets (%d) in a transaction, discard.\n",
|
|
device_xname(un->un_dev), pkt_count));
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (pkt_count)
|
|
rnd_add_uint32(&un->un_rndsrc, pkt_count);
|
|
|
|
do {
|
|
if ((buf[0] != 0xee) || (buf[1] != 0xee)) {
|
|
aprint_error_dev(un->un_dev,
|
|
"invalid buffer(pkt#%d), continue\n", pkt_count);
|
|
ifp->if_ierrors += pkt_count;
|
|
return;
|
|
}
|
|
|
|
pkt_hdr = le32toh(*hdr_p);
|
|
pkt_len = (pkt_hdr >> 16) & 0x1fff;
|
|
DPRINTFN(10,
|
|
("%s: rxeof: packet#%d, pkt_hdr 0x%08x, pkt_len %zu\n",
|
|
device_xname(un->un_dev), pkt_count, pkt_hdr, pkt_len));
|
|
|
|
if (pkt_hdr & (AXEN_RXHDR_CRC_ERR | AXEN_RXHDR_DROP_ERR)) {
|
|
ifp->if_ierrors++;
|
|
/* move to next pkt header */
|
|
DPRINTF(("%s: %s err (pkt#%d)\n",
|
|
device_xname(un->un_dev),
|
|
(pkt_hdr & AXEN_RXHDR_CRC_ERR) ? "crc" : "drop",
|
|
pkt_count));
|
|
goto nextpkt;
|
|
}
|
|
|
|
usbnet_enqueue(un, buf + 2, pkt_len - 6,
|
|
axen_csum_flags_rx(ifp, pkt_hdr), 0, 0);
|
|
|
|
nextpkt:
|
|
/*
|
|
* prepare next packet
|
|
* as each packet will be aligned 8byte boundary,
|
|
* need to fix up the start point of the buffer.
|
|
*/
|
|
temp = ((pkt_len + 7) & 0xfff8);
|
|
buf = buf + temp;
|
|
hdr_p++;
|
|
pkt_count--;
|
|
} while (pkt_count > 0);
|
|
}
|
|
|
|
static unsigned
|
|
axen_tx_prepare(struct usbnet *un, struct mbuf *m, struct usbnet_chain *c)
|
|
{
|
|
struct axen_sframe_hdr hdr;
|
|
u_int length, boundary;
|
|
|
|
usbnet_isowned_tx(un);
|
|
|
|
/* XXX Is this needed? wMaxPacketSize? */
|
|
switch (un->un_udev->ud_speed) {
|
|
case USB_SPEED_SUPER:
|
|
boundary = 4096;
|
|
break;
|
|
case USB_SPEED_HIGH:
|
|
boundary = 512;
|
|
break;
|
|
default:
|
|
boundary = 64;
|
|
break;
|
|
}
|
|
|
|
length = m->m_pkthdr.len + sizeof(hdr);
|
|
KASSERT(length <= un->un_cdata.uncd_tx_bufsz);
|
|
|
|
hdr.plen = htole32(m->m_pkthdr.len);
|
|
|
|
hdr.gso = (m->m_pkthdr.csum_flags & M_CSUM_TSOv4) ?
|
|
m->m_pkthdr.segsz : 0;
|
|
if ((length % boundary) == 0) {
|
|
DPRINTF(("%s: boundary hit\n", device_xname(un->un_dev)));
|
|
hdr.gso |= 0x80008000; /* XXX enable padding */
|
|
}
|
|
hdr.gso = htole32(hdr.gso);
|
|
|
|
memcpy(c->unc_buf, &hdr, sizeof(hdr));
|
|
m_copydata(m, 0, m->m_pkthdr.len, c->unc_buf + sizeof(hdr));
|
|
|
|
return length;
|
|
}
|
|
|
|
static int
|
|
axen_init_locked(struct ifnet *ifp)
|
|
{
|
|
struct usbnet * const un = ifp->if_softc;
|
|
struct axen_softc * const sc = usbnet_softc(un);
|
|
uint16_t rxmode;
|
|
uint16_t wval;
|
|
uint8_t bval;
|
|
|
|
usbnet_isowned(un);
|
|
|
|
if (un->un_dying)
|
|
return EIO;
|
|
|
|
/* Cancel pending I/O */
|
|
usbnet_stop(un, ifp, 1);
|
|
|
|
/* Reset the ethernet interface. */
|
|
axen_reset(sc);
|
|
|
|
usbnet_lock_mii_un_locked(un);
|
|
|
|
/* XXX: ? */
|
|
bval = 0x01;
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE, 1, AXEN_UNK_28, &bval);
|
|
|
|
/* Configure offloading engine. */
|
|
axen_setoe_locked(un);
|
|
|
|
/* Program promiscuous mode and multicast filters. */
|
|
axen_setiff_locked(un);
|
|
|
|
/* Enable receiver, set RX mode */
|
|
axen_cmd(sc, AXEN_CMD_MAC_READ2, 2, AXEN_MAC_RXCTL, &wval);
|
|
rxmode = le16toh(wval);
|
|
rxmode |= AXEN_RXCTL_START;
|
|
wval = htole16(rxmode);
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE2, 2, AXEN_MAC_RXCTL, &wval);
|
|
|
|
usbnet_unlock_mii_un_locked(un);
|
|
|
|
return usbnet_init_rx_tx(un, 0, USBD_FORCE_SHORT_XFER);
|
|
}
|
|
|
|
static int
|
|
axen_init(struct ifnet *ifp)
|
|
{
|
|
struct usbnet * const un = ifp->if_softc;
|
|
|
|
usbnet_lock(un);
|
|
int ret = axen_init_locked(ifp);
|
|
usbnet_unlock(un);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
axen_stop_cb(struct ifnet *ifp, int disable)
|
|
{
|
|
struct usbnet * const un = ifp->if_softc;
|
|
struct axen_softc * const sc = usbnet_softc(un);
|
|
uint16_t rxmode, wval;
|
|
|
|
axen_reset(sc);
|
|
|
|
/* Disable receiver, set RX mode */
|
|
usbnet_lock_mii_un_locked(un);
|
|
axen_cmd(sc, AXEN_CMD_MAC_READ2, 2, AXEN_MAC_RXCTL, &wval);
|
|
rxmode = le16toh(wval);
|
|
rxmode &= ~AXEN_RXCTL_START;
|
|
wval = htole16(rxmode);
|
|
axen_cmd(sc, AXEN_CMD_MAC_WRITE2, 2, AXEN_MAC_RXCTL, &wval);
|
|
usbnet_unlock_mii_un_locked(un);
|
|
}
|
|
|
|
MODULE(MODULE_CLASS_DRIVER, if_axen, "usbnet");
|
|
|
|
#ifdef _MODULE
|
|
#include "ioconf.c"
|
|
#endif
|
|
|
|
static int
|
|
if_axen_modcmd(modcmd_t cmd, void *aux)
|
|
{
|
|
int error = 0;
|
|
|
|
switch (cmd) {
|
|
case MODULE_CMD_INIT:
|
|
#ifdef _MODULE
|
|
error = config_init_component(cfdriver_ioconf_axen,
|
|
cfattach_ioconf_axen, cfdata_ioconf_axen);
|
|
#endif
|
|
return error;
|
|
case MODULE_CMD_FINI:
|
|
#ifdef _MODULE
|
|
error = config_fini_component(cfdriver_ioconf_axen,
|
|
cfattach_ioconf_axen, cfdata_ioconf_axen);
|
|
#endif
|
|
return error;
|
|
default:
|
|
return ENOTTY;
|
|
}
|
|
}
|