diff options
Diffstat (limited to 'drivers/usb/gadget/legacy/multi.c')
-rw-r--r-- | drivers/usb/gadget/legacy/multi.c | 510 |
1 files changed, 510 insertions, 0 deletions
diff --git a/drivers/usb/gadget/legacy/multi.c b/drivers/usb/gadget/legacy/multi.c new file mode 100644 index 000000000000..39d27bb343b4 --- /dev/null +++ b/drivers/usb/gadget/legacy/multi.c | |||
@@ -0,0 +1,510 @@ | |||
1 | /* | ||
2 | * multi.c -- Multifunction Composite driver | ||
3 | * | ||
4 | * Copyright (C) 2008 David Brownell | ||
5 | * Copyright (C) 2008 Nokia Corporation | ||
6 | * Copyright (C) 2009 Samsung Electronics | ||
7 | * Author: Michal Nazarewicz (mina86@mina86.com) | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License as published by | ||
11 | * the Free Software Foundation; either version 2 of the License, or | ||
12 | * (at your option) any later version. | ||
13 | */ | ||
14 | |||
15 | |||
16 | #include <linux/kernel.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/netdevice.h> | ||
19 | |||
20 | #include "u_serial.h" | ||
21 | #if defined USB_ETH_RNDIS | ||
22 | # undef USB_ETH_RNDIS | ||
23 | #endif | ||
24 | #ifdef CONFIG_USB_G_MULTI_RNDIS | ||
25 | # define USB_ETH_RNDIS y | ||
26 | #endif | ||
27 | |||
28 | |||
29 | #define DRIVER_DESC "Multifunction Composite Gadget" | ||
30 | |||
31 | MODULE_DESCRIPTION(DRIVER_DESC); | ||
32 | MODULE_AUTHOR("Michal Nazarewicz"); | ||
33 | MODULE_LICENSE("GPL"); | ||
34 | |||
35 | |||
36 | #include "f_mass_storage.h" | ||
37 | |||
38 | #include "u_ecm.h" | ||
39 | #ifdef USB_ETH_RNDIS | ||
40 | # include "u_rndis.h" | ||
41 | # include "rndis.h" | ||
42 | #endif | ||
43 | #include "u_ether.h" | ||
44 | |||
45 | USB_GADGET_COMPOSITE_OPTIONS(); | ||
46 | |||
47 | USB_ETHERNET_MODULE_PARAMETERS(); | ||
48 | |||
49 | /***************************** Device Descriptor ****************************/ | ||
50 | |||
51 | #define MULTI_VENDOR_NUM 0x1d6b /* Linux Foundation */ | ||
52 | #define MULTI_PRODUCT_NUM 0x0104 /* Multifunction Composite Gadget */ | ||
53 | |||
54 | |||
55 | enum { | ||
56 | __MULTI_NO_CONFIG, | ||
57 | #ifdef CONFIG_USB_G_MULTI_RNDIS | ||
58 | MULTI_RNDIS_CONFIG_NUM, | ||
59 | #endif | ||
60 | #ifdef CONFIG_USB_G_MULTI_CDC | ||
61 | MULTI_CDC_CONFIG_NUM, | ||
62 | #endif | ||
63 | }; | ||
64 | |||
65 | |||
66 | static struct usb_device_descriptor device_desc = { | ||
67 | .bLength = sizeof device_desc, | ||
68 | .bDescriptorType = USB_DT_DEVICE, | ||
69 | |||
70 | .bcdUSB = cpu_to_le16(0x0200), | ||
71 | |||
72 | .bDeviceClass = USB_CLASS_MISC /* 0xEF */, | ||
73 | .bDeviceSubClass = 2, | ||
74 | .bDeviceProtocol = 1, | ||
75 | |||
76 | /* Vendor and product id can be overridden by module parameters. */ | ||
77 | .idVendor = cpu_to_le16(MULTI_VENDOR_NUM), | ||
78 | .idProduct = cpu_to_le16(MULTI_PRODUCT_NUM), | ||
79 | }; | ||
80 | |||
81 | |||
82 | static const struct usb_descriptor_header *otg_desc[] = { | ||
83 | (struct usb_descriptor_header *) &(struct usb_otg_descriptor){ | ||
84 | .bLength = sizeof(struct usb_otg_descriptor), | ||
85 | .bDescriptorType = USB_DT_OTG, | ||
86 | |||
87 | /* | ||
88 | * REVISIT SRP-only hardware is possible, although | ||
89 | * it would not be called "OTG" ... | ||
90 | */ | ||
91 | .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, | ||
92 | }, | ||
93 | NULL, | ||
94 | }; | ||
95 | |||
96 | |||
97 | enum { | ||
98 | MULTI_STRING_RNDIS_CONFIG_IDX = USB_GADGET_FIRST_AVAIL_IDX, | ||
99 | MULTI_STRING_CDC_CONFIG_IDX, | ||
100 | }; | ||
101 | |||
102 | static struct usb_string strings_dev[] = { | ||
103 | [USB_GADGET_MANUFACTURER_IDX].s = "", | ||
104 | [USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC, | ||
105 | [USB_GADGET_SERIAL_IDX].s = "", | ||
106 | [MULTI_STRING_RNDIS_CONFIG_IDX].s = "Multifunction with RNDIS", | ||
107 | [MULTI_STRING_CDC_CONFIG_IDX].s = "Multifunction with CDC ECM", | ||
108 | { } /* end of list */ | ||
109 | }; | ||
110 | |||
111 | static struct usb_gadget_strings *dev_strings[] = { | ||
112 | &(struct usb_gadget_strings){ | ||
113 | .language = 0x0409, /* en-us */ | ||
114 | .strings = strings_dev, | ||
115 | }, | ||
116 | NULL, | ||
117 | }; | ||
118 | |||
119 | |||
120 | |||
121 | |||
122 | /****************************** Configurations ******************************/ | ||
123 | |||
124 | static struct fsg_module_parameters fsg_mod_data = { .stall = 1 }; | ||
125 | #ifdef CONFIG_USB_GADGET_DEBUG_FILES | ||
126 | |||
127 | static unsigned int fsg_num_buffers = CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS; | ||
128 | |||
129 | #else | ||
130 | |||
131 | /* | ||
132 | * Number of buffers we will use. | ||
133 | * 2 is usually enough for good buffering pipeline | ||
134 | */ | ||
135 | #define fsg_num_buffers CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS | ||
136 | |||
137 | #endif /* CONFIG_USB_GADGET_DEBUG_FILES */ | ||
138 | |||
139 | FSG_MODULE_PARAMETERS(/* no prefix */, fsg_mod_data); | ||
140 | |||
141 | static struct usb_function_instance *fi_acm; | ||
142 | static struct usb_function_instance *fi_msg; | ||
143 | |||
144 | /********** RNDIS **********/ | ||
145 | |||
146 | #ifdef USB_ETH_RNDIS | ||
147 | static struct usb_function_instance *fi_rndis; | ||
148 | static struct usb_function *f_acm_rndis; | ||
149 | static struct usb_function *f_rndis; | ||
150 | static struct usb_function *f_msg_rndis; | ||
151 | |||
152 | static __init int rndis_do_config(struct usb_configuration *c) | ||
153 | { | ||
154 | struct fsg_opts *fsg_opts; | ||
155 | int ret; | ||
156 | |||
157 | if (gadget_is_otg(c->cdev->gadget)) { | ||
158 | c->descriptors = otg_desc; | ||
159 | c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; | ||
160 | } | ||
161 | |||
162 | f_rndis = usb_get_function(fi_rndis); | ||
163 | if (IS_ERR(f_rndis)) | ||
164 | return PTR_ERR(f_rndis); | ||
165 | |||
166 | ret = usb_add_function(c, f_rndis); | ||
167 | if (ret < 0) | ||
168 | goto err_func_rndis; | ||
169 | |||
170 | f_acm_rndis = usb_get_function(fi_acm); | ||
171 | if (IS_ERR(f_acm_rndis)) { | ||
172 | ret = PTR_ERR(f_acm_rndis); | ||
173 | goto err_func_acm; | ||
174 | } | ||
175 | |||
176 | ret = usb_add_function(c, f_acm_rndis); | ||
177 | if (ret) | ||
178 | goto err_conf; | ||
179 | |||
180 | f_msg_rndis = usb_get_function(fi_msg); | ||
181 | if (IS_ERR(f_msg_rndis)) { | ||
182 | ret = PTR_ERR(f_msg_rndis); | ||
183 | goto err_fsg; | ||
184 | } | ||
185 | |||
186 | fsg_opts = fsg_opts_from_func_inst(fi_msg); | ||
187 | ret = fsg_common_run_thread(fsg_opts->common); | ||
188 | if (ret) | ||
189 | goto err_run; | ||
190 | |||
191 | ret = usb_add_function(c, f_msg_rndis); | ||
192 | if (ret) | ||
193 | goto err_run; | ||
194 | |||
195 | return 0; | ||
196 | err_run: | ||
197 | usb_put_function(f_msg_rndis); | ||
198 | err_fsg: | ||
199 | usb_remove_function(c, f_acm_rndis); | ||
200 | err_conf: | ||
201 | usb_put_function(f_acm_rndis); | ||
202 | err_func_acm: | ||
203 | usb_remove_function(c, f_rndis); | ||
204 | err_func_rndis: | ||
205 | usb_put_function(f_rndis); | ||
206 | return ret; | ||
207 | } | ||
208 | |||
209 | static __ref int rndis_config_register(struct usb_composite_dev *cdev) | ||
210 | { | ||
211 | static struct usb_configuration config = { | ||
212 | .bConfigurationValue = MULTI_RNDIS_CONFIG_NUM, | ||
213 | .bmAttributes = USB_CONFIG_ATT_SELFPOWER, | ||
214 | }; | ||
215 | |||
216 | config.label = strings_dev[MULTI_STRING_RNDIS_CONFIG_IDX].s; | ||
217 | config.iConfiguration = strings_dev[MULTI_STRING_RNDIS_CONFIG_IDX].id; | ||
218 | |||
219 | return usb_add_config(cdev, &config, rndis_do_config); | ||
220 | } | ||
221 | |||
222 | #else | ||
223 | |||
224 | static __ref int rndis_config_register(struct usb_composite_dev *cdev) | ||
225 | { | ||
226 | return 0; | ||
227 | } | ||
228 | |||
229 | #endif | ||
230 | |||
231 | |||
232 | /********** CDC ECM **********/ | ||
233 | |||
234 | #ifdef CONFIG_USB_G_MULTI_CDC | ||
235 | static struct usb_function_instance *fi_ecm; | ||
236 | static struct usb_function *f_acm_multi; | ||
237 | static struct usb_function *f_ecm; | ||
238 | static struct usb_function *f_msg_multi; | ||
239 | |||
240 | static __init int cdc_do_config(struct usb_configuration *c) | ||
241 | { | ||
242 | struct fsg_opts *fsg_opts; | ||
243 | int ret; | ||
244 | |||
245 | if (gadget_is_otg(c->cdev->gadget)) { | ||
246 | c->descriptors = otg_desc; | ||
247 | c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; | ||
248 | } | ||
249 | |||
250 | f_ecm = usb_get_function(fi_ecm); | ||
251 | if (IS_ERR(f_ecm)) | ||
252 | return PTR_ERR(f_ecm); | ||
253 | |||
254 | ret = usb_add_function(c, f_ecm); | ||
255 | if (ret < 0) | ||
256 | goto err_func_ecm; | ||
257 | |||
258 | /* implicit port_num is zero */ | ||
259 | f_acm_multi = usb_get_function(fi_acm); | ||
260 | if (IS_ERR(f_acm_multi)) { | ||
261 | ret = PTR_ERR(f_acm_multi); | ||
262 | goto err_func_acm; | ||
263 | } | ||
264 | |||
265 | ret = usb_add_function(c, f_acm_multi); | ||
266 | if (ret) | ||
267 | goto err_conf; | ||
268 | |||
269 | f_msg_multi = usb_get_function(fi_msg); | ||
270 | if (IS_ERR(f_msg_multi)) { | ||
271 | ret = PTR_ERR(f_msg_multi); | ||
272 | goto err_fsg; | ||
273 | } | ||
274 | |||
275 | fsg_opts = fsg_opts_from_func_inst(fi_msg); | ||
276 | ret = fsg_common_run_thread(fsg_opts->common); | ||
277 | if (ret) | ||
278 | goto err_run; | ||
279 | |||
280 | ret = usb_add_function(c, f_msg_multi); | ||
281 | if (ret) | ||
282 | goto err_run; | ||
283 | |||
284 | return 0; | ||
285 | err_run: | ||
286 | usb_put_function(f_msg_multi); | ||
287 | err_fsg: | ||
288 | usb_remove_function(c, f_acm_multi); | ||
289 | err_conf: | ||
290 | usb_put_function(f_acm_multi); | ||
291 | err_func_acm: | ||
292 | usb_remove_function(c, f_ecm); | ||
293 | err_func_ecm: | ||
294 | usb_put_function(f_ecm); | ||
295 | return ret; | ||
296 | } | ||
297 | |||
298 | static __ref int cdc_config_register(struct usb_composite_dev *cdev) | ||
299 | { | ||
300 | static struct usb_configuration config = { | ||
301 | .bConfigurationValue = MULTI_CDC_CONFIG_NUM, | ||
302 | .bmAttributes = USB_CONFIG_ATT_SELFPOWER, | ||
303 | }; | ||
304 | |||
305 | config.label = strings_dev[MULTI_STRING_CDC_CONFIG_IDX].s; | ||
306 | config.iConfiguration = strings_dev[MULTI_STRING_CDC_CONFIG_IDX].id; | ||
307 | |||
308 | return usb_add_config(cdev, &config, cdc_do_config); | ||
309 | } | ||
310 | |||
311 | #else | ||
312 | |||
313 | static __ref int cdc_config_register(struct usb_composite_dev *cdev) | ||
314 | { | ||
315 | return 0; | ||
316 | } | ||
317 | |||
318 | #endif | ||
319 | |||
320 | |||
321 | |||
322 | /****************************** Gadget Bind ******************************/ | ||
323 | |||
324 | static int __ref multi_bind(struct usb_composite_dev *cdev) | ||
325 | { | ||
326 | struct usb_gadget *gadget = cdev->gadget; | ||
327 | #ifdef CONFIG_USB_G_MULTI_CDC | ||
328 | struct f_ecm_opts *ecm_opts; | ||
329 | #endif | ||
330 | #ifdef USB_ETH_RNDIS | ||
331 | struct f_rndis_opts *rndis_opts; | ||
332 | #endif | ||
333 | struct fsg_opts *fsg_opts; | ||
334 | struct fsg_config config; | ||
335 | int status; | ||
336 | |||
337 | if (!can_support_ecm(cdev->gadget)) { | ||
338 | dev_err(&gadget->dev, "controller '%s' not usable\n", | ||
339 | gadget->name); | ||
340 | return -EINVAL; | ||
341 | } | ||
342 | |||
343 | #ifdef CONFIG_USB_G_MULTI_CDC | ||
344 | fi_ecm = usb_get_function_instance("ecm"); | ||
345 | if (IS_ERR(fi_ecm)) | ||
346 | return PTR_ERR(fi_ecm); | ||
347 | |||
348 | ecm_opts = container_of(fi_ecm, struct f_ecm_opts, func_inst); | ||
349 | |||
350 | gether_set_qmult(ecm_opts->net, qmult); | ||
351 | if (!gether_set_host_addr(ecm_opts->net, host_addr)) | ||
352 | pr_info("using host ethernet address: %s", host_addr); | ||
353 | if (!gether_set_dev_addr(ecm_opts->net, dev_addr)) | ||
354 | pr_info("using self ethernet address: %s", dev_addr); | ||
355 | #endif | ||
356 | |||
357 | #ifdef USB_ETH_RNDIS | ||
358 | fi_rndis = usb_get_function_instance("rndis"); | ||
359 | if (IS_ERR(fi_rndis)) { | ||
360 | status = PTR_ERR(fi_rndis); | ||
361 | goto fail; | ||
362 | } | ||
363 | |||
364 | rndis_opts = container_of(fi_rndis, struct f_rndis_opts, func_inst); | ||
365 | |||
366 | gether_set_qmult(rndis_opts->net, qmult); | ||
367 | if (!gether_set_host_addr(rndis_opts->net, host_addr)) | ||
368 | pr_info("using host ethernet address: %s", host_addr); | ||
369 | if (!gether_set_dev_addr(rndis_opts->net, dev_addr)) | ||
370 | pr_info("using self ethernet address: %s", dev_addr); | ||
371 | #endif | ||
372 | |||
373 | #if (defined CONFIG_USB_G_MULTI_CDC && defined USB_ETH_RNDIS) | ||
374 | /* | ||
375 | * If both ecm and rndis are selected then: | ||
376 | * 1) rndis borrows the net interface from ecm | ||
377 | * 2) since the interface is shared it must not be bound | ||
378 | * twice - in ecm's _and_ rndis' binds, so do it here. | ||
379 | */ | ||
380 | gether_set_gadget(ecm_opts->net, cdev->gadget); | ||
381 | status = gether_register_netdev(ecm_opts->net); | ||
382 | if (status) | ||
383 | goto fail0; | ||
384 | |||
385 | rndis_borrow_net(fi_rndis, ecm_opts->net); | ||
386 | ecm_opts->bound = true; | ||
387 | #endif | ||
388 | |||
389 | /* set up serial link layer */ | ||
390 | fi_acm = usb_get_function_instance("acm"); | ||
391 | if (IS_ERR(fi_acm)) { | ||
392 | status = PTR_ERR(fi_acm); | ||
393 | goto fail0; | ||
394 | } | ||
395 | |||
396 | /* set up mass storage function */ | ||
397 | fi_msg = usb_get_function_instance("mass_storage"); | ||
398 | if (IS_ERR(fi_msg)) { | ||
399 | status = PTR_ERR(fi_msg); | ||
400 | goto fail1; | ||
401 | } | ||
402 | fsg_config_from_params(&config, &fsg_mod_data, fsg_num_buffers); | ||
403 | fsg_opts = fsg_opts_from_func_inst(fi_msg); | ||
404 | |||
405 | fsg_opts->no_configfs = true; | ||
406 | status = fsg_common_set_num_buffers(fsg_opts->common, fsg_num_buffers); | ||
407 | if (status) | ||
408 | goto fail2; | ||
409 | |||
410 | status = fsg_common_set_nluns(fsg_opts->common, config.nluns); | ||
411 | if (status) | ||
412 | goto fail_set_nluns; | ||
413 | |||
414 | status = fsg_common_set_cdev(fsg_opts->common, cdev, config.can_stall); | ||
415 | if (status) | ||
416 | goto fail_set_cdev; | ||
417 | |||
418 | fsg_common_set_sysfs(fsg_opts->common, true); | ||
419 | status = fsg_common_create_luns(fsg_opts->common, &config); | ||
420 | if (status) | ||
421 | goto fail_set_cdev; | ||
422 | |||
423 | fsg_common_set_inquiry_string(fsg_opts->common, config.vendor_name, | ||
424 | config.product_name); | ||
425 | |||
426 | /* allocate string IDs */ | ||
427 | status = usb_string_ids_tab(cdev, strings_dev); | ||
428 | if (unlikely(status < 0)) | ||
429 | goto fail_string_ids; | ||
430 | device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id; | ||
431 | |||
432 | /* register configurations */ | ||
433 | status = rndis_config_register(cdev); | ||
434 | if (unlikely(status < 0)) | ||
435 | goto fail_string_ids; | ||
436 | |||
437 | status = cdc_config_register(cdev); | ||
438 | if (unlikely(status < 0)) | ||
439 | goto fail_string_ids; | ||
440 | usb_composite_overwrite_options(cdev, &coverwrite); | ||
441 | |||
442 | /* we're done */ | ||
443 | dev_info(&gadget->dev, DRIVER_DESC "\n"); | ||
444 | return 0; | ||
445 | |||
446 | |||
447 | /* error recovery */ | ||
448 | fail_string_ids: | ||
449 | fsg_common_remove_luns(fsg_opts->common); | ||
450 | fail_set_cdev: | ||
451 | fsg_common_free_luns(fsg_opts->common); | ||
452 | fail_set_nluns: | ||
453 | fsg_common_free_buffers(fsg_opts->common); | ||
454 | fail2: | ||
455 | usb_put_function_instance(fi_msg); | ||
456 | fail1: | ||
457 | usb_put_function_instance(fi_acm); | ||
458 | fail0: | ||
459 | #ifdef USB_ETH_RNDIS | ||
460 | usb_put_function_instance(fi_rndis); | ||
461 | fail: | ||
462 | #endif | ||
463 | #ifdef CONFIG_USB_G_MULTI_CDC | ||
464 | usb_put_function_instance(fi_ecm); | ||
465 | #endif | ||
466 | return status; | ||
467 | } | ||
468 | |||
469 | static int __exit multi_unbind(struct usb_composite_dev *cdev) | ||
470 | { | ||
471 | #ifdef CONFIG_USB_G_MULTI_CDC | ||
472 | usb_put_function(f_msg_multi); | ||
473 | #endif | ||
474 | #ifdef USB_ETH_RNDIS | ||
475 | usb_put_function(f_msg_rndis); | ||
476 | #endif | ||
477 | usb_put_function_instance(fi_msg); | ||
478 | #ifdef CONFIG_USB_G_MULTI_CDC | ||
479 | usb_put_function(f_acm_multi); | ||
480 | #endif | ||
481 | #ifdef USB_ETH_RNDIS | ||
482 | usb_put_function(f_acm_rndis); | ||
483 | #endif | ||
484 | usb_put_function_instance(fi_acm); | ||
485 | #ifdef USB_ETH_RNDIS | ||
486 | usb_put_function(f_rndis); | ||
487 | usb_put_function_instance(fi_rndis); | ||
488 | #endif | ||
489 | #ifdef CONFIG_USB_G_MULTI_CDC | ||
490 | usb_put_function(f_ecm); | ||
491 | usb_put_function_instance(fi_ecm); | ||
492 | #endif | ||
493 | return 0; | ||
494 | } | ||
495 | |||
496 | |||
497 | /****************************** Some noise ******************************/ | ||
498 | |||
499 | |||
500 | static __refdata struct usb_composite_driver multi_driver = { | ||
501 | .name = "g_multi", | ||
502 | .dev = &device_desc, | ||
503 | .strings = dev_strings, | ||
504 | .max_speed = USB_SPEED_HIGH, | ||
505 | .bind = multi_bind, | ||
506 | .unbind = __exit_p(multi_unbind), | ||
507 | .needs_serial = 1, | ||
508 | }; | ||
509 | |||
510 | module_usb_composite_driver(multi_driver); | ||