patch-2.4.22 linux-2.4.22/drivers/usb/ax8817x.c
Next file: linux-2.4.22/drivers/usb/catc.c
Previous file: linux-2.4.22/drivers/usb/aiptek.c
Back to the patch index
Back to the overall index
- Lines: 1292
- Date:
2003-08-25 04:44:42.000000000 -0700
- Orig file:
linux-2.4.21/drivers/usb/ax8817x.c
- Orig date:
1969-12-31 16:00:00.000000000 -0800
diff -urN linux-2.4.21/drivers/usb/ax8817x.c linux-2.4.22/drivers/usb/ax8817x.c
@@ -0,0 +1,1291 @@
+/*
+ * ASIX AX8817x USB 2.0 10/100/HomePNA Ethernet controller driver
+ *
+ * $Id: ax8817x.c,v 1.15 2003/06/15 18:45:21 dhollis Exp $
+ *
+ * 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-06-28 - Dave Hollis <dhollis@davehollis.com> 1.0.2
+ * * Added support for Intellinet
+ * 2003-06-12 - Dave Hollis <dhollis@davehollis.com> 1.0.1
+ * * use usb_make_path for ethtool info
+ * * Use crc32.h for crc functions
+ * * Additional callback cases for other host ctrlrs
+ * * Force minimum default rcv buffers
+ *
+ * 2003-06-12 - Dave Hollis <dhollis@davehollis.com> 1.0.0
+ * * Backport removal of multiple tx_urb / lengthy
+ * start_xmit routines, etc.
+ * * ethtool driver name returns driver name, not
+ * long description
+ *
+ * 2003-06-05 - Dave Hollis <dhollis@davehollis.com> 0.9.9
+ * * Cleanup unnecessary #if 0
+ * * Fix coding style to match kernel style
+ *
+ * 2003-05-31 - Dave Hollis <dhollis@davehollis.com> 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 <dhollis@davehollis.com> 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 <dhollis@davehollis.com> 0.9.6
+ * * Added basic ethtool & mii support
+ *
+ * 2003-05-28 - Dave Hollis <dhollis@davehollis.com> 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
+ * Fix mii/ethtool output
+*/
+
+#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>
+#include <linux/crc32.h>
+
+/* Version Information */
+#define DRIVER_VERSION "v1.0.0"
+#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
+
+#define AX_RX_URBS_DEFAULT 2
+
+static const char driver_name[] = "ax8817x";
+static int n_rx_urbs = AX_RX_URBS_DEFAULT;
+
+MODULE_PARM(n_rx_urbs, "i");
+MODULE_PARM_DESC(n_rx_urbs,
+ "Number of rx 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_urb;
+ 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},
+ /* Intellinet USB Ethernet*/
+ {USB_DEVICE(0x0b95, 0x1720), driver_info:0x00130103},
+
+ {}
+};
+
+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 on UHCI */
+ case -ETIMEDOUT: /* OHCI */
+ case -EPROTO: /* EHCI */
+ case -EPIPE:
+ 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 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;
+
+ ax_info->tx_urb = usb_alloc_urb(0);
+ if (ax_info->tx_urb == NULL)
+ ret = -ENOMEM;
+
+ 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. */
+ usb_free_urb(ax_info->tx_urb);
+
+ 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);
+ }
+
+ if (ax_info->tx_urb != NULL) {
+ usb_unlink_urb(ax_info->tx_urb);
+ usb_free_urb(ax_info->tx_urb);
+ ax_info->tx_urb = 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 void write_bulk_callback(struct urb *urb)
+{
+ struct ax8817x_info *ax_info = urb->context;
+
+ if (!ax_info || (ax_info->drv_state == AX_DRV_STATE_EXITING))
+ return;
+
+ if (!netif_device_present(ax_info->net))
+ return;
+
+ if (urb->status)
+ info("%s: TX status %d", ax_info->net->name, urb->status);
+
+ ax_info->net->trans_start = jiffies;
+ netif_wake_queue(ax_info->net);
+}
+
+static int ax8817x_start_xmit(struct sk_buff *skb, struct net_device *net)
+{
+ struct ax8817x_info *ax_info = net->priv;
+ int res;
+
+ netif_stop_queue(net);
+
+ ax_info->tx_urb->transfer_flags |= USB_ZERO_PACKET;
+ usb_fill_bulk_urb(ax_info->tx_urb, ax_info->usb,
+ usb_sndbulkpipe(ax_info->usb, 2),
+ skb->data, skb->len, write_bulk_callback,
+ ax_info);
+ if ((res = usb_submit_urb(ax_info->tx_urb))) {
+ warn("Failed tx_urb %d", res);
+ ax_info->stats.tx_errors++;
+ netif_start_queue(net);
+ } else {
+ ax_info->stats.tx_packets++;
+ ax_info->stats.tx_bytes += skb->len;
+ net->trans_start = jiffies;
+ }
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+static void ax8817x_tx_timeout(struct net_device *net)
+{
+ struct ax8817x_info *ax_info = net->priv;
+
+ if (!ax_info)
+ return;
+
+ warn("%s: Tx timed out.", net->name);
+ ax_info->tx_urb->transfer_flags |= USB_ASYNC_UNLINK;
+ usb_unlink_urb(ax_info->tx_urb);
+ ax_info->stats.tx_errors++;
+}
+
+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;
+
+ 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_name,
+ ETHTOOL_BUSINFO_LEN);
+ strncpy(info.version, DRIVER_VERSION,
+ ETHTOOL_BUSINFO_LEN);
+ usb_make_path(ax_info->usb, info.bus_info,
+ sizeof info.bus_info);
+ 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];
+
+ /* Allocate the URB lists along with the device info struct */
+ ax_info = kmalloc(sizeof(struct ax8817x_info) +
+ n_rx_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 *));
+
+ ax_info->drv_state = AX_DRV_STATE_INITIALIZING;
+ ax_info->rx_urbs = (struct urb **) (ax_info + 1);
+ 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 = driver_name,
+ .probe = ax8817x_bind,
+ .disconnect = ax8817x_disconnect,
+ .id_table = ax8817x_id_table,
+};
+
+static int __init ax8817x_init(void)
+{
+ int ret;
+
+ if (n_rx_urbs < 1)
+ n_rx_urbs = AX_RX_URBS_DEFAULT;
+
+ 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;
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)