diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/usb/net/usbnet.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/usb/net/usbnet.c')
-rw-r--r-- | drivers/usb/net/usbnet.c | 4106 |
1 files changed, 4106 insertions, 0 deletions
diff --git a/drivers/usb/net/usbnet.c b/drivers/usb/net/usbnet.c new file mode 100644 index 00000000000..dd8b4456ea3 --- /dev/null +++ b/drivers/usb/net/usbnet.c @@ -0,0 +1,4106 @@ +/* + * USB Networking Links + * Copyright (C) 2000-2003 by David Brownell <dbrownell@users.sourceforge.net> + * Copyright (C) 2002 Pavel Machek <pavel@ucw.cz> + * Copyright (C) 2003-2005 David Hollis <dhollis@davehollis.com> + * Copyright (C) 2005 Phil Chang <pchang23@sbcglobal.net> + * Copyright (c) 2002-2003 TiVo Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * This is a generic "USB networking" framework that works with several + * kinds of full and high speed networking devices: + * + * + USB host-to-host "network cables", used for IP-over-USB links. + * These are often used for Laplink style connectivity products. + * - AnchorChip 2720 + * - Belkin, eTEK (interops with Win32 drivers) + * - GeneSys GL620USB-A + * - NetChip 1080 (interoperates with NetChip Win32 drivers) + * - Prolific PL-2301/2302 (replaces "plusb" driver) + * - KC Technology KC2190 + * + * + Smart USB devices can support such links directly, using Internet + * standard protocols instead of proprietary host-to-device links. + * - Linux PDAs like iPaq, Yopy, and Zaurus + * - The BLOB boot loader (for diskless booting) + * - Linux "gadgets", perhaps using PXA-2xx or Net2280 controllers + * - Devices using EPSON's sample USB firmware + * - CDC-Ethernet class devices, such as many cable modems + * + * + Adapters to networks such as Ethernet. + * - AX8817X based USB 2.0 products + * + * Links to these devices can be bridged using Linux Ethernet bridging. + * With minor exceptions, these all use similar USB framing for network + * traffic, but need different protocols for control traffic. + * + * USB devices can implement their side of this protocol at the cost + * of two bulk endpoints; it's not restricted to "cable" applications. + * See the SA1110, Zaurus, or EPSON device/client support in this driver; + * slave/target drivers such as "usb-eth" (on most SA-1100 PDAs) or + * "g_ether" (in the Linux "gadget" framework) implement that behavior + * within devices. + * + * + * CHANGELOG: + * + * 13-sep-2000 experimental, new + * 10-oct-2000 usb_device_id table created. + * 28-oct-2000 misc fixes; mostly, discard more TTL-mangled rx packets. + * 01-nov-2000 usb_device_id table and probing api update by + * Adam J. Richter <adam@yggdrasil.com>. + * 18-dec-2000 (db) tx watchdog, "net1080" renaming to "usbnet", device_info + * and prolific support, isolate net1080-specific bits, cleanup. + * fix unlink_urbs oops in D3 PM resume code path. + * + * 02-feb-2001 (db) fix tx skb sharing, packet length, match_flags, ... + * 08-feb-2001 stubbed in "linuxdev", maybe the SA-1100 folk can use it; + * AnchorChips 2720 support (from spec) for testing; + * fix bit-ordering problem with ethernet multicast addr + * 19-feb-2001 Support for clearing halt conditions. SA1100 UDC support + * updates. Oleg Drokin (green@iXcelerator.com) + * 25-mar-2001 More SA-1100 updates, including workaround for ip problem + * expecting cleared skb->cb and framing change to match latest + * handhelds.org version (Oleg). Enable device IDs from the + * Win32 Belkin driver; other cleanups (db). + * 16-jul-2001 Bugfixes for uhci oops-on-unplug, Belkin support, various + * cleanups for problems not yet seen in the field. (db) + * 17-oct-2001 Handle "Advance USBNET" product, like Belkin/eTEK devices, + * from Ioannis Mavroukakis <i.mavroukakis@btinternet.com>; + * rx unlinks somehow weren't async; minor cleanup. + * 03-nov-2001 Merged GeneSys driver; original code from Jiun-Jie Huang + * <huangjj@genesyslogic.com.tw>, updated by Stanislav Brabec + * <utx@penguin.cz>. Made framing options (NetChip/GeneSys) + * tie mostly to (sub)driver info. Workaround some PL-2302 + * chips that seem to reject SET_INTERFACE requests. + * + * 06-apr-2002 Added ethtool support, based on a patch from Brad Hards. + * Level of diagnostics is more configurable; they use device + * location (usb_device->devpath) instead of address (2.5). + * For tx_fixup, memflags can't be NOIO. + * 07-may-2002 Generalize/cleanup keventd support, handling rx stalls (mostly + * for USB 2.0 TTs) and memory shortages (potential) too. (db) + * Use "locally assigned" IEEE802 address space. (Brad Hards) + * 18-oct-2002 Support for Zaurus (Pavel Machek), related cleanup (db). + * 14-dec-2002 Remove Zaurus-private crc32 code (Pavel); 2.5 oops fix, + * cleanups and stubbed PXA-250 support (db), fix for framing + * issues on Z, net1080, and gl620a (Toby Milne) + * + * 31-mar-2003 Use endpoint descriptors: high speed support, simpler sa1100 + * vs pxa25x, and CDC Ethernet. Throttle down log floods on + * disconnect; other cleanups. (db) Flush net1080 fifos + * after several sequential framing errors. (Johannes Erdfelt) + * 22-aug-2003 AX8817X support (Dave Hollis). + * 14-jun-2004 Trivial patch for AX8817X based Buffalo LUA-U2-KTX in Japan + * (Neil Bortnak) + * 03-nov-2004 Trivial patch for KC2190 (KC-190) chip. (Jonathan McDowell) + * + * 01-feb-2005 AX88772 support (Phil Chang & Dave Hollis) + *-------------------------------------------------------------------------*/ + +// #define DEBUG // error path messages, extra info +// #define VERBOSE // more; success messages + +#include <linux/config.h> +#ifdef CONFIG_USB_DEBUG +# define DEBUG +#endif +#include <linux/module.h> +#include <linux/kmod.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/random.h> +#include <linux/ethtool.h> +#include <linux/workqueue.h> +#include <linux/mii.h> +#include <asm/uaccess.h> +#include <asm/unaligned.h> +#include <linux/usb.h> +#include <asm/io.h> +#include <asm/scatterlist.h> +#include <linux/mm.h> +#include <linux/dma-mapping.h> + +#define DRIVER_VERSION "03-Nov-2004" + + +/*-------------------------------------------------------------------------*/ + +/* + * Nineteen USB 1.1 max size bulk transactions per frame (ms), max. + * Several dozen bytes of IPv4 data can fit in two such transactions. + * One maximum size Ethernet packet takes twenty four of them. + * For high speed, each frame comfortably fits almost 36 max size + * Ethernet packets (so queues should be bigger). + */ +#define RX_QLEN(dev) (((dev)->udev->speed == USB_SPEED_HIGH) ? 60 : 4) +#define TX_QLEN(dev) (((dev)->udev->speed == USB_SPEED_HIGH) ? 60 : 4) + +// packets are always ethernet inside +// ... except they can be bigger (limit of 64K with NetChip framing) +#define MIN_PACKET sizeof(struct ethhdr) +#define MAX_PACKET 32768 + +// reawaken network queue this soon after stopping; else watchdog barks +#define TX_TIMEOUT_JIFFIES (5*HZ) + +// throttle rx/tx briefly after some faults, so khubd might disconnect() +// us (it polls at HZ/4 usually) before we report too many false errors. +#define THROTTLE_JIFFIES (HZ/8) + +// for vendor-specific control operations +#define CONTROL_TIMEOUT_MS 500 + +// between wakeups +#define UNLINK_TIMEOUT_MS 3 + +/*-------------------------------------------------------------------------*/ + +// randomly generated ethernet address +static u8 node_id [ETH_ALEN]; + +// state we keep for each device we handle +struct usbnet { + // housekeeping + struct usb_device *udev; + struct driver_info *driver_info; + wait_queue_head_t *wait; + + // i/o info: pipes etc + unsigned in, out; + struct usb_host_endpoint *status; + unsigned maxpacket; + struct timer_list delay; + + // protocol/interface state + struct net_device *net; + struct net_device_stats stats; + int msg_enable; + unsigned long data [5]; + + struct mii_if_info mii; + + // various kinds of pending driver work + struct sk_buff_head rxq; + struct sk_buff_head txq; + struct sk_buff_head done; + struct urb *interrupt; + struct tasklet_struct bh; + + struct work_struct kevent; + unsigned long flags; +# define EVENT_TX_HALT 0 +# define EVENT_RX_HALT 1 +# define EVENT_RX_MEMORY 2 +# define EVENT_STS_SPLIT 3 +}; + +// device-specific info used by the driver +struct driver_info { + char *description; + + int flags; +/* framing is CDC Ethernet, not writing ZLPs (hw issues), or optionally: */ +#define FLAG_FRAMING_NC 0x0001 /* guard against device dropouts */ +#define FLAG_FRAMING_GL 0x0002 /* genelink batches packets */ +#define FLAG_FRAMING_Z 0x0004 /* zaurus adds a trailer */ +#define FLAG_FRAMING_RN 0x0008 /* RNDIS batches, plus huge header */ + +#define FLAG_NO_SETINT 0x0010 /* device can't set_interface() */ +#define FLAG_ETHER 0x0020 /* maybe use "eth%d" names */ + +#define FLAG_FRAMING_AX 0x0040 /* AX88772/178 packets */ + + /* init device ... can sleep, or cause probe() failure */ + int (*bind)(struct usbnet *, struct usb_interface *); + + /* cleanup device ... can sleep, but can't fail */ + void (*unbind)(struct usbnet *, struct usb_interface *); + + /* reset device ... can sleep */ + int (*reset)(struct usbnet *); + + /* see if peer is connected ... can sleep */ + int (*check_connect)(struct usbnet *); + + /* for status polling */ + void (*status)(struct usbnet *, struct urb *); + + /* fixup rx packet (strip framing) */ + int (*rx_fixup)(struct usbnet *dev, struct sk_buff *skb); + + /* fixup tx packet (add framing) */ + struct sk_buff *(*tx_fixup)(struct usbnet *dev, + struct sk_buff *skb, int flags); + + // FIXME -- also an interrupt mechanism + // useful for at least PL2301/2302 and GL620USB-A + // and CDC use them to report 'is it connected' changes + + /* for new devices, use the descriptor-reading code instead */ + int in; /* rx endpoint */ + int out; /* tx endpoint */ + + unsigned long data; /* Misc driver specific data */ +}; + +// we record the state for each of our queued skbs +enum skb_state { + illegal = 0, + tx_start, tx_done, + rx_start, rx_done, rx_cleanup +}; + +struct skb_data { // skb->cb is one of these + struct urb *urb; + struct usbnet *dev; + enum skb_state state; + size_t length; +}; + +static const char driver_name [] = "usbnet"; + +/* use ethtool to change the level for any given device */ +static int msg_level = -1; +module_param (msg_level, int, 0); +MODULE_PARM_DESC (msg_level, "Override default message level"); + + +#ifdef DEBUG +#define devdbg(usbnet, fmt, arg...) \ + printk(KERN_DEBUG "%s: " fmt "\n" , (usbnet)->net->name , ## arg) +#else +#define devdbg(usbnet, fmt, arg...) do {} while(0) +#endif + +#define deverr(usbnet, fmt, arg...) \ + printk(KERN_ERR "%s: " fmt "\n" , (usbnet)->net->name , ## arg) +#define devwarn(usbnet, fmt, arg...) \ + printk(KERN_WARNING "%s: " fmt "\n" , (usbnet)->net->name , ## arg) + +#define devinfo(usbnet, fmt, arg...) \ + printk(KERN_INFO "%s: " fmt "\n" , (usbnet)->net->name , ## arg); \ + +/*-------------------------------------------------------------------------*/ + +static void usbnet_get_drvinfo (struct net_device *, struct ethtool_drvinfo *); +static u32 usbnet_get_link (struct net_device *); +static u32 usbnet_get_msglevel (struct net_device *); +static void usbnet_set_msglevel (struct net_device *, u32); + +/* mostly for PDA style devices, which are always connected if present */ +static int always_connected (struct usbnet *dev) +{ + return 0; +} + +/* handles CDC Ethernet and many other network "bulk data" interfaces */ +static int +get_endpoints (struct usbnet *dev, struct usb_interface *intf) +{ + int tmp; + struct usb_host_interface *alt = NULL; + struct usb_host_endpoint *in = NULL, *out = NULL; + struct usb_host_endpoint *status = NULL; + + for (tmp = 0; tmp < intf->num_altsetting; tmp++) { + unsigned ep; + + in = out = status = NULL; + alt = intf->altsetting + tmp; + + /* take the first altsetting with in-bulk + out-bulk; + * remember any status endpoint, just in case; + * ignore other endpoints and altsetttings. + */ + for (ep = 0; ep < alt->desc.bNumEndpoints; ep++) { + struct usb_host_endpoint *e; + int intr = 0; + + e = alt->endpoint + ep; + switch (e->desc.bmAttributes) { + case USB_ENDPOINT_XFER_INT: + if (!(e->desc.bEndpointAddress & USB_DIR_IN)) + continue; + intr = 1; + /* FALLTHROUGH */ + case USB_ENDPOINT_XFER_BULK: + break; + default: + continue; + } + if (e->desc.bEndpointAddress & USB_DIR_IN) { + if (!intr && !in) + in = e; + else if (intr && !status) + status = e; + } else { + if (!out) + out = e; + } + } + if (in && out) + break; + } + if (!alt || !in || !out) + return -EINVAL; + + if (alt->desc.bAlternateSetting != 0 + || !(dev->driver_info->flags & FLAG_NO_SETINT)) { + tmp = usb_set_interface (dev->udev, alt->desc.bInterfaceNumber, + alt->desc.bAlternateSetting); + if (tmp < 0) + return tmp; + } + + dev->in = usb_rcvbulkpipe (dev->udev, + in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev->out = usb_sndbulkpipe (dev->udev, + out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev->status = status; + return 0; +} + +static void intr_complete (struct urb *urb, struct pt_regs *regs); + +static int init_status (struct usbnet *dev, struct usb_interface *intf) +{ + char *buf = NULL; + unsigned pipe = 0; + unsigned maxp; + unsigned period; + + if (!dev->driver_info->status) + return 0; + + pipe = usb_rcvintpipe (dev->udev, + dev->status->desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + maxp = usb_maxpacket (dev->udev, pipe, 0); + + /* avoid 1 msec chatter: min 8 msec poll rate */ + period = max ((int) dev->status->desc.bInterval, + (dev->udev->speed == USB_SPEED_HIGH) ? 7 : 3); + + buf = kmalloc (maxp, SLAB_KERNEL); + if (buf) { + dev->interrupt = usb_alloc_urb (0, SLAB_KERNEL); + if (!dev->interrupt) { + kfree (buf); + return -ENOMEM; + } else { + usb_fill_int_urb(dev->interrupt, dev->udev, pipe, + buf, maxp, intr_complete, dev, period); + dev_dbg(&intf->dev, + "status ep%din, %d bytes period %d\n", + usb_pipeendpoint(pipe), maxp, period); + } + } + return 0; +} + +static void skb_return (struct usbnet *dev, struct sk_buff *skb) +{ + int status; + + skb->dev = dev->net; + skb->protocol = eth_type_trans (skb, dev->net); + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb->len; + + if (netif_msg_rx_status (dev)) + devdbg (dev, "< rx, len %d, type 0x%x", + skb->len + sizeof (struct ethhdr), skb->protocol); + memset (skb->cb, 0, sizeof (struct skb_data)); + status = netif_rx (skb); + if (status != NET_RX_SUCCESS && netif_msg_rx_err (dev)) + devdbg (dev, "netif_rx status %d", status); +} + + +#ifdef CONFIG_USB_ALI_M5632 +#define HAVE_HARDWARE + +/*------------------------------------------------------------------------- + * + * ALi M5632 driver ... does high speed + * + *-------------------------------------------------------------------------*/ + +static const struct driver_info ali_m5632_info = { + .description = "ALi M5632", +}; + + +#endif + + +#ifdef CONFIG_USB_AN2720 +#define HAVE_HARDWARE + +/*------------------------------------------------------------------------- + * + * AnchorChips 2720 driver ... http://www.cypress.com + * + * This doesn't seem to have a way to detect whether the peer is + * connected, or need any reset handshaking. It's got pretty big + * internal buffers (handles most of a frame's worth of data). + * Chip data sheets don't describe any vendor control messages. + * + *-------------------------------------------------------------------------*/ + +static const struct driver_info an2720_info = { + .description = "AnchorChips/Cypress 2720", + // no reset available! + // no check_connect available! + + .in = 2, .out = 2, // direction distinguishes these +}; + +#endif /* CONFIG_USB_AN2720 */ + + +#ifdef CONFIG_USB_AX8817X +/* ASIX AX8817X based USB 2.0 Ethernet Devices */ + +#define HAVE_HARDWARE +#define NEED_MII + +#include <linux/crc32.h> + +#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_READ_EEPROM 0x0b +#define AX_CMD_WRITE_EEPROM 0x0c +#define AX_CMD_WRITE_ENABLE 0x0d +#define AX_CMD_WRITE_DISABLE 0x0e +#define AX_CMD_WRITE_RX_CTL 0x10 +#define AX_CMD_READ_IPG012 0x11 +#define AX_CMD_WRITE_IPG0 0x12 +#define AX_CMD_WRITE_IPG1 0x13 +#define AX_CMD_WRITE_IPG2 0x14 +#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_READ_MONITOR_MODE 0x1c +#define AX_CMD_WRITE_MONITOR_MODE 0x1d +#define AX_CMD_WRITE_GPIOS 0x1f +#define AX_CMD_SW_RESET 0x20 +#define AX_CMD_SW_PHY_STATUS 0x21 +#define AX_CMD_SW_PHY_SELECT 0x22 +#define AX88772_CMD_READ_NODE_ID 0x13 + +#define AX_MONITOR_MODE 0x01 +#define AX_MONITOR_LINK 0x02 +#define AX_MONITOR_MAGIC 0x04 +#define AX_MONITOR_HSFS 0x10 + +#define AX_MCAST_FILTER_SIZE 8 +#define AX_MAX_MCAST 64 + +#define AX_INTERRUPT_BUFSIZE 8 + +#define AX_EEPROM_LEN 0x40 + +#define AX_SWRESET_CLEAR 0x00 +#define AX_SWRESET_RR 0x01 +#define AX_SWRESET_RT 0x02 +#define AX_SWRESET_PRTE 0x04 +#define AX_SWRESET_PRL 0x08 +#define AX_SWRESET_BZ 0x10 +#define AX_SWRESET_IPRL 0x20 +#define AX_SWRESET_IPPD 0x40 + +#define AX88772_IPG0_DEFAULT 0x15 +#define AX88772_IPG1_DEFAULT 0x0c +#define AX88772_IPG2_DEFAULT 0x12 + +#define AX_EEPROM_MAGIC 0xdeadbeef + +/* This structure cannot exceed sizeof(unsigned long [5]) AKA 20 bytes */ +struct ax8817x_data { + u8 multi_filter[AX_MCAST_FILTER_SIZE]; + struct urb *int_urb; + u8 *int_buf; +}; + +static int ax8817x_read_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + return usb_control_msg( + dev->udev, + usb_rcvctrlpipe(dev->udev, 0), + cmd, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, + index, + data, + size, + CONTROL_TIMEOUT_MS); +} + +static int ax8817x_write_cmd(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + return usb_control_msg( + dev->udev, + usb_sndctrlpipe(dev->udev, 0), + cmd, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, + index, + data, + size, + CONTROL_TIMEOUT_MS); +} + +static void ax8817x_async_cmd_callback(struct urb *urb, struct pt_regs *regs) +{ + struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context; + + if (urb->status < 0) + printk(KERN_DEBUG "ax8817x_async_cmd_callback() failed with %d", + urb->status); + + kfree(req); + usb_free_urb(urb); +} + +static void ax8817x_interrupt_complete(struct urb *urb, struct pt_regs *regs) +{ + struct usbnet *dev = (struct usbnet *)urb->context; + struct ax8817x_data *data = (struct ax8817x_data *)&dev->data; + int link; + + if (urb->status < 0) { + devdbg(dev,"ax8817x_interrupt_complete() failed with %d", + urb->status); + } else { + link = data->int_buf[2] & 0x01; + if (netif_carrier_ok(dev->net) != link) { + if (link) + netif_carrier_on(dev->net); + else + netif_carrier_off(dev->net); + devdbg(dev, "ax8817x - Link Status is: %d", link); + } + usb_submit_urb(data->int_urb, GFP_ATOMIC); + } +} + +static void ax8817x_write_cmd_async(struct usbnet *dev, u8 cmd, u16 value, u16 index, + u16 size, void *data) +{ + struct usb_ctrlrequest *req; + int status; + struct urb *urb; + + if ((urb = usb_alloc_urb(0, GFP_ATOMIC)) == NULL) { + devdbg(dev, "Error allocating URB in write_cmd_async!"); + return; + } + + if ((req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC)) == NULL) { + deverr(dev, "Failed to allocate memory for control request"); + usb_free_urb(urb); + return; + } + + req->bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE; + req->bRequest = cmd; + req->wValue = cpu_to_le16(value); + req->wIndex = cpu_to_le16(index); + req->wLength = cpu_to_le16(size); + + usb_fill_control_urb(urb, dev->udev, + usb_sndctrlpipe(dev->udev, 0), + (void *)req, data, size, + ax8817x_async_cmd_callback, req); + + if((status = usb_submit_urb(urb, GFP_ATOMIC)) < 0) { + deverr(dev, "Error submitting the control message: status=%d", status); + kfree(req); + usb_free_urb(urb); + } +} + +static void ax8817x_set_multicast(struct net_device *net) +{ + struct usbnet *dev = netdev_priv(net); + struct ax8817x_data *data = (struct ax8817x_data *)&dev->data; + 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 { + /* We use the 20 byte dev->data + * for our 8 byte filter buffer + * to avoid allocating memory that + * is tricky to free later */ + struct dev_mc_list *mc_list = net->mc_list; + u32 crc_bits; + int i; + + memset(data->multi_filter, 0, AX_MCAST_FILTER_SIZE); + + /* Build the multicast hash filter. */ + for (i = 0; i < net->mc_count; i++) { + crc_bits = + ether_crc(ETH_ALEN, + mc_list->dmi_addr) >> 26; + data->multi_filter[crc_bits >> 3] |= + 1 << (crc_bits & 7); + mc_list = mc_list->next; + } + + ax8817x_write_cmd_async(dev, AX_CMD_WRITE_MULTI_FILTER, 0, 0, + AX_MCAST_FILTER_SIZE, data->multi_filter); + + rx_ctl |= 0x10; + } + + ax8817x_write_cmd_async(dev, AX_CMD_WRITE_RX_CTL, rx_ctl, 0, 0, NULL); +} + +static int ax8817x_mdio_read(struct net_device *netdev, int phy_id, int loc) +{ + struct usbnet *dev = netdev_priv(netdev); + u16 res; + u8 buf[1]; + + ax8817x_write_cmd(dev, AX_CMD_SET_SW_MII, 0, 0, 0, &buf); + ax8817x_read_cmd(dev, AX_CMD_READ_MII_REG, phy_id, (__u16)loc, 2, (u16 *)&res); + ax8817x_write_cmd(dev, AX_CMD_SET_HW_MII, 0, 0, 0, &buf); + + return res & 0xffff; +} + +static void ax8817x_mdio_write(struct net_device *netdev, int phy_id, int loc, int val) +{ + struct usbnet *dev = netdev_priv(netdev); + u16 res = val; + u8 buf[1]; + + ax8817x_write_cmd(dev, AX_CMD_SET_SW_MII, 0, 0, 0, &buf); + ax8817x_write_cmd(dev, AX_CMD_WRITE_MII_REG, phy_id, (__u16)loc, 2, (u16 *)&res); + ax8817x_write_cmd(dev, AX_CMD_SET_HW_MII, 0, 0, 0, &buf); +} + +static void ax8817x_get_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo) +{ + struct usbnet *dev = netdev_priv(net); + u8 opt; + + if (ax8817x_read_cmd(dev, AX_CMD_READ_MONITOR_MODE, 0, 0, 1, &opt) < 0) { + wolinfo->supported = 0; + wolinfo->wolopts = 0; + return; + } + wolinfo->supported = WAKE_PHY | WAKE_MAGIC; + wolinfo->wolopts = 0; + if (opt & AX_MONITOR_MODE) { + if (opt & AX_MONITOR_LINK) + wolinfo->wolopts |= WAKE_PHY; + if (opt & AX_MONITOR_MAGIC) + wolinfo->wolopts |= WAKE_MAGIC; + } +} + +static int ax8817x_set_wol(struct net_device *net, struct ethtool_wolinfo *wolinfo) +{ + struct usbnet *dev = netdev_priv(net); + u8 opt = 0; + u8 buf[1]; + + if (wolinfo->wolopts & WAKE_PHY) + opt |= AX_MONITOR_LINK; + if (wolinfo->wolopts & WAKE_MAGIC) + opt |= AX_MONITOR_MAGIC; + if (opt != 0) + opt |= AX_MONITOR_MODE; + + if (ax8817x_write_cmd(dev, AX_CMD_WRITE_MONITOR_MODE, + opt, 0, 0, &buf) < 0) + return -EINVAL; + + return 0; +} + +static int ax8817x_get_eeprom_len(struct net_device *net) +{ + return AX_EEPROM_LEN; +} + +static int ax8817x_get_eeprom(struct net_device *net, + struct ethtool_eeprom *eeprom, u8 *data) +{ + struct usbnet *dev = netdev_priv(net); + u16 *ebuf = (u16 *)data; + int i; + + /* Crude hack to ensure that we don't overwrite memory + * if an odd length is supplied + */ + if (eeprom->len % 2) + return -EINVAL; + + eeprom->magic = AX_EEPROM_MAGIC; + + /* ax8817x returns 2 bytes from eeprom on read */ + for (i=0; i < eeprom->len / 2; i++) { + if (ax8817x_read_cmd(dev, AX_CMD_READ_EEPROM, + eeprom->offset + i, 0, 2, &ebuf[i]) < 0) + return -EINVAL; + } + return 0; +} + +static void ax8817x_get_drvinfo (struct net_device *net, + struct ethtool_drvinfo *info) +{ + /* Inherit standard device info */ + usbnet_get_drvinfo(net, info); + info->eedump_len = 0x3e; +} + +static int ax8817x_get_settings(struct net_device *net, struct ethtool_cmd *cmd) +{ + struct usbnet *dev = netdev_priv(net); + + return mii_ethtool_gset(&dev->mii,cmd); +} + +static int ax8817x_set_settings(struct net_device *net, struct ethtool_cmd *cmd) +{ + struct usbnet *dev = netdev_priv(net); + + return mii_ethtool_sset(&dev->mii,cmd); +} + +/* We need to override some ethtool_ops so we require our + own structure so we don't interfere with other usbnet + devices that may be connected at the same time. */ +static struct ethtool_ops ax8817x_ethtool_ops = { + .get_drvinfo = ax8817x_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_msglevel = usbnet_get_msglevel, + .set_msglevel = usbnet_set_msglevel, + .get_wol = ax8817x_get_wol, + .set_wol = ax8817x_set_wol, + .get_eeprom_len = ax8817x_get_eeprom_len, + .get_eeprom = ax8817x_get_eeprom, + .get_settings = ax8817x_get_settings, + .set_settings = ax8817x_set_settings, +}; + +static int ax8817x_bind(struct usbnet *dev, struct usb_interface *intf) +{ + int ret = 0; + void *buf; + int i; + unsigned long gpio_bits = dev->driver_info->data; + struct ax8817x_data *data = (struct ax8817x_data *)dev->data; + + get_endpoints(dev,intf); + + if ((data->int_urb = usb_alloc_urb (0, GFP_KERNEL)) == NULL) { + dbg ("%s: cannot allocate interrupt URB", + dev->net->name); + ret = -ENOMEM; + goto out1; + } + + if ((data->int_buf = kmalloc(AX_INTERRUPT_BUFSIZE, GFP_KERNEL)) == NULL) { + dbg ("%s: cannot allocate memory for interrupt buffer", + dev->net->name); + ret = -ENOMEM; + goto out1; + } + memset(data->int_buf, 0, AX_INTERRUPT_BUFSIZE); + + usb_fill_int_urb (data->int_urb, dev->udev, + usb_rcvintpipe (dev->udev, 1), + data->int_buf, AX_INTERRUPT_BUFSIZE, + ax8817x_interrupt_complete, dev, + dev->udev->speed == USB_SPEED_HIGH ? 8 : 100); + + buf = kmalloc(ETH_ALEN, GFP_KERNEL); + if(!buf) { + ret = -ENOMEM; + goto out2; + } + + /* Toggle the GPIOs in a manufacturer/model specific way */ + for (i = 2; i >= 0; i--) { + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_GPIOS, + (gpio_bits >> (i * 8)) & 0xff, 0, 0, + buf)) < 0) + goto out3; + msleep(5); + } + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, 0x80, 0, 0, buf)) < 0) { + dbg("send AX_CMD_WRITE_RX_CTL failed: %d", ret); + goto out3; + } + + /* Get the MAC address */ + memset(buf, 0, ETH_ALEN); + if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_NODE_ID, 0, 0, 6, buf)) < 0) { + dbg("read AX_CMD_READ_NODE_ID failed: %d", ret); + goto out3; + } + memcpy(dev->net->dev_addr, buf, ETH_ALEN); + + /* Get the PHY id */ + if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_PHY_ID, 0, 0, 2, buf)) < 0) { + dbg("error on read AX_CMD_READ_PHY_ID: %02x", ret); + goto out3; + } else if (ret < 2) { + /* this should always return 2 bytes */ + dbg("AX_CMD_READ_PHY_ID returned less than 2 bytes: ret=%02x", ret); + ret = -EIO; + goto out3; + } + + /* Initialize MII structure */ + dev->mii.dev = dev->net; + dev->mii.mdio_read = ax8817x_mdio_read; + dev->mii.mdio_write = ax8817x_mdio_write; + dev->mii.phy_id_mask = 0x3f; + dev->mii.reg_num_mask = 0x1f; + dev->mii.phy_id = *((u8 *)buf + 1); + + dev->net->set_multicast_list = ax8817x_set_multicast; + dev->net->ethtool_ops = &ax8817x_ethtool_ops; + + ax8817x_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, + cpu_to_le16(BMCR_RESET)); + ax8817x_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE, + cpu_to_le16(ADVERTISE_ALL | ADVERTISE_CSMA | 0x0400)); + mii_nway_restart(&dev->mii); + + if((ret = usb_submit_urb(data->int_urb, GFP_KERNEL)) < 0) { + dbg("Failed to submit interrupt URB: %02x", ret); + goto out2; + } + + return 0; +out3: + kfree(buf); +out2: + kfree(data->int_buf); +out1: + usb_free_urb(data->int_urb); + return ret; +} + +static void ax8817x_unbind(struct usbnet *dev, struct usb_interface *intf) +{ + struct ax8817x_data *data = (struct ax8817x_data *)dev->data; + + usb_kill_urb(data->int_urb); + usb_free_urb(data->int_urb); + kfree(data->int_buf); +} + +static struct ethtool_ops ax88772_ethtool_ops = { + .get_drvinfo = ax8817x_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_msglevel = usbnet_get_msglevel, + .set_msglevel = usbnet_set_msglevel, + .get_wol = ax8817x_get_wol, + .set_wol = ax8817x_set_wol, + .get_eeprom_len = ax8817x_get_eeprom_len, + .get_eeprom = ax8817x_get_eeprom, + .get_settings = ax8817x_get_settings, + .set_settings = ax8817x_set_settings, +}; + +static int ax88772_bind(struct usbnet *dev, struct usb_interface *intf) +{ + int ret; + void *buf; + struct ax8817x_data *data = (struct ax8817x_data *)dev->data; + + get_endpoints(dev,intf); + + if ((data->int_urb = usb_alloc_urb (0, GFP_KERNEL)) == 0) { + dbg ("Cannot allocate interrupt URB"); + ret = -ENOMEM; + goto out1; + } + + if ((data->int_buf = kmalloc(AX_INTERRUPT_BUFSIZE, GFP_KERNEL)) == NULL) { + dbg ("Cannot allocate memory for interrupt buffer"); + ret = -ENOMEM; + goto out1; + } + memset(data->int_buf, 0, AX_INTERRUPT_BUFSIZE); + + usb_fill_int_urb (data->int_urb, dev->udev, + usb_rcvintpipe (dev->udev, 1), + data->int_buf, AX_INTERRUPT_BUFSIZE, + ax8817x_interrupt_complete, dev, + dev->udev->speed == USB_SPEED_HIGH ? 8 : 100); + + buf = kmalloc(6, GFP_KERNEL); + if(!buf) { + dbg ("Cannot allocate memory for buffer"); + ret = -ENOMEM; + goto out2; + } + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_GPIOS, + 0x00B0, 0, 0, buf)) < 0) + goto out3; + + msleep(5); + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SW_PHY_SELECT, 0x0001, 0, 0, buf)) < 0) { + dbg("Select PHY #1 failed: %d", ret); + goto out3; + } + + if ((ret = + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_IPPD, 0, 0, buf)) < 0) { + dbg("Failed to power down internal PHY: %d", ret); + goto out3; + } + + msleep(150); + if ((ret = + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_CLEAR, 0, 0, buf)) < 0) { + dbg("Failed to perform software reset: %d", ret); + goto out3; + } + + msleep(150); + if ((ret = + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_IPRL | AX_SWRESET_PRL, 0, 0, buf)) < 0) { + dbg("Failed to set Internal/External PHY reset control: %d", ret); + goto out3; + } + + msleep(150); + if ((ret = + ax8817x_write_cmd(dev, AX_CMD_WRITE_RX_CTL, 0x0000, 0, 0, + buf)) < 0) { + dbg("Failed to reset RX_CTL: %d", ret); + goto out3; + } + + /* Get the MAC address */ + memset(buf, 0, ETH_ALEN); + if ((ret = ax8817x_read_cmd(dev, AX88772_CMD_READ_NODE_ID, 0, 0, ETH_ALEN, buf)) < 0) { + dbg("Failed to read MAC address: %d", ret); + goto out3; + } + memcpy(dev->net->dev_addr, buf, ETH_ALEN); + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_SET_SW_MII, 0, 0, 0, buf)) < 0) { + dbg("Enabling software MII failed: %d", ret); + goto out3; + } + + if (((ret = + ax8817x_read_cmd(dev, AX_CMD_READ_MII_REG, 0x0010, 2, 2, buf)) < 0) + || (*((u16 *)buf) != 0x003b)) { + dbg("Read PHY register 2 must be 0x3b00: %d", ret); + goto out3; + } + + /* Initialize MII structure */ + dev->mii.dev = dev->net; + dev->mii.mdio_read = ax8817x_mdio_read; + dev->mii.mdio_write = ax8817x_mdio_write; + dev->mii.phy_id_mask = 0xff; + dev->mii.reg_num_mask = 0xff; + + /* Get the PHY id */ + if ((ret = ax8817x_read_cmd(dev, AX_CMD_READ_PHY_ID, 0, 0, 2, buf)) < 0) { + dbg("Error reading PHY ID: %02x", ret); + goto out3; + } else if (ret < 2) { + /* this should always return 2 bytes */ + dbg("AX_CMD_READ_PHY_ID returned less than 2 bytes: ret=%02x", + ret); + ret = -EIO; + goto out3; + } + dev->mii.phy_id = *((u8 *)buf + 1); + + if ((ret = + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_PRL, 0, 0, buf)) < 0) { + dbg("Set external PHY reset pin level: %d", ret); + goto out3; + } + msleep(150); + if ((ret = + ax8817x_write_cmd(dev, AX_CMD_SW_RESET, AX_SWRESET_IPRL | AX_SWRESET_PRL, 0, 0, buf)) < 0) { + dbg("Set Internal/External PHY reset control: %d", ret); + goto out3; + } + msleep(150); + + + dev->net->set_multicast_list = ax8817x_set_multicast; + dev->net->ethtool_ops = &ax88772_ethtool_ops; + + ax8817x_mdio_write(dev->net, dev->mii.phy_id, MII_BMCR, + cpu_to_le16(BMCR_RESET)); + ax8817x_mdio_write(dev->net, dev->mii.phy_id, MII_ADVERTISE, + cpu_to_le16(ADVERTISE_ALL | ADVERTISE_CSMA)); + mii_nway_restart(&dev->mii); + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_MEDIUM_MODE, 0x0336, 0, 0, buf)) < 0) { + dbg("Write medium mode register: %d", ret); + goto out3; + } + + if ((ret = ax8817x_write_cmd(dev, AX_CMD_WRITE_IPG0, AX88772_IPG0_DEFAULT | AX88772_IPG1_DEFAULT,AX88772_IPG2_DEFAULT, 0, buf)) < 0) { + dbg("Write IPG,IPG1,IPG2 failed: %d", ret); + goto out3; + } + if ((ret = + ax8817x_write_cmd(dev, AX_CMD_SET_HW_MII, 0, 0, 0, &buf)) < 0) { + dbg("Failed to set hardware MII: %02x", ret); + goto out3; + } |