Commit | Line | Data |
---|---|---|
5ab54cf7 MN |
1 | /* |
2 | * g_ffs.c -- user mode file system API for USB composite function controllers | |
3 | * | |
4 | * Copyright (C) 2010 Samsung Electronics | |
54b8360f | 5 | * Author: Michal Nazarewicz <mina86@mina86.com> |
5ab54cf7 MN |
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. | |
5ab54cf7 MN |
11 | */ |
12 | ||
aa02f172 MN |
13 | #define pr_fmt(fmt) "g_ffs: " fmt |
14 | ||
c6c56008 MN |
15 | #include <linux/module.h> |
16 | #include <linux/utsname.h> | |
17 | ||
c6c56008 MN |
18 | /* |
19 | * kbuild is not very cooperative with respect to linking separately | |
20 | * compiled library objects into one module. So for now we won't use | |
21 | * separate compilation ... ensuring init/exit sections work to shrink | |
22 | * the runtime footprint, and giving us at least some parts of what | |
23 | * a "gcc --combine ... part1.c part2.c part3.c ... " build would. | |
24 | */ | |
25 | ||
26 | #include "composite.c" | |
27 | #include "usbstring.c" | |
28 | #include "config.c" | |
29 | #include "epautoconf.c" | |
30 | ||
31 | #if defined CONFIG_USB_FUNCTIONFS_ETH || defined CONFIG_USB_FUNCTIONFS_RNDIS | |
32 | # if defined USB_ETH_RNDIS | |
33 | # undef USB_ETH_RNDIS | |
34 | # endif | |
35 | # ifdef CONFIG_USB_FUNCTIONFS_RNDIS | |
36 | # define USB_ETH_RNDIS y | |
37 | # endif | |
38 | ||
39 | # include "f_ecm.c" | |
40 | # include "f_subset.c" | |
41 | # ifdef USB_ETH_RNDIS | |
42 | # include "f_rndis.c" | |
43 | # include "rndis.c" | |
44 | # endif | |
45 | # include "u_ether.c" | |
46 | ||
47 | static u8 gfs_hostaddr[ETH_ALEN]; | |
f8dae531 MN |
48 | # ifdef CONFIG_USB_FUNCTIONFS_ETH |
49 | static int eth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]); | |
c6c56008 | 50 | # endif |
f8dae531 | 51 | #else |
c6c56008 MN |
52 | # define gether_cleanup() do { } while (0) |
53 | # define gether_setup(gadget, hostaddr) ((int)0) | |
f8dae531 | 54 | # define gfs_hostaddr NULL |
c6c56008 MN |
55 | #endif |
56 | ||
57 | #include "f_fs.c" | |
58 | ||
c6c56008 MN |
59 | #define DRIVER_NAME "g_ffs" |
60 | #define DRIVER_DESC "USB Function Filesystem" | |
61 | #define DRIVER_VERSION "24 Aug 2004" | |
62 | ||
63 | MODULE_DESCRIPTION(DRIVER_DESC); | |
64 | MODULE_AUTHOR("Michal Nazarewicz"); | |
65 | MODULE_LICENSE("GPL"); | |
66 | ||
fc19de61 MN |
67 | #define GFS_VENDOR_ID 0x1d6b /* Linux Foundation */ |
68 | #define GFS_PRODUCT_ID 0x0105 /* FunctionFS Gadget */ | |
c6c56008 | 69 | |
581791f5 AP |
70 | #define GFS_MAX_DEVS 10 |
71 | ||
72 | struct gfs_ffs_obj { | |
73 | const char *name; | |
74 | bool mounted; | |
75 | bool desc_ready; | |
76 | struct ffs_data *ffs_data; | |
77 | }; | |
78 | ||
c6c56008 MN |
79 | static struct usb_device_descriptor gfs_dev_desc = { |
80 | .bLength = sizeof gfs_dev_desc, | |
81 | .bDescriptorType = USB_DT_DEVICE, | |
82 | ||
83 | .bcdUSB = cpu_to_le16(0x0200), | |
84 | .bDeviceClass = USB_CLASS_PER_INTERFACE, | |
85 | ||
fc19de61 MN |
86 | .idVendor = cpu_to_le16(GFS_VENDOR_ID), |
87 | .idProduct = cpu_to_le16(GFS_PRODUCT_ID), | |
c6c56008 MN |
88 | }; |
89 | ||
581791f5 AP |
90 | static char *func_names[GFS_MAX_DEVS]; |
91 | static unsigned int func_num; | |
92 | ||
fc19de61 MN |
93 | module_param_named(bDeviceClass, gfs_dev_desc.bDeviceClass, byte, 0644); |
94 | MODULE_PARM_DESC(bDeviceClass, "USB Device class"); | |
95 | module_param_named(bDeviceSubClass, gfs_dev_desc.bDeviceSubClass, byte, 0644); | |
96 | MODULE_PARM_DESC(bDeviceSubClass, "USB Device subclass"); | |
97 | module_param_named(bDeviceProtocol, gfs_dev_desc.bDeviceProtocol, byte, 0644); | |
98 | MODULE_PARM_DESC(bDeviceProtocol, "USB Device protocol"); | |
581791f5 AP |
99 | module_param_array_named(functions, func_names, charp, &func_num, 0); |
100 | MODULE_PARM_DESC(functions, "USB Functions list"); | |
c6c56008 | 101 | |
c6c56008 MN |
102 | static const struct usb_descriptor_header *gfs_otg_desc[] = { |
103 | (const struct usb_descriptor_header *) | |
104 | &(const struct usb_otg_descriptor) { | |
105 | .bLength = sizeof(struct usb_otg_descriptor), | |
106 | .bDescriptorType = USB_DT_OTG, | |
107 | ||
fc19de61 MN |
108 | /* |
109 | * REVISIT SRP-only hardware is possible, although | |
110 | * it would not be called "OTG" ... | |
111 | */ | |
c6c56008 MN |
112 | .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, |
113 | }, | |
114 | ||
115 | NULL | |
116 | }; | |
117 | ||
5ab54cf7 | 118 | /* String IDs are assigned dynamically */ |
c6c56008 | 119 | static struct usb_string gfs_strings[] = { |
c6c56008 | 120 | #ifdef CONFIG_USB_FUNCTIONFS_RNDIS |
f8dae531 | 121 | { .s = "FunctionFS + RNDIS" }, |
c6c56008 MN |
122 | #endif |
123 | #ifdef CONFIG_USB_FUNCTIONFS_ETH | |
f8dae531 | 124 | { .s = "FunctionFS + ECM" }, |
c6c56008 MN |
125 | #endif |
126 | #ifdef CONFIG_USB_FUNCTIONFS_GENERIC | |
f8dae531 | 127 | { .s = "FunctionFS" }, |
c6c56008 MN |
128 | #endif |
129 | { } /* end of list */ | |
130 | }; | |
131 | ||
132 | static struct usb_gadget_strings *gfs_dev_strings[] = { | |
133 | &(struct usb_gadget_strings) { | |
134 | .language = 0x0409, /* en-us */ | |
135 | .strings = gfs_strings, | |
136 | }, | |
137 | NULL, | |
138 | }; | |
139 | ||
f8dae531 MN |
140 | struct gfs_configuration { |
141 | struct usb_configuration c; | |
142 | int (*eth)(struct usb_configuration *c, u8 *ethaddr); | |
143 | } gfs_configurations[] = { | |
c6c56008 | 144 | #ifdef CONFIG_USB_FUNCTIONFS_RNDIS |
f8dae531 MN |
145 | { |
146 | .eth = rndis_bind_config, | |
147 | }, | |
c6c56008 MN |
148 | #endif |
149 | ||
c6c56008 | 150 | #ifdef CONFIG_USB_FUNCTIONFS_ETH |
f8dae531 MN |
151 | { |
152 | .eth = eth_bind_config, | |
153 | }, | |
c6c56008 MN |
154 | #endif |
155 | ||
c6c56008 | 156 | #ifdef CONFIG_USB_FUNCTIONFS_GENERIC |
f8dae531 MN |
157 | { |
158 | }, | |
c6c56008 | 159 | #endif |
f8dae531 | 160 | }; |
c6c56008 | 161 | |
c6c56008 MN |
162 | static int gfs_bind(struct usb_composite_dev *cdev); |
163 | static int gfs_unbind(struct usb_composite_dev *cdev); | |
f8dae531 | 164 | static int gfs_do_config(struct usb_configuration *c); |
c6c56008 | 165 | |
c2ec75c2 | 166 | static __refdata struct usb_composite_driver gfs_driver = { |
fc19de61 | 167 | .name = DRIVER_NAME, |
c6c56008 MN |
168 | .dev = &gfs_dev_desc, |
169 | .strings = gfs_dev_strings, | |
35a0e0bf | 170 | .max_speed = USB_SPEED_HIGH, |
c6c56008 | 171 | .unbind = gfs_unbind, |
fc19de61 | 172 | .iProduct = DRIVER_DESC, |
c6c56008 MN |
173 | }; |
174 | ||
581791f5 AP |
175 | static DEFINE_MUTEX(gfs_lock); |
176 | static unsigned int missing_funcs; | |
177 | static bool gfs_ether_setup; | |
178 | static bool gfs_registered; | |
179 | static bool gfs_single_func; | |
180 | static struct gfs_ffs_obj *ffs_tab; | |
c6c56008 | 181 | |
8545e603 | 182 | static int __init gfs_init(void) |
c6c56008 | 183 | { |
581791f5 AP |
184 | int i; |
185 | ||
c6c56008 MN |
186 | ENTER(); |
187 | ||
581791f5 AP |
188 | if (!func_num) { |
189 | gfs_single_func = true; | |
190 | func_num = 1; | |
191 | } | |
192 | ||
193 | ffs_tab = kcalloc(func_num, sizeof *ffs_tab, GFP_KERNEL); | |
194 | if (!ffs_tab) | |
195 | return -ENOMEM; | |
196 | ||
197 | if (!gfs_single_func) | |
198 | for (i = 0; i < func_num; i++) | |
199 | ffs_tab[i].name = func_names[i]; | |
200 | ||
201 | missing_funcs = func_num; | |
202 | ||
c6c56008 MN |
203 | return functionfs_init(); |
204 | } | |
205 | module_init(gfs_init); | |
206 | ||
8545e603 | 207 | static void __exit gfs_exit(void) |
c6c56008 MN |
208 | { |
209 | ENTER(); | |
581791f5 | 210 | mutex_lock(&gfs_lock); |
c6c56008 | 211 | |
581791f5 | 212 | if (gfs_registered) |
c6c56008 | 213 | usb_composite_unregister(&gfs_driver); |
581791f5 | 214 | gfs_registered = false; |
c6c56008 MN |
215 | |
216 | functionfs_cleanup(); | |
581791f5 AP |
217 | |
218 | mutex_unlock(&gfs_lock); | |
219 | kfree(ffs_tab); | |
c6c56008 MN |
220 | } |
221 | module_exit(gfs_exit); | |
222 | ||
581791f5 AP |
223 | static struct gfs_ffs_obj *gfs_find_dev(const char *dev_name) |
224 | { | |
225 | int i; | |
226 | ||
227 | ENTER(); | |
228 | ||
229 | if (gfs_single_func) | |
230 | return &ffs_tab[0]; | |
231 | ||
232 | for (i = 0; i < func_num; i++) | |
233 | if (strcmp(ffs_tab[i].name, dev_name) == 0) | |
234 | return &ffs_tab[i]; | |
235 | ||
236 | return NULL; | |
237 | } | |
238 | ||
c6c56008 MN |
239 | static int functionfs_ready_callback(struct ffs_data *ffs) |
240 | { | |
581791f5 | 241 | struct gfs_ffs_obj *ffs_obj; |
c6c56008 MN |
242 | int ret; |
243 | ||
244 | ENTER(); | |
581791f5 AP |
245 | mutex_lock(&gfs_lock); |
246 | ||
247 | ffs_obj = ffs->private_data; | |
248 | if (!ffs_obj) { | |
249 | ret = -EINVAL; | |
250 | goto done; | |
251 | } | |
252 | ||
253 | if (WARN_ON(ffs_obj->desc_ready)) { | |
254 | ret = -EBUSY; | |
255 | goto done; | |
256 | } | |
257 | ffs_obj->desc_ready = true; | |
258 | ffs_obj->ffs_data = ffs; | |
c6c56008 | 259 | |
581791f5 AP |
260 | if (--missing_funcs) { |
261 | ret = 0; | |
262 | goto done; | |
263 | } | |
264 | ||
265 | if (gfs_registered) { | |
266 | ret = -EBUSY; | |
267 | goto done; | |
268 | } | |
269 | gfs_registered = true; | |
c6c56008 | 270 | |
07a18bd7 | 271 | ret = usb_composite_probe(&gfs_driver, gfs_bind); |
c6c56008 | 272 | if (unlikely(ret < 0)) |
581791f5 AP |
273 | gfs_registered = false; |
274 | ||
275 | done: | |
276 | mutex_unlock(&gfs_lock); | |
c6c56008 MN |
277 | return ret; |
278 | } | |
279 | ||
280 | static void functionfs_closed_callback(struct ffs_data *ffs) | |
281 | { | |
581791f5 AP |
282 | struct gfs_ffs_obj *ffs_obj; |
283 | ||
c6c56008 | 284 | ENTER(); |
581791f5 | 285 | mutex_lock(&gfs_lock); |
c6c56008 | 286 | |
581791f5 AP |
287 | ffs_obj = ffs->private_data; |
288 | if (!ffs_obj) | |
289 | goto done; | |
290 | ||
291 | ffs_obj->desc_ready = false; | |
292 | missing_funcs++; | |
293 | ||
294 | if (gfs_registered) | |
c6c56008 | 295 | usb_composite_unregister(&gfs_driver); |
581791f5 AP |
296 | gfs_registered = false; |
297 | ||
298 | done: | |
299 | mutex_unlock(&gfs_lock); | |
c6c56008 MN |
300 | } |
301 | ||
581791f5 | 302 | static void *functionfs_acquire_dev_callback(const char *dev_name) |
c6c56008 | 303 | { |
581791f5 AP |
304 | struct gfs_ffs_obj *ffs_dev; |
305 | ||
306 | ENTER(); | |
307 | mutex_lock(&gfs_lock); | |
308 | ||
309 | ffs_dev = gfs_find_dev(dev_name); | |
310 | if (!ffs_dev) { | |
311 | ffs_dev = ERR_PTR(-ENODEV); | |
312 | goto done; | |
313 | } | |
314 | ||
315 | if (ffs_dev->mounted) { | |
316 | ffs_dev = ERR_PTR(-EBUSY); | |
317 | goto done; | |
318 | } | |
319 | ffs_dev->mounted = true; | |
320 | ||
321 | done: | |
322 | mutex_unlock(&gfs_lock); | |
323 | return ffs_dev; | |
c6c56008 MN |
324 | } |
325 | ||
581791f5 AP |
326 | static void functionfs_release_dev_callback(struct ffs_data *ffs_data) |
327 | { | |
328 | struct gfs_ffs_obj *ffs_dev; | |
329 | ||
330 | ENTER(); | |
331 | mutex_lock(&gfs_lock); | |
332 | ||
333 | ffs_dev = ffs_data->private_data; | |
334 | if (ffs_dev) | |
335 | ffs_dev->mounted = false; | |
336 | ||
337 | mutex_unlock(&gfs_lock); | |
338 | } | |
339 | ||
340 | /* | |
341 | * It is assumed that gfs_bind is called from a context where gfs_lock is held | |
342 | */ | |
c6c56008 MN |
343 | static int gfs_bind(struct usb_composite_dev *cdev) |
344 | { | |
f8dae531 | 345 | int ret, i; |
c6c56008 MN |
346 | |
347 | ENTER(); | |
348 | ||
581791f5 | 349 | if (missing_funcs) |
c6c56008 MN |
350 | return -ENODEV; |
351 | ||
352 | ret = gether_setup(cdev->gadget, gfs_hostaddr); | |
353 | if (unlikely(ret < 0)) | |
354 | goto error_quick; | |
581791f5 | 355 | gfs_ether_setup = true; |
c6c56008 | 356 | |
f8dae531 | 357 | ret = usb_string_ids_tab(cdev, gfs_strings); |
c6c56008 MN |
358 | if (unlikely(ret < 0)) |
359 | goto error; | |
c6c56008 | 360 | |
581791f5 AP |
361 | for (i = func_num; --i; ) { |
362 | ret = functionfs_bind(ffs_tab[i].ffs_data, cdev); | |
363 | if (unlikely(ret < 0)) { | |
364 | while (++i < func_num) | |
365 | functionfs_unbind(ffs_tab[i].ffs_data); | |
366 | goto error; | |
367 | } | |
368 | } | |
c6c56008 | 369 | |
f8dae531 MN |
370 | for (i = 0; i < ARRAY_SIZE(gfs_configurations); ++i) { |
371 | struct gfs_configuration *c = gfs_configurations + i; | |
c6c56008 | 372 | |
fc19de61 MN |
373 | c->c.label = gfs_strings[i].s; |
374 | c->c.iConfiguration = gfs_strings[i].id; | |
f8dae531 MN |
375 | c->c.bConfigurationValue = 1 + i; |
376 | c->c.bmAttributes = USB_CONFIG_ATT_SELFPOWER; | |
c6c56008 | 377 | |
c9bfff9c | 378 | ret = usb_add_config(cdev, &c->c, gfs_do_config); |
f8dae531 MN |
379 | if (unlikely(ret < 0)) |
380 | goto error_unbind; | |
381 | } | |
c6c56008 MN |
382 | |
383 | return 0; | |
384 | ||
385 | error_unbind: | |
581791f5 AP |
386 | for (i = 0; i < func_num; i++) |
387 | functionfs_unbind(ffs_tab[i].ffs_data); | |
c6c56008 MN |
388 | error: |
389 | gether_cleanup(); | |
390 | error_quick: | |
581791f5 | 391 | gfs_ether_setup = false; |
c6c56008 MN |
392 | return ret; |
393 | } | |
394 | ||
581791f5 AP |
395 | /* |
396 | * It is assumed that gfs_unbind is called from a context where gfs_lock is held | |
397 | */ | |
c6c56008 MN |
398 | static int gfs_unbind(struct usb_composite_dev *cdev) |
399 | { | |
581791f5 AP |
400 | int i; |
401 | ||
c6c56008 MN |
402 | ENTER(); |
403 | ||
fc19de61 MN |
404 | /* |
405 | * We may have been called in an error recovery from | |
c6c56008 MN |
406 | * composite_bind() after gfs_unbind() failure so we need to |
407 | * check if gfs_ffs_data is not NULL since gfs_bind() handles | |
408 | * all error recovery itself. I'd rather we werent called | |
409 | * from composite on orror recovery, but what you're gonna | |
fc19de61 MN |
410 | * do...? |
411 | */ | |
581791f5 | 412 | if (gfs_ether_setup) |
c6c56008 | 413 | gether_cleanup(); |
581791f5 AP |
414 | gfs_ether_setup = false; |
415 | ||
416 | for (i = func_num; --i; ) | |
417 | if (ffs_tab[i].ffs_data) | |
418 | functionfs_unbind(ffs_tab[i].ffs_data); | |
c6c56008 MN |
419 | |
420 | return 0; | |
421 | } | |
422 | ||
581791f5 AP |
423 | /* |
424 | * It is assumed that gfs_do_config is called from a context where | |
425 | * gfs_lock is held | |
426 | */ | |
f8dae531 | 427 | static int gfs_do_config(struct usb_configuration *c) |
c6c56008 | 428 | { |
f8dae531 MN |
429 | struct gfs_configuration *gc = |
430 | container_of(c, struct gfs_configuration, c); | |
581791f5 | 431 | int i; |
c6c56008 MN |
432 | int ret; |
433 | ||
581791f5 | 434 | if (missing_funcs) |
c6c56008 MN |
435 | return -ENODEV; |
436 | ||
437 | if (gadget_is_otg(c->cdev->gadget)) { | |
438 | c->descriptors = gfs_otg_desc; | |
439 | c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; | |
440 | } | |
441 | ||
f8dae531 MN |
442 | if (gc->eth) { |
443 | ret = gc->eth(c, gfs_hostaddr); | |
c6c56008 MN |
444 | if (unlikely(ret < 0)) |
445 | return ret; | |
446 | } | |
447 | ||
581791f5 AP |
448 | for (i = 0; i < func_num; i++) { |
449 | ret = functionfs_bind_config(c->cdev, c, ffs_tab[i].ffs_data); | |
450 | if (unlikely(ret < 0)) | |
451 | return ret; | |
452 | } | |
c6c56008 | 453 | |
fc19de61 MN |
454 | /* |
455 | * After previous do_configs there may be some invalid | |
f588c0db MN |
456 | * pointers in c->interface array. This happens every time |
457 | * a user space function with fewer interfaces than a user | |
458 | * space function that was run before the new one is run. The | |
459 | * compasit's set_config() assumes that if there is no more | |
460 | * then MAX_CONFIG_INTERFACES interfaces in a configuration | |
461 | * then there is a NULL pointer after the last interface in | |
fc19de61 MN |
462 | * c->interface array. We need to make sure this is true. |
463 | */ | |
f588c0db MN |
464 | if (c->next_interface_id < ARRAY_SIZE(c->interface)) |
465 | c->interface[c->next_interface_id] = NULL; | |
466 | ||
c6c56008 MN |
467 | return 0; |
468 | } | |
469 | ||
c6c56008 | 470 | #ifdef CONFIG_USB_FUNCTIONFS_ETH |
fc19de61 | 471 | |
f8dae531 | 472 | static int eth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) |
c6c56008 | 473 | { |
f8dae531 MN |
474 | return can_support_ecm(c->cdev->gadget) |
475 | ? ecm_bind_config(c, ethaddr) | |
476 | : geth_bind_config(c, ethaddr); | |
c6c56008 | 477 | } |
fc19de61 | 478 | |
c6c56008 | 479 | #endif |