From patchwork Tue Jun 8 15:34:12 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Natalia Portillo X-Patchwork-Id: 55001 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 87DD4B7D7F for ; Wed, 9 Jun 2010 01:38:57 +1000 (EST) Received: from localhost ([127.0.0.1]:43301 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1OM0sR-00076v-Vc for incoming@patchwork.ozlabs.org; Tue, 08 Jun 2010 11:38:08 -0400 Received: from [140.186.70.92] (port=56086 helo=eggs.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1OM0om-0004VA-3m for qemu-devel@nongnu.org; Tue, 08 Jun 2010 11:34:22 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.69) (envelope-from ) id 1OM0oi-0004nb-SS for qemu-devel@nongnu.org; Tue, 08 Jun 2010 11:34:19 -0400 Received: from 149.red-80-37-155.staticip.rima-tde.net ([80.37.155.149]:55737 helo=mail.claunia.com) by eggs.gnu.org with esmtp (Exim 4.69) (envelope-from ) id 1OM0oh-0004mG-3y for qemu-devel@nongnu.org; Tue, 08 Jun 2010 11:34:16 -0400 Received: from localhost (localhost [127.0.0.1]) by mail.claunia.com (Postfix) with ESMTP id 8161E120FF2C for ; Tue, 8 Jun 2010 16:34:12 +0100 (WEST) X-Virus-Scanned: amavisd-new at claunia.com Received: from mail.claunia.com ([127.0.0.1]) by localhost (www.natibot.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 7GDthHylpyBG for ; Tue, 8 Jun 2010 16:34:12 +0100 (WEST) Received: from zeus.claunia.com (zeus.claunia.com [172.26.2.1]) by mail.claunia.com (Postfix) with ESMTPA id 64C66120FF26 for ; Tue, 8 Jun 2010 16:34:12 +0100 (WEST) From: Natalia Portillo Date: Tue, 8 Jun 2010 16:34:12 +0100 Message-Id: <4F6AB6D9-39AF-42C9-9CC9-7A87F244A663@claunia.com> To: qemu-devel Developers Mime-Version: 1.0 (Apple Message framework v1078) X-Mailer: Apple Mail (2.1078) X-detected-operating-system: by eggs.gnu.org: FreeBSD 6.x (1) Subject: [Qemu-devel] [RFC PATCH 1/2] USB Video Class device emulation. X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Signed-off-by: Natalia Portillo --- hw/usb-uvc.c | 1096 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 1096 insertions(+), 0 deletions(-) create mode 100644 hw/usb-uvc.c -- diff --git a/hw/usb-uvc.c b/hw/usb-uvc.c new file mode 100644 index 0000000..b711f51 --- /dev/null +++ b/hw/usb-uvc.c @@ -0,0 +1,1096 @@ +/* + * 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 "hw.h" +#include "console.h" +#include "usb.h" +#include "qemu-error.h" +#include +#include +#include +// 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 + +typedef struct USBUVCState { + USBDevice dev; + char current_input; + char *v4l2_device; +} USBUVCState; + +static int v4l2_fd; +static char *frame; +static char *frame_start; +static int frame_length; +static int frame_id; +static int first_bulk_packet; +static int frame_remaining_bytes; +static int frame_max_length; + +static const uint8_t qemu_uvc_dev_descriptor[] = { + 0x12, /* u8 bLength; */ + 0x01, /* u8 bDescriptorType; Device */ + 0x00, 0x02, /* u16 bcdUSB; v2.0 */ + + 0xEF, /* u8 bDeviceClass; */ + 0x02, /* u8 bDeviceSubClass; */ + 0x01, /* u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x08, /* u8 bMaxPacketSize0; 8 Bytes */ + + /* Vendor and product id are arbitrary. */ + 0x00, 0x00, /* u16 idVendor; */ + 0x00, 0x00, /* u16 idProduct; */ + 0x00, 0x00, /* u16 bcdDevice */ + + 0x01, /* u8 iManufacturer; */ + 0x02, /* u8 iProduct; */ + 0x00, /* u8 iSerialNumber; */ + 0x01 /* u8 bNumConfigurations; */ +}; + +static const uint8_t qemu_uvc_config_descriptor[] = { + + /* one configuration */ + 0x09, /* u8 bLength; */ + 0x02, /* u8 bDescriptorType; Configuration */ + 0xB7, 0x00, /* u16 wTotalLength; */ + 0x02, /* u8 bNumInterfaces; (2) */ + 0x01, /* u8 bConfigurationValue; */ + 0x00, /* u8 iConfiguration; */ + 0x80, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 0xFA, /* u8 MaxPower; */ + + /* interface association */ + 0x08, /* u8 ifa_bLength; */ + 0x0B, /* u8 ifa_bDescriptorType; Interface Association */ + 0x00, /* u8 ifa_bFirstInterface; */ + 0x02, /* u8 ifa_bInterfaceCount; */ + 0x0E, /* u8 ifa_bFunctionClass; CC_VIDEO */ + 0x03, /* u8 ifa_bFunctionSubClass; SS_VIDEO_INTERFACE_COLLECTION */ + 0x00, /* u8 ifa_bFunctionProtocol; unused */ + 0x02, /* u8 ifa_iFunction; */ + + /* video control interface */ + 0x09, /* u8 if_bLength; */ + 0x04, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + 0x00, /* u8 if_bAlternateSetting; */ + 0x01, /* u8 if_bNumEndpoints; */ + 0x0E, /* u8 if_bInterfaceClass; CC_VIDEO */ + 0x01, /* u8 if_bInterfaceSubClass; SC_VIDEOCONTROL */ + 0x00, /* u8 if_bInterfaceProtocol; unused */ + 0x02, /* u8 if_iInterface; */ + + /* 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; */ + + /* 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 */ + + 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; */ + + /* 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; */ + + /* 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; */ + + /* 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; */ + + /* standard interrupt endpoint */ + 0x07, /* u8 ep_bLenght; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* u8 ep_bmAttributes; Interrupt */ + 0x08, 0x00, /* u8 ep_wMaxPacketSize; 8 bytes */ + 0xFF, /* u8 ep_bInterval; 32ms */ + + /* class-specific interrupt endpoint */ + 0x05, /* u8 ep_bLenght; */ + 0x25, /* u8 ep_bDescriptorType; CS_ENDPOINT */ + 0x03, /* u8 ep_bmAttributes; EP_INTERRUPT */ + 0x08, 0x00, /* u8 ep_wMaxPacketSize; 8 bytes */ + + /* standard vs interface descriptor alternate 0 */ + 0x09, /* u8 bLength; */ + 0x04, /* u8 bDescriptorType; INTERFACE */ + 0x01, /* u8 bInterfaceNumber; */ + 0x00, /* u8 bAlternateSetting; */ + 0x01, /* u8 bNumEndpoints; */ + 0x0E, /* u8 bInterfaceClass; CC_VIDEO */ + 0x02, /* u8 bInterfaceSubClass; SC_VIDEO_STREAMING */ + 0x00, /* u8 bInterfaceProtocol; PC_PROTOCOL_UNDEFINED */ + 0x00, /* u8 iInterface; Unused */ + + /* 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; */ + + /* 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; */ + + /* 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; */ + + /* standard vs isochronous video data endpoint descriptor */ + 0x07, /* u8 bLength; */ + 0x05, /* u8 bDescriptorType; */ + 0x82, /* u8 bEndpointAddress; IN endpoint 2 */ + 0x02, /* u8 bmAttributes; Isochronous transfer, asynchronous sync */ + 0x40, 0x00, /* u16 wMaxPacketSize; 510 bytes */ + 0x00 /* u8 bInterval; */ +}; + +static void get_frame_read(void) +{ + DPRINTF("Getting frame.\n"); + frame = frame_start; + frame_length = read(v4l2_fd, frame, frame_max_length); + + if(frame_length == -1) + { + DPRINTF("Error while reading frame.\n"); + frame_length = 0; + } + else + { + frame_id = frame_id ^ 1; + first_bulk_packet = 1; + frame_remaining_bytes = frame_length; + DPRINTF("Got a frame of %d bytes.\n", frame_length); + } + + return; +} + +static void usb_uvc_handle_reset(USBDevice *dev) +{ + DPRINTF("Reset called\n"); +} + +static int usb_uvc_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + int ret = 0; + USBUVCState *s = (USBUVCState *)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); + + switch(request) + { + case DeviceRequest | USB_REQ_GET_STATUS: + DPRINTF("USB Request: Get Status\n"); + data[0] = (1 << USB_DEVICE_SELF_POWERED) | + (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP); + data[1] = 0x00; + ret = 2; + break; + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + DPRINTF("USB Request: Clear feature\n"); + if (value == USB_DEVICE_REMOTE_WAKEUP) { + DPRINTF("USB Request: Unset remote wakeup\n"); + dev->remote_wakeup = 0; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + DPRINTF("USB Request: Set feature\n"); + if (value == USB_DEVICE_REMOTE_WAKEUP) { + DPRINTF("USB Request: Set remote wakeup\n"); + dev->remote_wakeup = 1; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + DPRINTF("USB Request: Set address to 0x%08X\n", value); + dev->addr = value; + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + DPRINTF("USB Request: Get descriptor\n"); + switch(value >> 8) { + case USB_DT_DEVICE: + DPRINTF("USB Request: Get device descriptor\n"); + memcpy(data, qemu_uvc_dev_descriptor, + sizeof(qemu_uvc_dev_descriptor)); + ret = sizeof(qemu_uvc_dev_descriptor); + break; + case USB_DT_CONFIG: + DPRINTF("USB Request: Get configuration descriptor\n"); + memcpy(data, qemu_uvc_config_descriptor, + sizeof(qemu_uvc_config_descriptor)); + ret = sizeof(qemu_uvc_config_descriptor); + break; + case USB_DT_STRING: + DPRINTF("USB Request: Get device strings\n"); + switch(value & 0xff) { + case 0: + DPRINTF("USB Request: Get language IDs\n"); + /* language ids */ + data[0] = 4; + data[1] = 3; + data[2] = 0x09; + data[3] = 0x04; + ret = 4; + break; + case 1: + /* vendor description */ + DPRINTF("USB Request: Get vendor string\n"); + ret = set_usb_string(data, "QEMU " QEMU_VERSION); + break; + case 2: + /* product description */ + DPRINTF("USB Request: Get product string\n"); + ret = set_usb_string(data, "QEMU USB VIDEO CLASS 2"); + break; + case 3: + /* serial number */ + DPRINTF("USB Request: Get serial number string\n"); + ret = set_usb_string(data, "1"); + break; + default: + goto fail; + } + break; + default: + goto fail; + } + break; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + DPRINTF("USB Request: Get configuration\n"); + data[0] = 1; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + DPRINTF("USB Request: Set configuration\n"); + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + DPRINTF("USB Request: Get interface\n"); + data[0] = 0; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + DPRINTF("USB Request: Set interface\n"); + ret = 0; + break; + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + DPRINTF("USB Request: Clear endpoint\n"); + ret = 0; + break; + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + DPRINTF("USB Request: Set interface\n"); + ret = 0; + break; + /* Class specific requests. */ + 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; + } + + data[0] = 0; // bmHint + data[1] = 0; + data[2] = 1; // bFormatIndex + data[3] = 1; // bFrameIndex + data[4] = 0x2A; // dwFrameInterval + data[5] = 0x2C; + data[6] = 0x0A; + data[7] = 0x00; + data[8] = 0; // wKeyFrameRate + data[9] = 0; + data[10] = 0; // wPFrameRate + data[11] = 0; + data[12] = 0; // wCompQuality + data[13] = 0; + data[14] = 1; // wCompWindowSize + data[15] = 0; + data[16] = 0x20; // wDelay + data[17] = 0; + data[18] = 0x72; // dwMaxVideoFrameSize + data[19] = 0xCE; + data[20] = 0x00; + data[21] = 0x00; + data[22] = 0x72; // dwMaxPayloadTransferSize + data[23] = 0xCE; + data[24] = 0x00; + data[25] = 0x00; + ret = 26; + } + 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; + } + + 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; + } + + data[0] = 0; // bmHint + data[1] = 0; + data[2] = 1; // bFormatIndex + data[3] = 1; // bFrameIndex + data[4] = 0x2A; // dwFrameInterval + data[5] = 0x2C; + data[6] = 0x0A; + data[7] = 0x00; + data[8] = 0; // wKeyFrameRate + data[9] = 0; + data[10] = 0; // wPFrameRate + data[11] = 0; + data[12] = 0; // wCompQuality + data[13] = 0; + data[14] = 1; // wCompWindowSize + data[15] = 0; + data[16] = 0x20; // wDelay + data[17] = 0; + data[18] = 0x72; // dwMaxVideoFrameSize + data[19] = 0xCE; + data[20] = 0x00; + data[21] = 0x00; + data[22] = 0x72; // dwMaxPayloadTransferSize + data[23] = 0xCE; + data[24] = 0x00; + data[25] = 0x00; + ret = 26; + } + 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; + } + + 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; + } + + data[0] = 0; // bmHint + data[1] = 0; + data[2] = 1; // bFormatIndex + data[3] = 1; // bFrameIndex + data[4] = 0x2A; // dwFrameInterval + data[5] = 0x2C; + data[6] = 0x0A; + data[7] = 0x00; + data[8] = 0; // wKeyFrameRate + data[9] = 0; + data[10] = 0; // wPFrameRate + data[11] = 0; + data[12] = 0; // wCompQuality + data[13] = 0; + data[14] = 1; // wCompWindowSize + data[15] = 0; + data[16] = 0x20; // wDelay + data[17] = 0; + data[18] = 0x72; // dwMaxVideoFrameSize + data[19] = 0xCE; + data[20] = 0x00; + data[21] = 0x00; + data[22] = 0x72; // dwMaxPayloadTransferSize + data[23] = 0xCE; + data[24] = 0x00; + data[25] = 0x00; + ret = 26; + } + 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; + } + + 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; + } + + data[0] = 0; // bmHint + data[1] = 0; + data[2] = 1; // bFormatIndex + data[3] = 1; // bFrameIndex + data[4] = 0x2A; // dwFrameInterval + data[5] = 0x2C; + data[6] = 0x0A; + data[7] = 0x00; + data[8] = 0; // wKeyFrameRate + data[9] = 0; + data[10] = 0; // wPFrameRate + data[11] = 0; + data[12] = 0; // wCompQuality + data[13] = 0; + data[14] = 1; // wCompWindowSize + data[15] = 0; + data[16] = 0x20; // wDelay + data[17] = 0; + data[18] = 0x72; // dwMaxVideoFrameSize + data[19] = 0xCE; + data[20] = 0x00; + data[21] = 0x00; + data[22] = 0x72; // dwMaxPayloadTransferSize + data[23] = 0xCE; + data[24] = 0x00; + data[25] = 0x00; + ret = 26; + } + 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; + } + + 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; + } + + DPRINTF("\tbmHint = 0x%02X%02X\n", data[1], data[0]); + DPRINTF("\tbFormatIndex = %d\n", data[2]); + DPRINTF("\tbFrameIndex = %d\n", data[3]); + DPRINTF("\tdwFrameInterval = 0x%02X%02X%02X%02X\n", data[7], data[6], data[5], data[4]); + DPRINTF("\twKeyFrameRate = 0x%02X%02X\n", data[9], data[8]); + DPRINTF("\twPFrameRate = 0x%02X%02X\n", data[11], data[10]); + DPRINTF("\twCompQuality = 0x%02X%02X\n", data[13], data[12]); + DPRINTF("\twCompWindowSize = 0x%02X%02X\n", data[15], data[14]); + DPRINTF("\twDelay = 0x%02X%02X\n", data[17], data[16]); + DPRINTF("\tdwMaxVideoFrameSize= 0x%02X%02X%02X%02X\n", data[21], data[20], data[19], data[18]); + DPRINTF("\tdwMaxPayloadTransferSize= 0x%02X%02X%02X%02X\n", data[25], data[24], data[23], data[22]); + + frame = frame_start; + frame_remaining_bytes = frame_length; + first_bulk_packet = 1; + + 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, value stays the same\n"); + if(length != 2) + { + DPRINTF("USB Request: Requested %d bytes, expected 2 bytes\n", length); + goto fail; + } + + 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; + } + + data[0] = 1; + data[1] = 0; + ret = 2; + } + else + goto fail; + 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); + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static int usb_uvc_handle_data(USBDevice *dev, USBPacket *p) +{ + int ret = 0; + + //DPRINTF("Data called\n"); + //DPRINTF("Packet ID: %d\n", p->pid); + //DPRINTF("Device address: %d\n", p->devaddr); + //DPRINTF("Device endpoint: %d\n", p->devep); + //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: + if(p->devep == 1) // IN endpoint 1 (hardware button) + { + p->data[0] = 2; + p->data[1] = 1; + p->data[2] = 0; + p->data[3] = 0; + } + else if(p->devep == 2) // IN endpoint 2 (video data) + { + if(first_bulk_packet) + { + p->data[0] = 2; + p->data[1] = 0x82 | frame_id; + memcpy((p->data)+2,frame,62); + ret = 64; + first_bulk_packet=0; + frame = frame + 62; + frame_remaining_bytes = frame_remaining_bytes - 62; + } + else if(frame_remaining_bytes<64) + { + memcpy(p->data,frame,frame_remaining_bytes); + ret = frame_remaining_bytes; + get_frame_read(); + } + else if(frame_remaining_bytes==64) + { + memcpy(p->data,frame,frame_remaining_bytes); + ret = frame_remaining_bytes; + frame_remaining_bytes = 0; + } + else if(frame_remaining_bytes==0) + { + ret = 0; + get_frame_read(); + } + else + { + memcpy(p->data,frame,64); + frame = frame+64; + frame_remaining_bytes = frame_remaining_bytes-64; + ret = 64; + } + } + else + { + DPRINTF("USB Data In requested.\n"); + DPRINTF("Requested data from endpoint %02X\n", p->devep); + } + break; + default: + DPRINTF("Bad token: %d\n", p->pid); + //fail: + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void usb_uvc_handle_destroy(USBDevice *dev) +{ + DPRINTF("Destroy called\n"); + close(v4l2_fd); +} + +static int usb_uvc_initfn(USBDevice *dev) +{ + 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 = (USBUVCState *)dev; + + s->current_input = 0; + s->dev.speed = USB_SPEED_FULL; + + if (!s->v4l2_device) { + error_report("V4L2 device specification needed.\n"); + return -1; + } + else + { + DPRINTF("Trying to open %s\n.", s->v4l2_device); + } + + v4l2_fd = open(s->v4l2_device, O_RDWR); + + if(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; + } + + return -1; + } + + DPRINTF("Device opened correctly.\n"); + + DPRINTF("Querying capabilities.\n"); + + ret_err = ioctl(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; + } + + return -1; + } + + 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(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(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; + } + + return -1; + } + + ioctl(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"); + return -1; + } + + DPRINTF("Trying to set 320x240 MJPEG.\n"); + memset(&v_format, 0, sizeof(v_format)); + v_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v_format.fmt.pix.width = 320; + v_format.fmt.pix.height = 240; + v_format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; + v_format.fmt.pix.field = V4L2_FIELD_INTERLACED; + + ret_err = ioctl (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; + } + + return -1; + } + + frame_max_length = v_format.fmt.pix.sizeimage; + + DPRINTF("Format correctly set.\n"); + DPRINTF("Maximum image size: %d bytes.\n", frame_max_length); + + DPRINTF("Allocating memory for frames.\n"); + frame = malloc(frame_max_length); + frame_start = frame; + + frame_id = 1; + + get_frame_read(); + + return 0; +} + +static USBDevice *usb_uvc_init(const char *filename) +{ + USBDevice *dev; + + dev = usb_create(NULL /* FIXME */, "usb-uvc-webcam"); + qdev_init_nofail(&dev->qdev); + + DPRINTF("Filename: %s\n.", filename); + + if (!*filename) { + error_report("character device specification needed"); + return NULL; + } + + return dev; +} + +static struct USBDeviceInfo usb_uvc_info = { + .product_desc = "QEMU USB Video Class Device", + .qdev.name = "usb-uvc-webcam", + .qdev.desc = "QEMU USB Video Class Device", + .usbdevice_name = "uvc-webcam", + .usbdevice_init = usb_uvc_init, + .qdev.size = sizeof(USBUVCState), + .init = usb_uvc_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = usb_uvc_handle_reset, + .handle_control = usb_uvc_handle_control, + .handle_data = usb_uvc_handle_data, + .handle_destroy = usb_uvc_handle_destroy, + .qdev.props = (Property[]) { + DEFINE_PROP_STRING("device", USBUVCState, v4l2_device), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void usb_uvc_register_devices(void) +{ + usb_qdev_register(&usb_uvc_info); +} +device_init(usb_uvc_register_devices)