Add uaq(4), a driver for Aquantia AQC111U/AQC112U USB ethernet devices.
authorjmatthew <jmatthew@openbsd.org>
Sat, 4 Sep 2021 12:11:45 +0000 (12:11 +0000)
committerjmatthew <jmatthew@openbsd.org>
Sat, 4 Sep 2021 12:11:45 +0000 (12:11 +0000)
hardware provided by Brad
tested with modest success by mlarkin@, kevlo@ and Brad
ok kevlo@

share/man/man4/Makefile
share/man/man4/uaq.4 [new file with mode: 0644]
share/man/man4/usb.4
sys/dev/usb/files.usb
sys/dev/usb/if_uaq.c [new file with mode: 0644]

index 1541bb0..5fd83ed 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: Makefile,v 1.805 2021/09/02 10:12:20 mlarkin Exp $
+#      $OpenBSD: Makefile,v 1.806 2021/09/04 12:11:45 jmatthew Exp $
 
 MAN=   aac.4 abcrtc.4 abl.4 ac97.4 acphy.4 acrtc.4 \
        acpi.4 acpiac.4 acpials.4 acpiasus.4 acpibat.4 \
@@ -83,7 +83,7 @@ MAN=  aac.4 abcrtc.4 abl.4 ac97.4 acphy.4 acrtc.4 \
        tcic.4 tcp.4 tcpci.4 termios.4 tht.4 ti.4 tipmic.4 titmp.4 tl.4 \
        tlphy.4 thmc.4 tpm.4 tpmr.4 tqphy.4 trm.4 trunk.4 tsl.4 tty.4 \
        tun.4 tap.4 twe.4 \
-       txp.4 txphy.4 uaudio.4 uark.4 uath.4 ubcmtp.4 uberry.4 ubsa.4 \
+       txp.4 txphy.4 uaudio.4 uaq.4 uark.4 uath.4 ubcmtp.4 uberry.4 ubsa.4 \
        ubsec.4 ucc.4 ucom.4 uchcom.4 ucrcom.4 ucycom.4 ukspan.4 uslhcom.4 \
        udav.4 udcf.4 udl.4 udp.4 udsbr.4 \
        uftdi.4 ugen.4 ugl.4 ugold.4 uguru.4 uhci.4 uhid.4 uhidev.4 uhidpp.4 \
diff --git a/share/man/man4/uaq.4 b/share/man/man4/uaq.4
new file mode 100644 (file)
index 0000000..e632d15
--- /dev/null
@@ -0,0 +1,72 @@
+.\" $OpenBSD: uaq.4,v 1.1 2021/09/04 12:11:45 jmatthew Exp $
+.\"
+.\" Copyright (c) 2021 Jonathan Matthew <jmatthew@openbsd.org>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd $Mdocdate: September 4 2021 $
+.Dt UAQ 4
+.Os
+.Sh NAME
+.Nm uaq
+.Nd Aquantia AQC111U/AQC112U 100/Gigabit/2.5Gb/5Gb USB Ethernet device
+.Sh SYNOPSIS
+.Cd "uaq*   at uhub?"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for USB Ethernet adapters based on the Aquantia
+AQC111U and AQC112U chipsets, including the following:
+.Pp
+.Bl -tag -width Ds -offset indent -compact
+.It Aquantia AQtion USB to 5GbE Controller
+.It ASIX USB 3.1 Gen1 to 5G Multi-Gigabit Ethernet Adapter
+.It ASIX USB 3.1 Gen1 to 2.5G Multi-Gigabit Ethernet Adapter
+.It TRENDnet TUC-ET5G USB-C 3.1 to 5GBASE-T Ethernet Adapter
+.It QNAP QNA-UC5G1T USB to 5GbE Adapter
+.El
+.Pp
+The AQC111U and AQC112U contain an integrated Multi-Gig Ethernet MAC and
+PHY, supporting 100, 1000, 2500 and, in the AQC111U, 5000Mbps operation.
+.Pp
+For more information on configuring this device, see
+.Xr ifconfig 8 .
+.Sh SEE ALSO
+.Xr arp 4 ,
+.Xr ifmedia 4 ,
+.Xr mii 4 ,
+.Xr netintro 4 ,
+.Xr uhub 4 ,
+.Xr usb 4 ,
+.Xr hostname.if 5 ,
+.Xr ifconfig 8
+.Sh HISTORY
+The
+.Nm
+device driver first appeared in
+.Ox 7.0 .
+.Sh AUTHORS
+The
+.Nm
+driver was written by
+.An Jonathan Matthew Aq Mt jmatthew@openbsd.org .
index 3dfe8be..5b170b6 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $OpenBSD: usb.4,v 1.209 2021/08/21 10:27:10 jmc Exp $
+.\"    $OpenBSD: usb.4,v 1.210 2021/09/04 12:11:45 jmatthew Exp $
 .\"    $NetBSD: usb.4,v 1.15 1999/07/29 14:20:32 augustss Exp $
 .\"
 .\" Copyright (c) 1999 The NetBSD Foundation, Inc.
@@ -28,7 +28,7 @@
 .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd $Mdocdate: August 21 2021 $
+.Dd $Mdocdate: September 4 2021 $
 .Dt USB 4
 .Os
 .Sh NAME
@@ -125,6 +125,8 @@ MosChip MCS7730/7830/7832 10/100 USB Ethernet device
 Microchip LAN75xx/LAN78xx 10/100/Gigabit USB Ethernet device
 .It Xr smsc 4
 SMSC LAN95xx 10/100 USB Ethernet device
+.It Xr uaq 4
+Aquantia AQC111U/AQC112U 100/Gigabit/2.5Gb/5Gb USB Ethernet device
 .It Xr udav 4
 Davicom DM9601 10/100 USB Ethernet device
 .It Xr ure 4
index 02bf653..86aa41c 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: files.usb,v 1.146 2021/08/20 05:23:19 anton Exp $
+#      $OpenBSD: files.usb,v 1.147 2021/09/04 12:11:45 jmatthew Exp $
 #      $NetBSD: files.usb,v 1.16 2000/02/14 20:29:54 augustss Exp $
 #
 # Config file and device description for machine-independent USB code.
@@ -295,6 +295,10 @@ device     ure: ether, ifnet, mii, ifmedia
 attach ure at uhub
 file   dev/usb/if_ure.c                ure
 
+# Aquantia AQC111
+device uaq: ether, ifnet, ifmedia
+attach uaq at uhub
+file   dev/usb/if_uaq.c                uaq
 
 # Serial drivers
 # Modems
diff --git a/sys/dev/usb/if_uaq.c b/sys/dev/usb/if_uaq.c
new file mode 100644 (file)
index 0000000..6bbeacd
--- /dev/null
@@ -0,0 +1,1397 @@
+/*     $OpenBSD: if_uaq.c,v 1.1 2021/09/04 12:11:45 jmatthew Exp $     */
+/*-
+ * Copyright (c) 2021 Jonathan Matthew <jonathan@d14n.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "bpfilter.h"
+#include "vlan.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/sockio.h>
+#include <sys/rwlock.h>
+#include <sys/mbuf.h>
+#include <sys/kernel.h>
+#include <sys/socket.h>
+#include <sys/device.h>
+
+#include <machine/bus.h>
+
+#include <net/if.h>
+#include <net/if_media.h>
+
+#if NBPFILTER > 0
+#include <net/bpf.h>
+#endif
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbdivar.h>
+#include <dev/usb/usbdevs.h>
+
+#ifdef UAQ_DEBUG
+#define DPRINTF(x)     do { if (uaqdebug) printf x; } while (0)
+#define DPRINTFN(n,x)  do { if (uaqdebug >= (n)) printf x; } while (0)
+int    uaqdebug = 0;
+#else
+#define DPRINTF(x)
+#define DPRINTFN(n,x)
+#endif
+
+#define UAQ_ENDPT_RX           0
+#define UAQ_ENDPT_TX           1
+#define UAQ_ENDPT_INTR         2
+#define UAQ_ENDPT_MAX          3
+
+#define UAQ_TX_LIST_CNT                1
+#define UAQ_RX_LIST_CNT                1
+#define UAQ_TX_BUF_ALIGN       8
+#define UAQ_RX_BUF_ALIGN       8
+
+#define UAQ_TX_BUFSZ           16384
+#define UAQ_RX_BUFSZ           32768
+
+#define UAQ_CTL_READ           1
+#define UAQ_CTL_WRITE          2
+
+#define UAQ_MCAST_FILTER_SIZE  8
+
+/* control commands */
+#define UAQ_CMD_ACCESS_MAC     0x01
+#define UAQ_CMD_FLASH_PARAM    0x20
+#define UAQ_CMD_PHY_POWER      0x31
+#define UAQ_CMD_WOL_CFG                0x60
+#define UAQ_CMD_PHY_OPS                0x61
+
+/* SFR registers */
+#define UAQ_SFR_GENERAL_STATUS 0x03
+#define UAQ_SFR_CHIP_STATUS    0x05
+#define UAQ_SFR_RX_CTL         0x0B
+#define  UAQ_SFR_RX_CTL_STOP   0x0000
+#define  UAQ_SFR_RX_CTL_PRO    0x0001
+#define  UAQ_SFR_RX_CTL_AMALL  0x0002
+#define  UAQ_SFR_RX_CTL_AB     0x0008
+#define  UAQ_SFR_RX_CTL_AM     0x0010
+#define  UAQ_SFR_RX_CTL_START  0x0080
+#define  UAQ_SFR_RX_CTL_IPE    0x0200
+#define UAQ_SFR_IPG_0          0x0D
+#define UAQ_SFR_NODE_ID                0x10
+#define UAQ_SFR_MCAST_FILTER   0x16
+#define UAQ_SFR_MEDIUM_STATUS_MODE 0x22
+#define  UAQ_SFR_MEDIUM_XGMIIMODE      0x0001
+#define  UAQ_SFR_MEDIUM_FULL_DUPLEX    0x0002
+#define  UAQ_SFR_MEDIUM_RXFLOW_CTRLEN  0x0010
+#define  UAQ_SFR_MEDIUM_TXFLOW_CTRLEN  0x0020
+#define  UAQ_SFR_MEDIUM_JUMBO_EN       0x0040
+#define  UAQ_SFR_MEDIUM_RECEIVE_EN     0x0100
+#define UAQ_SFR_MONITOR_MODE   0x24
+#define  UAQ_SFR_MONITOR_MODE_EPHYRW   0x01
+#define  UAQ_SFR_MONITOR_MODE_RWLC     0x02
+#define  UAQ_SFR_MONITOR_MODE_RWMP     0x04
+#define  UAQ_SFR_MONITOR_MODE_RWWF     0x08
+#define  UAQ_SFR_MONITOR_MODE_RW_FLAG  0x10
+#define  UAQ_SFR_MONITOR_MODE_PMEPOL   0x20
+#define  UAQ_SFR_MONITOR_MODE_PMETYPE  0x40
+#define UAQ_SFR_RX_BULKIN_QCTRL 0x2E
+#define UAQ_SFR_RXCOE_CTL      0x34
+#define  UAQ_SFR_RXCOE_IP              0x01
+#define  UAQ_SFR_RXCOE_TCP             0x02
+#define  UAQ_SFR_RXCOE_UDP             0x04
+#define  UAQ_SFR_RXCOE_ICMP            0x08
+#define  UAQ_SFR_RXCOE_IGMP            0x10
+#define  UAQ_SFR_RXCOE_TCPV6           0x20
+#define  UAQ_SFR_RXCOE_UDPV6           0x40
+#define  UAQ_SFR_RXCOE_ICMV6           0x80
+#define UAQ_SFR_TXCOE_CTL      0x35
+#define  UAQ_SFR_TXCOE_IP              0x01
+#define  UAQ_SFR_TXCOE_TCP             0x02
+#define  UAQ_SFR_TXCOE_UDP             0x04
+#define  UAQ_SFR_TXCOE_ICMP            0x08
+#define  UAQ_SFR_TXCOE_IGMP            0x10
+#define  UAQ_SFR_TXCOE_TCPV6           0x20
+#define  UAQ_SFR_TXCOE_UDPV6           0x40
+#define  UAQ_SFR_TXCOE_ICMV6           0x80
+#define UAQ_SFR_BM_INT_MASK    0x41
+#define UAQ_SFR_BMRX_DMA_CTRL  0x43
+#define  UAQ_SFR_BMRX_DMA_EN   0x80
+#define UAQ_SFR_BMTX_DMA_CTRL  0x46
+#define UAQ_SFR_PAUSE_WATERLVL_LOW 0x54
+#define UAQ_SFR_ARC_CTRL       0x9E
+#define UAQ_SFR_SWP_CTRL       0xB1
+#define UAQ_SFR_TX_PAUSE_RESEND_T 0xB2
+#define UAQ_SFR_ETH_MAC_PATH   0xB7
+#define  UAQ_SFR_RX_PATH_READY 0x01
+#define UAQ_SFR_BULK_OUT_CTRL  0xB9
+#define  UAQ_SFR_BULK_OUT_FLUSH_EN     0x01
+#define  UAQ_SFR_BULK_OUT_EFF_EN       0x02
+
+#define UAQ_FW_VER_MAJOR       0xDA
+#define UAQ_FW_VER_MINOR       0xDB
+#define UAQ_FW_VER_REV         0xDC
+
+/* phy ops */
+#define UAQ_PHY_ADV_100M       (1 << 0)
+#define UAQ_PHY_ADV_1G         (1 << 1)
+#define UAQ_PHY_ADV_2_5G       (1 << 2)
+#define UAQ_PHY_ADV_5G         (1 << 3)
+#define UAQ_PHY_ADV_MASK       0x0F
+
+#define UAQ_PHY_PAUSE          (1 << 16)
+#define UAQ_PHY_ASYM_PAUSE     (1 << 17)
+#define UAQ_PHY_LOW_POWER      (1 << 18)
+#define UAQ_PHY_POWER_EN       (1 << 19)
+#define UAQ_PHY_WOL            (1 << 20)
+#define UAQ_PHY_DOWNSHIFT      (1 << 21)
+
+#define UAQ_PHY_DSH_RETRY_SHIFT        0x18
+#define UAQ_PHY_DSH_RETRY_MASK 0xF000000
+
+/* status */
+#define UAQ_STATUS_LINK                0x8000
+#define UAQ_STATUS_SPEED_MASK  0x7F00
+#define UAQ_STATUS_SPEED_SHIFT 8
+#define UAQ_STATUS_SPEED_5G    0x000F
+#define UAQ_STATUS_SPEED_2_5G  0x0010
+#define UAQ_STATUS_SPEED_1G    0x0011
+#define UAQ_STATUS_SPEED_100M  0x0013
+
+/* rx descriptor */
+#define UAQ_RX_HDR_COUNT_MASK  0x1FFF
+#define UAQ_RX_HDR_OFFSET_MASK 0xFFFFE000
+#define UAQ_RX_HDR_OFFSET_SHIFT        13
+
+/* rx packet descriptor */
+#define UAQ_RX_PKT_L4_ERR      0x01
+#define UAQ_RX_PKT_L3_ERR      0x02
+#define UAQ_RX_PKT_L4_MASK     0x1C
+#define UAQ_RX_PKT_L4_UDP      0x04
+#define UAQ_RX_PKT_L4_TCP      0x10
+#define UAQ_RX_PKT_L3_MASK     0x60
+#define UAQ_RX_PKT_L3_IP       0x20
+#define UAQ_RX_PKT_L3_IP6      0x40
+#define UAQ_RX_PKT_VLAN                0x400
+#define UAQ_RX_PKT_RX_OK       0x800
+#define UAQ_RX_PKT_DROP                0x80000000
+#define UAQ_RX_PKT_LEN_MASK    0x7FFF0000
+#define UAQ_RX_PKT_LEN_SHIFT   16
+#define UAQ_RX_PKT_VLAN_SHIFT  32
+
+/* tx packet descriptor */
+#define UAQ_TX_PKT_LEN_MASK    0x1FFFFF
+#define UAQ_TX_PKT_DROP_PADD   (1 << 28)
+#define UAQ_TX_PKT_VLAN                (1 << 29)
+#define UAQ_TX_PKT_VLAN_MASK   0xFFFF
+#define UAQ_TX_PKT_VLAN_SHIFT  0x30
+
+
+struct uaq_chain {
+       struct uaq_softc        *uc_sc;
+       struct usbd_xfer        *uc_xfer;
+       char                    *uc_buf;
+       uint32_t                 uc_cnt;
+       uint32_t                 uc_buflen;
+       uint32_t                 uc_bufmax;
+       SLIST_ENTRY(uaq_chain)   uc_list;
+       uint8_t                  uc_idx;
+};
+
+struct uaq_cdata {
+       struct uaq_chain         uaq_rx_chain[UAQ_RX_LIST_CNT];
+       struct uaq_chain         uaq_tx_chain[UAQ_TX_LIST_CNT];
+       SLIST_HEAD(uaq_list_head, uaq_chain) uaq_tx_free;
+};
+
+struct uaq_softc {
+       struct device            sc_dev;
+       struct usbd_device      *sc_udev;
+
+       struct usbd_interface   *sc_iface;
+       struct usb_task          sc_link_task;
+       struct timeval           sc_rx_notice;
+       int                      sc_ed[UAQ_ENDPT_MAX];
+       struct usbd_pipe        *sc_ep[UAQ_ENDPT_MAX];
+       int                      sc_out_frame_size;
+
+       struct arpcom            sc_ac;
+       struct ifmedia           sc_ifmedia;
+
+       struct uaq_cdata         sc_cdata;
+       uint64_t                 sc_link_status;
+       int                      sc_link_speed;
+
+       uint32_t                 sc_phy_cfg;
+       uint16_t                 sc_rxctl;
+};
+
+const struct usb_devno uaq_devs[] = {
+       { USB_VENDOR_AQUANTIA, USB_PRODUCT_AQUANTIA_AQC111 },
+       { USB_VENDOR_ASIX, USB_PRODUCT_ASIX_ASIX111 },
+       { USB_VENDOR_ASIX, USB_PRODUCT_ASIX_ASIX112 },
+       { USB_VENDOR_TRENDNET, USB_PRODUCT_TRENDNET_TUCET5G },
+       { USB_VENDOR_QNAP, USB_PRODUCT_QNAP_UC5G1T },
+};
+
+int            uaq_match(struct device *, void *, void *);
+void           uaq_attach(struct device *, struct device *, void *);
+int            uaq_detach(struct device *, int);
+
+int            uaq_ctl(struct uaq_softc *, uint8_t, uint8_t, uint16_t,
+                   uint16_t, void *, int);
+int            uaq_read_mem(struct uaq_softc *, uint8_t, uint16_t, uint16_t,
+                   void *, int);
+int            uaq_write_mem(struct uaq_softc *, uint8_t, uint16_t, uint16_t,
+                   void *, int);
+uint8_t                uaq_read_1(struct uaq_softc *, uint8_t, uint16_t, uint16_t);
+uint16_t       uaq_read_2(struct uaq_softc *, uint8_t, uint16_t, uint16_t);
+uint32_t       uaq_read_4(struct uaq_softc *, uint8_t, uint16_t, uint16_t);
+int            uaq_write_1(struct uaq_softc *, uint8_t, uint16_t, uint16_t,
+                   uint32_t);
+int            uaq_write_2(struct uaq_softc *, uint8_t, uint16_t, uint16_t,
+                   uint32_t);
+int            uaq_write_4(struct uaq_softc *, uint8_t, uint16_t, uint16_t,
+                   uint32_t);
+
+int            uaq_ifmedia_upd(struct ifnet *);
+void           uaq_ifmedia_sts(struct ifnet *, struct ifmediareq *);
+void           uaq_add_media_types(struct uaq_softc *);
+void           uaq_iff(struct uaq_softc *);
+
+void           uaq_init(void *);
+int            uaq_ioctl(struct ifnet *, u_long, caddr_t);
+int            uaq_xfer_list_init(struct uaq_softc *, struct uaq_chain *,
+                   uint32_t, int);
+void           uaq_xfer_list_free(struct uaq_softc *, struct uaq_chain *, int);
+
+void           uaq_stop(struct uaq_softc *);
+void           uaq_link(struct uaq_softc *);
+void           uaq_intr(struct usbd_xfer *, void *, usbd_status);
+void           uaq_start(struct ifnet *);
+void           uaq_rxeof(struct usbd_xfer *, void *, usbd_status);
+void           uaq_txeof(struct usbd_xfer *, void *, usbd_status);
+void           uaq_watchdog(struct ifnet *);
+void           uaq_reset(struct uaq_softc *);
+
+int            uaq_encap_txpkt(struct uaq_softc *, struct mbuf *, char *,
+                   uint32_t);
+int            uaq_encap_xfer(struct uaq_softc *, struct uaq_chain *);
+
+struct cfdriver uaq_cd = {
+       NULL, "uaq", DV_IFNET
+};
+
+const struct cfattach uaq_ca = {
+       sizeof(struct uaq_softc), uaq_match, uaq_attach, uaq_detach
+};
+
+int
+uaq_ctl(struct uaq_softc *sc, uint8_t rw, uint8_t cmd, uint16_t val,
+    uint16_t index, void *buf, int len)
+{
+       usb_device_request_t    req;
+       usbd_status             err;
+
+       if (usbd_is_dying(sc->sc_udev))
+               return 0;
+
+       if (rw == UAQ_CTL_WRITE)
+               req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
+       else
+               req.bmRequestType = UT_READ_VENDOR_DEVICE;
+       req.bRequest = cmd;
+       USETW(req.wValue, val);
+       USETW(req.wIndex, index);
+       USETW(req.wLength, len);
+
+       DPRINTFN(5, ("uaq_ctl: rw %d, val 0x%04hx, index 0x%04hx, len %d\n",
+           rw, val, index, len));
+       err = usbd_do_request(sc->sc_udev, &req, buf);
+       if (err) {
+               DPRINTF(("uaq_ctl: error %d\n", err));
+               return -1;
+       }
+
+       return 0;
+}
+
+int
+uaq_read_mem(struct uaq_softc *sc, uint8_t cmd, uint16_t addr, uint16_t index,
+    void *buf, int len)
+{
+       return (uaq_ctl(sc, UAQ_CTL_READ, cmd, addr, index, buf, len));
+}
+
+int
+uaq_write_mem(struct uaq_softc *sc, uint8_t cmd, uint16_t addr, uint16_t index,
+    void *buf, int len)
+{
+       return (uaq_ctl(sc, UAQ_CTL_WRITE, cmd, addr, index, buf, len));
+}
+
+uint8_t
+uaq_read_1(struct uaq_softc *sc, uint8_t cmd, uint16_t reg, uint16_t index)
+{
+       uint8_t         val;
+
+       uaq_read_mem(sc, cmd, reg, index, &val, 1);
+       DPRINTFN(4, ("uaq_read_1: cmd %x reg %x index %x = %x\n", cmd, reg,
+           index, val));
+       return (val);
+}
+
+uint16_t
+uaq_read_2(struct uaq_softc *sc, uint8_t cmd, uint16_t reg, uint16_t index)
+{
+       uint16_t        val;
+
+       uaq_read_mem(sc, cmd, reg, index, &val, 2);
+       DPRINTFN(4, ("uaq_read_2: cmd %x reg %x index %x = %x\n", cmd, reg,
+           index, UGETW(&val)));
+
+       return (UGETW(&val));
+}
+
+uint32_t
+uaq_read_4(struct uaq_softc *sc, uint8_t cmd, uint16_t reg, uint16_t index)
+{
+       uint32_t        val;
+
+       uaq_read_mem(sc, cmd, reg, index, &val, 4);
+       DPRINTFN(4, ("uaq_read_4: cmd %x reg %x index %x = %x\n", cmd, reg,
+           index, UGETDW(&val)));
+       return (UGETDW(&val));
+}
+
+int
+uaq_write_1(struct uaq_softc *sc, uint8_t cmd, uint16_t reg, uint16_t index,
+    uint32_t val)
+{
+       uint8_t         temp;
+
+       DPRINTFN(4, ("uaq_write_1: cmd %x reg %x index %x: %x\n", cmd, reg,
+           index, val));
+       temp = val & 0xff;
+       return (uaq_write_mem(sc, cmd, reg, index, &temp, 1));
+}
+
+int
+uaq_write_2(struct uaq_softc *sc, uint8_t cmd, uint16_t reg, uint16_t index,
+    uint32_t val)
+{
+       uint16_t        temp;
+
+       DPRINTFN(4, ("uaq_write_2: cmd %x reg %x index %x: %x\n", cmd, reg,
+           index, val));
+       USETW(&temp, val & 0xffff);
+       return (uaq_write_mem(sc, cmd, reg, index, &temp, 2));
+}
+
+int
+uaq_write_4(struct uaq_softc *sc, uint8_t cmd, uint16_t reg, uint16_t index,
+    uint32_t val)
+{
+       uint8_t temp[4];
+
+       DPRINTFN(4, ("uaq_write_4: cmd %x reg %x index %x: %x\n", cmd, reg,
+           index, val));
+       USETDW(temp, val);
+       return (uaq_write_mem(sc, cmd, reg, index, &temp, 4));
+}
+
+int
+uaq_match(struct device *parent, void *match, void *aux)
+{
+       struct usb_attach_arg   *uaa = aux;
+
+       if (uaa->iface == NULL || uaa->configno != 1)
+               return (UMATCH_NONE);
+       
+       return (usb_lookup(uaq_devs, uaa->vendor, uaa->product) != NULL ?
+           UMATCH_VENDOR_PRODUCT_CONF_IFACE : UMATCH_NONE);
+}
+
+void
+uaq_attach(struct device *parent, struct device *self, void *aux)
+{
+       struct uaq_softc                *sc = (struct uaq_softc *)self;
+       struct usb_attach_arg           *uaa = aux;
+       usb_interface_descriptor_t      *id;
+       usb_endpoint_descriptor_t       *ed;
+       struct ifnet                    *ifp;
+       int                             i, s;
+
+       sc->sc_udev = uaa->device;
+       sc->sc_iface = uaa->iface;
+
+       usb_init_task(&sc->sc_link_task, (void (*)(void *))uaq_link, sc,
+           USB_TASK_TYPE_GENERIC);
+
+       id = usbd_get_interface_descriptor(sc->sc_iface);
+
+       for (i = 0; i < id->bNumEndpoints; i++) {
+               ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i);
+               if (!ed) {
+                       printf("%s: couldn't get ep %d\n",
+                           sc->sc_dev.dv_xname, i);
+                       return;
+               }
+               if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
+                   UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
+                       sc->sc_ed[UAQ_ENDPT_RX] = ed->bEndpointAddress;
+               } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
+                   UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
+                       sc->sc_ed[UAQ_ENDPT_TX] = ed->bEndpointAddress;
+                       sc->sc_out_frame_size = UGETW(ed->wMaxPacketSize);
+               } else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
+                   UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) {
+                       sc->sc_ed[UAQ_ENDPT_INTR] = ed->bEndpointAddress;
+               }
+       }
+
+       if ((sc->sc_ed[UAQ_ENDPT_RX] == 0) ||
+           (sc->sc_ed[UAQ_ENDPT_TX] == 0) ||
+           (sc->sc_ed[UAQ_ENDPT_INTR] == 0)) {
+               printf("%s: missing one or more endpoints (%d, %d, %d)\n",
+                   sc->sc_dev.dv_xname, sc->sc_ed[UAQ_ENDPT_RX],
+                   sc->sc_ed[UAQ_ENDPT_TX], sc->sc_ed[UAQ_ENDPT_INTR]);
+               return;
+       }
+
+       s = splnet();
+
+       printf("%s: ver %u.%u.%u", sc->sc_dev.dv_xname,
+           uaq_read_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_FW_VER_MAJOR, 1) & 0x7f,
+           uaq_read_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_FW_VER_MINOR, 1),
+           uaq_read_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_FW_VER_REV, 1));
+
+       uaq_read_mem(sc, UAQ_CMD_FLASH_PARAM, 0, 0, &sc->sc_ac.ac_enaddr,
+           ETHER_ADDR_LEN);
+       printf(", address %s\n", ether_sprintf(sc->sc_ac.ac_enaddr));
+
+       ifp = &sc->sc_ac.ac_if;
+       ifp->if_softc = sc;
+       strlcpy(ifp->if_xname, sc->sc_dev.dv_xname, IFNAMSIZ);
+       ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
+       ifp->if_ioctl = uaq_ioctl;
+       ifp->if_start = uaq_start;
+       ifp->if_watchdog = uaq_watchdog;
+
+       ifp->if_capabilities = IFCAP_VLAN_MTU | IFCAP_CSUM_IPv4 |
+           IFCAP_CSUM_TCPv4 | IFCAP_CSUM_UDPv4;
+
+#if NVLAN > 0
+       ifp->if_capabilities |= IFCAP_VLAN_HWTAGGING;
+#endif
+
+       ifmedia_init(&sc->sc_ifmedia, IFM_IMASK, uaq_ifmedia_upd,
+           uaq_ifmedia_sts);
+       uaq_add_media_types(sc);
+       ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_AUTO, 0, NULL);
+       ifmedia_set(&sc->sc_ifmedia, IFM_ETHER | IFM_AUTO);
+       sc->sc_ifmedia.ifm_media = sc->sc_ifmedia.ifm_cur->ifm_media;
+
+       if_attach(ifp);
+       ether_ifattach(ifp);
+
+       splx(s);
+}
+
+int
+uaq_detach(struct device *self, int flags)
+{
+       struct uaq_softc        *sc = (struct uaq_softc *)self;
+       struct ifnet            *ifp = &sc->sc_ac.ac_if;
+       int                     s;
+
+       if (sc->sc_ep[UAQ_ENDPT_TX] != NULL)
+               usbd_abort_pipe(sc->sc_ep[UAQ_ENDPT_TX]);
+       if (sc->sc_ep[UAQ_ENDPT_RX] != NULL)
+               usbd_abort_pipe(sc->sc_ep[UAQ_ENDPT_RX]);
+       if (sc->sc_ep[UAQ_ENDPT_INTR] != NULL)
+               usbd_abort_pipe(sc->sc_ep[UAQ_ENDPT_INTR]);
+
+       s = splusb();
+
+       usb_rem_task(sc->sc_udev, &sc->sc_link_task);
+
+       usb_detach_wait(&sc->sc_dev);
+
+       if (ifp->if_flags & IFF_RUNNING)
+               uaq_stop(sc);
+
+       if (ifp->if_softc != NULL) {
+               ether_ifdetach(ifp);
+               if_detach(ifp);
+       }
+
+       splx(s);
+
+       return 0;
+}
+
+int
+uaq_ifmedia_upd(struct ifnet *ifp)
+{
+       struct uaq_softc        *sc = ifp->if_softc;
+       struct ifmedia          *ifm = &sc->sc_ifmedia;
+       int                      auto_adv;
+
+       if (IFM_TYPE(ifm->ifm_media) != IFM_ETHER)
+               return (EINVAL);
+
+       auto_adv = UAQ_PHY_ADV_100M | UAQ_PHY_ADV_1G;
+       if (sc->sc_udev->speed == USB_SPEED_SUPER)
+               auto_adv |= UAQ_PHY_ADV_2_5G | UAQ_PHY_ADV_5G;
+
+       sc->sc_phy_cfg &= ~(UAQ_PHY_ADV_MASK);
+       sc->sc_phy_cfg |= UAQ_PHY_PAUSE | UAQ_PHY_ASYM_PAUSE |
+           UAQ_PHY_DOWNSHIFT | (3 << UAQ_PHY_DSH_RETRY_SHIFT);
+
+       switch (IFM_SUBTYPE(ifm->ifm_media)) {
+       case IFM_AUTO:
+               sc->sc_phy_cfg |= auto_adv;
+               break;
+       case IFM_5000_T:
+               sc->sc_phy_cfg |= UAQ_PHY_ADV_5G;
+               break;
+       case IFM_2500_T:
+               sc->sc_phy_cfg |= UAQ_PHY_ADV_2_5G;
+               break;
+       case IFM_1000_T:
+               sc->sc_phy_cfg |= UAQ_PHY_ADV_1G;
+               break;
+       case IFM_100_TX:
+               sc->sc_phy_cfg |= UAQ_PHY_ADV_100M;
+               break;
+       default:
+               printf("%s: unsupported media type\n", sc->sc_dev.dv_xname);
+               return (EINVAL);
+       }
+
+       DPRINTFN(1, ("%s: phy cfg %x\n", sc->sc_dev.dv_xname, sc->sc_phy_cfg));
+       uaq_write_4(sc, UAQ_CMD_PHY_OPS, 0, 0, sc->sc_phy_cfg);
+       return (0);
+}
+
+void
+uaq_ifmedia_sts(struct ifnet *ifp, struct ifmediareq *ifmr)
+{
+       struct uaq_softc        *sc = ifp->if_softc;
+
+       ifmr->ifm_status = IFM_AVALID;
+       if (sc->sc_link_speed > 0) {
+               ifmr->ifm_status |= IFM_ACTIVE;
+               ifmr->ifm_active = IFM_ETHER | IFM_FDX;
+               switch (sc->sc_link_speed) {
+               case UAQ_STATUS_SPEED_5G:
+                       ifmr->ifm_active |= IFM_5000_T;
+                       break;
+               case UAQ_STATUS_SPEED_2_5G:
+                       ifmr->ifm_active |= IFM_2500_T;
+                       break;
+               case UAQ_STATUS_SPEED_1G:
+                       ifmr->ifm_active |= IFM_1000_T;
+                       break;
+               case UAQ_STATUS_SPEED_100M:
+                       ifmr->ifm_active |= IFM_100_TX;
+                       break;
+               default:
+                       break;
+               }
+       }
+}
+
+void
+uaq_add_media_types(struct uaq_softc *sc)
+{
+       ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_100_TX, 0, NULL);
+       ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_100_TX | IFM_FDX, 0,
+           NULL);
+       ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_1000_T, 0, NULL);
+       ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_1000_T | IFM_FDX, 0,
+           NULL);
+       /* only add 2.5G and 5G if at super speed */
+       ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_2500_T, 0, NULL);
+       ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_2500_T | IFM_FDX, 0,
+           NULL);
+       ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_5000_T, 0, NULL);
+       ifmedia_add(&sc->sc_ifmedia, IFM_ETHER | IFM_5000_T | IFM_FDX, 0,
+           NULL);
+}
+
+void
+uaq_iff(struct uaq_softc *sc)
+{
+       struct ifnet            *ifp = &sc->sc_ac.ac_if;
+       struct ether_multi      *enm;
+       struct ether_multistep  step;
+       uint8_t                 filter[UAQ_MCAST_FILTER_SIZE];
+       uint32_t                hash;
+
+       if (usbd_is_dying(sc->sc_udev))
+               return;
+
+       sc->sc_rxctl &= ~(UAQ_SFR_RX_CTL_PRO | UAQ_SFR_RX_CTL_AMALL |
+           UAQ_SFR_RX_CTL_AM);
+       if (ifp->if_flags & IFF_PROMISC || sc->sc_ac.ac_multirangecnt > 0) {
+               sc->sc_rxctl |= UAQ_SFR_RX_CTL_PRO;
+       } else if (ifp->if_flags & IFF_ALLMULTI ||
+           sc->sc_ac.ac_multirangecnt > 0) {
+               sc->sc_rxctl |= UAQ_SFR_RX_CTL_AMALL;
+       } else if (sc->sc_ac.ac_multicnt > 0) {
+               sc->sc_rxctl |= UAQ_SFR_RX_CTL_AM;
+
+               bzero(filter, sizeof(filter));
+               ETHER_FIRST_MULTI(step, &sc->sc_ac, enm);
+               while (enm != NULL) {
+                       hash = ether_crc32_be(enm->enm_addrlo, ETHER_ADDR_LEN)
+                           >> 26;
+                       filter[hash >> 3] |= (1 << (hash & 7));
+                       ETHER_NEXT_MULTI(step, enm);
+               }
+
+               uaq_write_mem(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_MCAST_FILTER,
+                   UAQ_MCAST_FILTER_SIZE, filter, UAQ_MCAST_FILTER_SIZE);
+       }
+
+       DPRINTFN(1, ("%s: rxctl = %x\n", sc->sc_dev.dv_xname, sc->sc_rxctl));
+       uaq_write_2(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_RX_CTL, 2, sc->sc_rxctl);
+}
+
+void
+uaq_reset(struct uaq_softc *sc)
+{
+       uint8_t mode;
+
+       sc->sc_phy_cfg = UAQ_PHY_POWER_EN;
+       uaq_write_4(sc, UAQ_CMD_PHY_OPS, 0, 0, sc->sc_phy_cfg);
+
+       uaq_write_mem(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_NODE_ID, 0,
+           sc->sc_ac.ac_enaddr, ETHER_ADDR_LEN);
+       uaq_write_mem(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_NODE_ID, ETHER_ADDR_LEN,
+           sc->sc_ac.ac_enaddr, ETHER_ADDR_LEN);
+
+       uaq_write_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_BM_INT_MASK, 0, 0xff);
+       uaq_write_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_SWP_CTRL, 0, 0);
+
+       mode = uaq_read_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_MONITOR_MODE, 1);
+       mode &= ~(UAQ_SFR_MONITOR_MODE_EPHYRW | UAQ_SFR_MONITOR_MODE_RWLC |
+           UAQ_SFR_MONITOR_MODE_RWMP | UAQ_SFR_MONITOR_MODE_RWWF |
+           UAQ_SFR_MONITOR_MODE_RW_FLAG);
+       uaq_write_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_MONITOR_MODE, 1, mode);
+
+       sc->sc_link_status = 0;
+       sc->sc_link_speed = 0;
+}
+
+void
+uaq_init(void *xsc)
+{
+       struct uaq_softc        *sc = xsc;
+       struct uaq_chain        *c;
+       struct ifnet            *ifp = &sc->sc_ac.ac_if;
+       usbd_status             err;
+       int                     s, i;
+
+       s = splnet();
+
+       uaq_stop(sc);
+
+       uaq_reset(sc);
+
+       if (uaq_xfer_list_init(sc, sc->sc_cdata.uaq_rx_chain,
+               UAQ_RX_BUFSZ, UAQ_RX_LIST_CNT) == ENOBUFS) {
+               printf("%s: rx list init failed\n", sc->sc_dev.dv_xname);
+               splx(s);
+               return;
+       }
+
+       if (uaq_xfer_list_init(sc, sc->sc_cdata.uaq_tx_chain,
+               UAQ_TX_BUFSZ, UAQ_TX_LIST_CNT) == ENOBUFS) {
+               printf("%s: tx list init failed\n", sc->sc_dev.dv_xname);
+               splx(s);
+               return;
+       }
+
+       SLIST_INIT(&sc->sc_cdata.uaq_tx_free);
+       for (i = 0; i < UAQ_TX_LIST_CNT; i++)
+               SLIST_INSERT_HEAD(&sc->sc_cdata.uaq_tx_free,
+                   &sc->sc_cdata.uaq_tx_chain[i], uc_list);
+
+       err = usbd_open_pipe(sc->sc_iface, sc->sc_ed[UAQ_ENDPT_RX],
+           USBD_EXCLUSIVE_USE, &sc->sc_ep[UAQ_ENDPT_RX]);
+       if (err) {
+               printf("%s: open rx pipe failed: %s\n",
+                   sc->sc_dev.dv_xname, usbd_errstr(err));
+               splx(s);
+               return;
+       }
+
+       err = usbd_open_pipe(sc->sc_iface, sc->sc_ed[UAQ_ENDPT_TX],
+           USBD_EXCLUSIVE_USE, &sc->sc_ep[UAQ_ENDPT_TX]);
+       if (err) {
+               printf("%s: open tx pipe failed: %s\n",
+                   sc->sc_dev.dv_xname, usbd_errstr(err));
+               splx(s);
+               return;
+       }
+
+       for (i = 0; i < UAQ_RX_LIST_CNT; i++) {
+               c = &sc->sc_cdata.uaq_rx_chain[i];
+               usbd_setup_xfer(c->uc_xfer, sc->sc_ep[UAQ_ENDPT_RX],
+                   c, c->uc_buf, c->uc_bufmax,
+                   USBD_SHORT_XFER_OK | USBD_NO_COPY,
+                   USBD_NO_TIMEOUT, uaq_rxeof);
+               usbd_transfer(c->uc_xfer);
+       }
+
+       err = usbd_open_pipe_intr(sc->sc_iface, sc->sc_ed[UAQ_ENDPT_INTR],
+           0, &sc->sc_ep[UAQ_ENDPT_INTR], sc,
+           &sc->sc_link_status, sizeof(sc->sc_link_status), uaq_intr,
+           USBD_DEFAULT_INTERVAL);
+       if (err) {
+               printf("%s: couldn't open interrupt pipe\n",
+                   sc->sc_dev.dv_xname);
+               return;
+       }
+
+       uaq_ifmedia_upd(ifp);
+
+       ifp->if_flags |= IFF_RUNNING;
+       ifq_clr_oactive(&ifp->if_snd);
+
+       splx(s);
+}
+
+int
+uaq_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
+{
+       struct uaq_softc        *sc = ifp->if_softc;
+       struct ifreq            *ifr = (struct ifreq *)data;
+       int                     s, error = 0;
+
+       s = splnet();
+
+       switch (cmd) {
+       case SIOCSIFADDR:
+               ifp->if_flags |= IFF_UP;
+               if (!(ifp->if_flags & IFF_RUNNING))
+                       uaq_init(sc);
+               break;
+
+       case SIOCSIFFLAGS:
+               if (ifp->if_flags & IFF_UP) {
+                       if (ifp->if_flags & IFF_RUNNING)
+                               error = ENETRESET;
+                       else
+                               uaq_init(sc);
+               } else {
+                       if (ifp->if_flags & IFF_RUNNING)
+                               uaq_stop(sc);
+               }
+               break;
+
+       case SIOCGIFMEDIA:
+       case SIOCSIFMEDIA:
+               error = ifmedia_ioctl(ifp, ifr, &sc->sc_ifmedia, cmd);
+               break;
+
+       default:
+               error = ether_ioctl(ifp, &sc->sc_ac, cmd, data);
+       }
+
+       if (error == ENETRESET) {
+               if (ifp->if_flags & IFF_RUNNING)
+                       uaq_iff(sc);
+               error = 0;
+       }
+
+       splx(s);
+
+       return (error);
+}
+
+int
+uaq_xfer_list_init(struct uaq_softc *sc, struct uaq_chain *ch,
+    uint32_t bufsize, int listlen)
+{
+       struct uaq_chain        *c;
+       int                     i;
+
+       for (i = 0; i < listlen; i++) {
+               c = &ch[i];
+               c->uc_sc = sc;
+               c->uc_idx = i;
+               c->uc_buflen = 0;
+               c->uc_bufmax = bufsize;
+               c->uc_cnt = 0;
+               if (c->uc_xfer == NULL) {
+                       c->uc_xfer = usbd_alloc_xfer(sc->sc_udev);
+                       if (c->uc_xfer == NULL)
+                               return (ENOBUFS);
+
+                       c->uc_buf = usbd_alloc_buffer(c->uc_xfer, c->uc_bufmax);
+                       if (c->uc_buf == NULL) {
+                               usbd_free_xfer(c->uc_xfer);
+                               c->uc_xfer = NULL;
+                               return (ENOBUFS);
+                       }
+               }
+       }
+
+       return (0);
+}
+
+void
+uaq_xfer_list_free(struct uaq_softc *sc, struct uaq_chain *ch, int listlen)
+{
+       int     i;
+
+       for (i = 0; i < listlen; i++) {
+               if (ch[i].uc_buf != NULL) {
+                       ch[i].uc_buf = NULL;
+               }
+               ch[i].uc_cnt = 0;
+               if (ch[i].uc_xfer != NULL) {
+                       usbd_free_xfer(ch[i].uc_xfer);
+                       ch[i].uc_xfer = NULL;
+               }
+       }
+}
+
+void
+uaq_stop(struct uaq_softc *sc)
+{
+       struct uaq_cdata        *cd;
+       struct ifnet            *ifp;
+       usbd_status             err;
+
+       ifp = &sc->sc_ac.ac_if;
+       ifp->if_timer = 0;
+       ifp->if_flags &= ~IFF_RUNNING;
+       ifq_clr_oactive(&ifp->if_snd);
+
+       sc->sc_link_status = 0;
+       sc->sc_link_speed = 0;
+
+       if (sc->sc_ep[UAQ_ENDPT_RX] != NULL) {
+               err = usbd_close_pipe(sc->sc_ep[UAQ_ENDPT_RX]);
+               if (err) {
+                       printf("%s: close rx pipe failed: %s\n",
+                           sc->sc_dev.dv_xname, usbd_errstr(err));
+               }
+               sc->sc_ep[UAQ_ENDPT_RX] = NULL;
+       }
+
+       if (sc->sc_ep[UAQ_ENDPT_TX] != NULL) {
+               err = usbd_close_pipe(sc->sc_ep[UAQ_ENDPT_TX]);
+               if (err) {
+                       printf("%s: close tx pipe failed: %s\n",
+                           sc->sc_dev.dv_xname, usbd_errstr(err));
+               }
+               sc->sc_ep[UAQ_ENDPT_TX] = NULL;
+       }
+
+       if (sc->sc_ep[UAQ_ENDPT_INTR] != NULL) {
+               err = usbd_close_pipe(sc->sc_ep[UAQ_ENDPT_INTR]);
+               if (err) {
+                       printf("%s: close intr pipe failed: %s\n",
+                           sc->sc_dev.dv_xname, usbd_errstr(err));
+               }
+               sc->sc_ep[UAQ_ENDPT_INTR] = NULL;
+       }
+
+       cd = &sc->sc_cdata;
+       uaq_xfer_list_free(sc, cd->uaq_rx_chain, UAQ_RX_LIST_CNT);
+       uaq_xfer_list_free(sc, cd->uaq_tx_chain, UAQ_TX_LIST_CNT);
+}
+
+void
+uaq_link(struct uaq_softc *sc)
+{
+       if (sc->sc_link_speed > 0) {
+               uint8_t resend[3] = { 0, 0xf8, 7 };
+               uint8_t qctrl[5] = { 7, 0x00, 0x01, 0x1e, 0xff };
+               uint8_t ipg = 0;
+
+               switch (sc->sc_link_speed) {
+               case UAQ_STATUS_SPEED_100M:
+                       resend[1] = 0xfb;
+                       resend[2] = 0x4;
+                       break;
+
+               case UAQ_STATUS_SPEED_5G:
+                       ipg = 5;
+                       break;
+               }
+
+               uaq_write_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_IPG_0, 1, ipg);
+
+               uaq_write_mem(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_TX_PAUSE_RESEND_T,
+                   3, resend, 3);
+               uaq_write_mem(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_RX_BULKIN_QCTRL,
+                   5, qctrl, 5);
+               uaq_write_2(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_PAUSE_WATERLVL_LOW,
+                   2, 0x0810);
+
+               uaq_write_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_BMRX_DMA_CTRL, 1,
+                   0);
+               uaq_write_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_BMTX_DMA_CTRL, 1,
+                   0);
+               uaq_write_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_ARC_CTRL, 1, 0);
+
+               sc->sc_rxctl = UAQ_SFR_RX_CTL_IPE | UAQ_SFR_RX_CTL_AB;
+               uaq_write_2(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_RX_CTL, 2,
+                   sc->sc_rxctl);
+
+               uaq_write_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_ETH_MAC_PATH, 1,
+                   UAQ_SFR_RX_PATH_READY);
+
+               uaq_write_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_BULK_OUT_CTRL, 1,
+                   UAQ_SFR_BULK_OUT_EFF_EN);
+
+               uaq_write_2(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_MEDIUM_STATUS_MODE,
+                   2, 0);
+               uaq_write_2(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_MEDIUM_STATUS_MODE,
+                   2, UAQ_SFR_MEDIUM_XGMIIMODE | UAQ_SFR_MEDIUM_FULL_DUPLEX |
+                   UAQ_SFR_MEDIUM_RECEIVE_EN | UAQ_SFR_MEDIUM_RXFLOW_CTRLEN |
+                   UAQ_SFR_MEDIUM_TXFLOW_CTRLEN);      /* JUMBO_EN */
+
+               uaq_write_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_RXCOE_CTL, 1,
+                   UAQ_SFR_RXCOE_IP | UAQ_SFR_RXCOE_TCP | UAQ_SFR_RXCOE_UDP |
+                   UAQ_SFR_RXCOE_TCPV6 | UAQ_SFR_RXCOE_UDPV6);
+               uaq_write_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_TXCOE_CTL, 1,
+                   UAQ_SFR_TXCOE_IP | UAQ_SFR_TXCOE_TCP | UAQ_SFR_TXCOE_UDP |
+                   UAQ_SFR_TXCOE_TCPV6 | UAQ_SFR_TXCOE_UDPV6);
+
+               sc->sc_rxctl |= UAQ_SFR_RX_CTL_START;
+               uaq_write_2(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_RX_CTL, 2,
+                   sc->sc_rxctl);
+       } else {
+               uint16_t mode;
+
+               mode = uaq_read_2(sc, UAQ_CMD_ACCESS_MAC,
+                   UAQ_SFR_MEDIUM_STATUS_MODE, 2);
+               mode &= ~UAQ_SFR_MEDIUM_RECEIVE_EN;
+               uaq_write_2(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_MEDIUM_STATUS_MODE,
+                   2, mode);
+
+               sc->sc_rxctl &= ~UAQ_SFR_RX_CTL_START;
+               uaq_write_2(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_RX_CTL, 2,
+                   sc->sc_rxctl);
+
+               uaq_write_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_BULK_OUT_CTRL, 1,
+                   UAQ_SFR_BULK_OUT_FLUSH_EN | UAQ_SFR_BULK_OUT_EFF_EN);
+
+               uaq_write_1(sc, UAQ_CMD_ACCESS_MAC, UAQ_SFR_BULK_OUT_CTRL, 1,
+                   UAQ_SFR_BULK_OUT_EFF_EN);
+       }
+}
+
+void
+uaq_intr(struct usbd_xfer *xfer, void *priv, usbd_status status)
+{
+       struct uaq_softc        *sc = priv;
+       struct ifnet            *ifp = &sc->sc_ac.ac_if;
+       uint64_t                linkstatus;
+       uint64_t                baudrate;
+       int                     link_state;
+
+       if (status == USBD_CANCELLED)
+               return;
+
+       if (status != USBD_NORMAL_COMPLETION) {
+               DPRINTFN(2, ("uaq_intr: status=%d\n", status));
+               if (status == USBD_STALLED)
+                       usbd_clear_endpoint_stall_async(
+                           sc->sc_ep[UAQ_ENDPT_INTR]);
+               return;
+       }
+
+       linkstatus = letoh64(sc->sc_link_status);
+       DPRINTFN(1, ("uaq_intr: link status %llx\n", linkstatus));
+
+       if (linkstatus & UAQ_STATUS_LINK) {
+               link_state = LINK_STATE_FULL_DUPLEX;
+               sc->sc_link_speed = (linkstatus & UAQ_STATUS_SPEED_MASK)
+                   >> UAQ_STATUS_SPEED_SHIFT;
+               switch (sc->sc_link_speed) {
+               case UAQ_STATUS_SPEED_5G:
+                       baudrate = IF_Gbps(5);
+                       break;
+               case UAQ_STATUS_SPEED_2_5G:
+                       baudrate = IF_Mbps(2500);
+                       break;
+               case UAQ_STATUS_SPEED_1G:
+                       baudrate = IF_Gbps(1);
+                       break;
+               case UAQ_STATUS_SPEED_100M:
+                       baudrate = IF_Mbps(100);
+                       break;
+               default:
+                       baudrate = 0;
+                       break;
+               }
+
+               ifp->if_baudrate = baudrate;
+       } else {
+               link_state = LINK_STATE_DOWN;
+               sc->sc_link_speed = 0;
+       }
+
+       if (link_state != ifp->if_link_state) {
+               ifp->if_link_state = link_state;
+               if_link_state_change(ifp);
+               usb_add_task(sc->sc_udev, &sc->sc_link_task);
+       }
+}
+
+void
+uaq_rxeof(struct usbd_xfer *xfer, void *priv, usbd_status status)
+{
+       struct uaq_chain        *c = (struct uaq_chain *)priv;
+       struct uaq_softc        *sc = c->uc_sc;
+       struct ifnet            *ifp = &sc->sc_ac.ac_if;
+       uint8_t                 *buf;
+       uint64_t                *pdesc;
+       uint64_t                desc;
+       uint32_t                total_len;
+       struct mbuf_list        ml = MBUF_LIST_INITIALIZER();
+       struct mbuf             *m;
+       int                     pktlen, s;
+       int                     count, offset;
+       
+       if (usbd_is_dying(sc->sc_udev))
+               return;
+
+       if (!(ifp->if_flags & IFF_RUNNING))
+               return;
+
+       if (status != USBD_NORMAL_COMPLETION) {
+               if (status == USBD_NOT_STARTED || status == USBD_CANCELLED)
+                       return;
+               if (usbd_ratecheck(&sc->sc_rx_notice)) {
+                       printf("%s: usb errors on rx: %s\n",
+                               sc->sc_dev.dv_xname, usbd_errstr(status));
+               }
+               if (status == USBD_STALLED)
+                       usbd_clear_endpoint_stall_async(
+                           sc->sc_ep[UAQ_ENDPT_RX]);
+               goto done;
+       }
+
+       usbd_get_xfer_status(xfer, NULL, (void **)&buf, &total_len, NULL);
+       DPRINTFN(3, ("received %d bytes\n", total_len));
+       if ((total_len & 7) != 0) {
+               printf("%s: weird rx transfer length %d\n",
+                   sc->sc_dev.dv_xname, total_len);
+               goto done;
+       }
+       
+       pdesc = (uint64_t *)(buf + (total_len - sizeof(desc)));
+       desc = lemtoh64(pdesc);
+       
+       count = desc & UAQ_RX_HDR_COUNT_MASK;
+       if (count == 0)
+               goto done;
+
+       /* get offset of packet headers */
+       offset = total_len - ((count + 1) * sizeof(desc));
+       if (offset != ((desc & UAQ_RX_HDR_OFFSET_MASK) >>
+           UAQ_RX_HDR_OFFSET_SHIFT)) {
+               printf("%s: offset mismatch, got %d expected %lld\n",
+                   sc->sc_dev.dv_xname, offset,
+                   desc >> UAQ_RX_HDR_OFFSET_SHIFT);
+               goto done;
+       }
+       if (offset < 0 || offset > total_len) {
+               printf("%s: offset %d outside buffer (%d)\n",
+                   sc->sc_dev.dv_xname, offset, total_len);
+               goto done;
+       }
+
+       pdesc = (uint64_t *)(buf + offset);
+       total_len = offset;
+
+       while (count-- > 0) {
+               desc = lemtoh64(pdesc);
+               pdesc++;
+
+               pktlen = (desc & UAQ_RX_PKT_LEN_MASK) >> UAQ_RX_PKT_LEN_SHIFT;
+               if (pktlen > total_len) {
+                       DPRINTFN(2, ("not enough bytes for this packet\n"));
+                       ifp->if_ierrors++;
+                       goto done;
+               }
+               
+               m = m_devget(buf + 2, pktlen - 2, ETHER_ALIGN);
+               if (m == NULL) {
+                       DPRINTFN(2, ("m_devget failed for this packet\n"));
+                       ifp->if_ierrors++;
+                       goto done;
+               }
+
+               if ((desc & UAQ_RX_PKT_L3_ERR) == 0)
+                       m->m_pkthdr.csum_flags |= M_IPV4_CSUM_IN_OK;
+
+               if ((desc & UAQ_RX_PKT_L4_ERR) == 0)
+                       m->m_pkthdr.csum_flags |= M_TCP_CSUM_IN_OK |
+                           M_UDP_CSUM_IN_OK;
+
+#if NVLAN > 0
+               if (desc & UAQ_RX_PKT_VLAN) {
+                       m->m_pkthdr.ether_vtag = (desc >> UAQ_RX_PKT_VLAN_SHIFT) &
+                           0xfff;
+                       m->m_flags |= M_VLANTAG;
+               }
+#endif
+               ml_enqueue(&ml, m);
+
+               total_len -= roundup(pktlen, UAQ_RX_BUF_ALIGN);
+               buf += roundup(pktlen, UAQ_RX_BUF_ALIGN);
+       }
+
+done:
+       s = splnet();
+       if_input(ifp, &ml);
+       splx(s);
+       memset(c->uc_buf, 0, UAQ_RX_BUFSZ);
+
+       usbd_setup_xfer(xfer, sc->sc_ep[UAQ_ENDPT_RX], c, c->uc_buf,
+           UAQ_RX_BUFSZ, USBD_SHORT_XFER_OK | USBD_NO_COPY,
+           USBD_NO_TIMEOUT, uaq_rxeof);
+       usbd_transfer(xfer);
+}
+
+
+void
+uaq_watchdog(struct ifnet *ifp)
+{
+       struct uaq_softc        *sc = ifp->if_softc;
+       struct uaq_chain        *c;
+       usbd_status             err;
+       int                     i, s;
+
+       ifp->if_timer = 0;
+
+       if (usbd_is_dying(sc->sc_udev))
+               return;
+
+       if ((ifp->if_flags & (IFF_RUNNING|IFF_UP)) != (IFF_RUNNING|IFF_UP))
+               return;
+
+       sc = ifp->if_softc;
+       s = splnet();
+
+       ifp->if_oerrors++;
+       DPRINTF(("%s: watchdog timeout\n", sc->sc_dev.dv_xname));
+
+       for (i = 0; i < UAQ_TX_LIST_CNT; i++) {
+               c = &sc->sc_cdata.uaq_tx_chain[i];
+               if (c->uc_cnt > 0) {
+                       usbd_get_xfer_status(c->uc_xfer, NULL, NULL, NULL,
+                           &err);
+                       uaq_txeof(c->uc_xfer, c, err);
+               }
+       }
+
+       if (ifq_is_oactive(&ifp->if_snd))
+               ifq_restart(&ifp->if_snd);
+       splx(s);
+}
+
+void
+uaq_txeof(struct usbd_xfer *xfer, void *priv, usbd_status status)
+{
+       struct uaq_softc        *sc;
+       struct uaq_chain        *c;
+       struct ifnet            *ifp;
+       int                     s;
+
+       c = priv;
+       sc = c->uc_sc;
+       ifp = &sc->sc_ac.ac_if;
+
+       if (usbd_is_dying(sc->sc_udev))
+               return;
+
+       if (status != USBD_NORMAL_COMPLETION)
+               DPRINTF(("%s: %s uc_idx=%u : %s\n", sc->sc_dev.dv_xname,
+                       __func__, c->uc_idx, usbd_errstr(status)));
+       else
+               DPRINTF(("%s: txeof\n", sc->sc_dev.dv_xname));
+
+       s = splnet();
+
+       c->uc_cnt = 0;
+       c->uc_buflen = 0;
+
+       SLIST_INSERT_HEAD(&sc->sc_cdata.uaq_tx_free, c, uc_list);
+
+       if (status != USBD_NORMAL_COMPLETION) {
+               if (status == USBD_NOT_STARTED || status == USBD_CANCELLED) {
+                       splx(s);
+                       return;
+               }
+
+               ifp->if_oerrors++;
+               printf("%s: usb error on tx: %s\n", sc->sc_dev.dv_xname,
+                   usbd_errstr(status));
+
+               if (status == USBD_STALLED)
+                       usbd_clear_endpoint_stall_async(
+                           sc->sc_ep[UAQ_ENDPT_TX]);
+               splx(s);
+               return;
+       }
+
+       ifp->if_timer = 0;
+       if (ifq_is_oactive(&ifp->if_snd))
+               ifq_restart(&ifp->if_snd);
+       splx(s);
+}
+
+void
+uaq_start(struct ifnet *ifp)
+{
+       struct uaq_softc        *sc = ifp->if_softc;
+       struct uaq_cdata        *cd = &sc->sc_cdata;
+       struct uaq_chain        *c;
+       struct mbuf             *m = NULL;
+       int                     s, mlen;
+
+       if ((sc->sc_link_speed == 0) ||
+               (ifp->if_flags & (IFF_RUNNING|IFF_UP)) !=
+                   (IFF_RUNNING|IFF_UP)) {
+               return;
+       }
+
+       s = splnet();
+
+       c = SLIST_FIRST(&cd->uaq_tx_free);
+       while (c != NULL) {
+               m = ifq_deq_begin(&ifp->if_snd);
+               if (m == NULL)
+                       break;
+
+               mlen = m->m_pkthdr.len;
+
+               /* Discard packet larger than buffer. */
+               if (mlen + sizeof(uint64_t) >= c->uc_bufmax) {
+                       ifq_deq_commit(&ifp->if_snd, m);
+                       m_freem(m);
+                       ifp->if_oerrors++;
+                       continue;
+               }
+
+               /* Append packet to current buffer. */
+               mlen = uaq_encap_txpkt(sc, m, c->uc_buf + c->uc_buflen,
+                   c->uc_bufmax - c->uc_buflen);
+               if (mlen <= 0) {
+                       ifq_deq_rollback(&ifp->if_snd, m);
+                       break;
+               }
+
+               ifq_deq_commit(&ifp->if_snd, m);
+               c->uc_cnt += 1;
+               c->uc_buflen += mlen;
+
+#if NBPFILTER > 0
+               if (ifp->if_bpf)
+                       bpf_mtap(ifp->if_bpf, m, BPF_DIRECTION_OUT);
+#endif
+
+               m_freem(m);
+       }
+
+       if (c != NULL) {
+               /* Send current buffer unless empty */
+               if (c->uc_buflen > 0 && c->uc_cnt > 0) {
+                       SLIST_REMOVE_HEAD(&cd->uaq_tx_free, uc_list);
+                       if (uaq_encap_xfer(sc, c)) {
+                               SLIST_INSERT_HEAD(&cd->uaq_tx_free, c,
+                                   uc_list);
+                       }
+                       c = SLIST_FIRST(&cd->uaq_tx_free);
+
+                       ifp->if_timer = 5;
+                       if (c == NULL)
+                               ifq_set_oactive(&ifp->if_snd);
+               }
+       }
+
+       splx(s);
+}
+
+int
+uaq_encap_txpkt(struct uaq_softc *sc, struct mbuf *m, char *buf,
+    uint32_t maxlen)
+{
+       uint64_t                desc;
+       int                     padded;
+
+       desc = m->m_pkthdr.len;
+       padded = roundup(m->m_pkthdr.len, UAQ_TX_BUF_ALIGN);
+       if (((padded + sizeof(desc)) % sc->sc_out_frame_size) == 0) {
+               desc |= UAQ_TX_PKT_DROP_PADD;
+               padded += 8;
+       }
+
+       if (padded + sizeof(desc) > maxlen)
+               return (-1);
+
+#if NVLAN > 0
+       if (m->m_flags & M_VLANTAG)
+               desc |= (((uint64_t)m->m_pkthdr.ether_vtag) <<
+                   UAQ_TX_PKT_VLAN_SHIFT) | UAQ_TX_PKT_VLAN;
+#endif
+
+       htolem64((uint64_t *)buf, desc);
+       m_copydata(m, 0, m->m_pkthdr.len, buf + sizeof(desc));
+       return (padded + sizeof(desc));
+}
+
+int
+uaq_encap_xfer(struct uaq_softc *sc, struct uaq_chain *c)
+{
+       usbd_status     err;
+
+       usbd_setup_xfer(c->uc_xfer, sc->sc_ep[UAQ_ENDPT_TX], c, c->uc_buf,
+           c->uc_buflen, USBD_FORCE_SHORT_XFER | USBD_NO_COPY, 10000,
+           uaq_txeof);
+
+       err = usbd_transfer(c->uc_xfer);
+       if (err != USBD_IN_PROGRESS) {
+               c->uc_cnt = 0;
+               c->uc_buflen = 0;
+               uaq_stop(sc);
+               return (EIO);
+       }
+
+       return (0);
+}