Greg KH wrote:

On Fri, May 30, 2003 at 09:56:27PM -0400, David T Hollis wrote:


This version has taken some of the suggestions from Oliver Neukum and David Brownell to make this driver a bit more robust. With some trials, I am able to run full ttcp tests without error, transfer very large files and I am not getting any transmit timeouts.



Care to post a patch? I'd be glad to add this to the main kernel trees


thanks,

greg k-h


-------------------------------------------------------
This SF.net email is sponsored by: eBay
Get office equipment for less on eBay!
http://adfarm.mediaplex.com/ad/ck/711-11697-6916-5
_______________________________________________
[EMAIL PROTECTED]
To unsubscribe, use the last form field at:
https://lists.sourceforge.net/lists/listinfo/linux-usb-devel


Here you are. Would be happy to see it included mainline.
--- Documentation/Configure.help.orig   2003-05-31 18:54:24.000000000 -0400
+++ Documentation/Configure.help        2003-05-31 18:57:13.000000000 -0400
@@ -14548,6 +14548,24 @@
   The module will be called catc.o. If you want to compile it as a
   module, say M here and read <file:Documentation/modules.txt>.
 
+USB ASIX AX88172 based ethernet device support
+CONFIG_USB_AX8817X
+  Say Y if you want to use one of the following 10/100 USB2 Ethernet
+  devices based on the ASIX AX88172 chip.  Supported devices are:
+    Linksys USB200M
+    Netgear FA120
+    D-Link DUB-E100
+    Hawking UF200
+
+  This driver makes the adapter appear as a normal Ethernet interface,
+  typically on eth0, if it is the only ethernet device, or perhaps on
+  eth1, if you have a PCI or ISA ethernet card installed.
+
+  This code is also available as a module ( = code which can be
+  inserted in and removed from the running kernel whenever you want).
+  The module will be called catc.o. If you want to compile it as a
+  module, say M here and read <file:Documentation/modules.txt>.
+
 USB Kodak DC-2xx Camera support
 CONFIG_USB_DC2XX
   Say Y here if you want to connect this type of still camera to your
--- drivers/usb/Config.in.orig  2003-05-31 18:51:34.000000000 -0400
+++ drivers/usb/Config.in       2003-05-31 18:52:47.000000000 -0400
@@ -93,6 +93,7 @@
       dep_tristate '  USB Realtek RTL8150 based ethernet device support 
(EXPERIMENTAL)' CONFIG_USB_RTL8150 $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL
       dep_tristate '  USB KLSI KL5USB101-based ethernet device support 
(EXPERIMENTAL)' CONFIG_USB_KAWETH $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL
       dep_tristate '  USB CATC NetMate-based Ethernet device support (EXPERIMENTAL)' 
CONFIG_USB_CATC $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL
+      dep_tristate '  USB ASIX AX88172 based ethernet device support (EXPERIMENTAL)' 
CONFIG_USB_AX8817X $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL
       dep_tristate '  USB Communication Class Ethernet device support (EXPERIMENTAL)' 
CONFIG_USB_CDCETHER $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL
       dep_tristate '  USB-to-USB Networking cable device support (EXPERIMENTAL)' 
CONFIG_USB_USBNET $CONFIG_USB $CONFIG_NET $CONFIG_EXPERIMENTAL
    fi
--- drivers/usb/Makefile.orig   2003-05-31 18:52:51.000000000 -0400
+++ drivers/usb/Makefile        2003-05-31 18:53:09.000000000 -0400
@@ -85,6 +85,7 @@
 obj-$(CONFIG_USB_RTL8150)      += rtl8150.o
 obj-$(CONFIG_USB_CATC)         += catc.o
 obj-$(CONFIG_USB_KAWETH)        += kaweth.o
+obj-$(CONFIG_USB_AX8817X)      += ax8817x.o
 obj-$(CONFIG_USB_CDCETHER)     += CDCEther.o
 obj-$(CONFIG_USB_RIO500)       += rio500.o
 obj-$(CONFIG_USB_TIGL)          += tiglusb.o
--- drivers/usb/ax8817x.c.orig  2003-05-31 18:58:42.000000000 -0400
+++ drivers/usb/ax8817x.c       2003-05-31 18:53:22.000000000 -0400
@@ -0,0 +1,1415 @@
+/*
+ * ASIX AX8817x USB 2.0 10/100/HomePNA Ethernet controller driver
+ *
+ * Copyright (c) 2002-2003 TiVo Inc.
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * History 
+ *
+ *     2003-05-31 - Dave Hollis <[EMAIL PROTECTED]>  0.9.8
+ *             * Don't stop/start the queue in start_xmit
+ *             * Swallow URB status upon hard removal
+ *             * Cleanup remaining comments (kill // style)
+ *
+ *     2003-05-29 - Dave Hollis <[EMAIL PROTECTED]>  0.9.7
+ *             * Set module owner
+ *             * Follow-up on suggestions from David Brownell &
+ *               Oliver Neukum which should help with robustness
+ *             * Use ether_crc from stock kernel if available
+ *
+ *     2003-05-28 - Dave Hollis <[EMAIL PROTECTED]>  0.9.6
+ *             * Added basic ethtool & mii support
+ *
+ *     2003-05-28 - Dave Hollis <[EMAIL PROTECTED]>  0.9.5
+ *             * Workout devrequest change to usb_ctrlrequest structure
+ *             * Replace FILL_BULK_URB macros to non-deprecated 
+ *               usb_fill_bulk_urb macros
+ *             * Replace printks with equivalent macros
+ *             * Use defines for module description, version, author to
+ *               simplify future changes
+ *
+ * Known Issues
+ *     usb-uhci.c: process_transfer: fixed toggle message to console
+ *             Possible bug in usb-uhci or bug in this driver that
+ *             makes it spit that out.  Doesn't seem to have harmful
+ *             effects.
+ *
+ * Todo
+ *     Port to 2.5/2.6 kernel to include NAPI support
+ *     Fix tx_timeout problems
+*/
+
+#include <linux/slab.h>
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/skbuff.h>
+#include <linux/mii.h>
+#include <asm/uaccess.h>
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,18)
+#include <linux/crc32.h>
+#else /* for now, this is swiped out of various drivers in drivers/net/... */
+static unsigned const ethernet_polynomial = 0x04c11db7U;
+static inline u32 ether_crc(int length, unsigned char *data)
+{
+       int crc = -1;
+       while (--length >= 0) {
+               unsigned char current_octet = *data++;
+               int bit;
+               for (bit = 0; bit < 8; bit++, current_octet >>= 1) {
+                       crc = (crc << 1) ^
+                               ((crc < 0) ^ (current_octet & 1) ?
+                                ethernet_polynomial : 0);
+               }
+       }
+       return crc;
+}
+#endif
+
+/* Version Information */
+#define DRIVER_VERSION "v0.9.8"
+#define DRIVER_AUTHOR "TiVo, Inc."
+#define DRIVER_DESC "ASIX AX8817x USB Ethernet driver"
+
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_LICENSE( "GPL" );
+
+
+#define AX_REQ_READ         ( USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE )
+#define AX_REQ_WRITE        ( USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE )
+
+#define AX_CMD_SET_SW_MII           0x06
+#define AX_CMD_READ_MII_REG         0x07
+#define AX_CMD_WRITE_MII_REG        0x08
+#define AX_CMD_SET_HW_MII           0x0a
+#define AX_CMD_WRITE_RX_CTL         0x10
+#define AX_CMD_WRITE_MULTI_FILTER   0x16
+#define AX_CMD_READ_NODE_ID         0x17
+#define AX_CMD_READ_PHY_ID          0x19
+#define AX_CMD_WRITE_MEDIUM_MODE    0x1b
+#define AX_CMD_WRITE_GPIOS          0x1f
+
+#define AX_RX_MAX                   ETH_FRAME_LEN
+#define AX_TIMEOUT_CMD              ( HZ / 10 )
+#define AX_TIMEOUT_TX               ( HZ * 2 )
+#define AX_MAX_MCAST                64
+
+#define AX_DRV_STATE_INITIALIZING   0x00
+#define AX_DRV_STATE_RUNNING        0x01
+#define AX_DRV_STATE_EXITING        0x02
+
+#define AX_PHY_STATE_INITIALIZING   0x00
+#define AX_PHY_STATE_NO_LINK        0x01
+#define AX_PHY_STATE_POLLING_1      0x02
+#define AX_PHY_STATE_POLLING_2      0x03
+#define AX_PHY_STATE_POLLING_3      0x04
+#define AX_PHY_STATE_POLLING_4      0x05
+#define AX_PHY_STATE_SETTING_MAC    0x06
+#define AX_PHY_STATE_LINK           0x07
+#define AX_PHY_STATE_ABORT_POLL     0x08
+#define AX_PHY_STATE_ABORTING       0x09
+
+#define AX_MAX_PHY_RETRY            50
+
+
+static int n_rx_urbs = 2;
+static int n_tx_urbs = 2;
+
+MODULE_PARM( n_rx_urbs, "i" );
+MODULE_PARM( n_tx_urbs, "i" );
+MODULE_PARM_DESC( n_rx_urbs, "Number of rx buffers to queue at once (def 2)" );
+MODULE_PARM_DESC( n_tx_urbs, "Number of tx buffers to queue at once (def 2)" );
+
+struct ax8817x_info;
+struct ax_cmd_req;
+typedef int (*ax_cmd_callback_t)(struct ax8817x_info *, struct ax_cmd_req *);
+
+struct ax_cmd_req {
+    struct list_head        list;
+    ax_cmd_callback_t       cmd_callback;
+    void *                  priv;
+    int                     status;
+    void *                  data;
+    int                     data_size;
+    int                     timeout;
+    struct usb_ctrlrequest  devreq;
+};
+
+struct ax8817x_info {
+    struct usb_device *     usb;
+    struct net_device *     net;
+    struct urb **           rx_urbs;
+    struct urb **           tx_urbs;
+    struct urb *            int_urb;
+    u8 *                    int_buf;
+    struct urb *            ctl_urb;
+    struct list_head        ctl_queue;
+    spinlock_t              ctl_lock;
+    atomic_t                rx_refill_cnt;
+    int                     tx_head;
+    int                     tx_tail;
+    spinlock_t              tx_lock;
+    struct net_device_stats stats;
+    struct ax_cmd_req       phy_req;
+    u8                      phy_id;
+    u8                      phy_state;
+    u8                      drv_state;
+};
+
+
+const struct usb_device_id ax8817x_id_table[] __devinitdata = {
+        /* Linksys USB200M */
+    { USB_DEVICE( 0x077b, 0x2226 ), driver_info: 0x00130103 },
+        /* Hawking UF200, TRENDnet TU2-ET100 */
+    { USB_DEVICE( 0x07b8, 0x420a ), driver_info: 0x001f1d1f },
+        /* NETGEAR FA120 */
+    { USB_DEVICE( 0x0846, 0x1040 ), driver_info: 0x00130103 },
+        /* D-Link DUB-E100 */
+    { USB_DEVICE( 0x2001, 0x1a00 ), driver_info: 0x009f9d9f },
+
+    { }
+};
+
+MODULE_DEVICE_TABLE( usb, ax8817x_id_table );
+
+
+static void ax_run_ctl_queue( struct ax8817x_info *, struct ax_cmd_req *, int );
+static void ax_rx_callback( struct urb *urb );
+
+static void ax_ctl_callback( struct urb *urb )
+{
+    struct ax8817x_info *ax_info = (struct ax8817x_info *)urb->context;
+
+    ax_run_ctl_queue( ax_info, NULL,
+                      urb->status ? urb->status : urb->actual_length );
+}
+
+/* 
+ * Queue a new ctl request, or dequeue the first in the list
+*/
+static void ax_run_ctl_queue( struct ax8817x_info *ax_info,
+                              struct ax_cmd_req *req, int status )
+{
+    struct ax_cmd_req *next_req = NULL;
+    struct ax_cmd_req *last_req = NULL;
+    unsigned long flags;
+
+    /* Need to lock around queue list manipulation */
+    spin_lock_irqsave( &ax_info->ctl_lock, flags );
+
+    if ( req == NULL ) {
+        last_req = list_entry( ax_info->ctl_queue.next, struct ax_cmd_req,
+                               list );
+    } else {
+        if ( list_empty( &ax_info->ctl_queue ) ) {
+            next_req = req;
+        }
+
+        req->status = -EINPROGRESS;
+        list_add_tail( &req->list, &ax_info->ctl_queue );
+    }
+
+    while ( 1 ) {
+        if ( last_req != NULL ) {
+            /* dequeue completed entry */
+            list_del( &last_req->list );
+
+            last_req->status = status;
+            if ( last_req->cmd_callback( ax_info, last_req ) ) {
+                /* requeue if told to do so */
+                last_req->status = -EINPROGRESS;
+                list_add_tail( &last_req->list, &ax_info->ctl_queue );
+            }
+
+            if ( list_empty( &ax_info->ctl_queue ) ) {
+                next_req = NULL;
+            } else {
+                next_req = list_entry( ax_info->ctl_queue.next,
+                                       struct ax_cmd_req, list );
+            }
+        }
+
+        spin_unlock_irqrestore( &ax_info->ctl_lock, flags );
+
+        if ( next_req == NULL ) {
+            break;
+        }
+
+        /* XXX: do something with timeout */
+        usb_fill_control_urb(
+                ax_info->ctl_urb, ax_info->usb,
+                next_req->devreq.bRequestType & USB_DIR_IN ?
+                    usb_rcvctrlpipe( ax_info->usb, 0 ) :
+                    usb_sndctrlpipe( ax_info->usb, 0 ),
+                (void *)&next_req->devreq, next_req->data,
+                next_req->data_size, ax_ctl_callback, ax_info
+                );
+
+        status = usb_submit_urb( ax_info->ctl_urb );
+        if ( status >= 0 ) {
+            break;
+        }
+
+        last_req = next_req;
+
+        spin_lock_irqsave( &ax_info->ctl_lock, flags );
+    }
+}
+
+static int ax_sync_cmd_callback( struct ax8817x_info *unused,
+                                 struct ax_cmd_req *req )
+{
+    wait_queue_head_t *wq = (wait_queue_head_t *)req->priv;
+
+    wake_up( wq );
+
+    return 0;
+}
+
+static int ax_async_cmd_callback( struct ax8817x_info *unused,
+                                  struct ax_cmd_req *req )
+{
+    if ( req->status < 0 ) {
+        err( "%s: Async command %d failed: %d\n", __FUNCTION__,
+                req->devreq.bRequest, req->status );
+    }
+
+    /* Nothing else to do here, just need to free the request (and its
+       allocated data) */
+    if ( req->data != NULL ) {
+        kfree( req->data );
+    }
+    kfree( req );
+
+    return 0;
+}
+
+/*
+ * This is mostly the same as usb_control_msg(), except that it is able
+ * to queue control messages
+*/
+static int ax_control_msg( struct ax8817x_info *ax_info, u8 requesttype,
+                           u8 request, u16 value, u16 index, void *data,
+                           u16 size, int timeout )
+{
+    struct ax_cmd_req *req;
+    DECLARE_WAIT_QUEUE_HEAD( wq );
+    DECLARE_WAITQUEUE( wait, current );
+    int ret;
+
+    req = kmalloc( sizeof( struct ax_cmd_req ), GFP_KERNEL );
+    if ( req == NULL ) {
+        return -ENOMEM;
+    }
+
+    req->devreq.bRequestType = requesttype;
+    req->devreq.bRequest = request;
+    req->devreq.wValue = cpu_to_le16( value );
+    req->devreq.wIndex = cpu_to_le16( index );
+    req->devreq.wLength = cpu_to_le16( size );
+    req->data = data;
+    req->data_size = size;
+    req->timeout = timeout;
+
+    req->priv = &wq;
+    set_current_state( TASK_UNINTERRUPTIBLE );
+    add_wait_queue( &wq, &wait );
+
+    req->cmd_callback = ax_sync_cmd_callback;
+
+    ax_run_ctl_queue( ax_info, req, 0 );
+    schedule();
+
+    ret = req->status;
+
+    kfree( req );
+
+    return ret;
+}
+
+/*
+ * Same, but can be used asynchronously, may fail, and returns no exit
+ * status
+*/
+static void ax_control_msg_async( struct ax8817x_info *ax_info,
+        u8 requesttype, u8 request, u16 value, u16 index, void *data,
+        u16 size, int timeout )
+{
+    struct ax_cmd_req *req;
+
+    req = kmalloc( sizeof( struct ax_cmd_req ), GFP_ATOMIC );
+    if ( req == NULL ) {
+        /* There's not much else we can do here... */
+        err( "%s: Failed alloc\n", __FUNCTION__);
+        return;
+    }
+
+    req->devreq.bRequestType = requesttype;
+    req->devreq.bRequest = request;
+    req->devreq.wValue = cpu_to_le16( value );
+    req->devreq.wIndex = cpu_to_le16( index );
+    req->devreq.wLength = cpu_to_le16( size );
+    req->data = data;
+    req->data_size = size;
+    req->timeout = timeout;
+
+    req->cmd_callback = ax_async_cmd_callback;
+
+    ax_run_ctl_queue( ax_info, req, 0 );
+}
+
+static inline int ax_read_cmd( struct ax8817x_info *ax_info, u8 cmd,
+                               u16 value, u16 index, u16 size, void *data )
+{
+    return ax_control_msg(
+            ax_info, AX_REQ_READ, cmd, value, index, data, size,
+            AX_TIMEOUT_CMD
+            );
+}
+
+static inline int ax_write_cmd( struct ax8817x_info *ax_info, u8 cmd,
+                                u16 value, u16 index, u16 size, void *data )
+{
+    return ax_control_msg(
+            ax_info, AX_REQ_WRITE, cmd, value, index, data, size,
+            AX_TIMEOUT_CMD
+            );
+}
+
+static inline void ax_write_cmd_async( struct ax8817x_info *ax_info, u8 cmd,
+                                u16 value, u16 index, u16 size, void *data )
+{
+    ax_control_msg_async(
+            ax_info, AX_REQ_WRITE, cmd, value, index, data, size,
+            AX_TIMEOUT_CMD
+            );
+}
+
+static int ax_refill_rx_urb( struct ax8817x_info *ax_info, struct urb *urb )
+{
+    struct sk_buff *skb;
+    int ret;
+
+    skb = dev_alloc_skb( AX_RX_MAX + 2 );
+    if ( skb != NULL ) {
+        skb_reserve( skb, 2 );  /* for IP header alignment */
+        skb->dev = ax_info->net;
+
+        usb_fill_bulk_urb( 
+                urb, ax_info->usb,
+                usb_rcvbulkpipe( ax_info->usb, 3 ),
+                skb->data, AX_RX_MAX, ax_rx_callback, skb
+                );
+
+        ret = usb_submit_urb( urb );
+        if ( ret < 0 ) {
+            err( "Failed submit rx URB (%d)\n", ret );
+            dev_kfree_skb_irq( skb );
+            urb->context = NULL;
+        } else {
+            ret = 0;
+        }
+    } else {
+        /* this just means we're low on memory at the moment. Try to
+           handle it gracefully. */
+        urb->context = NULL;
+        ret = 1;
+    }
+
+    return ret;
+}
+
+static int ax_phy_cmd_callback( struct ax8817x_info *ax_info,
+                                struct ax_cmd_req *req )
+{
+    int full_duplex;
+    int flow_control;
+    u16 mii_data_le;
+
+    if ( req->status < 0 ) {
+        err( "%s: Failed at state %d: %d\n", __FUNCTION__,
+                ax_info->phy_state, req->status );
+        /* Not sure what else we can do, so just bail */
+        ax_info->phy_state = AX_PHY_STATE_ABORTING;
+    }
+
+    switch ( ax_info->phy_state ) {
+        /* Now that we're in software MII mode, read the BMSR */
+        case AX_PHY_STATE_POLLING_1:
+            ax_info->phy_state = AX_PHY_STATE_POLLING_2;
+            req->devreq.bRequestType = AX_REQ_READ;
+            req->devreq.bRequest = AX_CMD_READ_MII_REG;
+            req->devreq.wValue = cpu_to_le16( ax_info->phy_id );
+            req->devreq.wIndex = cpu_to_le16( MII_BMSR );
+            req->devreq.wLength = cpu_to_le16( 2 );
+            req->data_size = 2;
+            (long)req->priv = 0;    /* This is the retry count */
+            return 1;
+
+        /* Done reading BMSR */
+        case AX_PHY_STATE_POLLING_2:
+            mii_data_le = *(u16*)req->data;
+            if ( (mii_data_le & cpu_to_le16( BMSR_LSTATUS | BMSR_ANEGCAPABLE ))
+                 == cpu_to_le16( BMSR_LSTATUS | BMSR_ANEGCAPABLE ) ) {
+                if ( mii_data_le & cpu_to_le16( BMSR_ANEGCOMPLETE ) ) {
+                    /* Autonegotiation done, go on to read LPA */
+                    ax_info->phy_state = AX_PHY_STATE_POLLING_3;
+                    req->devreq.wIndex = cpu_to_le16( MII_LPA );
+                    return 1;
+                } else if ( (long)req->priv++ < AX_MAX_PHY_RETRY ) {
+                    /* Reread BMSR if it's still autonegotiating. This is
+                       probably unnecessary logic, I've never seen it take
+                       more than 1 try... */
+                    return 1;
+                }
+                /* else fall through to abort */
+            }
+            /* XXX: should probably handle auto-neg failure better,
+               by reverting to manual setting of something safe. (?) */
+
+            ax_info->phy_state = AX_PHY_STATE_ABORT_POLL;
+            /* and then fall through to set hw MII */
+
+        /* Got what we needed from PHY, set back to hardware MII mode
+           (Do same for abort in mid-poll) */
+        case AX_PHY_STATE_POLLING_3:
+        case AX_PHY_STATE_ABORT_POLL:
+            ax_info->phy_state += 1;
+            req->devreq.bRequestType = AX_REQ_WRITE;
+            req->devreq.bRequest = AX_CMD_SET_HW_MII;
+            req->devreq.wValue = cpu_to_le16( 0 );
+            req->devreq.wIndex = cpu_to_le16( 0 );
+            req->devreq.wLength = cpu_to_le16( 0 );
+            req->data_size = 0;
+            return 1;
+
+        /* The end result, set the right duplex and flow control mode in the
+           MAC (based on the PHY's LPA reg, which should still be in the data
+           buffer) */
+        case AX_PHY_STATE_POLLING_4:
+            mii_data_le = *(u16*)req->data;
+            ax_info->phy_state = AX_PHY_STATE_SETTING_MAC;
+            req->devreq.bRequest = AX_CMD_WRITE_MEDIUM_MODE;
+            full_duplex = mii_data_le & cpu_to_le16( LPA_DUPLEX );
+            flow_control = full_duplex &&
+                           ( mii_data_le & cpu_to_le16( 0x0400 ) );
+            req->devreq.wValue = cpu_to_le16( 0x04 ) |
+                ( full_duplex ? cpu_to_le16( 0x02 ) : 0 ) |
+                ( flow_control ? cpu_to_le16( 0x10 ) : 0 );
+            info( "%s: Link established, %s duplex, flow control %sabled\n",
+                  ax_info->net->name,
+                  full_duplex ? "full" : "half",
+                  flow_control ? "en" : "dis" );
+            return 1;
+
+        /* All done */
+        case AX_PHY_STATE_SETTING_MAC:
+            ax_info->phy_state = AX_PHY_STATE_LINK;
+            netif_carrier_on( ax_info->net );
+            return 0;
+
+        default:
+            err( "%s: Unknown state %d\n", __FUNCTION__,
+                    ax_info->phy_state );
+            /* fall through */
+        case AX_PHY_STATE_ABORTING:
+            ax_info->phy_state = AX_PHY_STATE_NO_LINK;
+            return 0;
+    }
+}
+
+static void ax_int_callback( struct urb *urb )
+{
+    struct ax8817x_info *ax_info = (struct ax8817x_info *)urb->context;
+    u8 phy_link;
+
+    if ( ax_info->drv_state == AX_DRV_STATE_EXITING ||
+         urb->actual_length < 3 ) {
+        return;
+    }
+
+    /* Ignore the first PHY link report, it will sometimes be reported as
+       link active, even though we just told the PHY to reset. If it
+       really has link, we'll pick it up next int callback.
+    */
+    if ( ax_info->phy_state == AX_PHY_STATE_INITIALIZING ) {
+        netif_carrier_off( ax_info->net );
+        ax_info->phy_state = AX_PHY_STATE_NO_LINK;
+        return;
+    }
+
+    /* Assume we're only interested in the primary PHY for now. */
+    phy_link = ax_info->int_buf[2] & 1;
+
+    if ( phy_link == ( ax_info->phy_state == AX_PHY_STATE_NO_LINK ) ? 0 : 1 ) {
+        /* Common case, no change */
+        return;
+    }
+
+    if ( phy_link == 0 ) {
+        netif_carrier_off( ax_info->net );
+        /* Abort an in-progress poll of the PHY if necessary */
+        switch ( ax_info->phy_state ) {
+            case AX_PHY_STATE_POLLING_1:
+            case AX_PHY_STATE_POLLING_2:
+            case AX_PHY_STATE_POLLING_3:
+                ax_info->phy_state = AX_PHY_STATE_ABORT_POLL;
+                break;
+
+            case AX_PHY_STATE_POLLING_4:
+            case AX_PHY_STATE_SETTING_MAC:
+                ax_info->phy_state = AX_PHY_STATE_ABORTING;
+                break;
+
+            case AX_PHY_STATE_LINK:
+                ax_info->phy_state = AX_PHY_STATE_NO_LINK;
+                break;
+
+            default:
+                /* If we're already aborting, continue aborting */
+                break;
+        }
+    } else {
+        /* Note that we only fall into this case if previous phy_state was
+           AX_PHY_STATE_NO_LINK. When the link is reported active while
+           we're still polling, or when we're aborting, the logic above
+           will just return, and we'll check again next int callback. */
+
+        ax_info->phy_state = AX_PHY_STATE_POLLING_1;
+        ax_info->phy_req.devreq.bRequestType = AX_REQ_WRITE;
+        ax_info->phy_req.devreq.bRequest = AX_CMD_SET_SW_MII;
+        ax_info->phy_req.devreq.wValue = cpu_to_le16( 0 );
+        ax_info->phy_req.devreq.wIndex = cpu_to_le16( 0 );
+        ax_info->phy_req.devreq.wLength = cpu_to_le16( 0 );
+        ax_info->phy_req.data_size = 0;
+        ax_info->phy_req.timeout = AX_TIMEOUT_CMD;
+        ax_info->phy_req.cmd_callback = ax_phy_cmd_callback;
+
+        ax_run_ctl_queue( ax_info, &ax_info->phy_req, 0 );
+    }
+}
+
+static void ax_rx_callback( struct urb *urb )
+{
+    struct sk_buff *skb = (struct sk_buff *)urb->context;
+    struct net_device *net = skb->dev;
+    struct ax8817x_info *ax_info = (struct ax8817x_info *)net->priv;
+    int ret, len, refill;
+
+    switch ( urb->status ) {
+        case 0:
+            break;
+
+        default:
+            err( "%s: URB status %d\n", __FUNCTION__, urb->status );
+            /* It's not clear that we can do much in this case, the rx pipe
+               doesn't ever seem to stall, so if we got -ETIMEDOUT, that
+               usually means the device was unplugged, and we just haven't
+               noticed yet.
+               Just fall through and free skb without resubmitting urb. */
+        case -ENOENT:          /* */
+        case -ECONNRESET:      /* Async unlink */
+       case -ESHUTDOWN:        /* Hardware gone */
+       case -EILSEQ:           /* Get this when you yank it out */
+            dev_kfree_skb_any( skb );
+            urb->context = NULL;
+            return;
+    }
+
+    if ( ax_info->drv_state == AX_DRV_STATE_INITIALIZING ) {
+        /* Not really expecting this to ever happen, since we haven't yet
+           enabled receive in the rx_ctl register, but ya never know... */
+        goto refill_same;
+    } else if ( ax_info->drv_state == AX_DRV_STATE_EXITING ) {
+        dev_kfree_skb_any( skb );
+        urb->context = NULL;
+        return;
+    }
+
+    len = urb->actual_length;
+    if ( len == 0 ) {
+        /* this shouldn't happen... */
+        goto refill_same;
+    }
+
+    refill = ax_refill_rx_urb( ax_info, urb );
+
+    if ( refill == 0 || atomic_read( &ax_info->rx_refill_cnt ) < n_rx_urbs ) {
+        /* Send the receive buffer up the network stack */
+        skb_put( skb, len );
+        skb->protocol = eth_type_trans( skb, net );
+        net->last_rx = jiffies;
+        ax_info->stats.rx_packets++;
+        ax_info->stats.rx_bytes += len;
+
+        netif_rx( skb );
+
+        if ( refill == 0 ) {
+            int i;
+
+            /* This is the common case. This URB got refilled OK, and
+               no other URBs need to be refilled. */
+            if ( atomic_read( &ax_info->rx_refill_cnt ) == 0 ) {
+                return;
+            }
+
+            for ( i = 0; i < n_rx_urbs; i++ ) {
+                struct urb *urb = ax_info->rx_urbs[ i ];
+
+                if ( urb->context == NULL ) {
+                    if ( ax_refill_rx_urb( ax_info, urb ) == 0 ) {
+                        atomic_dec( &ax_info->rx_refill_cnt );
+                    } else {
+                        break;
+                    }
+                }
+            }
+        } else {
+            /* remember to refill this one later */
+            atomic_inc( &ax_info->rx_refill_cnt );
+        }
+
+        return;
+    } else {
+        ax_info->stats.rx_dropped++;
+        if ( refill < 0 ) {
+            /* the error code was already printk'ed in ax_refill_rx_urb()
+               so just note the consequences here: */
+            warn( "Halting rx due to error\n" );
+            return;
+        }
+
+        /* fall through to resubmit this URB with the existing skb
+           will try to reallocate skb's on next rx callback */
+    }
+
+refill_same:
+    usb_fill_bulk_urb( 
+            urb, ax_info->usb, usb_rcvbulkpipe( ax_info->usb, 3 ),
+            skb->data, AX_RX_MAX, ax_rx_callback, skb
+            );
+
+    ret = usb_submit_urb( urb );
+    if ( ret < 0 ) {
+        err( "Failed submit rx URB (%d)\n", ret );
+    }
+}
+
+static void ax_tx_callback( struct urb *urb )
+{
+    struct sk_buff *skb = (struct sk_buff *)urb->context;
+    struct net_device *net = skb->dev;
+    struct ax8817x_info *ax_info = (struct ax8817x_info *)net->priv;
+    unsigned long flags;
+    int head;
+
+    switch ( urb->status ) {
+        case 0:
+            ax_info->stats.tx_packets++;
+            ax_info->stats.tx_bytes += skb->len;
+            break;
+
+        case -ENOENT:       /* these 2 are the result of unlink */
+        case -ECONNRESET:   
+            break;
+
+        default:
+            err( "%s: URB %p status %d\n", __FUNCTION__,
+                    urb, urb->status );
+            ax_info->stats.tx_errors++;
+            /* XXX: more specfic errors? */
+            break;
+    }
+
+    dev_kfree_skb_any( skb );
+
+    spin_lock_irqsave( &ax_info->tx_lock, flags );
+
+    head = ax_info->tx_head;
+
+    /* XXX: This was originally just a debug print, but we may need it
+       anyway to deal with potential race from timeout handler. Need to
+       think about this one a bit more... */
+    if ( urb != ax_info->tx_urbs[ head ] ) {
+        warn( "%s: URB head mismatch\n", __FUNCTION__);
+        goto out_unlock;
+    }
+
+    if ( head == ax_info->tx_tail &&
+        ax_info->drv_state != AX_DRV_STATE_EXITING ) {
+        netif_wake_queue( net );
+    }
+
+    if ( ++head >= n_tx_urbs ) {
+        head = 0;
+    }
+
+    ax_info->tx_head = head;
+
+out_unlock:
+    spin_unlock_irqrestore( &ax_info->tx_lock, flags );
+}
+
+
+static int ax8817x_open( struct net_device *net )
+{
+    struct ax8817x_info *ax_info = (struct ax8817x_info *)net->priv;
+    u8 buf[4];
+    int i, ret;
+
+    ret = ax_write_cmd( ax_info, AX_CMD_WRITE_RX_CTL, 0x80, 0, 0, buf );
+    if ( ret < 0 ) {
+        return ret;
+    }
+
+    ret = 0;
+
+    for ( i = 0; i < n_tx_urbs; i++ ) {
+        struct urb *urb = ax_info->tx_urbs[ i ];
+
+        if ( urb == NULL ) {
+            urb = ax_info->tx_urbs[ i ] = usb_alloc_urb( 0 );
+            if ( urb == NULL ) {
+                ret = -ENOMEM;
+                break;
+            }
+            if ( n_tx_urbs > 1 ) {
+                urb->transfer_flags |= USB_QUEUE_BULK;
+            }
+        }
+    }
+
+    atomic_set( &ax_info->rx_refill_cnt, 0 );
+
+    for ( i = 0; i < n_rx_urbs && ret == 0; i++ ) {
+        struct urb *urb = ax_info->rx_urbs[ i ];
+
+        if ( urb == NULL ) {
+            urb = ax_info->rx_urbs[ i ] = usb_alloc_urb( 0 );
+            if ( urb == NULL ) {
+                ret = -ENOMEM;
+                break;
+            }
+            if ( n_rx_urbs > 1 ) {
+                urb->transfer_flags |= USB_QUEUE_BULK;
+            }
+        }
+        ret = ax_refill_rx_urb( ax_info, urb );
+        if ( ret == 1 ) {
+            atomic_inc( &ax_info->rx_refill_cnt );
+            ret = 0;
+        }
+    }
+
+    /* XXX: should handle the case where we couldn't allocate any skb's
+       better. They get allocated with GFP_ATOMIC, so they may all fail... */
+    if ( ret == 0 && atomic_read( &ax_info->rx_refill_cnt ) < n_rx_urbs ) {
+        netif_start_queue( net );
+    } else {
+        /* Error: clean up anything we allocated and bail. */
+        for ( i = 0; i < n_tx_urbs; i++ ) {
+            struct urb *urb = ax_info->tx_urbs[ i ];
+
+            if ( urb != NULL ) {
+                usb_free_urb( urb );
+                ax_info->tx_urbs[ i ] = NULL;
+            }
+        }
+
+        for ( i = 0; i < n_rx_urbs; i++ ) {
+            struct urb *urb = ax_info->rx_urbs[ i ];
+
+            if ( urb != NULL ) {
+                /* skb gets freed in the URB callback */
+                usb_unlink_urb( urb );
+                usb_free_urb( urb );
+            }
+        }
+
+        err( "%s: Failed start rx queue (%d)\n", __FUNCTION__, ret );
+    }
+    return ret;
+}
+
+static int ax8817x_stop( struct net_device *net )
+{
+    struct ax8817x_info *ax_info = (struct ax8817x_info *)net->priv;
+    u8 buf[4];
+    int i, ret;
+
+    netif_stop_queue( net );
+
+    ret = ax_write_cmd( ax_info, AX_CMD_WRITE_RX_CTL, 0x80, 0, 0, buf );
+    if ( ret < 0 && ax_info->drv_state != AX_DRV_STATE_EXITING ) {
+        err( "%s: Failed cmd (%d)\n", __FUNCTION__, ret );
+    }
+
+    for ( i = 0; i < n_tx_urbs; i++ ) {
+        struct urb *urb = ax_info->tx_urbs[ i ];
+
+        if ( urb != NULL ) {
+            usb_unlink_urb( urb );
+            usb_free_urb( urb );
+            ax_info->tx_urbs[ i ] = NULL;
+        }
+    }
+
+    for ( i = 0; i < n_rx_urbs; i++ ) {
+        struct urb *urb = ax_info->rx_urbs[ i ];
+        if ( urb != NULL ) {
+            /* skb gets freed in the URB callback */
+            usb_unlink_urb( urb );
+            usb_free_urb( urb );
+            ax_info->rx_urbs[ i ] = NULL;
+        }
+    }
+
+    return 0;
+}
+
+static int ax8817x_start_xmit( struct sk_buff *skb, struct net_device *net )
+{
+    struct ax8817x_info *ax_info = (struct ax8817x_info *)net->priv;
+    int ret, tail;
+    unsigned long flags;
+    struct urb *urb;
+
+    spin_lock_irqsave( &ax_info->tx_lock, flags );
+
+    tail = ax_info->tx_tail;
+    urb = ax_info->tx_urbs[ tail ];
+
+    if ( ++tail >= n_tx_urbs ) {
+        tail = 0;
+    }
+    ax_info->tx_tail = tail;
+
+#if 0 /* stopping the queues not needed any more */
+    /* Note that it's important to inc the tail counter and stop
+       the tx queue if necessary _before_ submitting the URB. */
+    if ( tail == ax_info->tx_head ) {
+        netif_stop_queue( net );
+    }
+#endif
+
+    spin_unlock_irqrestore( &ax_info->tx_lock, flags );
+
+    /* Since the total packet length does not get sent up the TX
+       pipe anywhere, the AX8817x chip appears to rely on a non-64
+       byte USB data packet (non-512 for USB 2.0) to mark the end of
+       the Ethernet packet. */
+    urb->transfer_flags |= USB_ZERO_PACKET;
+
+    usb_fill_bulk_urb(
+            urb, ax_info->usb, usb_sndbulkpipe( ax_info->usb, 2 ),
+            skb->data, skb->len, ax_tx_callback, skb
+            );
+
+    ret = usb_submit_urb( urb );
+    if ( ret >= 0 ) {
+        net->trans_start = jiffies;
+    } else {
+        err( "Failed submit tx URB (%d)\n", ret );
+
+        ax_info->stats.tx_errors++;
+        ax_info->stats.tx_aborted_errors++;
+
+        /* Need to undo what we did above to the tail counter
+           and tx queue. This could be avoided by holding the
+           lock around submitting the URB, but this is not the
+           common case, we don't expect the submit to fail. */
+        spin_lock_irqsave( &ax_info->tx_lock, flags );
+
+#if 0
+        if ( tail == ax_info->tx_head ) {
+            netif_wake_queue( net );
+        }
+#endif
+
+        if ( --tail < 0 ) {
+            tail = n_tx_urbs - 1;
+            tail = 0;
+        }
+        ax_info->tx_tail = tail;
+
+        spin_unlock_irqrestore( &ax_info->tx_lock, flags );
+        /* Free the skb, we're dropping it on the floor */
+        dev_kfree_skb_any( skb );
+    }
+
+    return NET_XMIT_SUCCESS;
+}
+
+/*
+ * Note that this is inherently race-prone, but the case where a tx URB
+ * completes out from underneath us just as the timeout happens will
+ * probably never happen, since the timeout is large enough such that if
+ * the tx URB hasn't completed by then it probably never will.  Also,
+ * the worst that will happen is that we cancel the wrong tx packet.
+*/
+static void ax8817x_tx_timeout( struct net_device *net )
+{
+    struct ax8817x_info *ax_info = (struct ax8817x_info *)net->priv;
+    struct urb *urb;
+
+    if ( ax_info == NULL || ax_info->drv_state == AX_DRV_STATE_EXITING ) {
+        return;
+    }
+
+    /* XXX: hmmm... probably do need to make sure we don't unlink
+       the wrong URB. tx_head gets updated in the tx callback. */
+    urb = ax_info->tx_urbs[ ax_info->tx_head ];
+
+    err( "%s: Transmit timeout, URB %p status %d\n", net->name,
+            urb, urb->status );
+
+    urb->transfer_flags |= USB_ASYNC_UNLINK;
+    usb_unlink_urb( urb );
+
+    /* XXX: The reality is that if we ever really do get a real TX URB
+       timeout (and not just a driver queue maintenance bug), we've got
+       bigger problems, and will probably need to cancel oll the
+       outstanding TX URBs, and issue an async clear_halt, or reset the
+       port, or something. */
+}
+
+static struct net_device_stats *ax8817x_stats( struct net_device *net )
+{
+    struct ax8817x_info *ax_info = (struct ax8817x_info *)net->priv;
+
+    return &ax_info->stats;
+}
+
+static void ax8817x_set_multicast( struct net_device *net )
+{
+    struct ax8817x_info *ax_info = (struct ax8817x_info *)net->priv;
+    u8 rx_ctl = 0x8c;
+
+    if ( net->flags & IFF_PROMISC ) {
+        rx_ctl |= 0x01;
+    } else if ( net->flags & IFF_ALLMULTI || net->mc_count > AX_MAX_MCAST ) {
+        rx_ctl |= 0x02;
+    } else if ( net->mc_count == 0 ) {
+        /* just broadcast and directed */
+    } else {
+        struct dev_mc_list *mc_list = net->mc_list;
+        u8* multi_filter;
+        u32 crc_bits;
+        int i;
+
+        multi_filter = kmalloc( 8, GFP_ATOMIC);
+        if ( multi_filter == NULL ) {
+            /* Oops, couldn't allocate a DMA buffer for setting the multicast
+               filter. Try all multi mode, although the ax_write_cmd_async
+               will almost certainly fail, too... (but it will printk). */
+            rx_ctl |= 0x02;
+        } else {
+            memset( multi_filter, 0, 8 );
+
+            /* Build the multicast hash filter. */
+            for ( i = 0; i < net->mc_count; i++ ) {
+                crc_bits = ether_crc( ETH_ALEN, mc_list->dmi_addr ) >> 26;
+                multi_filter[ crc_bits >> 3 ] |= 1 << ( crc_bits & 7 );
+                mc_list = mc_list->next;
+            }
+
+            ax_write_cmd_async( ax_info, AX_CMD_WRITE_MULTI_FILTER, 0, 0, 8,
+                                multi_filter );
+
+            rx_ctl |= 0x10;
+        }
+    }
+
+    ax_write_cmd_async( ax_info, AX_CMD_WRITE_RX_CTL, rx_ctl, 0, 0, NULL );
+}
+
+static int ax8817x_ethtool_ioctl(struct net_device *net, void *uaddr)
+{
+       struct ax8817x_info *ax_info;
+       int cmd;
+       char tmp[128];
+
+       ax_info = net->priv;
+       if (get_user(cmd, (int *) uaddr))
+               return -EFAULT;
+
+       switch (cmd) {
+       case ETHTOOL_GDRVINFO:{
+                       struct ethtool_drvinfo info = { ETHTOOL_GDRVINFO };
+
+                       strncpy(info.driver, DRIVER_DESC, ETHTOOL_BUSINFO_LEN);
+                       strncpy(info.version, DRIVER_VERSION,
+                               ETHTOOL_BUSINFO_LEN);
+                       sprintf(tmp, "usb%d:%d", ax_info->usb->bus->busnum,
+                               ax_info->usb->devnum);
+                       strncpy(info.bus_info, tmp, ETHTOOL_BUSINFO_LEN);
+                       if (copy_to_user(uaddr, &info, sizeof(info)))
+                               return -EFAULT;
+                       return 0;
+               }
+       case ETHTOOL_GSET:{
+                       struct ethtool_cmd ecmd;
+
+                       if (copy_from_user(&ecmd, uaddr, sizeof(ecmd)))
+                               return -EFAULT;
+                       ecmd.supported = (SUPPORTED_10baseT_Half |
+                                         SUPPORTED_10baseT_Full |
+                                         SUPPORTED_100baseT_Half |
+                                         SUPPORTED_100baseT_Full |
+                                         SUPPORTED_Autoneg |
+                                         SUPPORTED_TP | SUPPORTED_MII);
+                       ecmd.port = PORT_TP;
+                       ecmd.transceiver = XCVR_INTERNAL;
+                       ecmd.phy_address = 0;   /* FIXME */
+
+                       if (copy_to_user(uaddr, &ecmd, sizeof(ecmd)))
+                               return -EFAULT;
+                       return 0;
+               }
+       case ETHTOOL_SSET:
+               return -ENOTSUPP;
+       case ETHTOOL_GLINK:{
+                       struct ethtool_value edata = { ETHTOOL_GLINK };
+
+                       edata.data = netif_carrier_ok(net);
+                       if (copy_to_user(uaddr, &edata, sizeof(edata)))
+                               return -EFAULT;
+                       return 0;
+               }
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static int ax8817x_mii_ioctl ( struct net_device *net, struct ifreq *ifr, int cmd )
+{
+       struct ax8817x_info *ax_info;
+       struct mii_ioctl_data *data_ptr = 
+               (struct mii_ioctl_data *) &(ifr->ifr_data);
+                                                                                
+        ax_info = net->priv;
+       
+       switch ( cmd ) {
+       case SIOCGMIIPHY:
+               data_ptr->phy_id = ax_info->phy_id;
+               break;
+       case SIOCGMIIREG:
+               if (!capable(CAP_NET_ADMIN))
+                       return -EPERM;
+
+               ax_read_cmd( ax_info, AX_CMD_READ_MII_REG, 0, data_ptr->reg_num & 
0x1f, 2, &(data_ptr->val_out) );
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+       return 0;
+}
+
+static int ax8817x_ioctl ( struct net_device *net, struct ifreq *ifr, int cmd )
+{      
+       struct ax8817x_info *ax_info;
+       int res;
+       
+       ax_info = net->priv;
+       res = 0;
+
+       switch (cmd) {
+       case SIOCETHTOOL:
+               res = ax8817x_ethtool_ioctl( net, ifr->ifr_data );
+               break;
+       case SIOCGMIIPHY:       /* Get address of PHY in use */
+       case SIOCGMIIREG:       /* Read from MII PHY register */
+       case SIOCSMIIREG:       /* Write to MII PHY register */
+               return ax8817x_mii_ioctl( net, ifr, cmd );
+       default:
+               res = -EOPNOTSUPP;
+       }
+
+       return res;
+}
+
+static int ax8817x_net_init( struct net_device *net )
+{
+    struct ax8817x_info *ax_info = (struct ax8817x_info *)net->priv;
+    u8 buf[6];
+    u16 *buf16 = (u16 *)buf;
+    int ret;
+
+    spin_lock_init( &ax_info->tx_lock );
+
+    ret = ax_write_cmd( ax_info, AX_CMD_WRITE_RX_CTL, 0x80, 0, 0, buf );
+    if ( ret < 0 ) {
+        return ret;
+    }
+
+    memset( buf, 0, 6 );
+
+    /* Get the MAC address */
+    ret = ax_read_cmd( ax_info, AX_CMD_READ_NODE_ID, 0, 0, 6, buf );
+    if ( ret < 0 ) {
+        return ret;
+    }
+
+    memcpy( net->dev_addr, buf, 6 );
+
+    /* Get the PHY id */
+    ret = ax_read_cmd( ax_info, AX_CMD_READ_PHY_ID, 0, 0, 2, buf );
+    if ( ret < 0 ) {
+        return ret;
+    } else if ( ret < 2 ) {
+        /* this should always return 2 bytes */
+        return -EIO;
+    }
+
+    /* Reset the PHY, and drop it into auto-negotiation mode */
+    ax_info->phy_id = buf[1];
+    ax_info->phy_state = AX_PHY_STATE_INITIALIZING;
+
+    ret = ax_write_cmd( ax_info, AX_CMD_SET_SW_MII, 0, 0, 0, &buf );
+    if ( ret < 0 ) {
+        return ret;
+    }
+
+    *buf16 = cpu_to_le16( BMCR_RESET );
+    ret = ax_write_cmd( ax_info, AX_CMD_WRITE_MII_REG,
+                        ax_info->phy_id, MII_BMCR, 2, buf16 );
+    if ( ret < 0 ) {
+        return ret;
+    }
+
+    /* Advertise that we can do full-duplex pause */
+    *buf16 = cpu_to_le16( ADVERTISE_ALL | ADVERTISE_CSMA | 0x0400 );
+    ret = ax_write_cmd( ax_info, AX_CMD_WRITE_MII_REG,
+                        ax_info->phy_id, MII_ADVERTISE, 2, buf16 );
+    if ( ret < 0 ) {
+        return ret;
+    }
+
+    *buf16 = cpu_to_le16( BMCR_ANENABLE | BMCR_ANRESTART );
+    ret = ax_write_cmd( ax_info, AX_CMD_WRITE_MII_REG,
+                        ax_info->phy_id, MII_BMCR, 2, buf16 );
+    if ( ret < 0 ) {
+        return ret;
+    }
+
+    ret = ax_write_cmd( ax_info, AX_CMD_SET_HW_MII, 0, 0, 0, &buf );
+    if ( ret < 0 ) {
+        return ret;
+    }
+
+    net->open = ax8817x_open;
+    net->stop = ax8817x_stop;
+    net->hard_start_xmit = ax8817x_start_xmit;
+    net->tx_timeout = ax8817x_tx_timeout;
+    net->watchdog_timeo = AX_TIMEOUT_TX;
+    net->get_stats = ax8817x_stats;
+    net->do_ioctl = ax8817x_ioctl;
+    net->set_multicast_list = ax8817x_set_multicast;
+
+    return 0;
+}
+
+static void *ax8817x_bind( struct usb_device *usb, unsigned intf,
+                           const struct usb_device_id *id )
+{
+    struct ax8817x_info *ax_info;
+    struct net_device *net;
+    int i, ret;
+    unsigned long gpio_bits = id->driver_info;
+    u8 buf[2];
+
+#if 0
+    ret = usb_set_configuration( usb, 1 );
+    if ( ret < 0 ) {
+        err( "%s: Failed set config (%d)\n", __FUNCTION__, ret );
+        goto exit_err;
+    }
+#endif
+
+    /* Allocate the URB lists along with the device info struct */
+    ax_info = kmalloc ( sizeof( struct ax8817x_info ) +
+                        n_rx_urbs * sizeof( struct urb * ) +
+                        n_tx_urbs * sizeof( struct urb * ), GFP_KERNEL );
+    if ( ax_info == NULL ) {
+        err( "%s: Failed ax alloc\n", __FUNCTION__);
+        goto exit_err;
+    }
+
+    memset( ax_info, 0, sizeof( struct ax8817x_info ) +
+                        n_rx_urbs * sizeof( struct urb * ) +
+                        n_tx_urbs * sizeof( struct urb * ) );
+
+    ax_info->drv_state = AX_DRV_STATE_INITIALIZING;
+    ax_info->rx_urbs = (struct urb **)(ax_info + 1);
+    ax_info->tx_urbs = ax_info->rx_urbs + n_rx_urbs;
+    ax_info->usb = usb;
+
+    /* Set up the control URB queue */
+    
+    INIT_LIST_HEAD( &ax_info->ctl_queue );
+    spin_lock_init( &ax_info->ctl_lock );
+    ax_info->ctl_urb = usb_alloc_urb( 0 );
+    if ( ax_info->ctl_urb == NULL ) {
+        goto exit_err_free_ax;
+    }
+
+    /* Toggle the GPIOs in a manufacturer/model specific way */
+    
+    for ( i = 2; i >= 0; i-- ) {
+        ret = ax_write_cmd( ax_info, AX_CMD_WRITE_GPIOS,
+                            ( gpio_bits >> ( i * 8 ) ) & 0xff, 0, 0, buf );
+        if ( ret < 0 ) {
+            goto exit_err_free_ax;
+        }
+        wait_ms( 5 );
+    }
+
+    /* Set up the net device */
+    
+    net = alloc_etherdev( 0 );
+    if ( net == NULL ) {
+        err( "%s: Failed net alloc\n", __FUNCTION__);
+        goto exit_err_free_ax;
+    }
+
+    ax_info->net = net;
+
+    SET_MODULE_OWNER( net );
+    net->init = ax8817x_net_init;
+    net->priv = ax_info;
+
+    ret = register_netdev( net );
+    if ( ret < 0 ) {
+        err( "%s: Failed net init (%d)\n", __FUNCTION__, ret );
+        goto exit_err_free_net;
+    }
+
+    /* Set up the interrupt URB, and start PHY state monitoring */
+    
+    ax_info->int_urb = usb_alloc_urb( 0 );
+    if ( ax_info->int_urb == NULL ) {
+        goto exit_err_unregister_net;
+    }
+    ax_info->int_buf = kmalloc( 8, GFP_KERNEL );
+    if ( ax_info->int_buf == NULL ) {
+        goto exit_err_free_int_urb;
+    }
+    ax_info->phy_req.data = kmalloc( 2, GFP_KERNEL );
+    if ( ax_info->phy_req.data == NULL ) {
+        goto exit_err_free_int_buf;
+    }
+
+    usb_fill_int_urb(
+            ax_info->int_urb, usb, usb_rcvintpipe( usb, 1 ),
+            ax_info->int_buf, 8, ax_int_callback, ax_info, 100
+            );
+
+    ret = usb_submit_urb( ax_info->int_urb );
+    if ( ret < 0 ) {
+        err( "%s: Failed int URB submit (%d)\n", __FUNCTION__, ret );
+        goto exit_err_free_phy_buf;
+    }
+
+    ax_info->drv_state = AX_DRV_STATE_RUNNING;
+    return ax_info;
+
+exit_err_free_phy_buf:
+    kfree( ax_info->phy_req.data);
+
+exit_err_free_int_buf:
+    kfree( ax_info->int_buf);
+
+exit_err_free_int_urb:
+    usb_free_urb( ax_info->int_urb);
+
+exit_err_unregister_net:
+    ax_info->drv_state = AX_DRV_STATE_EXITING;
+    unregister_netdev( net );
+
+exit_err_free_net:
+    kfree( net );
+
+exit_err_free_ax:
+    if ( ax_info->ctl_urb != NULL ) {
+        /* no need to unlink, since there should not be any ctl URBs
+           pending at this point */
+        usb_free_urb( ax_info->ctl_urb );
+    }
+
+    kfree( ax_info );
+
+exit_err:
+    err( "%s: Failed to initialize\n", __FUNCTION__);
+    return NULL;
+}
+
+static void ax8817x_disconnect( struct usb_device *usb, void *p )
+{
+    struct ax8817x_info *ax_info = (struct ax8817x_info *)p;
+
+    ax_info->drv_state = AX_DRV_STATE_EXITING;
+
+    if ( ax_info->int_urb != NULL ) {
+        usb_unlink_urb( ax_info->int_urb );
+        usb_free_urb( ax_info->int_urb );
+        kfree( ax_info->int_buf );
+    }
+
+    unregister_netdev( ax_info->net );
+
+    /* XXX: hmmm... need to go through and clear out the ctl queue, too... */
+    if ( ax_info->ctl_urb != NULL ) {
+        usb_unlink_urb( ax_info->ctl_urb );
+        usb_free_urb( ax_info->ctl_urb );
+    }
+
+    kfree( ax_info );
+}
+
+
+static struct usb_driver ax8817x_driver = {
+    .owner =           THIS_MODULE,
+    .name =            "ax8817x",
+    .probe =           ax8817x_bind,
+    .disconnect =      ax8817x_disconnect,
+    .id_table =                ax8817x_id_table,
+};
+
+static int __init ax8817x_init( void )
+{
+    int ret;
+
+    if ( n_rx_urbs <= 0 || n_tx_urbs <= 0 ) {
+        err( "%s: Non-positive URB count\n", __FUNCTION__ );
+        return -EINVAL;
+    }
+
+    ret = usb_register( &ax8817x_driver );
+    if ( ret < 0 ) {
+        err( "%s: Failed to register\n", __FUNCTION__ );
+    } else {
+       info(DRIVER_DESC " " DRIVER_VERSION);
+    }
+
+    return ret;
+}
+
+static void __exit ax8817x_exit( void )
+{
+    usb_deregister( &ax8817x_driver );
+}
+
+module_init( ax8817x_init );
+module_exit( ax8817x_exit );
+
+EXPORT_NO_SYMBOLS;
+

Reply via email to