]>
Commit | Line | Data |
---|---|---|
9f722c09 OL |
1 | /* |
2 | * USB CDC EEM network interface driver | |
3 | * Copyright (C) 2009 Oberthur Technologies | |
4 | * by Omar Laazimani, Olivier Condemine | |
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/ctype.h> | |
26 | #include <linux/ethtool.h> | |
27 | #include <linux/workqueue.h> | |
28 | #include <linux/mii.h> | |
29 | #include <linux/usb.h> | |
30 | #include <linux/crc32.h> | |
31 | #include <linux/usb/cdc.h> | |
32 | #include <linux/usb/usbnet.h> | |
5a0e3ad6 | 33 | #include <linux/gfp.h> |
9f722c09 OL |
34 | |
35 | ||
36 | /* | |
37 | * This driver is an implementation of the CDC "Ethernet Emulation | |
38 | * Model" (EEM) specification, which encapsulates Ethernet frames | |
39 | * for transport over USB using a simpler USB device model than the | |
40 | * previous CDC "Ethernet Control Model" (ECM, or "CDC Ethernet"). | |
41 | * | |
42 | * For details, see www.usb.org/developers/devclass_docs/CDC_EEM10.pdf | |
43 | * | |
44 | * This version has been tested with GIGAntIC WuaoW SIM Smart Card on 2.6.24, | |
45 | * 2.6.27 and 2.6.30rc2 kernel. | |
46 | * It has also been validated on Openmoko Om 2008.12 (based on 2.6.24 kernel). | |
47 | * build on 23-April-2009 | |
48 | */ | |
49 | ||
50 | #define EEM_HEAD 2 /* 2 byte header */ | |
51 | ||
52 | /*-------------------------------------------------------------------------*/ | |
53 | ||
54 | static void eem_linkcmd_complete(struct urb *urb) | |
55 | { | |
56 | dev_kfree_skb(urb->context); | |
57 | usb_free_urb(urb); | |
58 | } | |
59 | ||
60 | static void eem_linkcmd(struct usbnet *dev, struct sk_buff *skb) | |
61 | { | |
62 | struct urb *urb; | |
63 | int status; | |
64 | ||
65 | urb = usb_alloc_urb(0, GFP_ATOMIC); | |
66 | if (!urb) | |
67 | goto fail; | |
68 | ||
69 | usb_fill_bulk_urb(urb, dev->udev, dev->out, | |
70 | skb->data, skb->len, eem_linkcmd_complete, skb); | |
71 | ||
72 | status = usb_submit_urb(urb, GFP_ATOMIC); | |
73 | if (status) { | |
74 | usb_free_urb(urb); | |
75 | fail: | |
76 | dev_kfree_skb(skb); | |
60b86755 | 77 | netdev_warn(dev->net, "link cmd failure\n"); |
9f722c09 OL |
78 | return; |
79 | } | |
80 | } | |
81 | ||
82 | static int eem_bind(struct usbnet *dev, struct usb_interface *intf) | |
83 | { | |
84 | int status = 0; | |
85 | ||
86 | status = usbnet_get_endpoints(dev, intf); | |
87 | if (status < 0) { | |
88 | usb_set_intfdata(intf, NULL); | |
89 | usb_driver_release_interface(driver_of(intf), intf); | |
90 | return status; | |
91 | } | |
92 | ||
93 | /* no jumbogram (16K) support for now */ | |
94 | ||
95 | dev->net->hard_header_len += EEM_HEAD + ETH_FCS_LEN; | |
96 | ||
97 | return 0; | |
98 | } | |
99 | ||
100 | /* | |
101 | * EEM permits packing multiple Ethernet frames into USB transfers | |
102 | * (a "bundle"), but for TX we don't try to do that. | |
103 | */ | |
104 | static struct sk_buff *eem_tx_fixup(struct usbnet *dev, struct sk_buff *skb, | |
105 | gfp_t flags) | |
106 | { | |
107 | struct sk_buff *skb2 = NULL; | |
108 | u16 len = skb->len; | |
109 | u32 crc = 0; | |
110 | int padlen = 0; | |
111 | ||
112 | /* When ((len + EEM_HEAD + ETH_FCS_LEN) % dev->maxpacket) is | |
113 | * zero, stick two bytes of zero length EEM packet on the end. | |
114 | * Else the framework would add invalid single byte padding, | |
115 | * since it can't know whether ZLPs will be handled right by | |
116 | * all the relevant hardware and software. | |
117 | */ | |
118 | if (!((len + EEM_HEAD + ETH_FCS_LEN) % dev->maxpacket)) | |
119 | padlen += 2; | |
120 | ||
121 | if (!skb_cloned(skb)) { | |
122 | int headroom = skb_headroom(skb); | |
123 | int tailroom = skb_tailroom(skb); | |
124 | ||
8e95a202 JP |
125 | if ((tailroom >= ETH_FCS_LEN + padlen) && |
126 | (headroom >= EEM_HEAD)) | |
9f722c09 OL |
127 | goto done; |
128 | ||
129 | if ((headroom + tailroom) | |
130 | > (EEM_HEAD + ETH_FCS_LEN + padlen)) { | |
131 | skb->data = memmove(skb->head + | |
132 | EEM_HEAD, | |
133 | skb->data, | |
134 | skb->len); | |
135 | skb_set_tail_pointer(skb, len); | |
136 | goto done; | |
137 | } | |
138 | } | |
139 | ||
140 | skb2 = skb_copy_expand(skb, EEM_HEAD, ETH_FCS_LEN + padlen, flags); | |
141 | if (!skb2) | |
142 | return NULL; | |
143 | ||
144 | dev_kfree_skb_any(skb); | |
145 | skb = skb2; | |
146 | ||
147 | done: | |
148 | /* we don't use the "no Ethernet CRC" option */ | |
149 | crc = crc32_le(~0, skb->data, skb->len); | |
150 | crc = ~crc; | |
151 | ||
152 | put_unaligned_le32(crc, skb_put(skb, 4)); | |
153 | ||
154 | /* EEM packet header format: | |
155 | * b0..13: length of ethernet frame | |
156 | * b14: bmCRC (1 == valid Ethernet CRC) | |
157 | * b15: bmType (0 == data) | |
158 | */ | |
159 | len = skb->len; | |
160 | put_unaligned_le16(BIT(14) | len, skb_push(skb, 2)); | |
161 | ||
162 | /* Bundle a zero length EEM packet if needed */ | |
163 | if (padlen) | |
164 | put_unaligned_le16(0, skb_put(skb, 2)); | |
165 | ||
166 | return skb; | |
167 | } | |
168 | ||
169 | static int eem_rx_fixup(struct usbnet *dev, struct sk_buff *skb) | |
170 | { | |
171 | /* | |
172 | * Our task here is to strip off framing, leaving skb with one | |
173 | * data frame for the usbnet framework code to process. But we | |
174 | * may have received multiple EEM payloads, or command payloads. | |
175 | * So we must process _everything_ as if it's a header, except | |
176 | * maybe the last data payload | |
177 | * | |
178 | * REVISIT the framework needs updating so that when we consume | |
179 | * all payloads (the last or only message was a command, or a | |
180 | * zero length EEM packet) that is not accounted as an rx_error. | |
181 | */ | |
182 | do { | |
183 | struct sk_buff *skb2 = NULL; | |
184 | u16 header; | |
185 | u16 len = 0; | |
186 | ||
187 | /* incomplete EEM header? */ | |
188 | if (skb->len < EEM_HEAD) | |
189 | return 0; | |
190 | ||
191 | /* | |
192 | * EEM packet header format: | |
25985edc | 193 | * b0..14: EEM type dependent (Data or Command) |
9f722c09 OL |
194 | * b15: bmType |
195 | */ | |
196 | header = get_unaligned_le16(skb->data); | |
197 | skb_pull(skb, EEM_HEAD); | |
198 | ||
199 | /* | |
200 | * The bmType bit helps to denote when EEM | |
201 | * packet is data or command : | |
202 | * bmType = 0 : EEM data payload | |
203 | * bmType = 1 : EEM (link) command | |
204 | */ | |
205 | if (header & BIT(15)) { | |
206 | u16 bmEEMCmd; | |
207 | ||
208 | /* | |
209 | * EEM (link) command packet: | |
210 | * b0..10: bmEEMCmdParam | |
211 | * b11..13: bmEEMCmd | |
212 | * b14: bmReserved (must be 0) | |
213 | * b15: 1 (EEM command) | |
214 | */ | |
215 | if (header & BIT(14)) { | |
60b86755 JP |
216 | netdev_dbg(dev->net, "reserved command %04x\n", |
217 | header); | |
9f722c09 OL |
218 | continue; |
219 | } | |
220 | ||
221 | bmEEMCmd = (header >> 11) & 0x7; | |
222 | switch (bmEEMCmd) { | |
223 | ||
224 | /* Responding to echo requests is mandatory. */ | |
225 | case 0: /* Echo command */ | |
226 | len = header & 0x7FF; | |
227 | ||
228 | /* bogus command? */ | |
229 | if (skb->len < len) | |
230 | return 0; | |
231 | ||
232 | skb2 = skb_clone(skb, GFP_ATOMIC); | |
233 | if (unlikely(!skb2)) | |
234 | goto next; | |
235 | skb_trim(skb2, len); | |
236 | put_unaligned_le16(BIT(15) | (1 << 11) | len, | |
237 | skb_push(skb2, 2)); | |
238 | eem_linkcmd(dev, skb2); | |
239 | break; | |
240 | ||
241 | /* | |
242 | * Host may choose to ignore hints. | |
243 | * - suspend: peripheral ready to suspend | |
244 | * - response: suggest N millisec polling | |
245 | * - response complete: suggest N sec polling | |
246 | */ | |
247 | case 2: /* Suspend hint */ | |
248 | case 3: /* Response hint */ | |
249 | case 4: /* Response complete hint */ | |
250 | continue; | |
251 | ||
252 | /* | |
253 | * Hosts should never receive host-to-peripheral | |
254 | * or reserved command codes; or responses to an | |
255 | * echo command we didn't send. | |
256 | */ | |
257 | case 1: /* Echo response */ | |
258 | case 5: /* Tickle */ | |
259 | default: /* reserved */ | |
60b86755 JP |
260 | netdev_warn(dev->net, |
261 | "unexpected link command %d\n", | |
262 | bmEEMCmd); | |
9f722c09 OL |
263 | continue; |
264 | } | |
265 | ||
266 | } else { | |
267 | u32 crc, crc2; | |
268 | int is_last; | |
269 | ||
270 | /* zero length EEM packet? */ | |
271 | if (header == 0) | |
272 | continue; | |
273 | ||
274 | /* | |
275 | * EEM data packet header : | |
276 | * b0..13: length of ethernet frame | |
277 | * b14: bmCRC | |
278 | * b15: 0 (EEM data) | |
279 | */ | |
280 | len = header & 0x3FFF; | |
281 | ||
282 | /* bogus EEM payload? */ | |
283 | if (skb->len < len) | |
284 | return 0; | |
285 | ||
286 | /* bogus ethernet frame? */ | |
287 | if (len < (ETH_HLEN + ETH_FCS_LEN)) | |
288 | goto next; | |
289 | ||
290 | /* | |
291 | * Treat the last payload differently: framework | |
292 | * code expects our "fixup" to have stripped off | |
293 | * headers, so "skb" is a data packet (or error). | |
294 | * Else if it's not the last payload, keep "skb" | |
295 | * for further processing. | |
296 | */ | |
297 | is_last = (len == skb->len); | |
298 | if (is_last) | |
299 | skb2 = skb; | |
300 | else { | |
301 | skb2 = skb_clone(skb, GFP_ATOMIC); | |
302 | if (unlikely(!skb2)) | |
303 | return 0; | |
304 | } | |
305 | ||
9f722c09 OL |
306 | /* |
307 | * The bmCRC helps to denote when the CRC field in | |
308 | * the Ethernet frame contains a calculated CRC: | |
309 | * bmCRC = 1 : CRC is calculated | |
310 | * bmCRC = 0 : CRC = 0xDEADBEEF | |
311 | */ | |
9ca33a0f BN |
312 | if (header & BIT(14)) { |
313 | crc = get_unaligned_le32(skb2->data | |
314 | + len - ETH_FCS_LEN); | |
315 | crc2 = ~crc32_le(~0, skb2->data, skb2->len | |
316 | - ETH_FCS_LEN); | |
317 | } else { | |
318 | crc = get_unaligned_be32(skb2->data | |
319 | + len - ETH_FCS_LEN); | |
9f722c09 | 320 | crc2 = 0xdeadbeef; |
9ca33a0f BN |
321 | } |
322 | skb_trim(skb2, len - ETH_FCS_LEN); | |
9f722c09 OL |
323 | |
324 | if (is_last) | |
325 | return crc == crc2; | |
326 | ||
327 | if (unlikely(crc != crc2)) { | |
eaea43ab | 328 | dev->net->stats.rx_errors++; |
9f722c09 OL |
329 | dev_kfree_skb_any(skb2); |
330 | } else | |
331 | usbnet_skb_return(dev, skb2); | |
332 | } | |
333 | ||
334 | next: | |
335 | skb_pull(skb, len); | |
336 | } while (skb->len); | |
337 | ||
338 | return 1; | |
339 | } | |
340 | ||
341 | static const struct driver_info eem_info = { | |
342 | .description = "CDC EEM Device", | |
343 | .flags = FLAG_ETHER, | |
344 | .bind = eem_bind, | |
345 | .rx_fixup = eem_rx_fixup, | |
346 | .tx_fixup = eem_tx_fixup, | |
347 | }; | |
348 | ||
349 | /*-------------------------------------------------------------------------*/ | |
350 | ||
351 | static const struct usb_device_id products[] = { | |
352 | { | |
353 | USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_EEM, | |
354 | USB_CDC_PROTO_EEM), | |
355 | .driver_info = (unsigned long) &eem_info, | |
356 | }, | |
357 | { | |
358 | /* EMPTY == end of list */ | |
359 | }, | |
360 | }; | |
361 | MODULE_DEVICE_TABLE(usb, products); | |
362 | ||
363 | static struct usb_driver eem_driver = { | |
364 | .name = "cdc_eem", | |
365 | .id_table = products, | |
366 | .probe = usbnet_probe, | |
367 | .disconnect = usbnet_disconnect, | |
368 | .suspend = usbnet_suspend, | |
369 | .resume = usbnet_resume, | |
370 | }; | |
371 | ||
372 | ||
373 | static int __init eem_init(void) | |
374 | { | |
375 | return usb_register(&eem_driver); | |
376 | } | |
377 | module_init(eem_init); | |
378 | ||
379 | static void __exit eem_exit(void) | |
380 | { | |
381 | usb_deregister(&eem_driver); | |
382 | } | |
383 | module_exit(eem_exit); | |
384 | ||
385 | MODULE_AUTHOR("Omar Laazimani <omar.oberthur@gmail.com>"); | |
386 | MODULE_DESCRIPTION("USB CDC EEM"); | |
387 | MODULE_LICENSE("GPL"); |