diff mbox series

[v4,7/7] usb: gadget: uvc: add format/frame handling code

Message ID 20211205225803.268492-8-m.grzeschik@pengutronix.de (mailing list archive)
State Superseded
Headers show
Series usb: gadget: uvc: use configfs entries for negotiation and v4l2 VIDIOCS | expand

Commit Message

Michael Grzeschik Dec. 5, 2021, 10:58 p.m. UTC
The Hostside format selection is currently only done in userspace, as
the events for SET_CUR and GET_CUR are allways moved to the application
layer. Since the v4l2 device parses the configfs data, the format
negotiation can be done in the kernel. This patch adds the functions to
set the current configuration while continuing to forward all unknown
events to the userspace level.

Signed-off-by: Michael Grzeschik <m.grzeschik@pengutronix.de>

---
v1 -> v2:
   - fixed the commit message
   - changed pr_debug to pr_err in events_process_data
   - aligned many indentations
   - simplified uvc_events_process_data
   - fixed uvc_fill_streaming_control calls in uvcg_video_init
   - added setup_subcribed to decide if userspace takes over on EOPNOTSUPP
   - added data_subscribed to decide if userspace takes over on EOPNOTSUPP
   - removed duplicate send_response
   - wrting fmt and frm in full
v2 -> v3:
   - added find_format_index to set the right probe
v3 -> v4:
   - add function find_ival_index and use for cur_ival
   - fix swapped frame and format in uvc_events_process_data on uvc_fill_streaming_control
   - set proper resp.length on ep0 complete
   - dropped setting cur_probe on set_format since function was removed
   - added locking around getting correspondent cur_{frame,format,ival}

 drivers/usb/gadget/function/f_uvc.c     | 233 +++++++++++++++++++++++-
 drivers/usb/gadget/function/uvc.h       |  19 ++
 drivers/usb/gadget/function/uvc_v4l2.c  |  66 ++++++-
 drivers/usb/gadget/function/uvc_video.c |  12 +-
 4 files changed, 323 insertions(+), 7 deletions(-)

Comments

kernel test robot Dec. 6, 2021, 10:30 a.m. UTC | #1
Hi Michael,

I love your patch! Perhaps something to improve:

[auto build test WARNING on media-tree/master]
[also build test WARNING on usb/usb-testing v5.16-rc4 next-20211206]
[cannot apply to balbi-usb/testing/next peter-chen-usb/for-usb-next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Michael-Grzeschik/usb-gadget-uvc-use-configfs-entries-for-negotiation-and-v4l2-VIDIOCS/20211206-070014
base:   git://linuxtv.org/media_tree.git master
config: i386-randconfig-s002-20211206 (https://download.01.org/0day-ci/archive/20211206/202112061803.4OpPVpwb-lkp@intel.com/config)
compiler: gcc-9 (Debian 9.3.0-22) 9.3.0
reproduce:
        # apt-get install sparse
        # sparse version: v0.6.4-dirty
        # https://github.com/0day-ci/linux/commit/8fdc5cd9a8845e9a061b42155d8d05ddb8514921
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Michael-Grzeschik/usb-gadget-uvc-use-configfs-entries-for-negotiation-and-v4l2-VIDIOCS/20211206-070014
        git checkout 8fdc5cd9a8845e9a061b42155d8d05ddb8514921
        # save the config file to linux build tree
        mkdir build_dir
        make W=1 C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' O=build_dir ARCH=i386 SHELL=/bin/bash drivers/usb/gadget/function/

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>


sparse warnings: (new ones prefixed by >>)
>> drivers/usb/gadget/function/f_uvc.c:240:48: sparse: sparse: incorrect type in assignment (different base types) @@     expected unsigned int [usertype] dwMaxPayloadTransferSize @@     got restricted __le16 const [usertype] wMaxPacketSize @@
   drivers/usb/gadget/function/f_uvc.c:240:48: sparse:     expected unsigned int [usertype] dwMaxPayloadTransferSize
   drivers/usb/gadget/function/f_uvc.c:240:48: sparse:     got restricted __le16 const [usertype] wMaxPacketSize
>> drivers/usb/gadget/function/f_uvc.c:359:18: sparse: sparse: restricted __le16 degrades to integer
   drivers/usb/gadget/function/f_uvc.c:361:23: sparse: sparse: restricted __le16 degrades to integer
   drivers/usb/gadget/function/f_uvc.c:363:50: sparse: sparse: restricted __le16 degrades to integer

vim +240 drivers/usb/gadget/function/f_uvc.c

   197	
   198	/* --------------------------------------------------------------------------
   199	 * Control requests
   200	 */
   201	
   202	void uvc_fill_streaming_control(struct uvc_device *uvc,
   203				   struct uvc_streaming_control *ctrl,
   204				   int iframe, int iformat, unsigned int ival)
   205	{
   206		struct uvcg_format *uformat;
   207		struct uvcg_frame *uframe;
   208	
   209		/* Restrict the iformat, iframe and ival to valid values. Negative
   210		 * values for ifrmat and iframe will result in the maximum valid value
   211		 * being selected
   212		 */
   213		iformat = clamp((unsigned int)iformat, 1U,
   214				(unsigned int)uvc->header->num_fmt);
   215		uformat = find_format_by_index(uvc, iformat);
   216		if (!uformat)
   217			return;
   218	
   219		iframe = clamp((unsigned int)iframe, 1U,
   220			       (unsigned int)uformat->num_frames);
   221		uframe = find_frame_by_index(uvc, uformat, iframe);
   222		if (!uframe)
   223			return;
   224	
   225		ival = clamp((unsigned int)ival, 1U,
   226			     (unsigned int)uframe->frame.b_frame_interval_type);
   227		if (!uframe->dw_frame_interval[ival - 1])
   228			return;
   229	
   230		memset(ctrl, 0, sizeof(*ctrl));
   231	
   232		ctrl->bmHint = 1;
   233		ctrl->bFormatIndex = iformat;
   234		ctrl->bFrameIndex = iframe;
   235		ctrl->dwFrameInterval = uframe->dw_frame_interval[ival - 1];
   236		ctrl->dwMaxVideoFrameSize =
   237			uframe->frame.dw_max_video_frame_buffer_size;
   238	
   239		if (uvc->video.ep->desc)
 > 240			ctrl->dwMaxPayloadTransferSize =
   241				uvc->video.ep->desc->wMaxPacketSize;
   242		ctrl->bmFramingInfo = 3;
   243		ctrl->bPreferedVersion = 1;
   244		ctrl->bMaxVersion = 1;
   245	}
   246	
   247	static int uvc_events_process_data(struct uvc_device *uvc,
   248					   struct usb_request *req)
   249	{
   250		struct uvc_video *video = &uvc->video;
   251		struct uvc_streaming_control *target;
   252		struct uvc_streaming_control *ctrl;
   253		struct uvcg_frame *uframe;
   254		struct uvcg_format *uformat;
   255	
   256		switch (video->control) {
   257		case UVC_VS_PROBE_CONTROL:
   258			pr_debug("setting probe control, length = %d\n", req->actual);
   259			target = &video->probe;
   260			break;
   261	
   262		case UVC_VS_COMMIT_CONTROL:
   263			pr_debug("setting commit control, length = %d\n", req->actual);
   264			target = &video->commit;
   265			break;
   266	
   267		default:
   268			pr_err("setting unknown control, length = %d\n", req->actual);
   269			return -EOPNOTSUPP;
   270		}
   271	
   272		ctrl = (struct uvc_streaming_control *)req->buf;
   273	
   274		uvc_fill_streaming_control(uvc, target, ctrl->bFrameIndex,
   275				   ctrl->bFormatIndex, ctrl->dwFrameInterval);
   276	
   277		if (video->control == UVC_VS_COMMIT_CONTROL) {
   278			uformat = find_format_by_index(uvc, target->bFormatIndex);
   279			if (!uformat)
   280				return -EINVAL;
   281	
   282			uframe = find_frame_by_index(uvc, uformat, ctrl->bFrameIndex);
   283			if (!uframe)
   284				return -EINVAL;
   285	
   286			spin_lock(&video->frame_lock);
   287	
   288			video->cur_frame = uframe;
   289			video->cur_format = uformat;
   290			video->cur_ival = find_ival_index(uframe, ctrl->dwFrameInterval);
   291	
   292			spin_unlock(&video->frame_lock);
   293		}
   294	
   295		return 0;
   296	}
   297	
   298	static void
   299	uvc_events_process_streaming(struct uvc_device *uvc, uint8_t req, uint8_t cs,
   300				     struct uvc_request_data *resp)
   301	{
   302		struct uvc_streaming_control *ctrl;
   303	
   304		pr_debug("streaming request (req %02x cs %02x)\n", req, cs);
   305	
   306		if (cs != UVC_VS_PROBE_CONTROL && cs != UVC_VS_COMMIT_CONTROL)
   307			return;
   308	
   309		ctrl = (struct uvc_streaming_control *)&resp->data;
   310		resp->length = sizeof(*ctrl);
   311	
   312		switch (req) {
   313		case UVC_SET_CUR:
   314			uvc->video.control = cs;
   315			resp->length = 34;
   316			break;
   317	
   318		case UVC_GET_CUR:
   319			if (cs == UVC_VS_PROBE_CONTROL)
   320				memcpy(ctrl, &uvc->video.probe, sizeof(*ctrl));
   321			else
   322				memcpy(ctrl, &uvc->video.commit, sizeof(*ctrl));
   323			break;
   324	
   325		case UVC_GET_MIN:
   326		case UVC_GET_MAX:
   327		case UVC_GET_DEF:
   328			if (req == UVC_GET_MAX)
   329				uvc_fill_streaming_control(uvc, ctrl, -1, -1, UINT_MAX);
   330			else
   331				uvc_fill_streaming_control(uvc, ctrl, 1, 1, 0);
   332			break;
   333	
   334		case UVC_GET_RES:
   335			memset(ctrl, 0, sizeof(*ctrl));
   336			break;
   337	
   338		case UVC_GET_LEN:
   339			resp->data[0] = 0x00;
   340			resp->data[1] = 0x22;
   341			resp->length = 2;
   342			break;
   343	
   344		case UVC_GET_INFO:
   345			resp->data[0] = 0x03;
   346			resp->length = 1;
   347			break;
   348		}
   349	}
   350	
   351	static int
   352	uvc_events_process_class(struct uvc_device *uvc,
   353				 const struct usb_ctrlrequest *ctrl,
   354				 struct uvc_request_data *resp)
   355	{
   356		if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE)
   357			return -EINVAL;
   358	
 > 359		if ((ctrl->wIndex & 0xff) == uvc->control_intf)
   360			return -EOPNOTSUPP;
   361		else if ((ctrl->wIndex & 0xff) == uvc->streaming_intf)
   362			uvc_events_process_streaming(uvc, ctrl->bRequest,
   363						     ctrl->wValue >> 8, resp);
   364	
   365		return 0;
   366	}
   367	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
kernel test robot Dec. 6, 2021, 2:04 p.m. UTC | #2
Hi Michael,

I love your patch! Yet something to improve:

[auto build test ERROR on media-tree/master]
[also build test ERROR on usb/usb-testing v5.16-rc4 next-20211206]
[cannot apply to balbi-usb/testing/next peter-chen-usb/for-usb-next]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Michael-Grzeschik/usb-gadget-uvc-use-configfs-entries-for-negotiation-and-v4l2-VIDIOCS/20211206-070014
base:   git://linuxtv.org/media_tree.git master
config: riscv-randconfig-r024-20211205 (https://download.01.org/0day-ci/archive/20211206/202112062124.HEJZJ0fR-lkp@intel.com/config)
compiler: clang version 14.0.0 (https://github.com/llvm/llvm-project 6e8678903523019903222e4521a5e41af743cab0)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install riscv cross compiling tool for clang build
        # apt-get install binutils-riscv64-linux-gnu
        # https://github.com/0day-ci/linux/commit/8fdc5cd9a8845e9a061b42155d8d05ddb8514921
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Michael-Grzeschik/usb-gadget-uvc-use-configfs-entries-for-negotiation-and-v4l2-VIDIOCS/20211206-070014
        git checkout 8fdc5cd9a8845e9a061b42155d8d05ddb8514921
        # save the config file to linux build tree
        mkdir build_dir
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=riscv SHELL=/bin/bash drivers/usb/gadget/function/

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   drivers/usb/gadget/function/uvc_v4l2.c:73:2: warning: variable 'uformat' is used uninitialized whenever 'for' loop exits because its condition is false [-Wsometimes-uninitialized]
           list_for_each_entry(format, &uvc->header->formats, entry) {
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/list.h:631:7: note: expanded from macro 'list_for_each_entry'
                !list_entry_is_head(pos, head, member);                    \
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/usb/gadget/function/uvc_v4l2.c:81:9: note: uninitialized use occurs here
           return uformat;
                  ^~~~~~~
   drivers/usb/gadget/function/uvc_v4l2.c:73:2: note: remove the condition if it is always true
           list_for_each_entry(format, &uvc->header->formats, entry) {
           ^
   include/linux/list.h:631:7: note: expanded from macro 'list_for_each_entry'
                !list_entry_is_head(pos, head, member);                    \
                ^
   drivers/usb/gadget/function/uvc_v4l2.c:70:29: note: initialize the variable 'uformat' to silence this warning
           struct uvcg_format *uformat;
                                      ^
                                       = NULL
   drivers/usb/gadget/function/uvc_v4l2.c:139:2: warning: variable 'uformat' is used uninitialized whenever 'for' loop exits because its condition is false [-Wsometimes-uninitialized]
           list_for_each_entry(format, &uvc->header->formats, entry) {
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   include/linux/list.h:631:7: note: expanded from macro 'list_for_each_entry'
                !list_entry_is_head(pos, head, member);                    \
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/usb/gadget/function/uvc_v4l2.c:148:9: note: uninitialized use occurs here
           return uformat;
                  ^~~~~~~
   drivers/usb/gadget/function/uvc_v4l2.c:139:2: note: remove the condition if it is always true
           list_for_each_entry(format, &uvc->header->formats, entry) {
           ^
   include/linux/list.h:631:7: note: expanded from macro 'list_for_each_entry'
                !list_entry_is_head(pos, head, member);                    \
                ^
   drivers/usb/gadget/function/uvc_v4l2.c:137:29: note: initialize the variable 'uformat' to silence this warning
           struct uvcg_format *uformat;
                                      ^
                                       = NULL
>> drivers/usb/gadget/function/uvc_v4l2.c:334:14: error: use of undeclared identifier 'video'
                   spin_lock(&video->frame_lock);
                              ^
   drivers/usb/gadget/function/uvc_v4l2.c:339:16: error: use of undeclared identifier 'video'
                   spin_unlock(&video->frame_lock);
                                ^
   2 warnings and 2 errors generated.


vim +/video +334 drivers/usb/gadget/function/uvc_v4l2.c

   305	
   306	static int
   307	uvc_v4l2_enum_frameintervals(struct file *file, void *fh,
   308			struct v4l2_frmivalenum *fival)
   309	{
   310		struct video_device *vdev = video_devdata(file);
   311		struct uvc_device *uvc = video_get_drvdata(vdev);
   312		struct uvcg_format *uformat = NULL;
   313		struct uvcg_frame *uframe = NULL;
   314		struct uvcg_frame_ptr *frame;
   315	
   316		uformat = find_format_by_pix(uvc, fival->pixel_format);
   317		if (!uformat)
   318			return -EINVAL;
   319	
   320		list_for_each_entry(frame, &uformat->frames, entry) {
   321			if (frame->frm->frame.w_width == fival->width &&
   322			    frame->frm->frame.w_height == fival->height) {
   323				uframe = frame->frm;
   324				break;
   325			}
   326		}
   327		if (!uframe)
   328			return -EINVAL;
   329	
   330		if (uvc->streamon) {
   331			if (fival->index >= 1)
   332				return -EINVAL;
   333	
 > 334			spin_lock(&video->frame_lock);
   335	
   336			fival->discrete.numerator =
   337				uframe->dw_frame_interval[uvc->video.cur_ival - 1];
   338	
   339			spin_unlock(&video->frame_lock);
   340		} else {
   341			if (fival->index >= uframe->frame.b_frame_interval_type)
   342				return -EINVAL;
   343	
   344			fival->discrete.numerator =
   345				uframe->dw_frame_interval[fival->index];
   346		}
   347	
   348		/* TODO: handle V4L2_FRMIVAL_TYPE_STEPWISE */
   349		fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
   350		fival->discrete.denominator = 10000000;
   351		v4l2_simplify_fraction(&fival->discrete.numerator,
   352			&fival->discrete.denominator, 8, 333);
   353	
   354		return 0;
   355	}
   356	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff mbox series

Patch

diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c
index e081a4e6572ee2..de97a6e29ea955 100644
--- a/drivers/usb/gadget/function/f_uvc.c
+++ b/drivers/usb/gadget/function/f_uvc.c
@@ -16,7 +16,6 @@ 
 #include <linux/string.h>
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
-#include <linux/usb/g_uvc.h>
 #include <linux/usb/video.h>
 #include <linux/vmalloc.h>
 #include <linux/wait.h>
@@ -200,16 +199,229 @@  static const struct usb_descriptor_header * const uvc_ss_streaming[] = {
  * Control requests
  */
 
+void uvc_fill_streaming_control(struct uvc_device *uvc,
+			   struct uvc_streaming_control *ctrl,
+			   int iframe, int iformat, unsigned int ival)
+{
+	struct uvcg_format *uformat;
+	struct uvcg_frame *uframe;
+
+	/* Restrict the iformat, iframe and ival to valid values. Negative
+	 * values for ifrmat and iframe will result in the maximum valid value
+	 * being selected
+	 */
+	iformat = clamp((unsigned int)iformat, 1U,
+			(unsigned int)uvc->header->num_fmt);
+	uformat = find_format_by_index(uvc, iformat);
+	if (!uformat)
+		return;
+
+	iframe = clamp((unsigned int)iframe, 1U,
+		       (unsigned int)uformat->num_frames);
+	uframe = find_frame_by_index(uvc, uformat, iframe);
+	if (!uframe)
+		return;
+
+	ival = clamp((unsigned int)ival, 1U,
+		     (unsigned int)uframe->frame.b_frame_interval_type);
+	if (!uframe->dw_frame_interval[ival - 1])
+		return;
+
+	memset(ctrl, 0, sizeof(*ctrl));
+
+	ctrl->bmHint = 1;
+	ctrl->bFormatIndex = iformat;
+	ctrl->bFrameIndex = iframe;
+	ctrl->dwFrameInterval = uframe->dw_frame_interval[ival - 1];
+	ctrl->dwMaxVideoFrameSize =
+		uframe->frame.dw_max_video_frame_buffer_size;
+
+	if (uvc->video.ep->desc)
+		ctrl->dwMaxPayloadTransferSize =
+			uvc->video.ep->desc->wMaxPacketSize;
+	ctrl->bmFramingInfo = 3;
+	ctrl->bPreferedVersion = 1;
+	ctrl->bMaxVersion = 1;
+}
+
+static int uvc_events_process_data(struct uvc_device *uvc,
+				   struct usb_request *req)
+{
+	struct uvc_video *video = &uvc->video;
+	struct uvc_streaming_control *target;
+	struct uvc_streaming_control *ctrl;
+	struct uvcg_frame *uframe;
+	struct uvcg_format *uformat;
+
+	switch (video->control) {
+	case UVC_VS_PROBE_CONTROL:
+		pr_debug("setting probe control, length = %d\n", req->actual);
+		target = &video->probe;
+		break;
+
+	case UVC_VS_COMMIT_CONTROL:
+		pr_debug("setting commit control, length = %d\n", req->actual);
+		target = &video->commit;
+		break;
+
+	default:
+		pr_err("setting unknown control, length = %d\n", req->actual);
+		return -EOPNOTSUPP;
+	}
+
+	ctrl = (struct uvc_streaming_control *)req->buf;
+
+	uvc_fill_streaming_control(uvc, target, ctrl->bFrameIndex,
+			   ctrl->bFormatIndex, ctrl->dwFrameInterval);
+
+	if (video->control == UVC_VS_COMMIT_CONTROL) {
+		uformat = find_format_by_index(uvc, target->bFormatIndex);
+		if (!uformat)
+			return -EINVAL;
+
+		uframe = find_frame_by_index(uvc, uformat, ctrl->bFrameIndex);
+		if (!uframe)
+			return -EINVAL;
+
+		spin_lock(&video->frame_lock);
+
+		video->cur_frame = uframe;
+		video->cur_format = uformat;
+		video->cur_ival = find_ival_index(uframe, ctrl->dwFrameInterval);
+
+		spin_unlock(&video->frame_lock);
+	}
+
+	return 0;
+}
+
+static void
+uvc_events_process_streaming(struct uvc_device *uvc, uint8_t req, uint8_t cs,
+			     struct uvc_request_data *resp)
+{
+	struct uvc_streaming_control *ctrl;
+
+	pr_debug("streaming request (req %02x cs %02x)\n", req, cs);
+
+	if (cs != UVC_VS_PROBE_CONTROL && cs != UVC_VS_COMMIT_CONTROL)
+		return;
+
+	ctrl = (struct uvc_streaming_control *)&resp->data;
+	resp->length = sizeof(*ctrl);
+
+	switch (req) {
+	case UVC_SET_CUR:
+		uvc->video.control = cs;
+		resp->length = 34;
+		break;
+
+	case UVC_GET_CUR:
+		if (cs == UVC_VS_PROBE_CONTROL)
+			memcpy(ctrl, &uvc->video.probe, sizeof(*ctrl));
+		else
+			memcpy(ctrl, &uvc->video.commit, sizeof(*ctrl));
+		break;
+
+	case UVC_GET_MIN:
+	case UVC_GET_MAX:
+	case UVC_GET_DEF:
+		if (req == UVC_GET_MAX)
+			uvc_fill_streaming_control(uvc, ctrl, -1, -1, UINT_MAX);
+		else
+			uvc_fill_streaming_control(uvc, ctrl, 1, 1, 0);
+		break;
+
+	case UVC_GET_RES:
+		memset(ctrl, 0, sizeof(*ctrl));
+		break;
+
+	case UVC_GET_LEN:
+		resp->data[0] = 0x00;
+		resp->data[1] = 0x22;
+		resp->length = 2;
+		break;
+
+	case UVC_GET_INFO:
+		resp->data[0] = 0x03;
+		resp->length = 1;
+		break;
+	}
+}
+
+static int
+uvc_events_process_class(struct uvc_device *uvc,
+			 const struct usb_ctrlrequest *ctrl,
+			 struct uvc_request_data *resp)
+{
+	if ((ctrl->bRequestType & USB_RECIP_MASK) != USB_RECIP_INTERFACE)
+		return -EINVAL;
+
+	if ((ctrl->wIndex & 0xff) == uvc->control_intf)
+		return -EOPNOTSUPP;
+	else if ((ctrl->wIndex & 0xff) == uvc->streaming_intf)
+		uvc_events_process_streaming(uvc, ctrl->bRequest,
+					     ctrl->wValue >> 8, resp);
+
+	return 0;
+}
+
+static int
+uvc_events_process_setup(struct uvc_device *uvc,
+			 const struct usb_ctrlrequest *ctrl,
+			 struct uvc_request_data *resp)
+{
+	uvc->video.control = 0;
+
+	pr_debug("bRequestType %02x bRequest %02x wValue %04x wIndex %04x wLength %04x\n",
+		ctrl->bRequestType, ctrl->bRequest, ctrl->wValue,
+		ctrl->wIndex, ctrl->wLength);
+
+	switch (ctrl->bRequestType & USB_TYPE_MASK) {
+	case USB_TYPE_STANDARD:
+		return -EOPNOTSUPP;
+
+	case USB_TYPE_CLASS:
+		return uvc_events_process_class(uvc, ctrl, resp);
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
 static void
 uvc_function_ep0_complete(struct usb_ep *ep, struct usb_request *req)
 {
 	struct uvc_device *uvc = req->context;
 	struct v4l2_event v4l2_event;
 	struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
+	struct uvc_request_data resp;
+	int ret;
 
 	if (uvc->event_setup_out) {
 		uvc->event_setup_out = 0;
 
+		memset(&resp, 0, sizeof(resp));
+		resp.length = -EL2HLT;
+
+		ret = uvc_events_process_data(uvc, req);
+		/* If we have no error on process */
+		if (!ret) {
+			resp.length = req->length;
+			uvc_send_response(uvc, &resp);
+			return;
+		}
+
+		/* If we have a real error on process */
+		if (ret != -EOPNOTSUPP)
+			return;
+
+		/* If we have -EOPNOTSUPP */
+		if (!uvc->data_subscribed)
+			return;
+
+		/* If we have data subscribed */
 		memset(&v4l2_event, 0, sizeof(v4l2_event));
 		v4l2_event.type = UVC_EVENT_DATA;
 		uvc_event->data.length = req->actual;
@@ -224,6 +436,8 @@  uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	struct uvc_device *uvc = to_uvc(f);
 	struct v4l2_event v4l2_event;
 	struct uvc_event *uvc_event = (void *)&v4l2_event.u.data;
+	struct uvc_request_data resp;
+	int ret = 0;
 
 	if ((ctrl->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS) {
 		uvcg_info(f, "invalid request type\n");
@@ -240,6 +454,23 @@  uvc_function_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
 	uvc->event_setup_out = !(ctrl->bRequestType & USB_DIR_IN);
 	uvc->event_length = le16_to_cpu(ctrl->wLength);
 
+	memset(&resp, 0, sizeof(resp));
+	resp.length = -EL2HLT;
+
+	ret = uvc_events_process_setup(uvc, ctrl, &resp);
+	/* If we have no error on process */
+	if (!ret)
+		return uvc_send_response(uvc, &resp);
+
+	/* If we have a real error on process */
+	if (ret != -EOPNOTSUPP)
+		return ret;
+
+	/* If we have -EOPNOTSUPP */
+	if (!uvc->setup_subscribed)
+		return uvc_send_response(uvc, &resp);
+
+	/* If we have setup subscribed */
 	memset(&v4l2_event, 0, sizeof(v4l2_event));
 	v4l2_event.type = UVC_EVENT_SETUP;
 	memcpy(&uvc_event->req, ctrl, sizeof(uvc_event->req));
diff --git a/drivers/usb/gadget/function/uvc.h b/drivers/usb/gadget/function/uvc.h
index b7246b4bee1d87..a9984d26ebe043 100644
--- a/drivers/usb/gadget/function/uvc.h
+++ b/drivers/usb/gadget/function/uvc.h
@@ -13,6 +13,8 @@ 
 #include <linux/mutex.h>
 #include <linux/spinlock.h>
 #include <linux/usb/composite.h>
+#include <linux/usb/g_uvc.h>
+#include <linux/usb/video.h>
 #include <linux/videodev2.h>
 
 #include <media/v4l2-device.h>
@@ -93,6 +95,12 @@  struct uvc_video {
 	unsigned int cur_ival;
 
 	struct mutex mutex;	/* protects frame parameters */
+	spinlock_t frame_lock;
+
+	struct uvc_streaming_control probe;
+	struct uvc_streaming_control commit;
+
+	int control;
 
 	unsigned int uvc_num_requests;
 
@@ -128,6 +136,8 @@  struct uvc_device {
 	struct usb_function func;
 	struct uvc_video video;
 	bool func_connected;
+	bool setup_subscribed;
+	bool data_subscribed;
 
 	struct uvcg_streaming_header *header;
 
@@ -183,5 +193,14 @@  extern struct uvcg_format *find_format_by_index(struct uvc_device *uvc,
 extern struct uvcg_frame *find_frame_by_index(struct uvc_device *uvc,
 					      struct uvcg_format *uformat,
 					      int index);
+extern int find_format_index(struct uvc_device *uvc,
+			       struct uvcg_format *uformat);
+extern int find_ival_index(struct uvcg_frame *uframe, int dwFrameInterval);
+extern void uvc_fill_streaming_control(struct uvc_device *uvc,
+				       struct uvc_streaming_control *ctrl,
+				       int iframe, int iformat,
+				       unsigned int ival);
+extern int uvc_send_response(struct uvc_device *uvc,
+			     struct uvc_request_data *data);
 
 #endif /* _UVC_GADGET_H_ */
diff --git a/drivers/usb/gadget/function/uvc_v4l2.c b/drivers/usb/gadget/function/uvc_v4l2.c
index 1663e70c41eb24..8eac45e3a31a12 100644
--- a/drivers/usb/gadget/function/uvc_v4l2.c
+++ b/drivers/usb/gadget/function/uvc_v4l2.c
@@ -81,6 +81,33 @@  struct uvcg_format *find_format_by_index(struct uvc_device *uvc, int index)
 	return uformat;
 }
 
+int find_format_index(struct uvc_device *uvc, struct uvcg_format *uformat)
+{
+	struct uvcg_format_ptr *format;
+	int i = 1;
+
+	list_for_each_entry(format, &uvc->header->formats, entry) {
+		if (uformat == format->fmt)
+			return i;
+		i++;
+	}
+
+	return 0;
+}
+
+int find_ival_index(struct uvcg_frame *uframe, int dwFrameInterval)
+{
+	int i;
+
+	for (i = 0; i < uframe->frame.b_frame_interval_type; i++) {
+		if (dwFrameInterval == uframe->dw_frame_interval[i])
+			return i + 1;
+	}
+
+	/* fallback */
+	return 1;
+}
+
 struct uvcg_frame *find_frame_by_index(struct uvc_device *uvc,
 				       struct uvcg_format *uformat,
 				       int index)
@@ -169,8 +196,7 @@  static struct uvcg_frame *find_closest_frame_by_size(struct uvc_device *uvc,
  * Requests handling
  */
 
-static int
-uvc_send_response(struct uvc_device *uvc, struct uvc_request_data *data)
+int uvc_send_response(struct uvc_device *uvc, struct uvc_request_data *data)
 {
 	struct usb_composite_dev *cdev = uvc->func.config->cdev;
 	struct usb_request *req = uvc->control_req;
@@ -212,6 +238,8 @@  uvc_v4l2_get_format(struct file *file, void *fh, struct v4l2_format *fmt)
 	struct uvc_video *video = &uvc->video;
 	struct uvc_format_desc *fmtdesc;
 
+	spin_lock(&video->frame_lock);
+
 	fmtdesc = to_uvc_format(video->cur_format);
 
 	fmt->fmt.pix.pixelformat = fmtdesc->fcc;
@@ -225,6 +253,8 @@  uvc_v4l2_get_format(struct file *file, void *fh, struct v4l2_format *fmt)
 	fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
 	fmt->fmt.pix.priv = 0;
 
+	spin_unlock(&video->frame_lock);
+
 	return 0;
 }
 
@@ -236,6 +266,7 @@  uvc_v4l2_try_set_fmt(struct file *file, void *fh, struct v4l2_format *fmt)
 	struct uvc_video *video = &uvc->video;
 	struct uvcg_format *uformat;
 	struct uvcg_frame *uframe;
+	int iformat;
 	u8 *fcc;
 
 	if (fmt->type != video->queue.queue.type)
@@ -251,6 +282,10 @@  uvc_v4l2_try_set_fmt(struct file *file, void *fh, struct v4l2_format *fmt)
 	if (!uformat)
 		return -EINVAL;
 
+	iformat = find_format_index(uvc, uformat);
+	if (!iformat)
+		return -EINVAL;
+
 	uframe = find_closest_frame_by_size(uvc, uformat,
 				fmt->fmt.pix.width, fmt->fmt.pix.height);
 	if (!uframe)
@@ -296,8 +331,12 @@  uvc_v4l2_enum_frameintervals(struct file *file, void *fh,
 		if (fival->index >= 1)
 			return -EINVAL;
 
+		spin_lock(&video->frame_lock);
+
 		fival->discrete.numerator =
 			uframe->dw_frame_interval[uvc->video.cur_ival - 1];
+
+		spin_unlock(&video->frame_lock);
 	} else {
 		if (fival->index >= uframe->frame.b_frame_interval_type)
 			return -EINVAL;
@@ -329,8 +368,12 @@  uvc_v4l2_enum_framesizes(struct file *file, void *fh,
 		if (fsize->index >= 1)
 			return -EINVAL;
 
+		spin_lock(&video->frame_lock);
+
 		uformat = video->cur_format;
 		uframe = video->cur_frame;
+
+		spin_unlock(&video->frame_lock);
 	} else {
 		uformat = find_format_by_pix(uvc, fsize->pixel_format);
 		if (!uformat)
@@ -364,7 +407,11 @@  uvc_v4l2_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f)
 		if (f->index >= 1)
 			return -EINVAL;
 
+		spin_lock(&video->frame_lock);
+
 		uformat = video->cur_format;
+
+		spin_unlock(&video->frame_lock);
 	} else {
 		if (f->index >= uvc->header->num_fmt)
 			return -EINVAL;
@@ -488,14 +535,20 @@  uvc_v4l2_subscribe_event(struct v4l2_fh *fh,
 	if (sub->type < UVC_EVENT_FIRST || sub->type > UVC_EVENT_LAST)
 		return -EINVAL;
 
-	if (sub->type == UVC_EVENT_SETUP && uvc->func_connected)
+	if (sub->type == UVC_EVENT_STREAMON && uvc->func_connected)
 		return -EBUSY;
 
 	ret = v4l2_event_subscribe(fh, sub, 2, NULL);
 	if (ret < 0)
 		return ret;
 
-	if (sub->type == UVC_EVENT_SETUP) {
+	if (sub->type == UVC_EVENT_SETUP)
+		uvc->setup_subscribed = true;
+
+	if (sub->type == UVC_EVENT_DATA)
+		uvc->data_subscribed = true;
+
+	if (sub->type == UVC_EVENT_STREAMON) {
 		uvc->func_connected = true;
 		handle->is_uvc_app_handle = true;
 		uvc_function_connect(uvc);
@@ -524,7 +577,10 @@  uvc_v4l2_unsubscribe_event(struct v4l2_fh *fh,
 	if (ret < 0)
 		return ret;
 
-	if (sub->type == UVC_EVENT_SETUP && handle->is_uvc_app_handle) {
+	if (sub->type == UVC_EVENT_SETUP)
+		uvc->setup_subscribed = false;
+
+	if (sub->type == UVC_EVENT_STREAMON && handle->is_uvc_app_handle) {
 		uvc_v4l2_disable(uvc);
 		handle->is_uvc_app_handle = false;
 	}
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 9fb2b9c575ed34..7eb5364997028b 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -517,10 +517,11 @@  static int uvc_default_frame_interval(struct uvc_video *video)
  */
 int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 {
-	int iframe;
+	int iframe, iformat;
 
 	INIT_LIST_HEAD(&video->req_free);
 	spin_lock_init(&video->req_lock);
+	spin_lock_init(&video->frame_lock);
 	INIT_WORK(&video->pump, uvcg_video_pump);
 
 	if (list_empty(&uvc->header->formats))
@@ -531,6 +532,10 @@  int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 	if (!video->cur_format)
 		return -EINVAL;
 
+	iformat = find_format_index(uvc, video->cur_format);
+	if (!iformat)
+		return -EINVAL;
+
 	iframe = uvc_frame_default(video->cur_format);
 	if (!iframe)
 		return -EINVAL;
@@ -541,6 +546,11 @@  int uvcg_video_init(struct uvc_video *video, struct uvc_device *uvc)
 
 	video->cur_ival = uvc_default_frame_interval(video);
 
+	uvc_fill_streaming_control(uvc, &video->probe, iframe, iformat,
+				   video->cur_ival);
+	uvc_fill_streaming_control(uvc, &video->commit, iframe, iformat,
+				   video->cur_ival);
+
 	/* Initialize the video buffers queue. */
 	uvcg_queue_init(&video->queue, uvc->v4l2_dev.dev->parent,
 			V4L2_BUF_TYPE_VIDEO_OUTPUT, &video->mutex);