]>
Commit | Line | Data |
---|---|---|
5fd54ace | 1 | // SPDX-License-Identifier: GPL-2.0+ |
5ab54cf7 MN |
2 | /* |
3 | * g_ffs.c -- user mode file system API for USB composite function controllers | |
4 | * | |
5 | * Copyright (C) 2010 Samsung Electronics | |
54b8360f | 6 | * Author: Michal Nazarewicz <mina86@mina86.com> |
5ab54cf7 MN |
7 | */ |
8 | ||
aa02f172 MN |
9 | #define pr_fmt(fmt) "g_ffs: " fmt |
10 | ||
c6c56008 | 11 | #include <linux/module.h> |
6f823cd5 | 12 | |
c6c56008 | 13 | #if defined CONFIG_USB_FUNCTIONFS_ETH || defined CONFIG_USB_FUNCTIONFS_RNDIS |
85aec59f AP |
14 | #include <linux/netdevice.h> |
15 | ||
c6c56008 MN |
16 | # if defined USB_ETH_RNDIS |
17 | # undef USB_ETH_RNDIS | |
18 | # endif | |
19 | # ifdef CONFIG_USB_FUNCTIONFS_RNDIS | |
20 | # define USB_ETH_RNDIS y | |
21 | # endif | |
22 | ||
f212ad4e | 23 | # include "u_ecm.h" |
85aec59f | 24 | # include "u_gether.h" |
c6c56008 | 25 | # ifdef USB_ETH_RNDIS |
6e257b14 | 26 | # include "u_rndis.h" |
cbbd14a9 | 27 | # include "rndis.h" |
c6c56008 | 28 | # endif |
f1a1823f | 29 | # include "u_ether.h" |
c6c56008 | 30 | |
f212ad4e AP |
31 | USB_ETHERNET_MODULE_PARAMETERS(); |
32 | ||
f8dae531 | 33 | # ifdef CONFIG_USB_FUNCTIONFS_ETH |
6e257b14 | 34 | static int eth_bind_config(struct usb_configuration *c); |
f212ad4e AP |
35 | static struct usb_function_instance *fi_ecm; |
36 | static struct usb_function *f_ecm; | |
85aec59f AP |
37 | static struct usb_function_instance *fi_geth; |
38 | static struct usb_function *f_geth; | |
c6c56008 | 39 | # endif |
6e257b14 AP |
40 | # ifdef CONFIG_USB_FUNCTIONFS_RNDIS |
41 | static int bind_rndis_config(struct usb_configuration *c); | |
42 | static struct usb_function_instance *fi_rndis; | |
43 | static struct usb_function *f_rndis; | |
44 | # endif | |
c6c56008 MN |
45 | #endif |
46 | ||
6f823cd5 | 47 | #include "u_fs.h" |
c6c56008 | 48 | |
c6c56008 MN |
49 | #define DRIVER_NAME "g_ffs" |
50 | #define DRIVER_DESC "USB Function Filesystem" | |
51 | #define DRIVER_VERSION "24 Aug 2004" | |
52 | ||
53 | MODULE_DESCRIPTION(DRIVER_DESC); | |
54 | MODULE_AUTHOR("Michal Nazarewicz"); | |
55 | MODULE_LICENSE("GPL"); | |
56 | ||
fc19de61 MN |
57 | #define GFS_VENDOR_ID 0x1d6b /* Linux Foundation */ |
58 | #define GFS_PRODUCT_ID 0x0105 /* FunctionFS Gadget */ | |
c6c56008 | 59 | |
581791f5 AP |
60 | #define GFS_MAX_DEVS 10 |
61 | ||
7d16e8d3 SAS |
62 | USB_GADGET_COMPOSITE_OPTIONS(); |
63 | ||
c6c56008 MN |
64 | static struct usb_device_descriptor gfs_dev_desc = { |
65 | .bLength = sizeof gfs_dev_desc, | |
66 | .bDescriptorType = USB_DT_DEVICE, | |
67 | ||
0aecfc1b | 68 | /* .bcdUSB = DYNAMIC */ |
c6c56008 MN |
69 | .bDeviceClass = USB_CLASS_PER_INTERFACE, |
70 | ||
fc19de61 MN |
71 | .idVendor = cpu_to_le16(GFS_VENDOR_ID), |
72 | .idProduct = cpu_to_le16(GFS_PRODUCT_ID), | |
c6c56008 MN |
73 | }; |
74 | ||
581791f5 AP |
75 | static char *func_names[GFS_MAX_DEVS]; |
76 | static unsigned int func_num; | |
77 | ||
fc19de61 MN |
78 | module_param_named(bDeviceClass, gfs_dev_desc.bDeviceClass, byte, 0644); |
79 | MODULE_PARM_DESC(bDeviceClass, "USB Device class"); | |
80 | module_param_named(bDeviceSubClass, gfs_dev_desc.bDeviceSubClass, byte, 0644); | |
81 | MODULE_PARM_DESC(bDeviceSubClass, "USB Device subclass"); | |
82 | module_param_named(bDeviceProtocol, gfs_dev_desc.bDeviceProtocol, byte, 0644); | |
83 | MODULE_PARM_DESC(bDeviceProtocol, "USB Device protocol"); | |
581791f5 AP |
84 | module_param_array_named(functions, func_names, charp, &func_num, 0); |
85 | MODULE_PARM_DESC(functions, "USB Functions list"); | |
c6c56008 | 86 | |
75c9310a | 87 | static const struct usb_descriptor_header *gfs_otg_desc[2]; |
c6c56008 | 88 | |
5ab54cf7 | 89 | /* String IDs are assigned dynamically */ |
c6c56008 | 90 | static struct usb_string gfs_strings[] = { |
276e2e4f | 91 | [USB_GADGET_MANUFACTURER_IDX].s = "", |
d33f74fc | 92 | [USB_GADGET_PRODUCT_IDX].s = DRIVER_DESC, |
276e2e4f | 93 | [USB_GADGET_SERIAL_IDX].s = "", |
c6c56008 | 94 | #ifdef CONFIG_USB_FUNCTIONFS_RNDIS |
f8dae531 | 95 | { .s = "FunctionFS + RNDIS" }, |
c6c56008 MN |
96 | #endif |
97 | #ifdef CONFIG_USB_FUNCTIONFS_ETH | |
f8dae531 | 98 | { .s = "FunctionFS + ECM" }, |
c6c56008 MN |
99 | #endif |
100 | #ifdef CONFIG_USB_FUNCTIONFS_GENERIC | |
f8dae531 | 101 | { .s = "FunctionFS" }, |
c6c56008 MN |
102 | #endif |
103 | { } /* end of list */ | |
104 | }; | |
105 | ||
106 | static struct usb_gadget_strings *gfs_dev_strings[] = { | |
107 | &(struct usb_gadget_strings) { | |
108 | .language = 0x0409, /* en-us */ | |
109 | .strings = gfs_strings, | |
110 | }, | |
111 | NULL, | |
112 | }; | |
113 | ||
f8dae531 MN |
114 | struct gfs_configuration { |
115 | struct usb_configuration c; | |
6e257b14 | 116 | int (*eth)(struct usb_configuration *c); |
6f823cd5 | 117 | int num; |
2b87cd24 LP |
118 | }; |
119 | ||
120 | static struct gfs_configuration gfs_configurations[] = { | |
c6c56008 | 121 | #ifdef CONFIG_USB_FUNCTIONFS_RNDIS |
f8dae531 | 122 | { |
6e257b14 | 123 | .eth = bind_rndis_config, |
f8dae531 | 124 | }, |
c6c56008 MN |
125 | #endif |
126 | ||
c6c56008 | 127 | #ifdef CONFIG_USB_FUNCTIONFS_ETH |
f8dae531 MN |
128 | { |
129 | .eth = eth_bind_config, | |
130 | }, | |
c6c56008 MN |
131 | #endif |
132 | ||
c6c56008 | 133 | #ifdef CONFIG_USB_FUNCTIONFS_GENERIC |
f8dae531 MN |
134 | { |
135 | }, | |
c6c56008 | 136 | #endif |
f8dae531 | 137 | }; |
c6c56008 | 138 | |
6f823cd5 AP |
139 | static void *functionfs_acquire_dev(struct ffs_dev *dev); |
140 | static void functionfs_release_dev(struct ffs_dev *dev); | |
4b187fce AP |
141 | static int functionfs_ready_callback(struct ffs_data *ffs); |
142 | static void functionfs_closed_callback(struct ffs_data *ffs); | |
c6c56008 MN |
143 | static int gfs_bind(struct usb_composite_dev *cdev); |
144 | static int gfs_unbind(struct usb_composite_dev *cdev); | |
f8dae531 | 145 | static int gfs_do_config(struct usb_configuration *c); |
c6c56008 | 146 | |
6f823cd5 | 147 | |
c94e289f | 148 | static struct usb_composite_driver gfs_driver = { |
fc19de61 | 149 | .name = DRIVER_NAME, |
c6c56008 MN |
150 | .dev = &gfs_dev_desc, |
151 | .strings = gfs_dev_strings, | |
c8c0c6ad | 152 | .max_speed = USB_SPEED_SUPER, |
03e42bd5 | 153 | .bind = gfs_bind, |
c6c56008 MN |
154 | .unbind = gfs_unbind, |
155 | }; | |
156 | ||
581791f5 | 157 | static unsigned int missing_funcs; |
581791f5 AP |
158 | static bool gfs_registered; |
159 | static bool gfs_single_func; | |
6f823cd5 AP |
160 | static struct usb_function_instance **fi_ffs; |
161 | static struct usb_function **f_ffs[] = { | |
162 | #ifdef CONFIG_USB_FUNCTIONFS_RNDIS | |
163 | NULL, | |
164 | #endif | |
165 | ||
166 | #ifdef CONFIG_USB_FUNCTIONFS_ETH | |
167 | NULL, | |
168 | #endif | |
169 | ||
170 | #ifdef CONFIG_USB_FUNCTIONFS_GENERIC | |
171 | NULL, | |
172 | #endif | |
173 | }; | |
174 | ||
175 | #define N_CONF ARRAY_SIZE(f_ffs) | |
c6c56008 | 176 | |
8545e603 | 177 | static int __init gfs_init(void) |
c6c56008 | 178 | { |
6f823cd5 | 179 | struct f_fs_opts *opts; |
581791f5 | 180 | int i; |
4b187fce | 181 | int ret = 0; |
581791f5 | 182 | |
c6c56008 MN |
183 | ENTER(); |
184 | ||
4b187fce | 185 | if (func_num < 2) { |
581791f5 AP |
186 | gfs_single_func = true; |
187 | func_num = 1; | |
188 | } | |
189 | ||
6f823cd5 AP |
190 | /* |
191 | * Allocate in one chunk for easier maintenance | |
192 | */ | |
193 | f_ffs[0] = kcalloc(func_num * N_CONF, sizeof(*f_ffs), GFP_KERNEL); | |
194 | if (!f_ffs[0]) { | |
195 | ret = -ENOMEM; | |
196 | goto no_func; | |
197 | } | |
198 | for (i = 1; i < N_CONF; ++i) | |
199 | f_ffs[i] = f_ffs[0] + i * func_num; | |
200 | ||
201 | fi_ffs = kcalloc(func_num, sizeof(*fi_ffs), GFP_KERNEL); | |
202 | if (!fi_ffs) { | |
203 | ret = -ENOMEM; | |
204 | goto no_func; | |
205 | } | |
581791f5 | 206 | |
4b187fce | 207 | for (i = 0; i < func_num; i++) { |
6f823cd5 AP |
208 | fi_ffs[i] = usb_get_function_instance("ffs"); |
209 | if (IS_ERR(fi_ffs[i])) { | |
210 | ret = PTR_ERR(fi_ffs[i]); | |
4b187fce AP |
211 | --i; |
212 | goto no_dev; | |
213 | } | |
6f823cd5 | 214 | opts = to_f_fs_opts(fi_ffs[i]); |
4b187fce | 215 | if (gfs_single_func) |
6f823cd5 | 216 | ret = ffs_single_dev(opts->dev); |
4b187fce | 217 | else |
6f823cd5 | 218 | ret = ffs_name_dev(opts->dev, func_names[i]); |
4b187fce AP |
219 | if (ret) |
220 | goto no_dev; | |
6f823cd5 AP |
221 | opts->dev->ffs_ready_callback = functionfs_ready_callback; |
222 | opts->dev->ffs_closed_callback = functionfs_closed_callback; | |
223 | opts->dev->ffs_acquire_dev_callback = functionfs_acquire_dev; | |
224 | opts->dev->ffs_release_dev_callback = functionfs_release_dev; | |
225 | opts->no_configfs = true; | |
4b187fce | 226 | } |
581791f5 AP |
227 | |
228 | missing_funcs = func_num; | |
229 | ||
4b187fce AP |
230 | return 0; |
231 | no_dev: | |
4b187fce | 232 | while (i >= 0) |
6f823cd5 AP |
233 | usb_put_function_instance(fi_ffs[i--]); |
234 | kfree(fi_ffs); | |
235 | no_func: | |
236 | kfree(f_ffs[0]); | |
4b187fce | 237 | return ret; |
c6c56008 MN |
238 | } |
239 | module_init(gfs_init); | |
240 | ||
8545e603 | 241 | static void __exit gfs_exit(void) |
c6c56008 | 242 | { |
4b187fce AP |
243 | int i; |
244 | ||
c6c56008 MN |
245 | ENTER(); |
246 | ||
581791f5 | 247 | if (gfs_registered) |
c6c56008 | 248 | usb_composite_unregister(&gfs_driver); |
581791f5 | 249 | gfs_registered = false; |
c6c56008 | 250 | |
6f823cd5 AP |
251 | kfree(f_ffs[0]); |
252 | ||
4b187fce | 253 | for (i = 0; i < func_num; i++) |
6f823cd5 AP |
254 | usb_put_function_instance(fi_ffs[i]); |
255 | ||
256 | kfree(fi_ffs); | |
c6c56008 MN |
257 | } |
258 | module_exit(gfs_exit); | |
259 | ||
6f823cd5 AP |
260 | static void *functionfs_acquire_dev(struct ffs_dev *dev) |
261 | { | |
262 | if (!try_module_get(THIS_MODULE)) | |
d668b4f3 | 263 | return ERR_PTR(-ENOENT); |
872ce511 | 264 | |
2b87cd24 | 265 | return NULL; |
6f823cd5 AP |
266 | } |
267 | ||
268 | static void functionfs_release_dev(struct ffs_dev *dev) | |
269 | { | |
270 | module_put(THIS_MODULE); | |
271 | } | |
272 | ||
4b187fce | 273 | /* |
872ce511 | 274 | * The caller of this function takes ffs_lock |
4b187fce | 275 | */ |
c6c56008 MN |
276 | static int functionfs_ready_callback(struct ffs_data *ffs) |
277 | { | |
4b187fce | 278 | int ret = 0; |
581791f5 | 279 | |
4b187fce AP |
280 | if (--missing_funcs) |
281 | return 0; | |
c6c56008 | 282 | |
4b187fce AP |
283 | if (gfs_registered) |
284 | return -EBUSY; | |
581791f5 | 285 | |
581791f5 | 286 | gfs_registered = true; |
c6c56008 | 287 | |
03e42bd5 | 288 | ret = usb_composite_probe(&gfs_driver); |
c41b33c5 KO |
289 | if (unlikely(ret < 0)) { |
290 | ++missing_funcs; | |
581791f5 | 291 | gfs_registered = false; |
c41b33c5 | 292 | } |
872ce511 | 293 | |
c6c56008 MN |
294 | return ret; |
295 | } | |
296 | ||
4b187fce | 297 | /* |
872ce511 | 298 | * The caller of this function takes ffs_lock |
4b187fce | 299 | */ |
c6c56008 MN |
300 | static void functionfs_closed_callback(struct ffs_data *ffs) |
301 | { | |
581791f5 AP |
302 | missing_funcs++; |
303 | ||
304 | if (gfs_registered) | |
c6c56008 | 305 | usb_composite_unregister(&gfs_driver); |
581791f5 | 306 | gfs_registered = false; |
581791f5 AP |
307 | } |
308 | ||
309 | /* | |
4b187fce | 310 | * It is assumed that gfs_bind is called from a context where ffs_lock is held |
581791f5 | 311 | */ |
c6c56008 MN |
312 | static int gfs_bind(struct usb_composite_dev *cdev) |
313 | { | |
6e257b14 | 314 | #if defined CONFIG_USB_FUNCTIONFS_ETH || defined CONFIG_USB_FUNCTIONFS_RNDIS |
85aec59f AP |
315 | struct net_device *net; |
316 | #endif | |
f8dae531 | 317 | int ret, i; |
c6c56008 MN |
318 | |
319 | ENTER(); | |
320 | ||
581791f5 | 321 | if (missing_funcs) |
c6c56008 | 322 | return -ENODEV; |
f212ad4e AP |
323 | #if defined CONFIG_USB_FUNCTIONFS_ETH |
324 | if (can_support_ecm(cdev->gadget)) { | |
325 | struct f_ecm_opts *ecm_opts; | |
326 | ||
327 | fi_ecm = usb_get_function_instance("ecm"); | |
328 | if (IS_ERR(fi_ecm)) | |
329 | return PTR_ERR(fi_ecm); | |
330 | ecm_opts = container_of(fi_ecm, struct f_ecm_opts, func_inst); | |
85aec59f | 331 | net = ecm_opts->net; |
f212ad4e | 332 | } else { |
85aec59f AP |
333 | struct f_gether_opts *geth_opts; |
334 | ||
335 | fi_geth = usb_get_function_instance("geth"); | |
336 | if (IS_ERR(fi_geth)) | |
337 | return PTR_ERR(fi_geth); | |
338 | geth_opts = container_of(fi_geth, struct f_gether_opts, | |
339 | func_inst); | |
340 | net = geth_opts->net; | |
f212ad4e | 341 | } |
6e257b14 AP |
342 | #endif |
343 | ||
344 | #ifdef CONFIG_USB_FUNCTIONFS_RNDIS | |
345 | { | |
6e257b14 AP |
346 | fi_rndis = usb_get_function_instance("rndis"); |
347 | if (IS_ERR(fi_rndis)) { | |
348 | ret = PTR_ERR(fi_rndis); | |
349 | goto error; | |
350 | } | |
6e257b14 | 351 | #ifndef CONFIG_USB_FUNCTIONFS_ETH |
872ce511 MN |
352 | net = container_of(fi_rndis, struct f_rndis_opts, |
353 | func_inst)->net; | |
6e257b14 AP |
354 | #endif |
355 | } | |
356 | #endif | |
85aec59f | 357 | |
6e257b14 AP |
358 | #if defined CONFIG_USB_FUNCTIONFS_ETH || defined CONFIG_USB_FUNCTIONFS_RNDIS |
359 | gether_set_qmult(net, qmult); | |
85aec59f AP |
360 | if (!gether_set_host_addr(net, host_addr)) |
361 | pr_info("using host ethernet address: %s", host_addr); | |
362 | if (!gether_set_dev_addr(net, dev_addr)) | |
363 | pr_info("using self ethernet address: %s", dev_addr); | |
d6a01439 | 364 | #endif |
f212ad4e AP |
365 | |
366 | #if defined CONFIG_USB_FUNCTIONFS_RNDIS && defined CONFIG_USB_FUNCTIONFS_ETH | |
85aec59f AP |
367 | gether_set_gadget(net, cdev->gadget); |
368 | ret = gether_register_netdev(net); | |
369 | if (ret) | |
6e257b14 | 370 | goto error_rndis; |
85aec59f | 371 | |
f212ad4e AP |
372 | if (can_support_ecm(cdev->gadget)) { |
373 | struct f_ecm_opts *ecm_opts; | |
374 | ||
375 | ecm_opts = container_of(fi_ecm, struct f_ecm_opts, func_inst); | |
f212ad4e | 376 | ecm_opts->bound = true; |
85aec59f AP |
377 | } else { |
378 | struct f_gether_opts *geth_opts; | |
379 | ||
380 | geth_opts = container_of(fi_geth, struct f_gether_opts, | |
381 | func_inst); | |
382 | geth_opts->bound = true; | |
d6a01439 | 383 | } |
6e257b14 AP |
384 | |
385 | rndis_borrow_net(fi_rndis, net); | |
f212ad4e | 386 | #endif |
c6c56008 | 387 | |
6f823cd5 | 388 | /* TODO: gstrings_attach? */ |
f8dae531 | 389 | ret = usb_string_ids_tab(cdev, gfs_strings); |
c6c56008 | 390 | if (unlikely(ret < 0)) |
6e257b14 | 391 | goto error_rndis; |
d33f74fc | 392 | gfs_dev_desc.iProduct = gfs_strings[USB_GADGET_PRODUCT_IDX].id; |
c6c56008 | 393 | |
75c9310a LJ |
394 | if (gadget_is_otg(cdev->gadget) && !gfs_otg_desc[0]) { |
395 | struct usb_descriptor_header *usb_desc; | |
396 | ||
397 | usb_desc = usb_otg_descriptor_alloc(cdev->gadget); | |
398 | if (!usb_desc) | |
399 | goto error_rndis; | |
400 | usb_otg_descriptor_init(cdev->gadget, usb_desc); | |
401 | gfs_otg_desc[0] = usb_desc; | |
402 | gfs_otg_desc[1] = NULL; | |
403 | } | |
404 | ||
f8dae531 MN |
405 | for (i = 0; i < ARRAY_SIZE(gfs_configurations); ++i) { |
406 | struct gfs_configuration *c = gfs_configurations + i; | |
276e2e4f | 407 | int sid = USB_GADGET_FIRST_AVAIL_IDX + i; |
c6c56008 | 408 | |
276e2e4f SAS |
409 | c->c.label = gfs_strings[sid].s; |
410 | c->c.iConfiguration = gfs_strings[sid].id; | |
f8dae531 MN |
411 | c->c.bConfigurationValue = 1 + i; |
412 | c->c.bmAttributes = USB_CONFIG_ATT_SELFPOWER; | |
c6c56008 | 413 | |
6f823cd5 AP |
414 | c->num = i; |
415 | ||
c9bfff9c | 416 | ret = usb_add_config(cdev, &c->c, gfs_do_config); |
f8dae531 MN |
417 | if (unlikely(ret < 0)) |
418 | goto error_unbind; | |
419 | } | |
7d16e8d3 | 420 | usb_composite_overwrite_options(cdev, &coverwrite); |
c6c56008 MN |
421 | return 0; |
422 | ||
6f823cd5 | 423 | /* TODO */ |
c6c56008 | 424 | error_unbind: |
75c9310a LJ |
425 | kfree(gfs_otg_desc[0]); |
426 | gfs_otg_desc[0] = NULL; | |
6e257b14 AP |
427 | error_rndis: |
428 | #ifdef CONFIG_USB_FUNCTIONFS_RNDIS | |
429 | usb_put_function_instance(fi_rndis); | |
c6c56008 | 430 | error: |
6e257b14 | 431 | #endif |
f212ad4e AP |
432 | #if defined CONFIG_USB_FUNCTIONFS_ETH |
433 | if (can_support_ecm(cdev->gadget)) | |
434 | usb_put_function_instance(fi_ecm); | |
435 | else | |
85aec59f | 436 | usb_put_function_instance(fi_geth); |
f212ad4e | 437 | #endif |
c6c56008 MN |
438 | return ret; |
439 | } | |
440 | ||
581791f5 | 441 | /* |
4b187fce | 442 | * It is assumed that gfs_unbind is called from a context where ffs_lock is held |
581791f5 | 443 | */ |
c6c56008 MN |
444 | static int gfs_unbind(struct usb_composite_dev *cdev) |
445 | { | |
581791f5 AP |
446 | int i; |
447 | ||
c6c56008 MN |
448 | ENTER(); |
449 | ||
f212ad4e | 450 | |
6e257b14 AP |
451 | #ifdef CONFIG_USB_FUNCTIONFS_RNDIS |
452 | usb_put_function(f_rndis); | |
453 | usb_put_function_instance(fi_rndis); | |
454 | #endif | |
455 | ||
f212ad4e AP |
456 | #if defined CONFIG_USB_FUNCTIONFS_ETH |
457 | if (can_support_ecm(cdev->gadget)) { | |
458 | usb_put_function(f_ecm); | |
459 | usb_put_function_instance(fi_ecm); | |
460 | } else { | |
85aec59f AP |
461 | usb_put_function(f_geth); |
462 | usb_put_function_instance(fi_geth); | |
f212ad4e | 463 | } |
f212ad4e | 464 | #endif |
6f823cd5 AP |
465 | for (i = 0; i < N_CONF * func_num; ++i) |
466 | usb_put_function(*(f_ffs[0] + i)); | |
c6c56008 | 467 | |
75c9310a LJ |
468 | kfree(gfs_otg_desc[0]); |
469 | gfs_otg_desc[0] = NULL; | |
470 | ||
c6c56008 MN |
471 | return 0; |
472 | } | |
473 | ||
581791f5 AP |
474 | /* |
475 | * It is assumed that gfs_do_config is called from a context where | |
4b187fce | 476 | * ffs_lock is held |
581791f5 | 477 | */ |
f8dae531 | 478 | static int gfs_do_config(struct usb_configuration *c) |
c6c56008 | 479 | { |
f8dae531 MN |
480 | struct gfs_configuration *gc = |
481 | container_of(c, struct gfs_configuration, c); | |
581791f5 | 482 | int i; |
c6c56008 MN |
483 | int ret; |
484 | ||
581791f5 | 485 | if (missing_funcs) |
c6c56008 MN |
486 | return -ENODEV; |
487 | ||
488 | if (gadget_is_otg(c->cdev->gadget)) { | |
489 | c->descriptors = gfs_otg_desc; | |
490 | c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; | |
491 | } | |
492 | ||
f8dae531 | 493 | if (gc->eth) { |
6e257b14 | 494 | ret = gc->eth(c); |
c6c56008 MN |
495 | if (unlikely(ret < 0)) |
496 | return ret; | |
497 | } | |
498 | ||
581791f5 | 499 | for (i = 0; i < func_num; i++) { |
6f823cd5 AP |
500 | f_ffs[gc->num][i] = usb_get_function(fi_ffs[i]); |
501 | if (IS_ERR(f_ffs[gc->num][i])) { | |
502 | ret = PTR_ERR(f_ffs[gc->num][i]); | |
503 | goto error; | |
504 | } | |
505 | ret = usb_add_function(c, f_ffs[gc->num][i]); | |
506 | if (ret < 0) { | |
507 | usb_put_function(f_ffs[gc->num][i]); | |
508 | goto error; | |
509 | } | |
581791f5 | 510 | } |
c6c56008 | 511 | |
fc19de61 MN |
512 | /* |
513 | * After previous do_configs there may be some invalid | |
f588c0db MN |
514 | * pointers in c->interface array. This happens every time |
515 | * a user space function with fewer interfaces than a user | |
516 | * space function that was run before the new one is run. The | |
517 | * compasit's set_config() assumes that if there is no more | |
518 | * then MAX_CONFIG_INTERFACES interfaces in a configuration | |
519 | * then there is a NULL pointer after the last interface in | |
fc19de61 MN |
520 | * c->interface array. We need to make sure this is true. |
521 | */ | |
f588c0db MN |
522 | if (c->next_interface_id < ARRAY_SIZE(c->interface)) |
523 | c->interface[c->next_interface_id] = NULL; | |
524 | ||
c6c56008 | 525 | return 0; |
6f823cd5 AP |
526 | error: |
527 | while (--i >= 0) { | |
528 | if (!IS_ERR(f_ffs[gc->num][i])) | |
529 | usb_remove_function(c, f_ffs[gc->num][i]); | |
530 | usb_put_function(f_ffs[gc->num][i]); | |
531 | } | |
532 | return ret; | |
c6c56008 MN |
533 | } |
534 | ||
c6c56008 | 535 | #ifdef CONFIG_USB_FUNCTIONFS_ETH |
fc19de61 | 536 | |
6e257b14 | 537 | static int eth_bind_config(struct usb_configuration *c) |
c6c56008 | 538 | { |
f212ad4e AP |
539 | int status = 0; |
540 | ||
541 | if (can_support_ecm(c->cdev->gadget)) { | |
542 | f_ecm = usb_get_function(fi_ecm); | |
543 | if (IS_ERR(f_ecm)) | |
544 | return PTR_ERR(f_ecm); | |
545 | ||
546 | status = usb_add_function(c, f_ecm); | |
547 | if (status < 0) | |
548 | usb_put_function(f_ecm); | |
549 | ||
550 | } else { | |
85aec59f AP |
551 | f_geth = usb_get_function(fi_geth); |
552 | if (IS_ERR(f_geth)) | |
553 | return PTR_ERR(f_geth); | |
554 | ||
555 | status = usb_add_function(c, f_geth); | |
556 | if (status < 0) | |
557 | usb_put_function(f_geth); | |
f212ad4e AP |
558 | } |
559 | return status; | |
c6c56008 | 560 | } |
fc19de61 | 561 | |
c6c56008 | 562 | #endif |
6e257b14 AP |
563 | |
564 | #ifdef CONFIG_USB_FUNCTIONFS_RNDIS | |
565 | ||
566 | static int bind_rndis_config(struct usb_configuration *c) | |
567 | { | |
568 | int status = 0; | |
569 | ||
570 | f_rndis = usb_get_function(fi_rndis); | |
571 | if (IS_ERR(f_rndis)) | |
572 | return PTR_ERR(f_rndis); | |
573 | ||
574 | status = usb_add_function(c, f_rndis); | |
575 | if (status < 0) | |
576 | usb_put_function(f_rndis); | |
577 | ||
578 | return status; | |
579 | } | |
580 | ||
581 | #endif |