hw/usb/Makefile.objs | 1 + hw/usb/usb-uvc.c | 1435 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1436 insertions(+) create mode 100644 hw/usb/usb-uvc.c
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
© 2016 - 2024 Red Hat, Inc.