From nobody Fri Sep 20 22:16:20 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; 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; dmarc=fail(p=none dis=none) header.from=bytedance.com ARC-Seal: i=1; a=rsa-sha256; t=1640615458; cv=none; d=zohomail.com; s=zohoarc; b=hlUB3dos108+fSCJPLUptjb3GC2GVMU57BHkHjaPVBzrxJCICyVALNxHfndsMfdM7KBgOJQumUhkvDzojlSEMMr3I1DPTSvAgwmIzaPCRVZJv4anCn+fAY0Y9MLFK7CutFoCdsKD+qy9RXDdS5614pS4QS+3XXGrsdLYNBWw7/8= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1640615458; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=dbiVyScK7Ebz11C8E/2SekdrjorCd1xxRN+8KjKE9io=; b=i1GTorvI63Lf2iCPmuvK0y8f/i2B5iS5CQuhzGsPBbxdGQq+e89ksZBNKj9vyb4SGKpr6qGU875JrwsZssYJnr7Q/XoD27tG6djtZ4UKtcmy8OE0cTwm5kwvMnrC1ae0AjTRn9kUQMoyp6YVhTgs2NBCj9WsikCbSz8gzNsUVpE= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; 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; dmarc=fail header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1640615458421266.64866778865473; Mon, 27 Dec 2021 06:30:58 -0800 (PST) Received: from localhost ([::1]:40974 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1n1r1c-0004ls-Gs for importer@patchew.org; Mon, 27 Dec 2021 09:30:56 -0500 Received: from eggs.gnu.org ([209.51.188.92]:54844) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1n1qzO-0001in-0D for qemu-devel@nongnu.org; Mon, 27 Dec 2021 09:28:38 -0500 Received: from [2607:f8b0:4864:20::1033] (port=35643 helo=mail-pj1-x1033.google.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1n1qzI-00031u-Of for qemu-devel@nongnu.org; Mon, 27 Dec 2021 09:28:36 -0500 Received: by mail-pj1-x1033.google.com with SMTP id r16-20020a17090a0ad000b001b276aa3aabso5502045pje.0 for ; Mon, 27 Dec 2021 06:28:32 -0800 (PST) Received: from libai.bytedance.net ([153.254.110.96]) by smtp.gmail.com with ESMTPSA id p2sm15072916pgn.73.2021.12.27.06.28.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 27 Dec 2021 06:28:30 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bytedance-com.20210112.gappssmtp.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=dbiVyScK7Ebz11C8E/2SekdrjorCd1xxRN+8KjKE9io=; b=yCLvQMOacJKiuN6SZKKMHd7EEtUXE07aQUw7/602YXPegMOEKXsb7wDK5UVGO8KwYA FtldIluqhvoszjQOsDBRJ6izuftIrV6lM2Jn3nfqEeZ347G89sY6RVaHEBxoMTwXwOZC ZT+c2esL50mMZvqvzRpm+9lWZSTvNxXhf+EiP3nnYiWSA36wtFMduQtQuLUbTv5Wffz8 gkRKlhK8Wr2DBDYhR9LLojqpcaLiJh/C1y7gTMshqw4DLCQumWg5lYhDP6NfYnx0xE5B TMnifZvJhoVIeG8bTchcG81C6997Q+GFvx6tbston5YjEaLdoCMHSoX1oqQGHcfZHZOH erMQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=dbiVyScK7Ebz11C8E/2SekdrjorCd1xxRN+8KjKE9io=; b=OznfWCBEFXweqL5k3sKPQwdoO0uMRBrAmNzpK0rcP3OuLVt4tSBzFw99BNPbt2i0Dv ACp/Z6Vdi0QHQh8wQyN87koy+s/s/626GjQLvchCf/aBrHrJd9sCgqelhdQCXYUCX+LT PdCmN782BhpRy5X7+HXN0n5p7pZFHFI5jA+SbpKZ7H7wHi7Ku3sN3RCQsHruReWT1TTV tm/fkMqsu0wUEZJ0sbnlPVffyDYocKSNHxZPkEakXzcx9T4RrPkJWqasEwxTxcKbKh/b IOj0ZOpX++mUQu+pq5bSvlBKLVor9RvTOfHRnL0hcRvH8JdE3pYHeulnhZNf2zMmdOle 4Ngg== X-Gm-Message-State: AOAM532X2Zje3QC2QGxZOIvGUdX12Vy5vzyLf1DBFJl8b1vFoFef/Tn/ UFxXrJGt53NQhwvkaVdr2d3ukA== X-Google-Smtp-Source: ABdhPJwq73d55NYm/yQsgkyfeB4LWRGqhwOIKOqqa2VFK09OShWYOTW5UFFTU6gy9IRWFG5arh8vXA== X-Received: by 2002:a17:902:f284:b0:149:699d:49bf with SMTP id k4-20020a170902f28400b00149699d49bfmr11645812plc.11.1640615310781; Mon, 27 Dec 2021 06:28:30 -0800 (PST) From: zhenwei pi To: peter.maydell@linaro.org, richard.henderson@linaro.org, kraxel@redhat.com, eblake@redhat.com, pbonzini@redhat.com Subject: [PATCH 1/5] camera: Introduce camera subsystem and builtin driver Date: Mon, 27 Dec 2021 22:27:30 +0800 Message-Id: <20211227142734.691900-2-pizhenwei@bytedance.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20211227142734.691900-1-pizhenwei@bytedance.com> References: <20211227142734.691900-1-pizhenwei@bytedance.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Host-Lookup-Failed: Reverse DNS lookup failed for 2607:f8b0:4864:20::1033 (failed) 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=2607:f8b0:4864:20::1033; envelope-from=pizhenwei@bytedance.com; helo=mail-pj1-x1033.google.com X-Spam_score_int: -10 X-Spam_score: -1.1 X-Spam_bar: - X-Spam_report: (-1.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RDNS_NONE=0.793, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: qemu-devel@nongnu.org, zhenwei pi Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: pass (identity @bytedance-com.20210112.gappssmtp.com) X-ZM-MESSAGEID: 1640615591504000001 Content-Type: text/plain; charset="utf-8" Web camera is an important port of a desktop instance, QEMU supports USB passthrough and USB redirect to support for general cases. Several problems we have hit: 1, the heavy bandwidth of network. a 1080*720@30FPS(MJPEG) uses ~5MB/s. 2, Issues of USB passthrough, Ex USB reset from guest side triggers wrong state of host side. 3, extention features, Ex to limit FPS/width&height of a camera device by hypervisor. ... So introduce camera subsystem to QEMU, abstruct basic API to operate a camera device. Also introduce a builtin driver which draws pure color, rainbow and digital rain background, and shows information for guest side to debug by libcairo. The full picture of QEMU camera subsystem works like this: +----------+ +------------+ +---------------+ |UVC(ready)| |virtio(TODO)| |other HW device| +----------+ +------------+ +---------------+ | | | | +--------+ | +------------+ Camera +-----------------+ +----+---+ | +-----------------+---------------------+ | | | +---+---+ +-----+-----+ +-------------+ |builtin| |v4l2(ready)| |other drivers| +-------+ +-----------+ +-------------+ Signed-off-by: zhenwei pi --- camera/builtin.c | 717 ++++++++++++++++++++++++++++++++++++++++ camera/camera-int.h | 19 ++ camera/camera.c | 522 +++++++++++++++++++++++++++++ camera/meson.build | 16 + camera/trace-events | 24 ++ camera/trace.h | 1 + include/camera/camera.h | 238 +++++++++++++ meson.build | 20 +- meson_options.txt | 3 + qapi/camera.json | 84 +++++ qapi/meson.build | 1 + qapi/qapi-schema.json | 1 + qemu-options.hx | 10 + softmmu/vl.c | 4 + 14 files changed, 1659 insertions(+), 1 deletion(-) create mode 100644 camera/builtin.c create mode 100644 camera/camera-int.h create mode 100644 camera/camera.c create mode 100644 camera/meson.build create mode 100644 camera/trace-events create mode 100644 camera/trace.h create mode 100644 include/camera/camera.h create mode 100644 qapi/camera.json diff --git a/camera/builtin.c b/camera/builtin.c new file mode 100644 index 0000000000..26828e74d1 --- /dev/null +++ b/camera/builtin.c @@ -0,0 +1,717 @@ +/* + * Builtin camera backend implemention + * + * Copyright 2021 Bytedance, Inc. + * + * Authors: + * zhenwei pi + * + * This work is licensed under the terms of the GNU GPL, version 2 or late= r. + * See the COPYING file in the top-level directory. + */ +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "qom/object.h" +#include "qapi/error.h" +#include "qapi/visitor.h" +#include "qapi/qapi-visit-camera.h" +#include "camera/camera.h" +#include "camera-int.h" +#include "trace.h" + +#ifdef CONFIG_VNC_JPEG /* TODO shoud it rename to CONFIG_LIB_JPEG? */ +#include +#endif + +#include + +#define TYPE_CAMERA_BUILTIN TYPE_CAMERADEV"-builtin" + +#define CAMERA_BUILTIN_DEF_WIDTH 640 +#define CAMERA_BUILTIN_MAX_WIDTH 3840 +#define CAMERA_BUILTIN_MIN_WIDTH 160 +#define CAMERA_BUILTIN_DEF_HEIGHT 480 +#define CAMERA_BUILTIN_MAX_HEIGHT 2880 +#define CAMERA_BUILTIN_MIN_HEIGHT 120 +#define CAMERA_BUILTIN_DEF_FPS 10 +#define CAMERA_BUILTIN_MAX_FPS 60 +#define CAMERA_BUILTIN_MIN_FPS 1 + +/* mjpeg, yuv, rgb565 */ +#define CAMERA_BUILTIN_MAX_PIXFMT 3 + +enum AttributeIndex { + ATTRIBUTE_DEF, + ATTRIBUTE_MIN, + ATTRIBUTE_MAX, + ATTRIBUTE_CUR, + ATTRIBUTE_STEP, + + ATTRIBUTE_ALL +}; + +typedef struct CameraBuiltin { + QEMUCamera parent; + + /* opts */ + uint16_t width; + uint16_t height; + uint16_t fps; + bool debug; + bool mjpeg; + bool yuv; + bool rgb565; + ColorType bgcolor; + + /* state */ + QEMUTimer *frame_timer; + cairo_surface_t *surface; + cairo_t *cr; + size_t image_size; + uint8_t *image; + uint8_t pixbytes; + uint8_t npixfmts; + uint32_t pixfmts[CAMERA_BUILTIN_MAX_PIXFMT]; + uint32_t pixfmt; /* current in use */ + void *opaque; /* used by driver itself */ + + /* debug infomations */ + uint32_t sequence; + int32_t ctrl[QEMUCameraControlMax][ATTRIBUTE_ALL]; +} CameraBuiltin; + +DECLARE_INSTANCE_CHECKER(CameraBuiltin, CAMERA_BUILTIN_DEV, TYPE_CAMERA_BU= ILTIN) + +static inline uint8_t pixel_clip(int x) +{ + if (x > 255) { + return 255; + } else if (x < 0) { + return 0; + } + + return x; +} + +static void camera_builtin_rgb24_to_yuyv(uint8_t *rgb, uint8_t *yuv, int w= idth, + int height, uint8_t pixbytes) +{ + int r1, g1, b1, r2, g2, b2, y1, u1, y2, v1; + int x, y; + uint8_t *dst, *src; + uint32_t val; + + for (x =3D 0; x < height; x++) { + for (y =3D 0; y < width / 2; y++) { + src =3D rgb + x * width * pixbytes + y * pixbytes * 2; + val =3D le32_to_cpu(*(uint32_t *)src); + r1 =3D (val >> 16) & 0xff; + g1 =3D (val >> 8) & 0xff; + b1 =3D val & 0xff; + val =3D le32_to_cpu(*(uint32_t *)(src + pixbytes)); + r2 =3D (val >> 16) & 0xff; + g2 =3D (val >> 8) & 0xff; + b2 =3D val & 0xff; + + y1 =3D pixel_clip(((66 * r1 + 129 * g1 + 25 * b1 + 128) >> 8) = + 16); + u1 =3D pixel_clip((((-38 * r1 - 74 * g1 + 112 * b1 + 128) >> 8) + + ((-38 * r2 - 74 * g2 + 112 * b2 + 128) >> 8)= ) / 2 + + 128); + y2 =3D pixel_clip(((66 * r2 + 129 * g2 + 25 * b2 + 128) >> 8) = + 16); + v1 =3D pixel_clip((((112 * r1 - 94 * g1 - 18 * b1 + 128) >> 8) + + ((112 * r2 - 94 * g2 - 18 * b2 + 128) >> 8))= / 2 + + 128); + dst =3D yuv + x * width * 2 + y * 4; + *dst++ =3D y1; + *dst++ =3D u1; + *dst++ =3D y2; + *dst =3D v1; + } + } +} + +static void camera_builtin_draw_info(QEMUCamera *camera) +{ + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + char text[32]; + uint16_t fontsize =3D 20; + uint16_t y =3D fontsize; + QEMUCameraControlType t; + + cairo_set_source_rgb(builtin->cr, 1, 1, 1); + cairo_select_font_face(builtin->cr, "Georgia", CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_BOLD); + + cairo_set_font_size(builtin->cr, fontsize); + cairo_move_to(builtin->cr, 0, y); + sprintf(text, "Sequence %d", builtin->sequence++); + cairo_show_text(builtin->cr, text); + + for (t =3D 0; (t < QEMUCameraControlMax)&&(y <=3D builtin->height); t+= +) { + y +=3D fontsize; + cairo_move_to(builtin->cr, 0, y); + sprintf(text, "%s %d", QEMUCameraControlTypeString(t), + builtin->ctrl[t][ATTRIBUTE_CUR]); + cairo_show_text(builtin->cr, text); + } +} + +static void camera_builtin_draw_pure_color(QEMUCamera *camera, ColorType c= olor) +{ + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + uint8_t r =3D 0, g =3D 0, b =3D 0; + + switch ((int)color) { + case COLOR_TYPE_RED: + r =3D 1; + break; + case COLOR_TYPE_GREEN: + g =3D 1; + break; + case COLOR_TYPE_BLUE: + b =3D 1; + break; + } + + cairo_move_to(builtin->cr, 0, 0); + cairo_set_source_rgb(builtin->cr, r, g, b); + cairo_rectangle(builtin->cr, 0, 0, builtin->width, builtin->height); + cairo_fill(builtin->cr); +} + +static void camera_builtin_draw_rainbow(QEMUCamera *camera) +{ + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + uint16_t colors; + uint16_t lines =3D builtin->height / 7; + uint8_t rainbow[7][3] =3D { + {0xff, 0x00, 0x00}, /* red */ + {0xff, 0xa5, 0x00}, /* orange */ + {0xff, 0xff, 0x00}, /* yellow */ + {0x00, 0x80, 0x00}, /* green */ + {0x00, 0x00, 0xff}, /* blue */ + {0x4b, 0x00, 0x82}, /* indigo */ + {0xee, 0x82, 0xee}, /* violet */ + }; + uint8_t *addr; + + for (colors =3D 0 ; colors < 7; colors++) { + cairo_move_to(builtin->cr, 0, lines * colors); + addr =3D rainbow[colors]; + cairo_set_source_rgb(builtin->cr, addr[0] / (float)255, + addr[1] / (float)255, addr[2] / (float)255); + cairo_rectangle(builtin->cr, 0, lines * colors, builtin->width, li= nes); + cairo_fill(builtin->cr); + } +} + +static void camera_builtin_draw_digital_rain(QEMUCamera *camera) +{ +#define DIGITAL_RAIN_FONT 20 + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + int rain_rows =3D builtin->width / 2 / DIGITAL_RAIN_FONT; + int rain_len =3D builtin->height * 2 / DIGITAL_RAIN_FONT; + int i, j, x, y, asterisks, first, last; + char *addr, *base; + char text[2] =3D {0}; + int len =3D rain_len / 2; + + if (!builtin->opaque) { + builtin->opaque =3D g_malloc(rain_rows * rain_len); + memset(builtin->opaque, '*', rain_rows * rain_len); + } + + base =3D builtin->opaque; + + cairo_set_source_rgb(builtin->cr, 0, 0, 0); + cairo_rectangle(builtin->cr, 0, 0, builtin->width, builtin->height); + cairo_fill(builtin->cr); + + cairo_select_font_face(builtin->cr, "Georgia", CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size(builtin->cr, DIGITAL_RAIN_FONT); + for (i =3D 0; i < rain_rows; i++) { + addr =3D base + i * rain_len + len; + asterisks =3D 0; + + for (j =3D 0; (j < len) && (addr[j] =3D=3D '*'); j++) { + asterisks++; + } + + if (asterisks =3D=3D len) { +rerandom: + first =3D random() % len; + last =3D random() % len; + if ((first + len / 4) >=3D last) { + goto rerandom; + } + + for (j =3D first; j < last; j++) { + *(addr + j) =3D random() % 26 + 'A' + (random() % 2) * 32; + } + } + + addr =3D base + i * rain_len; + + for (j =3D 0; (j < len) && (addr[j] =3D=3D '*'); ) { + j++; + } + + if (j =3D=3D len) { + goto update_frame; + } + + cairo_set_source_rgb(builtin->cr, 1, 1, 1); /* first char of row */ + x =3D DIGITAL_RAIN_FONT * i * 2; + y =3D DIGITAL_RAIN_FONT * (len - j); + cairo_move_to(builtin->cr, x, y); + sprintf(text, "%c", addr[j]); + cairo_show_text(builtin->cr, text); + + for (j++; j < len; j++) { + if (addr[j] =3D=3D '*') { + continue; + } + x =3D DIGITAL_RAIN_FONT * i * 2; + y =3D DIGITAL_RAIN_FONT * (len - j); + cairo_set_source_rgb(builtin->cr, 0, 1, 0); + cairo_move_to(builtin->cr, x, y); + + sprintf(text, "%c", addr[j]); + cairo_show_text(builtin->cr, text); + } + +update_frame: + addr =3D base + i * rain_len; + memmove(addr, addr + 1, 2 * len - 1); + addr[2 * len - 1] =3D '*'; + } +} + +#ifdef CONFIG_VNC_JPEG +static void camera_builtin_jpeg_init_destination(j_compress_ptr cinfo) +{ + CameraBuiltin *builtin =3D cinfo->client_data; + + cinfo->dest->next_output_byte =3D builtin->image; + cinfo->dest->free_in_buffer =3D builtin->image_size; +} + +static void camera_builtin_jpeg_term_destination(j_compress_ptr cinfo) +{ + /* nothing to do, but avoid libjpeg to crash! */ +} + +static void camera_builtin_rgb24_to_jpeg_line(uint8_t *rgb, uint8_t *jpeg, + uint16_t width, uint8_t pixb= ytes) +{ + uint16_t x; + uint32_t val; + uint8_t *dst, *src; + + for (x =3D 0; x < width; x++) { + src =3D rgb + x * pixbytes; + val =3D le32_to_cpu(*(uint32_t *)src); + dst =3D jpeg + 3 * x; + *(dst++) =3D (val >> 16) & 0xff; /* R */ + *(dst++) =3D (val >> 8) & 0xff; /* G */ + *dst =3D val & 0xff; /* B */ + } +} + +static size_t camera_builtin_rgb24_to_jpeg(QEMUCamera *camera, uint8_t *rg= b, + uint8_t *jpeg, uint16_t width, + int height, uint8_t pixbytes) +{ + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + struct jpeg_destination_mgr manager =3D {0}; + JSAMPROW row_pointer[1]; + g_autofree uint8_t *linebuf =3D g_malloc(width * 3); + uint8_t *addr; + int quality =3D 50; + + cinfo.err =3D jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + cinfo.client_data =3D builtin; + cinfo.image_width =3D builtin->width; + cinfo.image_height =3D builtin->height; + cinfo.input_components =3D 3; + cinfo.in_color_space =3D JCS_RGB; + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE); + manager.init_destination =3D camera_builtin_jpeg_init_destination; + manager.term_destination =3D camera_builtin_jpeg_term_destination; + cinfo.dest =3D &manager; + row_pointer[0] =3D linebuf; + + jpeg_start_compress(&cinfo, true); + + while (cinfo.next_scanline < cinfo.image_height) { + addr =3D rgb + cinfo.next_scanline * width * pixbytes; + camera_builtin_rgb24_to_jpeg_line(addr, linebuf, width, pixbytes); + jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + + return builtin->image_size - cinfo.dest->free_in_buffer; +} +#else +static size_t camera_builtin_rgb24_to_jpeg(QEMUCamera *camera, uint8_t *rg= b, + uint8_t *jpeg, uint16_t width, + int height, uint8_t pixbytes) +{ + return -1; +} +#endif + +static void camera_builtin_frame_timer(void *opaque) +{ + QEMUCamera *camera =3D opaque; + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + uint8_t *image_addr =3D cairo_image_surface_get_data(builtin->surface); + size_t image_bytes =3D 0; + uint16_t w =3D builtin->width, h =3D builtin->height; + + /* 1, draw a frame by cairo */ + switch (builtin->bgcolor) { + case COLOR_TYPE_BLUE: + case COLOR_TYPE_GREEN: + case COLOR_TYPE_RED: + camera_builtin_draw_pure_color(camera, builtin->bgcolor); + break; + + case COLOR_TYPE_RAINBOW: + camera_builtin_draw_rainbow(camera); + break; + + case COLOR_TYPE_DIGITAL_RAIN: + camera_builtin_draw_digital_rain(camera); + break; + + case COLOR_TYPE__MAX: + default: + return; + } + + if (builtin->debug) { + camera_builtin_draw_info(camera); + } + + /* 2, convert to a suitable format */ + switch (builtin->pixfmt) { + case QEMU_CAMERA_PIX_FMT_MJPEG: + image_bytes =3D camera_builtin_rgb24_to_jpeg(camera, image_addr, + builtin->image, w, h, builtin->pixbytes); + image_addr =3D builtin->image; + break; + case QEMU_CAMERA_PIX_FMT_YUYV: + camera_builtin_rgb24_to_yuyv(image_addr, builtin->image, w, h, + builtin->pixbytes); + image_bytes =3D w * h * 2; + image_addr =3D builtin->image; + break; + case QEMU_CAMERA_PIX_FMT_RGB565: + /* no need to convert, just put builtin->surface to uplayer */ + image_bytes =3D w * h * 2; + break; + } + + /* 3, delivery to uplayer */ + qemu_camera_new_image(camera, image_addr, image_bytes); + + /* 4, modify timer for next frame */ + timer_mod(builtin->frame_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + NANOSECONDS_PER_SECOND / builtin->fps); + + trace_camera_builtin_timer(qemu_camera_id(camera)); +} + +static int camera_builtin_enum_pixel_format(QEMUCamera *camera, + uint32_t *pixfmts, int npixfmt, + Error **errp) +{ + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + int index, total =3D 0; + + for (index =3D 0; index < MIN(npixfmt, builtin->npixfmts); index++) { + pixfmts[total++] =3D builtin->pixfmts[index]; + } + + return total; +} + +static int camera_builtin_enum_frame_size(QEMUCamera *camera, uint32_t pix= fmt, + QEMUCameraFrameSize *frmszs, + int nfrmsz, Error **errp) +{ + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + QEMUCameraFrameSize *frmsz; + + if (nfrmsz < 1) { + return 0; + } + + frmsz =3D frmszs; + frmsz->pixel_format =3D pixfmt; + frmsz->type =3D QEMU_CAMERA_FRMSIZE_TYPE_DISCRETE; + frmsz->d.width =3D builtin->width; + frmsz->d.height =3D builtin->height; + + return 1; +} + +static int camera_builtin_enum_frame_interval(QEMUCamera *camera, + const QEMUCameraFormat *form= at, + QEMUCameraFrameInterval *frm= ivals, + int nfrmival, Error **errp) +{ + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + QEMUCameraFrameInterval *frmival; + + if (nfrmival < 1) { + return 0; + } + + if (format->width !=3D builtin->width || format->height !=3D builtin->= height) { + error_setg(errp, "%s: enum frame interval unsupported mismatched " + "width(%d)/height(%d)", TYPE_CAMERA_BUILTIN, format->wi= dth, + format->height); + return 0; + } + + frmival =3D frmivals; + frmival->pixel_format =3D format->pixel_format; + frmival->width =3D format->width; + frmival->height =3D format->height; + frmival->type =3D QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE; + frmival->d.numerator =3D 1; + frmival->d.denominator =3D builtin->fps; + + return 1; +} + +static int camera_builtin_set_frame_interval(QEMUCamera *camera, + const QEMUCameraFrameInterval *frmival, Error **errp) +{ + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + + if (frmival->width !=3D builtin->width + || frmival->height !=3D builtin->height) { + error_setg(errp, "%s: set frame interval unsupported mismatched " + "width(%d)/height(%d)", TYPE_CAMERA_BUILTIN, frmival->w= idth, + frmival->height); + return 0; + } + + builtin->pixfmt =3D frmival->pixel_format; + + return 0; +} + +static int camera_builtin_enum_control(QEMUCamera *camera, + QEMUCameraControl *controls, int ncontrols, Error **errp) +{ + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + QEMUCameraControl *control; + QEMUCameraControlType t; + + for (t =3D 0; t < QEMUCameraControlMax; t++) { + control =3D controls + t; + control->type =3D t; + control->cur =3D builtin->ctrl[t][ATTRIBUTE_CUR]; + control->def =3D builtin->ctrl[t][ATTRIBUTE_DEF]; + control->min =3D builtin->ctrl[t][ATTRIBUTE_MIN]; + control->max =3D builtin->ctrl[t][ATTRIBUTE_MAX]; + control->step =3D builtin->ctrl[t][ATTRIBUTE_STEP]; + } + + return t; +} + +static int camera_builtin_set_control(QEMUCamera *camera, + const QEMUCameraControl *control, Error **errp) +{ + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + + builtin->ctrl[control->type][ATTRIBUTE_CUR] =3D control->cur; + + return 0; +} + +#define CHECK_AND_GET_OPTS(x, y) \ + do { \ + if (builtinopts->has_##x) { \ + if (builtinopts->x > CAMERA_BUILTIN_MAX_##y \ + || builtinopts->x < CAMERA_BUILTIN_MIN_##y) { \ + error_setg(errp, "%s: unsupported %s(%d, %d)", \ + TYPE_CAMERA_BUILTIN, #x, \ + CAMERA_BUILTIN_MIN_##y, \ + CAMERA_BUILTIN_MAX_##y); \ + return; \ + } \ + builtin->x =3D builtinopts->x; \ + } else { \ + builtin->x =3D CAMERA_BUILTIN_DEF_##y; \ + } \ + } while (0) + +#define CHECK_AND_GET_VAL(x, def) \ + do { \ + if (builtinopts->has_##x) { \ + builtin->x =3D builtinopts->x; \ + } else { \ + builtin->x =3D def; \ + } \ + } while (0) + +static void camera_builtin_open(QEMUCamera *camera, Error **errp) +{ + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + CameraBuiltinOptions *builtinopts =3D &camera->dev->u.builtin; + + CHECK_AND_GET_OPTS(width, WIDTH); + CHECK_AND_GET_OPTS(height, HEIGHT); + CHECK_AND_GET_OPTS(fps, FPS); + CHECK_AND_GET_VAL(bgcolor, COLOR_TYPE_BLUE); + CHECK_AND_GET_VAL(debug, false); + CHECK_AND_GET_VAL(yuv, true); + CHECK_AND_GET_VAL(rgb565, true); +#ifdef CONFIG_VNC_JPEG + CHECK_AND_GET_VAL(mjpeg, true); +#else + if (builtinopts->has_mjpeg && builtinopts->mjpeg) { + error_setg(errp, "%s: no libjpeg supported", TYPE_CAMERA_BUILTIN); + return; + } +#endif + + if (builtin->mjpeg) { + builtin->pixfmts[builtin->npixfmts++] =3D QEMU_CAMERA_PIX_FMT_MJPE= G; + } + + if (builtin->yuv) { + builtin->pixfmts[builtin->npixfmts++] =3D QEMU_CAMERA_PIX_FMT_YUYV; + } + + if (builtin->rgb565) { + builtin->pixfmts[builtin->npixfmts++] =3D QEMU_CAMERA_PIX_FMT_RGB5= 65; + } + + if (!builtin->npixfmts) { + error_setg(errp, "%s: all formats disabled", TYPE_CAMERA_BUILTIN); + } +} + +static void camera_builtin_stream_on(QEMUCamera *camera, Error **errp) +{ + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + cairo_format_t cairofmt; + size_t imagesize; + + imagesize =3D builtin->height * builtin->width * 2; + if (builtin->pixfmt =3D=3D QEMU_CAMERA_PIX_FMT_RGB565) { + cairofmt =3D CAIRO_FORMAT_RGB16_565; + builtin->pixbytes =3D 2; + } else { + cairofmt =3D CAIRO_FORMAT_RGB24; + builtin->pixbytes =3D 4; /* see enum cairo_format_t in cairo.h */ + } + builtin->surface =3D cairo_image_surface_create(cairofmt, builtin->wid= th, + builtin->height); + builtin->cr =3D cairo_create(builtin->surface); + qemu_camera_alloc_image(camera, imagesize, errp); + builtin->image_size =3D imagesize; + builtin->image =3D g_malloc(imagesize); + + builtin->frame_timer =3D timer_new_ns(QEMU_CLOCK_VIRTUAL, + camera_builtin_frame_timer, camera); + timer_mod(builtin->frame_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + NANOSECONDS_PER_SECOND / builtin->fps); +} + +static void camera_builtin_stream_off(QEMUCamera *camera, Error **errp) +{ + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + + timer_free(builtin->frame_timer); + + qemu_camera_free_image(camera); + g_free(builtin->image); + builtin->image =3D NULL; + builtin->sequence =3D 0; + + cairo_destroy(builtin->cr); + cairo_surface_destroy(builtin->surface); + + g_free(builtin->opaque); + builtin->opaque =3D NULL; +} + + +static void camera_builtin_init(Object *obj) +{ + QEMUCamera *camera =3D CAMERADEV(obj); + CameraBuiltin *builtin =3D CAMERA_BUILTIN_DEV(camera); + int i; + + /* + * Because builtin camera is designed for debug purpose only, so this = table + * does't keep align with the read camera, just to make the code easy. + */ + for (i =3D 0; i < QEMUCameraControlMax; i++) { + builtin->ctrl[i][ATTRIBUTE_DEF] =3D 0x7f; + builtin->ctrl[i][ATTRIBUTE_MIN] =3D 0; + builtin->ctrl[i][ATTRIBUTE_MAX] =3D 0xff; + builtin->ctrl[i][ATTRIBUTE_CUR] =3D 0; + builtin->ctrl[i][ATTRIBUTE_STEP] =3D 1; + } + + builtin->ctrl[QEMUCameraHueAuto][ATTRIBUTE_DEF] =3D 0; + builtin->ctrl[QEMUCameraHueAuto][ATTRIBUTE_MIN] =3D 0; + builtin->ctrl[QEMUCameraHueAuto][ATTRIBUTE_MAX] =3D 1; +} + +static void camera_builtin_finalize(Object *obj) +{ + QEMUCamera *camera =3D CAMERADEV(obj); + Error *local_err =3D NULL; + + camera_builtin_stream_off(camera, &local_err); +} + +static void camera_builtin_class_init(ObjectClass *oc, void *data) +{ + QEMUCameraClass *klass =3D CAMERADEV_CLASS(oc); + + klass->open =3D camera_builtin_open; + klass->stream_on =3D camera_builtin_stream_on; + klass->stream_off =3D camera_builtin_stream_off; + klass->enum_pixel_format =3D camera_builtin_enum_pixel_format; + klass->enum_frame_size =3D camera_builtin_enum_frame_size; + klass->enum_frame_interval =3D camera_builtin_enum_frame_interval; + klass->set_frame_interval =3D camera_builtin_set_frame_interval; + klass->enum_control =3D camera_builtin_enum_control; + klass->set_control =3D camera_builtin_set_control; +} + +static const TypeInfo camera_builtin_type_info =3D { + .name =3D TYPE_CAMERA_BUILTIN, + .parent =3D TYPE_CAMERADEV, + .instance_size =3D sizeof(CameraBuiltin), + .instance_init =3D camera_builtin_init, + .instance_finalize =3D camera_builtin_finalize, + .class_init =3D camera_builtin_class_init, +}; + +static void register_types(void) +{ + type_register_static(&camera_builtin_type_info); +} + +type_init(register_types); diff --git a/camera/camera-int.h b/camera/camera-int.h new file mode 100644 index 0000000000..f3f67ac43e --- /dev/null +++ b/camera/camera-int.h @@ -0,0 +1,19 @@ +/* + * QEMU Camera subsystem + * + * Copyright 2021 Bytedance, Inc. + * + * Authors: + * zhenwei pi + * + * This work is licensed under the terms of the GNU GPL, version 2 or late= r. + * See the COPYING file in the top-level directory. + */ +#ifndef QEMU_CAMERA_INT_H +#define QEMU_CAMERA_INT_H + +void qemu_camera_alloc_image(QEMUCamera *camera, size_t size, Error **errp= ); +void qemu_camera_free_image(QEMUCamera *camera); +void qemu_camera_new_image(QEMUCamera *camera, const void *addr, size_t si= ze); + +#endif /* QEMU_CAMERA_INT_H */ diff --git a/camera/camera.c b/camera/camera.c new file mode 100644 index 0000000000..0bbc60b8fc --- /dev/null +++ b/camera/camera.c @@ -0,0 +1,522 @@ +/* + * QEMU camera subsystem + * + * Copyright 2021 Bytedance, Inc. + * + * Authors: + * zhenwei pi + * + * This work is licensed under the terms of the GNU GPL, version 2 or late= r. + * See the COPYING file in the top-level directory. + */ +#include "qemu/osdep.h" +#include "qemu/help_option.h" +#include "qemu/iov.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/qemu-print.h" +#include "qom/object.h" +#include "qapi/error.h" +#include "qapi/visitor.h" +#include "qapi/qobject-input-visitor.h" +#include "qapi/qapi-visit-camera.h" +#include "camera/camera.h" +#include "camera-int.h" +#include "trace.h" + +static QLIST_HEAD(, QEMUCamera) qemu_cameras; + +const char *qemu_camera_id(QEMUCamera *camera) +{ + if (camera->dev && camera->dev->id) { + return camera->dev->id; + } + + return ""; +} + +QEMUCamera *qemu_camera_by_id(const char *id) +{ + QEMUCamera *camera; + + if (!id) { + return NULL; + } + + QLIST_FOREACH(camera, &qemu_cameras, list) { + if (!strcmp(qemu_camera_id(camera), id)) { + return camera; + } + } + + return NULL; +} + +static const QEMUCameraClass *camera_get_class(const char *typename, + Error **errp) +{ + ObjectClass *oc; + + oc =3D module_object_class_by_name(typename); + + if (!object_class_dynamic_cast(oc, TYPE_CAMERADEV)) { + error_setg(errp, "%s: missing %s implementation", + TYPE_CAMERADEV, typename); + return NULL; + } + + if (object_class_is_abstract(oc)) { + error_setg(errp, "%s: %s is abstract type", TYPE_CAMERADEV, typena= me); + return NULL; + } + + return CAMERADEV_CLASS(oc); +} + +static QEMUCamera *qemu_camera_new(Cameradev *dev, Error **errp) +{ + Object *obj; + QEMUCamera *camera =3D NULL; + g_autofree char *typename =3D NULL; + Error *local_err =3D NULL; + QEMUCameraClass *klass; + const char *driver =3D CameradevDriver_str(dev->driver); + + typename =3D g_strdup_printf("%s-%s", TYPE_CAMERADEV, driver); + if (!camera_get_class(typename, errp)) { + return NULL; + } + + obj =3D object_new(typename); + if (!obj) { + return NULL; + } + + camera =3D CAMERADEV(obj); + camera->dev =3D dev; + + klass =3D CAMERADEV_GET_CLASS(camera); + if (klass->open) { + klass->open(camera, &local_err); + if (local_err) { + error_propagate(errp, local_err); + goto error; + } + } + + QLIST_INSERT_HEAD(&qemu_cameras, camera, list); + trace_qemu_camera_new(qemu_camera_id(camera), typename); + + return camera; + +error: + if (obj) { + object_unref(obj); + } + + return NULL; +} + +typedef struct CameradevClassFE { + void (*fn)(const char *name, void *opaque); + void *opaque; +} CameradevClassFE; + +static void cameradev_class_foreach(ObjectClass *klass, void *opaque) +{ + CameradevClassFE *fe =3D opaque; + + assert(g_str_has_prefix(object_class_get_name(klass), TYPE_CAMERADEV"-= ")); + fe->fn(object_class_get_name(klass) + 10, fe->opaque); +} + +static void cameradev_name_foreach(void (*fn)(const char *name, void *opaq= ue), + void *opaque) +{ + CameradevClassFE fe =3D { .fn =3D fn, .opaque =3D opaque }; + + object_class_foreach(cameradev_class_foreach, TYPE_CAMERADEV, false, &= fe); +} + +static void help_string_append(const char *name, void *opaque) +{ + GString *str =3D opaque; + + g_string_append_printf(str, "\n %s", name); +} + +void qemu_camera_new_from_opts(const char *opt) +{ + Cameradev *dev; + + if (opt && is_help_option(opt)) { + GString *str =3D g_string_new(""); + + cameradev_name_foreach(help_string_append, str); + + qemu_printf("Available cameradev backend types: %s\n", str->str); + g_string_free(str, true); + return; + } + + Visitor *v =3D qobject_input_visitor_new_str(opt, "driver", &error_fat= al); + visit_type_Cameradev(v, NULL, &dev, &error_fatal); + visit_free(v); + + if (qemu_camera_by_id(dev->id)) { + error_setg(&error_fatal, "%s: id %s already existed", + TYPE_CAMERADEV, dev->id); + } + + if (!qemu_camera_new(dev, &error_fatal)) { + qapi_free_Cameradev(dev); + } +} + +void qemu_camera_del(QEMUCamera *camera) +{ + Error *local_err =3D NULL; + + trace_qemu_camera_del(qemu_camera_id(camera)); + + qemu_camera_stream_off(camera, &local_err); + QLIST_REMOVE(camera, list); + qapi_free_Cameradev(camera->dev); + object_unref(camera); +} + +int qemu_camera_enum_pixel_format(QEMUCamera *camera, uint32_t *pixfmts, + int npixfmt, Error **errp) +{ + QEMUCameraClass *klass =3D CAMERADEV_GET_CLASS(camera); + int ret, i; + + if (!klass->enum_pixel_format) { + error_setg(errp, "%s: %s missing enum pixel format implementation", + TYPE_CAMERADEV, qemu_camera_id(camera)); + return -ENOTSUP; + } + + ret =3D klass->enum_pixel_format(camera, pixfmts, npixfmt, errp); + if (ret > 0) { + for (i =3D 0; i < ret; i++) { + trace_qemu_camera_enum_pixel_format(qemu_camera_id(camera), + pixfmts[i]); + } + } else { + trace_qemu_camera_enum_pixel_format_ret(qemu_camera_id(camera), re= t); + } + + return ret; +} + +int qemu_camera_enum_frame_size(QEMUCamera *camera, uint32_t pixfmt, + QEMUCameraFrameSize *frmszs, int nfrmsz, + Error **errp) +{ + QEMUCameraClass *klass =3D CAMERADEV_GET_CLASS(camera); + int ret, i; + + if (!klass->enum_frame_size) { + error_setg(errp, "%s: %s missing enum frame size implementation", + TYPE_CAMERADEV, qemu_camera_id(camera)); + return -ENOTSUP; + } + + ret =3D klass->enum_frame_size(camera, pixfmt, frmszs, nfrmsz, errp); + if (ret > 0) { + QEMUCameraFrameSize *frmsz; + + for (i =3D 0; i < ret; i++) { + frmsz =3D frmszs + i; + if (frmsz->type =3D=3D QEMU_CAMERA_FRMSIZE_TYPE_DISCRETE) + trace_qemu_camera_enum_frame_size_d(qemu_camera_id(camera), + frmsz->pixel_format, frmsz->d.width, frmsz->d.height); + } + } else { + trace_qemu_camera_enum_frame_size_ret(qemu_camera_id(camera), ret); + } + + return ret; +} + +int qemu_camera_enum_frame_interval(QEMUCamera *camera, + const QEMUCameraFormat *format, + QEMUCameraFrameInterval *frmivals, + int nfrmival, Error **errp) +{ + QEMUCameraClass *klass =3D CAMERADEV_GET_CLASS(camera); + int ret, i; + + if (!klass->enum_frame_interval) { + error_setg(errp, "%s: %s missing enum frame interval implementatio= n", + TYPE_CAMERADEV, qemu_camera_id(camera)); + return -ENOTSUP; + } + + ret =3D klass->enum_frame_interval(camera, format, frmivals, nfrmival,= errp); + if (ret > 0) { + QEMUCameraFrameInterval *frmival; + + for (i =3D 0; i < ret; i++) { + frmival =3D frmivals + i; + if (frmival->type =3D=3D QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE) { + trace_qemu_camera_enum_frame_interval_d(qemu_camera_id(cam= era), + frmival->pixel_format, frmival->width, frmival->height, + frmival->d.numerator, frmival->d.denominator); + } + } + } else { + trace_qemu_camera_enum_frame_interval_ret(qemu_camera_id(camera), = ret); + } + + return ret; +} + +int qemu_camera_set_frame_interval(QEMUCamera *camera, + const QEMUCameraFrameInterval *frmival, + Error **errp) +{ + QEMUCameraClass *klass =3D CAMERADEV_GET_CLASS(camera); + int ret; + + if (!klass->set_frame_interval) { + error_setg(errp, "%s: %s missing set frame interval implementation= ", + TYPE_CAMERADEV, qemu_camera_id(camera)); + return -ENOTSUP; + } + + ret =3D klass->set_frame_interval(camera, frmival, errp); + if (frmival->type =3D=3D QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE) { + trace_qemu_camera_set_frame_interval(qemu_camera_id(camera), + frmival->pixel_format, frmival->width, frmival->height, + frmival->d.numerator, frmival->d.denominator, ret); + } + + return ret; +} + +int qemu_camera_enum_control(QEMUCamera *camera, QEMUCameraControl *contro= ls, + int ncontrols, Error **errp) +{ + QEMUCameraClass *klass =3D CAMERADEV_GET_CLASS(camera); + int ret, i; + + if (!klass->enum_control) { + error_setg(errp, "%s: %s missing enum control implementation", + TYPE_CAMERADEV, qemu_camera_id(camera)); + return -ENOTSUP; + } + + ret =3D klass->enum_control(camera, controls, ncontrols, errp); + if (ret > 0) { + QEMUCameraControl *control; + + for (i =3D 0; i < ret; i++) { + control =3D controls + i; + trace_qemu_camera_enum_control(qemu_camera_id(camera), + QEMUCameraControlTypeString(control->type), control->def, + control->min, control->max, control->step); + } + } else { + trace_qemu_camera_enum_control_ret(qemu_camera_id(camera), ret); + } + + return ret; +} + +int qemu_camera_set_control(QEMUCamera *camera, + const QEMUCameraControl *control, Error **errp) +{ + QEMUCameraClass *klass =3D CAMERADEV_GET_CLASS(camera); + + if (!klass->set_control) { + error_setg(errp, "%s: %s missing set control implementation", + TYPE_CAMERADEV, qemu_camera_id(camera)); + return -ENOTSUP; + } + + trace_qemu_camera_set_control(qemu_camera_id(camera), + QEMUCameraControlTypeString(control->type), control->cur); + + return klass->set_control(camera, control, errp); +} + +void qemu_camera_stream_on(QEMUCamera *camera, qemu_camera_image_cb cb, + void *opaque, Error **errp) +{ + QEMUCameraClass *klass =3D CAMERADEV_GET_CLASS(camera); + + if (!klass->stream_on) { + error_setg(errp, "%s: %s missing stream on implementation", + TYPE_CAMERADEV, qemu_camera_id(camera)); + return; + } + + qemu_mutex_lock(&camera->image_lock); + camera->cb_fn =3D cb; + camera->cb_opaque =3D opaque; + qemu_mutex_unlock(&camera->image_lock); + + klass->stream_on(camera, errp); + assert(camera->image_addr); + assert(camera->image_size); + + trace_qemu_camera_stream_on(qemu_camera_id(camera)); +} + +void qemu_camera_stream_off(QEMUCamera *camera, Error **errp) +{ + QEMUCameraClass *klass =3D CAMERADEV_GET_CLASS(camera); + + if (!klass->stream_off) { + error_setg(errp, "%s: %s missing stream off implementation", + TYPE_CAMERADEV, qemu_camera_id(camera)); + return; + } + + qemu_mutex_lock(&camera->image_lock); + camera->cb_fn =3D NULL; + camera->cb_opaque =3D NULL; + qemu_mutex_unlock(&camera->image_lock); + + klass->stream_off(camera, errp); + + trace_qemu_camera_stream_off(qemu_camera_id(camera)); +} + +size_t qemu_camera_stream_length(QEMUCamera *camera) +{ + size_t length =3D 0; + + qemu_mutex_lock(&camera->image_lock); + assert(camera->image_pos <=3D camera->image_bytes); + length =3D camera->image_bytes - camera->image_pos; + qemu_mutex_unlock(&camera->image_lock); + + return length; +} + +size_t qemu_camera_stream_read(QEMUCamera *camera, const struct iovec *iov, + const uint32_t iov_cnt, size_t offset, + size_t size) +{ + size_t length =3D 0; + void *addr; + + qemu_mutex_lock(&camera->image_lock); + + assert(camera->image_pos <=3D camera->image_bytes); + length =3D camera->image_bytes - camera->image_pos; + length =3D MIN(size, length); + if (!length) { + goto out; + } + + addr =3D camera->image_addr + camera->image_pos; + iov_from_buf(iov, iov_cnt, offset, addr, size); + camera->image_pos +=3D length; + if (camera->image_pos =3D=3D camera->image_bytes) { + /* previous frame already fully read*/ + camera->image_frames =3D camera->image_sequence; + camera->image_pos =3D 0; + camera->image_bytes =3D 0; + } + +out: + qemu_mutex_unlock(&camera->image_lock); + + trace_qemu_camera_stream_read(qemu_camera_id(camera), length); + + return length; +} + + +static void camera_init(Object *obj) +{ + QEMUCamera *camera =3D CAMERADEV(obj); + + qemu_mutex_init(&camera->image_lock); +} + +static void camera_finalize(Object *obj) +{ + QEMUCamera *camera =3D CAMERADEV(obj); + + qemu_mutex_destroy(&camera->image_lock); +} + +static const TypeInfo camera_type_info =3D { + .name =3D TYPE_CAMERADEV, + .parent =3D TYPE_OBJECT, + .instance_size =3D sizeof(QEMUCamera), + .instance_init =3D camera_init, + .instance_finalize =3D camera_finalize, + .abstract =3D true, + .class_size =3D sizeof(QEMUCameraClass), +}; + +static void register_types(void) +{ + type_register_static(&camera_type_info); +} + +type_init(register_types); + +/* internal functions, declared in camera-int.h */ + +void qemu_camera_alloc_image(QEMUCamera *camera, size_t size, Error **errp) +{ + trace_qemu_camera_alloc_image(qemu_camera_id(camera), size); + + qemu_mutex_lock(&camera->image_lock); + if (camera->image_size =3D=3D size) { + /* no need to re-allocate the same size image buffer */ + goto out; + } + + g_free(camera->image_addr); + camera->image_addr =3D g_malloc0(size); + camera->image_size =3D size; + camera->image_pos =3D 0; + camera->image_bytes =3D 0; + +out: + qemu_mutex_unlock(&camera->image_lock); +} + +void qemu_camera_free_image(QEMUCamera *camera) +{ + trace_qemu_camera_free_image(qemu_camera_id(camera)); + + qemu_mutex_lock(&camera->image_lock); + g_free(camera->image_addr); + camera->image_addr =3D NULL; + camera->image_size =3D 0; + camera->image_pos =3D 0; + camera->image_bytes =3D 0; + qemu_mutex_unlock(&camera->image_lock); +} + +void qemu_camera_new_image(QEMUCamera *camera, const void *addr, size_t si= ze) +{ + trace_qemu_camera_new_image(qemu_camera_id(camera), camera->image_sequ= ence, + size); + qemu_mutex_lock(&camera->image_lock); + + assert(camera->image_addr); + assert(size <=3D camera->image_size); + + camera->image_sequence++; + + if (camera->image_pos) { + /* previous frame in process */ + goto out; + } + + memcpy(camera->image_addr, addr, size); + camera->image_bytes =3D size; + +out: + qemu_mutex_unlock(&camera->image_lock); +} diff --git a/camera/meson.build b/camera/meson.build new file mode 100644 index 0000000000..d50ee5ebf7 --- /dev/null +++ b/camera/meson.build @@ -0,0 +1,16 @@ +camera_ss.add([files( + 'camera.c', +)]) + +camera_modules =3D {} +foreach m : [ + ['builtin', cairo, files('builtin.c')], +] + if m[1].found() + module_ss =3D ss.source_set() + module_ss.add(m[1], m[2]) + camera_modules +=3D {m[0] : module_ss} + endif +endforeach + +modules +=3D {'camera': camera_modules} diff --git a/camera/trace-events b/camera/trace-events new file mode 100644 index 0000000000..2f4d93e924 --- /dev/null +++ b/camera/trace-events @@ -0,0 +1,24 @@ +# See docs/devel/tracing.rst for syntax documentation. + +# camera.c +qemu_camera_new(const char *dev, char *typename) "%s: new camera with type= %s" +qemu_camera_del(const char *dev) "%s: delete camera" +qemu_camera_set_control(const char *dev, const char *type, int value) "%s:= set control type %s, value %d" +qemu_camera_stream_on(const char *dev) "%s: stream on" +qemu_camera_stream_off(const char *dev) "%s: stream off" +qemu_camera_stream_read(const char *dev, size_t length) "%s: stream read l= ength %ld" +qemu_camera_alloc_image(const char *dev, size_t size) "%s: alloc image siz= e %ld" +qemu_camera_free_image(const char *dev) "%s: free image size" +qemu_camera_new_image(const char *dev, uint32_t seq, size_t size) "%s: new= image sequence %u, size %ld" +qemu_camera_enum_pixel_format(const char *dev, uint32_t pixfmt) "%s: pixfm= t 0x%x" +qemu_camera_enum_pixel_format_ret(const char *dev, int ret) "%s: ret %d" +qemu_camera_enum_frame_size_d(const char *dev, uint32_t pixfmt, uint32_t w= , uint32_t h) "%s: pixfmt 0x%x, discrete width %u, height %u" +qemu_camera_enum_frame_size_ret(const char *dev, int ret) "%s: ret %d" +qemu_camera_enum_frame_interval_d(const char *dev, uint32_t pixfmt, uint32= _t w, uint32_t h, uint32_t n, uint32_t d) "%s: pixfmt 0x%x, width %u, heigh= t %u, discrete numerator %u, denominator %u" +qemu_camera_enum_frame_interval_ret(const char *dev, int ret) "%s: ret %d" +qemu_camera_set_frame_interval(const char *dev, uint32_t pixfmt, uint32_t = w, uint32_t h, uint32_t n, uint32_t d, int ret) "%s: pixfmt 0x%x, width %u,= height %u, discrete numerator %u, denominator %u, retval %d" +qemu_camera_enum_control(const char *dev, const char *type, int def, int m= in, int max, int step) "%s: type %s, def %d, min %d, max %d, step %d" +qemu_camera_enum_control_ret(const char *dev, int ret) "%s: ret %d" + +# builtin.c +camera_builtin_timer(const char *dev) "%s: new image" diff --git a/camera/trace.h b/camera/trace.h new file mode 100644 index 0000000000..f248af5c9b --- /dev/null +++ b/camera/trace.h @@ -0,0 +1 @@ +#include "trace/trace-camera.h" diff --git a/include/camera/camera.h b/include/camera/camera.h new file mode 100644 index 0000000000..edf6d01d58 --- /dev/null +++ b/include/camera/camera.h @@ -0,0 +1,238 @@ +/* + * QEMU Camera subsystem + * + * Copyright 2021 Bytedance, Inc. + * + * Authors: + * zhenwei pi + * + * This work is licensed under the terms of the GNU GPL, version 2 or late= r. + * See the COPYING file in the top-level directory. + */ +#ifndef QEMU_CAMERA_H +#define QEMU_CAMERA_H + +#include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" +#include "qapi/qapi-types-camera.h" +#include "qemu/queue.h" + +#define camera_fourcc_code(a, b, c, d) \ + ((uint32_t)(a) | ((uint32_t)(b) << 8) | \ + ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24)) + +#define QEMU_CAMERA_PIX_FMT_MJPEG camera_fourcc_code('M', 'J', 'P', 'G') +#define QEMU_CAMERA_PIX_FMT_YUYV camera_fourcc_code('Y', 'U', 'Y', 'V') +#define QEMU_CAMERA_PIX_FMT_RGB565 camera_fourcc_code('R', 'G', 'B', 'P') + +static inline bool qemu_camera_pixel_supported(uint32_t pixfmt) +{ + /* only process MJPEG & YUYV, may support others in future */ + if ((pixfmt =3D=3D QEMU_CAMERA_PIX_FMT_MJPEG) + || (pixfmt =3D=3D QEMU_CAMERA_PIX_FMT_YUYV) + || (pixfmt =3D=3D QEMU_CAMERA_PIX_FMT_RGB565)) { + return true; + } + + return false; +} + +#define QEMU_CAMERA_FRMSIZE_TYPE_DISCRETE 0x00 +#define QEMU_CAMERA_FRMSIZE_TYPE_STEPWISE 0x01 + +typedef struct QEMUCameraFrameSize { + uint32_t pixel_format; + + uint32_t type; + union { + struct FrameSizeDiscrete { + uint32_t width; + uint32_t height; + } d; + + struct FrameSizeStepwise { + uint32_t min_width; + uint32_t max_width; + uint32_t step_width; + uint32_t min_height; + uint32_t max_height; + uint32_t step_height; + } s; + }; +} QEMUCameraFrameSize; + +typedef struct QEMUCameraFormat { + uint32_t pixel_format; + uint32_t width; + uint32_t height; +} QEMUCameraFormat; + +#define QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE 0x00 +#define QEMU_CAMERA_FRMIVAL_TYPE_STEPWISE 0x01 + +typedef struct QEMUCameraFrameInterval { + uint32_t pixel_format; + uint32_t width; + uint32_t height; + + uint32_t type; + union { + struct FrameIntervalDiscrete { + uint32_t numerator; + uint32_t denominator; + } d; + + struct FrameIntervalStepwise { + struct FrameIntervalDiscrete min; + struct FrameIntervalDiscrete max; + struct FrameIntervalDiscrete step; + } s; + }; +} QEMUCameraFrameInterval; + +typedef enum QEMUCameraControlType { + QEMUCameraBrightness, + QEMUCameraContrast, + QEMUCameraGain, + QEMUCameraGamma, + QEMUCameraHue, + QEMUCameraHueAuto, + QEMUCameraSaturation, + QEMUCameraSharpness, + QEMUCameraWhiteBalanceTemperature, + QEMUCameraControlMax +} QEMUCameraControlType; + +static const char *QEMUCameraControlTypeStr[] =3D { + "Brightness", + "Contrast", + "Gain", + "Gamma", + "Hue", + "HueAuto", + "Saturation", + "Sharpness", + "WhiteBalanceTemperature", + "Max", +}; + +static inline const char *QEMUCameraControlTypeString(QEMUCameraControlTyp= e t) +{ + if (t > QEMUCameraControlMax) { + return ""; + } + + return QEMUCameraControlTypeStr[t]; +} + +typedef struct QEMUCameraControl { + QEMUCameraControlType type; + int32_t cur; + int32_t def; + int32_t min; + int32_t max; + int32_t step; +} QEMUCameraControl; + +#define TYPE_CAMERADEV "cameradev" + +typedef void (*qemu_camera_image_cb) (void *opaque, void *buf, ssize_t ava= il); + +struct QEMUCamera { + Object parent_obj; + + char *model; + Cameradev *dev; + + /* camera image buffer to store recent frame */ + QemuMutex image_lock; + /* sequence number generated by driver */ + uint32_t image_sequence; + /* frame sequence number currently work on */ + uint32_t image_frames; + unsigned char *image_addr; + /* size of buffer */ + ssize_t image_size; + /* real size of this frame, clear to zero after fully read*/ + ssize_t image_bytes; + /* offset already read, clear to zero after fully read */ + ssize_t image_pos; + qemu_camera_image_cb cb_fn; + void *cb_opaque; + + QLIST_ENTRY(QEMUCamera) list; +}; + +OBJECT_DECLARE_TYPE(QEMUCamera, QEMUCameraClass, CAMERADEV) + +struct QEMUCameraClass { + ObjectClass parent_class; + + void (*open)(QEMUCamera *camera, Error **errp); + + void (*stream_on)(QEMUCamera *camera, Error **errp); + + void (*stream_off)(QEMUCamera *camera, Error **errp); + + int (*enum_pixel_format)(QEMUCamera *camera, uint32_t *pixfmts, + int npixfmt, Error **errp); + + int (*enum_frame_size)(QEMUCamera *camera, uint32_t pixfmt, + QEMUCameraFrameSize *frmszs, int nfrmsz, + Error **errp); + + int (*enum_frame_interval)(QEMUCamera *camera, + const QEMUCameraFormat *format, + QEMUCameraFrameInterval *frmivals, int nfrm= ival, + Error **errp); + + int (*set_frame_interval)(QEMUCamera *camera, + const QEMUCameraFrameInterval *frmival, + Error **errp); + + int (*enum_control)(QEMUCamera *camera, QEMUCameraControl *controls, + int ncontrols, Error **errp); + + int (*set_control)(QEMUCamera *camera, const QEMUCameraControl *contro= l, + Error **errp); +}; + +void qemu_camera_new_from_opts(const char *opt); +void qemu_camera_del(QEMUCamera *camera); +const char *qemu_camera_id(QEMUCamera *camera); +QEMUCamera *qemu_camera_by_id(const char *id); + +int qemu_camera_enum_pixel_format(QEMUCamera *camera, uint32_t *pixfmts, + int npixfmt, Error **errp); + +int qemu_camera_enum_frame_size(QEMUCamera *camera, uint32_t pixfmt, + QEMUCameraFrameSize *frmszs, int nfrmsz, + Error **errp); + +int qemu_camera_enum_frame_interval(QEMUCamera *camera, + const QEMUCameraFormat *format, + QEMUCameraFrameInterval *frmivals, + int nfrmival, Error **errp); + +int qemu_camera_set_frame_interval(QEMUCamera *camera, + const QEMUCameraFrameInterval *frmival, + Error **errp); + +int qemu_camera_enum_control(QEMUCamera *camera, QEMUCameraControl *contro= ls, + int ncontrols, Error **errp); + +int qemu_camera_set_control(QEMUCamera *camera, + const QEMUCameraControl *control, Error **errp= ); + +void qemu_camera_stream_on(QEMUCamera *camera, qemu_camera_image_cb cb, + void *opaque, Error **errp); + +void qemu_camera_stream_off(QEMUCamera *camera, Error **errp); + +size_t qemu_camera_stream_length(QEMUCamera *camera); + +size_t qemu_camera_stream_read(QEMUCamera *camera, const struct iovec *iov, + const uint32_t iov_cnt, size_t offset, + size_t size); + +#endif /* QEMU_CAMERA_H */ diff --git a/meson.build b/meson.build index 886f0a9343..f0b51a0861 100644 --- a/meson.build +++ b/meson.build @@ -423,6 +423,13 @@ if 'ust' in get_option('trace_backends') lttng =3D dependency('lttng-ust', required: true, method: 'pkg-config', kwargs: static_kwargs) endif + +cairo =3D not_found +if have_system + cairo =3D dependency('cairo', required: have_system, + method: 'pkg-config', kwargs: static_kwargs) +endif + pixman =3D not_found if have_system or have_tools pixman =3D dependency('pixman-1', required: have_system, version:'>=3D0.= 21.8', @@ -1452,6 +1459,7 @@ config_host_data.set('HOST_' + host_arch.to_upper(), = 1) =20 config_host_data.set('CONFIG_ATTR', libattr.found()) config_host_data.set('CONFIG_BRLAPI', brlapi.found()) +config_host_data.set('CONFIG_CAIRO', cairo.found()) config_host_data.set('CONFIG_COCOA', cocoa.found()) config_host_data.set('CONFIG_FUZZ', get_option('fuzzing')) config_host_data.set('CONFIG_GCOV', get_option('b_coverage')) @@ -2395,6 +2403,7 @@ genh +=3D hxdep authz_ss =3D ss.source_set() blockdev_ss =3D ss.source_set() block_ss =3D ss.source_set() +camera_ss =3D ss.source_set() chardev_ss =3D ss.source_set() common_ss =3D ss.source_set() common_user_ss =3D ss.source_set() @@ -2453,6 +2462,7 @@ if have_system 'audio', 'backends', 'backends/tpm', + 'camera', 'chardev', 'ebpf', 'hw/9pfs', @@ -2572,6 +2582,7 @@ endif =20 subdir('audio') subdir('io') +subdir('camera') subdir('chardev') subdir('fsdev') subdir('dump') @@ -2848,6 +2859,13 @@ libchardev =3D static_library('chardev', chardev_ss.= sources() + genh, =20 chardev =3D declare_dependency(link_whole: libchardev) =20 +camera_ss =3D camera_ss.apply(config_host, strict: false) +libcamera =3D static_library('camera', camera_ss.sources() + genh, + name_suffix: 'fa', + build_by_default: false) + +camera =3D declare_dependency(link_whole: libcamera) + hwcore_ss =3D hwcore_ss.apply(config_host, strict: false) libhwcore =3D static_library('hwcore', sources: hwcore_ss.sources() + genh, name_suffix: 'fa', @@ -2867,7 +2885,7 @@ foreach m : block_mods + softmmu_mods install_dir: qemu_moddir) endforeach =20 -softmmu_ss.add(authz, blockdev, chardev, crypto, io, qmp) +softmmu_ss.add(authz, blockdev, camera, chardev, crypto, io, qmp) common_ss.add(qom, qemuutil) =20 common_ss.add_all(when: 'CONFIG_SOFTMMU', if_true: [softmmu_ss]) diff --git a/meson_options.txt b/meson_options.txt index 921967eddb..d51729441a 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -208,3 +208,6 @@ option('fdt', type: 'combo', value: 'auto', =20 option('selinux', type: 'feature', value: 'auto', description: 'SELinux support in qemu-nbd') + +option('camera', type: 'feature', value: 'auto', + description: 'Camera subsystem support') diff --git a/qapi/camera.json b/qapi/camera.json new file mode 100644 index 0000000000..2c8314ba4a --- /dev/null +++ b/qapi/camera.json @@ -0,0 +1,84 @@ +# -*- mode: python -*- +# +# Copyright (C) 2021 zhenwei pi +# +# This work is licensed under the terms of the GNU GPL, version 2 or later. +# See the COPYING file in the top-level directory. + +## +# =3D Camera +## + +## +# @ColorType: +# +# An enumeration of color type. +# +# Since: 6.3 +## +{ 'enum': 'ColorType', + 'data': [ 'blue', 'green', 'red', 'rainbow', 'digital-rain' ] } + +## +# @CameraBuiltinOptions: +# +# Options of the builtin camera. +# +# @debug: enable/disable debug information in camera video +# +# @fps: the FPS of builtin camera +# +# @width: the width of frame +# +# @height: the height of frame +# +# @mjpeg: enable/disable mjpeg format +# +# @yuv: enable/disable yuv format +# +# @rgb565: enable/disable rgb565 format +# +# @bgcolor: background color of camera +# +# Since: 6.3 +## +{ 'struct': 'CameraBuiltinOptions', + 'data': { + '*debug': 'bool', + '*fps': 'uint32', + '*width': 'uint32', + '*height': 'uint32', + '*mjpeg': 'bool', + '*yuv': 'bool', + '*rgb565': 'bool', + '*bgcolor': 'ColorType' } } + + +## +# @CameradevDriver: +# +# An enumeration of possible camera backend drivers. +# +# Since: 6.3 +## +{ 'enum': 'CameradevDriver', + 'data': [ 'builtin' ] } + +## +# @Cameradev: +# +# Options of an camera backend. +# +# @id: identifier of the backend +# +# @driver: the backend driver to use +# +# Since: 6.3 +## +{ 'union': 'Cameradev', + 'base': { + 'id': 'str', + 'driver': 'CameradevDriver'}, + 'discriminator': 'driver', + 'data': { + 'builtin': 'CameraBuiltinOptions' } } diff --git a/qapi/meson.build b/qapi/meson.build index c0c49c15e4..404eb2e573 100644 --- a/qapi/meson.build +++ b/qapi/meson.build @@ -59,6 +59,7 @@ if have_system 'rdma', 'rocker', 'tpm', + 'camera', ] endif if have_system or have_tools diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json index 4912b9744e..58afb77639 100644 --- a/qapi/qapi-schema.json +++ b/qapi/qapi-schema.json @@ -93,3 +93,4 @@ { 'include': 'audio.json' } { 'include': 'acpi.json' } { 'include': 'pci.json' } +{ 'include': 'camera.json' } diff --git a/qemu-options.hx b/qemu-options.hx index 7d47510947..fa439134dc 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -3529,6 +3529,16 @@ The available backends are: traffic identified by a name (preferably a fqdn). ERST =20 +DEFHEADING(Camera device options:) + +DEF("cameradev", HAS_ARG, QEMU_OPTION_cameradev, + "-cameradev help\n" +#ifdef CONFIG_CAIRO + "-cameradev builtin,id=3Did[,debug=3Dtrue|false][,fps=3DFPS][,width=3D= WIDTH][,height=3DHEIGHT][,mjpeg=3Dtrue|false][,yuv=3Dtrue|false][,rgb565=3D= true|false][,bgcolor=3Dblue|gree|red|rainbow|digital-rain]\n" +#endif + , QEMU_ARCH_ALL +) + DEFHEADING() =20 #ifdef CONFIG_TPM diff --git a/softmmu/vl.c b/softmmu/vl.c index 620a1f1367..3c5f483355 100644 --- a/softmmu/vl.c +++ b/softmmu/vl.c @@ -94,6 +94,7 @@ #ifdef CONFIG_VIRTFS #include "fsdev/qemu-fsdev.h" #endif +#include "camera/camera.h" #include "sysemu/qtest.h" =20 #include "disas/disas.h" @@ -3244,6 +3245,9 @@ void qemu_init(int argc, char **argv, char **envp) qemu_opt_get(opts, "mount_tag"), &error_abort= ); break; } + case QEMU_OPTION_cameradev: + qemu_camera_new_from_opts(optarg); + break; case QEMU_OPTION_serial: add_device_config(DEV_SERIAL, optarg); default_serial =3D 0; --=20 2.25.1 From nobody Fri Sep 20 22:16:20 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; 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; dmarc=fail(p=none dis=none) header.from=bytedance.com ARC-Seal: i=1; a=rsa-sha256; t=1640615450; cv=none; d=zohomail.com; s=zohoarc; b=n6wS2u8N/tF4lNLgYRc1vlYdqDZZVb2gOgZoohYtKIl/Gwy8xis3iWHrWa/EKCHoBv2yHebxoNAAxmFFFgT1lO69pUiYgQqu3ukw1b1ZME9ln7XSuFXU+n6jOgaYPF31tmLeOrst1DsV49LMLGd9kNl3ltBxCP9nZxyOfJnfQO0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1640615450; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=6aSGfDjayQ1PrEwkX49lBg7Z2rZJio7adzqNVFRKOIQ=; b=b0Hx70A9ao5wWmeDOCvHiycCGaOfIIL7uPzm1i10F1Tev6a0Hy4/bzQFcC6ahHCVzbt7BA0Yv0noNn6oWdTnwh7vn7s1MdYE+AH0OXDzQvp8wtlAHAIAIc4QX30mqTvOGF6frV1wbB1L/ai6KUeKegiVtwzAr4ORFGhgQijmPDU= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; 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; dmarc=fail header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1640615450305299.4550865023922; Mon, 27 Dec 2021 06:30:50 -0800 (PST) Received: from localhost ([::1]:40588 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1n1r1V-0004Vs-Ak for importer@patchew.org; Mon, 27 Dec 2021 09:30:49 -0500 Received: from eggs.gnu.org ([209.51.188.92]:54848) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1n1qzN-0001iz-WB for qemu-devel@nongnu.org; Mon, 27 Dec 2021 09:28:38 -0500 Received: from [2607:f8b0:4864:20::535] (port=44740 helo=mail-pg1-x535.google.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1n1qzL-000324-3K for qemu-devel@nongnu.org; Mon, 27 Dec 2021 09:28:37 -0500 Received: by mail-pg1-x535.google.com with SMTP id m15so13633874pgu.11 for ; Mon, 27 Dec 2021 06:28:34 -0800 (PST) Received: from libai.bytedance.net ([153.254.110.96]) by smtp.gmail.com with ESMTPSA id p2sm15072916pgn.73.2021.12.27.06.28.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 27 Dec 2021 06:28:33 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bytedance-com.20210112.gappssmtp.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=6aSGfDjayQ1PrEwkX49lBg7Z2rZJio7adzqNVFRKOIQ=; b=RMo/3WHGF7BlkNjif91Ij/qUq/2rI+C3SZiL03y0Ees8/f4WBRqvroK7kg47xrQ9ax 05BD0up95T/MK/wCVu9iA3fyQ5kFllNyCio2QNieHow/YSlMUuG4i6TcFLaKZcUYDKXf g6acM/JXDfBRfjzNuZoeoxptataP/4S5iOyCoz/E39dl9iTC9TtZQf8sRCzkZjpGANXo lAUepq8KPQAKCoNh3/RUcniByE57+5W9vs0EmsTClaOFV6/nVsKf8lUofQFiL9VrDf5I fvTlT8hGRNJS3zBtw9N8aD6rS9CxdkfdXoWgQwxVbJqDvkrqBZNUErPIfzgmUPjPQWvl LmRA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=6aSGfDjayQ1PrEwkX49lBg7Z2rZJio7adzqNVFRKOIQ=; b=wxIoRxg5nX2O9sCAexLIjkV9s7s9w831l9pAnev1en83iQ8KHbLT6//3StmATBwrQN eJoYEJUs6gVfdKUosVqrZaXQMmUHoZzoJbZLh7NrRqLWKrS0aK8C+nnHVDLq9h7RvYbP zrwaIM+ih+2H1OCkieKoWA0jtJgRZwIIkDGbPmx7s2Or78ixldR93+vCWO11pSXoBFud Pnw7u/c3gz8fEKqUDTrIWd62j0+EtYg9vCpOvAwXwkTddqNyb2xdtdYHC050g1Zg9Kiy 2qHgE+tGz22F4AsijriBNNK3m/usXin6kjimMvWBcN9rc6Y4czg0FBK9J60dnYOLlGO3 SGgw== X-Gm-Message-State: AOAM533ddvwEblOKAm/dYmS078VzIDS5aTb5rtSqfeQMSehb5c2OcOna xvvsZyBxZBk1CY5jkur/eEb2sw== X-Google-Smtp-Source: ABdhPJz4/viepMH3eu4xBtjWw1ZxaPQmSXMF/X+bSfTn+e+Ck111PTbEnqe0VvKsYCUXjm0eTHxmdg== X-Received: by 2002:a05:6a00:ac7:b0:4bb:d3b8:3faf with SMTP id c7-20020a056a000ac700b004bbd3b83fafmr7823716pfl.48.1640615313774; Mon, 27 Dec 2021 06:28:33 -0800 (PST) From: zhenwei pi To: peter.maydell@linaro.org, richard.henderson@linaro.org, kraxel@redhat.com, eblake@redhat.com, pbonzini@redhat.com Subject: [PATCH 2/5] camera: v4l2: Introduce v4l2 camera driver Date: Mon, 27 Dec 2021 22:27:31 +0800 Message-Id: <20211227142734.691900-3-pizhenwei@bytedance.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20211227142734.691900-1-pizhenwei@bytedance.com> References: <20211227142734.691900-1-pizhenwei@bytedance.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Host-Lookup-Failed: Reverse DNS lookup failed for 2607:f8b0:4864:20::535 (failed) 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=2607:f8b0:4864:20::535; envelope-from=pizhenwei@bytedance.com; helo=mail-pg1-x535.google.com X-Spam_score_int: -10 X-Spam_score: -1.1 X-Spam_bar: - X-Spam_report: (-1.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RDNS_NONE=0.793, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: qemu-devel@nongnu.org, zhenwei pi Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: pass (identity @bytedance-com.20210112.gappssmtp.com) X-ZM-MESSAGEID: 1640615587247000001 Content-Type: text/plain; charset="utf-8" On a Linux platform, user process could accesses /dev/videoX to capture video frames. We can run QEMU like this: qemu-system-x86_64 ... -cameradev v4l2,path=3D/dev/video0,id=3Dcamera0 The basic logic of v4l2 driver: stream on -> qbuf -> dqbuf(drive by POLLIN event) -> qbuf -> dqbuf ... -> stream off Signed-off-by: zhenwei pi --- camera/meson.build | 4 + camera/trace-events | 4 + camera/v4l2.c | 637 ++++++++++++++++++++++++++++++++++++++++++++ qapi/camera.json | 21 +- qemu-options.hx | 3 + 5 files changed, 667 insertions(+), 2 deletions(-) create mode 100644 camera/v4l2.c diff --git a/camera/meson.build b/camera/meson.build index d50ee5ebf7..6e7aeb5ebd 100644 --- a/camera/meson.build +++ b/camera/meson.build @@ -2,6 +2,10 @@ camera_ss.add([files( 'camera.c', )]) =20 +camera_ss.add(when: 'CONFIG_LINUX', if_true: files( + 'v4l2.c', +)) + camera_modules =3D {} foreach m : [ ['builtin', cairo, files('builtin.c')], diff --git a/camera/trace-events b/camera/trace-events index 2f4d93e924..4527303d58 100644 --- a/camera/trace-events +++ b/camera/trace-events @@ -22,3 +22,7 @@ qemu_camera_enum_control_ret(const char *dev, int ret) "%= s: ret %d" =20 # builtin.c camera_builtin_timer(const char *dev) "%s: new image" + +# v4l2.c +camera_v4l2_qbuf(const char *dev, uint32_t index) "%s: qbuf index %u" +camera_v4l2_dqbuf(const char *dev, uint32_t index) "%s: qbuf index %u" diff --git a/camera/v4l2.c b/camera/v4l2.c new file mode 100644 index 0000000000..6425e0e1e9 --- /dev/null +++ b/camera/v4l2.c @@ -0,0 +1,637 @@ +/* + * V4L2 camera backend implemention + * + * Copyright 2021 Bytedance, Inc. + * + * Authors: + * zhenwei pi + * + * This work is licensed under the terms of the GNU GPL, version 2 or late= r. + * See the COPYING file in the top-level directory. + */ +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qemu/log.h" +#include "qemu/main-loop.h" +#include "qom/object.h" +#include "qapi/error.h" +#include "qapi/visitor.h" +#include "qapi/qapi-visit-camera.h" +#include "camera/camera.h" +#include "camera-int.h" +#include "trace.h" +#include +#include + +#define TYPE_CAMERA_V4L2 TYPE_CAMERADEV"-v4l2" + +#define CAMERA_V4L2_BUFFER_MAX 16 +#define CAMERA_V4L2_BUFFER_DEF 2 + +typedef struct CameraV4l2Buffer { + unsigned char *addr; + uint32_t length; +} CameraV4l2Buffer; + +typedef struct CameraV4l2 { + QEMUCamera parent; + + int devfd; + size_t sizeimage; + uint8_t nbuffers; + CameraV4l2Buffer buffers[CAMERA_V4L2_BUFFER_MAX]; +} CameraV4l2; + +DECLARE_INSTANCE_CHECKER(CameraV4l2, CAMERA_V4L2_DEV, TYPE_CAMERA_V4L2) + +typedef struct CameraV4l2Ctrl { + QEMUCameraControlType q; + uint32_t v; +} CameraV4l2Ctrl; + +static CameraV4l2Ctrl camera_v4l2_ctrl_table[] =3D { + { .q =3D QEMUCameraBrightness, + .v =3D V4L2_CID_BRIGHTNESS }, + { .q =3D QEMUCameraContrast, + .v =3D V4L2_CID_CONTRAST }, + { .q =3D QEMUCameraGain, + .v =3D V4L2_CID_GAIN }, + { .q =3D QEMUCameraGamma, + .v =3D V4L2_CID_GAMMA }, + { .q =3D QEMUCameraHue, + .v =3D V4L2_CID_HUE }, + { .q =3D QEMUCameraHueAuto, + .v =3D V4L2_CID_HUE_AUTO }, + { .q =3D QEMUCameraSaturation, + .v =3D V4L2_CID_SATURATION }, + { .q =3D QEMUCameraSharpness, + .v =3D V4L2_CID_SHARPNESS }, + { .q =3D QEMUCameraWhiteBalanceTemperature, + .v =3D V4L2_CID_WHITE_BALANCE_TEMPERATURE }, +}; + +static QEMUCameraControlType camera_v4l2_control_to_qemu(uint32_t id) +{ + CameraV4l2Ctrl *ctrl; + int i; + + for (i =3D 0; i < ARRAY_SIZE(camera_v4l2_ctrl_table); i++) { + ctrl =3D &camera_v4l2_ctrl_table[i]; + if (ctrl->v =3D=3D id) { + return ctrl->q; + } + } + + return QEMUCameraControlMax; +} + +static uint32_t camera_qemu_control_to_v4l2(QEMUCameraControlType type) +{ + CameraV4l2Ctrl *ctrl; + int i; + + for (i =3D 0; i < ARRAY_SIZE(camera_v4l2_ctrl_table); i++) { + ctrl =3D &camera_v4l2_ctrl_table[i]; + if (ctrl->q =3D=3D type) { + return ctrl->v; + } + } + + return 0; +} + +static int camera_v4l2_enum_pixel_format(QEMUCamera *camera, uint32_t *pix= fmts, + int npixfmt, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + struct v4l2_fmtdesc v4l2_fmt; + int index, total =3D 0; + + for (index =3D 0; total < npixfmt; index++) { + v4l2_fmt.index =3D index; + v4l2_fmt.type =3D V4L2_CAP_VIDEO_CAPTURE; + if (ioctl(v4l2->devfd, VIDIOC_ENUM_FMT, &v4l2_fmt) < 0) { + if (errno =3D=3D EINVAL) { + break; /* the last one */ + } + + error_setg(errp, "%s: enum fmt on device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + if (!qemu_camera_pixel_supported(v4l2_fmt.pixelformat)) { + continue; + } + + pixfmts[total++] =3D v4l2_fmt.pixelformat; + } + + return total; +} + +static int camera_v4l2_enum_frame_size(QEMUCamera *camera, uint32_t pixfmt, + QEMUCameraFrameSize *frmszs, int nf= rmsz, + Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + QEMUCameraFrameSize *frmsz; + struct v4l2_frmsizeenum v4l2_frmsz; + int index, total =3D 0; + + if (!qemu_camera_pixel_supported(pixfmt)) { + return -EINVAL; + } + + for (index =3D 0; total < nfrmsz; index++) { + v4l2_frmsz.index =3D index; + v4l2_frmsz.pixel_format =3D pixfmt; + if (ioctl(v4l2->devfd, VIDIOC_ENUM_FRAMESIZES, &v4l2_frmsz) < 0) { + if (errno =3D=3D EINVAL) { + break; /* the last one */ + } + + error_setg(errp, "%s: enum frame size device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + if (v4l2_frmsz.type !=3D V4L2_FRMSIZE_TYPE_DISCRETE) { + continue; /* TODO stepwise support in future*/ + } + + frmsz =3D frmszs + total++; + frmsz->pixel_format =3D v4l2_frmsz.pixel_format; + frmsz->type =3D QEMU_CAMERA_FRMSIZE_TYPE_DISCRETE; + frmsz->d.width =3D v4l2_frmsz.discrete.width; + frmsz->d.height =3D v4l2_frmsz.discrete.height; + } + + return total; +} + +static int camera_v4l2_enum_frame_interval(QEMUCamera *camera, + const QEMUCameraFormat *format, + QEMUCameraFrameInterval *frmiva= ls, + int nfrmival, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + QEMUCameraFrameInterval *frmival; + struct v4l2_frmivalenum v4l2_frmival; + int index, total =3D 0; + + for (index =3D 0; total < nfrmival; index++) { + v4l2_frmival.index =3D index; + v4l2_frmival.pixel_format =3D format->pixel_format; + v4l2_frmival.width =3D format->width; + v4l2_frmival.height =3D format->height; + if (ioctl(v4l2->devfd, VIDIOC_ENUM_FRAMEINTERVALS, &v4l2_frmival) = < 0) { + if (errno =3D=3D EINVAL) { + break; /* the last one */ + } + + error_setg(errp, "%s: enum frame intervals on device %s failed= , %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + if (v4l2_frmival.type !=3D V4L2_FRMIVAL_TYPE_DISCRETE) { + continue; /* TODO stepwise support in future*/ + } + + frmival =3D frmivals + total++; + frmival->pixel_format =3D v4l2_frmival.pixel_format; + frmival->type =3D QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE; + frmival->width =3D v4l2_frmival.width; + frmival->height =3D v4l2_frmival.height; + frmival->d.numerator =3D v4l2_frmival.discrete.numerator; + frmival->d.denominator =3D v4l2_frmival.discrete.denominator; + } + + return total; +} + +static int camera_v4l2_get_format(QEMUCamera *camera, QEMUCameraFormat *fo= rmat, + Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + struct v4l2_format v4l2_fmt =3D {.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE= }; + struct v4l2_pix_format *v4l2_pix =3D &v4l2_fmt.fmt.pix; + + if (ioctl(v4l2->devfd, VIDIOC_G_FMT, &v4l2_fmt) < 0) { + error_setg(errp, "%s: get fmt on device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + if (format) { + format->pixel_format =3D v4l2_pix->pixelformat; + format->width =3D v4l2_pix->width; + format->height =3D v4l2_pix->height; + } + + v4l2->sizeimage =3D v4l2_pix->sizeimage; + + return 0; +} + +static int camera_v4l2_set_format(QEMUCamera *camera, + const QEMUCameraFormat *format, Error **= errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + struct v4l2_format v4l2_fmt =3D {0}; + struct v4l2_pix_format *v4l2_pix; + + v4l2_fmt.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_pix =3D &v4l2_fmt.fmt.pix; + v4l2_pix->pixelformat =3D format->pixel_format; + v4l2_pix->width =3D format->width; + v4l2_pix->height =3D format->height; + if (ioctl(v4l2->devfd, VIDIOC_S_FMT, &v4l2_fmt) < 0) { + error_setg(errp, "%s: set fmt on device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + v4l2->sizeimage =3D v4l2_pix->sizeimage; + + return 0; +} + +static int camera_v4l2_set_frame_interval(QEMUCamera *camera, + const QEMUCameraFrameInterval *frmival, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + QEMUCameraFormat fmt; + struct v4l2_streamparm streamparm; + struct v4l2_captureparm *capture; + int ret; + + if (frmival->type !=3D QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE) { + error_setg(errp, "%s: only support discrete mode", TYPE_CAMERA_V4L= 2); + return -ENOTSUP; + } + + fmt.pixel_format =3D frmival->pixel_format; + fmt.width =3D frmival->width; + fmt.height =3D frmival->height; + ret =3D camera_v4l2_set_format(camera, &fmt, errp); + if (ret) { + return ret; + } + + streamparm.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + capture =3D &streamparm.parm.capture; + capture->timeperframe.numerator =3D frmival->d.numerator; + capture->timeperframe.denominator =3D frmival->d.denominator; + if (ioctl(v4l2->devfd, VIDIOC_S_PARM, &streamparm) < 0) { + error_setg(errp, "%s: set stream parm on device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + return 0; +} + +static int camera_v4l2_enum_control(QEMUCamera *camera, + QEMUCameraControl *controls, int ncontrols, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + QEMUCameraControl *control; + struct v4l2_queryctrl v4l2_ctrl =3D {0}; + QEMUCameraControlType type; + int index, total =3D 0; + + for (index =3D 0; total < ncontrols; index++) { + v4l2_ctrl.id |=3D V4L2_CTRL_FLAG_NEXT_CTRL; + if (ioctl(v4l2->devfd, VIDIOC_QUERYCTRL, &v4l2_ctrl) < 0) { + if (errno =3D=3D EINVAL) { + break; /* the last one */ + } + + error_setg(errp, "%s: enum control on device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + if (v4l2_ctrl.flags & V4L2_CTRL_FLAG_INACTIVE) { + continue; + } + + type =3D camera_v4l2_control_to_qemu(v4l2_ctrl.id); + if (type =3D=3D QEMUCameraControlMax) { + continue; + } + + control =3D controls + total++; + control->type =3D type; + control->def =3D v4l2_ctrl.default_value; + control->min =3D v4l2_ctrl.minimum; + control->max =3D v4l2_ctrl.maximum; + control->step =3D v4l2_ctrl.step; + } + + return total; +} + +static int camera_v4l2_set_control(QEMUCamera *camera, + const QEMUCameraControl *control, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + struct v4l2_control v4l2_ctrl; + uint32_t cid; + + cid =3D camera_qemu_control_to_v4l2(control->type); + if (!cid) { + error_setg(errp, "%s: unsupported control type %d", + TYPE_CAMERA_V4L2, control->type); + return -EINVAL; + } + + v4l2_ctrl.id =3D cid; + v4l2_ctrl.value =3D control->cur; + if (ioctl(v4l2->devfd, VIDIOC_S_CTRL, &v4l2_ctrl) < 0) { + error_setg(errp, "%s: set ctrl on device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return -errno; + } + + return 0; +} + +static int camera_v4l2_qbuf(QEMUCamera *camera, int index) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + struct v4l2_buffer buffer =3D {0}; + + trace_camera_v4l2_qbuf(qemu_camera_id(camera), index); + + buffer.index =3D index; + buffer.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.field =3D V4L2_FIELD_ANY; + buffer.memory =3D V4L2_MEMORY_MMAP; + + return ioctl(v4l2->devfd, VIDIOC_QBUF, &buffer); +} + +static int camera_v4l2_dqbuf(QEMUCamera *camera) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + struct v4l2_buffer buffer; + + buffer.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory =3D V4L2_MEMORY_MMAP; + + if (ioctl(v4l2->devfd, VIDIOC_DQBUF, &buffer) < 0) { + return -errno; + } + + trace_camera_v4l2_dqbuf(qemu_camera_id(camera), buffer.index); + + return buffer.index; +} + +static void camera_v4l2_free_buffers(QEMUCamera *camera) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + struct v4l2_requestbuffers v4l2_reqbufs =3D {0}; + CameraV4l2Buffer *buffer; + int index; + + /* 1, try to dequeue all buffers */ + for (index =3D 0; index < v4l2->nbuffers; index++) { + camera_v4l2_dqbuf(camera); + } + + /* 2, try to unmap all buffers */ + for (index =3D 0; index < v4l2->nbuffers; index++) { + buffer =3D &v4l2->buffers[index]; + if (buffer->addr) { + munmap(buffer->addr, buffer->length); + buffer->addr =3D NULL; + buffer->length =3D 0; + } + } + + /* 3, free all the v4l2 reqbufs */ + v4l2_reqbufs.count =3D 0; + v4l2_reqbufs.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_reqbufs.memory =3D V4L2_MEMORY_MMAP; + ioctl(v4l2->devfd, VIDIOC_REQBUFS, &v4l2_reqbufs); +} + +static int camera_v4l2_request_buffers(QEMUCamera *camera, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + struct v4l2_requestbuffers v4l2_reqbufs =3D {0}; + struct v4l2_buffer v4l2_buf; + CameraV4l2Buffer *buffer; + void *addr; + int index; + + v4l2_reqbufs.count =3D v4l2->nbuffers; + v4l2_reqbufs.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_reqbufs.memory =3D V4L2_MEMORY_MMAP; + if (ioctl(v4l2->devfd, VIDIOC_REQBUFS, &v4l2_reqbufs) < 0) { + return -errno; + } + + for (index =3D 0; index < v4l2->nbuffers; index++) { + v4l2_buf.index =3D index; + v4l2_buf.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_buf.memory =3D V4L2_MEMORY_MMAP; + v4l2_buf.length =3D 0; + if (ioctl(v4l2->devfd, VIDIOC_QUERYBUF, &v4l2_buf) < 0) { + goto error; + } + + if (v4l2_buf.type !=3D V4L2_BUF_TYPE_VIDEO_CAPTURE) { + continue; /* TODO V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE support */ + } + + addr =3D mmap(NULL, v4l2_buf.length, PROT_READ | PROT_WRITE, MAP_S= HARED, + v4l2->devfd, v4l2_buf.m.offset); + if (addr =3D=3D MAP_FAILED) { + goto error; + } + + if (camera_v4l2_qbuf(camera, index) < 0) { + goto error; + } + + buffer =3D &v4l2->buffers[index]; + buffer->addr =3D addr; + buffer->length =3D v4l2_buf.length; + } + + return 0; + +error: + camera_v4l2_free_buffers(camera); + + return -errno; +} + +static void camera_v4l2_open(QEMUCamera *camera, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + struct v4l2_capability v4l2_cap =3D {0}; + + if (v4l2opts->has_buffers) { + if (!v4l2opts->buffers || v4l2opts->buffers > CAMERA_V4L2_BUFFER_M= AX) { + error_setg(errp, "%s: zero buffers or too large(max %d)", + TYPE_CAMERA_V4L2, CAMERA_V4L2_BUFFER_MAX); + return; + } + + v4l2->nbuffers =3D v4l2opts->buffers; + } else { + v4l2->nbuffers =3D CAMERA_V4L2_BUFFER_DEF; + } + + if (!v4l2opts->has_path) { + error_setg(errp, "%s: missing device path", TYPE_CAMERA_V4L2); + return; + } + + v4l2->devfd =3D open(v4l2opts->path, O_RDWR | O_NONBLOCK); + if (v4l2->devfd =3D=3D -1) { + error_setg(errp, "%s: open device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + return; + } + + if (ioctl(v4l2->devfd, VIDIOC_QUERYCAP, &v4l2_cap) < 0) { + error_setg(errp, "%s: query device %s failed, %s", + TYPE_CAMERA_V4L2, v4l2opts->path, strerror(errno)); + goto error; + } + + if (!(v4l2_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) || + !(v4l2_cap.device_caps & V4L2_CAP_VIDEO_CAPTURE)) { + error_setg(errp, "%s: %s is not a video capture device", + TYPE_CAMERA_V4L2, v4l2opts->path); + goto error; + } + + if (camera_v4l2_get_format(camera, NULL, errp) < 0) { + goto error; + } + + return; + +error: + if (v4l2->devfd > 0) { + close(v4l2->devfd); + } +} + +static void camera_v4l2_read_handler(void *opaque) +{ + QEMUCamera *camera =3D (QEMUCamera *)opaque; + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Buffer *buffer; + int index; + + index =3D camera_v4l2_dqbuf(camera); + if (index < 0) { + return; + } + + buffer =3D &v4l2->buffers[index]; + qemu_camera_new_image(camera, buffer->addr, buffer->length); + + camera_v4l2_qbuf(camera, index); +} + +static void camera_v4l2_stream_on(QEMUCamera *camera, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + int type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + + qemu_camera_alloc_image(camera, v4l2->sizeimage, errp); + + if (camera_v4l2_request_buffers(camera, errp)) { + return; + } + + if (ioctl(v4l2->devfd, VIDIOC_STREAMON, &type) < 0) { + error_setg(errp, "%s: stream on failed on %s", + TYPE_CAMERA_V4L2, v4l2opts->path); + camera_v4l2_free_buffers(camera); + return; + } + + qemu_set_fd_handler(v4l2->devfd, camera_v4l2_read_handler, NULL, camer= a); +} + +static void camera_v4l2_stream_off(QEMUCamera *camera, Error **errp) +{ + CameraV4l2 *v4l2 =3D CAMERA_V4L2_DEV(camera); + CameraV4l2Options *v4l2opts =3D &camera->dev->u.v4l2; + int type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + + qemu_set_fd_handler(v4l2->devfd, NULL, NULL, camera); + + if (ioctl(v4l2->devfd, VIDIOC_STREAMOFF, &type) < 0) { + error_setg(errp, "%s: stream off failed on %s", + TYPE_CAMERA_V4L2, v4l2opts->path); + } + + camera_v4l2_free_buffers(camera); + + qemu_camera_free_image(camera); +} + +static void camera_v4l2_init(Object *obj) +{ +} + +static void camera_v4l2_finalize(Object *obj) +{ + QEMUCamera *camera =3D CAMERADEV(obj); + Error *local_err =3D NULL; + + camera_v4l2_stream_off(camera, &local_err); +} + +static void camera_v4l2_class_init(ObjectClass *oc, void *data) +{ + QEMUCameraClass *klass =3D CAMERADEV_CLASS(oc); + + klass->open =3D camera_v4l2_open; + klass->stream_on =3D camera_v4l2_stream_on; + klass->stream_off =3D camera_v4l2_stream_off; + klass->enum_pixel_format =3D camera_v4l2_enum_pixel_format; + klass->enum_frame_size =3D camera_v4l2_enum_frame_size; + klass->enum_frame_interval =3D camera_v4l2_enum_frame_interval; + klass->set_frame_interval =3D camera_v4l2_set_frame_interval; + klass->enum_control =3D camera_v4l2_enum_control; + klass->set_control =3D camera_v4l2_set_control; +} + +static const TypeInfo camera_v4l2_type_info =3D { + .name =3D TYPE_CAMERA_V4L2, + .parent =3D TYPE_CAMERADEV, + .instance_size =3D sizeof(CameraV4l2), + .instance_init =3D camera_v4l2_init, + .instance_finalize =3D camera_v4l2_finalize, + .class_init =3D camera_v4l2_class_init, +}; + +static void register_types(void) +{ + type_register_static(&camera_v4l2_type_info); +} + +type_init(register_types); diff --git a/qapi/camera.json b/qapi/camera.json index 2c8314ba4a..763f7f0c57 100644 --- a/qapi/camera.json +++ b/qapi/camera.json @@ -9,6 +9,22 @@ # =3D Camera ## =20 +## +# @CameraV4l2Options: +# +# Options of the v4l2 camera. +# +# @path: video capture device path +# +# @buffers: buffer count of v4l2 driver +# +# Since: 6.3 +## +{ 'struct': 'CameraV4l2Options', + 'data': { + '*path': 'str', + '*buffers': 'uint32' } } + ## # @ColorType: # @@ -62,7 +78,7 @@ # Since: 6.3 ## { 'enum': 'CameradevDriver', - 'data': [ 'builtin' ] } + 'data': [ 'builtin', 'v4l2' ] } =20 ## # @Cameradev: @@ -81,4 +97,5 @@ 'driver': 'CameradevDriver'}, 'discriminator': 'driver', 'data': { - 'builtin': 'CameraBuiltinOptions' } } + 'builtin': 'CameraBuiltinOptions', + 'v4l2': 'CameraV4l2Options' } } diff --git a/qemu-options.hx b/qemu-options.hx index fa439134dc..60975d6c3d 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -3535,6 +3535,9 @@ DEF("cameradev", HAS_ARG, QEMU_OPTION_cameradev, "-cameradev help\n" #ifdef CONFIG_CAIRO "-cameradev builtin,id=3Did[,debug=3Dtrue|false][,fps=3DFPS][,width=3D= WIDTH][,height=3DHEIGHT][,mjpeg=3Dtrue|false][,yuv=3Dtrue|false][,rgb565=3D= true|false][,bgcolor=3Dblue|gree|red|rainbow|digital-rain]\n" +#endif +#ifdef CONFIG_LINUX + "-cameradev v4l2,id=3Did[,path=3DPATH][,buffers=3DCOUNT]\n" #endif , QEMU_ARCH_ALL ) --=20 2.25.1 From nobody Fri Sep 20 22:16:20 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; 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; dmarc=fail(p=none dis=none) header.from=bytedance.com ARC-Seal: i=1; a=rsa-sha256; t=1640615448; cv=none; d=zohomail.com; s=zohoarc; b=blPGz0G8O7L9mcB8EPVFHEkp0EbjLB5x+WeKmy6EdtyvOKbVCqWAiJec3LeQ5TR1D0c+5XoYYxuUmt18oLwosQmzfiJ4kcCCA9mwtpOIdDFANLzjLzbizEmOQFtaqLXDM3vbd1/8CEAz73Rowh39acCQaEhgImt6l6R3sszzdjQ= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1640615448; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=tqTtnT1TEOPMErpZuVNDwuohtAdiw2v+obbt+THHjU4=; b=Q9Pduk3TxDK01hyPxkHNL15K8maykyxwmVSaX1hUR3MCvNQWcpjwbZ7IV33r6JjBNBurAzRC7P/OO4cDvdw9aBHvla+fME5/kUDOZZSxrkLlw2biniApC14od5N0I6/O3v0AveSeIpo+LsvKVUG63BKcOlc+QJXelbtFaaq79Kw= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; 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; dmarc=fail header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1640615448791344.08258915657336; Mon, 27 Dec 2021 06:30:48 -0800 (PST) Received: from localhost ([::1]:40418 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1n1r1T-0004Q6-Oo for importer@patchew.org; Mon, 27 Dec 2021 09:30:47 -0500 Received: from eggs.gnu.org ([209.51.188.92]:54862) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1n1qzP-0001jw-7R for qemu-devel@nongnu.org; Mon, 27 Dec 2021 09:28:39 -0500 Received: from [2607:f8b0:4864:20::62c] (port=33451 helo=mail-pl1-x62c.google.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1n1qzN-00032P-MB for qemu-devel@nongnu.org; Mon, 27 Dec 2021 09:28:38 -0500 Received: by mail-pl1-x62c.google.com with SMTP id i6so5480451pla.0 for ; Mon, 27 Dec 2021 06:28:37 -0800 (PST) Received: from libai.bytedance.net ([153.254.110.96]) by smtp.gmail.com with ESMTPSA id p2sm15072916pgn.73.2021.12.27.06.28.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 27 Dec 2021 06:28:36 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bytedance-com.20210112.gappssmtp.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=tqTtnT1TEOPMErpZuVNDwuohtAdiw2v+obbt+THHjU4=; b=Oug5mnmJjCAp4gIu03fOknpsdN52rgRNnSaXsjx/Koa4OXq4ilog1tvqblB4L1Lf/Y YneRo2Ul0e08eR/l3gIodv1eeUxR1aKP+ekTDdqjH8SELq8bs6eAKdlBAkARX91obvUV v41zMNz1YtcRmDXbDLeIg9N0OFBLQMrJnS+/eT4NZjXCkoZ6sVxo6aM36hya3DoRwKyq RdEjT12Ay5yyI2ZFVsjWJ46Sk+VuUqHrqkzdfcZALWCMmQ3AiZd8NTdc0B9TI4RalcGa kURaNjUsFjUSxGd7+RvMorrDauHlrHdAPQ4K79EGV932VgENm72jDRWCECtzfMAyFEuH HpNQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=tqTtnT1TEOPMErpZuVNDwuohtAdiw2v+obbt+THHjU4=; b=DgGawC/a94SbMaI9xkHeYQH4uKvqsDWnVZEySJeaHtyDk+dvXbPV28dT9uQspzOPxX SS2CMd6DIAxc/vxIeYk9criypL+TxGLVXcFl+cRKVtHPplCUcaZGMaoMAQgtgbcxM7Zr ROzSp4fxW4C4ukeYH3BVCXVLN7Wdj04ba2EC3v4vGShov8qtb23Okf1lzBGkYp4m6FBH QkflB4FMtS3OQ+/jTEmvfAfmDAgXEaSAJ6cvgOhdoGy1GwUqAqAgfnDqxDs9TKtCE2xM eRJAmlOIUc/Fck9UL9zWJOlf/aX/TmhS9yNVh5hb04Ijws9TsAQrhkrJDrtViQbNiWpt Jzaw== X-Gm-Message-State: AOAM533Id3pg67TqWnM1aIPBnxQ235hnYu8rhxzU+16F4eknkpWVWeD9 oe8BRv7Q2PvBAQtVnDxHCSuftw== X-Google-Smtp-Source: ABdhPJxn9NFOf2ONAOpk+f6gdsd+gQNM3yoZRYvnnQ+3Pyhtc+M5N9e+XeyWH3vIlGJrMaqhD6RMuA== X-Received: by 2002:a17:902:ec81:b0:148:f1f7:2989 with SMTP id x1-20020a170902ec8100b00148f1f72989mr17576247plg.169.1640615316575; Mon, 27 Dec 2021 06:28:36 -0800 (PST) From: zhenwei pi To: peter.maydell@linaro.org, richard.henderson@linaro.org, kraxel@redhat.com, eblake@redhat.com, pbonzini@redhat.com Subject: [PATCH 3/5] usb: Introduce video&mescellaneous Date: Mon, 27 Dec 2021 22:27:32 +0800 Message-Id: <20211227142734.691900-4-pizhenwei@bytedance.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20211227142734.691900-1-pizhenwei@bytedance.com> References: <20211227142734.691900-1-pizhenwei@bytedance.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Host-Lookup-Failed: Reverse DNS lookup failed for 2607:f8b0:4864:20::62c (failed) 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=2607:f8b0:4864:20::62c; envelope-from=pizhenwei@bytedance.com; helo=mail-pl1-x62c.google.com X-Spam_score_int: -10 X-Spam_score: -1.1 X-Spam_bar: - X-Spam_report: (-1.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RDNS_NONE=0.793, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: qemu-devel@nongnu.org, zhenwei pi Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: pass (identity @bytedance-com.20210112.gappssmtp.com) X-ZM-MESSAGEID: 1640615587255000002 Content-Type: text/plain; charset="utf-8" Define USB class code for video&mescellaneous. Signed-off-by: zhenwei pi --- include/hw/usb.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/hw/usb.h b/include/hw/usb.h index 33668dd0a9..8e3d30b9f4 100644 --- a/include/hw/usb.h +++ b/include/hw/usb.h @@ -77,9 +77,11 @@ #define USB_CLASS_PRINTER 7 #define USB_CLASS_MASS_STORAGE 8 #define USB_CLASS_HUB 9 +#define USB_CLASS_VIDEO 0xe #define USB_CLASS_CDC_DATA 0x0a #define USB_CLASS_CSCID 0x0b #define USB_CLASS_CONTENT_SEC 0x0d +#define USB_CLASS_MISCELLANEOUS 0xef #define USB_CLASS_APP_SPEC 0xfe #define USB_CLASS_VENDOR_SPEC 0xff =20 --=20 2.25.1 From nobody Fri Sep 20 22:16:20 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; 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; dmarc=fail(p=none dis=none) header.from=bytedance.com ARC-Seal: i=1; a=rsa-sha256; t=1640615655; cv=none; d=zohomail.com; s=zohoarc; b=bOkvh4bI1zkF15PTdekr9zgENYKWJ3XSPIxrIujK9KqVpU4V004ciRKbGVPDiP2w+igh+6qd2uHcaPoLsVHJgc1y5HTxYZ3r3NIfwas69O7+yhdD9vjKaD7/cXwLjY4O+B+/mJjvr301l35ylDM78vxUS/lE1MuOhg3nwKFRqww= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1640615655; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=0fW4HyjuAp36AbIw73RCVLFwUwkSsst1cjVitHSDhEw=; b=HDl7hBv98xqTPnFoOur/fESHlRfSUImHKTnGCwNiawkMLmEU2FuBLG8vpTGWPrFwhrXWzaIc/fZflOBAyqJRH+vYCeBGSH8V2uTQTOV8unhEHlCgm+EPc4JDGkrQgQ/nKl0DTG2C5E6hQ7So/c2BbB4lFCWR13P76iQLBegS+gM= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; 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; dmarc=fail header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1640615655026948.2949152014177; Mon, 27 Dec 2021 06:34:15 -0800 (PST) Received: from localhost ([::1]:48988 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1n1r4n-0001n6-9X for importer@patchew.org; Mon, 27 Dec 2021 09:34:13 -0500 Received: from eggs.gnu.org ([209.51.188.92]:54878) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1n1qzU-0001nw-5g for qemu-devel@nongnu.org; Mon, 27 Dec 2021 09:28:44 -0500 Received: from [2607:f8b0:4864:20::434] (port=33578 helo=mail-pf1-x434.google.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1n1qzQ-000330-Rf for qemu-devel@nongnu.org; Mon, 27 Dec 2021 09:28:43 -0500 Received: by mail-pf1-x434.google.com with SMTP id 205so13844580pfu.0 for ; Mon, 27 Dec 2021 06:28:40 -0800 (PST) Received: from libai.bytedance.net ([153.254.110.96]) by smtp.gmail.com with ESMTPSA id p2sm15072916pgn.73.2021.12.27.06.28.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 27 Dec 2021 06:28:39 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bytedance-com.20210112.gappssmtp.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=0fW4HyjuAp36AbIw73RCVLFwUwkSsst1cjVitHSDhEw=; b=YFA11a7BNSwZAiQ56ogFvwy6ldgApwKzKkiyxEuc/j5Im711w6GHG+RjdXHhEufJQf cTxQYE6wN2u9qEx6zv5D18EdYLte27Tn6R3v/4hlrnindniai56gV7Uor4GEiudPU6i9 l3upuHIu+mkaycjZGuO0gfqX9fLSNW6vexQCyPB0+ADsMMobUXvMNcjGLMvI2r4kazE1 AC3E8Q53zmK/h9dbK/y8zRjZJfvaTTIbwiggUJNGzXzCVF+Qs/05Oh0cdOVA3nCo9eGj NUhB077gk5LEcWP+UXO93ntVBD0W2axdVSXc+XYDXYKH0bMj6YF6e8SQAQgmyiqhoWFp c6ew== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=0fW4HyjuAp36AbIw73RCVLFwUwkSsst1cjVitHSDhEw=; b=Fpz8IGcDJ1GRTgG5n7LkOHw+Zi89CQJyAaGqB7S03Lmeb+OyhZ9k3qlLwisiRZPVwS aOSPxRsyDlXVSvV0kzcYb2btS1pZ9erTDp3FzmqVmvjmU76gGepxIoit389CTC7DFl+M 6YXY4B6+vXpUWZkGCzlxOEl1k1wqN6VOFza9tS2belZJdD9C7XHyXsU0k0Uo8KXHsTUl yXAUOIBfznA8f327pJau43nXErr4Xl8eHCugMu7Bzzyy5AeMPx9fDdQkB3Pie1rQMV6l sfRKz2v21TDxKmpVMCdGzmTPggXXC5fdKRnAAU5kc+R4LxLj5z44HBxK4pgp0pq1vXPQ vwig== X-Gm-Message-State: AOAM5303DcbxcdC3rDPz9zRXqqAljk06vKSSjUGopWF2K3s8sEKE7tHh 2uslzwH1CG55frJN4bNfU0e/7w== X-Google-Smtp-Source: ABdhPJzWymqRdz89AyooQwb/vDLjvlhGN6+bPUMJVCoSfQbyP2pfp5Xt1r3dG/oI3DJnNG+hKXHf+w== X-Received: by 2002:a63:d34e:: with SMTP id u14mr15724473pgi.327.1640615319419; Mon, 27 Dec 2021 06:28:39 -0800 (PST) From: zhenwei pi To: peter.maydell@linaro.org, richard.henderson@linaro.org, kraxel@redhat.com, eblake@redhat.com, pbonzini@redhat.com Subject: [PATCH 4/5] usb: allow max 8192 bytes for desc Date: Mon, 27 Dec 2021 22:27:33 +0800 Message-Id: <20211227142734.691900-5-pizhenwei@bytedance.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20211227142734.691900-1-pizhenwei@bytedance.com> References: <20211227142734.691900-1-pizhenwei@bytedance.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Host-Lookup-Failed: Reverse DNS lookup failed for 2607:f8b0:4864:20::434 (failed) 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=2607:f8b0:4864:20::434; envelope-from=pizhenwei@bytedance.com; helo=mail-pf1-x434.google.com X-Spam_score_int: -10 X-Spam_score: -1.1 X-Spam_bar: - X-Spam_report: (-1.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RDNS_NONE=0.793, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: qemu-devel@nongnu.org, zhenwei pi Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: pass (identity @bytedance-com.20210112.gappssmtp.com) X-ZM-MESSAGEID: 1640615776214000001 Content-Type: text/plain; charset="utf-8" A device of USB video class usually uses larger desc structure, so use larger buffer to avoid failure. Signed-off-by: zhenwei pi Reviewed-by: Daniel P. Berrang=C3=A9 Reviewed-by: Philippe Mathieu-Daud=C3=A9 --- hw/usb/desc.c | 15 ++++++++------- hw/usb/desc.h | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/hw/usb/desc.c b/hw/usb/desc.c index 8b6eaea407..7f6cc2f99b 100644 --- a/hw/usb/desc.c +++ b/hw/usb/desc.c @@ -632,7 +632,8 @@ int usb_desc_get_descriptor(USBDevice *dev, USBPacket *= p, bool msos =3D (dev->flags & (1 << USB_DEV_FLAG_MSOS_DESC_IN_USE)); const USBDesc *desc =3D usb_device_get_usb_desc(dev); const USBDescDevice *other_dev; - uint8_t buf[256]; + size_t buflen =3D USB_DESC_MAX_LEN; + g_autofree uint8_t *buf =3D g_malloc(buflen); uint8_t type =3D value >> 8; uint8_t index =3D value & 0xff; int flags, ret =3D -1; @@ -650,36 +651,36 @@ int usb_desc_get_descriptor(USBDevice *dev, USBPacket= *p, =20 switch(type) { case USB_DT_DEVICE: - ret =3D usb_desc_device(&desc->id, dev->device, msos, buf, sizeof(= buf)); + ret =3D usb_desc_device(&desc->id, dev->device, msos, buf, buflen); trace_usb_desc_device(dev->addr, len, ret); break; case USB_DT_CONFIG: if (index < dev->device->bNumConfigurations) { ret =3D usb_desc_config(dev->device->confs + index, flags, - buf, sizeof(buf)); + buf, buflen); } trace_usb_desc_config(dev->addr, index, len, ret); break; case USB_DT_STRING: - ret =3D usb_desc_string(dev, index, buf, sizeof(buf)); + ret =3D usb_desc_string(dev, index, buf, buflen); trace_usb_desc_string(dev->addr, index, len, ret); break; case USB_DT_DEVICE_QUALIFIER: if (other_dev !=3D NULL) { - ret =3D usb_desc_device_qualifier(other_dev, buf, sizeof(buf)); + ret =3D usb_desc_device_qualifier(other_dev, buf, buflen); } trace_usb_desc_device_qualifier(dev->addr, len, ret); break; case USB_DT_OTHER_SPEED_CONFIG: if (other_dev !=3D NULL && index < other_dev->bNumConfigurations) { ret =3D usb_desc_config(other_dev->confs + index, flags, - buf, sizeof(buf)); + buf, buflen); buf[0x01] =3D USB_DT_OTHER_SPEED_CONFIG; } trace_usb_desc_other_speed_config(dev->addr, index, len, ret); break; case USB_DT_BOS: - ret =3D usb_desc_bos(desc, buf, sizeof(buf)); + ret =3D usb_desc_bos(desc, buf, buflen); trace_usb_desc_bos(dev->addr, len, ret); break; =20 diff --git a/hw/usb/desc.h b/hw/usb/desc.h index 3ac604ecfa..35babdeff6 100644 --- a/hw/usb/desc.h +++ b/hw/usb/desc.h @@ -199,6 +199,7 @@ struct USBDesc { const USBDescMSOS *msos; }; =20 +#define USB_DESC_MAX_LEN 8192 #define USB_DESC_FLAG_SUPER (1 << 1) =20 /* little helpers */ --=20 2.25.1 From nobody Fri Sep 20 22:16:20 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; 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; dmarc=fail(p=none dis=none) header.from=bytedance.com ARC-Seal: i=1; a=rsa-sha256; t=1640615571; cv=none; d=zohomail.com; s=zohoarc; b=m2NmcOAQEijzUzSeyxnP4RW5fxgFLfHlvMBW43gqmkDpV7UIoBOKhvDSeUmBJBxLJzSviE66ITYwPcFa8xiOzK/z/ostL0bdETDBwyGl94fjwl08T72DB/AlcoVuS7fDz4/hH7ISVcXFAH2epHtNAcvgX6YyDtFeuXhsfqoFxKU= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1640615571; h=Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To; bh=6mEd/osbuSjOVpyi8udlcWfwjS3kdZ+LdcppIAR6sZk=; b=aA6UohEmlXcAROasu95Q+71I2ovpHPxw4nDxeG906rPZk2rtRSVfZUcQlKRXdzTnBosfwpodaa7zHMQ0AWcmw/JAg9A33NsHUrwA807kik72iNEVGAT38837fdHI1dKod7srm1nUa+Mi8O1NUX9grH0UglyezV+hGGOtmJImWK4= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; 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; dmarc=fail header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1640615571693744.7869160348664; Mon, 27 Dec 2021 06:32:51 -0800 (PST) Received: from localhost ([::1]:46004 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1n1r3S-0008Dp-T5 for importer@patchew.org; Mon, 27 Dec 2021 09:32:50 -0500 Received: from eggs.gnu.org ([209.51.188.92]:54908) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1n1qzX-0001ry-RF for qemu-devel@nongnu.org; Mon, 27 Dec 2021 09:28:48 -0500 Received: from [2607:f8b0:4864:20::102a] (port=50917 helo=mail-pj1-x102a.google.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1n1qzU-00033F-8F for qemu-devel@nongnu.org; Mon, 27 Dec 2021 09:28:47 -0500 Received: by mail-pj1-x102a.google.com with SMTP id gj24so13582495pjb.0 for ; Mon, 27 Dec 2021 06:28:43 -0800 (PST) Received: from libai.bytedance.net ([153.254.110.96]) by smtp.gmail.com with ESMTPSA id p2sm15072916pgn.73.2021.12.27.06.28.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 27 Dec 2021 06:28:42 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bytedance-com.20210112.gappssmtp.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=6mEd/osbuSjOVpyi8udlcWfwjS3kdZ+LdcppIAR6sZk=; b=NFssYictq+Kl7YNtSowW1kc0T2CmRuRRsX4olZ9A8voBKEPhRfJTa4MUZNaLAo+FhD BewHdbaAS7Dq/OJVhhLI4k8YzbuuF5ESiaH+l7Jml/RfpVmV6Bxvm8/k2f7MpevxxcZW /CWZ69MF82F+kErvg5IY/MF5YpKaw/830YykQGIA+p4gvA6bHidhqrWzD56fd4R+PSNz mzVIdKFGvqVYva145rh7FbckUoR9H/a82GjQwk3bK1Mq8Y4vIcd3fgaG6G0vS07kWrCI MFXKY1O0p6pUJ2LhzgbmLCgwDVM2t2L1lYR12PY+vsHq9SZKfHP4mZMvfYuBTu/ctxPg xKcQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=6mEd/osbuSjOVpyi8udlcWfwjS3kdZ+LdcppIAR6sZk=; b=eBKvGFOez6BJC74aT/OrWYgADxbDvkxEzULOqSF9Ayb3JIizUwEEYQt9rPv9ZQMmuC vsfe/+6u5eDjCUz91N2y1Mk7z/gnZnDHOgg6uMxrCzVoK3V3+5FXWQFSWucmignfIjTT 1lyrDIORAJzw2rg8ac7G8RzDCYw7afz1kcXV60Xwu538fxTstfKkhYjpOp6NzMlrPrFV pTKVjYv+zg3wFYcQBWpfDcIIhWfQZGG4b8SMA0duS6/u27ma5sbzoMZHDchThdCjSO2Z c8jcKMl9NTZb26l5N+ewdHr5bw5fxY9e5hjZS6wgj5DJrtcrgXq81R0cAB+83SDTTrMC zR+w== X-Gm-Message-State: AOAM5332IpEwF/QELql+2sWfNx4vZe6HFy8bOIJWc1DgrAri0UxGQcLl XbmIQ5xrOxoq4mc1gAioFniZrA== X-Google-Smtp-Source: ABdhPJwRiAxBajhowDhv3RPh1av0kWWKtuOTmlddsEiO4vCMa0Ij8TOqqe3W9R1pEe4N/VetQ/TIKQ== X-Received: by 2002:a17:902:b08b:b0:149:88bb:b477 with SMTP id p11-20020a170902b08b00b0014988bbb477mr2232638plr.137.1640615322533; Mon, 27 Dec 2021 06:28:42 -0800 (PST) From: zhenwei pi To: peter.maydell@linaro.org, richard.henderson@linaro.org, kraxel@redhat.com, eblake@redhat.com, pbonzini@redhat.com Subject: [PATCH 5/5] usb-video: Introduce USB video class Date: Mon, 27 Dec 2021 22:27:34 +0800 Message-Id: <20211227142734.691900-6-pizhenwei@bytedance.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20211227142734.691900-1-pizhenwei@bytedance.com> References: <20211227142734.691900-1-pizhenwei@bytedance.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Host-Lookup-Failed: Reverse DNS lookup failed for 2607:f8b0:4864:20::102a (failed) 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=2607:f8b0:4864:20::102a; envelope-from=pizhenwei@bytedance.com; helo=mail-pj1-x102a.google.com X-Spam_score_int: -10 X-Spam_score: -1.1 X-Spam_bar: - X-Spam_report: (-1.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RDNS_NONE=0.793, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: qemu-devel@nongnu.org, zhenwei pi Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail-DKIM: pass (identity @bytedance-com.20210112.gappssmtp.com) X-ZM-MESSAGEID: 1640615695997000001 Content-Type: text/plain; charset="utf-8" Base on UVC specification 1.5, implement UVC device emulation(camera only). Several changes in this patch: 1, define types and structures(in include/hw/usb/video.h) 2, a camera device with UVC chain: OT 3 <- PU 5 <- SU 4 <- IT a, video control descriptor. (auto-detected bmControl in PU) b, video streaming descriptor. (auto-detected pixel format, frame size and frame interval, build .descs dynamicly during .realize) c, standard VS isochronous video data endpoint 3, support data payload without PresentationTime & scrSourceClock. 4, support brightness, hue ... control settings. 5, support control status interrupt. Test guest Ubuntu-2004 desktop: 1, several applications: cheese, kamoso, guvcview, qcam(self-built from libcamera source code), all work fine. 2, both builtin and v4l2 driver work fine. Signed-off-by: zhenwei pi --- docs/system/devices/usb.rst | 3 + hw/usb/Kconfig | 5 + hw/usb/dev-video.c | 1395 +++++++++++++++++++++++++++++++++++ hw/usb/meson.build | 1 + hw/usb/trace-events | 11 + include/hw/usb/video.h | 303 ++++++++ 6 files changed, 1718 insertions(+) create mode 100644 hw/usb/dev-video.c create mode 100644 include/hw/usb/video.h diff --git a/docs/system/devices/usb.rst b/docs/system/devices/usb.rst index afb7d6c226..bf84e3e3d9 100644 --- a/docs/system/devices/usb.rst +++ b/docs/system/devices/usb.rst @@ -199,6 +199,9 @@ option or the ``device_add`` monitor command. Available= devices are: ``u2f-{emulated,passthru}`` Universal Second Factor device =20 +``usb-video`` + USB video device + Physical port addressing ^^^^^^^^^^^^^^^^^^^^^^^^ =20 diff --git a/hw/usb/Kconfig b/hw/usb/Kconfig index 53f8283ffd..1355db7989 100644 --- a/hw/usb/Kconfig +++ b/hw/usb/Kconfig @@ -133,3 +133,8 @@ config XLNX_USB_SUBSYS bool default y if XLNX_VERSAL select USB_DWC3 + +config USB_VIDEO + bool + default y + depends on USB diff --git a/hw/usb/dev-video.c b/hw/usb/dev-video.c new file mode 100644 index 0000000000..e9217061a0 --- /dev/null +++ b/hw/usb/dev-video.c @@ -0,0 +1,1395 @@ +/* + * UVC Device emulation, base on UVC specification 1.5 + * + * Copyright 2021 Bytedance, Inc. + * + * Authors: + * zhenwei pi + * + * This work is licensed under the terms of the GNU GPL, version 2 or late= r. + * See the COPYING file in the top-level directory. + */ +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "qemu/log.h" +#include "qom/object.h" +#include "qapi/error.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "hw/usb.h" +#include "hw/usb/video.h" +#include "camera/camera.h" +#include "desc.h" +#include "trace.h" + +#define USBVIDEO_VENDOR_NUM 0x46f4 /* CRC16() of "QEMU" */ +#define USBVIDEO_PRODUCT_NUM 0x0e01 + +enum usb_video_strings { + STRING_NULL, + STRING_MANUFACTURER, + STRING_PRODUCT, + STRING_SERIALNUMBER, + STRING_CONFIG, + STRING_INTERFACE_ASSOCIATION, + STRING_VIDEO_CONTROL, + STRING_INPUT_TERMINAL, + STRING_SELECTOR_UNIT, + STRING_PROCESSING_UNIT, + STRING_OUTPUT_TERMINAL, + STRING_VIDEO_STREAMING, + STRING_VIDEO_STREAMING_ALTERNATE1, +}; + +static const USBDescStrings usb_video_stringtable =3D { + [STRING_MANUFACTURER] =3D "QEMU", + [STRING_PRODUCT] =3D "QEMU USB Video", + [STRING_SERIALNUMBER] =3D "1", + [STRING_CONFIG] =3D "Video Configuration", + [STRING_INTERFACE_ASSOCIATION] =3D "Integrated Camera", + [STRING_VIDEO_CONTROL] =3D "Video Control", + [STRING_INPUT_TERMINAL] =3D "Video Input Terminal", + [STRING_SELECTOR_UNIT] =3D "Video Selector Unit", + [STRING_PROCESSING_UNIT] =3D "Video Processing Unit", + [STRING_OUTPUT_TERMINAL] =3D "Video Output Terminal", + [STRING_VIDEO_STREAMING] =3D "Video Streaming", + [STRING_VIDEO_STREAMING_ALTERNATE1] =3D "Video Streaming Alternate Set= ting 1", +}; + +/* Interface IDs */ +#define IF_CONTROL 0x0 +#define IF_STREAMING 0x1 + +/* Endpoint IDs */ +#define EP_CONTROL 0x1 +#define EP_STREAMING 0x2 + +/* Terminal IDs */ +#define INPUT_TERMINAL 0x1 +#define OUTPUT_TERMINAL 0x3 + +/* XU IDs */ +#define SELECTOR_UNIT 0x4 +#define PROCESSING_UNIT 0x5 +#define ENCODING_UNIT 0x6 + +/* Alternate Settings */ +#define ALTSET_OFF 0x0 +#define ALTSET_STREAMING 0x1 + +#define U16(x) ((x) & 0xff), (((x) >> 8) & 0xff) +#define U24(x) U16(x), (((x) >> 16) & 0xff) +#define U32(x) U24(x), (((x) >> 24) & 0xff) + +/* + * Note that desc_ifaces works as template, because UVC need to detect + * format/frame/interval from backend, and built the interfaces dynamically + */ +static const USBDescIface desc_ifaces[] =3D { + { + /* VideoControl Interface Descriptor */ + .bInterfaceNumber =3D IF_CONTROL, + .bNumEndpoints =3D 1, + .bInterfaceClass =3D USB_CLASS_VIDEO, + .bInterfaceSubClass =3D SC_VIDEOCONTROL, + .bInterfaceProtocol =3D PC_PROTOCOL_15, + .iInterface =3D STRING_VIDEO_CONTROL, + .ndesc =3D 5, + .descs =3D (USBDescOther[]) { + { + /* Class-specific VS Interface Input Header Descriptor */ + .data =3D (uint8_t[]) { + 0x0d, /* u8 bLength */ + CS_INTERFACE, /* u8 bDescriptorType */ + VC_HEADER, /* u8 bDescriptorSubtype */ + U16(0x0110), /* u16 bcdADC */ + U16(0x3b), /* u16 wTotalLength */ + U32(0x005B8D80), /* u32 dwClockFrequency */ + 0x01, /* u8 bInCollection */ + 0x01, /* u8 baInterfaceNr */ + } + }, { + /* Input Terminal Descriptor (Camera) */ + .data =3D (uint8_t[]) { + 0x11, /* u8 bLength */ + CS_INTERFACE, /* u8 bDescriptorType */ + VC_INPUT_TERMINAL, /* u8 bDescriptorSubtype */ + INPUT_TERMINAL, /* u8 bTerminalID */ + U16(ITT_CAMERA), /* u16 wTerminalType */ + 0x00, /* u8 bAssocTerminal */ + STRING_INPUT_TERMINAL, /* u8 iTerminal */ + U16(0x0000), /* u16 wObjectiveFocalLength= Min */ + U16(0x0000), /* u16 wObjectiveFocalLength= Max */ + U16(0x0000), /* u16 wOcularFocalLength */ + 0x02, /* u8 bControlSize */ + U16(0x0000), /* u16 bmControls */ + } + }, { + /* Output Terminal Descriptor */ + .data =3D (uint8_t[]) { + 0x09, /* u8 bLength */ + CS_INTERFACE, /* u8 bDescriptorType */ + VC_OUTPUT_TERMINAL, /* u8 bDescriptorSubtype */ + OUTPUT_TERMINAL, /* u8 bTerminalID */ + U16(TT_STREAMING), /* u16 wTerminalType */ + 0x00, /* u8 bAssocTerminal */ + PROCESSING_UNIT, /* u8 bSourceID */ + STRING_OUTPUT_TERMINAL, /* u8 iTerminal */ + } + }, { + /* Selector Unit Descriptor */ + .data =3D (uint8_t[]) { + 0x08, /* u8 bLength */ + CS_INTERFACE, /* u8 bDescriptorType */ + VC_SELECTOR_UNIT, /* u8 bDescriptorSubtype */ + SELECTOR_UNIT, /* u8 bUnitID */ + 1, /* u8 bNrInPins */ + INPUT_TERMINAL, /* u8 baSourceID(1) */ + STRING_SELECTOR_UNIT, /* u8 iSelector */ + } + }, { + /* Processing Unit Descriptor */ + .data =3D (uint8_t[]) { + 0x0d, /* u8 bLength */ + CS_INTERFACE, /* u8 bDescriptorType */ + VC_PROCESSING_UNIT, /* u8 bDescriptorSubtype */ + PROCESSING_UNIT, /* u8 bUnitID */ + SELECTOR_UNIT, /* u8 bSourceID */ + U16(0x0000), /* u16 wMaxMultiplier */ + 0x03, /* u8 bControlSize */ + U24(0x000000), /* u24 bmControls */ + STRING_PROCESSING_UNIT, /* u8 iProcessing */ + 0x00, /* u8 bmVideoStandards */ + } + } + }, + .eps =3D (USBDescEndpoint[]) { + { + /* 3.8.2.1 Standard VC Interrupt Endpoint Descriptor */ + .bEndpointAddress =3D USB_DIR_IN | EP_CONTROL, + .bmAttributes =3D USB_ENDPOINT_XFER_INT, + .wMaxPacketSize =3D 0x40, + .bInterval =3D 0x20, + }, + }, + }, { + /* VideoStreaming Interface Descriptor */ + .bInterfaceNumber =3D IF_STREAMING, + .bAlternateSetting =3D ALTSET_OFF, + .bNumEndpoints =3D 0, + .bInterfaceClass =3D USB_CLASS_VIDEO, + .bInterfaceSubClass =3D SC_VIDEOSTREAMING, + .bInterfaceProtocol =3D PC_PROTOCOL_15, + .iInterface =3D STRING_VIDEO_STREAMING, + /* .ndesc & .descs are built dynamicly during .realize */ + }, { + /* Operational Alternate Setting 1 */ + .bInterfaceNumber =3D IF_STREAMING, + .bAlternateSetting =3D ALTSET_STREAMING, + .bNumEndpoints =3D 1, + .bInterfaceClass =3D USB_CLASS_VIDEO, + .bInterfaceSubClass =3D SC_VIDEOSTREAMING, + .bInterfaceProtocol =3D PC_PROTOCOL_15, + .iInterface =3D STRING_VIDEO_STREAMING_ALTERNAT= E1, + .eps =3D (USBDescEndpoint[]) { + { + /* + * 3.10.1.1 Standard VS Isochronous Video Data Endpoint + * Descriptor + */ + .bEndpointAddress =3D USB_DIR_IN | EP_STREAMING, + .bmAttributes =3D 0x05, /* TODO define BITs USB 9= .6.6 */ + .wMaxPacketSize =3D 1024, + .bInterval =3D 0x1, + }, + }, + } +}; + +static const USBDescIfaceAssoc desc_if_groups[] =3D { + { + .bFirstInterface =3D IF_CONTROL, + .bInterfaceCount =3D 2, + .bFunctionClass =3D USB_CLASS_VIDEO, + .bFunctionSubClass =3D SC_VIDEO_INTERFACE_COLLECTION, + .bFunctionProtocol =3D PC_PROTOCOL_UNDEFINED, + .iFunction =3D STRING_INTERFACE_ASSOCIATION, + }, +}; + +static const USBDescDevice desc_device_full =3D { + .bcdUSB =3D 0x0100, + .bDeviceClass =3D USB_CLASS_MISCELLANEOUS, + .bDeviceSubClass =3D 2, + .bDeviceProtocol =3D 1, /* Interface Association */ + .bMaxPacketSize0 =3D 8, + .bNumConfigurations =3D 1, + .confs =3D (USBDescConfig[]) { + { + .bNumInterfaces =3D 2, + .bConfigurationValue =3D 1, + .iConfiguration =3D STRING_CONFIG, + .bmAttributes =3D USB_CFG_ATT_ONE | USB_CFG_ATT_SELFP= OWER, + .bMaxPower =3D 0x32, + .nif_groups =3D ARRAY_SIZE(desc_if_groups), + .if_groups =3D desc_if_groups, + .nif =3D ARRAY_SIZE(desc_ifaces), + .ifs =3D desc_ifaces, + }, + }, +}; + +static const USBDescDevice desc_device_high =3D { + .bcdUSB =3D 0x0200, + .bDeviceClass =3D USB_CLASS_MISCELLANEOUS, + .bDeviceSubClass =3D 2, + .bDeviceProtocol =3D 1, /* Interface Association */ + .bMaxPacketSize0 =3D 64, + .bNumConfigurations =3D 1, + .confs =3D (USBDescConfig[]) { + { + .bNumInterfaces =3D 2, + .bConfigurationValue =3D 1, + .iConfiguration =3D STRING_CONFIG, + .bmAttributes =3D USB_CFG_ATT_ONE | USB_CFG_ATT_SELFP= OWER, + .bMaxPower =3D 0x32, + .nif_groups =3D ARRAY_SIZE(desc_if_groups), + .if_groups =3D desc_if_groups, + .nif =3D ARRAY_SIZE(desc_ifaces), + .ifs =3D desc_ifaces, + }, + }, +}; + +static const USBDesc desc_video =3D { + .id =3D { + .idVendor =3D USBVIDEO_VENDOR_NUM, + .idProduct =3D USBVIDEO_PRODUCT_NUM, + .bcdDevice =3D 0, + .iManufacturer =3D STRING_MANUFACTURER, + .iProduct =3D STRING_PRODUCT, + .iSerialNumber =3D STRING_SERIALNUMBER, + }, + .full =3D &desc_device_full, + .high =3D &desc_device_high, + .str =3D usb_video_stringtable, +}; + +enum AttributeIndex { + ATTRIBUTE_DEF, + ATTRIBUTE_MIN, + ATTRIBUTE_MAX, + ATTRIBUTE_CUR, + ATTRIBUTE_RES, + + ATTRIBUTE_ALL +}; + +typedef struct USBVideoControlStats { + VideoControlStatus status; + uint8_t size; /* value size in bytes */ + QTAILQ_ENTRY(USBVideoControlStats) list; +} USBVideoControlStats; + +typedef struct USBVideoControlInfo { + uint8_t selector; + uint8_t caps; + uint8_t size; + uint32_t value[ATTRIBUTE_ALL]; /* store in le32 */ +} USBVideoControlInfo; + +struct USBVideoState { + /* qemu interfaces */ + USBDevice dev; + + /* state */ + QEMUCamera *camera; + USBDesc desc_video; + USBDescDevice desc_device_full; + USBDescDevice desc_device_high; + USBDescIface desc_ifaces[ARRAY_SIZE(desc_ifaces)]; + USBDescOther *vs_descs; + uint8_t n_vs_descs; + uint8_t *vs_data; + + /* UVC control */ + int streaming_altset; + bool fid; + uint8_t error; + QTAILQ_HEAD(, USBVideoControlStats) control_status; + + /* video control attributes */ + USBVideoControlInfo pu_attrs[PU_MAX]; + + /* video streaming control attributes, vsc_attrs in little endian */ + uint8_t vsc_info; + uint16_t vsc_len; + VideoStreamingControl vsc_attrs[ATTRIBUTE_ALL]; + + /* properties */ + char *cameradev; + char *terminal; +}; + +static int usb_video_pu_control_bits(QEMUCameraControlType type) +{ + switch ((int)type) { + case QEMUCameraBrightness: + return PU_CONTRL_BRIGHTNESS; + case QEMUCameraContrast: + return PU_CONTRL_CONTRAST; + case QEMUCameraGain: + return PU_CONTRL_GAIN; + case QEMUCameraGamma: + return PU_CONTRL_GAMMA; + case QEMUCameraHue: + return PU_CONTRL_HUE; + case QEMUCameraHueAuto: + return PU_CONTRL_HUE_AUTO; + case QEMUCameraSaturation: + return PU_CONTRL_SATURATION; + case QEMUCameraSharpness: + return PU_CONTRL_SHARPNESS; + case QEMUCameraWhiteBalanceTemperature: + return PU_CONTRL_WHITE_BALANCE_TEMPERATURE; + } + + return 0; +} + +static int usb_video_pu_control_type(QEMUCameraControlType type, uint8_t *= size) +{ + switch ((int)type) { + case QEMUCameraBrightness: + *size =3D 2; + return PU_BRIGHTNESS_CONTROL; + case QEMUCameraContrast: + *size =3D 2; + return PU_CONTRAST_CONTROL; + case QEMUCameraGain: + *size =3D 2; + return PU_GAIN_CONTROL; + case QEMUCameraGamma: + *size =3D 2; + return PU_GAMMA_CONTROL; + case QEMUCameraHue: + *size =3D 2; + return PU_HUE_CONTROL; + case QEMUCameraHueAuto: + *size =3D 1; + return PU_HUE_AUTO_CONTROL; + case QEMUCameraSaturation: + *size =3D 2; + return PU_SATURATION_CONTROL; + case QEMUCameraSharpness: + *size =3D 2; + return PU_SHARPNESS_CONTROL; + case QEMUCameraWhiteBalanceTemperature: + *size =3D 2; + return PU_WHITE_BALANCE_TEMPERATURE_CONTROL; + } + + return 0; +} + +static QEMUCameraControlType usb_video_pu_control_type_to_qemu(uint8_t cs) +{ + switch (cs) { + case PU_BRIGHTNESS_CONTROL: + return QEMUCameraBrightness; + case PU_CONTRAST_CONTROL: + return QEMUCameraContrast; + case PU_GAIN_CONTROL: + return QEMUCameraGain; + case PU_GAMMA_CONTROL: + return QEMUCameraGamma; + case PU_HUE_CONTROL: + return QEMUCameraHue; + case PU_HUE_AUTO_CONTROL: + return QEMUCameraHueAuto; + case PU_SATURATION_CONTROL: + return QEMUCameraSaturation; + case PU_SHARPNESS_CONTROL: + return QEMUCameraSharpness; + case PU_WHITE_BALANCE_TEMPERATURE_CONTROL: + return QEMUCameraWhiteBalanceTemperature; + } + + return QEMUCameraControlMax; +} + +#define REQ_TO_ATTR(req, idx) \ + switch (req) { \ + case SET_CUR: \ + case GET_CUR: \ + idx =3D ATTRIBUTE_CUR; \ + break; \ + case GET_MIN: \ + idx =3D ATTRIBUTE_MIN; \ + break; \ + case GET_MAX: \ + idx =3D ATTRIBUTE_MAX; \ + break; \ + case GET_RES: \ + idx =3D ATTRIBUTE_RES; \ + break; \ + case GET_DEF: \ + idx =3D ATTRIBUTE_DEF; \ + break; \ + default: \ + idx =3D -1; \ + break; \ + } + +#define handle_get_control(attrs, req, cs, length, data, ret) = \ + do { = \ + if (!attrs[cs].selector) { = \ + break; = \ + } = \ + if ((req =3D=3D GET_INFO) && (length >=3D 1)) { = \ + *((uint8_t *)data) =3D attrs[cs].caps; = \ + ret =3D 1; = \ + } else if ((req =3D=3D GET_LEN) && (length >=3D 2)) { = \ + *((uint16_t *)data) =3D cpu_to_le16(attrs[cs].size); = \ + ret =3D 2; = \ + } else { = \ + int idx =3D -1; = \ + int len =3D MIN(length, sizeof(attrs[cs].size)); = \ + REQ_TO_ATTR(req, idx); = \ + if (idx >=3D 0) { = \ + memcpy(data, &attrs[cs].value[idx], len); = \ + ret =3D length; = \ + } = \ + } = \ + } while (0) + + +#define handle_get_streaming(s, req, cs, length, data, ret) = \ + do { = \ + if ((req =3D=3D GET_INFO) && (length >=3D 1)) { = \ + *((uint8_t *)data) =3D s->cs##_len; = \ + ret =3D 1; = \ + } else if ((req =3D=3D GET_LEN) && (length >=3D 2)) { = \ + *((uint16_t *)data) =3D cpu_to_le16(s->cs##_len); = \ + ret =3D 2; = \ + } else { = \ + int idx =3D -1; = \ + int len =3D MIN(length, sizeof(s->cs##_attrs[0])); = \ + REQ_TO_ATTR(req, idx); = \ + if (idx >=3D 0) { = \ + memcpy(data, s->cs##_attrs + idx, len); = \ + ret =3D length; = \ + } = \ + } = \ + } while (0) + +#define TYPE_USB_VIDEO "usb-video" +OBJECT_DECLARE_SIMPLE_TYPE(USBVideoState, USB_VIDEO) + +static uint32_t usb_video_vsfmt_to_pixfmt(const uint8_t *data) +{ + uint8_t bDescriptorSubtype =3D data[2]; + uint32_t pixfmt =3D 0; + + switch (bDescriptorSubtype) { + case VS_FORMAT_MJPEG: + return QEMU_CAMERA_PIX_FMT_MJPEG; + + case VS_FORMAT_UNCOMPRESSED: + pixfmt =3D *(uint32_t *)(data + 5); + if (pixfmt =3D=3D camera_fourcc_code('Y', 'U', 'Y', '2')) { + return QEMU_CAMERA_PIX_FMT_YUYV; + } else if (pixfmt =3D=3D camera_fourcc_code('R', 'G', 'B', 'P')) { + return QEMU_CAMERA_PIX_FMT_RGB565; + } + } + + return 0; +} + +static uint8_t usb_video_pixfmt_to_vsfmt(uint32_t pixfmt) +{ + switch (pixfmt) { + case QEMU_CAMERA_PIX_FMT_MJPEG: + return VS_FORMAT_MJPEG; + + case QEMU_CAMERA_PIX_FMT_YUYV: + case QEMU_CAMERA_PIX_FMT_RGB565: + return VS_FORMAT_UNCOMPRESSED; + } + + return VS_UNDEFINED; +} + +static uint8_t usb_video_pixfmt_to_vsfrm(uint32_t pixfmt) +{ + switch (pixfmt) { + case QEMU_CAMERA_PIX_FMT_MJPEG: + return VS_FRAME_MJPEG; + + case QEMU_CAMERA_PIX_FMT_YUYV: + case QEMU_CAMERA_PIX_FMT_RGB565: + return VS_FRAME_UNCOMPRESSED; + } + + return VS_UNDEFINED; +} + +static int usb_video_get_frmival_from_vsc(USBDevice *dev, + VideoStreamingControl *vsc, + QEMUCameraFrameInterval *frmival) +{ + USBVideoState *s =3D USB_VIDEO(dev); + USBDescOther *usb_desc; + uint32_t pixfmt =3D 0; + uint16_t width =3D 0, height =3D 0; + uint8_t bDescriptorSubtype; + uint8_t index; + + /* 1, search bFormatIndex */ + for (index =3D 0; index < s->n_vs_descs; index++) { + usb_desc =3D s->vs_descs + index; + if (usb_desc->data[0] < 4) { + return -ENODEV; + } + + bDescriptorSubtype =3D usb_desc->data[2]; + if ((bDescriptorSubtype =3D=3D VS_FORMAT_MJPEG) + || (bDescriptorSubtype =3D=3D VS_FORMAT_UNCOMPRESSED)) { + if (usb_desc->data[3] =3D=3D vsc->bFormatIndex) { + pixfmt =3D usb_video_vsfmt_to_pixfmt(usb_desc->data); + break; + } + } + } + + /* 2, search bFormatIndex */ + for (index++ ; pixfmt && index < s->n_vs_descs; index++) { + usb_desc =3D s->vs_descs + index; + if (usb_desc->data[0] < 4) { + return -ENODEV; + } + + bDescriptorSubtype =3D usb_desc->data[2]; + if ((bDescriptorSubtype =3D=3D VS_FRAME_MJPEG) + || (bDescriptorSubtype =3D=3D VS_FRAME_UNCOMPRESSED)) { + if (usb_desc->data[3] =3D=3D vsc->bFrameIndex) { + /* see Class-specific VS Frame Descriptor */ + width =3D le16_to_cpu(*(uint16_t *)(usb_desc->data + 5)); + height =3D le16_to_cpu(*(uint16_t *)(usb_desc->data + 7)); + break; + } + } else { + break; + } + } + + if (pixfmt && width && height) { + frmival->pixel_format =3D pixfmt; + frmival->width =3D width; + frmival->height =3D height; + frmival->type =3D QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE; + frmival->d.numerator =3D 30; /* prime number 2 * 3 * 5 */ + frmival->d.denominator =3D frmival->d.numerator * 10000000 + / le32_to_cpu(vsc->dwFrameInterval); + return 0; + } + + return -ENODEV; +} + +static void usb_video_queue_control_status(USBDevice *dev, uint8_t bOrigin= ator, + uint8_t bSelector, uint32_t *value, uint8_t size) +{ + USBVideoState *s =3D USB_VIDEO(dev); + USBBus *bus =3D usb_bus_from_device(dev); + USBVideoControlStats *usb_status; + VideoControlStatus *status; + + usb_status =3D g_malloc0(sizeof(USBVideoControlStats)); + usb_status->size =3D size; + status =3D &usb_status->status; + status->bStatusType =3D STATUS_INTERRUPT_CONTROL; + status->bOriginator =3D bOriginator; + status->bEvent =3D 0; + status->bSelector =3D bSelector; + status->bAttribute =3D STATUS_CONTROL_VALUE_CHANGE; + memcpy(status->bValue, value, size); + + QTAILQ_INSERT_TAIL(&s->control_status, usb_status, list); + trace_usb_video_queue_control_status(bus->busnr, dev->addr, bOriginato= r, + bSelector, *value, size); +} + +static int usb_video_get_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBVideoState *s =3D USB_VIDEO(dev); + USBBus *bus =3D usb_bus_from_device(dev); + uint8_t req =3D request & 0xff; + uint8_t cs =3D value >> 8; + uint8_t intfnum =3D index & 0xff; + uint8_t unit =3D index >> 8; + int ret =3D USB_RET_STALL; + + switch (intfnum) { + case IF_CONTROL: + switch (unit) { + case 0: + if (length !=3D 1) { + break; + } + + if (cs =3D=3D VC_VIDEO_POWER_MODE_CONTROL) { + data[0] =3D 127; /* 4.2.1.1 Power Mode Control */ + ret =3D 1; + } else if (cs =3D=3D VC_REQUEST_ERROR_CODE_CONTROL) { + data[0] =3D s->error; /* 4.2.1.2 Request Error Code Contro= l */ + s->error =3D 0; + ret =3D 1; + } + break; + + case PROCESSING_UNIT: + { + QEMUCameraControlType t =3D usb_video_pu_control_type_to_q= emu(cs); + handle_get_control(s->pu_attrs, req, t, length, data, ret); + } + break; + + case SELECTOR_UNIT: + case ENCODING_UNIT: + default: + /* TODO XU control support */ + break; + } + break; + + case IF_STREAMING: + switch (cs) { + case VS_PROBE_CONTROL: + handle_get_streaming(s, req, vsc, length, data, ret); + break; + + default: + qemu_log_mask(LOG_UNIMP, "%s: get streamimg %d not implemented= \n", + TYPE_USB_VIDEO, cs); + } + + break; + } + + trace_usb_video_get_control(bus->busnr, dev->addr, intfnum, unit, cs, = ret); + + return ret; +} + +static int usb_video_set_vs_control(USBDevice *dev, uint8_t req, int lengt= h, + uint8_t *data) +{ + USBVideoState *s =3D USB_VIDEO(dev); + int idx =3D -1; + int ret =3D USB_RET_STALL; + + REQ_TO_ATTR(req, idx); + if ((idx >=3D 0) && (length <=3D sizeof(s->vsc_attrs[0]))) { + VideoStreamingControl *dst =3D s->vsc_attrs + idx; + VideoStreamingControl *src =3D (VideoStreamingControl *)data; + + dst->bFormatIndex =3D src->bFormatIndex; + dst->bFrameIndex =3D src->bFrameIndex; + VIDEO_CONTROL_TEST_AND_SET(src->bmHint, dwFrameInterval, src, dst); + VIDEO_CONTROL_TEST_AND_SET(src->bmHint, wKeyFrameRate, src, dst); + VIDEO_CONTROL_TEST_AND_SET(src->bmHint, wPFrameRate, src, dst); + VIDEO_CONTROL_TEST_AND_SET(src->bmHint, wCompQuality, src, dst); + VIDEO_CONTROL_TEST_AND_SET(src->bmHint, wCompWindowSize, src, dst); + ret =3D length; + } + + return ret; +} + +static int usb_video_set_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBVideoState *s =3D USB_VIDEO(dev); + USBBus *bus =3D usb_bus_from_device(dev); + uint8_t req =3D request & 0xff; + uint8_t cs =3D value >> 8; + uint8_t intfnum =3D index & 0xff; + uint8_t unit =3D index >> 8; + int ret =3D USB_RET_STALL; + + switch (intfnum) { + case IF_CONTROL: + switch (unit) { + case PROCESSING_UNIT: + { + uint32_t value =3D 0; + QEMUCameraControl ctrl; + QEMUCameraControlType type; + Error *local_err =3D NULL; + + type =3D usb_video_pu_control_type_to_qemu(cs); + if (type =3D=3D QEMUCameraControlMax) { + break; + } + + if (length > 4) { + break; + } + + memcpy(&value, data, length); + value =3D le32_to_cpu(value); + ctrl.type =3D type; + ctrl.cur =3D value; + if (qemu_camera_set_control(s->camera, &ctrl, &local_err))= { + error_reportf_err(local_err, "%s: ", TYPE_USB_VIDEO); + break; + } + + memcpy(&s->pu_attrs[type].value[ATTRIBUTE_CUR], data, leng= th); + ret =3D length; + usb_video_queue_control_status(dev, PROCESSING_UNIT, cs, + &value, length); + } + break; + + /* TODO XU control support */ + } + + break; + + case IF_STREAMING: + switch (cs) { + case VS_PROBE_CONTROL: + case VS_COMMIT_CONTROL: + { + QEMUCameraFrameInterval frmival; + if (usb_video_get_frmival_from_vsc(dev, + (VideoStreamingControl *)data, &frmival)) { + s->error =3D VC_ERROR_OUT_OF_RANGE; + break; + } + + ret =3D usb_video_set_vs_control(dev, req, length, data); + } + break; + + default: + qemu_log_mask(LOG_UNIMP, "%s: set streamimg %d not implemented= \n", + TYPE_USB_VIDEO, cs); + } + + break; + } + + trace_usb_video_set_control(bus->busnr, dev->addr, intfnum, cs, ret); + + return ret; +} + +static void usb_video_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, + int length, uint8_t *data) +{ + USBBus *bus =3D usb_bus_from_device(dev); + int ret =3D 0; + + ret =3D usb_desc_handle_control(dev, p, request, value, index, length,= data); + if (ret >=3D 0) { + return; + } + + switch (request) { + case ClassInterfaceRequest | GET_CUR: + case ClassInterfaceRequest | GET_MIN: + case ClassInterfaceRequest | GET_MAX: + case ClassInterfaceRequest | GET_RES: + case ClassInterfaceRequest | GET_LEN: + case ClassInterfaceRequest | GET_INFO: + case ClassInterfaceRequest | GET_DEF: + ret =3D usb_video_get_control(dev, request, value, index, length, = data); + if (ret < 0) { + goto error; + } + break; + case ClassInterfaceOutRequest | SET_CUR: + ret =3D usb_video_set_control(dev, request, value, index, length, = data); + if (ret < 0) { + goto error; + } + break; + case ClassInterfaceRequest | GET_CUR_ALL: + case ClassInterfaceRequest | GET_MIN_ALL: + case ClassInterfaceRequest | GET_MAX_ALL: + case ClassInterfaceRequest | GET_RES_ALL: + case ClassInterfaceRequest | GET_DEF_ALL: + case ClassInterfaceOutRequest | SET_CUR_ALL: + default: + qemu_log_mask(LOG_UNIMP, "%s: request %d not implemented\n", + TYPE_USB_VIDEO, request); + goto error; + } + + p->actual_length =3D ret; + p->status =3D USB_RET_SUCCESS; + return; + +error: + trace_usb_video_handle_control_error(bus->busnr, dev->addr, request, + value, index, length); + p->status =3D USB_RET_STALL; +} + +static void usb_video_set_streaming_altset(USBDevice *dev, int altset) +{ + USBVideoState *s =3D USB_VIDEO(dev); + Error *local_err =3D NULL; + + if (s->streaming_altset =3D=3D altset) { + return; + } + + switch (altset) { + case ALTSET_OFF: + qemu_camera_stream_off(s->camera, &local_err); + break; + + case ALTSET_STREAMING: + { + QEMUCameraFrameInterval frmival; + + if (usb_video_get_frmival_from_vsc(dev, + &s->vsc_attrs[ATTRIBUTE_CUR], &frmival)) { + s->error =3D VC_ERROR_OUT_OF_RANGE; + break; + } + + qemu_camera_set_frame_interval(s->camera, &frmival, &local_err= ); + if (local_err) { + s->error =3D VC_ERROR_INVALID_VALUE_WITHIN_RANGE; + error_reportf_err(local_err, "%s: ", TYPE_USB_VIDEO); + return; + } + + qemu_camera_stream_on(s->camera, NULL, s, &local_err); + if (local_err) { + s->error =3D VC_ERROR_INVALID_REQUEST; + error_reportf_err(local_err, "%s: ", TYPE_USB_VIDEO); + return; + } + } + break; + } + + s->streaming_altset =3D altset; +} + +static void usb_video_set_interface(USBDevice *dev, int iface, + int old, int value) +{ + USBBus *bus =3D usb_bus_from_device(dev); + + trace_usb_video_set_interface(bus->busnr, dev->addr, iface, value); + + if (iface =3D=3D IF_STREAMING) { + usb_video_set_streaming_altset(dev, value); + } +} + +static void usb_video_handle_reset(USBDevice *dev) +{ + USBVideoState *s =3D USB_VIDEO(dev); + USBBus *bus =3D usb_bus_from_device(dev); + Error *local_err =3D NULL; + + trace_usb_video_handle_reset(bus->busnr, dev->addr); + qemu_camera_stream_off(s->camera, &local_err); +} + +static void usb_video_handle_streaming_in(USBDevice *dev, USBPacket *p) +{ + USBVideoState *s =3D USB_VIDEO(dev); + USBBus *bus =3D usb_bus_from_device(dev); + QEMUIOVector *iov =3D p->combined ? &p->combined->iov : &p->iov; + VideoImagePayloadHeader header; + int len; + + if (s->streaming_altset !=3D ALTSET_STREAMING) { + p->status =3D USB_RET_NAK; + return; + } + + /* TODO PresentationTime & scrSourceClock support */ + header.bmHeaderInfo =3D PAYLOAD_HEADER_EOH; + header.bmHeaderInfo |=3D s->fid ? PAYLOAD_HEADER_FID : 0; + header.bHeaderLength =3D 2; + if (p->actual_length + header.bHeaderLength > iov->size) { + p->status =3D USB_RET_STALL; + return; + } + + len =3D qemu_camera_stream_length(s->camera); + if (!len) { + p->status =3D USB_RET_NAK; + return; + } + + if (len < iov->size - header.bHeaderLength) { + /* + * if we can take all of the remained data, mark EOF in payload he= ader, + * also change fid state. + */ + header.bmHeaderInfo |=3D PAYLOAD_HEADER_EOF; + s->fid =3D !s->fid; + } + + /* firstly, copy payload header */ + usb_packet_copy(p, &header, header.bHeaderLength); + + /* then, copy payload data */ + len =3D qemu_camera_stream_read(s->camera, iov->iov, iov->niov, + p->actual_length, iov->size - p->actual_length); + p->actual_length +=3D len; + + p->status =3D USB_RET_SUCCESS; + + trace_usb_video_handle_streaming_in(bus->busnr, dev->addr, + header.bHeaderLength + len); +} + +static void usb_video_handle_control_in(USBDevice *dev, USBPacket *p) +{ + USBVideoState *s =3D USB_VIDEO(dev); + USBBus *bus =3D usb_bus_from_device(dev); + USBVideoControlStats *usb_status =3D NULL; + QEMUIOVector *iov =3D p->combined ? &p->combined->iov : &p->iov; + int len =3D 0; + + if (QTAILQ_EMPTY(&s->control_status)) { + p->status =3D USB_RET_NAK; + goto out; + } + + usb_status =3D QTAILQ_FIRST(&s->control_status); + QTAILQ_REMOVE(&s->control_status, usb_status, list); + len =3D MIN(5 + usb_status->size, iov->size); /* see VideoControlStatu= s */ + usb_packet_copy(p, &usb_status->status, len); + p->status =3D USB_RET_SUCCESS; + +out: + trace_usb_video_handle_control_in(bus->busnr, dev->addr, len); +} + +static void usb_video_handle_data(USBDevice *dev, USBPacket *p) +{ + if ((p->pid =3D=3D USB_TOKEN_IN) && (p->ep->nr =3D=3D EP_STREAMING)) { + usb_video_handle_streaming_in(dev, p); + return; + } else if ((p->pid =3D=3D USB_TOKEN_IN) && (p->ep->nr =3D=3D EP_CONTRO= L)) { + usb_video_handle_control_in(dev, p); + return; + } + + p->status =3D USB_RET_STALL; +} + +static void usb_video_unrealize(USBDevice *dev) +{ +} + +static int usb_video_build_vc(USBDevice *dev) +{ + USBVideoState *s =3D USB_VIDEO(dev); + USBBus *bus =3D usb_bus_from_device(dev); + Error *local_err =3D NULL; + USBDescIface *vc_iface; + USBDescOther *usb_desc; + QEMUCameraControl controls[QEMUCameraControlMax], *control; + USBVideoControlInfo *controlinfo; + uint32_t bmControl =3D 0; + uint8_t *bmControls =3D NULL; + int i, ncontrols, pucontrol; + + vc_iface =3D &s->desc_ifaces[0]; /* see VideoControl Interface Descrip= tor */ + + /* search Processing Unit Descriptor, and build bmControls field */ + for (i =3D 0; i < vc_iface->ndesc; i++) { + usb_desc =3D &vc_iface->descs[i]; + if (usb_desc->data[2] =3D=3D VC_PROCESSING_UNIT) { + bmControls =3D (uint8_t *)usb_desc->data + 8; + } + } + + ncontrols =3D qemu_camera_enum_control(s->camera, controls, + ARRAY_SIZE(controls), &local_err); + + for (i =3D 0; i < ncontrols; i++) { + uint8_t size =3D 0; + control =3D &controls[i]; + bmControl |=3D usb_video_pu_control_bits(control->type); + pucontrol =3D usb_video_pu_control_type(control->type, &size); + assert(pucontrol < PU_MAX); + if (pucontrol) { + controlinfo =3D &s->pu_attrs[control->type]; + controlinfo->selector =3D pucontrol; + controlinfo->caps =3D CONTROL_CAP_GET | CONTROL_CAP_SET + | CONTROL_CAP_ASYNCHRONOUS; + controlinfo->size =3D size; + controlinfo->value[ATTRIBUTE_DEF] =3D cpu_to_le32(control->def= ); + controlinfo->value[ATTRIBUTE_MIN] =3D cpu_to_le32(control->min= ); + controlinfo->value[ATTRIBUTE_MAX] =3D cpu_to_le32(control->max= ); + controlinfo->value[ATTRIBUTE_CUR] =3D cpu_to_le32(control->def= ); + controlinfo->value[ATTRIBUTE_RES] =3D cpu_to_le32(control->ste= p); + + trace_usb_video_pu(bus->busnr, dev->addr, pucontrol, size, + control->def, control->min, control->max, control->step); + } + } + + if (bmControls) { + bmControl =3D cpu_to_le32(bmControl); + *bmControls =3D bmControl & 0xff; + *(bmControls + 1) =3D (bmControl >> 8) & 0xff; + *(bmControls + 2) =3D (bmControl >> 16) & 0xff; + } + + return 0; +} + +#define USB_VIDEO_PIX_FORMAT_MAX 4 +#define USB_VIDEO_FRAME_SIZE_MAX 32 +#define USB_VIDEO_FRAME_IVAL_MAX 8 + +#define VS_HEADER_LEN 0xe +#define VS_FORMAT_UNCOMPRESSED_LEN 0x1b +#define VS_FORMAT_MJPEG_LEN 0xb +#define VS_FORMAT_MAX_LEN MAX(VS_FORMAT_UNCOMPRESSED_LEN, VS_FORMAT_MJPEG_= LEN) +#define VS_FRAME_MIN_LEN 0x1a +#define VS_FRAME_MAX_LEN (VS_FRAME_MIN_LEN + 4 * USB_VIDEO_FRAME_IVAL_MAX) + +static int usb_video_vs_build_header(uint8_t *addr, uint16_t wTotalLength) +{ + /* Class-specific VS Header Descriptor (Input) */ + uint8_t data[] =3D { + VS_HEADER_LEN, /* u8 bLength */ + CS_INTERFACE, /* u8 bDescriptorType */ + VS_INPUT_HEADER, /* u8 bDescriptorSubtype */ + 0x01, /* u8 bNumFormats */ + U16(wTotalLength), /* u16 wTotalLength */ + USB_DIR_IN | EP_STREAMING, /* u8 bEndPointAddress */ + 0x00, /* u8 bmInfo */ + OUTPUT_TERMINAL, /* u8 bTerminalLink */ + 0x01, /* u8 bStillCaptureMethod */ + 0x01, /* u8 bTriggerSupport */ + 0x00, /* u8 bTriggerUsage */ + 0x01, /* u8 bControlSize */ + 0x00, /* u8 bmaControls */ + }; + + memcpy(addr, data, data[0]); + + return data[0]; +} + +static int usb_video_vs_build_format(uint8_t *addr, uint32_t pixfmt, + uint8_t bFormatIndex, + uint8_t bNumFrameDescriptors) +{ + /* Class-specific VS Format Descriptor */ + uint8_t bDescriptorSubtype =3D usb_video_pixfmt_to_vsfmt(pixfmt); + uint8_t *data =3D NULL; + + uint8_t data_mjpeg[] =3D { + VS_FORMAT_MJPEG_LEN, /* u8 bLength */ + CS_INTERFACE, /* u8 bDescriptorType */ + bDescriptorSubtype, /* u8 bDescriptorSubtype */ + bFormatIndex, /* u8 bFormatIndex */ + bNumFrameDescriptors, /* u8 bNumFrameDescriptors */ + 0x01, /* u8 bmFlags */ + 0x01, /* u8 bDefaultFrameIndex */ + 0x00, /* u8 bAspectRatioX */ + 0x00, /* u8 bAspectRatioY */ + 0x00, /* u8 bmInterlaceFlags */ + 0x00, /* u8 bCopyProtect */ + }; + + uint8_t data_uncompressed_yuy2[] =3D { + VS_FORMAT_UNCOMPRESSED_LEN, /* u8 bLength */ + CS_INTERFACE, /* u8 bDescriptorType */ + bDescriptorSubtype, /* u8 bDescriptorSubtype */ + bFormatIndex, /* u8 bFormatIndex */ + bNumFrameDescriptors, /* u8 bNumFrameDescriptors */ + /* guidFormat */ + 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, + 0x10, /* u8 bBitsPerPixel */ + 0x01, /* u8 bDefaultFrameIndex */ + 0x00, /* u8 bAspectRatioX */ + 0x00, /* u8 bAspectRatioY */ + 0x00, /* u8 bmInterlaceFlags */ + 0x00, /* u8 bCopyProtect */ + }; + + uint8_t data_uncompressed_rgb565[] =3D { + VS_FORMAT_UNCOMPRESSED_LEN, /* u8 bLength */ + CS_INTERFACE, /* u8 bDescriptorType */ + bDescriptorSubtype, /* u8 bDescriptorSubtype */ + bFormatIndex, /* u8 bFormatIndex */ + bNumFrameDescriptors, /* u8 bNumFrameDescriptors */ + /* guidFormat */ + 'R', 'G', 'B', 'P', 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, + 0x10, /* u8 bBitsPerPixel */ + 0x01, /* u8 bDefaultFrameIndex */ + 0x00, /* u8 bAspectRatioX */ + 0x00, /* u8 bAspectRatioY */ + 0x00, /* u8 bmInterlaceFlags */ + 0x00, /* u8 bCopyProtect */ + }; + + if (pixfmt =3D=3D QEMU_CAMERA_PIX_FMT_MJPEG) { + data =3D data_mjpeg; + } else if (pixfmt =3D=3D QEMU_CAMERA_PIX_FMT_YUYV) { + data =3D data_uncompressed_yuy2; + } else if (pixfmt =3D=3D QEMU_CAMERA_PIX_FMT_RGB565) { + data =3D data_uncompressed_rgb565; + } else { + return 0; + } + + memcpy(addr, data, data[0]); + + return data[0]; +} + +static int usb_video_vs_build_frame(uint8_t *addr, uint8_t bDescriptorSubt= ype, + uint8_t bFrameIndex, + QEMUCameraFrameInterval *frmivals, + uint8_t nfrmivals) +{ + uint8_t bLength =3D VS_FRAME_MIN_LEN + nfrmivals * 4; + QEMUCameraFrameInterval *deffrmival =3D &frmivals[0]; + struct FrameIntervalDiscrete *d =3D &deffrmival->d; + uint16_t wWidth =3D deffrmival->width; + uint16_t wHeight =3D deffrmival->height; + uint32_t dwMaxVideoFrameBufSize =3D wWidth * wHeight * 2; + uint32_t dwDefaultFrameInterval =3D 10000000 * d->numerator / d->denom= inator; + uint32_t *ival; + int index; + + /* Class-specific VS Frame Descriptor */ + uint8_t data[VS_FRAME_MAX_LEN] =3D { + bLength, /* u8 bLength */ + CS_INTERFACE, /* u8 bDescriptorType */ + bDescriptorSubtype, /* u8 bDescriptorSubtype */ + bFrameIndex, /* u8 bFrameIndex */ + 0x03, /* u8 bmCapabilities */ + U16(wWidth), /* u16 wWidth */ + U16(wHeight), /* u16 wHeight */ + U32(442368000), /* u32 dwMinBitRate */ + U32(442368000), /* u32 dwMaxBitRate */ + U32(dwMaxVideoFrameBufSize),/* u32 dwMaxVideoFrameBufSize */ + U32(dwDefaultFrameInterval),/* u32 dwDefaultFrameInterval */ + nfrmivals, /* u8 bFrameIntervalType */ + }; + + for (index =3D 0; index < nfrmivals; index++) { + ival =3D (uint32_t *)(data + VS_FRAME_MIN_LEN + 4 * index); + d =3D &frmivals[index].d; + *ival =3D cpu_to_le32(10000000 * d->numerator / d->denominator); + } + + memcpy(addr, data, data[0]); + + return data[0]; +} + +static void usb_video_initialize(USBDevice *dev, Error **errp) +{ + USBVideoState *s =3D USB_VIDEO(dev); + VideoStreamingControl *vsc; + uint32_t pixfmts[USB_VIDEO_PIX_FORMAT_MAX]; + int npixfmts, pixfmtidx, frmszidx; + USBDescIface *vs_iface; + USBDescOther *usb_desc; + uint32_t dwMaxVideoFrameSize =3D 0; + uint32_t vs_length =3D VS_HEADER_LEN; + + s->vs_descs =3D g_new0(USBDescOther, 1 + USB_VIDEO_PIX_FORMAT_MAX + + USB_VIDEO_PIX_FORMAT_MAX * USB_VIDEO_FRAME_SIZE_= MAX); + s->vs_data =3D g_malloc0(VS_HEADER_LEN + VS_FORMAT_MAX_LEN + * USB_VIDEO_PIX_FORMAT_MAX + VS_FRAME_MAX_LEN + * USB_VIDEO_PIX_FORMAT_MAX * USB_VIDEO_FRAME_SIZE_MA= X); + usb_desc =3D s->vs_descs; + usb_desc->data =3D s->vs_data; + + /* build desc video from template */ + memcpy(s->desc_ifaces, desc_ifaces, sizeof(s->desc_ifaces)); + + s->desc_device_full =3D desc_device_full; + *(USBDescIface **)&(s->desc_device_full.confs[0].ifs) =3D s->desc_ifac= es; + + s->desc_device_high =3D desc_device_high; + *(USBDescIface **)&(s->desc_device_high.confs[0].ifs) =3D s->desc_ifac= es; + + s->desc_video =3D desc_video; + s->desc_video.full =3D &s->desc_device_full; + s->desc_video.high =3D &s->desc_device_high; + + usb_video_build_vc(dev); + + /* + * let's build USBDescIfaces layout like this: + * 1, VideoControl Interface Descriptor(fully copied from template) + * 2, VideoStreaming Interface Descriptor(detect format & frame dynami= cally) + * 2.1 Class-specific VS Header Descriptor(dynamic wTotalLength) + * 2.2 Class-specific VS Format Descriptor(bFormatIndex 1) + * 2.3 Class-specific VS Frame Descriptor(bFrameIndex 1) + * ... + * 2.x Class-specific VS Frame Descriptor(bFrameIndex x-2) + * 2.y Class-specific VS Format Descriptor(bFormatIndex 2) + * 2.z Class-specific VS Frame Descriptor(bFrameIndex 1) + * ... + * 3, Operational Alternate Setting 1(fully copied from template) + */ + s->n_vs_descs =3D 1; /* at least 1 header */ + + npixfmts =3D qemu_camera_enum_pixel_format(s->camera, pixfmts, + ARRAY_SIZE(pixfmts), errp); + if (!npixfmts) { + error_setg(errp, "%s: no available pixel format support on %s", + TYPE_USB_VIDEO, s->cameradev); + return; + } + + for (pixfmtidx =3D 0; pixfmtidx < npixfmts; pixfmtidx++) { + QEMUCameraFrameSize frmszs[USB_VIDEO_FRAME_SIZE_MAX], *frmsz; + uint8_t vsfrm =3D usb_video_pixfmt_to_vsfrm(pixfmts[pixfmtidx]); + int nfrmszs; + + usb_desc =3D s->vs_descs + s->n_vs_descs++; + usb_desc->data =3D s->vs_data + vs_length; + + nfrmszs =3D qemu_camera_enum_frame_size(s->camera, pixfmts[pixfmti= dx], + frmszs, ARRAY_SIZE(frmszs), errp); + + vs_length +=3D usb_video_vs_build_format(s->vs_data + vs_length, + pixfmts[pixfmtidx], (uint8_t)pixfmtidx + 1, + (uint8_t)nfrmszs); + + for (frmszidx =3D 0; frmszidx < nfrmszs; frmszidx++) { + QEMUCameraFrameInterval frmivals[USB_VIDEO_FRAME_IVAL_MAX]; + QEMUCameraFormat fmt; + int nfrmivals; + + frmsz =3D &frmszs[frmszidx]; + if (frmsz->type !=3D QEMU_CAMERA_FRMSIZE_TYPE_DISCRETE) { + continue; /* TODO stepwise support */ + } + + fmt.pixel_format =3D frmsz->pixel_format; + fmt.width =3D frmsz->d.width; + fmt.height =3D frmsz->d.height; + nfrmivals =3D qemu_camera_enum_frame_interval(s->camera, &fmt, + frmivals, ARRAY_SIZE(frmivals), errp); + if (!nfrmivals) { + continue; + } + + if (dwMaxVideoFrameSize < fmt.height * fmt.width * 2) { + dwMaxVideoFrameSize =3D fmt.height * fmt.width * 2; + } + + usb_desc =3D s->vs_descs + s->n_vs_descs++; + usb_desc->data =3D s->vs_data + vs_length; + vs_length +=3D usb_video_vs_build_frame((uint8_t *)usb_desc->d= ata, + vsfrm, (uint8_t)frmszidx= + 1, + frmivals, (uint8_t)nfrmi= vals); + } + } + + /* build VideoStreaming Interface Descriptor */ + vs_iface =3D &s->desc_ifaces[1]; /* see VideoStreaming Interface Descr= iptor */ + usb_video_vs_build_header(s->vs_data, vs_length); + vs_iface->ndesc =3D s->n_vs_descs; + vs_iface->descs =3D s->vs_descs; + + /* keep align with VideoStreaming Interface Descriptor */ + s->vsc_info =3D 0; + s->vsc_len =3D sizeof(*vsc); + vsc =3D s->vsc_attrs + ATTRIBUTE_DEF; + vsc->bFormatIndex =3D 1; + vsc->bFrameIndex =3D 1; + vsc->dwFrameInterval =3D cpu_to_le32(1000000); /* default 10 FPS */ + vsc->wDelay =3D cpu_to_le16(32); + vsc->dwMaxVideoFrameSize =3D cpu_to_le32(dwMaxVideoFrameSize); + vsc->dwMaxPayloadTransferSize =3D cpu_to_le32(1024); + vsc->dwClockFrequency =3D cpu_to_le32(15000000); + memcpy(s->vsc_attrs + ATTRIBUTE_CUR, vsc, sizeof(*vsc)); + memcpy(s->vsc_attrs + ATTRIBUTE_MIN, vsc, sizeof(*vsc)); + memcpy(s->vsc_attrs + ATTRIBUTE_MAX, vsc, sizeof(*vsc)); +} + +static void usb_video_realize(USBDevice *dev, Error **errp) +{ + USBVideoState *s =3D USB_VIDEO(dev); + + if (!s->terminal || strcmp(s->terminal, "camera")) { + error_setg(errp, "%s: support terminal camera only", TYPE_USB_VIDE= O); + return; + } + + s->camera =3D qemu_camera_by_id(s->cameradev); + if (!s->camera) { + error_setg(errp, "%s: invalid cameradev %s", + TYPE_USB_VIDEO, s->cameradev); + return; + } + + QTAILQ_INIT(&s->control_status); + + usb_video_initialize(dev, errp); + dev->usb_desc =3D &s->desc_video; + + usb_desc_create_serial(dev); + usb_desc_init(dev); + s->dev.opaque =3D s; +} + +static Property usb_video_properties[] =3D { + DEFINE_PROP_STRING("cameradev", USBVideoState, cameradev), + DEFINE_PROP_STRING("terminal", USBVideoState, terminal), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_video_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + USBDeviceClass *k =3D USB_DEVICE_CLASS(klass); + + device_class_set_props(dc, usb_video_properties); + set_bit(DEVICE_CATEGORY_USB, dc->categories); + k->product_desc =3D "QEMU USB Video Interface"; + k->realize =3D usb_video_realize; + k->handle_reset =3D usb_video_handle_reset; + k->handle_control =3D usb_video_handle_control; + k->handle_data =3D usb_video_handle_data; + k->unrealize =3D usb_video_unrealize; + k->set_interface =3D usb_video_set_interface; +} + +static const TypeInfo usb_video_info =3D { + .name =3D TYPE_USB_VIDEO, + .parent =3D TYPE_USB_DEVICE, + .instance_size =3D sizeof(USBVideoState), + .class_init =3D usb_video_class_init, +}; + +static void usb_video_register_types(void) +{ + type_register_static(&usb_video_info); +} + +type_init(usb_video_register_types) diff --git a/hw/usb/meson.build b/hw/usb/meson.build index de853d780d..7706e7088e 100644 --- a/hw/usb/meson.build +++ b/hw/usb/meson.build @@ -44,6 +44,7 @@ softmmu_ss.add(when: 'CONFIG_USB_STORAGE_UAS', if_true: f= iles('dev-uas.c')) softmmu_ss.add(when: 'CONFIG_USB_AUDIO', if_true: files('dev-audio.c')) softmmu_ss.add(when: 'CONFIG_USB_SERIAL', if_true: files('dev-serial.c')) softmmu_ss.add(when: 'CONFIG_USB_NETWORK', if_true: files('dev-network.c')) +softmmu_ss.add(when: 'CONFIG_USB_VIDEO', if_true: files('dev-video.c')) softmmu_ss.add(when: ['CONFIG_POSIX', 'CONFIG_USB_STORAGE_MTP'], if_true: = files('dev-mtp.c')) =20 # smartcard diff --git a/hw/usb/trace-events b/hw/usb/trace-events index b8287b63f1..f250da29bd 100644 --- a/hw/usb/trace-events +++ b/hw/usb/trace-events @@ -345,3 +345,14 @@ usb_serial_set_baud(int bus, int addr, int baud) "dev = %d:%u baud rate %d" usb_serial_set_data(int bus, int addr, int parity, int data, int stop) "de= v %d:%u parity %c, data bits %d, stop bits %d" usb_serial_set_flow_control(int bus, int addr, int index) "dev %d:%u flow = control %d" usb_serial_set_xonxoff(int bus, int addr, uint8_t xon, uint8_t xoff) "dev = %d:%u xon 0x%x xoff 0x%x" + +# dev-video.c +usb_video_handle_reset(int bus, int addr) "dev %d:%u reset" +usb_video_pu(int bus, int addr, int selector, int size, int def, int min, = int max, int step) "dev %d:%u build PU control selector %d, size %d, def %d= , min %d, max %d, step %d" +usb_video_handle_streaming_in(int bus, int addr, int len) "dev %d:%u strea= ming in %d" +usb_video_handle_control_in(int bus, int addr, int len) "dev %d:%u streami= ng in %d" +usb_video_set_interface(int bus, int addr, int iface, int value) "dev %d:%= u set iface %d with value %d" +usb_video_get_control(int bus, int addr, int intfnum, int unit, int cs, in= t retval) "dev %d:%u get control iface %d, unit %d, cs %d, retval %d" +usb_video_set_control(int bus, int addr, int intfnum, int cs, int retval) = "dev %d:%u set control iface %d, cs %d, retval %d" +usb_video_handle_control_error(int bus, int addr, int request, int value, = int index, int length) "dev %d:%u handle control error, request 0x%x, value= 0x%x, index 0x%x, length 0x%x" +usb_video_queue_control_status(int bus, int addr, uint8_t bOriginator, uin= t8_t bSelector, uint32_t value, uint8_t size) "dev %d:%u queue control stat= us, originator %d, selector %d, value(le32) 0x%x, size %d" diff --git a/include/hw/usb/video.h b/include/hw/usb/video.h new file mode 100644 index 0000000000..5a3f78d1d9 --- /dev/null +++ b/include/hw/usb/video.h @@ -0,0 +1,303 @@ +#ifndef HW_USB_VIDEO_H +#define HW_USB_VIDEO_H + +/* Base on UVC specification 1.5 */ + +/* A.2. Video Interface Subclass Codes */ +#define SC_UNDEFINED 0x00 +#define SC_VIDEOCONTROL 0x01 +#define SC_VIDEOSTREAMING 0x02 +#define SC_VIDEO_INTERFACE_COLLECTION 0x03 + +/* A.3. Video Interface Protocol Codes */ +#define PC_PROTOCOL_UNDEFINED 0x00 +#define PC_PROTOCOL_15 0x01 + +/* A.4. Video Class-Specific Descriptor Types */ +#define CS_UNDEFINED 0x20 +#define CS_DEVICE 0x21 +#define CS_CONFIGURATION 0x22 +#define CS_STRING 0x23 +#define CS_INTERFACE 0x24 +#define CS_ENDPOINT 0x25 + +/* A.5. Video Class-Specific VC Interface Descriptor Subtypes */ +#define VC_DESCRIPTOR_UNDEFINED 0x00 +#define VC_HEADER 0x01 +#define VC_INPUT_TERMINAL 0x02 +#define VC_OUTPUT_TERMINAL 0x03 +#define VC_SELECTOR_UNIT 0x04 +#define VC_PROCESSING_UNIT 0x05 +#define VC_EXTENSION_UNIT 0x06 +#define VC_ENCODING_UNIT 0x07 + +/* A.6. Video Class-Specific VS Interface Descriptor Subtypes */ +#define VS_UNDEFINED 0x00 +#define VS_INPUT_HEADER 0x01 +#define VS_OUTPUT_HEADER 0x02 +#define VS_STILL_IMAGE_FRAME 0x03 +#define VS_FORMAT_UNCOMPRESSED 0x04 +#define VS_FRAME_UNCOMPRESSED 0x05 +#define VS_FORMAT_MJPEG 0x06 +#define VS_FRAME_MJPEG 0x07 +#define VS_FORMAT_MPEG2TS 0x0A +#define VS_FORMAT_DV 0x0C +#define VS_COLORFORMAT 0x0D +#define VS_FORMAT_FRAME_BASED 0x10 +#define VS_FRAME_FRAME_BASED 0x11 +#define VS_FORMAT_STREAM_BASED 0x12 +#define VS_FORMAT_H264 0x13 +#define VS_FRAME_H264 0x14 +#define VS_FORMAT_H264_SIMULCAST 0x15 +#define VS_FORMAT_VP8 0x16 +#define VS_FRAME_VP8 0x17 +#define VS_FORMAT_VP8_SIMULCAST 0x18 + +/* A.7. Video Class-Specific Endpoint Descriptor Subtypes */ +#define EP_UNDEFINED 0x00 +#define EP_GENERAL 0x01 +#define EP_ENDPOINT 0x02 +#define EP_INTERRUPT 0x03 + +/* A.8. Video Class-Specific Request Codes */ +#define RC_UNDEFINED 0x00 +#define SET_CUR 0x01 +#define SET_CUR_ALL 0x11 +#define GET_CUR 0x81 +#define GET_MIN 0x82 +#define GET_MAX 0x83 +#define GET_RES 0x84 +#define GET_LEN 0x85 +#define GET_INFO 0x86 +#define GET_DEF 0x87 +#define GET_CUR_ALL 0x91 +#define GET_MIN_ALL 0x92 +#define GET_MAX_ALL 0x93 +#define GET_RES_ALL 0x94 +#define GET_DEF_ALL 0x97 + +/* 4.1.2 Get Request: Defined Bits Containing Capabilities of the Control = */ +#define CONTROL_CAP_GET (1 << 0) +#define CONTROL_CAP_SET (1 << 1) +#define CONTROL_CAP_DISABLED (1 << 2) +#define CONTROL_CAP_AUTOUPDATE (1 << 3) +#define CONTROL_CAP_ASYNCHRONOUS (1 << 4) + +/* 4.2.1.2 Request Error Code Control */ +#define VC_ERROR_NOT_READY 0x01 +#define VC_ERROR_WRONG_STATE 0x02 +#define VC_ERROR_POWER 0x03 +#define VC_ERROR_OUT_OF_RANGE 0x04 +#define VC_ERROR_INVALID_UNIT 0x05 +#define VC_ERROR_INVALID_CONTROL 0x06 +#define VC_ERROR_INVALID_REQUEST 0x07 +#define VC_ERROR_INVALID_VALUE_WITHIN_RANGE 0x08 + +/* A.9.1. VideoControl Interface Control Selectors */ +#define VC_CONTROL_UNDEFINED 0x00 +#define VC_VIDEO_POWER_MODE_CONTROL 0x01 +#define VC_REQUEST_ERROR_CODE_CONTROL 0x02 + +/* A.9.2. Terminal Control Selectors */ +#define TE_CONTROL_UNDEFINED 0x00 + +/* A.9.3. Selector Unit Control Selectors */ +#define SU_CONTROL_UNDEFINED 0x00 +#define SU_INPUT_SELECT_CONTROL 0x01 + +/* A.9.4. Camera Terminal Control Selectors */ +#define CT_CONTROL_UNDEFINED 0x00 +#define CT_SCANNING_MODE_CONTROL 0x01 +#define CT_AE_MODE_CONTROL 0x02 +#define CT_AE_PRIORITY_CONTROL 0x03 +#define CT_EXPOSURE_TIME_ABSOLUTE_CONTROL 0x04 +#define CT_EXPOSURE_TIME_RELATIVE_CONTROL 0x05 +#define CT_FOCUS_ABSOLUTE_CONTROL 0x06 +#define CT_FOCUS_RELATIVE_CONTROL 0x07 +#define CT_FOCUS_AUTO_CONTROL 0x08 +#define CT_IRIS_ABSOLUTE_CONTROL 0x09 +#define CT_IRIS_RELATIVE_CONTROL 0x0A +#define CT_ZOOM_ABSOLUTE_CONTROL 0x0B +#define CT_ZOOM_RELATIVE_CONTROL 0x0C +#define CT_PANTILT_ABSOLUTE_CONTROL 0x0D +#define CT_PANTILT_RELATIVE_CONTROL 0x0E +#define CT_ROLL_ABSOLUTE_CONTROL 0x0F +#define CT_ROLL_RELATIVE_CONTROL 0x10 +#define CT_PRIVACY_CONTROL 0x11 +#define CT_FOCUS_SIMPLE_CONTROL 0x12 +#define CT_WINDOW_CONTROL 0x13 +#define CT_REGION_OF_INTEREST_CONTROL 0x14 + +/* A.9.5. Processing Unit Control Selectors */ +#define PU_CONTROL_UNDEFINED 0x00 +#define PU_BACKLIGHT_COMPENSATION_CONTROL 0x01 +#define PU_BRIGHTNESS_CONTROL 0x02 +#define PU_CONTRAST_CONTROL 0x03 +#define PU_GAIN_CONTROL 0x04 +#define PU_POWER_LINE_FREQUENCY_CONTROL 0x05 +#define PU_HUE_CONTROL 0x06 +#define PU_SATURATION_CONTROL 0x07 +#define PU_SHARPNESS_CONTROL 0x08 +#define PU_GAMMA_CONTROL 0x09 +#define PU_WHITE_BALANCE_TEMPERATURE_CONTROL 0x0A +#define PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL 0x0B +#define PU_WHITE_BALANCE_COMPONENT_CONTROL 0x0C +#define PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL 0x0D +#define PU_DIGITAL_MULTIPLIER_CONTROL 0x0E +#define PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL 0x0F +#define PU_HUE_AUTO_CONTROL 0x10 +#define PU_ANALOG_VIDEO_STANDARD_CONTROL 0x11 +#define PU_ANALOG_LOCK_STATUS_CONTROL 0x12 +#define PU_CONTRAST_AUTO_CONTROL 0x13 +#define PU_MAX 0x14 /* self defined */ + +/* 3.7.2.5 Processing Unit Descriptor bmControl bits */ +#define PU_CONTRL_BRIGHTNESS (1 << 0) +#define PU_CONTRL_CONTRAST (1 << 1) +#define PU_CONTRL_HUE (1 << 2) +#define PU_CONTRL_SATURATION (1 << 3) +#define PU_CONTRL_SHARPNESS (1 << 4) +#define PU_CONTRL_GAMMA (1 << 5) +#define PU_CONTRL_WHITE_BALANCE_TEMPERATURE (1 << 6) +#define PU_CONTRL_WHITE_BALANCE_COMPONENT (1 << 7) +#define PU_CONTRL_BACKLIGHT_COMPENSATION (1 << 8) +#define PU_CONTRL_GAIN (1 << 9) +#define PU_CONTRL_POWER_LINE_FREQUENCY (1 << 10) +#define PU_CONTRL_HUE_AUTO (1 << 11) +#define PU_CONTRL_WHITE_BALANCE_TEMPERATURE_AUTO (1 << 12) +#define PU_CONTRL_WHITE_BALANCE_COMPONENT_AUTO (1 << 13) +#define PU_CONTRL_DIGITAL_MULTIPLIER (1 << 14) +#define PU_CONTRL_DIGITAL_MULTIPLIER_LIMIT (1 << 15) +#define PU_CONTRL_ANALOG_VIDEO_STANDARD (1 << 16) +#define PU_CONTRL_ANALOG_VIDEO_LOCK_STATUS (1 << 17) +#define PU_CONTRL_CONTRAST_AUTO (1 << 18) + +/* A.9.6. Encoding Unit Control Selectors */ +#define EU_CONTROL_UNDEFINED 0x00 +#define EU_SELECT_LAYER_CONTROL 0x01 +#define EU_PROFILE_TOOLSET_CONTROL 0x02 +#define EU_VIDEO_RESOLUTION_CONTROL 0x03 +#define EU_MIN_FRAME_INTERVAL_CONTROL 0x04 +#define EU_SLICE_MODE_CONTROL 0x05 +#define EU_RATE_CONTROL_MODE_CONTROL 0x06 +#define EU_AVERAGE_BITRATE_CONTROL 0x07 +#define EU_CPB_SIZE_CONTROL 0x08 +#define EU_PEAK_BIT_RATE_CONTROL 0x09 +#define EU_QUANTIZATION_PARAMS_CONTROL 0x0A +#define EU_SYNC_REF_FRAME_CONTROL 0x0B +#define EU_LTR_BUFFER_ CONTROL 0x0C +#define EU_LTR_PICTURE_CONTROL 0x0D +#define EU_LTR_VALIDATION_CONTROL 0x0E +#define EU_LEVEL_IDC_LIMIT_CONTROL 0x0F +#define EU_SEI_PAYLOADTYPE_CONTROL 0x10 +#define EU_QP_RANGE_CONTROL 0x11 +#define EU_PRIORITY_CONTROL 0x12 +#define EU_START_OR_STOP_LAYER_CONTROL 0x13 +#define EU_ERROR_RESILIENCY_CONTROL 0x14 + +/* A.9.8. VideoStreaming Interface Control Selectors */ +#define VS_CONTROL_UNDEFINED 0x00 +#define VS_PROBE_CONTROL 0x01 +#define VS_COMMIT_CONTROL 0x02 +#define VS_STILL_PROBE_CONTROL 0x03 +#define VS_STILL_COMMIT_CONTROL 0x04 +#define VS_STILL_IMAGE_TRIGGER_CONTROL 0x05 +#define VS_STREAM_ERROR_CODE_CONTROL 0x06 +#define VS_GENERATE_KEY_FRAME_CONTROL 0x07 +#define VS_UPDATE_FRAME_SEGMENT_CONTROL 0x08 +#define VS_SYNCH_DELAY_CONTROL 0x09 + +/* B.1. USB Terminal Types */ +#define TT_VENDOR_SPECIFIC 0x0100 +#define TT_STREAMING 0x0101 + +/* B.2. Input Terminal Types */ +#define ITT_VENDOR_SPECIFIC 0x0200 +#define ITT_CAMERA 0x0201 +#define ITT_MEDIA_TRANSPORT_INPUT 0x0202 + +/* B.3. Output Terminal Types */ +#define OTT_VENDOR_SPECIFIC 0x0300 +#define OTT_DISPLAY 0x0301 +#define OTT_MEDIA_TRANSPORT_OUTPUT 0x0302 + +/* B.4. External Terminal Types */ +#define EXTERNAL_VENDOR_SPECIFIC 0x0400 +#define COMPOSITE_CONNECTOR 0x0401 +#define SVIDEO_CONNECTOR 0x0402 +#define COMPONENT_CONNECTOR 0x0403 + +/* 4.3.1.1. Video Probe and Commit Controls */ +#define VIDEO_CONTROL_dwFrameInterval (1 << 0) +#define VIDEO_CONTROL_wKeyFrameRate (1 << 1) +#define VIDEO_CONTROL_wPFrameRate (1 << 2) +#define VIDEO_CONTROL_wCompQuality (1 << 3) +#define VIDEO_CONTROL_wCompWindowSize (1 << 4) + +#define VIDEO_CONTROL_TEST_AND_SET(bmHint, field, src, dst) \ + ((VIDEO_CONTROL_##field & bmHint) ? dst->field =3D src->field : 0) + +typedef struct QEMU_PACKED VideoStreamingControl { + 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; + uint32_t dwClockFrequency; + uint8_t bmFramingInfo; + uint8_t bPreferedVersion; + uint8_t bMinVersion; + uint8_t bMaxVersion; + uint8_t bUsage; + uint8_t bBitDepthLuma; + uint8_t bmSettings; + uint8_t bMaxNumberOfRefFramesPlus1; + uint16_t bmRateControlModes; + uint16_t bmLayoutPerStream[4]; +} VideoStreamingControl; + +/* 2.4.3.3 Video and Still Image Payload Headers */ +#define PAYLOAD_HEADER_FID (1 << 0) +#define PAYLOAD_HEADER_EOF (1 << 1) +#define PAYLOAD_HEADER_PTS (1 << 2) +#define PAYLOAD_HEADER_SCR (1 << 3) +#define PAYLOAD_HEADER_RES (1 << 4) +#define PAYLOAD_HEADER_STI (1 << 5) +#define PAYLOAD_HEADER_ERR (1 << 6) +#define PAYLOAD_HEADER_EOH (1 << 7) + +typedef struct QEMU_PACKED VideoImagePayloadHeader { + uint8_t bHeaderLength; + uint8_t bmHeaderInfo; + uint32_t dwPresentationTime; + /* 6 bytes scrSourceClock */ + uint32_t dwStc; /* D31..D0 */ + uint16_t bmSof; /* D42..D32 */ +} VideoImagePayloadHeader; + +/* 2.4.2.2 Status Interrupt Endpoint */ +#define STATUS_INTERRUPT_CONTROL 0x1 +#define STATUS_INTERRUPT_STREAMING 0x2 + +#define STATUS_CONTROL_VALUE_CHANGE 0x00 +#define STATUS_CONTROL_INFO_CHANGE 0x01 +#define STATUS_CONTROL_FAILURE_CHANGE 0x02 +#define STATUS_CONTROL_MIN_CHANGE 0x03 +#define STATUS_CONTROL_MAX_CHANGE 0x04 + +typedef struct QEMU_PACKED VideoControlStatus { + uint8_t bStatusType; + uint8_t bOriginator; + uint8_t bEvent; + uint8_t bSelector; + uint8_t bAttribute; + uint8_t bValue[4]; +} VideoControlStatus; + +#endif --=20 2.25.1