diff options
author | David Brownell <dbrownell@users.sourceforge.net> | 2008-06-19 21:19:32 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2008-07-21 18:16:13 -0400 |
commit | 8a40819e97368f2b6e67fea103348f9fc2f68ceb (patch) | |
tree | f1b7b38ecb63adcd5220ff62eb21551fdaa60ed6 /drivers/usb/gadget/f_subset.c | |
parent | 2b3d942c4878084a37991a65e66512c02b8fa2ad (diff) |
usb ethernet gadget: split CDC Subset function
This is a simple "CDC Subset" (and MCCI "SAFE") function driver, extracted
from the all-in-one Ethernet gadget driver.
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/gadget/f_subset.c')
-rw-r--r-- | drivers/usb/gadget/f_subset.c | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/drivers/usb/gadget/f_subset.c b/drivers/usb/gadget/f_subset.c new file mode 100644 index 000000000000..afeab9a0523f --- /dev/null +++ b/drivers/usb/gadget/f_subset.c | |||
@@ -0,0 +1,423 @@ | |||
1 | /* | ||
2 | * f_subset.c -- "CDC Subset" Ethernet link function driver | ||
3 | * | ||
4 | * Copyright (C) 2003-2005,2008 David Brownell | ||
5 | * Copyright (C) 2008 Nokia Corporation | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation; either version 2 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program; if not, write to the Free Software | ||
19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
20 | */ | ||
21 | |||
22 | #include <linux/kernel.h> | ||
23 | #include <linux/device.h> | ||
24 | #include <linux/etherdevice.h> | ||
25 | |||
26 | #include "u_ether.h" | ||
27 | |||
28 | |||
29 | /* | ||
30 | * This function packages a simple "CDC Subset" Ethernet port with no real | ||
31 | * control mechanisms; just raw data transfer over two bulk endpoints. | ||
32 | * The data transfer model is exactly that of CDC Ethernet, which is | ||
33 | * why we call it the "CDC Subset". | ||
34 | * | ||
35 | * Because it's not standardized, this has some interoperability issues. | ||
36 | * They mostly relate to driver binding, since the data transfer model is | ||
37 | * so simple (CDC Ethernet). The original versions of this protocol used | ||
38 | * specific product/vendor IDs: byteswapped IDs for Digital Equipment's | ||
39 | * SA-1100 "Itsy" board, which could run Linux 2.4 kernels and supported | ||
40 | * daughtercards with USB peripheral connectors. (It was used more often | ||
41 | * with other boards, using the Itsy identifiers.) Linux hosts recognized | ||
42 | * this with CONFIG_USB_ARMLINUX; these devices have only one configuration | ||
43 | * and one interface. | ||
44 | * | ||
45 | * At some point, MCCI defined a (nonconformant) CDC MDLM variant called | ||
46 | * "SAFE", which happens to have a mode which is identical to the "CDC | ||
47 | * Subset" in terms of data transfer and lack of control model. This was | ||
48 | * adopted by later Sharp Zaurus models, and by some other software which | ||
49 | * Linux hosts recognize with CONFIG_USB_NET_ZAURUS. | ||
50 | * | ||
51 | * Because Microsoft's RNDIS drivers are far from robust, we added a few | ||
52 | * descriptors to the CDC Subset code, making this code look like a SAFE | ||
53 | * implementation. This lets you use MCCI's host side MS-Windows drivers | ||
54 | * if you get fed up with RNDIS. It also makes it easier for composite | ||
55 | * drivers to work, since they can use class based binding instead of | ||
56 | * caring about specific product and vendor IDs. | ||
57 | */ | ||
58 | |||
59 | struct geth_descs { | ||
60 | struct usb_endpoint_descriptor *in; | ||
61 | struct usb_endpoint_descriptor *out; | ||
62 | }; | ||
63 | |||
64 | struct f_gether { | ||
65 | struct gether port; | ||
66 | |||
67 | char ethaddr[14]; | ||
68 | |||
69 | struct usb_descriptor_header **fs_function; | ||
70 | struct geth_descs fs; | ||
71 | struct usb_descriptor_header **hs_function; | ||
72 | struct geth_descs hs; | ||
73 | }; | ||
74 | |||
75 | static inline struct f_gether *func_to_geth(struct usb_function *f) | ||
76 | { | ||
77 | return container_of(f, struct f_gether, port.func); | ||
78 | } | ||
79 | |||
80 | /*-------------------------------------------------------------------------*/ | ||
81 | |||
82 | /* | ||
83 | * "Simple" CDC-subset option is a simple vendor-neutral model that most | ||
84 | * full speed controllers can handle: one interface, two bulk endpoints. | ||
85 | * To assist host side drivers, we fancy it up a bit, and add descriptors so | ||
86 | * some host side drivers will understand it as a "SAFE" variant. | ||
87 | * | ||
88 | * "SAFE" loosely follows CDC WMC MDLM, violating the spec in various ways. | ||
89 | * Data endpoints live in the control interface, there's no data interface. | ||
90 | * And it's not used to talk to a cell phone radio. | ||
91 | */ | ||
92 | |||
93 | /* interface descriptor: */ | ||
94 | |||
95 | static struct usb_interface_descriptor subset_data_intf __initdata = { | ||
96 | .bLength = sizeof subset_data_intf, | ||
97 | .bDescriptorType = USB_DT_INTERFACE, | ||
98 | |||
99 | /* .bInterfaceNumber = DYNAMIC */ | ||
100 | .bAlternateSetting = 0, | ||
101 | .bNumEndpoints = 2, | ||
102 | .bInterfaceClass = USB_CLASS_COMM, | ||
103 | .bInterfaceSubClass = USB_CDC_SUBCLASS_MDLM, | ||
104 | .bInterfaceProtocol = 0, | ||
105 | /* .iInterface = DYNAMIC */ | ||
106 | }; | ||
107 | |||
108 | static struct usb_cdc_header_desc header_desc __initdata = { | ||
109 | .bLength = sizeof header_desc, | ||
110 | .bDescriptorType = USB_DT_CS_INTERFACE, | ||
111 | .bDescriptorSubType = USB_CDC_HEADER_TYPE, | ||
112 | |||
113 | .bcdCDC = __constant_cpu_to_le16(0x0110), | ||
114 | }; | ||
115 | |||
116 | static struct usb_cdc_mdlm_desc mdlm_desc __initdata = { | ||
117 | .bLength = sizeof mdlm_desc, | ||
118 | .bDescriptorType = USB_DT_CS_INTERFACE, | ||
119 | .bDescriptorSubType = USB_CDC_MDLM_TYPE, | ||
120 | |||
121 | .bcdVersion = __constant_cpu_to_le16(0x0100), | ||
122 | .bGUID = { | ||
123 | 0x5d, 0x34, 0xcf, 0x66, 0x11, 0x18, 0x11, 0xd6, | ||
124 | 0xa2, 0x1a, 0x00, 0x01, 0x02, 0xca, 0x9a, 0x7f, | ||
125 | }, | ||
126 | }; | ||
127 | |||
128 | /* since "usb_cdc_mdlm_detail_desc" is a variable length structure, we | ||
129 | * can't really use its struct. All we do here is say that we're using | ||
130 | * the submode of "SAFE" which directly matches the CDC Subset. | ||
131 | */ | ||
132 | static u8 mdlm_detail_desc[] __initdata = { | ||
133 | 6, | ||
134 | USB_DT_CS_INTERFACE, | ||
135 | USB_CDC_MDLM_DETAIL_TYPE, | ||
136 | |||
137 | 0, /* "SAFE" */ | ||
138 | 0, /* network control capabilities (none) */ | ||
139 | 0, /* network data capabilities ("raw" encapsulation) */ | ||
140 | }; | ||
141 | |||
142 | static struct usb_cdc_ether_desc ether_desc __initdata = { | ||
143 | .bLength = sizeof ether_desc, | ||
144 | .bDescriptorType = USB_DT_CS_INTERFACE, | ||
145 | .bDescriptorSubType = USB_CDC_ETHERNET_TYPE, | ||
146 | |||
147 | /* this descriptor actually adds value, surprise! */ | ||
148 | /* .iMACAddress = DYNAMIC */ | ||
149 | .bmEthernetStatistics = __constant_cpu_to_le32(0), /* no statistics */ | ||
150 | .wMaxSegmentSize = __constant_cpu_to_le16(ETH_FRAME_LEN), | ||
151 | .wNumberMCFilters = __constant_cpu_to_le16(0), | ||
152 | .bNumberPowerFilters = 0, | ||
153 | }; | ||
154 | |||
155 | /* full speed support: */ | ||
156 | |||
157 | static struct usb_endpoint_descriptor fs_in_desc __initdata = { | ||
158 | .bLength = USB_DT_ENDPOINT_SIZE, | ||
159 | .bDescriptorType = USB_DT_ENDPOINT, | ||
160 | |||
161 | .bEndpointAddress = USB_DIR_IN, | ||
162 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | ||
163 | }; | ||
164 | |||
165 | static struct usb_endpoint_descriptor fs_out_desc __initdata = { | ||
166 | .bLength = USB_DT_ENDPOINT_SIZE, | ||
167 | .bDescriptorType = USB_DT_ENDPOINT, | ||
168 | |||
169 | .bEndpointAddress = USB_DIR_OUT, | ||
170 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | ||
171 | }; | ||
172 | |||
173 | static struct usb_descriptor_header *fs_eth_function[] __initdata = { | ||
174 | (struct usb_descriptor_header *) &subset_data_intf, | ||
175 | (struct usb_descriptor_header *) &header_desc, | ||
176 | (struct usb_descriptor_header *) &mdlm_desc, | ||
177 | (struct usb_descriptor_header *) &mdlm_detail_desc, | ||
178 | (struct usb_descriptor_header *) ðer_desc, | ||
179 | (struct usb_descriptor_header *) &fs_in_desc, | ||
180 | (struct usb_descriptor_header *) &fs_out_desc, | ||
181 | NULL, | ||
182 | }; | ||
183 | |||
184 | /* high speed support: */ | ||
185 | |||
186 | static struct usb_endpoint_descriptor hs_in_desc __initdata = { | ||
187 | .bLength = USB_DT_ENDPOINT_SIZE, | ||
188 | .bDescriptorType = USB_DT_ENDPOINT, | ||
189 | |||
190 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | ||
191 | .wMaxPacketSize = __constant_cpu_to_le16(512), | ||
192 | }; | ||
193 | |||
194 | static struct usb_endpoint_descriptor hs_out_desc __initdata = { | ||
195 | .bLength = USB_DT_ENDPOINT_SIZE, | ||
196 | .bDescriptorType = USB_DT_ENDPOINT, | ||
197 | |||
198 | .bmAttributes = USB_ENDPOINT_XFER_BULK, | ||
199 | .wMaxPacketSize = __constant_cpu_to_le16(512), | ||
200 | }; | ||
201 | |||
202 | static struct usb_descriptor_header *hs_eth_function[] __initdata = { | ||
203 | (struct usb_descriptor_header *) &subset_data_intf, | ||
204 | (struct usb_descriptor_header *) &header_desc, | ||
205 | (struct usb_descriptor_header *) &mdlm_desc, | ||
206 | (struct usb_descriptor_header *) &mdlm_detail_desc, | ||
207 | (struct usb_descriptor_header *) ðer_desc, | ||
208 | (struct usb_descriptor_header *) &hs_in_desc, | ||
209 | (struct usb_descriptor_header *) &hs_out_desc, | ||
210 | NULL, | ||
211 | }; | ||
212 | |||
213 | /* string descriptors: */ | ||
214 | |||
215 | static struct usb_string geth_string_defs[] = { | ||
216 | [0].s = "CDC Ethernet Subset/SAFE", | ||
217 | [1].s = NULL /* DYNAMIC */, | ||
218 | { } /* end of list */ | ||
219 | }; | ||
220 | |||
221 | static struct usb_gadget_strings geth_string_table = { | ||
222 | .language = 0x0409, /* en-us */ | ||
223 | .strings = geth_string_defs, | ||
224 | }; | ||
225 | |||
226 | static struct usb_gadget_strings *geth_strings[] = { | ||
227 | &geth_string_table, | ||
228 | NULL, | ||
229 | }; | ||
230 | |||
231 | /*-------------------------------------------------------------------------*/ | ||
232 | |||
233 | static int geth_set_alt(struct usb_function *f, unsigned intf, unsigned alt) | ||
234 | { | ||
235 | struct f_gether *geth = func_to_geth(f); | ||
236 | struct usb_composite_dev *cdev = f->config->cdev; | ||
237 | struct net_device *net; | ||
238 | |||
239 | /* we know alt == 0, so this is an activation or a reset */ | ||
240 | |||
241 | if (geth->port.in_ep->driver_data) { | ||
242 | DBG(cdev, "reset cdc subset\n"); | ||
243 | gether_disconnect(&geth->port); | ||
244 | } | ||
245 | |||
246 | DBG(cdev, "init + activate cdc subset\n"); | ||
247 | geth->port.in = ep_choose(cdev->gadget, | ||
248 | geth->hs.in, geth->fs.in); | ||
249 | geth->port.out = ep_choose(cdev->gadget, | ||
250 | geth->hs.out, geth->fs.out); | ||
251 | |||
252 | net = gether_connect(&geth->port); | ||
253 | return IS_ERR(net) ? PTR_ERR(net) : 0; | ||
254 | } | ||
255 | |||
256 | static void geth_disable(struct usb_function *f) | ||
257 | { | ||
258 | struct f_gether *geth = func_to_geth(f); | ||
259 | struct usb_composite_dev *cdev = f->config->cdev; | ||
260 | |||
261 | DBG(cdev, "net deactivated\n"); | ||
262 | gether_disconnect(&geth->port); | ||
263 | } | ||
264 | |||
265 | /*-------------------------------------------------------------------------*/ | ||
266 | |||
267 | /* serial function driver setup/binding */ | ||
268 | |||
269 | static int __init | ||
270 | geth_bind(struct usb_configuration *c, struct usb_function *f) | ||
271 | { | ||
272 | struct usb_composite_dev *cdev = c->cdev; | ||
273 | struct f_gether *geth = func_to_geth(f); | ||
274 | int status; | ||
275 | struct usb_ep *ep; | ||
276 | |||
277 | /* allocate instance-specific interface IDs */ | ||
278 | status = usb_interface_id(c, f); | ||
279 | if (status < 0) | ||
280 | goto fail; | ||
281 | subset_data_intf.bInterfaceNumber = status; | ||
282 | |||
283 | status = -ENODEV; | ||
284 | |||
285 | /* allocate instance-specific endpoints */ | ||
286 | ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc); | ||
287 | if (!ep) | ||
288 | goto fail; | ||
289 | geth->port.in_ep = ep; | ||
290 | ep->driver_data = cdev; /* claim */ | ||
291 | |||
292 | ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc); | ||
293 | if (!ep) | ||
294 | goto fail; | ||
295 | geth->port.out_ep = ep; | ||
296 | ep->driver_data = cdev; /* claim */ | ||
297 | |||
298 | /* copy descriptors, and track endpoint copies */ | ||
299 | f->descriptors = usb_copy_descriptors(fs_eth_function); | ||
300 | |||
301 | geth->fs.in = usb_find_endpoint(fs_eth_function, | ||
302 | f->descriptors, &fs_in_desc); | ||
303 | geth->fs.out = usb_find_endpoint(fs_eth_function, | ||
304 | f->descriptors, &fs_out_desc); | ||
305 | |||
306 | |||
307 | /* support all relevant hardware speeds... we expect that when | ||
308 | * hardware is dual speed, all bulk-capable endpoints work at | ||
309 | * both speeds | ||
310 | */ | ||
311 | if (gadget_is_dualspeed(c->cdev->gadget)) { | ||
312 | hs_in_desc.bEndpointAddress = | ||
313 | fs_in_desc.bEndpointAddress; | ||
314 | hs_out_desc.bEndpointAddress = | ||
315 | fs_out_desc.bEndpointAddress; | ||
316 | |||
317 | /* copy descriptors, and track endpoint copies */ | ||
318 | f->hs_descriptors = usb_copy_descriptors(hs_eth_function); | ||
319 | |||
320 | geth->hs.in = usb_find_endpoint(hs_eth_function, | ||
321 | f->hs_descriptors, &hs_in_desc); | ||
322 | geth->hs.out = usb_find_endpoint(hs_eth_function, | ||
323 | f->hs_descriptors, &hs_out_desc); | ||
324 | } | ||
325 | |||
326 | /* NOTE: all that is done without knowing or caring about | ||
327 | * the network link ... which is unavailable to this code | ||
328 | * until we're activated via set_alt(). | ||
329 | */ | ||
330 | |||
331 | DBG(cdev, "CDC Subset: %s speed IN/%s OUT/%s\n", | ||
332 | gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", | ||
333 | geth->port.in_ep->name, geth->port.out_ep->name); | ||
334 | return 0; | ||
335 | |||
336 | fail: | ||
337 | /* we might as well release our claims on endpoints */ | ||
338 | if (geth->port.out) | ||
339 | geth->port.out_ep->driver_data = NULL; | ||
340 | if (geth->port.in) | ||
341 | geth->port.in_ep->driver_data = NULL; | ||
342 | |||
343 | ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); | ||
344 | |||
345 | return status; | ||
346 | } | ||
347 | |||
348 | static void | ||
349 | geth_unbind(struct usb_configuration *c, struct usb_function *f) | ||
350 | { | ||
351 | if (gadget_is_dualspeed(c->cdev->gadget)) | ||
352 | usb_free_descriptors(f->hs_descriptors); | ||
353 | usb_free_descriptors(f->descriptors); | ||
354 | geth_string_defs[1].s = NULL; | ||
355 | kfree(func_to_geth(f)); | ||
356 | } | ||
357 | |||
358 | /** | ||
359 | * geth_bind_config - add CDC Subset network link to a configuration | ||
360 | * @c: the configuration to support the network link | ||
361 | * @ethaddr: a buffer in which the ethernet address of the host side | ||
362 | * side of the link was recorded | ||
363 | * Context: single threaded during gadget setup | ||
364 | * | ||
365 | * Returns zero on success, else negative errno. | ||
366 | * | ||
367 | * Caller must have called @gether_setup(). Caller is also responsible | ||
368 | * for calling @gether_cleanup() before module unload. | ||
369 | */ | ||
370 | int __init geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) | ||
371 | { | ||
372 | struct f_gether *geth; | ||
373 | int status; | ||
374 | |||
375 | if (!ethaddr) | ||
376 | return -EINVAL; | ||
377 | |||
378 | /* maybe allocate device-global string IDs */ | ||
379 | if (geth_string_defs[0].id == 0) { | ||
380 | |||
381 | /* interface label */ | ||
382 | status = usb_string_id(c->cdev); | ||
383 | if (status < 0) | ||
384 | return status; | ||
385 | geth_string_defs[0].id = status; | ||
386 | subset_data_intf.iInterface = status; | ||
387 | |||
388 | /* MAC address */ | ||
389 | status = usb_string_id(c->cdev); | ||
390 | if (status < 0) | ||
391 | return status; | ||
392 | geth_string_defs[1].id = status; | ||
393 | ether_desc.iMACAddress = status; | ||
394 | } | ||
395 | |||
396 | /* allocate and initialize one new instance */ | ||
397 | geth = kzalloc(sizeof *geth, GFP_KERNEL); | ||
398 | if (!geth) | ||
399 | return -ENOMEM; | ||
400 | |||
401 | /* export host's Ethernet address in CDC format */ | ||
402 | snprintf(geth->ethaddr, sizeof geth->ethaddr, | ||
403 | "%02X%02X%02X%02X%02X%02X", | ||
404 | ethaddr[0], ethaddr[1], ethaddr[2], | ||
405 | ethaddr[3], ethaddr[4], ethaddr[5]); | ||
406 | geth_string_defs[1].s = geth->ethaddr; | ||
407 | |||
408 | geth->port.cdc_filter = DEFAULT_FILTER; | ||
409 | |||
410 | geth->port.func.name = "cdc_subset"; | ||
411 | geth->port.func.strings = geth_strings; | ||
412 | geth->port.func.bind = geth_bind; | ||
413 | geth->port.func.unbind = geth_unbind; | ||
414 | geth->port.func.set_alt = geth_set_alt; | ||
415 | geth->port.func.disable = geth_disable; | ||
416 | |||
417 | status = usb_add_function(c, &geth->port.func); | ||
418 | if (status) { | ||
419 | geth_string_defs[1].s = NULL; | ||
420 | kfree(geth); | ||
421 | } | ||
422 | return status; | ||
423 | } | ||