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; | |
78fb72f7 | 96 | dev->hard_mtu = dev->net->mtu + dev->net->hard_header_len; |
9f722c09 OL |
97 | |
98 | return 0; | |
99 | } | |
100 | ||
101 | /* | |
102 | * EEM permits packing multiple Ethernet frames into USB transfers | |
103 | * (a "bundle"), but for TX we don't try to do that. | |
104 | */ | |
105 | static struct sk_buff *eem_tx_fixup(struct usbnet *dev, struct sk_buff *skb, | |
106 | gfp_t flags) | |
107 | { | |
108 | struct sk_buff *skb2 = NULL; | |
109 | u16 len = skb->len; | |
110 | u32 crc = 0; | |
111 | int padlen = 0; | |
112 | ||
113 | /* When ((len + EEM_HEAD + ETH_FCS_LEN) % dev->maxpacket) is | |
114 | * zero, stick two bytes of zero length EEM packet on the end. | |
115 | * Else the framework would add invalid single byte padding, | |
116 | * since it can't know whether ZLPs will be handled right by | |
117 | * all the relevant hardware and software. | |
118 | */ | |
119 | if (!((len + EEM_HEAD + ETH_FCS_LEN) % dev->maxpacket)) | |
120 | padlen += 2; | |
121 | ||
122 | if (!skb_cloned(skb)) { | |
123 | int headroom = skb_headroom(skb); | |
124 | int tailroom = skb_tailroom(skb); | |
125 | ||
8e95a202 JP |
126 | if ((tailroom >= ETH_FCS_LEN + padlen) && |
127 | (headroom >= EEM_HEAD)) | |
9f722c09 OL |
128 | goto done; |
129 | ||
130 | if ((headroom + tailroom) | |
131 | > (EEM_HEAD + ETH_FCS_LEN + padlen)) { | |
132 | skb->data = memmove(skb->head + | |
133 | EEM_HEAD, | |
134 | skb->data, | |
135 | skb->len); | |
136 | skb_set_tail_pointer(skb, len); | |
137 | goto done; | |
138 | } | |
139 | } | |
140 | ||
141 | skb2 = skb_copy_expand(skb, EEM_HEAD, ETH_FCS_LEN + padlen, flags); | |
142 | if (!skb2) | |
143 | return NULL; | |
144 | ||
145 | dev_kfree_skb_any(skb); | |
146 | skb = skb2; | |
147 | ||
148 | done: | |
149 | /* we don't use the "no Ethernet CRC" option */ | |
150 | crc = crc32_le(~0, skb->data, skb->len); | |
151 | crc = ~crc; | |
152 | ||
153 | put_unaligned_le32(crc, skb_put(skb, 4)); | |
154 | ||
155 | /* EEM packet header format: | |
156 | * b0..13: length of ethernet frame | |
157 | * b14: bmCRC (1 == valid Ethernet CRC) | |
158 | * b15: bmType (0 == data) | |
159 | */ | |
160 | len = skb->len; | |
161 | put_unaligned_le16(BIT(14) | len, skb_push(skb, 2)); | |
162 | ||
163 | /* Bundle a zero length EEM packet if needed */ | |
164 | if (padlen) | |
165 | put_unaligned_le16(0, skb_put(skb, 2)); | |
166 | ||
167 | return skb; | |
168 | } | |
169 | ||
170 | static int eem_rx_fixup(struct usbnet *dev, struct sk_buff *skb) | |
171 | { | |
172 | /* | |
173 | * Our task here is to strip off framing, leaving skb with one | |
174 | * data frame for the usbnet framework code to process. But we | |
175 | * may have received multiple EEM payloads, or command payloads. | |
176 | * So we must process _everything_ as if it's a header, except | |
177 | * maybe the last data payload | |
178 | * | |
179 | * REVISIT the framework needs updating so that when we consume | |
180 | * all payloads (the last or only message was a command, or a | |
181 | * zero length EEM packet) that is not accounted as an rx_error. | |
182 | */ | |
183 | do { | |
184 | struct sk_buff *skb2 = NULL; | |
185 | u16 header; | |
186 | u16 len = 0; | |
187 | ||
188 | /* incomplete EEM header? */ | |
189 | if (skb->len < EEM_HEAD) | |
190 | return 0; | |
191 | ||
192 | /* | |
193 | * EEM packet header format: | |
25985edc | 194 | * b0..14: EEM type dependent (Data or Command) |
9f722c09 OL |
195 | * b15: bmType |
196 | */ | |
197 | header = get_unaligned_le16(skb->data); | |
198 | skb_pull(skb, EEM_HEAD); | |
199 | ||
200 | /* | |
201 | * The bmType bit helps to denote when EEM | |
202 | * packet is data or command : | |
203 | * bmType = 0 : EEM data payload | |
204 | * bmType = 1 : EEM (link) command | |
205 | */ | |
206 | if (header & BIT(15)) { | |
207 | u16 bmEEMCmd; | |
208 | ||
209 | /* | |
210 | * EEM (link) command packet: | |
211 | * b0..10: bmEEMCmdParam | |
212 | * b11..13: bmEEMCmd | |
213 | * b14: bmReserved (must be 0) | |
214 | * b15: 1 (EEM command) | |
215 | */ | |
216 | if (header & BIT(14)) { | |
60b86755 JP |
217 | netdev_dbg(dev->net, "reserved command %04x\n", |
218 | header); | |
9f722c09 OL |
219 | continue; |
220 | } | |
221 | ||
222 | bmEEMCmd = (header >> 11) & 0x7; | |
223 | switch (bmEEMCmd) { | |
224 | ||
225 | /* Responding to echo requests is mandatory. */ | |
226 | case 0: /* Echo command */ | |
227 | len = header & 0x7FF; | |
228 | ||
229 | /* bogus command? */ | |
230 | if (skb->len < len) | |
231 | return 0; | |
232 | ||
233 | skb2 = skb_clone(skb, GFP_ATOMIC); | |
234 | if (unlikely(!skb2)) | |
235 | goto next; | |
236 | skb_trim(skb2, len); | |
237 | put_unaligned_le16(BIT(15) | (1 << 11) | len, | |
238 | skb_push(skb2, 2)); | |
239 | eem_linkcmd(dev, skb2); | |
240 | break; | |
241 | ||
242 | /* | |
243 | * Host may choose to ignore hints. | |
244 | * - suspend: peripheral ready to suspend | |
245 | * - response: suggest N millisec polling | |
246 | * - response complete: suggest N sec polling | |
247 | */ | |
248 | case 2: /* Suspend hint */ | |
249 | case 3: /* Response hint */ | |
250 | case 4: /* Response complete hint */ | |
251 | continue; | |
252 | ||
253 | /* | |
254 | * Hosts should never receive host-to-peripheral | |
255 | * or reserved command codes; or responses to an | |
256 | * echo command we didn't send. | |
257 | */ | |
258 | case 1: /* Echo response */ | |
259 | case 5: /* Tickle */ | |
260 | default: /* reserved */ | |
60b86755 JP |
261 | netdev_warn(dev->net, |
262 | "unexpected link command %d\n", | |
263 | bmEEMCmd); | |
9f722c09 OL |
264 | continue; |
265 | } | |
266 | ||
267 | } else { | |
268 | u32 crc, crc2; | |
269 | int is_last; | |
270 | ||
271 | /* zero length EEM packet? */ | |
272 | if (header == 0) | |
273 | continue; | |
274 | ||
275 | /* | |
276 | * EEM data packet header : | |
277 | * b0..13: length of ethernet frame | |
278 | * b14: bmCRC | |
279 | * b15: 0 (EEM data) | |
280 | */ | |
281 | len = header & 0x3FFF; | |
282 | ||
283 | /* bogus EEM payload? */ | |
284 | if (skb->len < len) | |
285 | return 0; | |
286 | ||
287 | /* bogus ethernet frame? */ | |
288 | if (len < (ETH_HLEN + ETH_FCS_LEN)) | |
289 | goto next; | |
290 | ||
291 | /* | |
292 | * Treat the last payload differently: framework | |
293 | * code expects our "fixup" to have stripped off | |
294 | * headers, so "skb" is a data packet (or error). | |
295 | * Else if it's not the last payload, keep "skb" | |
296 | * for further processing. | |
297 | */ | |
298 | is_last = (len == skb->len); | |
299 | if (is_last) | |
300 | skb2 = skb; | |
301 | else { | |
302 | skb2 = skb_clone(skb, GFP_ATOMIC); | |
303 | if (unlikely(!skb2)) | |
304 | return 0; | |
305 | } | |
306 | ||
9f722c09 OL |
307 | /* |
308 | * The bmCRC helps to denote when the CRC field in | |
309 | * the Ethernet frame contains a calculated CRC: | |
310 | * bmCRC = 1 : CRC is calculated | |
311 | * bmCRC = 0 : CRC = 0xDEADBEEF | |
312 | */ | |
9ca33a0f BN |
313 | if (header & BIT(14)) { |
314 | crc = get_unaligned_le32(skb2->data | |
315 | + len - ETH_FCS_LEN); | |
316 | crc2 = ~crc32_le(~0, skb2->data, skb2->len | |
317 | - ETH_FCS_LEN); | |
318 | } else { | |
319 | crc = get_unaligned_be32(skb2->data | |
320 | + len - ETH_FCS_LEN); | |
9f722c09 | 321 | crc2 = 0xdeadbeef; |
9ca33a0f BN |
322 | } |
323 | skb_trim(skb2, len - ETH_FCS_LEN); | |
9f722c09 OL |
324 | |
325 | if (is_last) | |
326 | return crc == crc2; | |
327 | ||
328 | if (unlikely(crc != crc2)) { | |
eaea43ab | 329 | dev->net->stats.rx_errors++; |
9f722c09 OL |
330 | dev_kfree_skb_any(skb2); |
331 | } else | |
332 | usbnet_skb_return(dev, skb2); | |
333 | } | |
334 | ||
335 | next: | |
336 | skb_pull(skb, len); | |
337 | } while (skb->len); | |
338 | ||
339 | return 1; | |
340 | } | |
341 | ||
342 | static const struct driver_info eem_info = { | |
343 | .description = "CDC EEM Device", | |
c261344d | 344 | .flags = FLAG_ETHER | FLAG_POINTTOPOINT, |
9f722c09 OL |
345 | .bind = eem_bind, |
346 | .rx_fixup = eem_rx_fixup, | |
347 | .tx_fixup = eem_tx_fixup, | |
348 | }; | |
349 | ||
350 | /*-------------------------------------------------------------------------*/ | |
351 | ||
352 | static const struct usb_device_id products[] = { | |
353 | { | |
354 | USB_INTERFACE_INFO(USB_CLASS_COMM, USB_CDC_SUBCLASS_EEM, | |
355 | USB_CDC_PROTO_EEM), | |
356 | .driver_info = (unsigned long) &eem_info, | |
357 | }, | |
358 | { | |
359 | /* EMPTY == end of list */ | |
360 | }, | |
361 | }; | |
362 | MODULE_DEVICE_TABLE(usb, products); | |
363 | ||
364 | static struct usb_driver eem_driver = { | |
365 | .name = "cdc_eem", | |
366 | .id_table = products, | |
367 | .probe = usbnet_probe, | |
368 | .disconnect = usbnet_disconnect, | |
369 | .suspend = usbnet_suspend, | |
370 | .resume = usbnet_resume, | |
e1f12eb6 | 371 | .disable_hub_initiated_lpm = 1, |
9f722c09 OL |
372 | }; |
373 | ||
d632eb1b | 374 | module_usb_driver(eem_driver); |
9f722c09 OL |
375 | |
376 | MODULE_AUTHOR("Omar Laazimani <omar.oberthur@gmail.com>"); | |
377 | MODULE_DESCRIPTION("USB CDC EEM"); | |
378 | MODULE_LICENSE("GPL"); |