/*      $NetBSD: if_mue.c,v 1.83 2022/10/31 21:22:06 andvar Exp $       */
/*      $OpenBSD: if_mue.c,v 1.3 2018/08/04 16:42:46 jsg Exp $  */

/*
* Copyright (c) 2018 Kevin Lo <[email protected]>
*
* 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.83 2022/10/31 21:22:06 andvar 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 assigned 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);

       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 (usbnet_ispromisc(un)) {
               rxfilt |= MUE_RFE_CTL_UNICAST;
allmulti:       rxfilt |= MUE_RFE_CTL_MULTICAST;
               ec->ec_flags |= ETHER_F_ALLMULTI;
               if (usbnet_ispromisc(un))
                       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;

       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 0;
}

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

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

       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)