1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Driver for USB ethernet port of Conexant CX82310-based ADSL routers |
4 | * Copyright (C) 2010 by Ondrej Zary |
5 | * some parts inspired by the cxacru driver |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/netdevice.h> |
10 | #include <linux/etherdevice.h> |
11 | #include <linux/ethtool.h> |
12 | #include <linux/workqueue.h> |
13 | #include <linux/mii.h> |
14 | #include <linux/usb.h> |
15 | #include <linux/usb/usbnet.h> |
16 | |
17 | enum cx82310_cmd { |
18 | CMD_START = 0x84, /* no effect? */ |
19 | CMD_STOP = 0x85, /* no effect? */ |
20 | CMD_GET_STATUS = 0x90, /* returns nothing? */ |
21 | CMD_GET_MAC_ADDR = 0x91, /* read MAC address */ |
22 | CMD_GET_LINK_STATUS = 0x92, /* not useful, link is always up */ |
23 | CMD_ETHERNET_MODE = 0x99, /* unknown, needed during init */ |
24 | }; |
25 | |
26 | enum cx82310_status { |
27 | STATUS_UNDEFINED, |
28 | STATUS_SUCCESS, |
29 | STATUS_ERROR, |
30 | STATUS_UNSUPPORTED, |
31 | STATUS_UNIMPLEMENTED, |
32 | STATUS_PARAMETER_ERROR, |
33 | STATUS_DBG_LOOPBACK, |
34 | }; |
35 | |
36 | #define CMD_PACKET_SIZE 64 |
37 | #define CMD_TIMEOUT 100 |
38 | #define CMD_REPLY_RETRY 5 |
39 | |
40 | #define CX82310_MTU 1514 |
41 | #define CMD_EP 0x01 |
42 | |
43 | struct cx82310_priv { |
44 | struct work_struct reenable_work; |
45 | struct usbnet *dev; |
46 | }; |
47 | |
48 | /* |
49 | * execute control command |
50 | * - optionally send some data (command parameters) |
51 | * - optionally wait for the reply |
52 | * - optionally read some data from the reply |
53 | */ |
54 | static int cx82310_cmd(struct usbnet *dev, enum cx82310_cmd cmd, bool reply, |
55 | u8 *wdata, int wlen, u8 *rdata, int rlen) |
56 | { |
57 | int actual_len, retries, ret; |
58 | struct usb_device *udev = dev->udev; |
59 | u8 *buf = kzalloc(CMD_PACKET_SIZE, GFP_KERNEL); |
60 | |
61 | if (!buf) |
62 | return -ENOMEM; |
63 | |
64 | /* create command packet */ |
65 | buf[0] = cmd; |
66 | if (wdata) |
67 | memcpy(buf + 4, wdata, min_t(int, wlen, CMD_PACKET_SIZE - 4)); |
68 | |
69 | /* send command packet */ |
70 | ret = usb_bulk_msg(usb_dev: udev, usb_sndbulkpipe(udev, CMD_EP), data: buf, |
71 | CMD_PACKET_SIZE, actual_length: &actual_len, CMD_TIMEOUT); |
72 | if (ret < 0) { |
73 | if (cmd != CMD_GET_LINK_STATUS) |
74 | netdev_err(dev: dev->net, format: "send command %#x: error %d\n" , |
75 | cmd, ret); |
76 | goto end; |
77 | } |
78 | |
79 | if (reply) { |
80 | /* wait for reply, retry if it's empty */ |
81 | for (retries = 0; retries < CMD_REPLY_RETRY; retries++) { |
82 | ret = usb_bulk_msg(usb_dev: udev, usb_rcvbulkpipe(udev, CMD_EP), |
83 | data: buf, CMD_PACKET_SIZE, actual_length: &actual_len, |
84 | CMD_TIMEOUT); |
85 | if (ret < 0) { |
86 | if (cmd != CMD_GET_LINK_STATUS) |
87 | netdev_err(dev: dev->net, format: "reply receive error %d\n" , |
88 | ret); |
89 | goto end; |
90 | } |
91 | if (actual_len > 0) |
92 | break; |
93 | } |
94 | if (actual_len == 0) { |
95 | netdev_err(dev: dev->net, format: "no reply to command %#x\n" , cmd); |
96 | ret = -EIO; |
97 | goto end; |
98 | } |
99 | if (buf[0] != cmd) { |
100 | netdev_err(dev: dev->net, format: "got reply to command %#x, expected: %#x\n" , |
101 | buf[0], cmd); |
102 | ret = -EIO; |
103 | goto end; |
104 | } |
105 | if (buf[1] != STATUS_SUCCESS) { |
106 | netdev_err(dev: dev->net, format: "command %#x failed: %#x\n" , cmd, |
107 | buf[1]); |
108 | ret = -EIO; |
109 | goto end; |
110 | } |
111 | if (rdata) |
112 | memcpy(rdata, buf + 4, |
113 | min_t(int, rlen, CMD_PACKET_SIZE - 4)); |
114 | } |
115 | end: |
116 | kfree(objp: buf); |
117 | return ret; |
118 | } |
119 | |
120 | static int cx82310_enable_ethernet(struct usbnet *dev) |
121 | { |
122 | int ret = cx82310_cmd(dev, cmd: CMD_ETHERNET_MODE, reply: true, wdata: "\x01" , wlen: 1, NULL, rlen: 0); |
123 | |
124 | if (ret) |
125 | netdev_err(dev: dev->net, format: "unable to enable ethernet mode: %d\n" , |
126 | ret); |
127 | return ret; |
128 | } |
129 | |
130 | static void cx82310_reenable_work(struct work_struct *work) |
131 | { |
132 | struct cx82310_priv *priv = container_of(work, struct cx82310_priv, |
133 | reenable_work); |
134 | cx82310_enable_ethernet(dev: priv->dev); |
135 | } |
136 | |
137 | #define partial_len data[0] /* length of partial packet data */ |
138 | #define partial_rem data[1] /* remaining (missing) data length */ |
139 | #define partial_data data[2] /* partial packet data */ |
140 | |
141 | static int cx82310_bind(struct usbnet *dev, struct usb_interface *intf) |
142 | { |
143 | int ret; |
144 | char buf[15]; |
145 | struct usb_device *udev = dev->udev; |
146 | u8 link[3]; |
147 | int timeout = 50; |
148 | struct cx82310_priv *priv; |
149 | u8 addr[ETH_ALEN]; |
150 | |
151 | /* avoid ADSL modems - continue only if iProduct is "USB NET CARD" */ |
152 | if (usb_string(dev: udev, index: udev->descriptor.iProduct, buf, size: sizeof(buf)) > 0 |
153 | && strcmp(buf, "USB NET CARD" )) { |
154 | dev_info(&udev->dev, "ignoring: probably an ADSL modem\n" ); |
155 | return -ENODEV; |
156 | } |
157 | |
158 | ret = usbnet_get_endpoints(dev, intf); |
159 | if (ret) |
160 | return ret; |
161 | |
162 | /* |
163 | * this must not include ethernet header as the device can send partial |
164 | * packets with no header (and sometimes even empty URBs) |
165 | */ |
166 | dev->net->hard_header_len = 0; |
167 | /* we can send at most 1514 bytes of data (+ 2-byte header) per URB */ |
168 | dev->hard_mtu = CX82310_MTU + 2; |
169 | /* we can receive URBs up to 4KB from the device */ |
170 | dev->rx_urb_size = 4096; |
171 | |
172 | dev->partial_data = (unsigned long) kmalloc(size: dev->hard_mtu, GFP_KERNEL); |
173 | if (!dev->partial_data) |
174 | return -ENOMEM; |
175 | |
176 | priv = kzalloc(size: sizeof(*priv), GFP_KERNEL); |
177 | if (!priv) { |
178 | ret = -ENOMEM; |
179 | goto err_partial; |
180 | } |
181 | dev->driver_priv = priv; |
182 | INIT_WORK(&priv->reenable_work, cx82310_reenable_work); |
183 | priv->dev = dev; |
184 | |
185 | /* wait for firmware to become ready (indicated by the link being up) */ |
186 | while (--timeout) { |
187 | ret = cx82310_cmd(dev, cmd: CMD_GET_LINK_STATUS, reply: true, NULL, wlen: 0, |
188 | rdata: link, rlen: sizeof(link)); |
189 | /* the command can time out during boot - it's not an error */ |
190 | if (!ret && link[0] == 1 && link[2] == 1) |
191 | break; |
192 | msleep(msecs: 500); |
193 | } |
194 | if (!timeout) { |
195 | netdev_err(dev: dev->net, format: "firmware not ready in time\n" ); |
196 | ret = -ETIMEDOUT; |
197 | goto err; |
198 | } |
199 | |
200 | /* enable ethernet mode (?) */ |
201 | ret = cx82310_enable_ethernet(dev); |
202 | if (ret) |
203 | goto err; |
204 | |
205 | /* get the MAC address */ |
206 | ret = cx82310_cmd(dev, cmd: CMD_GET_MAC_ADDR, reply: true, NULL, wlen: 0, rdata: addr, ETH_ALEN); |
207 | if (ret) { |
208 | netdev_err(dev: dev->net, format: "unable to read MAC address: %d\n" , ret); |
209 | goto err; |
210 | } |
211 | eth_hw_addr_set(dev: dev->net, addr); |
212 | |
213 | /* start (does not seem to have any effect?) */ |
214 | ret = cx82310_cmd(dev, cmd: CMD_START, reply: false, NULL, wlen: 0, NULL, rlen: 0); |
215 | if (ret) |
216 | goto err; |
217 | |
218 | return 0; |
219 | err: |
220 | kfree(objp: dev->driver_priv); |
221 | err_partial: |
222 | kfree(objp: (void *)dev->partial_data); |
223 | return ret; |
224 | } |
225 | |
226 | static void cx82310_unbind(struct usbnet *dev, struct usb_interface *intf) |
227 | { |
228 | struct cx82310_priv *priv = dev->driver_priv; |
229 | |
230 | kfree(objp: (void *)dev->partial_data); |
231 | cancel_work_sync(work: &priv->reenable_work); |
232 | kfree(objp: dev->driver_priv); |
233 | } |
234 | |
235 | /* |
236 | * RX is NOT easy - we can receive multiple packets per skb, each having 2-byte |
237 | * packet length at the beginning. |
238 | * The last packet might be incomplete (when it crosses the 4KB URB size), |
239 | * continuing in the next skb (without any headers). |
240 | * If a packet has odd length, there is one extra byte at the end (before next |
241 | * packet or at the end of the URB). |
242 | */ |
243 | static int cx82310_rx_fixup(struct usbnet *dev, struct sk_buff *skb) |
244 | { |
245 | int len; |
246 | struct sk_buff *skb2; |
247 | struct cx82310_priv *priv = dev->driver_priv; |
248 | |
249 | /* |
250 | * If the last skb ended with an incomplete packet, this skb contains |
251 | * end of that packet at the beginning. |
252 | */ |
253 | if (dev->partial_rem) { |
254 | len = dev->partial_len + dev->partial_rem; |
255 | skb2 = alloc_skb(size: len, GFP_ATOMIC); |
256 | if (!skb2) |
257 | return 0; |
258 | skb_put(skb: skb2, len); |
259 | memcpy(skb2->data, (void *)dev->partial_data, |
260 | dev->partial_len); |
261 | memcpy(skb2->data + dev->partial_len, skb->data, |
262 | dev->partial_rem); |
263 | usbnet_skb_return(dev, skb2); |
264 | skb_pull(skb, len: (dev->partial_rem + 1) & ~1); |
265 | dev->partial_rem = 0; |
266 | if (skb->len < 2) |
267 | return 1; |
268 | } |
269 | |
270 | /* a skb can contain multiple packets */ |
271 | while (skb->len > 1) { |
272 | /* first two bytes are packet length */ |
273 | len = skb->data[0] | (skb->data[1] << 8); |
274 | skb_pull(skb, len: 2); |
275 | |
276 | /* if last packet in the skb, let usbnet to process it */ |
277 | if (len == skb->len || len + 1 == skb->len) { |
278 | skb_trim(skb, len); |
279 | break; |
280 | } |
281 | |
282 | if (len == 0xffff) { |
283 | netdev_info(dev: dev->net, format: "router was rebooted, re-enabling ethernet mode" ); |
284 | schedule_work(work: &priv->reenable_work); |
285 | } else if (len > CX82310_MTU) { |
286 | netdev_err(dev: dev->net, format: "RX packet too long: %d B\n" , len); |
287 | return 0; |
288 | } |
289 | |
290 | /* incomplete packet, save it for the next skb */ |
291 | if (len > skb->len) { |
292 | dev->partial_len = skb->len; |
293 | dev->partial_rem = len - skb->len; |
294 | memcpy((void *)dev->partial_data, skb->data, |
295 | dev->partial_len); |
296 | skb_pull(skb, len: skb->len); |
297 | break; |
298 | } |
299 | |
300 | skb2 = alloc_skb(size: len, GFP_ATOMIC); |
301 | if (!skb2) |
302 | return 0; |
303 | skb_put(skb: skb2, len); |
304 | memcpy(skb2->data, skb->data, len); |
305 | /* process the packet */ |
306 | usbnet_skb_return(dev, skb2); |
307 | |
308 | skb_pull(skb, len: (len + 1) & ~1); |
309 | } |
310 | |
311 | /* let usbnet process the last packet */ |
312 | return 1; |
313 | } |
314 | |
315 | /* TX is easy, just add 2 bytes of length at the beginning */ |
316 | static struct sk_buff *cx82310_tx_fixup(struct usbnet *dev, struct sk_buff *skb, |
317 | gfp_t flags) |
318 | { |
319 | int len = skb->len; |
320 | |
321 | if (skb_cow_head(skb, headroom: 2)) { |
322 | dev_kfree_skb_any(skb); |
323 | return NULL; |
324 | } |
325 | skb_push(skb, len: 2); |
326 | |
327 | skb->data[0] = len; |
328 | skb->data[1] = len >> 8; |
329 | |
330 | return skb; |
331 | } |
332 | |
333 | |
334 | static const struct driver_info cx82310_info = { |
335 | .description = "Conexant CX82310 USB ethernet" , |
336 | .flags = FLAG_ETHER, |
337 | .bind = cx82310_bind, |
338 | .unbind = cx82310_unbind, |
339 | .rx_fixup = cx82310_rx_fixup, |
340 | .tx_fixup = cx82310_tx_fixup, |
341 | }; |
342 | |
343 | #define USB_DEVICE_CLASS(vend, prod, cl, sc, pr) \ |
344 | .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ |
345 | USB_DEVICE_ID_MATCH_DEV_INFO, \ |
346 | .idVendor = (vend), \ |
347 | .idProduct = (prod), \ |
348 | .bDeviceClass = (cl), \ |
349 | .bDeviceSubClass = (sc), \ |
350 | .bDeviceProtocol = (pr) |
351 | |
352 | static const struct usb_device_id products[] = { |
353 | { |
354 | USB_DEVICE_CLASS(0x0572, 0xcb01, 0xff, 0, 0), |
355 | .driver_info = (unsigned long) &cx82310_info |
356 | }, |
357 | { }, |
358 | }; |
359 | MODULE_DEVICE_TABLE(usb, products); |
360 | |
361 | static struct usb_driver cx82310_driver = { |
362 | .name = "cx82310_eth" , |
363 | .id_table = products, |
364 | .probe = usbnet_probe, |
365 | .disconnect = usbnet_disconnect, |
366 | .suspend = usbnet_suspend, |
367 | .resume = usbnet_resume, |
368 | .disable_hub_initiated_lpm = 1, |
369 | }; |
370 | |
371 | module_usb_driver(cx82310_driver); |
372 | |
373 | MODULE_AUTHOR("Ondrej Zary" ); |
374 | MODULE_DESCRIPTION("Conexant CX82310-based ADSL router USB ethernet driver" ); |
375 | MODULE_LICENSE("GPL" ); |
376 | |