From nobody Sat May 18 10:57:14 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org ARC-Seal: i=1; a=rsa-sha256; t=1623012590; cv=none; d=zohomail.com; s=zohoarc; b=c82MA+yxDRMlqIxBaBIFvFL9G0xBLRixvxISsIGGrSzKLBtz+UtTb8oJJYe7+/t5D+27C9rRgJKPe+vR8i0Kq/dlsl45jT1FX7+qOdCuuZfhkhy9iymrPPxXzq6VDEice/0WxGxf6ZIAPXw4PiW5h3n59sVIxA+la1h2aUQJcNI= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1623012590; h=Cc:Date:From:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:Message-ID:Sender:Subject:To; bh=t4q6MZOyxxYk0u9iMmh29yRxgNlSFO3FhnP8eMfZNw0=; b=EeL9mi6uxi9lZ7oRFXXG6sGD1kSXwQtWAXfuviTCf+xHQKZ84KV/3q4K5sCEzTPup0jR0bMLqgGQzvSu9Yq4VNMqcQq5oOLM8VQbgkplEEIXtslOqr5FnUB1GNAS3fRQba1mgI1a+bs9qdl+eCOVAOogZSIVmst4o4tQZRQTALY= ARC-Authentication-Results: i=1; mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1623012590537877.1338456524063; Sun, 6 Jun 2021 13:49:50 -0700 (PDT) Received: from localhost ([::1]:47688 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lpziO-0001dF-1L for importer@patchew.org; Sun, 06 Jun 2021 16:49:48 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:42644) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lpwcJ-0003X7-Ri for qemu-devel@nongnu.org; Sun, 06 Jun 2021 13:31:19 -0400 Received: from no.mail.very.legit.solutions ([116.202.25.44]:53880) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lpwc9-0007LO-FM for qemu-devel@nongnu.org; Sun, 06 Jun 2021 13:31:19 -0400 Received: by no.mail.very.legit.solutions (OpenSMTPD) with ESMTPSA id cb773df3 (TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO); Sun, 6 Jun 2021 17:31:02 +0000 (UTC) Received: by der.bie.st (OpenSMTPD) with ESMTPSA id fc0077c1 (TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO); Sun, 6 Jun 2021 17:30:59 +0000 (UTC) From: Raphael Lisicki To: qemu-devel@nongnu.org Subject: [PATCH RFC] USB Video Class device emulation. Date: Sun, 6 Jun 2021 19:30:58 +0200 Message-Id: <20210606173058.9890-1-raphael@ralisi.de> X-Mailer: git-send-email 2.17.1 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=116.202.25.44; envelope-from=raphael@ralisi.de; helo=no.mail.very.legit.solutions X-Spam_score_int: -18 X-Spam_score: -1.9 X-Spam_bar: - X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, NO_DNS_FOR_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Sun, 06 Jun 2021 16:48:55 -0400 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: claunia@claunia.com Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" This continues work started by Natalia Portillo , as submitted here: https://lists.nongnu.org/archive/html/qemu-devel/2010-06/ms= g01126.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) +=3D dev-audio.o common-obj-$(CONFIG_USB_SERIAL) +=3D dev-serial.o common-obj-$(CONFIG_USB_NETWORK) +=3D dev-network.o common-obj-$(CONFIG_USB_BLUETOOTH) +=3D dev-bluetooth.o +common-obj-$(CONFIG_LINUX) +=3D usb-uvc.o =20 ifeq ($(CONFIG_USB_SMARTCARD),y) common-obj-y +=3D 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 + * + * 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 . + * + */ +#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 +#include +#include +#include "qapi/error.h" +// V4L2 ioctls +#include +#include + +#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 =3D 0, + V4L2_API_READ =3D 1, + V4L2_API_USERPTRS =3D 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 =3D s->frame_start; + int frame_length =3D read(s->v4l2_fd, s->frame+2, s->frame_max_length); +=09 + if(frame_length =3D=3D -1) + { + DPRINTF("Error while reading frame. errno %d\n", errno); + } + else + { + s->frame[0] =3D 2; + s->frame_id =3D s->frame_id ^ 1; + s->frame[1] =3D 0x82 | s->frame_id; + s->frame_remaining_bytes =3D frame_length+2; + DPRINTF("Got a frame of %d bytes.\n", frame_length); + } +=09 + return; +} + + + +static void get_frame_userptrs_api(USBUVCState *s) +{ + struct v4l2_buffer buf; + memset(&buf, 0, sizeof(buf)); + buf.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory =3D V4L2_MEMORY_USERPTR; + //printf("starting VIDIOC_DQBUF ioctl\n"); + int ret_err =3D ioctl(s->v4l2_fd, VIDIOC_DQBUF, &buf); + //printf("ended VIDIOC_DQBUF ioctl\n"); + if(ret_err=3D=3D-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 =3D (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 =3D (unsigned long)s->frame_start+2; + + s->frame_start =3D ptr - 2; + s->frame =3D s->frame_start; + + int frame_length =3D buf.bytesused; + + s->frame[0] =3D 2; + s->frame_id =3D s->frame_id ^ 1; + s->frame[1] =3D 0x82 | s->frame_id; + s->frame_remaining_bytes =3D frame_length+2; + + + //printf("starting VIDIOC_QBUF ioctl\n"); + ret_err =3D ioctl(s->v4l2_fd, VIDIOC_QBUF, &buf); + //printf("ended VIDIOC_QBUF ioctl\n"); + if(ret_err=3D=3D-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* qu= eryctrl) +{ + + +memset(queryctrl, 0, sizeof(struct v4l2_queryctrl)); +queryctrl->id =3D cid; + +if (-1 =3D=3D ioctl(fd, VIDIOC_QUERYCTRL, queryctrl)) { + if (errno !=3D 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 reque= st, int value, + int index, int length, uint8_t *data) +{ + int ret =3D 0; + USBUVCState *s =3D DO_UPCAST(USBUVCState, dev, dev); +=09 + 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 =3D usb_desc_handle_control(dev, p, request, value, index, lengt= h, data); + if (ret >=3D 0) { + DPRINTF("Control handled generically\n"); + return; + } + ret =3D 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) !=3D 26); + +=09 + switch(request) + { + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + break; + case UVCGetVideoControl | USB_UVC_GET_CUR: + ret =3D 0; + =09 + if((index&0xFF) =3D=3D 0x01 && ((value&0xFF00) =3D=3D 0x0100 || (value&= 0xFF00) =3D=3D 0x0200)) + { + DPRINTF("USB Request: Get video control current setting attribute for = interface %d\n", index&0xFF); + if((value&0xFF00) =3D=3D 0x0100) + DPRINTF("\tVS_PROBE_CONTROL\n"); + else + DPRINTF("\tVS_COMMIT_CONTROL\n"); + =09 + if(length !=3D 26) + { + DPRINTF("USB Request: Requested %d bytes, expected 26 bytes\n", lengt= h); + goto fail; + } + + struct commit_control_msg msg; + memset(&msg, 0, sizeof(msg)); + memcpy(&msg, data, MIN(length, sizeof(msg))); + + + msg.dwFrameInterval =3D cpu_to_le32(666666); + msg.wKeyFrameRate =3D cpu_to_le16(1); + msg.wPFrameRate =3D cpu_to_le16(0); + msg.wCompQuality =3D cpu_to_le16(0); + msg.wCompWindowSize =3D cpu_to_le16(1); + msg.wDelay =3D cpu_to_le16(0x20); + msg.dwMaxVideoFrameSize =3D cpu_to_le32(s->frame_max_length); + msg.dwMaxPayloadTransferSize =3D cpu_to_le32(s->frame_max_length); + memcpy(data, &msg, sizeof(msg)); + ret =3D sizeof(msg); + } + else if((index&0xFF00) =3D=3D 0x0400 && (value&0xFF00) =3D=3D 0x0100) /= / Setting input + { + DPRINTF("USB Request: Asking for current input\n"); + if(length !=3D 1) + { + DPRINTF("USB Request: Requested %d bytes, expected 1 byte\n", length); + goto fail; + } + =09 + data[0] =3D s->current_input; + ret =3D 1; + } + else if((index&0xFF00) =3D=3D 0x0500 && (value&0xFF00) =3D=3D 0x0200) /= / PU_BRIGHTNESS_CONTROL of PROCESSING_UNIT + { + DPRINTF("USB Resquest: Asking for current brightness\n"); + if(length !=3D 2) + { + DPRINTF("USB Request: Requested %d bytes, expected 2 bytes\n", length= ); + goto fail; + } + =09 + if (s->brightness.supported) { + printf("current brightness value is %d\n", s->brightness.current_valu= e); + int16_t for_writing =3D cpu_to_le16(s->brightness.current_value); + memcpy(data, &for_writing, 2); + ret =3D 2; + } + else { + data[0] =3D 1; + data[1] =3D 0; + ret =3D 2; + } + } + else + goto fail; + break; + case UVCGetVideoControl | USB_UVC_GET_MIN: + ret =3D 0; + =09 + if((index&0xFF) =3D=3D 0x01 && ((value&0xFF00) =3D=3D 0x0100 || (value&= 0xFF00) =3D=3D 0x0200)) + { + DPRINTF("USB Request: Get video control minimum setting attribute for = interface %d\n", index&0xFF); + =09 + if(length !=3D 26) + { + DPRINTF("USB Request: Requested %d bytes, expected 26 bytes\n", lengt= h); + goto fail; + } + =09 + struct commit_control_msg msg; + memset(&msg, 0, sizeof(msg)); + memcpy(&msg, data, MIN(length, sizeof(msg))); + + + msg.dwFrameInterval =3D cpu_to_le32(666666); + msg.wKeyFrameRate =3D cpu_to_le16(1); + msg.wPFrameRate =3D cpu_to_le16(0); + msg.wCompQuality =3D cpu_to_le16(0); + msg.wCompWindowSize =3D cpu_to_le16(1); + msg.wDelay =3D cpu_to_le16(0x20); + msg.dwMaxVideoFrameSize =3D cpu_to_le32(s->frame_max_length); + msg.dwMaxPayloadTransferSize =3D cpu_to_le32(s->frame_max_length); + memcpy(data, &msg, sizeof(msg)); + ret =3D sizeof(msg); + } + else if((index&0xFF00) =3D=3D 0x0400 && (value&0xFF00) =3D=3D 0x0100) /= / Setting input + { + DPRINTF("USB Request: Asking for minimum input\n"); + if(length !=3D 1) + { + DPRINTF("USB Request: Requested %d bytes, expected 1 byte\n", length); + goto fail; + } + =09 + data[0] =3D 0; + ret =3D 1; + } + else if((index&0xFF00) =3D=3D 0x0500 && (value&0xFF00) =3D=3D 0x0200) /= / PU_BRIGHTNESS_CONTROL of PROCESSING_UNIT + { + DPRINTF("USB Resquest: Asking for minimum brightness\n"); + if(length !=3D 2) + { + DPRINTF("USB Request: Requested %d bytes, expected 2 bytes\n", length= ); + goto fail; + } + =09 + if (s->brightness.supported) { + printf("min brightness value is %d\n", s->brightness.minimum); + int16_t for_writing =3D cpu_to_le16(s->brightness.minimum); + memcpy(data, &for_writing, 2); + ret =3D 2; + } + else { + data[0] =3D 1; + data[1] =3D 0; + ret =3D 2; + } + } + else + goto fail; + break; + case UVCGetVideoControl | USB_UVC_GET_MAX: + if((index&0xFF) =3D=3D 0x01 && ((value&0xFF00) =3D=3D 0x0100 || (value&= 0xFF00) =3D=3D 0x0200)) + { + DPRINTF("USB Request: Get video control maximum setting attribute for = interface %d\n", index&0xFF); + =09 + if(length !=3D 26) + { + DPRINTF("USB Request: Requested %d bytes, expected 26 bytes\n", lengt= h); + goto fail; + } + =09 + struct commit_control_msg msg; + memset(&msg, 0, sizeof(msg)); + memcpy(&msg, data, MIN(length, sizeof(msg))); + + + msg.dwFrameInterval =3D cpu_to_le32(666666); + msg.wKeyFrameRate =3D cpu_to_le16(1); + msg.wPFrameRate =3D cpu_to_le16(0); + msg.wCompQuality =3D cpu_to_le16(0); + msg.wCompWindowSize =3D cpu_to_le16(1); + msg.wDelay =3D cpu_to_le16(0x20); + msg.dwMaxVideoFrameSize =3D cpu_to_le32(s->frame_max_length); + msg.dwMaxPayloadTransferSize =3D cpu_to_le32(s->frame_max_length); + memcpy(data, &msg, sizeof(msg)); + ret =3D sizeof(msg); + } + else if((index&0xFF00) =3D=3D 0x0400 && (value&0xFF00) =3D=3D 0x0100) /= / Setting input + { + DPRINTF("USB Request: Asking maximum input\n"); + if(length !=3D 1) + { + DPRINTF("USB Request: Requested %d bytes, expected 1 byte\n", length); + goto fail; + } + =09 + data[0] =3D 1; + ret =3D 1; + } =09 + else if((index&0xFF00) =3D=3D 0x0500 && (value&0xFF00) =3D=3D 0x0200) /= / PU_BRIGHTNESS_CONTROL of PROCESSING_UNIT + { + DPRINTF("USB Resquest: Asking for maximum brightness\n"); + if(length !=3D 2) + { + DPRINTF("USB Request: Requested %d bytes, expected 2 bytes\n", length= ); + goto fail; + } + =09 + if (s->brightness.supported) { + printf("max brightness value is %d\n", s->brightness.maximum); + int16_t for_writing =3D cpu_to_le16(s->brightness.maximum); + memcpy(data, &for_writing, 2); + ret =3D 2; + } + else { + data[0] =3D 1; + data[1] =3D 0; + ret =3D 2; + } + } + else + goto fail; + break; + case UVCGetVideoControl | USB_UVC_GET_DEF: + if((index&0xFF) =3D=3D 0x01 && ((value&0xFF00) =3D=3D 0x0100 || (value&= 0xFF00) =3D=3D 0x0200)) + { + DPRINTF("USB Request: Get video control default setting attribute for = interface %d\n", index&0xFF); + =09 + if(length !=3D 26) + { + DPRINTF("USB Request: Requested %d bytes, expected 26 bytes\n", lengt= h); + goto fail; + } + =09 + struct commit_control_msg msg; + memset(&msg, 0, sizeof(msg)); + memcpy(&msg, data, MIN(length, sizeof(msg))); + + + msg.dwFrameInterval =3D cpu_to_le32(666666); + msg.wKeyFrameRate =3D cpu_to_le16(1); + msg.wPFrameRate =3D cpu_to_le16(0); + msg.wCompQuality =3D cpu_to_le16(0); + msg.wCompWindowSize =3D cpu_to_le16(1); + msg.wDelay =3D cpu_to_le16(0x20); + msg.dwMaxVideoFrameSize =3D cpu_to_le32(s->frame_max_length); + msg.dwMaxPayloadTransferSize =3D cpu_to_le32(s->frame_max_length); + memcpy(data, &msg, sizeof(msg)); + ret =3D sizeof(msg); + } + else if((index&0xFF00) =3D=3D 0x0400 && (value&0xFF00) =3D=3D 0x0100) /= / Setting input + { + DPRINTF("USB Request: Asking for default input\n"); + if(length !=3D 1) + { + DPRINTF("USB Request: Requested %d bytes, expected 1 byte\n", length); + goto fail; + } + =09 + data[0] =3D 0; + ret =3D 1; + } + else if((index&0xFF00) =3D=3D 0x0500 && (value&0xFF00) =3D=3D 0x0200) /= / PU_BRIGHTNESS_CONTROL of PROCESSING_UNIT + { + DPRINTF("USB Resquest: Asking for default brightness\n"); + if(length !=3D 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 =3D cpu_to_le16(s->brightness.default_value); + memcpy(data, &for_writing, 2); + ret =3D 2; + } + else { + data[0] =3D 1; + data[1] =3D 0; + ret =3D 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); + =09 + ret =3D 0; + =09 + if((index&0xFF) =3D=3D 0x01 && ((value&0xFF00) =3D=3D 0x0100 || (value&= 0xFF00) =3D=3D 0x0200)) + { + if((value&0xFF00) =3D=3D 0x0100) + DPRINTF("\tVS_PROBE_CONTROL\n"); + else + DPRINTF("\tVS_COMMIT_CONTROL\n"); + =09 + if(length !=3D 26) + { + DPRINTF("USB Request: Requested %d bytes, expected 26 bytes\n", lengt= h); + goto fail; + } + + struct commit_control_msg msg; + memset(&msg, 0, sizeof(msg)); + memcpy(&msg, data, MIN(length, sizeof(msg))); + + =09 + DPRINTF("\tbmHint =3D 0x%04X\n", le16_to_cpu(msg.bmHint)); + DPRINTF("\tbFormatIndex =3D %d\n", msg.bFormatIndex); + DPRINTF("\tbFrameIndex =3D %d\n", msg.bFrameIndex); + DPRINTF("\tdwFrameInterval =3D 0x%08X\n", le32_to_cpu(msg.dwFrameInter= val)); + DPRINTF("\twKeyFrameRate =3D 0x%04X\n", le16_to_cpu(msg.wKeyFrameRate)= ); + DPRINTF("\twPFrameRate =3D 0x%04X\n", le16_to_cpu(msg.wPFrameRate)); + DPRINTF("\twCompQuality =3D 0x%04X\n", le16_to_cpu(msg.wCompQuality)); + DPRINTF("\twCompWindowSize =3D 0x%04X\n", le16_to_cpu(msg.wCompWindowS= ize)); + DPRINTF("\twDelay =3D 0x%04X\n", le16_to_cpu(msg.wDelay)); + DPRINTF("\tdwMaxVideoFrameSize=3D 0x%08X\n", le32_to_cpu(msg.dwMaxVide= oFrameSize)); + DPRINTF("\tdwMaxPayloadTransferSize=3D 0x%08X\n", le32_to_cpu(msg.dwMa= xPayloadTransferSize)); + =09 + =09 + ret =3D 26; + } + else if((index&0xFF00) =3D=3D 0x0400 && (value&0xFF00) =3D=3D 0x0100) /= / Setting input + { + DPRINTF("Setting input to %d\n", data[0]); + if(length !=3D 1) + { + DPRINTF("USB Request: Requested %d bytes, expected 1 byte\n", length); + goto fail; + } + =09 + s->current_input =3D data[0]; + ret =3D 1; + } + else if((index&0xFF00) =3D=3D 0x0500 && (value&0xFF00) =3D=3D 0x0200) /= / PU_BRIGHTNESS_CONTROL of PROCESSING_UNIT + { + DPRINTF("USB Resquest: Setting brightness\n"); + if(length !=3D 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 =3D le16_to_cpu(brightness_field); + } + ret =3D 2; + } + else + goto fail; + break; + case UVCGetVideoControl | USB_UVC_GET_RES: + if((index&0xFF00) =3D=3D 0x0500 && (value&0xFF00) =3D=3D 0x0200) // PU_= BRIGHTNESS_CONTROL of PROCESSING_UNIT + { + DPRINTF("USB Resquest: Asking for brightness resolution\n"); + if(length !=3D 2) + { + DPRINTF("USB Request: Requested %d bytes, expected 2 bytes\n", length= ); + goto fail; + } + =09 + if (s->brightness.supported) { + printf("resolution value is %d\n", s->brightness.resolution); + int16_t for_writing =3D cpu_to_le16(s->brightness.resolution); + memcpy(data, &for_writing, 2); + ret =3D 2; + } + else { + data[0] =3D 1; + data[1] =3D 0; + ret =3D 2; + } + } + else + goto fail; + break; + case UVCGetVideoControl | USB_UVC_GET_INFO: + if(length !=3D 1) + { + DPRINTF("USB Request: Requested %d bytes, expected 1 bytes\n", length); + goto fail; + } + if((index&0xFF00) =3D=3D 0x0500 && (value&0xFF00) =3D=3D 0x0200 && 0) /= / PU_BRIGHTNESS_CONTROL of PROCESSING_UNIT + { + data[0] =3D 0x3; //support GET & SET + ret =3D 1; + } + else { + data[0] =3D 0; + printf("reporting no support for index 0x%08x and value 0x%08x\n", ind= ex, value); + ret =3D 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 =3D USB_RET_STALL; + return; + } + p->actual_length =3D ret; +} + +static void usb_uvc_handle_data(USBDevice *dev, USBPacket *p) +{ + USBUVCState *s =3D 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); +=09 + 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 =3D=3D 1) // IN endpoint 1 (hardware button) + { + uint8_t buf[p->iov.size]; + buf[0] =3D 2; + buf[1] =3D 1; + buf[2] =3D 0; + buf[3] =3D 0; + usb_packet_copy(p, buf, sizeof(buf)); + } + else if(p->ep->nr =3D=3D 2) // IN endpoint 2 (video data) + { + if(s->frame_remaining_bytes=3D=3D0) + { + get_frame_read(s); + } + uint32_t len; + len =3D p->iov.size - p->actual_length; + //printf("iov has size %zu\n", len); + int to_copy =3D MIN(512, MIN(len, s->frame_remaining_bytes)); + usb_packet_copy(p, s->frame, to_copy); + s->frame +=3D to_copy; + s->frame_remaining_bytes -=3D 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 =3D USB_RET_STALL; + break; + } +=09 + return; +} + +static void usb_uvc_unrealize(USBDevice *dev, Error **errp) +{ + USBUVCState *s =3D DO_UPCAST(USBUVCState, dev, dev); + DPRINTF("Unrealize called\n"); + close(s->v4l2_fd); +} + + + + + + +uint8_t frame_descriptor[] =3D { + /* 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 =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; +reqbuf.memory =3D V4L2_MEMORY_USERPTR; +reqbuf.count =3D 2; + +if (ioctl (s->v4l2_fd, VIDIOC_REQBUFS, &reqbuf) =3D=3D -1) { + if (errno =3D=3D EINVAL) + printf ("Video capturing or user pointer streaming is not supporte= d\n"); + else + perror ("VIDIOC_REQBUFS"); + + exit (EXIT_FAILURE); +} + + + DPRINTF("Allocating memory for frames.\n"); + s->frame_storage[0] =3D malloc(s->frame_max_length+2); + s->frame_storage[1] =3D malloc(s->frame_max_length+2); + s->frame_storage[2] =3D malloc(s->frame_max_length+2); + + + + + for (int i =3D 0; i < 2; ++i) { + struct v4l2_buffer buf; + + memset(&buf,0,sizeof(buf)); + buf.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory =3D V4L2_MEMORY_USERPTR; + buf.index =3D i; + buf.m.userptr =3D (unsigned long)s->frame_storage[i]+2; + buf.length =3D s->frame_max_length; + + + if (ioctl (s->v4l2_fd, VIDIOC_QBUF, &buf) =3D=3D -1) { + perror ("VIDIOC_REQBUFS"); + } + } + + s->frame =3D s->frame_storage[2]; + + + + s->frame_start =3D s->frame; + s->frame_remaining_bytes =3D 0; + s->frame_id =3D 0; + + + + + + + + +struct v4l2_streamparm parm; + + parm.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + + parm.parm.capture.timeperframe.numerator =3D 1; + parm.parm.capture.timeperframe.denominator =3D 1; + + + + + + + + +if (ioctl (s->v4l2_fd, VIDIOC_S_PARM, &parm) =3D=3D -1) { + if (errno =3D=3D EINVAL) + printf ("VIDIOC_S_PARM is not supported\\n"); + else + perror ("VIDIOC_S_PARM"); + + exit (EXIT_FAILURE); +} + + + + + + + + + +int cap_type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; +if (ioctl (s->v4l2_fd, VIDIOC_STREAMON, &cap_type) =3D=3D -1) { + if (errno =3D=3D 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; +=09 + DPRINTF("Init called\n"); +=09 + USBUVCState *s =3D DO_UPCAST(USBUVCState, dev, dev); + + usb_desc_create_serial(dev); + usb_desc_init(dev); +=09 + s->current_input =3D 0; + s->height =3D 240; + s->width =3D 320; + + + + *(uint16_t*)&frame_descriptor[5] =3D s->width; + *(uint16_t*)&frame_descriptor[7] =3D s->height; +=09 + if (!s->v4l2_device) { + error_setg(errp, "V4L2 device specification needed."); + return; + } + else + { + DPRINTF("Trying to open %s\n.", s->v4l2_device); + } +=09 + s->v4l2_fd =3D open(s->v4l2_device, O_RDWR); +=09 + if(s->v4l2_fd=3D=3D-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; + } +=09 + DPRINTF("Device opened correctly.\n"); +=09 + DPRINTF("Querying capabilities.\n"); +=09 + ret_err =3D ioctl(s->v4l2_fd, VIDIOC_QUERYCAP, &capabilities); +=09 + if(ret_err=3D=3D-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; + } +=09 + 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); + + + + + +=09 + DPRINTF("Enumerating video inputs.\n"); + memset(&video_input, 0, sizeof(video_input)); + video_input.index=3D0; + while((ioctl(s->v4l2_fd, VIDIOC_ENUMINPUT, &video_input)=3D=3D0)) + { + if(video_input.type =3D=3D V4L2_INPUT_TYPE_CAMERA) + { + video_input_index =3D video_input.index; + break; + } + =09 + video_input.index++; + } +=09 + DPRINTF("Setting video input to index %d\n", video_input_index); + ret_err =3D ioctl(s->v4l2_fd, VIDIOC_S_INPUT, &video_input_index); +=09 + if(ret_err=3D=3D-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; + } +=09 + ioctl(s->v4l2_fd, VIDIOC_G_INPUT, &ret_err); +=09 + if(ret_err=3D=3Dvideo_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; + } +=09 + DPRINTF("Trying to set %zux%zu MJPEG.\n", s->width, s->height); + memset(&v_format, 0, sizeof(v_format)); + v_format.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + v_format.fmt.pix.pixelformat =3D V4L2_PIX_FMT_MJPEG; + + + ret_err =3D ioctl (s->v4l2_fd, VIDIOC_G_FMT, &v_format); + if(ret_err =3D=3D -1) + { + error_setg(errp, "V4L2 IOCTL VIDIOC_G_FMT failed %d", errno); + return; + } + + v_format.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + v_format.fmt.pix.width =3D s->width;=20 + v_format.fmt.pix.height =3D s->height; + v_format.fmt.pix.pixelformat =3D V4L2_PIX_FMT_MJPEG; + v_format.fmt.pix.field =3D V4L2_FIELD_INTERLACED; +=09 + ret_err =3D ioctl (s->v4l2_fd, VIDIOC_S_FMT, &v_format); +=09 + if(ret_err =3D=3D -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 =3D v_format.fmt.pix.sizeimage; +=09 + 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) =3D= =3D 0) { + s->brightness.supported =3D true; + s->brightness.minimum =3D queryctrl.minimum; + s->brightness.maximum =3D queryctrl.maximum; + s->brightness.resolution =3D queryctrl.step; + s->brightness.default_value =3D queryctrl.default_value; + } + + + + + + + if (!s->v4l2_api && capabilities.capabilities & V4L2_CAP_READWRITE) { + printf("Device supports read/write\n"); + s->frame_storage[0] =3D malloc(s->frame_max_length+2); + s->frame_storage[1] =3D NULL; + s->frame_storage[2] =3D NULL; + s->frame =3D s->frame_storage[0]; + s->frame_start =3D s->frame; + s->frame_remaining_bytes =3D 0; + s->frame_id =3D 0; + s->v4l2_api =3D V4L2_API_READ; + } + + + if (!s->v4l2_api && capabilities.capabilities & V4L2_CAP_STREAMING) { + printf("Device supports V4L2_CAP_STREAMING\n"); + int retval =3D usb_uvc_try_userpointers_setup(s); + if (retval =3D=3D 0) { + printf("Selecting V4L2_API_USERPTRS\n"); + s->v4l2_api =3D 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 =3D { + [STRING_MANUFACTURER] =3D "QEMU", + [STRING_PRODUCT] =3D "QEMU USB VIDEO CLASS 2", + [STRING_SERIALNUMBER] =3D "1", +}; + + +static const USBDescIface video_control_iface =3D + +{ + .bInterfaceNumber =3D 0, + .bNumEndpoints =3D 1, + .bInterfaceClass =3D 0x0e, + .bInterfaceSubClass =3D 0x01, + .bInterfaceProtocol =3D 0x00, + .iInterface =3D 0x02, + .ndesc =3D 6, + .descs =3D (USBDescOther[]) { + { + .data =3D (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 =3D (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 =3D (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 =3D (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 =3D (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 =3D (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 =3D (USBDescEndpoint[]) { + { + /* standard interrupt endpoint */ + .bEndpointAddress =3D 0x81, + .bmAttributes =3D 0x03, + .wMaxPacketSize =3D 0x08, + .bInterval =3D 0xff, + }, + }, + =20 + }; + + + + + + +static const USBDescIface desc_iface_fullspeed[] =3D { + { + /* standard vs interface descriptor alternate 0 */ + .bInterfaceNumber =3D 1, + .bNumEndpoints =3D 1, + .bInterfaceClass =3D 0x0e, + .bInterfaceSubClass =3D 0x02, + .bInterfaceProtocol =3D 0x00, + .iInterface =3D 0x00, + .ndesc =3D 3, + .descs =3D (USBDescOther[]) { + { + .data =3D (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 =3D (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 =3D frame_descriptor + } + }, + + .eps =3D (USBDescEndpoint[]) { + { + /* standard vs isochronous video data endpoint descriptor = */ + .bEndpointAddress =3D 0x82, + .bmAttributes =3D 0x02, + .wMaxPacketSize =3D 0x40, + .bInterval =3D 0xff, + }, + }, + }, + =20 +}; + + + + +static const USBDescIface desc_iface_highspeed[] =3D { + { + /* standard vs interface descriptor alternate 0 */ + .bInterfaceNumber =3D 1, + .bNumEndpoints =3D 1, + .bInterfaceClass =3D 0x0e, + .bInterfaceSubClass =3D 0x02, + .bInterfaceProtocol =3D 0x00, + .iInterface =3D 0x00, + .ndesc =3D 3, + .descs =3D (USBDescOther[]) { + { + .data =3D (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 =3D (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 =3D frame_descriptor + } + }, + + .eps =3D (USBDescEndpoint[]) { + { + /* standard vs isochronous video data endpoint descriptor = */ + .bEndpointAddress =3D 0x82, + .bmAttributes =3D USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize =3D 512, + .bInterval =3D 0xff, + }, + }, + }, + =20 +}; + + + + + +static const USBDescIfaceAssoc desc_iface_groups[] =3D { + { +/* interface association */ + .bFirstInterface =3D 0x00, + .bInterfaceCount =3D 0x02, + .bFunctionClass =3D 0x0e, + .bFunctionSubClass =3D 0x03, + .bFunctionProtocol =3D 0x00, + .iFunction =3D 0x02, + .nif =3D 1, + .ifs =3D &video_control_iface, + + + }, +}; + + +static const USBDescDevice desc_device_highspeed =3D { + .bcdUSB =3D 0x0200, + .bMaxPacketSize0 =3D 64, + .bNumConfigurations =3D 1, + .confs =3D (USBDescConfig[]) { + { + .bNumInterfaces =3D 2, + .bConfigurationValue =3D 1, + .iConfiguration =3D 0, + .bmAttributes =3D 0x80, + .bMaxPower =3D 0xfa, + .nif_groups =3D ARRAY_SIZE(desc_iface_groups), + .if_groups =3D desc_iface_groups, + .nif =3D ARRAY_SIZE(desc_iface_highspeed), + .ifs =3D desc_iface_highspeed, + }, + }, +}; + +static const USBDescDevice desc_device_fullspeed =3D { + .bcdUSB =3D 0x0110, + .bMaxPacketSize0 =3D 64, + .bNumConfigurations =3D 1, + .confs =3D (USBDescConfig[]) { + { + .bNumInterfaces =3D 2, + .bConfigurationValue =3D 1, + .iConfiguration =3D 0, + .bmAttributes =3D 0x80, + .bMaxPower =3D 0xfa, + .nif_groups =3D ARRAY_SIZE(desc_iface_groups), + .if_groups =3D desc_iface_groups, + .nif =3D ARRAY_SIZE(desc_iface_fullspeed), + .ifs =3D desc_iface_fullspeed, + }, + }, +}; + +static const USBDesc desc_uvc =3D { + .id =3D { + .idVendor =3D 0, + .idProduct =3D 0, + .bcdDevice =3D 0, + .iManufacturer =3D STRING_MANUFACTURER, + .iProduct =3D STRING_PRODUCT, + .iSerialNumber =3D STRING_SERIALNUMBER, + }, + .full =3D &desc_device_fullspeed, + //.high =3D &desc_device_highspeed, + .str =3D usb_uvc_stringtable, +}; + + + +static Property usb_uvc_properties[] =3D { + DEFINE_PROP_STRING("device", USBUVCState, v4l2_device), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_uvc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + USBDeviceClass *k =3D USB_DEVICE_CLASS(klass); + + dc->props =3D usb_uvc_properties; + k->product_desc =3D "QEMU USB Video Class Device"; + k->usb_desc =3D &desc_uvc; + k->realize =3D usb_uvc_realize; + k->handle_reset =3D usb_uvc_handle_reset; + k->handle_control =3D usb_uvc_handle_control; + k->handle_data =3D usb_uvc_handle_data; + k->unrealize =3D usb_uvc_unrealize; +} + +static TypeInfo usb_uvc_info =3D { + .name =3D "usb-uvc-webcam", + .parent =3D TYPE_USB_DEVICE, + .instance_size =3D sizeof(USBUVCState), + .class_init =3D usb_uvc_class_init, +}; + +static void usb_uvc_register_types(void) +{ + type_register_static(&usb_uvc_info); +} + +type_init(usb_uvc_register_types) --=20 2.17.1