[PATCH RFC] USB Video Class device emulation.

Raphael Lisicki posted 1 patch 2 years, 11 months ago
Failed in applying to current master (apply log)
hw/usb/Makefile.objs |    1 +
hw/usb/usb-uvc.c     | 1435 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 1436 insertions(+)
create mode 100644 hw/usb/usb-uvc.c
[PATCH RFC] USB Video Class device emulation.
Posted by Raphael Lisicki 2 years, 11 months ago
This continues work started by Natalia Portillo <claunia@claunia.com>, as
submitted here: https://lists.nongnu.org/archive/html/qemu-devel/2010-06/msg01126.html

I have updated the changes so that the patch now applies to v4.2.1. Video
input has been expanded to support user-mode buffers so that an UVC device
itself can be used as input for this module.

My work on this has already stalled some weeks ago and I am submitting it in
case somebody might be interested.

Best regards
Raphael
---
 hw/usb/Makefile.objs |    1 +
 hw/usb/usb-uvc.c     | 1435 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 1436 insertions(+)
 create mode 100644 hw/usb/usb-uvc.c

diff --git a/hw/usb/Makefile.objs b/hw/usb/Makefile.objs
index 303ac084a0..83e460af8a 100644
--- a/hw/usb/Makefile.objs
+++ b/hw/usb/Makefile.objs
@@ -26,6 +26,7 @@ common-obj-$(CONFIG_USB_AUDIO)        += dev-audio.o
 common-obj-$(CONFIG_USB_SERIAL)       += dev-serial.o
 common-obj-$(CONFIG_USB_NETWORK)      += dev-network.o
 common-obj-$(CONFIG_USB_BLUETOOTH)    += dev-bluetooth.o
+common-obj-$(CONFIG_LINUX)            += usb-uvc.o
 
 ifeq ($(CONFIG_USB_SMARTCARD),y)
 common-obj-y                          += dev-smartcard-reader.o
diff --git a/hw/usb/usb-uvc.c b/hw/usb/usb-uvc.c
new file mode 100644
index 0000000000..a7ffba68d8
--- /dev/null
+++ b/hw/usb/usb-uvc.c
@@ -0,0 +1,1435 @@
+/*
+ * USB Video Class Device emulation.
+ *
+ * Copyright (c) 2010 Claunia.com
+ * Written by Natalia Portillo <natalia@claunia.com>
+ *
+ * Based on hw/usb-hid.c:
+ * Copyright (c) 2005 Fabrice Bellard
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation in its version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include "usb.h"
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/error-report.h"
+#include "hw/usb.h"
+#include "hw/usb/desc.h"
+#include "hw/qdev-properties.h"
+#include "hw/hw.h"
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include "qapi/error.h"
+// V4L2 ioctls
+#include <sys/ioctl.h>
+#include <linux/videodev2.h>
+
+#define DEBUG_UVC
+
+#ifdef DEBUG_UVC
+#define DPRINTF(fmt, ...) \
+do { printf("usb-uvc: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#endif
+
+/* USB Video Class Request codes */
+#define USB_UVC_RC_UNDEFINED	0x00
+#define USB_UVC_SET_CUR			0x01
+#define USB_UVC_GET_CUR			0x81
+#define USB_UVC_GET_MIN			0x82
+#define USB_UVC_GET_MAX			0x83
+#define USB_UVC_GET_RES			0x84
+#define USB_UVC_GET_LEN			0x85
+#define USB_UVC_GET_INFO		0x86
+#define USB_UVC_GET_DEF			0x87
+
+/* USB Video Class Request types */
+#define UVCSetVideoControl		0x2100
+#define UVCSetVideoStreaming	0x2200
+#define UVCGetVideoControl		0xA100
+#define UVCGetVideoStreaming	0xA200
+
+
+enum v4l2_api {
+	V4L2_API_UNKOWN = 0,
+	V4L2_API_READ = 1,
+	V4L2_API_USERPTRS = 2,
+};
+
+
+
+typedef struct USBUVCState {
+    USBDevice dev;
+	char	current_input;
+	char	*v4l2_device;
+	size_t	height;
+	size_t	width;
+
+
+	int v4l2_fd;
+	enum v4l2_api v4l2_api;
+	char *frame;
+	char *frame_start;
+	char* frame_storage[3];
+	int frame_id;
+	int frame_remaining_bytes;
+	int frame_max_length;
+
+	/* values in 16bit, as by UVC. Lets assume the 64bit values from V4L2 fit. Report error if the assumption breaks. */
+	struct {
+		bool supported;
+		int16_t minimum;
+		int16_t maximum;
+		int16_t resolution;
+		int16_t default_value;
+		int16_t current_value;
+	} brightness;
+} USBUVCState;
+
+
+
+static void get_frame_read_api(USBUVCState *s)
+{
+	DPRINTF("Getting frame.\n");
+	s->frame = s->frame_start;
+	int frame_length = read(s->v4l2_fd, s->frame+2, s->frame_max_length);
+	
+	if(frame_length == -1)
+	{
+		DPRINTF("Error while reading frame. errno %d\n", errno);
+	}
+	else
+	{
+		s->frame[0] = 2;
+		s->frame_id = s->frame_id ^ 1;
+		s->frame[1] = 0x82 | s->frame_id;
+		s->frame_remaining_bytes = frame_length+2;
+		DPRINTF("Got a frame of %d bytes.\n", frame_length);
+	}
+	
+	return;
+}
+
+
+
+static void get_frame_userptrs_api(USBUVCState *s)
+{
+	struct v4l2_buffer buf;
+	memset(&buf, 0, sizeof(buf));
+	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	buf.memory = V4L2_MEMORY_USERPTR;
+	//printf("starting VIDIOC_DQBUF ioctl\n");
+	int ret_err = ioctl(s->v4l2_fd,  VIDIOC_DQBUF, &buf);
+	//printf("ended VIDIOC_DQBUF ioctl\n");
+	if(ret_err==-1)
+	{
+		switch(errno)
+		{
+			case EINVAL:
+				printf("VIDIOC_DQBUF einval.\n");
+				break;
+			default:
+				printf("VIDIOC_DQBUF returned unknown error %d.\n", errno);
+				break;
+		}
+		printf("V4L2 IOCTL VIDIOC_DQBUF failed\n");
+		return;
+	}
+
+	char* ptr = (void*)buf.m.userptr;
+	//printf("got frame of length %d from DMA buf %d, starting at %p\n", buf.bytesused, buf.index, ptr);
+
+
+	buf.m.userptr = (unsigned long)s->frame_start+2;
+
+	s->frame_start = ptr - 2;
+	s->frame = s->frame_start;
+
+	int frame_length = buf.bytesused;
+
+	s->frame[0] = 2;
+	s->frame_id = s->frame_id ^ 1;
+	s->frame[1] = 0x82 | s->frame_id;
+	s->frame_remaining_bytes = frame_length+2;
+
+
+	//printf("starting VIDIOC_QBUF ioctl\n");
+	ret_err = ioctl(s->v4l2_fd,  VIDIOC_QBUF, &buf);
+	//printf("ended VIDIOC_QBUF ioctl\n");
+	if(ret_err==-1)
+	{
+		printf("VIDIOC_QBUF failed\n");
+		return;
+	}
+}
+
+static void get_frame_read(USBUVCState *s)
+{
+	switch (s->v4l2_api) {
+		case V4L2_API_READ:
+			return get_frame_read_api(s);
+		case V4L2_API_USERPTRS:
+			return get_frame_userptrs_api(s);
+		default:
+			DPRINTF("Unhandled api type %d\n", s->v4l2_api);
+	}
+}
+
+
+
+static void usb_uvc_handle_reset(USBDevice *dev)
+{
+	DPRINTF("Reset called\n");
+}
+
+
+
+static int assign_query_control(int fd, int cid, struct v4l2_queryctrl* queryctrl)
+{
+
+
+memset(queryctrl, 0, sizeof(struct v4l2_queryctrl));
+queryctrl->id = cid;
+
+if (-1 == ioctl(fd, VIDIOC_QUERYCTRL, queryctrl)) {
+    if (errno != EINVAL) {
+        perror("assign_query_control");
+        exit(EXIT_FAILURE);
+    } else {
+        printf("V4L2_CID_BRIGHTNESS is not supportedn");
+    }
+} else if (queryctrl->flags & V4L2_CTRL_FLAG_DISABLED) {
+    printf("V4L2_CID_BRIGHTNESS is not supportedn");
+} else {
+	return 0;
+}
+	return -1;
+}
+
+
+static void usb_uvc_handle_control(USBDevice *dev, USBPacket *p, int request, int value,
+								  int index, int length, uint8_t *data)
+{
+	int ret = 0;
+	USBUVCState *s = DO_UPCAST(USBUVCState, dev, dev);
+	
+	DPRINTF("Control called\n");
+	//	DPRINTF("Request: 0x%08X\n", request);
+	//	DPRINTF("Value: 0x%08X\n", value);
+	//	DPRINTF("Index: 0x%08X\n", index);
+	//	DPRINTF("Length: 0x%08X\n", length);
+
+      ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+      if (ret >= 0) {
+          DPRINTF("Control handled generically\n");
+          return;
+      }
+	ret = 0;
+
+	struct commit_control_msg {
+		uint16_t bmHint;
+		uint8_t bFormatIndex;
+		uint8_t bFrameIndex;
+		uint32_t dwFrameInterval;
+		uint16_t wKeyFrameRate;
+		uint16_t wPFrameRate;
+		uint16_t wCompQuality;
+		uint16_t wCompWindowSize;
+		uint16_t wDelay;
+		uint32_t dwMaxVideoFrameSize;
+		uint32_t dwMaxPayloadTransferSize;
+	} QEMU_PACKED;
+	QEMU_BUILD_BUG_ON(sizeof (struct commit_control_msg) != 26);
+
+	
+	switch(request)
+	{
+		case EndpointOutRequest | USB_REQ_CLEAR_FEATURE:
+			break;
+		case UVCGetVideoControl | USB_UVC_GET_CUR:
+			ret = 0;
+			
+			if((index&0xFF) == 0x01 && ((value&0xFF00) == 0x0100 || (value&0xFF00) == 0x0200))
+			{
+				DPRINTF("USB Request: Get video control current setting attribute for interface %d\n", index&0xFF);
+				if((value&0xFF00) == 0x0100)
+					DPRINTF("\tVS_PROBE_CONTROL\n");
+				else
+					DPRINTF("\tVS_COMMIT_CONTROL\n");
+				
+				if(length != 26)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 26 bytes\n", length);
+					goto fail;
+				}
+
+				struct commit_control_msg msg;
+				memset(&msg, 0, sizeof(msg));
+				memcpy(&msg, data, MIN(length, sizeof(msg)));
+
+
+				msg.dwFrameInterval = cpu_to_le32(666666);
+				msg.wKeyFrameRate = cpu_to_le16(1);
+				msg.wPFrameRate = cpu_to_le16(0);
+				msg.wCompQuality = cpu_to_le16(0);
+				msg.wCompWindowSize = cpu_to_le16(1);
+				msg.wDelay = cpu_to_le16(0x20);
+				msg.dwMaxVideoFrameSize = cpu_to_le32(s->frame_max_length);
+				msg.dwMaxPayloadTransferSize = cpu_to_le32(s->frame_max_length);
+				memcpy(data, &msg, sizeof(msg));
+				ret = sizeof(msg);
+			}
+			else if((index&0xFF00) == 0x0400 && (value&0xFF00) == 0x0100) // Setting input
+			{
+				DPRINTF("USB Request: Asking for current input\n");
+				if(length != 1)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 1 byte\n", length);
+					goto fail;
+				}
+				
+				data[0] = s->current_input;
+				ret = 1;
+			}
+			else if((index&0xFF00) == 0x0500 && (value&0xFF00) == 0x0200) // PU_BRIGHTNESS_CONTROL of PROCESSING_UNIT
+			{
+				DPRINTF("USB Resquest: Asking for current brightness\n");
+				if(length != 2)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 2 bytes\n", length);
+					goto fail;
+				}
+				
+				if (s->brightness.supported) {
+					printf("current brightness value is %d\n", s->brightness.current_value);
+					int16_t for_writing = cpu_to_le16(s->brightness.current_value);
+					memcpy(data, &for_writing, 2);
+					ret = 2;
+				}
+				else {
+					data[0] = 1;
+					data[1] = 0;
+					ret = 2;
+				}
+			}
+			else
+				goto fail;
+			break;
+		case UVCGetVideoControl | USB_UVC_GET_MIN:
+			ret = 0;
+			
+			if((index&0xFF) == 0x01 && ((value&0xFF00) == 0x0100 || (value&0xFF00) == 0x0200))
+			{
+				DPRINTF("USB Request: Get video control minimum setting attribute for interface %d\n", index&0xFF);
+				
+				if(length != 26)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 26 bytes\n", length);
+					goto fail;
+				}
+				
+				struct commit_control_msg msg;
+				memset(&msg, 0, sizeof(msg));
+				memcpy(&msg, data, MIN(length, sizeof(msg)));
+
+
+				msg.dwFrameInterval = cpu_to_le32(666666);
+				msg.wKeyFrameRate = cpu_to_le16(1);
+				msg.wPFrameRate = cpu_to_le16(0);
+				msg.wCompQuality = cpu_to_le16(0);
+				msg.wCompWindowSize = cpu_to_le16(1);
+				msg.wDelay = cpu_to_le16(0x20);
+				msg.dwMaxVideoFrameSize = cpu_to_le32(s->frame_max_length);
+				msg.dwMaxPayloadTransferSize = cpu_to_le32(s->frame_max_length);
+				memcpy(data, &msg, sizeof(msg));
+				ret = sizeof(msg);
+			}
+			else if((index&0xFF00) == 0x0400 && (value&0xFF00) == 0x0100) // Setting input
+			{
+				DPRINTF("USB Request: Asking for minimum input\n");
+				if(length != 1)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 1 byte\n", length);
+					goto fail;
+				}
+				
+				data[0] = 0;
+				ret = 1;
+			}
+			else if((index&0xFF00) == 0x0500 && (value&0xFF00) == 0x0200) // PU_BRIGHTNESS_CONTROL of PROCESSING_UNIT
+			{
+				DPRINTF("USB Resquest: Asking for minimum brightness\n");
+				if(length != 2)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 2 bytes\n", length);
+					goto fail;
+				}
+				
+				if (s->brightness.supported) {
+					printf("min brightness value is %d\n", s->brightness.minimum);
+					int16_t for_writing = cpu_to_le16(s->brightness.minimum);
+					memcpy(data, &for_writing, 2);
+					ret = 2;
+				}
+				else {
+					data[0] = 1;
+					data[1] = 0;
+					ret = 2;
+				}
+			}
+			else
+				goto fail;
+			break;
+		case UVCGetVideoControl | USB_UVC_GET_MAX:
+			if((index&0xFF) == 0x01 && ((value&0xFF00) == 0x0100 || (value&0xFF00) == 0x0200))
+			{
+				DPRINTF("USB Request: Get video control maximum setting attribute for interface %d\n", index&0xFF);
+				
+				if(length != 26)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 26 bytes\n", length);
+					goto fail;
+				}
+				
+				struct commit_control_msg msg;
+				memset(&msg, 0, sizeof(msg));
+				memcpy(&msg, data, MIN(length, sizeof(msg)));
+
+
+				msg.dwFrameInterval = cpu_to_le32(666666);
+				msg.wKeyFrameRate = cpu_to_le16(1);
+				msg.wPFrameRate = cpu_to_le16(0);
+				msg.wCompQuality = cpu_to_le16(0);
+				msg.wCompWindowSize = cpu_to_le16(1);
+				msg.wDelay = cpu_to_le16(0x20);
+				msg.dwMaxVideoFrameSize = cpu_to_le32(s->frame_max_length);
+				msg.dwMaxPayloadTransferSize = cpu_to_le32(s->frame_max_length);
+				memcpy(data, &msg, sizeof(msg));
+				ret = sizeof(msg);
+			}
+			else if((index&0xFF00) == 0x0400 && (value&0xFF00) == 0x0100) // Setting input
+			{
+				DPRINTF("USB Request: Asking maximum input\n");
+				if(length != 1)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 1 byte\n", length);
+					goto fail;
+				}
+				
+				data[0] = 1;
+				ret = 1;
+			}					
+			else if((index&0xFF00) == 0x0500 && (value&0xFF00) == 0x0200) // PU_BRIGHTNESS_CONTROL of PROCESSING_UNIT
+			{
+				DPRINTF("USB Resquest: Asking for maximum brightness\n");
+				if(length != 2)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 2 bytes\n", length);
+					goto fail;
+				}
+				
+				if (s->brightness.supported) {
+					printf("max brightness value is %d\n", s->brightness.maximum);
+					int16_t for_writing = cpu_to_le16(s->brightness.maximum);
+					memcpy(data, &for_writing, 2);
+					ret = 2;
+				}
+				else {
+					data[0] = 1;
+					data[1] = 0;
+					ret = 2;
+				}
+			}
+			else
+				goto fail;
+			break;
+		case UVCGetVideoControl | USB_UVC_GET_DEF:
+			if((index&0xFF) == 0x01 && ((value&0xFF00) == 0x0100 || (value&0xFF00) == 0x0200))
+			{
+				DPRINTF("USB Request: Get video control default setting attribute for interface %d\n", index&0xFF);
+				
+				if(length != 26)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 26 bytes\n", length);
+					goto fail;
+				}
+				
+				struct commit_control_msg msg;
+				memset(&msg, 0, sizeof(msg));
+				memcpy(&msg, data, MIN(length, sizeof(msg)));
+
+
+				msg.dwFrameInterval = cpu_to_le32(666666);
+				msg.wKeyFrameRate = cpu_to_le16(1);
+				msg.wPFrameRate = cpu_to_le16(0);
+				msg.wCompQuality = cpu_to_le16(0);
+				msg.wCompWindowSize = cpu_to_le16(1);
+				msg.wDelay = cpu_to_le16(0x20);
+				msg.dwMaxVideoFrameSize = cpu_to_le32(s->frame_max_length);
+				msg.dwMaxPayloadTransferSize = cpu_to_le32(s->frame_max_length);
+				memcpy(data, &msg, sizeof(msg));
+				ret = sizeof(msg);
+			}
+			else if((index&0xFF00) == 0x0400 && (value&0xFF00) == 0x0100) // Setting input
+			{
+				DPRINTF("USB Request: Asking for default input\n");
+				if(length != 1)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 1 byte\n", length);
+					goto fail;
+				}
+				
+				data[0] = 0;
+				ret = 1;
+			}
+			else if((index&0xFF00) == 0x0500 && (value&0xFF00) == 0x0200) // PU_BRIGHTNESS_CONTROL of PROCESSING_UNIT
+			{
+				DPRINTF("USB Resquest: Asking for default brightness\n");
+				if(length != 2)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 2 bytes\n", length);
+					goto fail;
+				}
+				if (s->brightness.supported) {
+					printf("default value is %d\n", s->brightness.default_value);
+					int16_t for_writing = cpu_to_le16(s->brightness.default_value);
+					memcpy(data, &for_writing, 2);
+					ret = 2;
+				}
+				else {
+					data[0] = 1;
+					data[1] = 0;
+					ret = 2;
+				}
+			}
+			else
+				goto fail;
+			break;
+		case UVCSetVideoControl | USB_UVC_SET_CUR:
+			DPRINTF("USB Request: Set video control setting attribute for interface %d\n", index&0xFF);
+			
+			ret = 0;
+			
+			if((index&0xFF) == 0x01 && ((value&0xFF00) == 0x0100 || (value&0xFF00) == 0x0200))
+			{
+				if((value&0xFF00) == 0x0100)
+					DPRINTF("\tVS_PROBE_CONTROL\n");
+				else
+					DPRINTF("\tVS_COMMIT_CONTROL\n");
+				
+				if(length != 26)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 26 bytes\n", length);
+					goto fail;
+				}
+
+				struct commit_control_msg msg;
+				memset(&msg, 0, sizeof(msg));
+				memcpy(&msg, data, MIN(length, sizeof(msg)));
+
+				
+				DPRINTF("\tbmHint = 0x%04X\n", le16_to_cpu(msg.bmHint));
+				DPRINTF("\tbFormatIndex = %d\n", msg.bFormatIndex);
+				DPRINTF("\tbFrameIndex = %d\n", msg.bFrameIndex);
+				DPRINTF("\tdwFrameInterval = 0x%08X\n", le32_to_cpu(msg.dwFrameInterval));
+				DPRINTF("\twKeyFrameRate = 0x%04X\n", le16_to_cpu(msg.wKeyFrameRate));
+				DPRINTF("\twPFrameRate = 0x%04X\n", le16_to_cpu(msg.wPFrameRate));
+				DPRINTF("\twCompQuality = 0x%04X\n", le16_to_cpu(msg.wCompQuality));
+				DPRINTF("\twCompWindowSize = 0x%04X\n", le16_to_cpu(msg.wCompWindowSize));
+				DPRINTF("\twDelay = 0x%04X\n", le16_to_cpu(msg.wDelay));
+				DPRINTF("\tdwMaxVideoFrameSize= 0x%08X\n", le32_to_cpu(msg.dwMaxVideoFrameSize));
+				DPRINTF("\tdwMaxPayloadTransferSize= 0x%08X\n", le32_to_cpu(msg.dwMaxPayloadTransferSize));
+				
+				
+				ret = 26;
+			}
+			else if((index&0xFF00) == 0x0400 && (value&0xFF00) == 0x0100) // Setting input
+			{
+				DPRINTF("Setting input to %d\n", data[0]);
+				if(length != 1)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 1 byte\n", length);
+					goto fail;
+				}
+				
+				s->current_input = data[0];
+				ret = 1;
+			}
+			else if((index&0xFF00) == 0x0500 && (value&0xFF00) == 0x0200) // PU_BRIGHTNESS_CONTROL of PROCESSING_UNIT
+			{
+				DPRINTF("USB Resquest: Setting brightness\n");
+				if(length != 2)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 2 bytes\n", length);
+					goto fail;
+				}
+
+
+				if (s->brightness.supported) {
+					int16_t brightness_field;
+					memcpy(&brightness_field, data, 2);
+					s->brightness.current_value = le16_to_cpu(brightness_field);
+				}
+				ret = 2;
+			}
+			else
+				goto fail;
+			break;
+		case UVCGetVideoControl | USB_UVC_GET_RES:
+			if((index&0xFF00) == 0x0500 && (value&0xFF00) == 0x0200) // PU_BRIGHTNESS_CONTROL of PROCESSING_UNIT
+			{
+				DPRINTF("USB Resquest: Asking for brightness resolution\n");
+				if(length != 2)
+				{
+					DPRINTF("USB Request: Requested %d bytes, expected 2 bytes\n", length);
+					goto fail;
+				}
+				
+				if (s->brightness.supported) {
+					printf("resolution value is %d\n", s->brightness.resolution);
+					int16_t for_writing = cpu_to_le16(s->brightness.resolution);
+					memcpy(data, &for_writing, 2);
+					ret = 2;
+				}
+				else {
+					data[0] = 1;
+					data[1] = 0;
+					ret = 2;
+				}
+			}
+			else
+				goto fail;
+			break;
+		case UVCGetVideoControl | USB_UVC_GET_INFO:
+			if(length != 1)
+			{
+				DPRINTF("USB Request: Requested %d bytes, expected 1 bytes\n", length);
+				goto fail;
+			}
+			if((index&0xFF00) == 0x0500 && (value&0xFF00) == 0x0200 && 0) // PU_BRIGHTNESS_CONTROL of PROCESSING_UNIT
+			{
+				data[0] = 0x3; //support GET & SET
+				ret = 1;
+			}
+			else {
+				data[0] = 0;
+				printf("reporting no support for index 0x%08x and value 0x%08x\n", index, value);
+				ret = 1;
+			}
+			break;
+		default:
+		fail:
+			DPRINTF("USB Request: Unhandled control request\n");
+			DPRINTF("\tRequest: 0x%08X\n", request);
+			DPRINTF("\tValue: 0x%08X\n", value);
+			DPRINTF("\tIndex: 0x%08X\n", index);
+			DPRINTF("\tLength: 0x%08X\n", length);
+			p->status = USB_RET_STALL;
+			return;
+    }
+	p->actual_length = ret;
+}
+
+static void usb_uvc_handle_data(USBDevice *dev, USBPacket *p)
+{
+	USBUVCState *s = DO_UPCAST(USBUVCState, dev, dev);
+	//DPRINTF("Data called\n");
+	//DPRINTF("Packet ID: %d\n", p->pid);
+	//DPRINTF("Device address: %d\n", p->devaddr);
+	//DPRINTF("Device endpoint: %d\n", p->ep->nr);
+	//DPRINTF("Data length: %d\n", p->len);
+	
+	switch (p->pid)
+	{
+		case USB_TOKEN_OUT:
+			DPRINTF("USB Data Out requested.\n");
+			break;
+		case USB_TOKEN_IN:
+			//printf("frame_remaining_bytes: %d\n", frame_remaining_bytes);
+			if(p->ep->nr == 1) // IN endpoint 1 (hardware button)
+			{
+				uint8_t buf[p->iov.size];
+				buf[0] = 2;
+				buf[1] = 1;
+				buf[2] = 0;
+				buf[3] = 0;
+				usb_packet_copy(p, buf, sizeof(buf));
+			}
+			else if(p->ep->nr == 2) // IN endpoint 2 (video data)
+			{
+				if(s->frame_remaining_bytes==0)
+				{
+					get_frame_read(s);
+				}
+				uint32_t len;
+				len = p->iov.size - p->actual_length;
+				//printf("iov has size %zu\n", len);
+				int to_copy = MIN(512, MIN(len, s->frame_remaining_bytes));
+				usb_packet_copy(p, s->frame, to_copy);
+				s->frame += to_copy;
+				s->frame_remaining_bytes -= to_copy;
+			}
+			else
+			{
+				DPRINTF("USB Data In requested.\n");
+				DPRINTF("Requested data from endpoint %02X\n", p->ep->nr);
+			}
+			break;
+	    default:
+			DPRINTF("Bad token: %d\n", p->pid);
+			//fail:
+			p->status = USB_RET_STALL;
+			break;
+    }
+	
+    return;
+}
+
+static void usb_uvc_unrealize(USBDevice *dev, Error **errp)
+{
+	USBUVCState *s = DO_UPCAST(USBUVCState, dev, dev);
+	DPRINTF("Unrealize called\n");
+	close(s->v4l2_fd);
+}
+
+
+
+
+
+
+uint8_t frame_descriptor[] = {
+	/* class-specific vs frame descriptor alternate 0 */
+	0x26,		/*	u8	bLength; */
+	0x24,		/*	u8	bDescriptorType; CS_INTERFACE */
+	0x07,		/*	u8	bDescriptorSubtype; VS_FRAME_MJPEG */
+	0x01,		/*	u8	bFrameIndex; */
+	0x01,		/*	u8	bmCapabilities; */
+	0x40, 0x01,	/*	u8	wWidth; 320 */
+	0xF0, 0x00,	/*	u8	wHeight; 240 */
+	0x00, 0xEC,
+	0x0D, 0x00,	/*	u32	dwMinBitRate; */
+	0x00, 0xEC,
+	0x0D, 0x00,	/*	u32	dwMaxBitRate; */
+	0x72, 0xCE,
+	0x00, 0x00,	/*	u32	dwMaxVideoFrameBufSize; */
+	0x2A, 0x2C,
+	0x0A, 0x00,	/*	u32	dwDefaultFrameInterval; */
+	0x00,		/*	u8	bFrameIntervalType;	*/
+	0x2A, 0x2C,
+	0x0A, 0x00,	/*	u32	dwMinFrameInterval; */
+	0x2A, 0x2C,
+	0x0A, 0x00,	/*	u32	dwMaxFrameInterval; */
+	0x00, 0x00,
+	0x00, 0x00,	/*	u32	dwFrameIntervalStep; */
+	};
+
+
+
+
+static int usb_uvc_try_userpointers_setup(USBUVCState *s)
+{
+struct v4l2_requestbuffers reqbuf;
+
+memset (&reqbuf, 0, sizeof (reqbuf));
+reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+reqbuf.memory = V4L2_MEMORY_USERPTR;
+reqbuf.count = 2;
+
+if (ioctl (s->v4l2_fd, VIDIOC_REQBUFS, &reqbuf) == -1) {
+    if (errno == EINVAL)
+        printf ("Video capturing or user pointer streaming is not supported\n");
+    else
+        perror ("VIDIOC_REQBUFS");
+
+    exit (EXIT_FAILURE);
+}
+
+
+	DPRINTF("Allocating memory for frames.\n");
+	s->frame_storage[0] = malloc(s->frame_max_length+2);
+	s->frame_storage[1] = malloc(s->frame_max_length+2);
+	s->frame_storage[2] = malloc(s->frame_max_length+2);
+
+
+
+
+        for (int i = 0; i < 2; ++i) {
+                struct v4l2_buffer buf;
+
+                memset(&buf,0,sizeof(buf));
+                buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+                buf.memory = V4L2_MEMORY_USERPTR;
+                buf.index = i;
+                buf.m.userptr = (unsigned long)s->frame_storage[i]+2;
+                buf.length = s->frame_max_length;
+
+
+			if (ioctl (s->v4l2_fd, VIDIOC_QBUF, &buf) == -1) {
+				perror ("VIDIOC_REQBUFS");
+			}
+        }
+
+	s->frame = s->frame_storage[2];
+
+
+
+	s->frame_start = s->frame;
+	s->frame_remaining_bytes = 0;
+	s->frame_id = 0;
+
+
+
+
+
+
+
+
+struct v4l2_streamparm parm;
+
+    parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+    parm.parm.capture.timeperframe.numerator = 1;
+    parm.parm.capture.timeperframe.denominator = 1;
+
+
+
+
+
+
+
+
+if (ioctl (s->v4l2_fd, VIDIOC_S_PARM, &parm) == -1) {
+    if (errno == EINVAL)
+        printf ("VIDIOC_S_PARM is not supported\\n");
+    else
+        perror ("VIDIOC_S_PARM");
+
+    exit (EXIT_FAILURE);
+}
+
+
+
+
+
+
+
+
+
+int cap_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+if (ioctl (s->v4l2_fd, VIDIOC_STREAMON, &cap_type) == -1) {
+    if (errno == EINVAL)
+        printf ("VIDIOC_STREAMON is not supported\\n");
+    else
+        perror ("VIDIOC_STREAMON");
+
+    exit (EXIT_FAILURE);
+}
+
+	return 0;
+}
+
+
+
+
+
+static void usb_uvc_realize(USBDevice *dev, Error **errp)
+{
+	struct v4l2_capability capabilities;
+	struct v4l2_input video_input;
+	struct v4l2_format v_format;
+	int video_input_index;
+	int ret_err;
+	
+	DPRINTF("Init called\n");
+	
+	USBUVCState *s = DO_UPCAST(USBUVCState, dev, dev);
+
+	usb_desc_create_serial(dev);
+	usb_desc_init(dev);
+	
+	s->current_input = 0;
+	s->height = 240;
+	s->width = 320;
+
+
+
+	*(uint16_t*)&frame_descriptor[5] = s->width;
+	*(uint16_t*)&frame_descriptor[7] = s->height;
+	
+	if (!s->v4l2_device) {
+        error_setg(errp, "V4L2 device specification needed.");
+        return;
+    }
+	else
+	{
+		DPRINTF("Trying to open %s\n.", s->v4l2_device);
+	}
+	
+	s->v4l2_fd = open(s->v4l2_device, O_RDWR);
+	
+	if(s->v4l2_fd==-1)
+	{
+		switch(errno)
+		{
+			case EACCES:
+				error_report("Access denied.");
+				break;
+			case EBUSY:
+				error_report("Device busy.");
+				break;
+			case ENXIO:
+				error_report("Device does not exist.");
+				break;
+			case ENOMEM:
+				error_report("Not enough memory to open device.");
+				break;
+			case EMFILE:
+				error_report("Process reached maximum files opened.");
+				break;
+			case ENFILE:
+				error_report("System reached maximum files opened.");
+				break;
+			default:
+				error_report("Unknown error %d opening device.", errno);
+				break;
+		}
+		error_setg(errp, "V4L2 device open failed.");
+		return;
+	}
+	
+	DPRINTF("Device opened correctly.\n");
+	
+	DPRINTF("Querying capabilities.\n");
+	
+	ret_err = ioctl(s->v4l2_fd, VIDIOC_QUERYCAP, &capabilities);
+	
+	if(ret_err==-1)
+	{
+		switch(errno)
+		{
+			case EINVAL:
+				error_report("Device is not V4L2 device.\n");
+				break;
+			default:
+				error_report("Device returned unknown error %d.\n", errno);
+				break;
+		}
+		error_setg(errp, "V4L2 IOCTL VIDIOC_QUERYCAP failed");
+		return;
+	}
+	
+	DPRINTF("Device driver: %s\n", capabilities.driver);
+	DPRINTF("Device name: %s\n", capabilities.card);
+	DPRINTF("Device bus: %s\n", capabilities.bus_info);
+	DPRINTF("Driver version: %u.%u.%u\n",(capabilities.version >> 16) & 0xFF,(capabilities.version >> 8) & 0xFF,	capabilities.version & 0xFF);
+	DPRINTF("Device capabilities: 0x%08X\n", capabilities.capabilities);
+
+
+
+
+
+	
+	DPRINTF("Enumerating video inputs.\n");
+	memset(&video_input, 0, sizeof(video_input));
+	video_input.index=0;
+	while((ioctl(s->v4l2_fd, VIDIOC_ENUMINPUT, &video_input)==0))
+	{
+		if(video_input.type == V4L2_INPUT_TYPE_CAMERA)
+		{
+			video_input_index = video_input.index;
+			break;
+		}
+		
+		video_input.index++;
+	}
+	
+	DPRINTF("Setting video input to index %d\n", video_input_index);
+	ret_err = ioctl(s->v4l2_fd, VIDIOC_S_INPUT, &video_input_index);
+	
+	if(ret_err==-1)
+	{
+		switch(errno)
+		{
+			case EINVAL:
+				error_report("Incorrect video input selected.\n");
+				break;
+			case EBUSY:
+				error_report("Input cannot be switched.\n");
+				break;
+			default:
+				error_report("Unknown error %d.\n", errno);
+				break;
+		}
+		error_setg(errp, "V4L2 IOCTL VIDIOC_S_INPUT failed");
+		return;
+	}
+	
+	ioctl(s->v4l2_fd, VIDIOC_G_INPUT, &ret_err);
+	
+	if(ret_err==video_input_index)
+		DPRINTF("Video input correctly set.\n");
+	else
+	{
+		error_report("Some error happened while setting video input.\n");
+		error_setg(errp, "V4L2 IOCTL VIDIOC_G_INPUT failed");
+		return;
+	}
+	
+	DPRINTF("Trying to set %zux%zu MJPEG.\n", s->width, s->height);
+	memset(&v_format, 0, sizeof(v_format));
+	v_format.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	v_format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
+
+
+	ret_err = ioctl (s->v4l2_fd, VIDIOC_G_FMT, &v_format);
+	if(ret_err == -1)
+	{
+		error_setg(errp, "V4L2 IOCTL VIDIOC_G_FMT failed %d", errno);
+		return;
+	}
+
+	v_format.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	v_format.fmt.pix.width       = s->width; 
+	v_format.fmt.pix.height      = s->height;
+	v_format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
+	v_format.fmt.pix.field       = V4L2_FIELD_INTERLACED;
+	
+	ret_err = ioctl (s->v4l2_fd, VIDIOC_S_FMT, &v_format);
+	
+	if(ret_err == -1)
+	{
+		switch(errno)
+		{
+			case EBUSY:
+				error_report("Device busy while changing format.\n");
+				break;
+			case EINVAL:
+				error_report("Invalid format.\n");
+				break;
+			default:
+				error_report("Unknown error %d while changing format.\n", errno);
+				break;
+		}
+		error_setg(errp, "V4L2 IOCTL VIDIOC_S_FMT failed");
+		return;
+	}
+
+	s->frame_max_length = v_format.fmt.pix.sizeimage;
+	
+	DPRINTF("Format correctly set.\n");
+
+	printf("%d %d\n", v_format.fmt.pix.width , v_format.fmt.pix.height);
+	DPRINTF("Maximum image size: %d bytes.\n", s->frame_max_length);
+
+
+	struct v4l2_queryctrl queryctrl;
+	if (assign_query_control(s->v4l2_fd, V4L2_CID_BRIGHTNESS, &queryctrl) == 0) {
+		s->brightness.supported = true;
+		s->brightness.minimum = queryctrl.minimum;
+		s->brightness.maximum = queryctrl.maximum;
+		s->brightness.resolution = queryctrl.step;
+		s->brightness.default_value = queryctrl.default_value;
+	}
+
+
+
+
+
+
+	if (!s->v4l2_api && capabilities.capabilities & V4L2_CAP_READWRITE) {
+		printf("Device supports read/write\n");
+		s->frame_storage[0] = malloc(s->frame_max_length+2);
+		s->frame_storage[1] = NULL;
+		s->frame_storage[2] = NULL;
+		s->frame = s->frame_storage[0];
+		s->frame_start = s->frame;
+		s->frame_remaining_bytes = 0;
+		s->frame_id = 0;
+		s->v4l2_api = V4L2_API_READ;
+	}
+
+
+	if (!s->v4l2_api && capabilities.capabilities & V4L2_CAP_STREAMING) {
+		printf("Device supports V4L2_CAP_STREAMING\n");
+		int retval = usb_uvc_try_userpointers_setup(s);
+		if (retval == 0) {
+			printf("Selecting  V4L2_API_USERPTRS\n");
+			s->v4l2_api = V4L2_API_USERPTRS;
+		}
+	}
+
+
+	if (!s->v4l2_api) {
+		error_setg(errp, "No possible V4L2 API found.");
+		return;
+	}
+}
+
+
+enum usb_audio_strings {
+    STRING_NULL,
+    STRING_MANUFACTURER,
+    STRING_PRODUCT,
+    STRING_SERIALNUMBER,
+};
+
+static const USBDescStrings usb_uvc_stringtable = {
+    [STRING_MANUFACTURER]       = "QEMU",
+    [STRING_PRODUCT]            = "QEMU USB VIDEO CLASS 2",
+    [STRING_SERIALNUMBER]       = "1",
+};
+
+
+static const USBDescIface video_control_iface =
+
+{
+        .bInterfaceNumber              = 0,
+        .bNumEndpoints                 = 1,
+        .bInterfaceClass               = 0x0e,
+        .bInterfaceSubClass            = 0x01,
+        .bInterfaceProtocol            = 0x00,
+        .iInterface                    = 0x02,
+        .ndesc                         = 6,
+        .descs = (USBDescOther[]) {
+             {
+		.data = (uint8_t[]) {
+			/* class specific vc interface descriptor */
+			0x0D,		/*  u8  cif_bLength; */
+			0x24,		/*  u8  cif_bDescriptorType; CS_INTERFACE */
+			0x01,		/*  u8  cif_bDescriptorSubType; VC_HEADER */
+			0x00, 0x01, /*  u16 cif_bcdUVC; 1.0 */
+			0x42, 0x00, /*  u16 cif_wTotalLength */
+			0x80, 0x8D, /*  u32 cif_dwClockFrequency; Deprecated, 6Mhz */
+			0x5B, 0x00,
+			0x01,		/*  u8  cif_bInCollection; */
+			0x01,		/*  u8  cif_baInterfaceNr; */
+                }
+            },{
+                .data = (uint8_t[]) {
+	/* input terminal descriptor */
+			0x11,		/*  u8  itd_bLength; */
+			0x24,		/*  u8  itd_bDescriptorType; CS_INTERFACE */
+			0x02,		/*  u8  itd_bDescriptorSubtype; VC_INPUT_TERMINAL */
+			0x01,		/*  u8  itd_bTerminalID; */
+			0x01, 0x02, /*  u16  itd_wTerminalType; ITT_CAMERA */
+			0x00,		/*  u8  itd_bAssocTerminal; No association */
+			0x00,		/*  u8  itd_iTerminal; Unused */
+			0x00, 0x00, /*  u16  itd_wObjectiveFocalLengthMin; No optical zoom */
+			0x00, 0x00, /*  u16  itd_wObjectiveFocalLengthMax; No optical zoom */
+			0x00, 0x00, /*  u16  itd_wOcularFocalLength; No optical zoom */
+			0x02,		/*  u8  itd_bControlSize; No controls implemented */
+			0x00, 0x00, /*  u16  itd_bmControls; No controls supported */
+                }
+            },{
+                .data = (uint8_t[]) {
+		0x08,		/*	u8	itd_bLength; */
+		0x24,		/*	u8	itd_bDescriptorType; CS_INTERFACE */
+		0x02,		/*	u8	itd_bDescriptorSubtype; VC_INPUT_TERMINAL */
+		0x02,		/*	u8	itd_bTerminalID; */
+		0x01, 0x04,	/*	u16	itd_wTerminalType; ITT_COMPOSITE */
+		0x00,		/*	u8	itd_bAssocTerminal; */
+		0x00,		/*	u8	itd_iTerminal; */
+                }
+            },{
+                .data = (uint8_t[]) {
+		/* output terminal descriptor */
+		0x09,		/*	u8	otd_bLength; */
+		0x24,		/*	u8	otd_bDescriptorType; CS_INTERFACE */
+		0x03,		/*	u8	otd_bDescriptorSubtype; VC_OUTPUT_TERMINAL */
+		0x03,		/*	u8	otd_bTerminalID; */
+		0x01, 0x01,	/*	u16	otd_wTerminalType; TT_STREAMING */
+		0x00,		/*	u8	otd_bAssocTerminal; No association */
+		0x05,		/*	u8	otd_bSourceID; */
+		0x00,		/*	u8	otd_iTerminal; */
+                }
+            },{
+                .data = (uint8_t[]) {
+		/* selector unit descriptor */
+		0x08,		/*	u8	sud_bLength; */
+		0x24,		/*	u8	sud_bDescriptorType; CS_INTERFACE */
+		0x04,		/*	u8	sud_bDescriptorSubtype; VC_SELECTOR_UNIT */
+		0x04,		/*	u8	sud_bUnitID; */
+		0x02,		/*	u8	sud_bNrInPins; */
+		0x01,		/*	u8	sud_baSourceID; */
+		0x02,
+		0x00,		/*	u8	sud_iSelector; */
+                }
+            },{
+                .data = (uint8_t[]) {
+		/* processing unit descriptor */
+		0x0B,		/*	u8	pud_bLength; */
+		0x24,		/*	u8	pud_bDescriptorType; CS_INTERFACE */
+		0x05,		/*	u8	pud_bDescriptorSubtype; VC_PROCESSING_UNIT */
+		0x05,		/*	u8	pud_bUnitID; */
+		0x04,		/*	u8	pud_bSourceID; */
+		0x00, 0x00,	/*	u16	pud_wMaxMultiplier; */
+		0x02,		/*	u8	pud_bControlSize; */
+		0x01, 0x00,	/*	u16	pud_bmControls; Brightness control supported */
+		0x00,		/*	u8	pud_iProcessing; */
+		}
+            }
+         },
+
+        .eps = (USBDescEndpoint[]) {
+            {
+                /* standard interrupt endpoint */
+                .bEndpointAddress      = 0x81,
+                .bmAttributes          = 0x03,
+                .wMaxPacketSize        = 0x08,
+                .bInterval             = 0xff,
+            },
+        },
+        
+    };
+
+
+
+
+
+
+static const USBDescIface desc_iface_fullspeed[] = {
+    {
+         /* standard vs interface descriptor alternate 0 */
+        .bInterfaceNumber              = 1,
+        .bNumEndpoints                 = 1,
+        .bInterfaceClass               = 0x0e,
+        .bInterfaceSubClass            = 0x02,
+        .bInterfaceProtocol            = 0x00,
+        .iInterface                    = 0x00,
+        .ndesc                         = 3,
+        .descs = (USBDescOther[]) {
+             {
+		.data = (uint8_t[]) {
+		/* class-specific vs header descriptor input alternate 0 */
+		0x0E,		/*	u8	bLength; */
+		0x24,		/*	u8	bDescriptorType; CS_INTERFACE */
+		0x01,		/*	u8	bDescriptorSubtype; VS_INPUT_HEADER */
+		0x01,		/*	u8	bNumFormats; */
+		0x46, 0x00,	/*	u8	wTotalLength; */
+		0x82,		/*	u8	bEndpointAddress; */
+		0x00,		/*	u8	bmInfo; */
+		0x03,		/*	u8	bTerminalLink; */
+		0x00,		/*	u8	bStillCaptureMethod; */
+		0x00,		/*	u8	bTriggerSupport; */
+		0x00,		/*	u8	bTriggerUsage; */
+		0x01,		/*	u8	bControlSize; */
+		0x00,		/*	u8	bmaControls; */
+                }
+            },{
+                .data = (uint8_t[]) {
+		/* class-specific vs format descriptor alternate 0 */
+		0x0B,		/*	u8	bLength; */
+		0x24,		/*	u8	bDescriptorType; CS_INTERFACE */
+		0x06,		/*	u8	bDescriptorSubtype; VS_FORMAT_MJPEG */
+		0x01,		/*	u8	bFormatIndex; */
+		0x01,		/*	u8	bNumFrameDescriptors; */
+		0x01,		/*	u8	bmFlags; */
+		0x01,		/*	u8	bDefaultFrameIndex; */
+		0x00,		/*	u8	bAspectRatioX; */
+		0x00,		/*	u8	bAspectRatioY; */
+		0x02,		/*	u8	bmInterlaceFlags; */
+		0x00,		/*	u8	bCopyProtect; */
+                }
+            },{
+                .data = frame_descriptor
+            }
+         },
+
+        .eps = (USBDescEndpoint[]) {
+            {
+                /* standard vs isochronous video data endpoint descriptor */
+                .bEndpointAddress      = 0x82,
+                .bmAttributes          = 0x02,
+                .wMaxPacketSize        = 0x40,
+                .bInterval             = 0xff,
+            },
+        },
+    },
+    
+};
+
+
+
+
+static const USBDescIface desc_iface_highspeed[] = {
+    {
+         /* standard vs interface descriptor alternate 0 */
+        .bInterfaceNumber              = 1,
+        .bNumEndpoints                 = 1,
+        .bInterfaceClass               = 0x0e,
+        .bInterfaceSubClass            = 0x02,
+        .bInterfaceProtocol            = 0x00,
+        .iInterface                    = 0x00,
+        .ndesc                         = 3,
+        .descs = (USBDescOther[]) {
+             {
+		.data = (uint8_t[]) {
+		/* class-specific vs header descriptor input alternate 0 */
+		0x0E,		/*	u8	bLength; */
+		0x24,		/*	u8	bDescriptorType; CS_INTERFACE */
+		0x01,		/*	u8	bDescriptorSubtype; VS_INPUT_HEADER */
+		0x01,		/*	u8	bNumFormats; */
+		0x46, 0x00,	/*	u8	wTotalLength; */
+		0x82,		/*	u8	bEndpointAddress; */
+		0x00,		/*	u8	bmInfo; */
+		0x03,		/*	u8	bTerminalLink; */
+		0x00,		/*	u8	bStillCaptureMethod; */
+		0x00,		/*	u8	bTriggerSupport; */
+		0x00,		/*	u8	bTriggerUsage; */
+		0x01,		/*	u8	bControlSize; */
+		0x00,		/*	u8	bmaControls; */
+                }
+            },{
+                .data = (uint8_t[]) {
+		/* class-specific vs format descriptor alternate 0 */
+		0x0B,		/*	u8	bLength; */
+		0x24,		/*	u8	bDescriptorType; CS_INTERFACE */
+		0x06,		/*	u8	bDescriptorSubtype; VS_FORMAT_MJPEG */
+		0x01,		/*	u8	bFormatIndex; */
+		0x01,		/*	u8	bNumFrameDescriptors; */
+		0x01,		/*	u8	bmFlags; */
+		0x01,		/*	u8	bDefaultFrameIndex; */
+		0x00,		/*	u8	bAspectRatioX; */
+		0x00,		/*	u8	bAspectRatioY; */
+		0x02,		/*	u8	bmInterlaceFlags; */
+		0x00,		/*	u8	bCopyProtect; */
+                }
+            },{
+                .data = frame_descriptor
+            }
+         },
+
+        .eps = (USBDescEndpoint[]) {
+            {
+                /* standard vs isochronous video data endpoint descriptor */
+                .bEndpointAddress      = 0x82,
+                .bmAttributes          = USB_ENDPOINT_XFER_BULK,
+                .wMaxPacketSize        = 512,
+                .bInterval             = 0xff,
+            },
+        },
+    },
+    
+};
+
+
+
+
+
+static const USBDescIfaceAssoc desc_iface_groups[] = {
+  {
+/* interface association */
+    .bFirstInterface = 0x00,
+    .bInterfaceCount = 0x02,
+    .bFunctionClass = 0x0e,
+    .bFunctionSubClass = 0x03,
+    .bFunctionProtocol = 0x00,
+    .iFunction = 0x02,
+    .nif = 1,
+    .ifs = &video_control_iface,
+
+
+  },
+};
+
+
+static const USBDescDevice desc_device_highspeed = {
+    .bcdUSB                        = 0x0200,
+    .bMaxPacketSize0               = 64,
+    .bNumConfigurations            = 1,
+    .confs = (USBDescConfig[]) {
+        {
+            .bNumInterfaces        = 2,
+            .bConfigurationValue   = 1,
+            .iConfiguration        = 0,
+            .bmAttributes          = 0x80,
+            .bMaxPower             = 0xfa,
+            .nif_groups = ARRAY_SIZE(desc_iface_groups),
+            .if_groups = desc_iface_groups,
+            .nif = ARRAY_SIZE(desc_iface_highspeed),
+            .ifs = desc_iface_highspeed,
+        },
+    },
+};
+
+static const USBDescDevice desc_device_fullspeed = {
+    .bcdUSB                        = 0x0110,
+    .bMaxPacketSize0               = 64,
+    .bNumConfigurations            = 1,
+    .confs = (USBDescConfig[]) {
+        {
+            .bNumInterfaces        = 2,
+            .bConfigurationValue   = 1,
+            .iConfiguration        = 0,
+            .bmAttributes          = 0x80,
+            .bMaxPower             = 0xfa,
+            .nif_groups = ARRAY_SIZE(desc_iface_groups),
+            .if_groups = desc_iface_groups,
+            .nif = ARRAY_SIZE(desc_iface_fullspeed),
+            .ifs = desc_iface_fullspeed,
+        },
+    },
+};
+
+static const USBDesc desc_uvc = {
+    .id = {
+        .idVendor          = 0,
+        .idProduct         = 0,
+        .bcdDevice         = 0,
+        .iManufacturer     = STRING_MANUFACTURER,
+        .iProduct          = STRING_PRODUCT,
+        .iSerialNumber     = STRING_SERIALNUMBER,
+    },
+    .full = &desc_device_fullspeed,
+    //.high = &desc_device_highspeed,
+    .str  = usb_uvc_stringtable,
+};
+
+
+
+static Property usb_uvc_properties[] = {
+    DEFINE_PROP_STRING("device", USBUVCState, v4l2_device),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void usb_uvc_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    USBDeviceClass *k = USB_DEVICE_CLASS(klass);
+
+    dc->props         = usb_uvc_properties;
+    k->product_desc   = "QEMU USB Video Class Device";
+    k->usb_desc       = &desc_uvc;
+    k->realize        = usb_uvc_realize;
+    k->handle_reset   = usb_uvc_handle_reset;
+    k->handle_control = usb_uvc_handle_control;
+    k->handle_data    = usb_uvc_handle_data;
+    k->unrealize      = usb_uvc_unrealize;
+}
+
+static TypeInfo usb_uvc_info = {
+    .name          = "usb-uvc-webcam",
+    .parent        = TYPE_USB_DEVICE,
+    .instance_size = sizeof(USBUVCState),
+    .class_init    = usb_uvc_class_init,
+};
+
+static void usb_uvc_register_types(void)
+{
+    type_register_static(&usb_uvc_info);
+}
+
+type_init(usb_uvc_register_types)
-- 
2.17.1