aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/usb/cx82310_eth.c
diff options
context:
space:
mode:
authorOndrej Zary <linux@rainbow-software.org>2010-09-03 22:39:34 -0400
committerDavid S. Miller <davem@davemloft.net>2010-09-08 16:10:53 -0400
commitcc28a20e77b261eb4e80c84abd621e810302f435 (patch)
tree184deb1e0ebbc63a0e04c3b57c38ee564ab9670d /drivers/net/usb/cx82310_eth.c
parent6febfca98f25c7ee5c3ff7fc85e048bf82230ad5 (diff)
introduce cx82310_eth: Conexant CX82310-based ADSL router USB ethernet driver
This patch introduces cx82310_eth driver - driver for USB ethernet port of ADSL routers based on Conexant CX82310 chips. Such routers usually have ethernet port(s) too which are bridged together with the USB ethernet port, allowing the USB-connected machine to communicate to the network (and also internet through the ADSL, of course). This is my first driver, so please check thoroughly. As there's no protocol documentation, it was done with usbsnoop dumps from Windows driver, some parts (the commands) inspired by cxacru driver and also other usbnet drivers. The driver passed my testing - some real work and also pings sized from 0 to 65507 B. The only problem I found is the ifconfig error counter. When I return 0 (or 1 but empty skb) from rx_fixup(), usbnet increases the error counter although it's not an error condition (because packets can cross URB boundaries). Maybe the usbnet should be fixed to allow rx_fixup() to return empty skbs (or some other value, e.g. 2)? The USB ID of my device is 0x0572:0xcb01 which conflicts with some ADSL modems using cxacru driver (they probably use the same chipset but simpler firmware). The modems seem to use bDeviceClass 0 and iProduct "ADSL USB MODEM", my router uses bDeviceClass 255 and iProduct "USB NET CARD". The driver matches only devices with class 255 and checks for the iProduct string during init. I already posted a patch for the cxacru driver to ignore these devices. Signed-off-by: Ondrej Zary <linux@rainbow-software.org> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/usb/cx82310_eth.c')
-rw-r--r--drivers/net/usb/cx82310_eth.c354
1 files changed, 354 insertions, 0 deletions
diff --git a/drivers/net/usb/cx82310_eth.c b/drivers/net/usb/cx82310_eth.c
new file mode 100644
index 000000000000..6fbe03276b27
--- /dev/null
+++ b/drivers/net/usb/cx82310_eth.c
@@ -0,0 +1,354 @@
1/*
2 * Driver for USB ethernet port of Conexant CX82310-based ADSL routers
3 * Copyright (C) 2010 by Ondrej Zary
4 * some parts inspired by the cxacru driver
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21#include <linux/module.h>
22#include <linux/init.h>
23#include <linux/netdevice.h>
24#include <linux/etherdevice.h>
25#include <linux/ethtool.h>
26#include <linux/workqueue.h>
27#include <linux/mii.h>
28#include <linux/usb.h>
29#include <linux/usb/usbnet.h>
30
31enum cx82310_cmd {
32 CMD_START = 0x84, /* no effect? */
33 CMD_STOP = 0x85, /* no effect? */
34 CMD_GET_STATUS = 0x90, /* returns nothing? */
35 CMD_GET_MAC_ADDR = 0x91, /* read MAC address */
36 CMD_GET_LINK_STATUS = 0x92, /* not useful, link is always up */
37 CMD_ETHERNET_MODE = 0x99, /* unknown, needed during init */
38};
39
40enum cx82310_status {
41 STATUS_UNDEFINED,
42 STATUS_SUCCESS,
43 STATUS_ERROR,
44 STATUS_UNSUPPORTED,
45 STATUS_UNIMPLEMENTED,
46 STATUS_PARAMETER_ERROR,
47 STATUS_DBG_LOOPBACK,
48};
49
50#define CMD_PACKET_SIZE 64
51/* first command after power on can take around 8 seconds */
52#define CMD_TIMEOUT 15000
53#define CMD_REPLY_RETRY 5
54
55#define CX82310_MTU 1514
56#define CMD_EP 0x01
57
58/*
59 * execute control command
60 * - optionally send some data (command parameters)
61 * - optionally wait for the reply
62 * - optionally read some data from the reply
63 */
64static int cx82310_cmd(struct usbnet *dev, enum cx82310_cmd cmd, bool reply,
65 u8 *wdata, int wlen, u8 *rdata, int rlen)
66{
67 int actual_len, retries, ret;
68 struct usb_device *udev = dev->udev;
69 u8 *buf = kzalloc(CMD_PACKET_SIZE, GFP_KERNEL);
70
71 if (!buf)
72 return -ENOMEM;
73
74 /* create command packet */
75 buf[0] = cmd;
76 if (wdata)
77 memcpy(buf + 4, wdata, min_t(int, wlen, CMD_PACKET_SIZE - 4));
78
79 /* send command packet */
80 ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, CMD_EP), buf,
81 CMD_PACKET_SIZE, &actual_len, CMD_TIMEOUT);
82 if (ret < 0) {
83 dev_err(&dev->udev->dev, "send command %#x: error %d\n",
84 cmd, ret);
85 goto end;
86 }
87
88 if (reply) {
89 /* wait for reply, retry if it's empty */
90 for (retries = 0; retries < CMD_REPLY_RETRY; retries++) {
91 ret = usb_bulk_msg(udev, usb_rcvbulkpipe(udev, CMD_EP),
92 buf, CMD_PACKET_SIZE, &actual_len,
93 CMD_TIMEOUT);
94 if (ret < 0) {
95 dev_err(&dev->udev->dev,
96 "reply receive error %d\n", ret);
97 goto end;
98 }
99 if (actual_len > 0)
100 break;
101 }
102 if (actual_len == 0) {
103 dev_err(&dev->udev->dev, "no reply to command %#x\n",
104 cmd);
105 ret = -EIO;
106 goto end;
107 }
108 if (buf[0] != cmd) {
109 dev_err(&dev->udev->dev,
110 "got reply to command %#x, expected: %#x\n",
111 buf[0], cmd);
112 ret = -EIO;
113 goto end;
114 }
115 if (buf[1] != STATUS_SUCCESS) {
116 dev_err(&dev->udev->dev, "command %#x failed: %#x\n",
117 cmd, buf[1]);
118 ret = -EIO;
119 goto end;
120 }
121 if (rdata)
122 memcpy(rdata, buf + 4,
123 min_t(int, rlen, CMD_PACKET_SIZE - 4));
124 }
125end:
126 kfree(buf);
127 return ret;
128}
129
130#define partial_len data[0] /* length of partial packet data */
131#define partial_rem data[1] /* remaining (missing) data length */
132#define partial_data data[2] /* partial packet data */
133
134static int cx82310_bind(struct usbnet *dev, struct usb_interface *intf)
135{
136 int ret;
137 char buf[15];
138 struct usb_device *udev = dev->udev;
139
140 /* avoid ADSL modems - continue only if iProduct is "USB NET CARD" */
141 if (udev->descriptor.iProduct &&
142 usb_string(udev, udev->descriptor.iProduct, buf, sizeof(buf)) &&
143 strcmp(buf, "USB NET CARD")) {
144 dev_err(&udev->dev,
145 "probably an ADSL modem, use cxacru driver instead\n");
146 return -ENODEV;
147 }
148
149 ret = usbnet_get_endpoints(dev, intf);
150 if (ret)
151 return ret;
152
153 /*
154 * this must not include ethernet header as the device can send partial
155 * packets with no header (URB is at least 2 bytes long, so 2 is OK)
156 */
157 dev->net->hard_header_len = 2;
158 /* we can send at most 1514 bytes of data (+ 2-byte header) per URB */
159 dev->hard_mtu = CX82310_MTU + dev->net->hard_header_len;
160 /* we can receive URBs up to 4KB from the device */
161 dev->rx_urb_size = 4096;
162
163 dev->partial_data = (unsigned long) kmalloc(dev->hard_mtu, GFP_KERNEL);
164 if (!dev->partial_data)
165 return -ENOMEM;
166
167 /* enable ethernet mode (?) */
168 ret = cx82310_cmd(dev, CMD_ETHERNET_MODE, true, "\x01", 1, NULL, 0);
169 if (ret) {
170 dev_err(&udev->dev, "unable to enable ethernet mode: %d\n",
171 ret);
172 goto err;
173 }
174
175 /* get the MAC address */
176 ret = cx82310_cmd(dev, CMD_GET_MAC_ADDR, true, NULL, 0,
177 dev->net->dev_addr, ETH_ALEN);
178 if (ret) {
179 dev_err(&udev->dev, "unable to read MAC address: %d\n", ret);
180 goto err;
181 }
182
183 /* start (does not seem to have any effect?) */
184 ret = cx82310_cmd(dev, CMD_START, false, NULL, 0, NULL, 0);
185 if (ret)
186 goto err;
187
188 return 0;
189err:
190 kfree((void *)dev->partial_data);
191 return ret;
192}
193
194static void cx82310_unbind(struct usbnet *dev, struct usb_interface *intf)
195{
196 kfree((void *)dev->partial_data);
197}
198
199/*
200 * RX is NOT easy - we can receive multiple packets per skb, each having 2-byte
201 * packet length at the beginning.
202 * The last packet might be incomplete (when it crosses the 4KB URB size),
203 * continuing in the next skb (without any headers).
204 * If a packet has odd length, there is one extra byte at the end (before next
205 * packet or at the end of the URB).
206 */
207static int cx82310_rx_fixup(struct usbnet *dev, struct sk_buff *skb)
208{
209 int len;
210 struct sk_buff *skb2;
211
212 /*
213 * If the last skb ended with an incomplete packet, this skb contains
214 * end of that packet at the beginning.
215 */
216 if (dev->partial_rem) {
217 len = dev->partial_len + dev->partial_rem;
218 skb2 = alloc_skb(len, GFP_ATOMIC);
219 if (!skb2)
220 return 0;
221 skb_put(skb2, len);
222 memcpy(skb2->data, (void *)dev->partial_data,
223 dev->partial_len);
224 memcpy(skb2->data + dev->partial_len, skb->data,
225 dev->partial_rem);
226 usbnet_skb_return(dev, skb2);
227 skb_pull(skb, (dev->partial_rem + 1) & ~1);
228 dev->partial_rem = 0;
229 if (skb->len < 2)
230 return 1;
231 }
232
233 if (skb->len < 2) {
234 dev_err(&dev->udev->dev, "RX frame too short: %d B\n",
235 skb->len);
236 return 0;
237 }
238
239 /* a skb can contain multiple packets */
240 while (skb->len > 1) {
241 /* first two bytes are packet length */
242 len = skb->data[0] | (skb->data[1] << 8);
243 skb_pull(skb, 2);
244
245 /* if last packet in the skb, let usbnet to process it */
246 if (len == skb->len || len + 1 == skb->len) {
247 skb_trim(skb, len);
248 break;
249 }
250
251 if (len > CX82310_MTU) {
252 dev_err(&dev->udev->dev, "RX packet too long: %d B\n",
253 len);
254 return 0;
255 }
256
257 /* incomplete packet, save it for the next skb */
258 if (len > skb->len) {
259 dev->partial_len = skb->len;
260 dev->partial_rem = len - skb->len;
261 memcpy((void *)dev->partial_data, skb->data,
262 dev->partial_len);
263 skb_pull(skb, skb->len);
264 break;
265 }
266
267 skb2 = alloc_skb(len, GFP_ATOMIC);
268 if (!skb2)
269 return 0;
270 skb_put(skb2, len);
271 memcpy(skb2->data, skb->data, len);
272 /* process the packet */
273 usbnet_skb_return(dev, skb2);
274
275 skb_pull(skb, (len + 1) & ~1);
276 }
277
278 /* let usbnet process the last packet */
279 return 1;
280}
281
282/* TX is easy, just add 2 bytes of length at the beginning */
283static struct sk_buff *cx82310_tx_fixup(struct usbnet *dev, struct sk_buff *skb,
284 gfp_t flags)
285{
286 int len = skb->len;
287
288 if (skb_headroom(skb) < 2) {
289 struct sk_buff *skb2 = skb_copy_expand(skb, 2, 0, flags);
290 dev_kfree_skb_any(skb);
291 skb = skb2;
292 if (!skb)
293 return NULL;
294 }
295 skb_push(skb, 2);
296
297 skb->data[0] = len;
298 skb->data[1] = len >> 8;
299
300 return skb;
301}
302
303
304static const struct driver_info cx82310_info = {
305 .description = "Conexant CX82310 USB ethernet",
306 .flags = FLAG_ETHER,
307 .bind = cx82310_bind,
308 .unbind = cx82310_unbind,
309 .rx_fixup = cx82310_rx_fixup,
310 .tx_fixup = cx82310_tx_fixup,
311};
312
313#define USB_DEVICE_CLASS(vend, prod, cl, sc, pr) \
314 .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
315 USB_DEVICE_ID_MATCH_DEV_INFO, \
316 .idVendor = (vend), \
317 .idProduct = (prod), \
318 .bDeviceClass = (cl), \
319 .bDeviceSubClass = (sc), \
320 .bDeviceProtocol = (pr)
321
322static const struct usb_device_id products[] = {
323 {
324 USB_DEVICE_CLASS(0x0572, 0xcb01, 0xff, 0, 0),
325 .driver_info = (unsigned long) &cx82310_info
326 },
327 { },
328};
329MODULE_DEVICE_TABLE(usb, products);
330
331static struct usb_driver cx82310_driver = {
332 .name = "cx82310_eth",
333 .id_table = products,
334 .probe = usbnet_probe,
335 .disconnect = usbnet_disconnect,
336 .suspend = usbnet_suspend,
337 .resume = usbnet_resume,
338};
339
340static int __init cx82310_init(void)
341{
342 return usb_register(&cx82310_driver);
343}
344module_init(cx82310_init);
345
346static void __exit cx82310_exit(void)
347{
348 usb_deregister(&cx82310_driver);
349}
350module_exit(cx82310_exit);
351
352MODULE_AUTHOR("Ondrej Zary");
353MODULE_DESCRIPTION("Conexant CX82310-based ADSL router USB ethernet driver");
354MODULE_LICENSE("GPL");