This continues work started by Natalia Portillo <clau...@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 <nata...@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