[BACK]Return to if_mue.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / sys / dev / usb

File: [cvs.NetBSD.org] / src / sys / dev / usb / if_mue.c (download)

Revision 1.76, Thu Mar 3 05:54:21 2022 UTC (2 years, 1 month ago) by riastradh
Branch: MAIN
Changes since 1.75: +7 -3 lines

usbnet drivers: Avoid undefined behaviour if read reg fails.

Some callers don't check the error code, e.g. ~all the mii phy
drivers using PHY_READ.  Just return zero if the device is gone or
the xfer fails for any other reason.

/*	$NetBSD: if_mue.c,v 1.76 2022/03/03 05:54:21 riastradh Exp $	*/
/*	$OpenBSD: if_mue.c,v 1.3 2018/08/04 16:42:46 jsg Exp $	*/

/*
 * Copyright (c) 2018 Kevin Lo <kevlo@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.
 */

/* Driver for Microchip LAN7500/LAN7800 chipsets. */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_mue.c,v 1.76 2022/03/03 05:54:21 riastradh Exp $");

#ifdef _KERNEL_OPT
#include "opt_usb.h"
#include "opt_inet.h"
#endif

#include <sys/param.h>

#include <dev/usb/usbnet.h>

#include <dev/usb/if_muereg.h>
#include <dev/usb/if_muevar.h>

#define MUE_PRINTF(un, fmt, args...)					\
	device_printf((un)->un_dev, "%s: " fmt, __func__, ##args);

#ifdef USB_DEBUG
int muedebug = 0;
#define DPRINTF(un, fmt, args...)					\
	do {								\
		if (muedebug)						\
			MUE_PRINTF(un, fmt, ##args);			\
	} while (0 /* CONSTCOND */)
#else
#define DPRINTF(un, fmt, args...)	__nothing
#endif

/*
 * Various supported device vendors/products.
 */
struct mue_type {
	struct usb_devno	mue_dev;
	uint16_t		mue_flags;
#define LAN7500		0x0001	/* LAN7500 */
#define LAN7800		0x0002	/* LAN7800 */
#define LAN7801		0x0004	/* LAN7801 */
#define LAN7850		0x0008	/* LAN7850 */
};

static const struct mue_type mue_devs[] = {
	{ { USB_VENDOR_SMSC, USB_PRODUCT_SMSC_LAN7500 }, LAN7500 },
	{ { USB_VENDOR_SMSC, USB_PRODUCT_SMSC_LAN7505 }, LAN7500 },
	{ { USB_VENDOR_SMSC, USB_PRODUCT_SMSC_LAN7800 }, LAN7800 },
	{ { USB_VENDOR_SMSC, USB_PRODUCT_SMSC_LAN7801 }, LAN7801 },
	{ { USB_VENDOR_SMSC, USB_PRODUCT_SMSC_LAN7850 }, LAN7850 }
};

#define MUE_LOOKUP(uaa)	((const struct mue_type *)usb_lookup(mue_devs, \
    uaa->uaa_vendor, uaa->uaa_product))

#define MUE_ENADDR_LO(enaddr) \
    ((enaddr[3] << 24) | (enaddr[2] << 16) | (enaddr[1] << 8) | enaddr[0])
#define MUE_ENADDR_HI(enaddr) \
    ((enaddr[5] << 8) | enaddr[4])

static int	mue_match(device_t, cfdata_t, void *);
static void	mue_attach(device_t, device_t, void *);

static uint32_t	mue_csr_read(struct usbnet *, uint32_t);
static int	mue_csr_write(struct usbnet *, uint32_t, uint32_t);
static int	mue_wait_for_bits(struct usbnet *, uint32_t, uint32_t,
		    uint32_t, uint32_t);
static uint8_t	mue_eeprom_getbyte(struct usbnet *, int, uint8_t *);
static bool	mue_eeprom_present(struct usbnet *);
static void	mue_dataport_write(struct usbnet *, uint32_t, uint32_t,
		    uint32_t, uint32_t *);
static void	mue_init_ltm(struct usbnet *);
static int	mue_chip_init(struct usbnet *);
static void	mue_set_macaddr(struct usbnet *);
static int	mue_get_macaddr(struct usbnet *, prop_dictionary_t);
static int	mue_prepare_tso(struct usbnet *, struct mbuf *);
static void	mue_uno_mcast(struct ifnet *);
static void	mue_sethwcsum_locked(struct usbnet *);
static void	mue_setmtu_locked(struct usbnet *);
static void	mue_reset(struct usbnet *);

static void	mue_uno_stop(struct ifnet *, int);
static int	mue_uno_ioctl(struct ifnet *, u_long, void *);
static int	mue_uno_mii_read_reg(struct usbnet *, int, int, uint16_t *);
static int	mue_uno_mii_write_reg(struct usbnet *, int, int, uint16_t);
static void	mue_uno_mii_statchg(struct ifnet *);
static void	mue_uno_rx_loop(struct usbnet *, struct usbnet_chain *,
				uint32_t);
static unsigned	mue_uno_tx_prepare(struct usbnet *, struct mbuf *,
				   struct usbnet_chain *);
static int	mue_uno_init(struct ifnet *);

static const struct usbnet_ops mue_ops = {
	.uno_stop = mue_uno_stop,
	.uno_ioctl = mue_uno_ioctl,
	.uno_mcast = mue_uno_mcast,
	.uno_read_reg = mue_uno_mii_read_reg,
	.uno_write_reg = mue_uno_mii_write_reg,
	.uno_statchg = mue_uno_mii_statchg,
	.uno_tx_prepare = mue_uno_tx_prepare,
	.uno_rx_loop = mue_uno_rx_loop,
	.uno_init = mue_uno_init,
};

#define MUE_SETBIT(un, reg, x)	\
	mue_csr_write(un, reg, mue_csr_read(un, reg) | (x))

#define MUE_CLRBIT(un, reg, x)	\
	mue_csr_write(un, reg, mue_csr_read(un, reg) & ~(x))

#define MUE_WAIT_SET(un, reg, set, fail)	\
	mue_wait_for_bits(un, reg, set, ~0, fail)

#define MUE_WAIT_CLR(un, reg, clear, fail)	\
	mue_wait_for_bits(un, reg, 0, clear, fail)

#define ETHER_IS_VALID(addr) \
	(!ETHER_IS_MULTICAST(addr) && !ETHER_IS_ZERO(addr))

#define ETHER_IS_ZERO(addr) \
	(!(addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]))

CFATTACH_DECL_NEW(mue, sizeof(struct usbnet), mue_match, mue_attach,
    usbnet_detach, usbnet_activate);

static uint32_t
mue_csr_read(struct usbnet *un, uint32_t reg)
{
	usb_device_request_t req;
	usbd_status err;
	uDWord val;

	if (usbnet_isdying(un))
		return 0;

	USETDW(val, 0);
	req.bmRequestType = UT_READ_VENDOR_DEVICE;
	req.bRequest = MUE_UR_READREG;
	USETW(req.wValue, 0);
	USETW(req.wIndex, reg);
	USETW(req.wLength, 4);

	err = usbd_do_request(un->un_udev, &req, &val);
	if (err) {
		MUE_PRINTF(un, "reg = %#x: %s\n", reg, usbd_errstr(err));
		return 0;
	}

	return UGETDW(val);
}

static int
mue_csr_write(struct usbnet *un, uint32_t reg, uint32_t aval)
{
	usb_device_request_t req;
	usbd_status err;
	uDWord val;

	if (usbnet_isdying(un))
		return 0;

	USETDW(val, aval);
	req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
	req.bRequest = MUE_UR_WRITEREG;
	USETW(req.wValue, 0);
	USETW(req.wIndex, reg);
	USETW(req.wLength, 4);

	err = usbd_do_request(un->un_udev, &req, &val);
	if (err) {
		MUE_PRINTF(un, "reg = %#x: %s\n", reg, usbd_errstr(err));
		return -1;
	}

	return 0;
}

static int
mue_wait_for_bits(struct usbnet *un, uint32_t reg,
    uint32_t set, uint32_t clear, uint32_t fail)
{
	uint32_t val;
	int ntries;

	for (ntries = 0; ntries < 1000; ntries++) {
		if (usbnet_isdying(un))
			return 1;
		val = mue_csr_read(un, reg);
		if ((val & set) || !(val & clear))
			return 0;
		if (val & fail)
			return 1;
		usbd_delay_ms(un->un_udev, 1);
	}

	return 1;
}

static int
mue_uno_mii_read_reg(struct usbnet *un, int phy, int reg, uint16_t *val)
{
	uint32_t data;

	if (un->un_phyno != phy) {
		*val = 0;
		return EINVAL;
	}

	if (MUE_WAIT_CLR(un, MUE_MII_ACCESS, MUE_MII_ACCESS_BUSY, 0)) {
		MUE_PRINTF(un, "not ready\n");
		*val = 0;
		return EBUSY;
	}

	mue_csr_write(un, MUE_MII_ACCESS, MUE_MII_ACCESS_READ |
	    MUE_MII_ACCESS_BUSY | MUE_MII_ACCESS_REGADDR(reg) |
	    MUE_MII_ACCESS_PHYADDR(phy));

	if (MUE_WAIT_CLR(un, MUE_MII_ACCESS, MUE_MII_ACCESS_BUSY, 0)) {
		MUE_PRINTF(un, "timed out\n");
		*val = 0;
		return ETIMEDOUT;
	}

	data = mue_csr_read(un, MUE_MII_DATA);
	*val = data & 0xffff;

	return 0;
}

static int
mue_uno_mii_write_reg(struct usbnet *un, int phy, int reg, uint16_t val)
{

	if (un->un_phyno != phy)
		return EINVAL;

	if (MUE_WAIT_CLR(un, MUE_MII_ACCESS, MUE_MII_ACCESS_BUSY, 0)) {
		MUE_PRINTF(un, "not ready\n");
		return EBUSY;
	}

	mue_csr_write(un, MUE_MII_DATA, val);
	mue_csr_write(un, MUE_MII_ACCESS, MUE_MII_ACCESS_WRITE |
	    MUE_MII_ACCESS_BUSY | MUE_MII_ACCESS_REGADDR(reg) |
	    MUE_MII_ACCESS_PHYADDR(phy));

	if (MUE_WAIT_CLR(un, MUE_MII_ACCESS, MUE_MII_ACCESS_BUSY, 0)) {
		MUE_PRINTF(un, "timed out\n");
		return ETIMEDOUT;
	}

	return 0;
}

static void
mue_uno_mii_statchg(struct ifnet *ifp)
{
	struct usbnet * const un = ifp->if_softc;
	struct mii_data * const mii = usbnet_mii(un);
	uint32_t flow, threshold;

	if (usbnet_isdying(un))
		return;

	if ((mii->mii_media_status & (IFM_ACTIVE | IFM_AVALID)) ==
	    (IFM_ACTIVE | IFM_AVALID)) {
		switch (IFM_SUBTYPE(mii->mii_media_active)) {
		case IFM_10_T:
		case IFM_100_TX:
		case IFM_1000_T:
			usbnet_set_link(un, true);
			break;
		default:
			break;
		}
	}

	/* Lost link, do nothing. */
	if (!usbnet_havelink(un)) {
		DPRINTF(un, "mii_media_status = %#x\n", mii->mii_media_status);
		return;
	}

	if (!(un->un_flags & LAN7500)) {
		if (un->un_udev->ud_speed == USB_SPEED_SUPER) {
			if (IFM_SUBTYPE(mii->mii_media_active) == IFM_1000_T) {
				/* Disable U2 and enable U1. */
				MUE_CLRBIT(un, MUE_USB_CFG1,
				    MUE_USB_CFG1_DEV_U2_INIT_EN);
				MUE_SETBIT(un, MUE_USB_CFG1,
				    MUE_USB_CFG1_DEV_U1_INIT_EN);
			} else {
				/* Enable U1 and U2. */
				MUE_SETBIT(un, MUE_USB_CFG1,
				    MUE_USB_CFG1_DEV_U1_INIT_EN |
				    MUE_USB_CFG1_DEV_U2_INIT_EN);
			}
		}
	}

	flow = 0;
	/* XXX Linux does not check IFM_FDX flag for 7800. */
	if (IFM_OPTIONS(mii->mii_media_active) & IFM_FDX) {
		if (IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_TXPAUSE)
			flow |= MUE_FLOW_TX_FCEN | MUE_FLOW_PAUSE_TIME;
		if (IFM_OPTIONS(mii->mii_media_active) & IFM_ETH_RXPAUSE)
			flow |= MUE_FLOW_RX_FCEN;
	}

	/* XXX Magic numbers taken from Linux driver. */
	if (un->un_flags & LAN7500)
		threshold = 0x820;
	else
		switch (un->un_udev->ud_speed) {
		case USB_SPEED_SUPER:
			threshold = 0x817;
			break;
		case USB_SPEED_HIGH:
			threshold = 0x211;
			break;
		default:
			threshold = 0;
			break;
		}

	/* Threshold value should be set before enabling flow. */
	mue_csr_write(un, (un->un_flags & LAN7500) ?
	    MUE_7500_FCT_FLOW : MUE_7800_FCT_FLOW, threshold);
	mue_csr_write(un, MUE_FLOW, flow);

	DPRINTF(un, "done\n");
}

static uint8_t
mue_eeprom_getbyte(struct usbnet *un, int off, uint8_t *dest)
{
	uint32_t val;

	if (MUE_WAIT_CLR(un, MUE_E2P_CMD, MUE_E2P_CMD_BUSY, 0)) {
		MUE_PRINTF(un, "not ready\n");
		return ETIMEDOUT;
	}

	KASSERT((off & ~MUE_E2P_CMD_ADDR_MASK) == 0);
	mue_csr_write(un, MUE_E2P_CMD, MUE_E2P_CMD_READ | MUE_E2P_CMD_BUSY |
	    off);

	if (MUE_WAIT_CLR(un, MUE_E2P_CMD, MUE_E2P_CMD_BUSY,
	    MUE_E2P_CMD_TIMEOUT)) {
		MUE_PRINTF(un, "timed out\n");
		return ETIMEDOUT;
	}

	val = mue_csr_read(un, MUE_E2P_DATA);
	*dest = val & 0xff;

	return 0;
}

static int
mue_read_eeprom(struct usbnet *un, uint8_t *dest, int off, int cnt)
{
	uint32_t val = 0; /* XXX gcc */
	uint8_t byte;
	int i, err = 0;

	/* 
	 * EEPROM pins are muxed with the LED function on LAN7800 device.
	 */
	if (un->un_flags & LAN7800) {
		val = mue_csr_read(un, MUE_HW_CFG);
		mue_csr_write(un, MUE_HW_CFG,
		    val & ~(MUE_HW_CFG_LED0_EN | MUE_HW_CFG_LED1_EN));
	}

	for (i = 0; i < cnt; i++) {
		err = mue_eeprom_getbyte(un, off + i, &byte);
		if (err)
			break;
		*(dest + i) = byte;
	}

	if (un->un_flags & LAN7800)
		mue_csr_write(un, MUE_HW_CFG, val);

	return err ? 1 : 0;
}

static bool
mue_eeprom_present(struct usbnet *un)
{
	uint32_t val;
	uint8_t sig;
	int ret;

	if (un->un_flags & LAN7500) {
		val = mue_csr_read(un, MUE_E2P_CMD);
		return val & MUE_E2P_CMD_LOADED;
	} else {
		ret = mue_read_eeprom(un, &sig, MUE_E2P_IND_OFFSET, 1);
		return (ret == 0) && (sig == MUE_E2P_IND);
	}
}

static int
mue_read_otp_raw(struct usbnet *un, uint8_t *dest, int off, int cnt)
{
	uint32_t val;
	int i, err;

	val = mue_csr_read(un, MUE_OTP_PWR_DN);

	/* Checking if bit is set. */
	if (val & MUE_OTP_PWR_DN_PWRDN_N) {
		/* Clear it, then wait for it to be cleared. */
		mue_csr_write(un, MUE_OTP_PWR_DN, 0);
		err = MUE_WAIT_CLR(un, MUE_OTP_PWR_DN, MUE_OTP_PWR_DN_PWRDN_N,
		    0);
		if (err) {
			MUE_PRINTF(un, "not ready\n");
			return 1;
		}
	}

	/* Start reading the bytes, one at a time. */
	for (i = 0; i < cnt; i++) {
		mue_csr_write(un, MUE_OTP_ADDR1,
		    ((off + i) >> 8) & MUE_OTP_ADDR1_MASK);
		mue_csr_write(un, MUE_OTP_ADDR2,
		    ((off + i) & MUE_OTP_ADDR2_MASK));
		mue_csr_write(un, MUE_OTP_FUNC_CMD, MUE_OTP_FUNC_CMD_READ);
		mue_csr_write(un, MUE_OTP_CMD_GO, MUE_OTP_CMD_GO_GO);

		err = MUE_WAIT_CLR(un, MUE_OTP_STATUS, MUE_OTP_STATUS_BUSY, 0);
		if (err) {
			MUE_PRINTF(un, "timed out\n");
			return 1;
		}
		val = mue_csr_read(un, MUE_OTP_RD_DATA);
		*(dest + i) = (uint8_t)(val & 0xff);
	}

	return 0;
}

static int
mue_read_otp(struct usbnet *un, uint8_t *dest, int off, int cnt)
{
	uint8_t sig;
	int err;

	if (un->un_flags & LAN7500)
		return 1;

	err = mue_read_otp_raw(un, &sig, MUE_OTP_IND_OFFSET, 1);
	if (err)
		return 1;
	switch (sig) {
	case MUE_OTP_IND_1:
		break;
	case MUE_OTP_IND_2:
		off += 0x100;
		break;
	default:
		DPRINTF(un, "OTP not found\n");
		return 1;
	}
	err = mue_read_otp_raw(un, dest, off, cnt);
	return err;
}

static void
mue_dataport_write(struct usbnet *un, uint32_t sel, uint32_t addr,
    uint32_t cnt, uint32_t *data)
{
	uint32_t i;

	if (MUE_WAIT_SET(un, MUE_DP_SEL, MUE_DP_SEL_DPRDY, 0)) {
		MUE_PRINTF(un, "not ready\n");
		return;
	}

	mue_csr_write(un, MUE_DP_SEL,
	    (mue_csr_read(un, MUE_DP_SEL) & ~MUE_DP_SEL_RSEL_MASK) | sel);

	for (i = 0; i < cnt; i++) {
		mue_csr_write(un, MUE_DP_ADDR, addr + i);
		mue_csr_write(un, MUE_DP_DATA, data[i]);
		mue_csr_write(un, MUE_DP_CMD, MUE_DP_CMD_WRITE);
		if (MUE_WAIT_SET(un, MUE_DP_SEL, MUE_DP_SEL_DPRDY, 0)) {
			MUE_PRINTF(un, "timed out\n");
			return;
		}
	}
}

static void
mue_init_ltm(struct usbnet *un)
{
	uint32_t idx[MUE_NUM_LTM_INDEX] = { 0, 0, 0, 0, 0, 0 };
	uint8_t temp[2];
	size_t i;

	if (mue_csr_read(un, MUE_USB_CFG1) & MUE_USB_CFG1_LTM_ENABLE) {
		if (mue_eeprom_present(un) &&
		    (mue_read_eeprom(un, temp, MUE_E2P_LTM_OFFSET, 2) == 0)) {
			if (temp[0] != sizeof(idx)) {
				DPRINTF(un, "EEPROM: unexpected size\n");
				goto done;
			}
			if (mue_read_eeprom(un, (uint8_t *)idx, temp[1] << 1,
				sizeof(idx))) {
				DPRINTF(un, "EEPROM: failed to read\n");
				goto done;
			}
			DPRINTF(un, "success\n");
		} else if (mue_read_otp(un, temp, MUE_E2P_LTM_OFFSET, 2) == 0) {
			if (temp[0] != sizeof(idx)) {
				DPRINTF(un, "OTP: unexpected size\n");
				goto done;
			}
			if (mue_read_otp(un, (uint8_t *)idx, temp[1] << 1,
				sizeof(idx))) {
				DPRINTF(un, "OTP: failed to read\n");
				goto done;
			}
			DPRINTF(un, "success\n");
		} else
			DPRINTF(un, "nothing to do\n");
	} else
		DPRINTF(un, "nothing to do\n");
done:
	for (i = 0; i < __arraycount(idx); i++)
		mue_csr_write(un, MUE_LTM_INDEX(i), idx[i]);
}

static int
mue_chip_init(struct usbnet *un)
{
	uint32_t val;

	if ((un->un_flags & LAN7500) &&
	    MUE_WAIT_SET(un, MUE_PMT_CTL, MUE_PMT_CTL_READY, 0)) {
		MUE_PRINTF(un, "not ready\n");
			return ETIMEDOUT;
	}

	MUE_SETBIT(un, MUE_HW_CFG, MUE_HW_CFG_LRST);
	if (MUE_WAIT_CLR(un, MUE_HW_CFG, MUE_HW_CFG_LRST, 0)) {
		MUE_PRINTF(un, "timed out\n");
		return ETIMEDOUT;
	}

	/* Respond to the IN token with a NAK. */
	if (un->un_flags & LAN7500)
		MUE_SETBIT(un, MUE_HW_CFG, MUE_HW_CFG_BIR);
	else
		MUE_SETBIT(un, MUE_USB_CFG0, MUE_USB_CFG0_BIR);

	if (un->un_flags & LAN7500) {
		if (un->un_udev->ud_speed == USB_SPEED_HIGH)
			val = MUE_7500_HS_RX_BUFSIZE /
			    MUE_HS_USB_PKT_SIZE;
		else
			val = MUE_7500_FS_RX_BUFSIZE /
			    MUE_FS_USB_PKT_SIZE;
		mue_csr_write(un, MUE_7500_BURST_CAP, val);
		mue_csr_write(un, MUE_7500_BULKIN_DELAY,
		    MUE_7500_DEFAULT_BULKIN_DELAY);

		MUE_SETBIT(un, MUE_HW_CFG, MUE_HW_CFG_BCE | MUE_HW_CFG_MEF);

		/* Set FIFO sizes. */
		val = (MUE_7500_MAX_RX_FIFO_SIZE - 512) / 512;
		mue_csr_write(un, MUE_7500_FCT_RX_FIFO_END, val);
		val = (MUE_7500_MAX_TX_FIFO_SIZE - 512) / 512;
		mue_csr_write(un, MUE_7500_FCT_TX_FIFO_END, val);
	} else {
		/* Init LTM. */
		mue_init_ltm(un);

		val = MUE_7800_RX_BUFSIZE;
		switch (un->un_udev->ud_speed) {
		case USB_SPEED_SUPER:
			val /= MUE_SS_USB_PKT_SIZE;
			break;
		case USB_SPEED_HIGH:
			val /= MUE_HS_USB_PKT_SIZE;
			break;
		default:
			val /= MUE_FS_USB_PKT_SIZE;
			break;
		}
		mue_csr_write(un, MUE_7800_BURST_CAP, val);
		mue_csr_write(un, MUE_7800_BULKIN_DELAY,
		    MUE_7800_DEFAULT_BULKIN_DELAY);

		MUE_SETBIT(un, MUE_HW_CFG, MUE_HW_CFG_MEF);
		MUE_SETBIT(un, MUE_USB_CFG0, MUE_USB_CFG0_BCE);

		/*
		 * Set FCL's RX and TX FIFO sizes: according to data sheet this
		 * is already the default value. But we initialize it to the
		 * same value anyways, as that's what the Linux driver does.
		 */
		val = (MUE_7800_MAX_RX_FIFO_SIZE - 512) / 512;
		mue_csr_write(un, MUE_7800_FCT_RX_FIFO_END, val);
		val = (MUE_7800_MAX_TX_FIFO_SIZE - 512) / 512;
		mue_csr_write(un, MUE_7800_FCT_TX_FIFO_END, val);
	}

	/* Enabling interrupts. */
	mue_csr_write(un, MUE_INT_STATUS, ~0);

	mue_csr_write(un, (un->un_flags & LAN7500) ?
	    MUE_7500_FCT_FLOW : MUE_7800_FCT_FLOW, 0);
	mue_csr_write(un, MUE_FLOW, 0);

	/* Reset PHY. */
	MUE_SETBIT(un, MUE_PMT_CTL, MUE_PMT_CTL_PHY_RST);
	if (MUE_WAIT_CLR(un, MUE_PMT_CTL, MUE_PMT_CTL_PHY_RST, 0)) {
		MUE_PRINTF(un, "PHY not ready\n");
		return ETIMEDOUT;
	}

	/* LAN7801 only has RGMII mode. */
	if (un->un_flags & LAN7801)
		MUE_CLRBIT(un, MUE_MAC_CR, MUE_MAC_CR_GMII_EN);

	if ((un->un_flags & (LAN7500 | LAN7800)) ||
	    !mue_eeprom_present(un)) {
		/* Allow MAC to detect speed and duplex from PHY. */
		MUE_SETBIT(un, MUE_MAC_CR, MUE_MAC_CR_AUTO_SPEED |
		    MUE_MAC_CR_AUTO_DUPLEX);
	}

	MUE_SETBIT(un, MUE_MAC_TX, MUE_MAC_TX_TXEN);
	MUE_SETBIT(un, (un->un_flags & LAN7500) ?
	    MUE_7500_FCT_TX_CTL : MUE_7800_FCT_TX_CTL, MUE_FCT_TX_CTL_EN);

	MUE_SETBIT(un, (un->un_flags & LAN7500) ?
	    MUE_7500_FCT_RX_CTL : MUE_7800_FCT_RX_CTL, MUE_FCT_RX_CTL_EN);

	/* Set default GPIO/LED settings only if no EEPROM is detected. */
	if ((un->un_flags & LAN7500) && !mue_eeprom_present(un)) {
		MUE_CLRBIT(un, MUE_LED_CFG, MUE_LED_CFG_LED10_FUN_SEL);
		MUE_SETBIT(un, MUE_LED_CFG,
		    MUE_LED_CFG_LEDGPIO_EN | MUE_LED_CFG_LED2_FUN_SEL);
	}

	/* XXX We assume two LEDs at least when EEPROM is missing. */
	if (un->un_flags & LAN7800 &&
	    !mue_eeprom_present(un))
		MUE_SETBIT(un, MUE_HW_CFG,
		    MUE_HW_CFG_LED0_EN | MUE_HW_CFG_LED1_EN);

	return 0;
}

static void
mue_set_macaddr(struct usbnet *un)
{
	struct ifnet * const ifp = usbnet_ifp(un);
	const uint8_t *enaddr = CLLADDR(ifp->if_sadl);
	uint32_t lo, hi;

	lo = MUE_ENADDR_LO(enaddr);
	hi = MUE_ENADDR_HI(enaddr);

	mue_csr_write(un, MUE_RX_ADDRL, lo);
	mue_csr_write(un, MUE_RX_ADDRH, hi);
}

static int
mue_get_macaddr(struct usbnet *un, prop_dictionary_t dict)
{
	prop_data_t eaprop;
	uint32_t low, high;

	if (!(un->un_flags & LAN7500)) {
		low  = mue_csr_read(un, MUE_RX_ADDRL);
		high = mue_csr_read(un, MUE_RX_ADDRH);
		un->un_eaddr[5] = (uint8_t)((high >> 8) & 0xff);
		un->un_eaddr[4] = (uint8_t)((high) & 0xff);
		un->un_eaddr[3] = (uint8_t)((low >> 24) & 0xff);
		un->un_eaddr[2] = (uint8_t)((low >> 16) & 0xff);
		un->un_eaddr[1] = (uint8_t)((low >> 8) & 0xff);
		un->un_eaddr[0] = (uint8_t)((low) & 0xff);
		if (ETHER_IS_VALID(un->un_eaddr))
			return 0;
		else
			DPRINTF(un, "registers: %s\n",
			    ether_sprintf(un->un_eaddr));
	}

	if (mue_eeprom_present(un) && !mue_read_eeprom(un, un->un_eaddr,
	    MUE_E2P_MAC_OFFSET, ETHER_ADDR_LEN)) {
		if (ETHER_IS_VALID(un->un_eaddr))
			return 0;
		else
			DPRINTF(un, "EEPROM: %s\n",
			    ether_sprintf(un->un_eaddr));
	}

	if (mue_read_otp(un, un->un_eaddr, MUE_OTP_MAC_OFFSET,
	    ETHER_ADDR_LEN) == 0) {
		if (ETHER_IS_VALID(un->un_eaddr))
			return 0;
		else
			DPRINTF(un, "OTP: %s\n",
			    ether_sprintf(un->un_eaddr));
	}

	/*
	 * Other MD methods. This should be tried only if other methods fail.
	 * Otherwise, MAC address for internal device can be assinged to
	 * external devices on Raspberry Pi, for example.
	 */
	eaprop = prop_dictionary_get(dict, "mac-address");
	if (eaprop != NULL) {
		KASSERT(prop_object_type(eaprop) == PROP_TYPE_DATA);
		KASSERT(prop_data_size(eaprop) == ETHER_ADDR_LEN);
		memcpy(un->un_eaddr, prop_data_value(eaprop),
		    ETHER_ADDR_LEN);
		if (ETHER_IS_VALID(un->un_eaddr))
			return 0;
		else
			DPRINTF(un, "prop_dictionary_get: %s\n",
			    ether_sprintf(un->un_eaddr));
	}

	return 1;
}


/* 
 * Probe for a Microchip chip.
 */
static int
mue_match(device_t parent, cfdata_t match, void *aux)
{
	struct usb_attach_arg *uaa = aux;

	return (MUE_LOOKUP(uaa) != NULL) ?  UMATCH_VENDOR_PRODUCT : UMATCH_NONE;
}

static void
mue_attach(device_t parent, device_t self, void *aux)
{
	USBNET_MII_DECL_DEFAULT(unm);
	struct usbnet * const un = device_private(self);
	prop_dictionary_t dict = device_properties(self);
	struct usb_attach_arg *uaa = aux;
	struct usbd_device *dev = uaa->uaa_device;
	usb_interface_descriptor_t *id;
	usb_endpoint_descriptor_t *ed;
	char *devinfop;
	usbd_status err;
	const char *descr;
	uint32_t id_rev;
	uint8_t i;
	unsigned rx_list_cnt, tx_list_cnt;
	unsigned rx_bufsz;

	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 = un;
	un->un_ops = &mue_ops;
	un->un_rx_xfer_flags = USBD_SHORT_XFER_OK;
	un->un_tx_xfer_flags = USBD_FORCE_SHORT_XFER;

#define MUE_CONFIG_NO	1
	err = usbd_set_config_no(dev, MUE_CONFIG_NO, 1);
	if (err) {
		aprint_error_dev(self, "failed to set configuration: %s\n",
		    usbd_errstr(err));
		return;
	}

#define MUE_IFACE_IDX	0
	err = usbd_device2interface_handle(dev, MUE_IFACE_IDX, &un->un_iface);
	if (err) {
		aprint_error_dev(self, "failed to get interface handle: %s\n",
		    usbd_errstr(err));
		return;
	}

	un->un_flags = MUE_LOOKUP(uaa)->mue_flags;

	/* Decide on what our bufsize will be. */
	if (un->un_flags & LAN7500) {
		rx_bufsz = (un->un_udev->ud_speed == USB_SPEED_HIGH) ?
		    MUE_7500_HS_RX_BUFSIZE : MUE_7500_FS_RX_BUFSIZE;
		rx_list_cnt = 1;
		tx_list_cnt = 1;
	} else {
		rx_bufsz = MUE_7800_RX_BUFSIZE;
		rx_list_cnt = MUE_RX_LIST_CNT;
		tx_list_cnt = MUE_TX_LIST_CNT;
	}

	un->un_rx_list_cnt = rx_list_cnt;
	un->un_tx_list_cnt = tx_list_cnt;
	un->un_rx_bufsz = rx_bufsz;
	un->un_tx_bufsz = MUE_TX_BUFSIZE;

	/* 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 == NULL) {
			aprint_error_dev(self, "failed to get ep %hhd\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;
		} 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;
		}
	}
	if (un->un_ed[USBNET_ENDPT_RX] == 0 ||
	    un->un_ed[USBNET_ENDPT_TX] == 0 ||
	    un->un_ed[USBNET_ENDPT_INTR] == 0) {
		aprint_error_dev(self, "failed to find endpoints\n");
		return;
	}

	/* Set these up now for mue_cmd().  */
	usbnet_attach(un, "muedet");

	un->un_phyno = 1;

	if (mue_chip_init(un)) {
		aprint_error_dev(self, "failed to initialize chip\n");
		return;
	}

	/* A Microchip chip was detected.  Inform the world. */
	id_rev = mue_csr_read(un, MUE_ID_REV);
	descr = (un->un_flags & LAN7500) ? "LAN7500" : "LAN7800";
	aprint_normal_dev(self, "%s id %#x rev %#x\n", descr,
		(unsigned)__SHIFTOUT(id_rev, MUE_ID_REV_ID),
		(unsigned)__SHIFTOUT(id_rev, MUE_ID_REV_REV));

	if (mue_get_macaddr(un, dict)) {
		aprint_error_dev(self, "failed to read MAC address\n");
		return;
	}

	struct ifnet *ifp = usbnet_ifp(un);
	ifp->if_capabilities = IFCAP_TSOv4 | IFCAP_TSOv6 |
	    IFCAP_CSUM_IPv4_Tx | IFCAP_CSUM_IPv4_Rx |
	    IFCAP_CSUM_TCPv4_Tx | IFCAP_CSUM_TCPv4_Rx |
	    IFCAP_CSUM_UDPv4_Tx | IFCAP_CSUM_UDPv4_Rx |
	    IFCAP_CSUM_TCPv6_Tx | IFCAP_CSUM_TCPv6_Rx |
	    IFCAP_CSUM_UDPv6_Tx | IFCAP_CSUM_UDPv6_Rx;

	struct ethercom *ec = usbnet_ec(un);
	ec->ec_capabilities = ETHERCAP_VLAN_MTU;
#if 0 /* XXX not yet */
	ec->ec_capabilities = ETHERCAP_VLAN_MTU | ETHERCAP_JUMBO_MTU;
#endif

	usbnet_attach_ifp(un, IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST,
	    0, &unm);
}

static unsigned
mue_uno_tx_prepare(struct usbnet *un, struct mbuf *m, struct usbnet_chain *c)
{
	struct ifnet * const ifp = usbnet_ifp(un);
	struct mue_txbuf_hdr hdr;
	uint32_t tx_cmd_a, tx_cmd_b;
	int csum, len, rv;
	bool tso, ipe, tpe;

	if ((unsigned)m->m_pkthdr.len > un->un_tx_bufsz - sizeof(hdr))
		return 0;

	csum = m->m_pkthdr.csum_flags;
	tso = csum & (M_CSUM_TSOv4 | M_CSUM_TSOv6);
	ipe = csum & M_CSUM_IPv4;
	tpe = csum & (M_CSUM_TCPv4 | M_CSUM_UDPv4 |
		      M_CSUM_TCPv6 | M_CSUM_UDPv6);

	len = m->m_pkthdr.len;
	if (__predict_false((!tso && len > (int)MUE_FRAME_LEN(ifp->if_mtu)) ||
			    ( tso && len > MUE_TSO_FRAME_LEN))) {
		MUE_PRINTF(un, "packet length %d\n too long", len);
		return 0;
	}

	KASSERT((len & ~MUE_TX_CMD_A_LEN_MASK) == 0);
	tx_cmd_a = len | MUE_TX_CMD_A_FCS;

	if (tso) {
		tx_cmd_a |= MUE_TX_CMD_A_LSO;
		if (__predict_true(m->m_pkthdr.segsz > MUE_TX_MSS_MIN))
			tx_cmd_b = m->m_pkthdr.segsz;
		else
			tx_cmd_b = MUE_TX_MSS_MIN;
		tx_cmd_b <<= MUE_TX_CMD_B_MSS_SHIFT;
		KASSERT((tx_cmd_b & ~MUE_TX_CMD_B_MSS_MASK) == 0);
		rv = mue_prepare_tso(un, m);
		if (__predict_false(rv))
			return 0;
	} else {
		if (ipe)
			tx_cmd_a |= MUE_TX_CMD_A_IPE;
		if (tpe)
			tx_cmd_a |= MUE_TX_CMD_A_TPE;
		tx_cmd_b = 0;
	}

	hdr.tx_cmd_a = htole32(tx_cmd_a);
	hdr.tx_cmd_b = htole32(tx_cmd_b);

	memcpy(c->unc_buf, &hdr, sizeof(hdr));
	m_copydata(m, 0, len, c->unc_buf + sizeof(hdr));

	return len + sizeof(hdr);
}

/*
 * L3 length field should be cleared.
 */
static int
mue_prepare_tso(struct usbnet *un, struct mbuf *m)
{
	struct ether_header *eh;
	struct ip *ip;
	struct ip6_hdr *ip6;
	uint16_t type, len = 0;
	int off;

	if (__predict_true(m->m_len >= (int)sizeof(*eh))) {
		eh = mtod(m, struct ether_header *);
		type = eh->ether_type;
	} else
		m_copydata(m, offsetof(struct ether_header, ether_type),
		    sizeof(type), &type);
	switch (type = htons(type)) {
	case ETHERTYPE_IP:
	case ETHERTYPE_IPV6:
		off = ETHER_HDR_LEN;
		break;
	case ETHERTYPE_VLAN:
		off = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
		break;
	default:
		return EINVAL;
	}

	if (m->m_pkthdr.csum_flags & M_CSUM_TSOv4) {
		if (__predict_true(m->m_len >= off + (int)sizeof(*ip))) {
			ip = (void *)(mtod(m, char *) + off);
			ip->ip_len = 0;
		} else
			m_copyback(m, off + offsetof(struct ip, ip_len),
			    sizeof(len), &len);
	} else {
		if (__predict_true(m->m_len >= off + (int)sizeof(*ip6))) {
			ip6 = (void *)(mtod(m, char *) + off);
			ip6->ip6_plen = 0;
		} else
			m_copyback(m, off + offsetof(struct ip6_hdr, ip6_plen),
			    sizeof(len), &len);
	}
	return 0;
}

static void
mue_uno_mcast(struct ifnet *ifp)
{
	struct usbnet *un = ifp->if_softc;
	struct ethercom *ec = usbnet_ec(un);
	const uint8_t *enaddr = CLLADDR(ifp->if_sadl);
	struct ether_multi *enm;
	struct ether_multistep step;
	uint32_t pfiltbl[MUE_NUM_ADDR_FILTX][2];
	uint32_t hashtbl[MUE_DP_SEL_VHF_HASH_LEN];
	uint32_t reg, rxfilt, h, hireg, loreg;
	size_t i;

	if (usbnet_isdying(un))
		return;

	/* Clear perfect filter and hash tables. */
	memset(pfiltbl, 0, sizeof(pfiltbl));
	memset(hashtbl, 0, sizeof(hashtbl));

	reg = (un->un_flags & LAN7500) ? MUE_7500_RFE_CTL : MUE_7800_RFE_CTL;
	rxfilt = mue_csr_read(un, reg);
	rxfilt &= ~(MUE_RFE_CTL_PERFECT | MUE_RFE_CTL_MULTICAST_HASH |
	    MUE_RFE_CTL_UNICAST | MUE_RFE_CTL_MULTICAST);

	/* Always accept broadcast frames. */
	rxfilt |= MUE_RFE_CTL_BROADCAST;

	ETHER_LOCK(ec);
	if (ifp->if_flags & IFF_PROMISC) {
		rxfilt |= MUE_RFE_CTL_UNICAST;
allmulti:	rxfilt |= MUE_RFE_CTL_MULTICAST;
		ec->ec_flags |= ETHER_F_ALLMULTI;
		if (ifp->if_flags & IFF_PROMISC)
			DPRINTF(un, "promisc\n");
		else
			DPRINTF(un, "allmulti\n");
	} else {
		/* Now program new ones. */
		pfiltbl[0][0] = MUE_ENADDR_HI(enaddr) | MUE_ADDR_FILTX_VALID;
		pfiltbl[0][1] = MUE_ENADDR_LO(enaddr);
		i = 1;
		ETHER_FIRST_MULTI(step, ec, enm);
		while (enm != NULL) {
			if (memcmp(enm->enm_addrlo, enm->enm_addrhi,
			    ETHER_ADDR_LEN)) {
				memset(pfiltbl, 0, sizeof(pfiltbl));
				memset(hashtbl, 0, sizeof(hashtbl));
				rxfilt &= ~MUE_RFE_CTL_MULTICAST_HASH;
				goto allmulti;
			}
			if (i < MUE_NUM_ADDR_FILTX) {
				/* Use perfect address table if possible. */
				pfiltbl[i][0] = MUE_ENADDR_HI(enm->enm_addrlo) |
				    MUE_ADDR_FILTX_VALID;
				pfiltbl[i][1] = MUE_ENADDR_LO(enm->enm_addrlo);
			} else {
				/* Otherwise, use hash table. */
				rxfilt |= MUE_RFE_CTL_MULTICAST_HASH;
				h = (ether_crc32_be(enm->enm_addrlo,
				    ETHER_ADDR_LEN) >> 23) & 0x1ff;
				hashtbl[h / 32] |= 1 << (h % 32);
			}
			i++;
			ETHER_NEXT_MULTI(step, enm);
		}
		ec->ec_flags &= ~ETHER_F_ALLMULTI;
		rxfilt |= MUE_RFE_CTL_PERFECT;
		if (rxfilt & MUE_RFE_CTL_MULTICAST_HASH)
			DPRINTF(un, "perfect filter and hash tables\n");
		else
			DPRINTF(un, "perfect filter\n");
	}
	ETHER_UNLOCK(ec);

	for (i = 0; i < MUE_NUM_ADDR_FILTX; i++) {
		hireg = (un->un_flags & LAN7500) ?
		    MUE_7500_ADDR_FILTX(i) : MUE_7800_ADDR_FILTX(i);
		loreg = hireg + 4;
		mue_csr_write(un, hireg, 0);
		mue_csr_write(un, loreg, pfiltbl[i][1]);
		mue_csr_write(un, hireg, pfiltbl[i][0]);
	}

	mue_dataport_write(un, MUE_DP_SEL_VHF, MUE_DP_SEL_VHF_VLAN_LEN,
	    MUE_DP_SEL_VHF_HASH_LEN, hashtbl);

	mue_csr_write(un, reg, rxfilt);
}

static void
mue_sethwcsum_locked(struct usbnet *un)
{
	struct ifnet * const ifp = usbnet_ifp(un);
	uint32_t reg, val;

	KASSERT(IFNET_LOCKED(ifp));

	reg = (un->un_flags & LAN7500) ? MUE_7500_RFE_CTL : MUE_7800_RFE_CTL;
	val = mue_csr_read(un, reg);

	if (ifp->if_capenable & IFCAP_CSUM_IPv4_Rx) {
		DPRINTF(un, "RX IPv4 hwcsum enabled\n");
		val |= MUE_RFE_CTL_IP_COE;
	} else {
		DPRINTF(un, "RX IPv4 hwcsum disabled\n");
		val &= ~MUE_RFE_CTL_IP_COE;
	}

	if (ifp->if_capenable &
	    (IFCAP_CSUM_TCPv4_Rx | IFCAP_CSUM_UDPv4_Rx |
	     IFCAP_CSUM_TCPv6_Rx | IFCAP_CSUM_UDPv6_Rx)) {
		DPRINTF(un, "RX L4 hwcsum enabled\n");
		val |= MUE_RFE_CTL_TCPUDP_COE;
	} else {
		DPRINTF(un, "RX L4 hwcsum disabled\n");
		val &= ~MUE_RFE_CTL_TCPUDP_COE;
	}

	val &= ~MUE_RFE_CTL_VLAN_FILTER;

	mue_csr_write(un, reg, val);
}

static void
mue_setmtu_locked(struct usbnet *un)
{
	struct ifnet * const ifp = usbnet_ifp(un);
	uint32_t val;

	KASSERT(IFNET_LOCKED(ifp));

	/* Set the maximum frame size. */
	MUE_CLRBIT(un, MUE_MAC_RX, MUE_MAC_RX_RXEN);
	val = mue_csr_read(un, MUE_MAC_RX);
	val &= ~MUE_MAC_RX_MAX_SIZE_MASK;
	val |= MUE_MAC_RX_MAX_LEN(MUE_FRAME_LEN(ifp->if_mtu));
	mue_csr_write(un, MUE_MAC_RX, val);
	MUE_SETBIT(un, MUE_MAC_RX, MUE_MAC_RX_RXEN);
}

static void
mue_uno_rx_loop(struct usbnet *un, struct usbnet_chain *c, uint32_t total_len)
{
	struct ifnet * const ifp = usbnet_ifp(un);
	struct mue_rxbuf_hdr *hdrp;
	uint32_t rx_cmd_a;
	uint16_t pktlen;
	int csum;
	uint8_t *buf = c->unc_buf;
	bool v6;

	KASSERTMSG(total_len <= un->un_rx_bufsz, "%u vs %u",
	    total_len, un->un_rx_bufsz);

	do {
		if (__predict_false(total_len < sizeof(*hdrp))) {
			MUE_PRINTF(un, "packet length %u too short\n", total_len);
			if_statinc(ifp, if_ierrors);
			return;
		}

		hdrp = (struct mue_rxbuf_hdr *)buf;
		rx_cmd_a = le32toh(hdrp->rx_cmd_a);

		if (__predict_false(rx_cmd_a & MUE_RX_CMD_A_ERRORS)) {
			/*
			 * We cannot use MUE_RX_CMD_A_RED bit here;
			 * it is turned on in the cases of L3/L4
			 * checksum errors which we handle below.
			 */
			MUE_PRINTF(un, "rx_cmd_a: %#x\n", rx_cmd_a);
			if_statinc(ifp, if_ierrors);
			return;
		}

		pktlen = (uint16_t)(rx_cmd_a & MUE_RX_CMD_A_LEN_MASK);
		if (un->un_flags & LAN7500)
			pktlen -= 2;

		if (__predict_false(pktlen < ETHER_HDR_LEN + ETHER_CRC_LEN ||
		    pktlen > MCLBYTES - ETHER_ALIGN || /* XXX */
		    pktlen + sizeof(*hdrp) > total_len)) {
			MUE_PRINTF(un, "invalid packet length %d\n", pktlen);
			if_statinc(ifp, if_ierrors);
			return;
		}

		if (__predict_false(rx_cmd_a & MUE_RX_CMD_A_ICSM)) {
			csum = 0;
		} else {
			v6 = rx_cmd_a & MUE_RX_CMD_A_IPV;
			switch (rx_cmd_a & MUE_RX_CMD_A_PID) {
			case MUE_RX_CMD_A_PID_TCP:
				csum = v6 ?
				    M_CSUM_TCPv6 : M_CSUM_IPv4 | M_CSUM_TCPv4;
				break;
			case MUE_RX_CMD_A_PID_UDP:
				csum = v6 ?
				    M_CSUM_UDPv6 : M_CSUM_IPv4 | M_CSUM_UDPv4;
				break;
			case MUE_RX_CMD_A_PID_IP:
				csum = v6 ? 0 : M_CSUM_IPv4;
				break;
			default:
				csum = 0;
				break;
			}
			csum &= ifp->if_csum_flags_rx;
			if (__predict_false((csum & M_CSUM_IPv4) &&
			    (rx_cmd_a & MUE_RX_CMD_A_ICE)))
				csum |= M_CSUM_IPv4_BAD;
			if (__predict_false((csum & ~M_CSUM_IPv4) &&
			    (rx_cmd_a & MUE_RX_CMD_A_TCE)))
				csum |= M_CSUM_TCP_UDP_BAD;
		}

		usbnet_enqueue(un, buf + sizeof(*hdrp), pktlen, csum,
			       0, M_HASFCS);

		/* Attention: sizeof(hdr) = 10 */
		pktlen = roundup(pktlen + sizeof(*hdrp), 4);
		if (pktlen > total_len)
			pktlen = total_len;
		total_len -= pktlen;
		buf += pktlen;
	} while (total_len > 0);
}

static int
mue_uno_init(struct ifnet *ifp)
{
	struct usbnet * const un = ifp->if_softc;

	if (usbnet_isdying(un)) {
		DPRINTF(un, "dying\n");
		return EIO;
	}

	/* Cancel pending I/O and free all TX/RX buffers. */
	if (ifp->if_flags & IFF_RUNNING)
		usbnet_stop(un, ifp, 1);

	mue_reset(un);

	/* Set MAC address. */
	mue_set_macaddr(un);

	/* TCP/UDP checksum offload engines. */
	mue_sethwcsum_locked(un);

	/* Set MTU. */
	mue_setmtu_locked(un);

	return usbnet_init_rx_tx(un);
}

static int
mue_uno_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
	struct usbnet * const un = ifp->if_softc;

	usbnet_lock_core(un);

	switch (cmd) {
	case SIOCSIFCAP:
		mue_sethwcsum_locked(un);
		break;
	case SIOCSIFMTU:
		mue_setmtu_locked(un);
		break;
	default:
		break;
	}

	usbnet_unlock_core(un);

	return 0;
}

static void
mue_reset(struct usbnet *un)
{
	if (usbnet_isdying(un))
		return;

	/* Wait a little while for the chip to get its brains in order. */
	usbd_delay_ms(un->un_udev, 1);

//	mue_chip_init(un); /* XXX */
}

static void
mue_uno_stop(struct ifnet *ifp, int disable)
{
	struct usbnet * const un = ifp->if_softc;

	mue_reset(un);
}

#ifdef _MODULE
#include "ioconf.c"
#endif

USBNET_MODULE(mue)