summaryrefslogblamecommitdiffstats
path: root/drivers/usb/gadget/function/uvc_configfs.c
blob: 3c0467bcb14fd9a3ccc15104a22e24f461781b4d (plain) (tree)
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720












































                                                                         
                                                                                   


























































































                                                                                
                                                           











                                                                               
                                        











                                                                              
                                                                











































































































































































































































































































































































































































































































































































                                                                                
                                                                   











































































                                                                               
                                                                                       











































                                                                              
                              









































































































                                                                              
                                                             











                                                                         
                                        











                                                                                
                                                                  










































                                                                         
                                                                 











                                                              
                                                                              












                                                                           
                                                                         













                                                                         




                                                                         












                                                                         
                                                                         












                                                                         
                              
 





                                                                             
                                                                          
                                              
                                                                  
                                              
 
                      










































                                                                            







































































































                                                                               
                                                  














                                                                      
                                        






















                                                                          
                         









                                                                     
                                                                                 





















                                                                   
                                                                               
















































































































































































































                                                                                
                                                         
















                                                                              
                                        


















                                                                          
                                                              






















                                                                     
                                                                 





















































































































































                                                                               
                                                  












                                                                       
                                        
















                                                                   
                                                       







































































































































                                                                               


















                                                                                

                                                                

                                                                          




























                                                                        







                                                                                
















































                                                                           






                                                            





















































































                                                                           
                               



























                                                                               
                              


                            
                                                     











                                                              




                                                                          






































































































































































































































































































                                                                              
/*
 * uvc_configfs.c
 *
 * Configfs support for the uvc function.
 *
 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
 *		http://www.samsung.com
 *
 * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include "u_uvc.h"
#include "uvc_configfs.h"

#define UVCG_STREAMING_CONTROL_SIZE	1

#define CONFIGFS_ATTR_OPS_RO(_item)					\
static ssize_t _item##_attr_show(struct config_item *item,		\
				 struct configfs_attribute *attr,	\
				 char *page)				\
{									\
	struct _item *_item = to_##_item(item);				\
	struct _item##_attribute *_item##_attr =			\
		container_of(attr, struct _item##_attribute, attr);	\
	ssize_t ret = 0;						\
									\
	if (_item##_attr->show)						\
		ret = _item##_attr->show(_item, page);			\
	return ret;							\
}

static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item);

/* control/header/<NAME> */
DECLARE_UVC_HEADER_DESCRIPTOR(1);

struct uvcg_control_header {
	struct config_item		item;
	struct UVC_HEADER_DESCRIPTOR(1)	desc;
	unsigned			linked;
};

static struct uvcg_control_header *to_uvcg_control_header(struct config_item *item)
{
	return container_of(item, struct uvcg_control_header, item);
}

CONFIGFS_ATTR_STRUCT(uvcg_control_header);
CONFIGFS_ATTR_OPS(uvcg_control_header);

static struct configfs_item_operations uvcg_control_header_item_ops = {
	.show_attribute		= uvcg_control_header_attr_show,
	.store_attribute	= uvcg_control_header_attr_store,
};

#define UVCG_CTRL_HDR_ATTR(cname, aname, conv, str2u, uxx, vnoc, limit)	\
static ssize_t uvcg_control_header_##cname##_show(			\
	struct uvcg_control_header *ch, char *page)			\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\
	int result;							\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = ch->item.ci_parent->ci_parent->ci_parent;		\
	opts = to_f_uvc_opts(opts_item);				\
									\
	mutex_lock(&opts->lock);					\
	result = sprintf(page, "%d\n", conv(ch->desc.aname));		\
	mutex_unlock(&opts->lock);					\
									\
	mutex_unlock(su_mutex);						\
	return result;							\
}									\
									\
static ssize_t								\
uvcg_control_header_##cname##_store(struct uvcg_control_header *ch,	\
			   const char *page, size_t len)		\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;\
	int ret;							\
	uxx num;							\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = ch->item.ci_parent->ci_parent->ci_parent;		\
	opts = to_f_uvc_opts(opts_item);				\
									\
	mutex_lock(&opts->lock);					\
	if (ch->linked || opts->refcnt) {				\
		ret = -EBUSY;						\
		goto end;						\
	}								\
									\
	ret = str2u(page, 0, &num);					\
	if (ret)							\
		goto end;						\
									\
	if (num > limit) {						\
		ret = -EINVAL;						\
		goto end;						\
	}								\
	ch->desc.aname = vnoc(num);					\
	ret = len;							\
end:									\
	mutex_unlock(&opts->lock);					\
	mutex_unlock(su_mutex);						\
	return ret;							\
}									\
									\
static struct uvcg_control_header_attribute				\
	uvcg_control_header_##cname =					\
	__CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR,			\
			uvcg_control_header_##cname##_show,		\
			uvcg_control_header_##cname##_store)

UVCG_CTRL_HDR_ATTR(bcd_uvc, bcdUVC, le16_to_cpu, kstrtou16, u16, cpu_to_le16,
		   0xffff);

UVCG_CTRL_HDR_ATTR(dw_clock_frequency, dwClockFrequency, le32_to_cpu, kstrtou32,
		   u32, cpu_to_le32, 0x7fffffff);

#undef UVCG_CTRL_HDR_ATTR

static struct configfs_attribute *uvcg_control_header_attrs[] = {
	&uvcg_control_header_bcd_uvc.attr,
	&uvcg_control_header_dw_clock_frequency.attr,
	NULL,
};

static struct config_item_type uvcg_control_header_type = {
	.ct_item_ops	= &uvcg_control_header_item_ops,
	.ct_attrs	= uvcg_control_header_attrs,
	.ct_owner	= THIS_MODULE,
};

static struct config_item *uvcg_control_header_make(struct config_group *group,
						    const char *name)
{
	struct uvcg_control_header *h;

	h = kzalloc(sizeof(*h), GFP_KERNEL);
	if (!h)
		return ERR_PTR(-ENOMEM);

	h->desc.bLength			= UVC_DT_HEADER_SIZE(1);
	h->desc.bDescriptorType		= USB_DT_CS_INTERFACE;
	h->desc.bDescriptorSubType	= UVC_VC_HEADER;
	h->desc.bcdUVC			= cpu_to_le16(0x0100);
	h->desc.dwClockFrequency	= cpu_to_le32(48000000);

	config_item_init_type_name(&h->item, name, &uvcg_control_header_type);

	return &h->item;
}

static void uvcg_control_header_drop(struct config_group *group,
			      struct config_item *item)
{
	struct uvcg_control_header *h = to_uvcg_control_header(item);

	kfree(h);
}

/* control/header */
static struct uvcg_control_header_grp {
	struct config_group	group;
} uvcg_control_header_grp;

static struct configfs_group_operations uvcg_control_header_grp_ops = {
	.make_item		= uvcg_control_header_make,
	.drop_item		= uvcg_control_header_drop,
};

static struct config_item_type uvcg_control_header_grp_type = {
	.ct_group_ops	= &uvcg_control_header_grp_ops,
	.ct_owner	= THIS_MODULE,
};

/* control/processing/default */
static struct uvcg_default_processing {
	struct config_group	group;
} uvcg_default_processing;

static inline struct uvcg_default_processing
*to_uvcg_default_processing(struct config_item *item)
{
	return container_of(to_config_group(item),
			    struct uvcg_default_processing, group);
}

CONFIGFS_ATTR_STRUCT(uvcg_default_processing);
CONFIGFS_ATTR_OPS_RO(uvcg_default_processing);

static struct configfs_item_operations uvcg_default_processing_item_ops = {
	.show_attribute		= uvcg_default_processing_attr_show,
};

#define UVCG_DEFAULT_PROCESSING_ATTR(cname, aname, conv)		\
static ssize_t uvcg_default_processing_##cname##_show(			\
	struct uvcg_default_processing *dp, char *page)			\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &dp->group.cg_subsys->su_mutex;	\
	struct uvc_processing_unit_descriptor *pd;			\
	int result;							\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = dp->group.cg_item.ci_parent->ci_parent->ci_parent;	\
	opts = to_f_uvc_opts(opts_item);				\
	pd = &opts->uvc_processing;					\
									\
	mutex_lock(&opts->lock);					\
	result = sprintf(page, "%d\n", conv(pd->aname));		\
	mutex_unlock(&opts->lock);					\
									\
	mutex_unlock(su_mutex);						\
	return result;							\
}									\
									\
static struct uvcg_default_processing_attribute				\
	uvcg_default_processing_##cname =				\
	__CONFIGFS_ATTR_RO(aname, uvcg_default_processing_##cname##_show)

#define identity_conv(x) (x)

UVCG_DEFAULT_PROCESSING_ATTR(b_unit_id, bUnitID, identity_conv);
UVCG_DEFAULT_PROCESSING_ATTR(b_source_id, bSourceID, identity_conv);
UVCG_DEFAULT_PROCESSING_ATTR(w_max_multiplier, wMaxMultiplier, le16_to_cpu);
UVCG_DEFAULT_PROCESSING_ATTR(i_processing, iProcessing, identity_conv);

#undef identity_conv

#undef UVCG_DEFAULT_PROCESSING_ATTR

static ssize_t uvcg_default_processing_bm_controls_show(
	struct uvcg_default_processing *dp, char *page)
{
	struct f_uvc_opts *opts;
	struct config_item *opts_item;
	struct mutex *su_mutex = &dp->group.cg_subsys->su_mutex;
	struct uvc_processing_unit_descriptor *pd;
	int result, i;
	char *pg = page;

	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	opts_item = dp->group.cg_item.ci_parent->ci_parent->ci_parent;
	opts = to_f_uvc_opts(opts_item);
	pd = &opts->uvc_processing;

	mutex_lock(&opts->lock);
	for (result = 0, i = 0; i < pd->bControlSize; ++i) {
		result += sprintf(pg, "%d\n", pd->bmControls[i]);
		pg = page + result;
	}
	mutex_unlock(&opts->lock);

	mutex_unlock(su_mutex);

	return result;
}

static struct uvcg_default_processing_attribute
	uvcg_default_processing_bm_controls =
	__CONFIGFS_ATTR_RO(bmControls,
		uvcg_default_processing_bm_controls_show);

static struct configfs_attribute *uvcg_default_processing_attrs[] = {
	&uvcg_default_processing_b_unit_id.attr,
	&uvcg_default_processing_b_source_id.attr,
	&uvcg_default_processing_w_max_multiplier.attr,
	&uvcg_default_processing_bm_controls.attr,
	&uvcg_default_processing_i_processing.attr,
	NULL,
};

static struct config_item_type uvcg_default_processing_type = {
	.ct_item_ops	= &uvcg_default_processing_item_ops,
	.ct_attrs	= uvcg_default_processing_attrs,
	.ct_owner	= THIS_MODULE,
};

/* struct uvcg_processing {}; */

static struct config_group *uvcg_processing_default_groups[] = {
	&uvcg_default_processing.group,
	NULL,
};

/* control/processing */
static struct uvcg_processing_grp {
	struct config_group	group;
} uvcg_processing_grp;

static struct config_item_type uvcg_processing_grp_type = {
	.ct_owner = THIS_MODULE,
};

/* control/terminal/camera/default */
static struct uvcg_default_camera {
	struct config_group	group;
} uvcg_default_camera;

static inline struct uvcg_default_camera
*to_uvcg_default_camera(struct config_item *item)
{
	return container_of(to_config_group(item),
			    struct uvcg_default_camera, group);
}

CONFIGFS_ATTR_STRUCT(uvcg_default_camera);
CONFIGFS_ATTR_OPS_RO(uvcg_default_camera);

static struct configfs_item_operations uvcg_default_camera_item_ops = {
	.show_attribute		= uvcg_default_camera_attr_show,
};

#define UVCG_DEFAULT_CAMERA_ATTR(cname, aname, conv)			\
static ssize_t uvcg_default_camera_##cname##_show(			\
	struct uvcg_default_camera *dc, char *page)			\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex;	\
	struct uvc_camera_terminal_descriptor *cd;			\
	int result;							\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent->	\
			ci_parent;					\
	opts = to_f_uvc_opts(opts_item);				\
	cd = &opts->uvc_camera_terminal;				\
									\
	mutex_lock(&opts->lock);					\
	result = sprintf(page, "%d\n", conv(cd->aname));		\
	mutex_unlock(&opts->lock);					\
									\
	mutex_unlock(su_mutex);						\
									\
	return result;							\
}									\
									\
static struct uvcg_default_camera_attribute				\
	uvcg_default_camera_##cname =					\
	__CONFIGFS_ATTR_RO(aname, uvcg_default_camera_##cname##_show)

#define identity_conv(x) (x)

UVCG_DEFAULT_CAMERA_ATTR(b_terminal_id, bTerminalID, identity_conv);
UVCG_DEFAULT_CAMERA_ATTR(w_terminal_type, wTerminalType, le16_to_cpu);
UVCG_DEFAULT_CAMERA_ATTR(b_assoc_terminal, bAssocTerminal, identity_conv);
UVCG_DEFAULT_CAMERA_ATTR(i_terminal, iTerminal, identity_conv);
UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_min, wObjectiveFocalLengthMin,
			 le16_to_cpu);
UVCG_DEFAULT_CAMERA_ATTR(w_objective_focal_length_max, wObjectiveFocalLengthMax,
			 le16_to_cpu);
UVCG_DEFAULT_CAMERA_ATTR(w_ocular_focal_length, wOcularFocalLength,
			 le16_to_cpu);

#undef identity_conv

#undef UVCG_DEFAULT_CAMERA_ATTR

static ssize_t uvcg_default_camera_bm_controls_show(
	struct uvcg_default_camera *dc, char *page)
{
	struct f_uvc_opts *opts;
	struct config_item *opts_item;
	struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex;
	struct uvc_camera_terminal_descriptor *cd;
	int result, i;
	char *pg = page;

	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent->
			ci_parent;
	opts = to_f_uvc_opts(opts_item);
	cd = &opts->uvc_camera_terminal;

	mutex_lock(&opts->lock);
	for (result = 0, i = 0; i < cd->bControlSize; ++i) {
		result += sprintf(pg, "%d\n", cd->bmControls[i]);
		pg = page + result;
	}
	mutex_unlock(&opts->lock);

	mutex_unlock(su_mutex);
	return result;
}

static struct uvcg_default_camera_attribute
	uvcg_default_camera_bm_controls =
	__CONFIGFS_ATTR_RO(bmControls, uvcg_default_camera_bm_controls_show);

static struct configfs_attribute *uvcg_default_camera_attrs[] = {
	&uvcg_default_camera_b_terminal_id.attr,
	&uvcg_default_camera_w_terminal_type.attr,
	&uvcg_default_camera_b_assoc_terminal.attr,
	&uvcg_default_camera_i_terminal.attr,
	&uvcg_default_camera_w_objective_focal_length_min.attr,
	&uvcg_default_camera_w_objective_focal_length_max.attr,
	&uvcg_default_camera_w_ocular_focal_length.attr,
	&uvcg_default_camera_bm_controls.attr,
	NULL,
};

static struct config_item_type uvcg_default_camera_type = {
	.ct_item_ops	= &uvcg_default_camera_item_ops,
	.ct_attrs	= uvcg_default_camera_attrs,
	.ct_owner	= THIS_MODULE,
};

/* struct uvcg_camera {}; */

static struct config_group *uvcg_camera_default_groups[] = {
	&uvcg_default_camera.group,
	NULL,
};

/* control/terminal/camera */
static struct uvcg_camera_grp {
	struct config_group	group;
} uvcg_camera_grp;

static struct config_item_type uvcg_camera_grp_type = {
	.ct_owner = THIS_MODULE,
};

/* control/terminal/output/default */
static struct uvcg_default_output {
	struct config_group	group;
} uvcg_default_output;

static inline struct uvcg_default_output
*to_uvcg_default_output(struct config_item *item)
{
	return container_of(to_config_group(item),
			    struct uvcg_default_output, group);
}

CONFIGFS_ATTR_STRUCT(uvcg_default_output);
CONFIGFS_ATTR_OPS_RO(uvcg_default_output);

static struct configfs_item_operations uvcg_default_output_item_ops = {
	.show_attribute		= uvcg_default_output_attr_show,
};

#define UVCG_DEFAULT_OUTPUT_ATTR(cname, aname, conv)			\
static ssize_t uvcg_default_output_##cname##_show(			\
	struct uvcg_default_output *dout, char *page)			\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &dout->group.cg_subsys->su_mutex;	\
	struct uvc_output_terminal_descriptor *cd;			\
	int result;							\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = dout->group.cg_item.ci_parent->ci_parent->		\
			ci_parent->ci_parent;				\
	opts = to_f_uvc_opts(opts_item);				\
	cd = &opts->uvc_output_terminal;				\
									\
	mutex_lock(&opts->lock);					\
	result = sprintf(page, "%d\n", conv(cd->aname));		\
	mutex_unlock(&opts->lock);					\
									\
	mutex_unlock(su_mutex);						\
									\
	return result;							\
}									\
									\
static struct uvcg_default_output_attribute				\
	uvcg_default_output_##cname =					\
	__CONFIGFS_ATTR_RO(aname, uvcg_default_output_##cname##_show)

#define identity_conv(x) (x)

UVCG_DEFAULT_OUTPUT_ATTR(b_terminal_id, bTerminalID, identity_conv);
UVCG_DEFAULT_OUTPUT_ATTR(w_terminal_type, wTerminalType, le16_to_cpu);
UVCG_DEFAULT_OUTPUT_ATTR(b_assoc_terminal, bAssocTerminal, identity_conv);
UVCG_DEFAULT_OUTPUT_ATTR(b_source_id, bSourceID, identity_conv);
UVCG_DEFAULT_OUTPUT_ATTR(i_terminal, iTerminal, identity_conv);

#undef identity_conv

#undef UVCG_DEFAULT_OUTPUT_ATTR

static struct configfs_attribute *uvcg_default_output_attrs[] = {
	&uvcg_default_output_b_terminal_id.attr,
	&uvcg_default_output_w_terminal_type.attr,
	&uvcg_default_output_b_assoc_terminal.attr,
	&uvcg_default_output_b_source_id.attr,
	&uvcg_default_output_i_terminal.attr,
	NULL,
};

static struct config_item_type uvcg_default_output_type = {
	.ct_item_ops	= &uvcg_default_output_item_ops,
	.ct_attrs	= uvcg_default_output_attrs,
	.ct_owner	= THIS_MODULE,
};

/* struct uvcg_output {}; */

static struct config_group *uvcg_output_default_groups[] = {
	&uvcg_default_output.group,
	NULL,
};

/* control/terminal/output */
static struct uvcg_output_grp {
	struct config_group	group;
} uvcg_output_grp;

static struct config_item_type uvcg_output_grp_type = {
	.ct_owner = THIS_MODULE,
};

static struct config_group *uvcg_terminal_default_groups[] = {
	&uvcg_camera_grp.group,
	&uvcg_output_grp.group,
	NULL,
};

/* control/terminal */
static struct uvcg_terminal_grp {
	struct config_group	group;
} uvcg_terminal_grp;

static struct config_item_type uvcg_terminal_grp_type = {
	.ct_owner = THIS_MODULE,
};

/* control/class/{fs} */
static struct uvcg_control_class {
	struct config_group	group;
} uvcg_control_class_fs, uvcg_control_class_ss;


static inline struct uvc_descriptor_header
**uvcg_get_ctl_class_arr(struct config_item *i, struct f_uvc_opts *o)
{
	struct uvcg_control_class *cl = container_of(to_config_group(i),
		struct uvcg_control_class, group);

	if (cl == &uvcg_control_class_fs)
		return o->uvc_fs_control_cls;

	if (cl == &uvcg_control_class_ss)
		return o->uvc_ss_control_cls;

	return NULL;
}

static int uvcg_control_class_allow_link(struct config_item *src,
					 struct config_item *target)
{
	struct config_item *control, *header;
	struct f_uvc_opts *opts;
	struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
	struct uvc_descriptor_header **class_array;
	struct uvcg_control_header *target_hdr;
	int ret = -EINVAL;

	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	control = src->ci_parent->ci_parent;
	header = config_group_find_item(to_config_group(control), "header");
	if (!header || target->ci_parent != header)
		goto out;

	opts = to_f_uvc_opts(control->ci_parent);

	mutex_lock(&opts->lock);

	class_array = uvcg_get_ctl_class_arr(src, opts);
	if (!class_array)
		goto unlock;
	if (opts->refcnt || class_array[0]) {
		ret = -EBUSY;
		goto unlock;
	}

	target_hdr = to_uvcg_control_header(target);
	++target_hdr->linked;
	class_array[0] = (struct uvc_descriptor_header *)&target_hdr->desc;
	ret = 0;

unlock:
	mutex_unlock(&opts->lock);
out:
	mutex_unlock(su_mutex);
	return ret;
}

static int uvcg_control_class_drop_link(struct config_item *src,
					struct config_item *target)
{
	struct config_item *control, *header;
	struct f_uvc_opts *opts;
	struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
	struct uvc_descriptor_header **class_array;
	struct uvcg_control_header *target_hdr;
	int ret = -EINVAL;

	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	control = src->ci_parent->ci_parent;
	header = config_group_find_item(to_config_group(control), "header");
	if (!header || target->ci_parent != header)
		goto out;

	opts = to_f_uvc_opts(control->ci_parent);

	mutex_lock(&opts->lock);

	class_array = uvcg_get_ctl_class_arr(src, opts);
	if (!class_array)
		goto unlock;
	if (opts->refcnt) {
		ret = -EBUSY;
		goto unlock;
	}

	target_hdr = to_uvcg_control_header(target);
	--target_hdr->linked;
	class_array[0] = NULL;
	ret = 0;

unlock:
	mutex_unlock(&opts->lock);
out:
	mutex_unlock(su_mutex);
	return ret;
}

static struct configfs_item_operations uvcg_control_class_item_ops = {
	.allow_link	= uvcg_control_class_allow_link,
	.drop_link	= uvcg_control_class_drop_link,
};

static struct config_item_type uvcg_control_class_type = {
	.ct_item_ops	= &uvcg_control_class_item_ops,
	.ct_owner	= THIS_MODULE,
};

static struct config_group *uvcg_control_class_default_groups[] = {
	&uvcg_control_class_fs.group,
	&uvcg_control_class_ss.group,
	NULL,
};

/* control/class */
static struct uvcg_control_class_grp {
	struct config_group	group;
} uvcg_control_class_grp;

static struct config_item_type uvcg_control_class_grp_type = {
	.ct_owner = THIS_MODULE,
};

static struct config_group *uvcg_control_default_groups[] = {
	&uvcg_control_header_grp.group,
	&uvcg_processing_grp.group,
	&uvcg_terminal_grp.group,
	&uvcg_control_class_grp.group,
	NULL,
};

/* control */
static struct uvcg_control_grp {
	struct config_group	group;
} uvcg_control_grp;

static struct config_item_type uvcg_control_grp_type = {
	.ct_owner = THIS_MODULE,
};

/* streaming/uncompressed */
static struct uvcg_uncompressed_grp {
	struct config_group	group;
} uvcg_uncompressed_grp;

/* streaming/mjpeg */
static struct uvcg_mjpeg_grp {
	struct config_group	group;
} uvcg_mjpeg_grp;

static struct config_item *fmt_parent[] = {
	&uvcg_uncompressed_grp.group.cg_item,
	&uvcg_mjpeg_grp.group.cg_item,
};

enum uvcg_format_type {
	UVCG_UNCOMPRESSED = 0,
	UVCG_MJPEG,
};

struct uvcg_format {
	struct config_group	group;
	enum uvcg_format_type	type;
	unsigned		linked;
	unsigned		num_frames;
	__u8			bmaControls[UVCG_STREAMING_CONTROL_SIZE];
};

static struct uvcg_format *to_uvcg_format(struct config_item *item)
{
	return container_of(to_config_group(item), struct uvcg_format, group);
}

static ssize_t uvcg_format_bma_controls_show(struct uvcg_format *f, char *page)
{
	struct f_uvc_opts *opts;
	struct config_item *opts_item;
	struct mutex *su_mutex = &f->group.cg_subsys->su_mutex;
	int result, i;
	char *pg = page;

	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	opts_item = f->group.cg_item.ci_parent->ci_parent->ci_parent;
	opts = to_f_uvc_opts(opts_item);

	mutex_lock(&opts->lock);
	result = sprintf(pg, "0x");
	pg += result;
	for (i = 0; i < UVCG_STREAMING_CONTROL_SIZE; ++i) {
		result += sprintf(pg, "%x\n", f->bmaControls[i]);
		pg = page + result;
	}
	mutex_unlock(&opts->lock);

	mutex_unlock(su_mutex);
	return result;
}

static ssize_t uvcg_format_bma_controls_store(struct uvcg_format *ch,
					      const char *page, size_t len)
{
	struct f_uvc_opts *opts;
	struct config_item *opts_item;
	struct mutex *su_mutex = &ch->group.cg_subsys->su_mutex;
	int ret = -EINVAL;

	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	opts_item = ch->group.cg_item.ci_parent->ci_parent->ci_parent;
	opts = to_f_uvc_opts(opts_item);

	mutex_lock(&opts->lock);
	if (ch->linked || opts->refcnt) {
		ret = -EBUSY;
		goto end;
	}

	if (len < 4 || *page != '0' ||
	    (*(page + 1) != 'x' && *(page + 1) != 'X'))
		goto end;
	ret = hex2bin(ch->bmaControls, page + 2, 1);
	if (ret < 0)
		goto end;
	ret = len;
end:
	mutex_unlock(&opts->lock);
	mutex_unlock(su_mutex);
	return ret;
}

struct uvcg_format_ptr {
	struct uvcg_format	*fmt;
	struct list_head	entry;
};

/* streaming/header/<NAME> */
struct uvcg_streaming_header {
	struct config_item				item;
	struct uvc_input_header_descriptor		desc;
	unsigned					linked;
	struct list_head				formats;
	unsigned					num_fmt;
};

static struct uvcg_streaming_header *to_uvcg_streaming_header(struct config_item *item)
{
	return container_of(item, struct uvcg_streaming_header, item);
}

CONFIGFS_ATTR_STRUCT(uvcg_streaming_header);
CONFIGFS_ATTR_OPS(uvcg_streaming_header);

static int uvcg_streaming_header_allow_link(struct config_item *src,
					    struct config_item *target)
{
	struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
	struct config_item *opts_item;
	struct f_uvc_opts *opts;
	struct uvcg_streaming_header *src_hdr;
	struct uvcg_format *target_fmt = NULL;
	struct uvcg_format_ptr *format_ptr;
	int i, ret = -EINVAL;

	src_hdr = to_uvcg_streaming_header(src);
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	opts_item = src->ci_parent->ci_parent->ci_parent;
	opts = to_f_uvc_opts(opts_item);

	mutex_lock(&opts->lock);

	if (src_hdr->linked) {
		ret = -EBUSY;
		goto out;
	}

	for (i = 0; i < ARRAY_SIZE(fmt_parent); ++i)
		if (target->ci_parent == fmt_parent[i])
			break;
	if (i == ARRAY_SIZE(fmt_parent))
		goto out;

	target_fmt = container_of(to_config_group(target), struct uvcg_format,
				  group);
	if (!target_fmt)
		goto out;

	format_ptr = kzalloc(sizeof(*format_ptr), GFP_KERNEL);
	if (!format_ptr) {
		ret = -ENOMEM;
		goto out;
	}
	ret = 0;
	format_ptr->fmt = target_fmt;
	list_add_tail(&format_ptr->entry, &src_hdr->formats);
	++src_hdr->num_fmt;

out:
	mutex_unlock(&opts->lock);
	mutex_unlock(su_mutex);
	return ret;
}

static int uvcg_streaming_header_drop_link(struct config_item *src,
					   struct config_item *target)
{
	struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
	struct config_item *opts_item;
	struct f_uvc_opts *opts;
	struct uvcg_streaming_header *src_hdr;
	struct uvcg_format *target_fmt = NULL;
	struct uvcg_format_ptr *format_ptr, *tmp;
	int ret = -EINVAL;

	src_hdr = to_uvcg_streaming_header(src);
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	opts_item = src->ci_parent->ci_parent->ci_parent;
	opts = to_f_uvc_opts(opts_item);

	mutex_lock(&opts->lock);
	target_fmt = container_of(to_config_group(target), struct uvcg_format,
				  group);
	if (!target_fmt)
		goto out;

	list_for_each_entry_safe(format_ptr, tmp, &src_hdr->formats, entry)
		if (format_ptr->fmt == target_fmt) {
			list_del(&format_ptr->entry);
			kfree(format_ptr);
			--src_hdr->num_fmt;
			break;
		}

out:
	mutex_unlock(&opts->lock);
	mutex_unlock(su_mutex);
	return ret;

}

static struct configfs_item_operations uvcg_streaming_header_item_ops = {
	.show_attribute		= uvcg_streaming_header_attr_show,
	.store_attribute	= uvcg_streaming_header_attr_store,
	.allow_link		= uvcg_streaming_header_allow_link,
	.drop_link		= uvcg_streaming_header_drop_link,
};

#define UVCG_STREAMING_HEADER_ATTR(cname, aname, conv)			\
static ssize_t uvcg_streaming_header_##cname##_show(			\
	struct uvcg_streaming_header *sh, char *page)			\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &sh->item.ci_group->cg_subsys->su_mutex;\
	int result;							\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = sh->item.ci_parent->ci_parent->ci_parent;		\
	opts = to_f_uvc_opts(opts_item);				\
									\
	mutex_lock(&opts->lock);					\
	result = sprintf(page, "%d\n", conv(sh->desc.aname));		\
	mutex_unlock(&opts->lock);					\
									\
	mutex_unlock(su_mutex);						\
	return result;							\
}									\
									\
static struct uvcg_streaming_header_attribute				\
	uvcg_streaming_header_##cname =					\
	__CONFIGFS_ATTR_RO(aname, uvcg_streaming_header_##cname##_show)

#define identity_conv(x) (x)

UVCG_STREAMING_HEADER_ATTR(bm_info, bmInfo, identity_conv);
UVCG_STREAMING_HEADER_ATTR(b_terminal_link, bTerminalLink, identity_conv);
UVCG_STREAMING_HEADER_ATTR(b_still_capture_method, bStillCaptureMethod,
			   identity_conv);
UVCG_STREAMING_HEADER_ATTR(b_trigger_support, bTriggerSupport, identity_conv);
UVCG_STREAMING_HEADER_ATTR(b_trigger_usage, bTriggerUsage, identity_conv);

#undef identity_conv

#undef UVCG_STREAMING_HEADER_ATTR

static struct configfs_attribute *uvcg_streaming_header_attrs[] = {
	&uvcg_streaming_header_bm_info.attr,
	&uvcg_streaming_header_b_terminal_link.attr,
	&uvcg_streaming_header_b_still_capture_method.attr,
	&uvcg_streaming_header_b_trigger_support.attr,
	&uvcg_streaming_header_b_trigger_usage.attr,
	NULL,
};

static struct config_item_type uvcg_streaming_header_type = {
	.ct_item_ops	= &uvcg_streaming_header_item_ops,
	.ct_attrs	= uvcg_streaming_header_attrs,
	.ct_owner	= THIS_MODULE,
};

static struct config_item
*uvcg_streaming_header_make(struct config_group *group, const char *name)
{
	struct uvcg_streaming_header *h;

	h = kzalloc(sizeof(*h), GFP_KERNEL);
	if (!h)
		return ERR_PTR(-ENOMEM);

	INIT_LIST_HEAD(&h->formats);
	h->desc.bDescriptorType		= USB_DT_CS_INTERFACE;
	h->desc.bDescriptorSubType	= UVC_VS_INPUT_HEADER;
	h->desc.bTerminalLink		= 3;
	h->desc.bControlSize		= UVCG_STREAMING_CONTROL_SIZE;

	config_item_init_type_name(&h->item, name, &uvcg_streaming_header_type);

	return &h->item;
}

static void uvcg_streaming_header_drop(struct config_group *group,
			      struct config_item *item)
{
	struct uvcg_streaming_header *h = to_uvcg_streaming_header(item);

	kfree(h);
}

/* streaming/header */
static struct uvcg_streaming_header_grp {
	struct config_group	group;
} uvcg_streaming_header_grp;

static struct configfs_group_operations uvcg_streaming_header_grp_ops = {
	.make_item		= uvcg_streaming_header_make,
	.drop_item		= uvcg_streaming_header_drop,
};

static struct config_item_type uvcg_streaming_header_grp_type = {
	.ct_group_ops	= &uvcg_streaming_header_grp_ops,
	.ct_owner	= THIS_MODULE,
};

/* streaming/<mode>/<format>/<NAME> */
struct uvcg_frame {
	struct {
		u8	b_length;
		u8	b_descriptor_type;
		u8	b_descriptor_subtype;
		u8	b_frame_index;
		u8	bm_capabilities;
		u16	w_width;
		u16	w_height;
		u32	dw_min_bit_rate;
		u32	dw_max_bit_rate;
		u32	dw_max_video_frame_buffer_size;
		u32	dw_default_frame_interval;
		u8	b_frame_interval_type;
	} __attribute__((packed)) frame;
	u32 *dw_frame_interval;
	enum uvcg_format_type	fmt_type;
	struct config_item	item;
};

static struct uvcg_frame *to_uvcg_frame(struct config_item *item)
{
	return container_of(item, struct uvcg_frame, item);
}

CONFIGFS_ATTR_STRUCT(uvcg_frame);
CONFIGFS_ATTR_OPS(uvcg_frame);

static struct configfs_item_operations uvcg_frame_item_ops = {
	.show_attribute		= uvcg_frame_attr_show,
	.store_attribute	= uvcg_frame_attr_store,
};

#define UVCG_FRAME_ATTR(cname, aname, to_cpu_endian, to_little_endian, bits) \
static ssize_t uvcg_frame_##cname##_show(struct uvcg_frame *f, char *page)\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\
	int result;							\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = f->item.ci_parent->ci_parent->ci_parent->ci_parent;	\
	opts = to_f_uvc_opts(opts_item);				\
									\
	mutex_lock(&opts->lock);					\
	result = sprintf(page, "%d\n", to_cpu_endian(f->frame.cname));	\
	mutex_unlock(&opts->lock);					\
									\
	mutex_unlock(su_mutex);						\
	return result;							\
}									\
									\
static ssize_t  uvcg_frame_##cname##_store(struct uvcg_frame *f,	\
					   const char *page, size_t len)\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct uvcg_format *fmt;					\
	struct mutex *su_mutex = &f->item.ci_group->cg_subsys->su_mutex;\
	int ret;							\
	u##bits num;							\
									\
	ret = kstrtou##bits(page, 0, &num);				\
	if (ret)							\
		return ret;						\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = f->item.ci_parent->ci_parent->ci_parent->ci_parent;	\
	opts = to_f_uvc_opts(opts_item);				\
	fmt = to_uvcg_format(f->item.ci_parent);			\
									\
	mutex_lock(&opts->lock);					\
	if (fmt->linked || opts->refcnt) {				\
		ret = -EBUSY;						\
		goto end;						\
	}								\
									\
	f->frame.cname = to_little_endian(num);				\
	ret = len;							\
end:									\
	mutex_unlock(&opts->lock);					\
	mutex_unlock(su_mutex);						\
	return ret;							\
}									\
									\
static struct uvcg_frame_attribute					\
	uvcg_frame_##cname =						\
	__CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR,			\
			uvcg_frame_##cname##_show,			\
			uvcg_frame_##cname##_store)

#define noop_conversion(x) (x)

UVCG_FRAME_ATTR(bm_capabilities, bmCapabilities, noop_conversion,
		noop_conversion, 8);
UVCG_FRAME_ATTR(w_width, wWidth, le16_to_cpu, cpu_to_le16, 16);
UVCG_FRAME_ATTR(w_height, wHeight, le16_to_cpu, cpu_to_le16, 16);
UVCG_FRAME_ATTR(dw_min_bit_rate, dwMinBitRate, le32_to_cpu, cpu_to_le32, 32);
UVCG_FRAME_ATTR(dw_max_bit_rate, dwMaxBitRate, le32_to_cpu, cpu_to_le32, 32);
UVCG_FRAME_ATTR(dw_max_video_frame_buffer_size, dwMaxVideoFrameBufferSize,
		le32_to_cpu, cpu_to_le32, 32);
UVCG_FRAME_ATTR(dw_default_frame_interval, dwDefaultFrameInterval,
		le32_to_cpu, cpu_to_le32, 32);

#undef noop_conversion

#undef UVCG_FRAME_ATTR

static ssize_t uvcg_frame_dw_frame_interval_show(struct uvcg_frame *frm,
						 char *page)
{
	struct f_uvc_opts *opts;
	struct config_item *opts_item;
	struct mutex *su_mutex = &frm->item.ci_group->cg_subsys->su_mutex;
	int result, i;
	char *pg = page;

	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	opts_item = frm->item.ci_parent->ci_parent->ci_parent->ci_parent;
	opts = to_f_uvc_opts(opts_item);

	mutex_lock(&opts->lock);
	for (result = 0, i = 0; i < frm->frame.b_frame_interval_type; ++i) {
		result += sprintf(pg, "%d\n",
				  le32_to_cpu(frm->dw_frame_interval[i]));
		pg = page + result;
	}
	mutex_unlock(&opts->lock);

	mutex_unlock(su_mutex);
	return result;
}

static inline int __uvcg_count_frm_intrv(char *buf, void *priv)
{
	++*((int *)priv);
	return 0;
}

static inline int __uvcg_fill_frm_intrv(char *buf, void *priv)
{
	u32 num, **interv;
	int ret;

	ret = kstrtou32(buf, 0, &num);
	if (ret)
		return ret;

	interv = priv;
	**interv = cpu_to_le32(num);
	++*interv;

	return 0;
}

static int __uvcg_iter_frm_intrv(const char *page, size_t len,
				 int (*fun)(char *, void *), void *priv)
{
	/* sign, base 2 representation, newline, terminator */
	char buf[1 + sizeof(u32) * 8 + 1 + 1];
	const char *pg = page;
	int i, ret;

	if (!fun)
		return -EINVAL;

	while (pg - page < len) {
		i = 0;
		while (i < sizeof(buf) && (pg - page < len) &&
				*pg != '\0' && *pg != '\n')
			buf[i++] = *pg++;
		if (i == sizeof(buf))
			return -EINVAL;
		while ((pg - page < len) && (*pg == '\0' || *pg == '\n'))
			++pg;
		buf[i] = '\0';
		ret = fun(buf, priv);
		if (ret)
			return ret;
	}

	return 0;
}

static ssize_t uvcg_frame_dw_frame_interval_store(struct uvcg_frame *ch,
						  const char *page, size_t len)
{
	struct f_uvc_opts *opts;
	struct config_item *opts_item;
	struct uvcg_format *fmt;
	struct mutex *su_mutex = &ch->item.ci_group->cg_subsys->su_mutex;
	int ret = 0, n = 0;
	u32 *frm_intrv, *tmp;

	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	opts_item = ch->item.ci_parent->ci_parent->ci_parent->ci_parent;
	opts = to_f_uvc_opts(opts_item);
	fmt = to_uvcg_format(ch->item.ci_parent);

	mutex_lock(&opts->lock);
	if (fmt->linked || opts->refcnt) {
		ret = -EBUSY;
		goto end;
	}

	ret = __uvcg_iter_frm_intrv(page, len, __uvcg_count_frm_intrv, &n);
	if (ret)
		goto end;

	tmp = frm_intrv = kcalloc(n, sizeof(u32), GFP_KERNEL);
	if (!frm_intrv) {
		ret = -ENOMEM;
		goto end;
	}

	ret = __uvcg_iter_frm_intrv(page, len, __uvcg_fill_frm_intrv, &tmp);
	if (ret) {
		kfree(frm_intrv);
		goto end;
	}

	kfree(ch->dw_frame_interval);
	ch->dw_frame_interval = frm_intrv;
	ch->frame.b_frame_interval_type = n;
	ret = len;

end:
	mutex_unlock(&opts->lock);
	mutex_unlock(su_mutex);
	return ret;
}

static struct uvcg_frame_attribute
	uvcg_frame_dw_frame_interval =
	__CONFIGFS_ATTR(dwFrameInterval, S_IRUGO | S_IWUSR,
			uvcg_frame_dw_frame_interval_show,
			uvcg_frame_dw_frame_interval_store);

static struct configfs_attribute *uvcg_frame_attrs[] = {
	&uvcg_frame_bm_capabilities.attr,
	&uvcg_frame_w_width.attr,
	&uvcg_frame_w_height.attr,
	&uvcg_frame_dw_min_bit_rate.attr,
	&uvcg_frame_dw_max_bit_rate.attr,
	&uvcg_frame_dw_max_video_frame_buffer_size.attr,
	&uvcg_frame_dw_default_frame_interval.attr,
	&uvcg_frame_dw_frame_interval.attr,
	NULL,
};

static struct config_item_type uvcg_frame_type = {
	.ct_item_ops	= &uvcg_frame_item_ops,
	.ct_attrs	= uvcg_frame_attrs,
	.ct_owner	= THIS_MODULE,
};

static struct config_item *uvcg_frame_make(struct config_group *group,
					   const char *name)
{
	struct uvcg_frame *h;
	struct uvcg_format *fmt;
	struct f_uvc_opts *opts;
	struct config_item *opts_item;

	h = kzalloc(sizeof(*h), GFP_KERNEL);
	if (!h)
		return ERR_PTR(-ENOMEM);

	h->frame.b_descriptor_type		= USB_DT_CS_INTERFACE;
	h->frame.b_frame_index			= 1;
	h->frame.w_width			= cpu_to_le16(640);
	h->frame.w_height			= cpu_to_le16(360);
	h->frame.dw_min_bit_rate		= cpu_to_le32(18432000);
	h->frame.dw_max_bit_rate		= cpu_to_le32(55296000);
	h->frame.dw_max_video_frame_buffer_size	= cpu_to_le32(460800);
	h->frame.dw_default_frame_interval	= cpu_to_le32(666666);

	opts_item = group->cg_item.ci_parent->ci_parent->ci_parent;
	opts = to_f_uvc_opts(opts_item);

	mutex_lock(&opts->lock);
	fmt = to_uvcg_format(&group->cg_item);
	if (fmt->type == UVCG_UNCOMPRESSED) {
		h->frame.b_descriptor_subtype = UVC_VS_FRAME_UNCOMPRESSED;
		h->fmt_type = UVCG_UNCOMPRESSED;
	} else if (fmt->type == UVCG_MJPEG) {
		h->frame.b_descriptor_subtype = UVC_VS_FRAME_MJPEG;
		h->fmt_type = UVCG_MJPEG;
	} else {
		mutex_unlock(&opts->lock);
		kfree(h);
		return ERR_PTR(-EINVAL);
	}
	++fmt->num_frames;
	mutex_unlock(&opts->lock);

	config_item_init_type_name(&h->item, name, &uvcg_frame_type);

	return &h->item;
}

static void uvcg_frame_drop(struct config_group *group, struct config_item *item)
{
	struct uvcg_frame *h = to_uvcg_frame(item);
	struct uvcg_format *fmt;
	struct f_uvc_opts *opts;
	struct config_item *opts_item;

	opts_item = group->cg_item.ci_parent->ci_parent->ci_parent;
	opts = to_f_uvc_opts(opts_item);

	mutex_lock(&opts->lock);
	fmt = to_uvcg_format(&group->cg_item);
	--fmt->num_frames;
	kfree(h);
	mutex_unlock(&opts->lock);
}

/* streaming/uncompressed/<NAME> */
struct uvcg_uncompressed {
	struct uvcg_format		fmt;
	struct uvc_format_uncompressed	desc;
};

static struct uvcg_uncompressed *to_uvcg_uncompressed(struct config_item *item)
{
	return container_of(
		container_of(to_config_group(item), struct uvcg_format, group),
		struct uvcg_uncompressed, fmt);
}

CONFIGFS_ATTR_STRUCT(uvcg_uncompressed);
CONFIGFS_ATTR_OPS(uvcg_uncompressed);

static struct configfs_item_operations uvcg_uncompressed_item_ops = {
	.show_attribute		= uvcg_uncompressed_attr_show,
	.store_attribute	= uvcg_uncompressed_attr_store,
};

static struct configfs_group_operations uvcg_uncompressed_group_ops = {
	.make_item		= uvcg_frame_make,
	.drop_item		= uvcg_frame_drop,
};

static ssize_t uvcg_uncompressed_guid_format_show(struct uvcg_uncompressed *ch,
							char *page)
{
	struct f_uvc_opts *opts;
	struct config_item *opts_item;
	struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex;

	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;
	opts = to_f_uvc_opts(opts_item);

	mutex_lock(&opts->lock);
	memcpy(page, ch->desc.guidFormat, sizeof(ch->desc.guidFormat));
	mutex_unlock(&opts->lock);

	mutex_unlock(su_mutex);

	return sizeof(ch->desc.guidFormat);
}

static ssize_t uvcg_uncompressed_guid_format_store(struct uvcg_uncompressed *ch,
						   const char *page, size_t len)
{
	struct f_uvc_opts *opts;
	struct config_item *opts_item;
	struct mutex *su_mutex = &ch->fmt.group.cg_subsys->su_mutex;
	int ret;

	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	opts_item = ch->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;
	opts = to_f_uvc_opts(opts_item);

	mutex_lock(&opts->lock);
	if (ch->fmt.linked || opts->refcnt) {
		ret = -EBUSY;
		goto end;
	}

	memcpy(ch->desc.guidFormat, page,
	       min(sizeof(ch->desc.guidFormat), len));
	ret = sizeof(ch->desc.guidFormat);

end:
	mutex_unlock(&opts->lock);
	mutex_unlock(su_mutex);
	return ret;
}

static struct uvcg_uncompressed_attribute uvcg_uncompressed_guid_format =
	__CONFIGFS_ATTR(guidFormat, S_IRUGO | S_IWUSR,
			uvcg_uncompressed_guid_format_show,
			uvcg_uncompressed_guid_format_store);


#define UVCG_UNCOMPRESSED_ATTR_RO(cname, aname, conv)			\
static ssize_t uvcg_uncompressed_##cname##_show(			\
	struct uvcg_uncompressed *u, char *page)			\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex;	\
	int result;							\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
	opts = to_f_uvc_opts(opts_item);				\
									\
	mutex_lock(&opts->lock);					\
	result = sprintf(page, "%d\n", conv(u->desc.aname));		\
	mutex_unlock(&opts->lock);					\
									\
	mutex_unlock(su_mutex);						\
	return result;							\
}									\
									\
static struct uvcg_uncompressed_attribute				\
	uvcg_uncompressed_##cname =					\
	__CONFIGFS_ATTR_RO(aname, uvcg_uncompressed_##cname##_show)

#define UVCG_UNCOMPRESSED_ATTR(cname, aname, conv)			\
static ssize_t uvcg_uncompressed_##cname##_show(			\
	struct uvcg_uncompressed *u, char *page)			\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex;	\
	int result;							\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
	opts = to_f_uvc_opts(opts_item);				\
									\
	mutex_lock(&opts->lock);					\
	result = sprintf(page, "%d\n", conv(u->desc.aname));		\
	mutex_unlock(&opts->lock);					\
									\
	mutex_unlock(su_mutex);						\
	return result;							\
}									\
									\
static ssize_t								\
uvcg_uncompressed_##cname##_store(struct uvcg_uncompressed *u,		\
				    const char *page, size_t len)	\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex;	\
	int ret;							\
	u8 num;								\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
	opts = to_f_uvc_opts(opts_item);				\
									\
	mutex_lock(&opts->lock);					\
	if (u->fmt.linked || opts->refcnt) {				\
		ret = -EBUSY;						\
		goto end;						\
	}								\
									\
	ret = kstrtou8(page, 0, &num);					\
	if (ret)							\
		goto end;						\
									\
	if (num > 255) {						\
		ret = -EINVAL;						\
		goto end;						\
	}								\
	u->desc.aname = num;						\
	ret = len;							\
end:									\
	mutex_unlock(&opts->lock);					\
	mutex_unlock(su_mutex);						\
	return ret;							\
}									\
									\
static struct uvcg_uncompressed_attribute				\
	uvcg_uncompressed_##cname =					\
	__CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR,			\
			uvcg_uncompressed_##cname##_show,		\
			uvcg_uncompressed_##cname##_store)

#define identity_conv(x) (x)

UVCG_UNCOMPRESSED_ATTR(b_bits_per_pixel, bBitsPerPixel, identity_conv);
UVCG_UNCOMPRESSED_ATTR(b_default_frame_index, bDefaultFrameIndex,
		       identity_conv);
UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, identity_conv);
UVCG_UNCOMPRESSED_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, identity_conv);
UVCG_UNCOMPRESSED_ATTR_RO(bm_interface_flags, bmInterfaceFlags, identity_conv);

#undef identity_conv

#undef UVCG_UNCOMPRESSED_ATTR
#undef UVCG_UNCOMPRESSED_ATTR_RO

static inline ssize_t
uvcg_uncompressed_bma_controls_show(struct uvcg_uncompressed *unc, char *page)
{
	return uvcg_format_bma_controls_show(&unc->fmt, page);
}

static inline ssize_t
uvcg_uncompressed_bma_controls_store(struct uvcg_uncompressed *ch,
				     const char *page, size_t len)
{
	return uvcg_format_bma_controls_store(&ch->fmt, page, len);
}

static struct uvcg_uncompressed_attribute uvcg_uncompressed_bma_controls =
	__CONFIGFS_ATTR(bmaControls, S_IRUGO | S_IWUSR,
			uvcg_uncompressed_bma_controls_show,
			uvcg_uncompressed_bma_controls_store);

static struct configfs_attribute *uvcg_uncompressed_attrs[] = {
	&uvcg_uncompressed_guid_format.attr,
	&uvcg_uncompressed_b_bits_per_pixel.attr,
	&uvcg_uncompressed_b_default_frame_index.attr,
	&uvcg_uncompressed_b_aspect_ratio_x.attr,
	&uvcg_uncompressed_b_aspect_ratio_y.attr,
	&uvcg_uncompressed_bm_interface_flags.attr,
	&uvcg_uncompressed_bma_controls.attr,
	NULL,
};

static struct config_item_type uvcg_uncompressed_type = {
	.ct_item_ops	= &uvcg_uncompressed_item_ops,
	.ct_group_ops	= &uvcg_uncompressed_group_ops,
	.ct_attrs	= uvcg_uncompressed_attrs,
	.ct_owner	= THIS_MODULE,
};

static struct config_group *uvcg_uncompressed_make(struct config_group *group,
						   const char *name)
{
	static char guid[] = {
		'Y',  'U',  'Y',  '2', 0x00, 0x00, 0x10, 0x00,
		 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
	};
	struct uvcg_uncompressed *h;

	h = kzalloc(sizeof(*h), GFP_KERNEL);
	if (!h)
		return ERR_PTR(-ENOMEM);

	h->desc.bLength			= UVC_DT_FORMAT_UNCOMPRESSED_SIZE;
	h->desc.bDescriptorType		= USB_DT_CS_INTERFACE;
	h->desc.bDescriptorSubType	= UVC_VS_FORMAT_UNCOMPRESSED;
	memcpy(h->desc.guidFormat, guid, sizeof(guid));
	h->desc.bBitsPerPixel		= 16;
	h->desc.bDefaultFrameIndex	= 1;
	h->desc.bAspectRatioX		= 0;
	h->desc.bAspectRatioY		= 0;
	h->desc.bmInterfaceFlags	= 0;
	h->desc.bCopyProtect		= 0;

	h->fmt.type = UVCG_UNCOMPRESSED;
	config_group_init_type_name(&h->fmt.group, name,
				    &uvcg_uncompressed_type);

	return &h->fmt.group;
}

static void uvcg_uncompressed_drop(struct config_group *group,
			    struct config_item *item)
{
	struct uvcg_uncompressed *h = to_uvcg_uncompressed(item);

	kfree(h);
}

static struct configfs_group_operations uvcg_uncompressed_grp_ops = {
	.make_group		= uvcg_uncompressed_make,
	.drop_item		= uvcg_uncompressed_drop,
};

static struct config_item_type uvcg_uncompressed_grp_type = {
	.ct_group_ops	= &uvcg_uncompressed_grp_ops,
	.ct_owner	= THIS_MODULE,
};

/* streaming/mjpeg/<NAME> */
struct uvcg_mjpeg {
	struct uvcg_format		fmt;
	struct uvc_format_mjpeg		desc;
};

static struct uvcg_mjpeg *to_uvcg_mjpeg(struct config_item *item)
{
	return container_of(
		container_of(to_config_group(item), struct uvcg_format, group),
		struct uvcg_mjpeg, fmt);
}

CONFIGFS_ATTR_STRUCT(uvcg_mjpeg);
CONFIGFS_ATTR_OPS(uvcg_mjpeg);

static struct configfs_item_operations uvcg_mjpeg_item_ops = {
	.show_attribute		= uvcg_mjpeg_attr_show,
	.store_attribute	= uvcg_mjpeg_attr_store,
};

static struct configfs_group_operations uvcg_mjpeg_group_ops = {
	.make_item		= uvcg_frame_make,
	.drop_item		= uvcg_frame_drop,
};

#define UVCG_MJPEG_ATTR_RO(cname, aname, conv)				\
static ssize_t uvcg_mjpeg_##cname##_show(struct uvcg_mjpeg *u, char *page)\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex;	\
	int result;							\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
	opts = to_f_uvc_opts(opts_item);				\
									\
	mutex_lock(&opts->lock);					\
	result = sprintf(page, "%d\n", conv(u->desc.aname));		\
	mutex_unlock(&opts->lock);					\
									\
	mutex_unlock(su_mutex);						\
	return result;							\
}									\
									\
static struct uvcg_mjpeg_attribute					\
	uvcg_mjpeg_##cname =						\
	__CONFIGFS_ATTR_RO(aname, uvcg_mjpeg_##cname##_show)

#define UVCG_MJPEG_ATTR(cname, aname, conv)				\
static ssize_t uvcg_mjpeg_##cname##_show(struct uvcg_mjpeg *u, char *page)\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex;	\
	int result;							\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
	opts = to_f_uvc_opts(opts_item);				\
									\
	mutex_lock(&opts->lock);					\
	result = sprintf(page, "%d\n", conv(u->desc.aname));		\
	mutex_unlock(&opts->lock);					\
									\
	mutex_unlock(su_mutex);						\
	return result;							\
}									\
									\
static ssize_t								\
uvcg_mjpeg_##cname##_store(struct uvcg_mjpeg *u,			\
			   const char *page, size_t len)		\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &u->fmt.group.cg_subsys->su_mutex;	\
	int ret;							\
	u8 num;								\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = u->fmt.group.cg_item.ci_parent->ci_parent->ci_parent;\
	opts = to_f_uvc_opts(opts_item);				\
									\
	mutex_lock(&opts->lock);					\
	if (u->fmt.linked || opts->refcnt) {				\
		ret = -EBUSY;						\
		goto end;						\
	}								\
									\
	ret = kstrtou8(page, 0, &num);					\
	if (ret)							\
		goto end;						\
									\
	if (num > 255) {						\
		ret = -EINVAL;						\
		goto end;						\
	}								\
	u->desc.aname = num;						\
	ret = len;							\
end:									\
	mutex_unlock(&opts->lock);					\
	mutex_unlock(su_mutex);						\
	return ret;							\
}									\
									\
static struct uvcg_mjpeg_attribute					\
	uvcg_mjpeg_##cname =						\
	__CONFIGFS_ATTR(aname, S_IRUGO | S_IWUSR,			\
			uvcg_mjpeg_##cname##_show,			\
			uvcg_mjpeg_##cname##_store)

#define identity_conv(x) (x)

UVCG_MJPEG_ATTR(b_default_frame_index, bDefaultFrameIndex,
		       identity_conv);
UVCG_MJPEG_ATTR_RO(bm_flags, bmFlags, identity_conv);
UVCG_MJPEG_ATTR_RO(b_aspect_ratio_x, bAspectRatioX, identity_conv);
UVCG_MJPEG_ATTR_RO(b_aspect_ratio_y, bAspectRatioY, identity_conv);
UVCG_MJPEG_ATTR_RO(bm_interface_flags, bmInterfaceFlags, identity_conv);

#undef identity_conv

#undef UVCG_MJPEG_ATTR
#undef UVCG_MJPEG_ATTR_RO

static inline ssize_t
uvcg_mjpeg_bma_controls_show(struct uvcg_mjpeg *unc, char *page)
{
	return uvcg_format_bma_controls_show(&unc->fmt, page);
}

static inline ssize_t
uvcg_mjpeg_bma_controls_store(struct uvcg_mjpeg *ch,
				     const char *page, size_t len)
{
	return uvcg_format_bma_controls_store(&ch->fmt, page, len);
}

static struct uvcg_mjpeg_attribute uvcg_mjpeg_bma_controls =
	__CONFIGFS_ATTR(bmaControls, S_IRUGO | S_IWUSR,
			uvcg_mjpeg_bma_controls_show,
			uvcg_mjpeg_bma_controls_store);

static struct configfs_attribute *uvcg_mjpeg_attrs[] = {
	&uvcg_mjpeg_b_default_frame_index.attr,
	&uvcg_mjpeg_bm_flags.attr,
	&uvcg_mjpeg_b_aspect_ratio_x.attr,
	&uvcg_mjpeg_b_aspect_ratio_y.attr,
	&uvcg_mjpeg_bm_interface_flags.attr,
	&uvcg_mjpeg_bma_controls.attr,
	NULL,
};

static struct config_item_type uvcg_mjpeg_type = {
	.ct_item_ops	= &uvcg_mjpeg_item_ops,
	.ct_group_ops	= &uvcg_mjpeg_group_ops,
	.ct_attrs	= uvcg_mjpeg_attrs,
	.ct_owner	= THIS_MODULE,
};

static struct config_group *uvcg_mjpeg_make(struct config_group *group,
						   const char *name)
{
	struct uvcg_mjpeg *h;

	h = kzalloc(sizeof(*h), GFP_KERNEL);
	if (!h)
		return ERR_PTR(-ENOMEM);

	h->desc.bLength			= UVC_DT_FORMAT_MJPEG_SIZE;
	h->desc.bDescriptorType		= USB_DT_CS_INTERFACE;
	h->desc.bDescriptorSubType	= UVC_VS_FORMAT_MJPEG;
	h->desc.bDefaultFrameIndex	= 1;
	h->desc.bAspectRatioX		= 0;
	h->desc.bAspectRatioY		= 0;
	h->desc.bmInterfaceFlags	= 0;
	h->desc.bCopyProtect		= 0;

	h->fmt.type = UVCG_MJPEG;
	config_group_init_type_name(&h->fmt.group, name,
				    &uvcg_mjpeg_type);

	return &h->fmt.group;
}

static void uvcg_mjpeg_drop(struct config_group *group,
			    struct config_item *item)
{
	struct uvcg_mjpeg *h = to_uvcg_mjpeg(item);

	kfree(h);
}

static struct configfs_group_operations uvcg_mjpeg_grp_ops = {
	.make_group		= uvcg_mjpeg_make,
	.drop_item		= uvcg_mjpeg_drop,
};

static struct config_item_type uvcg_mjpeg_grp_type = {
	.ct_group_ops	= &uvcg_mjpeg_grp_ops,
	.ct_owner	= THIS_MODULE,
};

/* streaming/color_matching/default */
static struct uvcg_default_color_matching {
	struct config_group	group;
} uvcg_default_color_matching;

static inline struct uvcg_default_color_matching
*to_uvcg_default_color_matching(struct config_item *item)
{
	return container_of(to_config_group(item),
			    struct uvcg_default_color_matching, group);
}

CONFIGFS_ATTR_STRUCT(uvcg_default_color_matching);
CONFIGFS_ATTR_OPS_RO(uvcg_default_color_matching);

static struct configfs_item_operations uvcg_default_color_matching_item_ops = {
	.show_attribute		= uvcg_default_color_matching_attr_show,
};

#define UVCG_DEFAULT_COLOR_MATCHING_ATTR(cname, aname, conv)		\
static ssize_t uvcg_default_color_matching_##cname##_show(		\
	struct uvcg_default_color_matching *dc, char *page)		\
{									\
	struct f_uvc_opts *opts;					\
	struct config_item *opts_item;					\
	struct mutex *su_mutex = &dc->group.cg_subsys->su_mutex;	\
	struct uvc_color_matching_descriptor *cd;			\
	int result;							\
									\
	mutex_lock(su_mutex); /* for navigating configfs hierarchy */	\
									\
	opts_item = dc->group.cg_item.ci_parent->ci_parent->ci_parent;	\
	opts = to_f_uvc_opts(opts_item);				\
	cd = &opts->uvc_color_matching;					\
									\
	mutex_lock(&opts->lock);					\
	result = sprintf(page, "%d\n", conv(cd->aname));		\
	mutex_unlock(&opts->lock);					\
									\
	mutex_unlock(su_mutex);						\
	return result;							\
}									\
									\
static struct uvcg_default_color_matching_attribute			\
	uvcg_default_color_matching_##cname =				\
	__CONFIGFS_ATTR_RO(aname, uvcg_default_color_matching_##cname##_show)

#define identity_conv(x) (x)

UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_color_primaries, bColorPrimaries,
				 identity_conv);
UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_transfer_characteristics,
				 bTransferCharacteristics, identity_conv);
UVCG_DEFAULT_COLOR_MATCHING_ATTR(b_matrix_coefficients, bMatrixCoefficients,
				 identity_conv);

#undef identity_conv

#undef UVCG_DEFAULT_COLOR_MATCHING_ATTR

static struct configfs_attribute *uvcg_default_color_matching_attrs[] = {
	&uvcg_default_color_matching_b_color_primaries.attr,
	&uvcg_default_color_matching_b_transfer_characteristics.attr,
	&uvcg_default_color_matching_b_matrix_coefficients.attr,
	NULL,
};

static struct config_item_type uvcg_default_color_matching_type = {
	.ct_item_ops	= &uvcg_default_color_matching_item_ops,
	.ct_attrs	= uvcg_default_color_matching_attrs,
	.ct_owner	= THIS_MODULE,
};

/* struct uvcg_color_matching {}; */

static struct config_group *uvcg_color_matching_default_groups[] = {
	&uvcg_default_color_matching.group,
	NULL,
};

/* streaming/color_matching */
static struct uvcg_color_matching_grp {
	struct config_group	group;
} uvcg_color_matching_grp;

static struct config_item_type uvcg_color_matching_grp_type = {
	.ct_owner = THIS_MODULE,
};

/* streaming/class/{fs|hs|ss} */
static struct uvcg_streaming_class {
	struct config_group	group;
} uvcg_streaming_class_fs, uvcg_streaming_class_hs, uvcg_streaming_class_ss;


static inline struct uvc_descriptor_header
***__uvcg_get_stream_class_arr(struct config_item *i, struct f_uvc_opts *o)
{
	struct uvcg_streaming_class *cl = container_of(to_config_group(i),
		struct uvcg_streaming_class, group);

	if (cl == &uvcg_streaming_class_fs)
		return &o->uvc_fs_streaming_cls;

	if (cl == &uvcg_streaming_class_hs)
		return &o->uvc_hs_streaming_cls;

	if (cl == &uvcg_streaming_class_ss)
		return &o->uvc_ss_streaming_cls;

	return NULL;
}

enum uvcg_strm_type {
	UVCG_HEADER = 0,
	UVCG_FORMAT,
	UVCG_FRAME
};

/*
 * Iterate over a hierarchy of streaming descriptors' config items.
 * The items are created by the user with configfs.
 *
 * It "processes" the header pointed to by @priv1, then for each format
 * that follows the header "processes" the format itself and then for
 * each frame inside a format "processes" the frame.
 *
 * As a "processing" function the @fun is used.
 *
 * __uvcg_iter_strm_cls() is used in two context: first, to calculate
 * the amount of memory needed for an array of streaming descriptors
 * and second, to actually fill the array.
 *
 * @h: streaming header pointer
 * @priv2: an "inout" parameter (the caller might want to see the changes to it)
 * @priv3: an "inout" parameter (the caller might want to see the changes to it)
 * @fun: callback function for processing each level of the hierarchy
 */
static int __uvcg_iter_strm_cls(struct uvcg_streaming_header *h,
	void *priv2, void *priv3,
	int (*fun)(void *, void *, void *, int, enum uvcg_strm_type type))
{
	struct uvcg_format_ptr *f;
	struct config_group *grp;
	struct config_item *item;
	struct uvcg_frame *frm;
	int ret, i, j;

	if (!fun)
		return -EINVAL;

	i = j = 0;
	ret = fun(h, priv2, priv3, 0, UVCG_HEADER);
	if (ret)
		return ret;
	list_for_each_entry(f, &h->formats, entry) {
		ret = fun(f->fmt, priv2, priv3, i++, UVCG_FORMAT);
		if (ret)
			return ret;
		grp = &f->fmt->group;
		list_for_each_entry(item, &grp->cg_children, ci_entry) {
			frm = to_uvcg_frame(item);
			ret = fun(frm, priv2, priv3, j++, UVCG_FRAME);
			if (ret)
				return ret;
		}
	}

	return ret;
}

/*
 * Count how many bytes are needed for an array of streaming descriptors.
 *
 * @priv1: pointer to a header, format or frame
 * @priv2: inout parameter, accumulated size of the array
 * @priv3: inout parameter, accumulated number of the array elements
 * @n: unused, this function's prototype must match @fun in __uvcg_iter_strm_cls
 */
static int __uvcg_cnt_strm(void *priv1, void *priv2, void *priv3, int n,
			   enum uvcg_strm_type type)
{
	size_t *size = priv2;
	size_t *count = priv3;

	switch (type) {
	case UVCG_HEADER: {
		struct uvcg_streaming_header *h = priv1;

		*size += sizeof(h->desc);
		/* bmaControls */
		*size += h->num_fmt * UVCG_STREAMING_CONTROL_SIZE;
	}
	break;
	case UVCG_FORMAT: {
		struct uvcg_format *fmt = priv1;

		if (fmt->type == UVCG_UNCOMPRESSED) {
			struct uvcg_uncompressed *u =
				container_of(fmt, struct uvcg_uncompressed,
					     fmt);

			*size += sizeof(u->desc);
		} else if (fmt->type == UVCG_MJPEG) {
			struct uvcg_mjpeg *m =
				container_of(fmt, struct uvcg_mjpeg, fmt);

			*size += sizeof(m->desc);
		} else {
			return -EINVAL;
		}
	}
	break;
	case UVCG_FRAME: {
		struct uvcg_frame *frm = priv1;
		int sz = sizeof(frm->dw_frame_interval);

		*size += sizeof(frm->frame);
		*size += frm->frame.b_frame_interval_type * sz;
	}
	break;
	}

	++*count;

	return 0;
}

/*
 * Fill an array of streaming descriptors.
 *
 * @priv1: pointer to a header, format or frame
 * @priv2: inout parameter, pointer into a block of memory
 * @priv3: inout parameter, pointer to a 2-dimensional array
 */
static int __uvcg_fill_strm(void *priv1, void *priv2, void *priv3, int n,
			    enum uvcg_strm_type type)
{
	void **dest = priv2;
	struct uvc_descriptor_header ***array = priv3;
	size_t sz;

	**array = *dest;
	++*array;

	switch (type) {
	case UVCG_HEADER: {
		struct uvc_input_header_descriptor *ihdr = *dest;
		struct uvcg_streaming_header *h = priv1;
		struct uvcg_format_ptr *f;

		memcpy(*dest, &h->desc, sizeof(h->desc));
		*dest += sizeof(h->desc);
		sz = UVCG_STREAMING_CONTROL_SIZE;
		list_for_each_entry(f, &h->formats, entry) {
			memcpy(*dest, f->fmt->bmaControls, sz);
			*dest += sz;
		}
		ihdr->bLength = sizeof(h->desc) + h->num_fmt * sz;
		ihdr->bNumFormats = h->num_fmt;
	}
	break;
	case UVCG_FORMAT: {
		struct uvcg_format *fmt = priv1;

		if (fmt->type == UVCG_UNCOMPRESSED) {
			struct uvc_format_uncompressed *unc = *dest;
			struct uvcg_uncompressed *u =
				container_of(fmt, struct uvcg_uncompressed,
					     fmt);

			memcpy(*dest, &u->desc, sizeof(u->desc));
			*dest += sizeof(u->desc);
			unc->bNumFrameDescriptors = fmt->num_frames;
			unc->bFormatIndex = n + 1;
		} else if (fmt->type == UVCG_MJPEG) {
			struct uvc_format_mjpeg *mjp = *dest;
			struct uvcg_mjpeg *m =
				container_of(fmt, struct uvcg_mjpeg, fmt);

			memcpy(*dest, &m->desc, sizeof(m->desc));
			*dest += sizeof(m->desc);
			mjp->bNumFrameDescriptors = fmt->num_frames;
			mjp->bFormatIndex = n + 1;
		} else {
			return -EINVAL;
		}
	}
	break;
	case UVCG_FRAME: {
		struct uvcg_frame *frm = priv1;
		struct uvc_descriptor_header *h = *dest;

		sz = sizeof(frm->frame);
		memcpy(*dest, &frm->frame, sz);
		*dest += sz;
		sz = frm->frame.b_frame_interval_type *
			sizeof(*frm->dw_frame_interval);
		memcpy(*dest, frm->dw_frame_interval, sz);
		*dest += sz;
		if (frm->fmt_type == UVCG_UNCOMPRESSED)
			h->bLength = UVC_DT_FRAME_UNCOMPRESSED_SIZE(
				frm->frame.b_frame_interval_type);
		else if (frm->fmt_type == UVCG_MJPEG)
			h->bLength = UVC_DT_FRAME_MJPEG_SIZE(
				frm->frame.b_frame_interval_type);
	}
	break;
	}

	return 0;
}

static int uvcg_streaming_class_allow_link(struct config_item *src,
					   struct config_item *target)
{
	struct config_item *streaming, *header;
	struct f_uvc_opts *opts;
	struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
	struct uvc_descriptor_header ***class_array, **cl_arr;
	struct uvcg_streaming_header *target_hdr;
	void *data, *data_save;
	size_t size = 0, count = 0;
	int ret = -EINVAL;

	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	streaming = src->ci_parent->ci_parent;
	header = config_group_find_item(to_config_group(streaming), "header");
	if (!header || target->ci_parent != header)
		goto out;

	opts = to_f_uvc_opts(streaming->ci_parent);

	mutex_lock(&opts->lock);

	class_array = __uvcg_get_stream_class_arr(src, opts);
	if (!class_array || *class_array || opts->refcnt) {
		ret = -EBUSY;
		goto unlock;
	}

	target_hdr = to_uvcg_streaming_header(target);
	ret = __uvcg_iter_strm_cls(target_hdr, &size, &count, __uvcg_cnt_strm);
	if (ret)
		goto unlock;

	count += 2; /* color_matching, NULL */
	*class_array = kcalloc(count, sizeof(void *), GFP_KERNEL);
	if (!*class_array) {
		ret = -ENOMEM;
		goto unlock;
	}

	data = data_save = kzalloc(size, GFP_KERNEL);
	if (!data) {
		kfree(*class_array);
		*class_array = NULL;
		ret = PTR_ERR(data);
		goto unlock;
	}
	cl_arr = *class_array;
	ret = __uvcg_iter_strm_cls(target_hdr, &data, &cl_arr,
				   __uvcg_fill_strm);
	if (ret) {
		kfree(*class_array);
		*class_array = NULL;
		/*
		 * __uvcg_fill_strm() called from __uvcg_iter_stream_cls()
		 * might have advanced the "data", so use a backup copy
		 */
		kfree(data_save);
		goto unlock;
	}
	*cl_arr = (struct uvc_descriptor_header *)&opts->uvc_color_matching;

	++target_hdr->linked;
	ret = 0;

unlock:
	mutex_unlock(&opts->lock);
out:
	mutex_unlock(su_mutex);
	return ret;
}

static int uvcg_streaming_class_drop_link(struct config_item *src,
					  struct config_item *target)
{
	struct config_item *streaming, *header;
	struct f_uvc_opts *opts;
	struct mutex *su_mutex = &src->ci_group->cg_subsys->su_mutex;
	struct uvc_descriptor_header ***class_array;
	struct uvcg_streaming_header *target_hdr;
	int ret = -EINVAL;

	mutex_lock(su_mutex); /* for navigating configfs hierarchy */

	streaming = src->ci_parent->ci_parent;
	header = config_group_find_item(to_config_group(streaming), "header");
	if (!header || target->ci_parent != header)
		goto out;

	opts = to_f_uvc_opts(streaming->ci_parent);

	mutex_lock(&opts->lock);

	class_array = __uvcg_get_stream_class_arr(src, opts);
	if (!class_array || !*class_array)
		goto unlock;

	if (opts->refcnt) {
		ret = -EBUSY;
		goto unlock;
	}

	target_hdr = to_uvcg_streaming_header(target);
	--target_hdr->linked;
	kfree(**class_array);
	kfree(*class_array);
	*class_array = NULL;
	ret = 0;

unlock:
	mutex_unlock(&opts->lock);
out:
	mutex_unlock(su_mutex);
	return ret;
}

static struct configfs_item_operations uvcg_streaming_class_item_ops = {
	.allow_link	= uvcg_streaming_class_allow_link,
	.drop_link	= uvcg_streaming_class_drop_link,
};

static struct config_item_type uvcg_streaming_class_type = {
	.ct_item_ops	= &uvcg_streaming_class_item_ops,
	.ct_owner	= THIS_MODULE,
};

static struct config_group *uvcg_streaming_class_default_groups[] = {
	&uvcg_streaming_class_fs.group,
	&uvcg_streaming_class_hs.group,
	&uvcg_streaming_class_ss.group,
	NULL,
};

/* streaming/class */
static struct uvcg_streaming_class_grp {
	struct config_group	group;
} uvcg_streaming_class_grp;

static struct config_item_type uvcg_streaming_class_grp_type = {
	.ct_owner = THIS_MODULE,
};

static struct config_group *uvcg_streaming_default_groups[] = {
	&uvcg_streaming_header_grp.group,
	&uvcg_uncompressed_grp.group,
	&uvcg_mjpeg_grp.group,
	&uvcg_color_matching_grp.group,
	&uvcg_streaming_class_grp.group,
	NULL,
};

/* streaming */
static struct uvcg_streaming_grp {
	struct config_group	group;
} uvcg_streaming_grp;

static struct config_item_type uvcg_streaming_grp_type = {
	.ct_owner = THIS_MODULE,
};

static struct config_group *uvcg_default_groups[] = {
	&uvcg_control_grp.group,
	&uvcg_streaming_grp.group,
	NULL,
};

static inline struct f_uvc_opts *to_f_uvc_opts(struct config_item *item)
{
	return container_of(to_config_group(item), struct f_uvc_opts,
			    func_inst.group);
}

CONFIGFS_ATTR_STRUCT(f_uvc_opts);
CONFIGFS_ATTR_OPS(f_uvc_opts);

static void uvc_attr_release(struct config_item *item)
{
	struct f_uvc_opts *opts = to_f_uvc_opts(item);

	usb_put_function_instance(&opts->func_inst);
}

static struct configfs_item_operations uvc_item_ops = {
	.release		= uvc_attr_release,
	.show_attribute		= f_uvc_opts_attr_show,
	.store_attribute	= f_uvc_opts_attr_store,
};

#define UVCG_OPTS_ATTR(cname, conv, str2u, uxx, vnoc, limit)		\
static ssize_t f_uvc_opts_##cname##_show(				\
	struct f_uvc_opts *opts, char *page)				\
{									\
	int result;							\
									\
	mutex_lock(&opts->lock);					\
	result = sprintf(page, "%d\n", conv(opts->cname));		\
	mutex_unlock(&opts->lock);					\
									\
	return result;							\
}									\
									\
static ssize_t								\
f_uvc_opts_##cname##_store(struct f_uvc_opts *opts,			\
			   const char *page, size_t len)		\
{									\
	int ret;							\
	uxx num;							\
									\
	mutex_lock(&opts->lock);					\
	if (opts->refcnt) {						\
		ret = -EBUSY;						\
		goto end;						\
	}								\
									\
	ret = str2u(page, 0, &num);					\
	if (ret)							\
		goto end;						\
									\
	if (num > limit) {						\
		ret = -EINVAL;						\
		goto end;						\
	}								\
	opts->cname = vnoc(num);					\
	ret = len;							\
end:									\
	mutex_unlock(&opts->lock);					\
	return ret;							\
}									\
									\
static struct f_uvc_opts_attribute					\
	f_uvc_opts_attribute_##cname =					\
	__CONFIGFS_ATTR(cname, S_IRUGO | S_IWUSR,			\
			f_uvc_opts_##cname##_show,			\
			f_uvc_opts_##cname##_store)

#define identity_conv(x) (x)

UVCG_OPTS_ATTR(streaming_interval, identity_conv, kstrtou8, u8, identity_conv,
	       16);
UVCG_OPTS_ATTR(streaming_maxpacket, le16_to_cpu, kstrtou16, u16, le16_to_cpu,
	       3072);
UVCG_OPTS_ATTR(streaming_maxburst, identity_conv, kstrtou8, u8, identity_conv,
	       15);

#undef identity_conv

#undef UVCG_OPTS_ATTR

static struct configfs_attribute *uvc_attrs[] = {
	&f_uvc_opts_attribute_streaming_interval.attr,
	&f_uvc_opts_attribute_streaming_maxpacket.attr,
	&f_uvc_opts_attribute_streaming_maxburst.attr,
	NULL,
};

static struct config_item_type uvc_func_type = {
	.ct_item_ops	= &uvc_item_ops,
	.ct_attrs	= uvc_attrs,
	.ct_owner	= THIS_MODULE,
};

static inline void uvcg_init_group(struct config_group *g,
				   struct config_group **default_groups,
				   const char *name,
				   struct config_item_type *type)
{
	g->default_groups = default_groups;
	config_group_init_type_name(g, name, type);
}

int uvcg_attach_configfs(struct f_uvc_opts *opts)
{
	config_group_init_type_name(&uvcg_control_header_grp.group,
				    "header",
				    &uvcg_control_header_grp_type);
	config_group_init_type_name(&uvcg_default_processing.group,
				    "default",
				    &uvcg_default_processing_type);
	uvcg_init_group(&uvcg_processing_grp.group,
			uvcg_processing_default_groups,
			"processing",
			&uvcg_processing_grp_type);
	config_group_init_type_name(&uvcg_default_camera.group,
				    "default",
				    &uvcg_default_camera_type);
	uvcg_init_group(&uvcg_camera_grp.group,
			uvcg_camera_default_groups,
			"camera",
			&uvcg_camera_grp_type);
	config_group_init_type_name(&uvcg_default_output.group,
				    "default",
				    &uvcg_default_output_type);
	uvcg_init_group(&uvcg_output_grp.group,
			uvcg_output_default_groups,
			"output",
			&uvcg_output_grp_type);
	uvcg_init_group(&uvcg_terminal_grp.group,
			uvcg_terminal_default_groups,
			"terminal",
			&uvcg_terminal_grp_type);
	config_group_init_type_name(&uvcg_control_class_fs.group,
				    "fs",
				    &uvcg_control_class_type);
	config_group_init_type_name(&uvcg_control_class_ss.group,
				    "ss",
				    &uvcg_control_class_type);
	uvcg_init_group(&uvcg_control_class_grp.group,
			uvcg_control_class_default_groups,
			"class",
			&uvcg_control_class_grp_type);
	uvcg_init_group(&uvcg_control_grp.group,
			uvcg_control_default_groups,
			"control",
			&uvcg_control_grp_type);
	config_group_init_type_name(&uvcg_streaming_header_grp.group,
				    "header",
				    &uvcg_streaming_header_grp_type);
	config_group_init_type_name(&uvcg_uncompressed_grp.group,
				    "uncompressed",
				    &uvcg_uncompressed_grp_type);
	config_group_init_type_name(&uvcg_mjpeg_grp.group,
				    "mjpeg",
				    &uvcg_mjpeg_grp_type);
	config_group_init_type_name(&uvcg_default_color_matching.group,
				    "default",
				    &uvcg_default_color_matching_type);
	uvcg_init_group(&uvcg_color_matching_grp.group,
			uvcg_color_matching_default_groups,
			"color_matching",
			&uvcg_color_matching_grp_type);
	config_group_init_type_name(&uvcg_streaming_class_fs.group,
				    "fs",
				    &uvcg_streaming_class_type);
	config_group_init_type_name(&uvcg_streaming_class_hs.group,
				    "hs",
				    &uvcg_streaming_class_type);
	config_group_init_type_name(&uvcg_streaming_class_ss.group,
				    "ss",
				    &uvcg_streaming_class_type);
	uvcg_init_group(&uvcg_streaming_class_grp.group,
			uvcg_streaming_class_default_groups,
			"class",
			&uvcg_streaming_class_grp_type);
	uvcg_init_group(&uvcg_streaming_grp.group,
			uvcg_streaming_default_groups,
			"streaming",
			&uvcg_streaming_grp_type);
	uvcg_init_group(&opts->func_inst.group,
			uvcg_default_groups,
			"",
			&uvc_func_type);
	return 0;
}