All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/5] Introduce camera subsystem and USB video device
@ 2021-12-27 14:27 zhenwei pi
  2021-12-27 14:27 ` [PATCH 1/5] camera: Introduce camera subsystem and builtin driver zhenwei pi
                   ` (5 more replies)
  0 siblings, 6 replies; 12+ messages in thread
From: zhenwei pi @ 2021-12-27 14:27 UTC (permalink / raw)
  To: peter.maydell, richard.henderson, kraxel, eblake, pbonzini
  Cc: qemu-devel, zhenwei pi

1, The full picture of this patch set:
   +---------+       +------------+     +---------------+
   |UVC(done)|       |virtio(TODO)|     |other HW device|
   +---------+       +------------+     +---------------+
         |                 |                     |
         |            +------------+             |
	 +------------+camera(done)+-------------+
                      +----+-------+
                           |
         +-----------------+---------------------+
         |                 |                     |
  +------+------+     +----+-----+        +------+------+
  |builtin(done)|     |v4l2(done)|        |other drivers|
  +-------------+     +----------+        +-------------+

With this patch set, We can run a desktop VM (Ex Ubuntu-2004), several camera
APPs(cheese, kamoso, guvcview and qcam) work fine.

Some works still in working:
  1, hot-plug
  2, compat with live migration
  3, several actions defined in UVC SPEC

Zhenwei Pi (5):
  camera: Introduce camera subsystem and builtin driver
  camera: v4l2: Introduce v4l2 camera driver
  usb: Introduce video&mescellaneous
  usb: allow max 8192 bytes for desc
  usb-video: Introduce USB video class

 camera/builtin.c        |  717 ++++++++++++++++++++
 camera/camera-int.h     |   19 +
 camera/camera.c         |  522 +++++++++++++++
 camera/meson.build      |   20 +
 camera/trace-events     |   28 +
 camera/trace.h          |    1 +
 camera/v4l2.c           |  637 ++++++++++++++++++
 hw/usb/Kconfig          |    5 +
 hw/usb/desc.c           |   15 +-
 hw/usb/desc.h           |    1 +
 hw/usb/dev-video.c      | 1395 +++++++++++++++++++++++++++++++++++++++
 hw/usb/meson.build      |    1 +
 hw/usb/trace-events     |   11 +
 include/camera/camera.h |  238 +++++++
 include/hw/usb.h        |    2 +
 include/hw/usb/video.h  |  303 +++++++++
 meson.build             |   20 +-
 meson_options.txt       |    3 +
 qapi/camera.json        |  101 +++
 qapi/meson.build        |    1 +
 qapi/qapi-schema.json   |    1 +
 qemu-options.hx         |   13 +
 softmmu/vl.c            |    4 +
 23 files changed, 4050 insertions(+), 8 deletions(-)
 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 camera/v4l2.c
 create mode 100644 hw/usb/dev-video.c
 create mode 100644 include/camera/camera.h
 create mode 100644 include/hw/usb/video.h
 create mode 100644 qapi/camera.json

-- 
2.25.1



^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH 1/5] camera: Introduce camera subsystem and builtin driver
  2021-12-27 14:27 [PATCH 0/5] Introduce camera subsystem and USB video device zhenwei pi
@ 2021-12-27 14:27 ` zhenwei pi
  2021-12-27 14:27 ` [PATCH 2/5] camera: v4l2: Introduce v4l2 camera driver zhenwei pi
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: zhenwei pi @ 2021-12-27 14:27 UTC (permalink / raw)
  To: peter.maydell, richard.henderson, kraxel, eblake, pbonzini
  Cc: qemu-devel, zhenwei pi

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 <pizhenwei@bytedance.com>
---
 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 <pizhenwei@bytedance.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * 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 <jpeglib.h>
+#endif
+
+#include <cairo/cairo.h>
+
+#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_BUILTIN)
+
+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 width,
+                                         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 = 0; x < height; x++) {
+        for (y = 0; y < width / 2; y++) {
+            src = rgb + x * width * pixbytes + y * pixbytes * 2;
+            val = le32_to_cpu(*(uint32_t *)src);
+            r1 = (val >> 16) & 0xff;
+            g1 = (val >> 8) & 0xff;
+            b1 = val & 0xff;
+            val = le32_to_cpu(*(uint32_t *)(src + pixbytes));
+            r2 = (val >> 16) & 0xff;
+            g2 = (val >> 8) & 0xff;
+            b2 = val & 0xff;
+
+            y1 = pixel_clip(((66 * r1 + 129 * g1 + 25 * b1 + 128) >> 8) + 16);
+            u1 = pixel_clip((((-38 * r1 - 74 * g1 + 112 * b1 + 128) >> 8)
+                            + ((-38 * r2 - 74 * g2 + 112 * b2 + 128) >> 8)) / 2
+                            + 128);
+            y2 = pixel_clip(((66 * r2 + 129 * g2 + 25 * b2 + 128) >> 8) + 16);
+            v1 = pixel_clip((((112 * r1 - 94 * g1 - 18 * b1 + 128) >> 8)
+                            + ((112 * r2 - 94 * g2 - 18 * b2 + 128) >> 8)) / 2
+                            + 128);
+            dst = yuv + x * width * 2 + y * 4;
+            *dst++ = y1;
+            *dst++ = u1;
+            *dst++ = y2;
+            *dst = v1;
+        }
+    }
+}
+
+static void camera_builtin_draw_info(QEMUCamera *camera)
+{
+    CameraBuiltin *builtin = CAMERA_BUILTIN_DEV(camera);
+    char text[32];
+    uint16_t fontsize = 20;
+    uint16_t y = 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 = 0; (t < QEMUCameraControlMax)&&(y <= builtin->height); t++) {
+        y += 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 color)
+{
+    CameraBuiltin *builtin = CAMERA_BUILTIN_DEV(camera);
+    uint8_t r = 0, g = 0, b = 0;
+
+    switch ((int)color) {
+    case COLOR_TYPE_RED:
+        r = 1;
+        break;
+    case COLOR_TYPE_GREEN:
+        g = 1;
+        break;
+    case COLOR_TYPE_BLUE:
+        b = 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 = CAMERA_BUILTIN_DEV(camera);
+    uint16_t colors;
+    uint16_t lines = builtin->height / 7;
+    uint8_t rainbow[7][3] = {
+        {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 = 0 ; colors < 7; colors++) {
+        cairo_move_to(builtin->cr, 0, lines * colors);
+        addr = 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, lines);
+        cairo_fill(builtin->cr);
+    }
+}
+
+static void camera_builtin_draw_digital_rain(QEMUCamera *camera)
+{
+#define DIGITAL_RAIN_FONT 20
+    CameraBuiltin *builtin = CAMERA_BUILTIN_DEV(camera);
+    int rain_rows = builtin->width / 2 / DIGITAL_RAIN_FONT;
+    int rain_len = builtin->height * 2 / DIGITAL_RAIN_FONT;
+    int i, j, x, y, asterisks, first, last;
+    char *addr, *base;
+    char text[2] = {0};
+    int len = rain_len / 2;
+
+    if (!builtin->opaque) {
+        builtin->opaque = g_malloc(rain_rows * rain_len);
+        memset(builtin->opaque, '*', rain_rows * rain_len);
+    }
+
+    base = 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 = 0; i < rain_rows; i++) {
+        addr = base + i * rain_len + len;
+        asterisks = 0;
+
+        for (j = 0; (j < len) && (addr[j] == '*'); j++) {
+            asterisks++;
+        }
+
+        if (asterisks == len) {
+rerandom:
+            first = random() % len;
+            last = random() % len;
+            if ((first + len / 4) >= last) {
+                goto rerandom;
+            }
+
+            for (j = first; j < last; j++) {
+                *(addr + j) = random() % 26 + 'A' + (random() % 2) * 32;
+            }
+        }
+
+        addr = base + i * rain_len;
+
+        for (j = 0; (j < len) && (addr[j] == '*'); ) {
+            j++;
+        }
+
+        if (j == len) {
+            goto update_frame;
+        }
+
+        cairo_set_source_rgb(builtin->cr, 1, 1, 1); /* first char of row */
+        x = DIGITAL_RAIN_FONT * i * 2;
+        y = 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] == '*') {
+                continue;
+            }
+            x = DIGITAL_RAIN_FONT * i * 2;
+            y = 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 = base + i * rain_len;
+        memmove(addr, addr + 1, 2 * len - 1);
+        addr[2 * len - 1] = '*';
+    }
+}
+
+#ifdef CONFIG_VNC_JPEG
+static void camera_builtin_jpeg_init_destination(j_compress_ptr cinfo)
+{
+    CameraBuiltin *builtin = cinfo->client_data;
+
+    cinfo->dest->next_output_byte = builtin->image;
+    cinfo->dest->free_in_buffer = 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 pixbytes)
+{
+    uint16_t x;
+    uint32_t val;
+    uint8_t *dst, *src;
+
+    for (x = 0; x < width; x++) {
+        src = rgb + x * pixbytes;
+        val = le32_to_cpu(*(uint32_t *)src);
+        dst = jpeg + 3 * x;
+        *(dst++) = (val >> 16) & 0xff; /* R */
+        *(dst++) = (val >> 8) & 0xff; /* G */
+        *dst = val & 0xff; /* B */
+    }
+}
+
+static size_t camera_builtin_rgb24_to_jpeg(QEMUCamera *camera, uint8_t *rgb,
+                                           uint8_t *jpeg, uint16_t width,
+                                           int height, uint8_t pixbytes)
+{
+    CameraBuiltin *builtin = CAMERA_BUILTIN_DEV(camera);
+    struct jpeg_compress_struct cinfo;
+    struct jpeg_error_mgr jerr;
+    struct jpeg_destination_mgr manager = {0};
+    JSAMPROW row_pointer[1];
+    g_autofree uint8_t *linebuf = g_malloc(width * 3);
+    uint8_t *addr;
+    int quality = 50;
+
+    cinfo.err = jpeg_std_error(&jerr);
+    jpeg_create_compress(&cinfo);
+    cinfo.client_data = builtin;
+    cinfo.image_width = builtin->width;
+    cinfo.image_height = builtin->height;
+    cinfo.input_components = 3;
+    cinfo.in_color_space = JCS_RGB;
+    jpeg_set_defaults(&cinfo);
+    jpeg_set_quality(&cinfo, quality, TRUE);
+    manager.init_destination = camera_builtin_jpeg_init_destination;
+    manager.term_destination = camera_builtin_jpeg_term_destination;
+    cinfo.dest = &manager;
+    row_pointer[0] = linebuf;
+
+    jpeg_start_compress(&cinfo, true);
+
+    while (cinfo.next_scanline < cinfo.image_height) {
+        addr = 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 *rgb,
+                                           uint8_t *jpeg, uint16_t width,
+                                           int height, uint8_t pixbytes)
+{
+    return -1;
+}
+#endif
+
+static void camera_builtin_frame_timer(void *opaque)
+{
+    QEMUCamera *camera = opaque;
+    CameraBuiltin *builtin = CAMERA_BUILTIN_DEV(camera);
+    uint8_t *image_addr = cairo_image_surface_get_data(builtin->surface);
+    size_t image_bytes = 0;
+    uint16_t w = builtin->width, h = 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 = camera_builtin_rgb24_to_jpeg(camera, image_addr,
+                          builtin->image, w, h, builtin->pixbytes);
+        image_addr = builtin->image;
+        break;
+    case QEMU_CAMERA_PIX_FMT_YUYV:
+        camera_builtin_rgb24_to_yuyv(image_addr, builtin->image, w, h,
+                                     builtin->pixbytes);
+        image_bytes = w * h * 2;
+        image_addr = builtin->image;
+        break;
+    case QEMU_CAMERA_PIX_FMT_RGB565:
+        /* no need to convert, just put builtin->surface to uplayer */
+        image_bytes = 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 = CAMERA_BUILTIN_DEV(camera);
+    int index, total = 0;
+
+    for (index = 0; index < MIN(npixfmt, builtin->npixfmts); index++) {
+        pixfmts[total++] = builtin->pixfmts[index];
+    }
+
+    return total;
+}
+
+static int camera_builtin_enum_frame_size(QEMUCamera *camera, uint32_t pixfmt,
+                                          QEMUCameraFrameSize *frmszs,
+                                          int nfrmsz, Error **errp)
+{
+    CameraBuiltin *builtin = CAMERA_BUILTIN_DEV(camera);
+    QEMUCameraFrameSize *frmsz;
+
+    if (nfrmsz < 1) {
+        return 0;
+    }
+
+    frmsz = frmszs;
+    frmsz->pixel_format = pixfmt;
+    frmsz->type = QEMU_CAMERA_FRMSIZE_TYPE_DISCRETE;
+    frmsz->d.width = builtin->width;
+    frmsz->d.height = builtin->height;
+
+    return 1;
+}
+
+static int camera_builtin_enum_frame_interval(QEMUCamera *camera,
+                                              const QEMUCameraFormat *format,
+                                              QEMUCameraFrameInterval *frmivals,
+                                              int nfrmival, Error **errp)
+{
+    CameraBuiltin *builtin = CAMERA_BUILTIN_DEV(camera);
+    QEMUCameraFrameInterval *frmival;
+
+    if (nfrmival < 1) {
+        return 0;
+    }
+
+    if (format->width != builtin->width || format->height != builtin->height) {
+        error_setg(errp, "%s: enum frame interval unsupported mismatched "
+                   "width(%d)/height(%d)", TYPE_CAMERA_BUILTIN, format->width,
+                   format->height);
+        return 0;
+    }
+
+    frmival = frmivals;
+    frmival->pixel_format = format->pixel_format;
+    frmival->width = format->width;
+    frmival->height = format->height;
+    frmival->type = QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE;
+    frmival->d.numerator = 1;
+    frmival->d.denominator = builtin->fps;
+
+    return 1;
+}
+
+static int camera_builtin_set_frame_interval(QEMUCamera *camera,
+               const QEMUCameraFrameInterval *frmival, Error **errp)
+{
+    CameraBuiltin *builtin = CAMERA_BUILTIN_DEV(camera);
+
+    if (frmival->width != builtin->width
+        || frmival->height != builtin->height) {
+        error_setg(errp, "%s: set frame interval unsupported mismatched "
+                   "width(%d)/height(%d)", TYPE_CAMERA_BUILTIN, frmival->width,
+                   frmival->height);
+        return 0;
+    }
+
+    builtin->pixfmt = frmival->pixel_format;
+
+    return 0;
+}
+
+static int camera_builtin_enum_control(QEMUCamera *camera,
+               QEMUCameraControl *controls, int ncontrols, Error **errp)
+{
+    CameraBuiltin *builtin = CAMERA_BUILTIN_DEV(camera);
+    QEMUCameraControl *control;
+    QEMUCameraControlType t;
+
+    for (t = 0; t < QEMUCameraControlMax; t++) {
+        control = controls + t;
+        control->type = t;
+        control->cur = builtin->ctrl[t][ATTRIBUTE_CUR];
+        control->def = builtin->ctrl[t][ATTRIBUTE_DEF];
+        control->min = builtin->ctrl[t][ATTRIBUTE_MIN];
+        control->max = builtin->ctrl[t][ATTRIBUTE_MAX];
+        control->step = builtin->ctrl[t][ATTRIBUTE_STEP];
+    }
+
+    return t;
+}
+
+static int camera_builtin_set_control(QEMUCamera *camera,
+               const QEMUCameraControl *control, Error **errp)
+{
+    CameraBuiltin *builtin = CAMERA_BUILTIN_DEV(camera);
+
+    builtin->ctrl[control->type][ATTRIBUTE_CUR] = 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 = builtinopts->x;                       \
+        } else {                                               \
+            builtin->x = CAMERA_BUILTIN_DEF_##y;               \
+        }                                                      \
+    } while (0)
+
+#define CHECK_AND_GET_VAL(x, def)                              \
+    do {                                                       \
+        if (builtinopts->has_##x) {                            \
+            builtin->x = builtinopts->x;                       \
+        } else {                                               \
+            builtin->x = def;                                  \
+        }                                                      \
+    } while (0)
+
+static void camera_builtin_open(QEMUCamera *camera, Error **errp)
+{
+    CameraBuiltin *builtin = CAMERA_BUILTIN_DEV(camera);
+    CameraBuiltinOptions *builtinopts = &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++] = QEMU_CAMERA_PIX_FMT_MJPEG;
+    }
+
+    if (builtin->yuv) {
+        builtin->pixfmts[builtin->npixfmts++] = QEMU_CAMERA_PIX_FMT_YUYV;
+    }
+
+    if (builtin->rgb565) {
+        builtin->pixfmts[builtin->npixfmts++] = QEMU_CAMERA_PIX_FMT_RGB565;
+    }
+
+    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 = CAMERA_BUILTIN_DEV(camera);
+    cairo_format_t cairofmt;
+    size_t imagesize;
+
+    imagesize = builtin->height * builtin->width * 2;
+    if (builtin->pixfmt == QEMU_CAMERA_PIX_FMT_RGB565) {
+        cairofmt = CAIRO_FORMAT_RGB16_565;
+        builtin->pixbytes = 2;
+    } else {
+        cairofmt = CAIRO_FORMAT_RGB24;
+        builtin->pixbytes = 4; /* see enum cairo_format_t in cairo.h */
+    }
+    builtin->surface = cairo_image_surface_create(cairofmt, builtin->width,
+                                                  builtin->height);
+    builtin->cr = cairo_create(builtin->surface);
+    qemu_camera_alloc_image(camera, imagesize, errp);
+    builtin->image_size = imagesize;
+    builtin->image = g_malloc(imagesize);
+
+    builtin->frame_timer = 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 = CAMERA_BUILTIN_DEV(camera);
+
+    timer_free(builtin->frame_timer);
+
+    qemu_camera_free_image(camera);
+    g_free(builtin->image);
+    builtin->image = NULL;
+    builtin->sequence = 0;
+
+    cairo_destroy(builtin->cr);
+    cairo_surface_destroy(builtin->surface);
+
+    g_free(builtin->opaque);
+    builtin->opaque = NULL;
+}
+
+
+static void camera_builtin_init(Object *obj)
+{
+    QEMUCamera *camera = CAMERADEV(obj);
+    CameraBuiltin *builtin = 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 = 0; i < QEMUCameraControlMax; i++) {
+        builtin->ctrl[i][ATTRIBUTE_DEF] = 0x7f;
+        builtin->ctrl[i][ATTRIBUTE_MIN] = 0;
+        builtin->ctrl[i][ATTRIBUTE_MAX] = 0xff;
+        builtin->ctrl[i][ATTRIBUTE_CUR] = 0;
+        builtin->ctrl[i][ATTRIBUTE_STEP] = 1;
+    }
+
+    builtin->ctrl[QEMUCameraHueAuto][ATTRIBUTE_DEF] = 0;
+    builtin->ctrl[QEMUCameraHueAuto][ATTRIBUTE_MIN] = 0;
+    builtin->ctrl[QEMUCameraHueAuto][ATTRIBUTE_MAX] = 1;
+}
+
+static void camera_builtin_finalize(Object *obj)
+{
+    QEMUCamera *camera = CAMERADEV(obj);
+    Error *local_err = NULL;
+
+    camera_builtin_stream_off(camera, &local_err);
+}
+
+static void camera_builtin_class_init(ObjectClass *oc, void *data)
+{
+    QEMUCameraClass *klass = CAMERADEV_CLASS(oc);
+
+    klass->open = camera_builtin_open;
+    klass->stream_on = camera_builtin_stream_on;
+    klass->stream_off = camera_builtin_stream_off;
+    klass->enum_pixel_format = camera_builtin_enum_pixel_format;
+    klass->enum_frame_size = camera_builtin_enum_frame_size;
+    klass->enum_frame_interval = camera_builtin_enum_frame_interval;
+    klass->set_frame_interval = camera_builtin_set_frame_interval;
+    klass->enum_control = camera_builtin_enum_control;
+    klass->set_control = camera_builtin_set_control;
+}
+
+static const TypeInfo camera_builtin_type_info = {
+    .name = TYPE_CAMERA_BUILTIN,
+    .parent = TYPE_CAMERADEV,
+    .instance_size = sizeof(CameraBuiltin),
+    .instance_init = camera_builtin_init,
+    .instance_finalize = camera_builtin_finalize,
+    .class_init = 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 <pizhenwei@bytedance.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * 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 size);
+
+#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 <pizhenwei@bytedance.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * 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 = 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, typename);
+        return NULL;
+    }
+
+    return CAMERADEV_CLASS(oc);
+}
+
+static QEMUCamera *qemu_camera_new(Cameradev *dev, Error **errp)
+{
+    Object *obj;
+    QEMUCamera *camera = NULL;
+    g_autofree char *typename = NULL;
+    Error *local_err = NULL;
+    QEMUCameraClass *klass;
+    const char *driver = CameradevDriver_str(dev->driver);
+
+    typename = g_strdup_printf("%s-%s", TYPE_CAMERADEV, driver);
+    if (!camera_get_class(typename, errp)) {
+        return NULL;
+    }
+
+    obj = object_new(typename);
+    if (!obj) {
+        return NULL;
+    }
+
+    camera = CAMERADEV(obj);
+    camera->dev = dev;
+
+    klass = 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 = 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 *opaque),
+                                   void *opaque)
+{
+    CameradevClassFE fe = { .fn = fn, .opaque = opaque };
+
+    object_class_foreach(cameradev_class_foreach, TYPE_CAMERADEV, false, &fe);
+}
+
+static void help_string_append(const char *name, void *opaque)
+{
+    GString *str = 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 = 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 = qobject_input_visitor_new_str(opt, "driver", &error_fatal);
+    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 = 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 = 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 = klass->enum_pixel_format(camera, pixfmts, npixfmt, errp);
+    if (ret > 0) {
+        for (i = 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), ret);
+    }
+
+    return ret;
+}
+
+int qemu_camera_enum_frame_size(QEMUCamera *camera, uint32_t pixfmt,
+                                QEMUCameraFrameSize *frmszs, int nfrmsz,
+                                Error **errp)
+{
+    QEMUCameraClass *klass = 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 = klass->enum_frame_size(camera, pixfmt, frmszs, nfrmsz, errp);
+    if (ret > 0) {
+        QEMUCameraFrameSize *frmsz;
+
+        for (i = 0; i < ret; i++) {
+            frmsz = frmszs + i;
+            if (frmsz->type == 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 = CAMERADEV_GET_CLASS(camera);
+    int ret, i;
+
+    if (!klass->enum_frame_interval) {
+        error_setg(errp, "%s: %s missing enum frame interval implementation",
+                   TYPE_CAMERADEV, qemu_camera_id(camera));
+        return -ENOTSUP;
+    }
+
+    ret = klass->enum_frame_interval(camera, format, frmivals, nfrmival, errp);
+    if (ret > 0) {
+        QEMUCameraFrameInterval *frmival;
+
+        for (i = 0; i < ret; i++) {
+            frmival = frmivals + i;
+            if (frmival->type == QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE) {
+                trace_qemu_camera_enum_frame_interval_d(qemu_camera_id(camera),
+                    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 = 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 = klass->set_frame_interval(camera, frmival, errp);
+    if (frmival->type == 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 *controls,
+                             int ncontrols, Error **errp)
+{
+    QEMUCameraClass *klass = 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 = klass->enum_control(camera, controls, ncontrols, errp);
+    if (ret > 0) {
+        QEMUCameraControl *control;
+
+        for (i = 0; i < ret; i++) {
+            control = 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 = 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 = 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 = cb;
+    camera->cb_opaque = 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 = 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 = NULL;
+    camera->cb_opaque = 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 = 0;
+
+    qemu_mutex_lock(&camera->image_lock);
+    assert(camera->image_pos <= camera->image_bytes);
+    length = 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 = 0;
+    void *addr;
+
+    qemu_mutex_lock(&camera->image_lock);
+
+    assert(camera->image_pos <= camera->image_bytes);
+    length = camera->image_bytes - camera->image_pos;
+    length = MIN(size, length);
+    if (!length) {
+        goto out;
+    }
+
+    addr = camera->image_addr + camera->image_pos;
+    iov_from_buf(iov, iov_cnt, offset, addr, size);
+    camera->image_pos += length;
+    if (camera->image_pos == camera->image_bytes) {
+        /* previous frame already fully read*/
+        camera->image_frames = camera->image_sequence;
+        camera->image_pos = 0;
+        camera->image_bytes = 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 = CAMERADEV(obj);
+
+    qemu_mutex_init(&camera->image_lock);
+}
+
+static void camera_finalize(Object *obj)
+{
+    QEMUCamera *camera = CAMERADEV(obj);
+
+    qemu_mutex_destroy(&camera->image_lock);
+}
+
+static const TypeInfo camera_type_info = {
+    .name = TYPE_CAMERADEV,
+    .parent = TYPE_OBJECT,
+    .instance_size = sizeof(QEMUCamera),
+    .instance_init = camera_init,
+    .instance_finalize = camera_finalize,
+    .abstract = true,
+    .class_size = 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 == size) {
+        /* no need to re-allocate the same size image buffer */
+        goto out;
+    }
+
+    g_free(camera->image_addr);
+    camera->image_addr = g_malloc0(size);
+    camera->image_size = size;
+    camera->image_pos = 0;
+    camera->image_bytes = 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 = NULL;
+    camera->image_size = 0;
+    camera->image_pos = 0;
+    camera->image_bytes = 0;
+    qemu_mutex_unlock(&camera->image_lock);
+}
+
+void qemu_camera_new_image(QEMUCamera *camera, const void *addr, size_t size)
+{
+    trace_qemu_camera_new_image(qemu_camera_id(camera), camera->image_sequence,
+                                size);
+    qemu_mutex_lock(&camera->image_lock);
+
+    assert(camera->image_addr);
+    assert(size <= 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 = 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 = {}
+foreach m : [
+  ['builtin', cairo, files('builtin.c')],
+]
+  if m[1].found()
+    module_ss = ss.source_set()
+    module_ss.add(m[1], m[2])
+    camera_modules += {m[0] : module_ss}
+  endif
+endforeach
+
+modules += {'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 length %ld"
+qemu_camera_alloc_image(const char *dev, size_t size) "%s: alloc image size %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: pixfmt 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, height %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 min, 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 <pizhenwei@bytedance.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * 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 == QEMU_CAMERA_PIX_FMT_MJPEG)
+        || (pixfmt == QEMU_CAMERA_PIX_FMT_YUYV)
+        || (pixfmt == 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[] = {
+    "Brightness",
+    "Contrast",
+    "Gain",
+    "Gamma",
+    "Hue",
+    "HueAuto",
+    "Saturation",
+    "Sharpness",
+    "WhiteBalanceTemperature",
+    "Max",
+};
+
+static inline const char *QEMUCameraControlTypeString(QEMUCameraControlType 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 avail);
+
+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 nfrmival,
+                               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 *control,
+                       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 *controls,
+                             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 = dependency('lttng-ust', required: true, method: 'pkg-config',
                      kwargs: static_kwargs)
 endif
+
+cairo = not_found
+if have_system
+  cairo = dependency('cairo', required: have_system,
+                     method: 'pkg-config', kwargs: static_kwargs)
+endif
+
 pixman = not_found
 if have_system or have_tools
   pixman = dependency('pixman-1', required: have_system, version:'>=0.21.8',
@@ -1452,6 +1459,7 @@ config_host_data.set('HOST_' + host_arch.to_upper(), 1)
 
 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 += hxdep
 authz_ss = ss.source_set()
 blockdev_ss = ss.source_set()
 block_ss = ss.source_set()
+camera_ss = ss.source_set()
 chardev_ss = ss.source_set()
 common_ss = ss.source_set()
 common_user_ss = ss.source_set()
@@ -2453,6 +2462,7 @@ if have_system
     'audio',
     'backends',
     'backends/tpm',
+    'camera',
     'chardev',
     'ebpf',
     'hw/9pfs',
@@ -2572,6 +2582,7 @@ endif
 
 subdir('audio')
 subdir('io')
+subdir('camera')
 subdir('chardev')
 subdir('fsdev')
 subdir('dump')
@@ -2848,6 +2859,13 @@ libchardev = static_library('chardev', chardev_ss.sources() + genh,
 
 chardev = declare_dependency(link_whole: libchardev)
 
+camera_ss = camera_ss.apply(config_host, strict: false)
+libcamera = static_library('camera', camera_ss.sources() + genh,
+                           name_suffix: 'fa',
+                           build_by_default: false)
+
+camera = declare_dependency(link_whole: libcamera)
+
 hwcore_ss = hwcore_ss.apply(config_host, strict: false)
 libhwcore = 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
 
-softmmu_ss.add(authz, blockdev, chardev, crypto, io, qmp)
+softmmu_ss.add(authz, blockdev, camera, chardev, crypto, io, qmp)
 common_ss.add(qom, qemuutil)
 
 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',
 
 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<pizhenwei@bytedance.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or later.
+# See the COPYING file in the top-level directory.
+
+##
+# = 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
 
+DEFHEADING(Camera device options:)
+
+DEF("cameradev", HAS_ARG, QEMU_OPTION_cameradev,
+    "-cameradev help\n"
+#ifdef CONFIG_CAIRO
+    "-cameradev builtin,id=id[,debug=true|false][,fps=FPS][,width=WIDTH][,height=HEIGHT][,mjpeg=true|false][,yuv=true|false][,rgb565=true|false][,bgcolor=blue|gree|red|rainbow|digital-rain]\n"
+#endif
+    , QEMU_ARCH_ALL
+)
+
 DEFHEADING()
 
 #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"
 
 #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 = 0;
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 2/5] camera: v4l2: Introduce v4l2 camera driver
  2021-12-27 14:27 [PATCH 0/5] Introduce camera subsystem and USB video device zhenwei pi
  2021-12-27 14:27 ` [PATCH 1/5] camera: Introduce camera subsystem and builtin driver zhenwei pi
@ 2021-12-27 14:27 ` zhenwei pi
  2021-12-27 14:27 ` [PATCH 3/5] usb: Introduce video&mescellaneous zhenwei pi
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: zhenwei pi @ 2021-12-27 14:27 UTC (permalink / raw)
  To: peter.maydell, richard.henderson, kraxel, eblake, pbonzini
  Cc: qemu-devel, zhenwei pi

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=/dev/video0,id=camera0

The basic logic of v4l2 driver:
   stream on -> qbuf -> dqbuf(drive by POLLIN event) -> qbuf -> dqbuf
     ... -> stream off

Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
---
 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',
 )])
 
+camera_ss.add(when: 'CONFIG_LINUX', if_true: files(
+  'v4l2.c',
+))
+
 camera_modules = {}
 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"
 
 # 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 <pizhenwei@bytedance.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * 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 <linux/videodev2.h>
+#include <sys/ioctl.h>
+
+#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[] = {
+    { .q = QEMUCameraBrightness,
+      .v = V4L2_CID_BRIGHTNESS },
+    { .q = QEMUCameraContrast,
+      .v = V4L2_CID_CONTRAST },
+    { .q = QEMUCameraGain,
+      .v = V4L2_CID_GAIN },
+    { .q = QEMUCameraGamma,
+      .v = V4L2_CID_GAMMA },
+    { .q = QEMUCameraHue,
+      .v = V4L2_CID_HUE },
+    { .q = QEMUCameraHueAuto,
+      .v = V4L2_CID_HUE_AUTO },
+    { .q = QEMUCameraSaturation,
+      .v = V4L2_CID_SATURATION },
+    { .q = QEMUCameraSharpness,
+      .v = V4L2_CID_SHARPNESS },
+    { .q = QEMUCameraWhiteBalanceTemperature,
+      .v = V4L2_CID_WHITE_BALANCE_TEMPERATURE },
+};
+
+static QEMUCameraControlType camera_v4l2_control_to_qemu(uint32_t id)
+{
+    CameraV4l2Ctrl *ctrl;
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(camera_v4l2_ctrl_table); i++) {
+        ctrl = &camera_v4l2_ctrl_table[i];
+        if (ctrl->v == id) {
+            return ctrl->q;
+        }
+    }
+
+    return QEMUCameraControlMax;
+}
+
+static uint32_t camera_qemu_control_to_v4l2(QEMUCameraControlType type)
+{
+    CameraV4l2Ctrl *ctrl;
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(camera_v4l2_ctrl_table); i++) {
+        ctrl = &camera_v4l2_ctrl_table[i];
+        if (ctrl->q == type) {
+            return ctrl->v;
+        }
+    }
+
+    return 0;
+}
+
+static int camera_v4l2_enum_pixel_format(QEMUCamera *camera, uint32_t *pixfmts,
+                                         int npixfmt, Error **errp)
+{
+    CameraV4l2 *v4l2 = CAMERA_V4L2_DEV(camera);
+    CameraV4l2Options *v4l2opts = &camera->dev->u.v4l2;
+    struct v4l2_fmtdesc v4l2_fmt;
+    int index, total = 0;
+
+    for (index = 0; total < npixfmt; index++) {
+        v4l2_fmt.index = index;
+        v4l2_fmt.type = V4L2_CAP_VIDEO_CAPTURE;
+        if (ioctl(v4l2->devfd, VIDIOC_ENUM_FMT, &v4l2_fmt) < 0) {
+            if (errno == 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++] = v4l2_fmt.pixelformat;
+    }
+
+    return total;
+}
+
+static int camera_v4l2_enum_frame_size(QEMUCamera *camera, uint32_t pixfmt,
+                                       QEMUCameraFrameSize *frmszs, int nfrmsz,
+                                       Error **errp)
+{
+    CameraV4l2 *v4l2 = CAMERA_V4L2_DEV(camera);
+    CameraV4l2Options *v4l2opts = &camera->dev->u.v4l2;
+    QEMUCameraFrameSize *frmsz;
+    struct v4l2_frmsizeenum v4l2_frmsz;
+    int index, total = 0;
+
+    if (!qemu_camera_pixel_supported(pixfmt)) {
+        return -EINVAL;
+    }
+
+    for (index = 0; total < nfrmsz; index++) {
+        v4l2_frmsz.index = index;
+        v4l2_frmsz.pixel_format = pixfmt;
+        if (ioctl(v4l2->devfd, VIDIOC_ENUM_FRAMESIZES, &v4l2_frmsz) < 0) {
+            if (errno == 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 != V4L2_FRMSIZE_TYPE_DISCRETE) {
+            continue; /* TODO stepwise support in future*/
+        }
+
+        frmsz = frmszs + total++;
+        frmsz->pixel_format = v4l2_frmsz.pixel_format;
+        frmsz->type = QEMU_CAMERA_FRMSIZE_TYPE_DISCRETE;
+        frmsz->d.width = v4l2_frmsz.discrete.width;
+        frmsz->d.height = v4l2_frmsz.discrete.height;
+    }
+
+    return total;
+}
+
+static int camera_v4l2_enum_frame_interval(QEMUCamera *camera,
+                                           const QEMUCameraFormat *format,
+                                           QEMUCameraFrameInterval *frmivals,
+                                           int nfrmival, Error **errp)
+{
+    CameraV4l2 *v4l2 = CAMERA_V4L2_DEV(camera);
+    CameraV4l2Options *v4l2opts = &camera->dev->u.v4l2;
+    QEMUCameraFrameInterval *frmival;
+    struct v4l2_frmivalenum v4l2_frmival;
+    int index, total = 0;
+
+    for (index = 0; total < nfrmival; index++) {
+        v4l2_frmival.index = index;
+        v4l2_frmival.pixel_format = format->pixel_format;
+        v4l2_frmival.width = format->width;
+        v4l2_frmival.height = format->height;
+        if (ioctl(v4l2->devfd, VIDIOC_ENUM_FRAMEINTERVALS, &v4l2_frmival) < 0) {
+            if (errno == 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 != V4L2_FRMIVAL_TYPE_DISCRETE) {
+            continue; /* TODO stepwise support in future*/
+        }
+
+        frmival = frmivals + total++;
+        frmival->pixel_format = v4l2_frmival.pixel_format;
+        frmival->type = QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE;
+        frmival->width = v4l2_frmival.width;
+        frmival->height = v4l2_frmival.height;
+        frmival->d.numerator = v4l2_frmival.discrete.numerator;
+        frmival->d.denominator = v4l2_frmival.discrete.denominator;
+    }
+
+    return total;
+}
+
+static int camera_v4l2_get_format(QEMUCamera *camera, QEMUCameraFormat *format,
+                                  Error **errp)
+{
+    CameraV4l2 *v4l2 = CAMERA_V4L2_DEV(camera);
+    CameraV4l2Options *v4l2opts = &camera->dev->u.v4l2;
+    struct v4l2_format v4l2_fmt = {.type = V4L2_BUF_TYPE_VIDEO_CAPTURE};
+    struct v4l2_pix_format *v4l2_pix = &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 = v4l2_pix->pixelformat;
+        format->width = v4l2_pix->width;
+        format->height = v4l2_pix->height;
+    }
+
+    v4l2->sizeimage = v4l2_pix->sizeimage;
+
+    return 0;
+}
+
+static int camera_v4l2_set_format(QEMUCamera *camera,
+                                  const QEMUCameraFormat *format, Error **errp)
+{
+    CameraV4l2 *v4l2 = CAMERA_V4L2_DEV(camera);
+    CameraV4l2Options *v4l2opts = &camera->dev->u.v4l2;
+    struct v4l2_format v4l2_fmt = {0};
+    struct v4l2_pix_format *v4l2_pix;
+
+    v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    v4l2_pix = &v4l2_fmt.fmt.pix;
+    v4l2_pix->pixelformat = format->pixel_format;
+    v4l2_pix->width = format->width;
+    v4l2_pix->height = 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 = v4l2_pix->sizeimage;
+
+    return 0;
+}
+
+static int camera_v4l2_set_frame_interval(QEMUCamera *camera,
+               const QEMUCameraFrameInterval *frmival, Error **errp)
+{
+    CameraV4l2 *v4l2 = CAMERA_V4L2_DEV(camera);
+    CameraV4l2Options *v4l2opts = &camera->dev->u.v4l2;
+    QEMUCameraFormat fmt;
+    struct v4l2_streamparm streamparm;
+    struct v4l2_captureparm *capture;
+    int ret;
+
+    if (frmival->type != QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE) {
+        error_setg(errp, "%s: only support discrete mode", TYPE_CAMERA_V4L2);
+        return -ENOTSUP;
+    }
+
+    fmt.pixel_format = frmival->pixel_format;
+    fmt.width = frmival->width;
+    fmt.height = frmival->height;
+    ret = camera_v4l2_set_format(camera, &fmt, errp);
+    if (ret) {
+        return ret;
+    }
+
+    streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    capture = &streamparm.parm.capture;
+    capture->timeperframe.numerator = frmival->d.numerator;
+    capture->timeperframe.denominator = 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 = CAMERA_V4L2_DEV(camera);
+    CameraV4l2Options *v4l2opts = &camera->dev->u.v4l2;
+    QEMUCameraControl *control;
+    struct v4l2_queryctrl v4l2_ctrl = {0};
+    QEMUCameraControlType type;
+    int index, total = 0;
+
+    for (index = 0; total < ncontrols; index++) {
+        v4l2_ctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
+        if (ioctl(v4l2->devfd, VIDIOC_QUERYCTRL, &v4l2_ctrl) < 0) {
+            if (errno == 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 = camera_v4l2_control_to_qemu(v4l2_ctrl.id);
+        if (type == QEMUCameraControlMax) {
+            continue;
+        }
+
+        control = controls + total++;
+        control->type = type;
+        control->def = v4l2_ctrl.default_value;
+        control->min = v4l2_ctrl.minimum;
+        control->max = v4l2_ctrl.maximum;
+        control->step = v4l2_ctrl.step;
+    }
+
+    return total;
+}
+
+static int camera_v4l2_set_control(QEMUCamera *camera,
+               const QEMUCameraControl *control, Error **errp)
+{
+    CameraV4l2 *v4l2 = CAMERA_V4L2_DEV(camera);
+    CameraV4l2Options *v4l2opts = &camera->dev->u.v4l2;
+    struct v4l2_control v4l2_ctrl;
+    uint32_t cid;
+
+    cid = 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 = cid;
+    v4l2_ctrl.value = 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 = CAMERA_V4L2_DEV(camera);
+    struct v4l2_buffer buffer = {0};
+
+    trace_camera_v4l2_qbuf(qemu_camera_id(camera), index);
+
+    buffer.index = index;
+    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    buffer.field = V4L2_FIELD_ANY;
+    buffer.memory = V4L2_MEMORY_MMAP;
+
+    return ioctl(v4l2->devfd, VIDIOC_QBUF, &buffer);
+}
+
+static int camera_v4l2_dqbuf(QEMUCamera *camera)
+{
+    CameraV4l2 *v4l2 = CAMERA_V4L2_DEV(camera);
+    struct v4l2_buffer buffer;
+
+    buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    buffer.memory = 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 = CAMERA_V4L2_DEV(camera);
+    struct v4l2_requestbuffers v4l2_reqbufs = {0};
+    CameraV4l2Buffer *buffer;
+    int index;
+
+    /* 1, try to dequeue all buffers */
+    for (index = 0; index < v4l2->nbuffers; index++) {
+        camera_v4l2_dqbuf(camera);
+    }
+
+    /* 2, try to unmap all buffers */
+    for (index = 0; index < v4l2->nbuffers; index++) {
+        buffer = &v4l2->buffers[index];
+        if (buffer->addr) {
+            munmap(buffer->addr, buffer->length);
+            buffer->addr = NULL;
+            buffer->length = 0;
+        }
+    }
+
+    /* 3, free all the v4l2 reqbufs */
+    v4l2_reqbufs.count = 0;
+    v4l2_reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    v4l2_reqbufs.memory = V4L2_MEMORY_MMAP;
+    ioctl(v4l2->devfd, VIDIOC_REQBUFS, &v4l2_reqbufs);
+}
+
+static int camera_v4l2_request_buffers(QEMUCamera *camera, Error **errp)
+{
+    CameraV4l2 *v4l2 = CAMERA_V4L2_DEV(camera);
+    struct v4l2_requestbuffers v4l2_reqbufs = {0};
+    struct v4l2_buffer v4l2_buf;
+    CameraV4l2Buffer *buffer;
+    void *addr;
+    int index;
+
+    v4l2_reqbufs.count = v4l2->nbuffers;
+    v4l2_reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+    v4l2_reqbufs.memory = V4L2_MEMORY_MMAP;
+    if (ioctl(v4l2->devfd, VIDIOC_REQBUFS, &v4l2_reqbufs) < 0) {
+        return -errno;
+    }
+
+    for (index = 0; index < v4l2->nbuffers; index++) {
+        v4l2_buf.index = index;
+        v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+        v4l2_buf.memory = V4L2_MEMORY_MMAP;
+        v4l2_buf.length = 0;
+        if (ioctl(v4l2->devfd, VIDIOC_QUERYBUF, &v4l2_buf) < 0) {
+            goto error;
+        }
+
+        if (v4l2_buf.type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+            continue; /* TODO V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE support */
+        }
+
+        addr = mmap(NULL, v4l2_buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
+                    v4l2->devfd, v4l2_buf.m.offset);
+        if (addr == MAP_FAILED) {
+            goto error;
+        }
+
+        if (camera_v4l2_qbuf(camera, index) < 0) {
+            goto error;
+        }
+
+        buffer = &v4l2->buffers[index];
+        buffer->addr = addr;
+        buffer->length = v4l2_buf.length;
+    }
+
+    return 0;
+
+error:
+    camera_v4l2_free_buffers(camera);
+
+    return -errno;
+}
+
+static void camera_v4l2_open(QEMUCamera *camera, Error **errp)
+{
+    CameraV4l2 *v4l2 = CAMERA_V4L2_DEV(camera);
+    CameraV4l2Options *v4l2opts = &camera->dev->u.v4l2;
+    struct v4l2_capability v4l2_cap = {0};
+
+    if (v4l2opts->has_buffers) {
+        if (!v4l2opts->buffers || v4l2opts->buffers > CAMERA_V4L2_BUFFER_MAX) {
+            error_setg(errp, "%s: zero buffers or too large(max %d)",
+                       TYPE_CAMERA_V4L2, CAMERA_V4L2_BUFFER_MAX);
+            return;
+        }
+
+        v4l2->nbuffers = v4l2opts->buffers;
+    } else {
+        v4l2->nbuffers = CAMERA_V4L2_BUFFER_DEF;
+    }
+
+    if (!v4l2opts->has_path) {
+        error_setg(errp, "%s: missing device path", TYPE_CAMERA_V4L2);
+        return;
+    }
+
+    v4l2->devfd = open(v4l2opts->path, O_RDWR | O_NONBLOCK);
+    if (v4l2->devfd == -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 = (QEMUCamera *)opaque;
+    CameraV4l2 *v4l2 = CAMERA_V4L2_DEV(camera);
+    CameraV4l2Buffer *buffer;
+    int index;
+
+    index = camera_v4l2_dqbuf(camera);
+    if (index < 0) {
+        return;
+    }
+
+    buffer = &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 = CAMERA_V4L2_DEV(camera);
+    CameraV4l2Options *v4l2opts = &camera->dev->u.v4l2;
+    int type = 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, camera);
+}
+
+static void camera_v4l2_stream_off(QEMUCamera *camera, Error **errp)
+{
+    CameraV4l2 *v4l2 = CAMERA_V4L2_DEV(camera);
+    CameraV4l2Options *v4l2opts = &camera->dev->u.v4l2;
+    int type = 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 = CAMERADEV(obj);
+    Error *local_err = NULL;
+
+    camera_v4l2_stream_off(camera, &local_err);
+}
+
+static void camera_v4l2_class_init(ObjectClass *oc, void *data)
+{
+    QEMUCameraClass *klass = CAMERADEV_CLASS(oc);
+
+    klass->open = camera_v4l2_open;
+    klass->stream_on = camera_v4l2_stream_on;
+    klass->stream_off = camera_v4l2_stream_off;
+    klass->enum_pixel_format = camera_v4l2_enum_pixel_format;
+    klass->enum_frame_size = camera_v4l2_enum_frame_size;
+    klass->enum_frame_interval = camera_v4l2_enum_frame_interval;
+    klass->set_frame_interval = camera_v4l2_set_frame_interval;
+    klass->enum_control = camera_v4l2_enum_control;
+    klass->set_control = camera_v4l2_set_control;
+}
+
+static const TypeInfo camera_v4l2_type_info = {
+    .name = TYPE_CAMERA_V4L2,
+    .parent = TYPE_CAMERADEV,
+    .instance_size = sizeof(CameraV4l2),
+    .instance_init = camera_v4l2_init,
+    .instance_finalize = camera_v4l2_finalize,
+    .class_init = 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 @@
 # = Camera
 ##
 
+##
+# @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' ] }
 
 ##
 # @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=id[,debug=true|false][,fps=FPS][,width=WIDTH][,height=HEIGHT][,mjpeg=true|false][,yuv=true|false][,rgb565=true|false][,bgcolor=blue|gree|red|rainbow|digital-rain]\n"
+#endif
+#ifdef CONFIG_LINUX
+    "-cameradev v4l2,id=id[,path=PATH][,buffers=COUNT]\n"
 #endif
     , QEMU_ARCH_ALL
 )
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 3/5] usb: Introduce video&mescellaneous
  2021-12-27 14:27 [PATCH 0/5] Introduce camera subsystem and USB video device zhenwei pi
  2021-12-27 14:27 ` [PATCH 1/5] camera: Introduce camera subsystem and builtin driver zhenwei pi
  2021-12-27 14:27 ` [PATCH 2/5] camera: v4l2: Introduce v4l2 camera driver zhenwei pi
@ 2021-12-27 14:27 ` zhenwei pi
  2021-12-27 14:27 ` [PATCH 4/5] usb: allow max 8192 bytes for desc zhenwei pi
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 12+ messages in thread
From: zhenwei pi @ 2021-12-27 14:27 UTC (permalink / raw)
  To: peter.maydell, richard.henderson, kraxel, eblake, pbonzini
  Cc: qemu-devel, zhenwei pi

Define USB class code for video&mescellaneous.

Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
---
 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
 
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 4/5] usb: allow max 8192 bytes for desc
  2021-12-27 14:27 [PATCH 0/5] Introduce camera subsystem and USB video device zhenwei pi
                   ` (2 preceding siblings ...)
  2021-12-27 14:27 ` [PATCH 3/5] usb: Introduce video&mescellaneous zhenwei pi
@ 2021-12-27 14:27 ` zhenwei pi
  2022-01-04 15:22   ` Philippe Mathieu-Daudé
  2022-01-11 12:37   ` Daniel P. Berrangé
  2021-12-27 14:27 ` [PATCH 5/5] usb-video: Introduce USB video class zhenwei pi
  2022-01-04 13:39 ` [PATCH 0/5] Introduce camera subsystem and USB video device Daniel P. Berrangé
  5 siblings, 2 replies; 12+ messages in thread
From: zhenwei pi @ 2021-12-27 14:27 UTC (permalink / raw)
  To: peter.maydell, richard.henderson, kraxel, eblake, pbonzini
  Cc: qemu-devel, zhenwei pi

A device of USB video class usually uses larger desc structure, so
use larger buffer to avoid failure.

Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
---
 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 = (dev->flags & (1 << USB_DEV_FLAG_MSOS_DESC_IN_USE));
     const USBDesc *desc = usb_device_get_usb_desc(dev);
     const USBDescDevice *other_dev;
-    uint8_t buf[256];
+    size_t buflen = USB_DESC_MAX_LEN;
+    g_autofree uint8_t *buf = g_malloc(buflen);
     uint8_t type = value >> 8;
     uint8_t index = value & 0xff;
     int flags, ret = -1;
@@ -650,36 +651,36 @@ int usb_desc_get_descriptor(USBDevice *dev, USBPacket *p,
 
     switch(type) {
     case USB_DT_DEVICE:
-        ret = usb_desc_device(&desc->id, dev->device, msos, buf, sizeof(buf));
+        ret = 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 = 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 = usb_desc_string(dev, index, buf, sizeof(buf));
+        ret = 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 != NULL) {
-            ret = usb_desc_device_qualifier(other_dev, buf, sizeof(buf));
+            ret = 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 != NULL && index < other_dev->bNumConfigurations) {
             ret = usb_desc_config(other_dev->confs + index, flags,
-                                  buf, sizeof(buf));
+                                  buf, buflen);
             buf[0x01] = USB_DT_OTHER_SPEED_CONFIG;
         }
         trace_usb_desc_other_speed_config(dev->addr, index, len, ret);
         break;
     case USB_DT_BOS:
-        ret = usb_desc_bos(desc, buf, sizeof(buf));
+        ret = usb_desc_bos(desc, buf, buflen);
         trace_usb_desc_bos(dev->addr, len, ret);
         break;
 
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;
 };
 
+#define USB_DESC_MAX_LEN    8192
 #define USB_DESC_FLAG_SUPER (1 << 1)
 
 /* little helpers */
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 5/5] usb-video: Introduce USB video class
  2021-12-27 14:27 [PATCH 0/5] Introduce camera subsystem and USB video device zhenwei pi
                   ` (3 preceding siblings ...)
  2021-12-27 14:27 ` [PATCH 4/5] usb: allow max 8192 bytes for desc zhenwei pi
@ 2021-12-27 14:27 ` zhenwei pi
  2022-01-04 13:39 ` [PATCH 0/5] Introduce camera subsystem and USB video device Daniel P. Berrangé
  5 siblings, 0 replies; 12+ messages in thread
From: zhenwei pi @ 2021-12-27 14:27 UTC (permalink / raw)
  To: peter.maydell, richard.henderson, kraxel, eblake, pbonzini
  Cc: qemu-devel, zhenwei pi

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 <pizhenwei@bytedance.com>
---
 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
 
+``usb-video``
+   USB video device
+
 Physical port addressing
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
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 <pizhenwei@bytedance.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * 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 = {
+    [STRING_MANUFACTURER]               = "QEMU",
+    [STRING_PRODUCT]                    = "QEMU USB Video",
+    [STRING_SERIALNUMBER]               = "1",
+    [STRING_CONFIG]                     = "Video Configuration",
+    [STRING_INTERFACE_ASSOCIATION]      = "Integrated Camera",
+    [STRING_VIDEO_CONTROL]              = "Video Control",
+    [STRING_INPUT_TERMINAL]             = "Video Input Terminal",
+    [STRING_SELECTOR_UNIT]              = "Video Selector Unit",
+    [STRING_PROCESSING_UNIT]            = "Video Processing Unit",
+    [STRING_OUTPUT_TERMINAL]            = "Video Output Terminal",
+    [STRING_VIDEO_STREAMING]            = "Video Streaming",
+    [STRING_VIDEO_STREAMING_ALTERNATE1] = "Video Streaming Alternate Setting 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[] = {
+    {
+        /* VideoControl Interface Descriptor */
+        .bInterfaceNumber              = IF_CONTROL,
+        .bNumEndpoints                 = 1,
+        .bInterfaceClass               = USB_CLASS_VIDEO,
+        .bInterfaceSubClass            = SC_VIDEOCONTROL,
+        .bInterfaceProtocol            = PC_PROTOCOL_15,
+        .iInterface                    = STRING_VIDEO_CONTROL,
+        .ndesc                         = 5,
+        .descs = (USBDescOther[]) {
+            {
+                /* Class-specific VS Interface Input Header Descriptor */
+                .data = (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 = (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  wObjectiveFocalLengthMin */
+                    U16(0x0000),             /* u16  wObjectiveFocalLengthMax */
+                    U16(0x0000),             /* u16  wOcularFocalLength */
+                    0x02,                    /*  u8  bControlSize */
+                    U16(0x0000),             /* u16  bmControls */
+                }
+            }, {
+                /* Output Terminal Descriptor */
+                .data = (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 = (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 = (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 = (USBDescEndpoint[]) {
+            {
+                /* 3.8.2.1 Standard VC Interrupt Endpoint Descriptor */
+                .bEndpointAddress      = USB_DIR_IN | EP_CONTROL,
+                .bmAttributes          = USB_ENDPOINT_XFER_INT,
+                .wMaxPacketSize        = 0x40,
+                .bInterval             = 0x20,
+            },
+        },
+    }, {
+        /* VideoStreaming Interface Descriptor */
+        .bInterfaceNumber              = IF_STREAMING,
+        .bAlternateSetting             = ALTSET_OFF,
+        .bNumEndpoints                 = 0,
+        .bInterfaceClass               = USB_CLASS_VIDEO,
+        .bInterfaceSubClass            = SC_VIDEOSTREAMING,
+        .bInterfaceProtocol            = PC_PROTOCOL_15,
+        .iInterface                    = STRING_VIDEO_STREAMING,
+        /* .ndesc & .descs are built dynamicly during .realize */
+    }, {
+        /* Operational Alternate Setting 1 */
+        .bInterfaceNumber              = IF_STREAMING,
+        .bAlternateSetting             = ALTSET_STREAMING,
+        .bNumEndpoints                 = 1,
+        .bInterfaceClass               = USB_CLASS_VIDEO,
+        .bInterfaceSubClass            = SC_VIDEOSTREAMING,
+        .bInterfaceProtocol            = PC_PROTOCOL_15,
+        .iInterface                    = STRING_VIDEO_STREAMING_ALTERNATE1,
+        .eps = (USBDescEndpoint[]) {
+            {
+                /*
+                 * 3.10.1.1 Standard VS Isochronous Video Data Endpoint
+                 * Descriptor
+                 */
+                .bEndpointAddress      = USB_DIR_IN | EP_STREAMING,
+                .bmAttributes          = 0x05, /* TODO define BITs USB 9.6.6 */
+                .wMaxPacketSize        = 1024,
+                .bInterval             = 0x1,
+            },
+        },
+    }
+};
+
+static const USBDescIfaceAssoc desc_if_groups[] = {
+    {
+        .bFirstInterface = IF_CONTROL,
+        .bInterfaceCount = 2,
+        .bFunctionClass = USB_CLASS_VIDEO,
+        .bFunctionSubClass = SC_VIDEO_INTERFACE_COLLECTION,
+        .bFunctionProtocol = PC_PROTOCOL_UNDEFINED,
+        .iFunction = STRING_INTERFACE_ASSOCIATION,
+    },
+};
+
+static const USBDescDevice desc_device_full = {
+    .bcdUSB                        = 0x0100,
+    .bDeviceClass                  = USB_CLASS_MISCELLANEOUS,
+    .bDeviceSubClass               = 2,
+    .bDeviceProtocol               = 1, /* Interface Association */
+    .bMaxPacketSize0               = 8,
+    .bNumConfigurations            = 1,
+    .confs = (USBDescConfig[]) {
+        {
+            .bNumInterfaces        = 2,
+            .bConfigurationValue   = 1,
+            .iConfiguration        = STRING_CONFIG,
+            .bmAttributes          = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+            .bMaxPower             = 0x32,
+            .nif_groups            = ARRAY_SIZE(desc_if_groups),
+            .if_groups             = desc_if_groups,
+            .nif                   = ARRAY_SIZE(desc_ifaces),
+            .ifs                   = desc_ifaces,
+        },
+    },
+};
+
+static const USBDescDevice desc_device_high = {
+    .bcdUSB                        = 0x0200,
+    .bDeviceClass                  = USB_CLASS_MISCELLANEOUS,
+    .bDeviceSubClass               = 2,
+    .bDeviceProtocol               = 1, /* Interface Association */
+    .bMaxPacketSize0               = 64,
+    .bNumConfigurations            = 1,
+    .confs = (USBDescConfig[]) {
+        {
+            .bNumInterfaces        = 2,
+            .bConfigurationValue   = 1,
+            .iConfiguration        = STRING_CONFIG,
+            .bmAttributes          = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER,
+            .bMaxPower             = 0x32,
+            .nif_groups            = ARRAY_SIZE(desc_if_groups),
+            .if_groups             = desc_if_groups,
+            .nif = ARRAY_SIZE(desc_ifaces),
+            .ifs = desc_ifaces,
+        },
+    },
+};
+
+static const USBDesc desc_video = {
+    .id = {
+        .idVendor          = USBVIDEO_VENDOR_NUM,
+        .idProduct         = USBVIDEO_PRODUCT_NUM,
+        .bcdDevice         = 0,
+        .iManufacturer     = STRING_MANUFACTURER,
+        .iProduct          = STRING_PRODUCT,
+        .iSerialNumber     = STRING_SERIALNUMBER,
+    },
+    .full = &desc_device_full,
+    .high = &desc_device_high,
+    .str  = 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 = 2;
+        return PU_BRIGHTNESS_CONTROL;
+    case QEMUCameraContrast:
+        *size = 2;
+        return PU_CONTRAST_CONTROL;
+    case QEMUCameraGain:
+        *size = 2;
+        return PU_GAIN_CONTROL;
+    case QEMUCameraGamma:
+        *size = 2;
+        return PU_GAMMA_CONTROL;
+    case QEMUCameraHue:
+        *size = 2;
+        return PU_HUE_CONTROL;
+    case QEMUCameraHueAuto:
+        *size = 1;
+        return PU_HUE_AUTO_CONTROL;
+    case QEMUCameraSaturation:
+        *size = 2;
+        return PU_SATURATION_CONTROL;
+    case QEMUCameraSharpness:
+        *size = 2;
+        return PU_SHARPNESS_CONTROL;
+    case QEMUCameraWhiteBalanceTemperature:
+        *size = 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 = ATTRIBUTE_CUR;   \
+        break;                 \
+    case GET_MIN:              \
+        idx = ATTRIBUTE_MIN;   \
+        break;                 \
+    case GET_MAX:              \
+        idx = ATTRIBUTE_MAX;   \
+        break;                 \
+    case GET_RES:              \
+        idx = ATTRIBUTE_RES;   \
+        break;                 \
+    case GET_DEF:              \
+        idx = ATTRIBUTE_DEF;   \
+        break;                 \
+    default:                   \
+        idx = -1;              \
+        break;                 \
+    }
+
+#define handle_get_control(attrs, req, cs, length, data, ret)                \
+    do {                                                                     \
+        if (!attrs[cs].selector) {                                           \
+            break;                                                           \
+        }                                                                    \
+        if ((req == GET_INFO) && (length >= 1)) {                            \
+            *((uint8_t *)data) = attrs[cs].caps;                             \
+            ret = 1;                                                         \
+        } else if ((req == GET_LEN) && (length >= 2)) {                      \
+            *((uint16_t *)data) = cpu_to_le16(attrs[cs].size);               \
+            ret = 2;                                                         \
+        } else {                                                             \
+            int idx = -1;                                                    \
+            int len = MIN(length, sizeof(attrs[cs].size));                   \
+            REQ_TO_ATTR(req, idx);                                           \
+            if (idx >= 0) {                                                  \
+                memcpy(data, &attrs[cs].value[idx], len);                    \
+                ret = length;                                                \
+            }                                                                \
+        }                                                                    \
+    } while (0)
+
+
+#define handle_get_streaming(s, req, cs, length, data, ret)                  \
+    do {                                                                     \
+        if ((req == GET_INFO) && (length >= 1)) {                            \
+            *((uint8_t *)data) = s->cs##_len;                                \
+            ret = 1;                                                         \
+        } else if ((req == GET_LEN) && (length >= 2)) {                      \
+            *((uint16_t *)data) = cpu_to_le16(s->cs##_len);                  \
+            ret = 2;                                                         \
+        } else {                                                             \
+            int idx = -1;                                                    \
+            int len = MIN(length, sizeof(s->cs##_attrs[0]));                 \
+            REQ_TO_ATTR(req, idx);                                           \
+            if (idx >= 0) {                                                  \
+                memcpy(data, s->cs##_attrs + idx, len);                      \
+                ret = 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 = data[2];
+    uint32_t pixfmt = 0;
+
+    switch (bDescriptorSubtype) {
+    case VS_FORMAT_MJPEG:
+        return QEMU_CAMERA_PIX_FMT_MJPEG;
+
+    case VS_FORMAT_UNCOMPRESSED:
+        pixfmt = *(uint32_t *)(data + 5);
+        if (pixfmt == camera_fourcc_code('Y', 'U', 'Y', '2')) {
+            return QEMU_CAMERA_PIX_FMT_YUYV;
+        } else if (pixfmt == 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 = USB_VIDEO(dev);
+    USBDescOther *usb_desc;
+    uint32_t pixfmt = 0;
+    uint16_t width = 0, height = 0;
+    uint8_t bDescriptorSubtype;
+    uint8_t index;
+
+    /* 1, search bFormatIndex */
+    for (index = 0; index < s->n_vs_descs; index++) {
+        usb_desc = s->vs_descs + index;
+        if (usb_desc->data[0] < 4) {
+            return -ENODEV;
+        }
+
+        bDescriptorSubtype = usb_desc->data[2];
+        if ((bDescriptorSubtype == VS_FORMAT_MJPEG)
+           || (bDescriptorSubtype == VS_FORMAT_UNCOMPRESSED)) {
+            if (usb_desc->data[3] == vsc->bFormatIndex) {
+                pixfmt = usb_video_vsfmt_to_pixfmt(usb_desc->data);
+                break;
+            }
+        }
+    }
+
+    /* 2, search bFormatIndex */
+    for (index++ ; pixfmt && index < s->n_vs_descs; index++) {
+        usb_desc = s->vs_descs + index;
+        if (usb_desc->data[0] < 4) {
+            return -ENODEV;
+        }
+
+        bDescriptorSubtype = usb_desc->data[2];
+        if ((bDescriptorSubtype == VS_FRAME_MJPEG)
+           || (bDescriptorSubtype == VS_FRAME_UNCOMPRESSED)) {
+            if (usb_desc->data[3] == vsc->bFrameIndex) {
+                /* see Class-specific VS Frame Descriptor */
+                width = le16_to_cpu(*(uint16_t *)(usb_desc->data + 5));
+                height = le16_to_cpu(*(uint16_t *)(usb_desc->data + 7));
+                break;
+            }
+        } else {
+            break;
+        }
+    }
+
+    if (pixfmt && width && height) {
+        frmival->pixel_format = pixfmt;
+        frmival->width = width;
+        frmival->height = height;
+        frmival->type = QEMU_CAMERA_FRMIVAL_TYPE_DISCRETE;
+        frmival->d.numerator = 30; /* prime number 2 * 3 * 5 */
+        frmival->d.denominator = frmival->d.numerator * 10000000
+                                     / le32_to_cpu(vsc->dwFrameInterval);
+        return 0;
+    }
+
+    return -ENODEV;
+}
+
+static void usb_video_queue_control_status(USBDevice *dev, uint8_t bOriginator,
+                uint8_t bSelector, uint32_t *value, uint8_t size)
+{
+    USBVideoState *s = USB_VIDEO(dev);
+    USBBus *bus = usb_bus_from_device(dev);
+    USBVideoControlStats *usb_status;
+    VideoControlStatus *status;
+
+    usb_status = g_malloc0(sizeof(USBVideoControlStats));
+    usb_status->size = size;
+    status = &usb_status->status;
+    status->bStatusType = STATUS_INTERRUPT_CONTROL;
+    status->bOriginator = bOriginator;
+    status->bEvent = 0;
+    status->bSelector = bSelector;
+    status->bAttribute = 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, bOriginator,
+        bSelector, *value, size);
+}
+
+static int usb_video_get_control(USBDevice *dev, int request, int value,
+                                 int index, int length, uint8_t *data)
+{
+    USBVideoState *s = USB_VIDEO(dev);
+    USBBus *bus = usb_bus_from_device(dev);
+    uint8_t req = request & 0xff;
+    uint8_t cs = value >> 8;
+    uint8_t intfnum = index & 0xff;
+    uint8_t unit = index >> 8;
+    int ret = USB_RET_STALL;
+
+    switch (intfnum) {
+    case IF_CONTROL:
+        switch (unit) {
+        case 0:
+            if (length != 1) {
+                break;
+            }
+
+            if (cs == VC_VIDEO_POWER_MODE_CONTROL) {
+                data[0] = 127; /* 4.2.1.1 Power Mode Control */
+                ret = 1;
+            } else if (cs == VC_REQUEST_ERROR_CODE_CONTROL) {
+                data[0] = s->error; /* 4.2.1.2 Request Error Code Control */
+                s->error = 0;
+                ret = 1;
+            }
+            break;
+
+        case PROCESSING_UNIT:
+            {
+                QEMUCameraControlType t = usb_video_pu_control_type_to_qemu(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 length,
+                                    uint8_t *data)
+{
+    USBVideoState *s = USB_VIDEO(dev);
+    int idx = -1;
+    int ret = USB_RET_STALL;
+
+    REQ_TO_ATTR(req, idx);
+    if ((idx >= 0) && (length <= sizeof(s->vsc_attrs[0]))) {
+        VideoStreamingControl *dst = s->vsc_attrs + idx;
+        VideoStreamingControl *src = (VideoStreamingControl *)data;
+
+        dst->bFormatIndex = src->bFormatIndex;
+        dst->bFrameIndex = 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 = length;
+    }
+
+    return ret;
+}
+
+static int usb_video_set_control(USBDevice *dev, int request, int value,
+                                 int index, int length, uint8_t *data)
+{
+    USBVideoState *s = USB_VIDEO(dev);
+    USBBus *bus = usb_bus_from_device(dev);
+    uint8_t req = request & 0xff;
+    uint8_t cs = value >> 8;
+    uint8_t intfnum = index & 0xff;
+    uint8_t unit = index >> 8;
+    int ret = USB_RET_STALL;
+
+    switch (intfnum) {
+    case IF_CONTROL:
+        switch (unit) {
+        case PROCESSING_UNIT:
+            {
+                uint32_t value = 0;
+                QEMUCameraControl ctrl;
+                QEMUCameraControlType type;
+                Error *local_err = NULL;
+
+                type = usb_video_pu_control_type_to_qemu(cs);
+                if (type == QEMUCameraControlMax) {
+                    break;
+                }
+
+                if (length > 4) {
+                    break;
+                }
+
+                memcpy(&value, data, length);
+                value = le32_to_cpu(value);
+                ctrl.type = type;
+                ctrl.cur = 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, length);
+                ret = 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 = VC_ERROR_OUT_OF_RANGE;
+                    break;
+                }
+
+                ret = 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 = usb_bus_from_device(dev);
+    int ret = 0;
+
+    ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
+    if (ret >= 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 = usb_video_get_control(dev, request, value, index, length, data);
+        if (ret < 0) {
+            goto error;
+        }
+        break;
+    case ClassInterfaceOutRequest | SET_CUR:
+        ret = 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 = ret;
+    p->status = USB_RET_SUCCESS;
+    return;
+
+error:
+    trace_usb_video_handle_control_error(bus->busnr, dev->addr, request,
+        value, index, length);
+    p->status = USB_RET_STALL;
+}
+
+static void usb_video_set_streaming_altset(USBDevice *dev, int altset)
+{
+    USBVideoState *s = USB_VIDEO(dev);
+    Error *local_err = NULL;
+
+    if (s->streaming_altset == 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 = VC_ERROR_OUT_OF_RANGE;
+                break;
+            }
+
+            qemu_camera_set_frame_interval(s->camera, &frmival, &local_err);
+            if (local_err) {
+                s->error = 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 = VC_ERROR_INVALID_REQUEST;
+                error_reportf_err(local_err, "%s: ", TYPE_USB_VIDEO);
+                return;
+            }
+        }
+        break;
+    }
+
+    s->streaming_altset = altset;
+}
+
+static void usb_video_set_interface(USBDevice *dev, int iface,
+                                    int old, int value)
+{
+    USBBus *bus = usb_bus_from_device(dev);
+
+    trace_usb_video_set_interface(bus->busnr, dev->addr, iface, value);
+
+    if (iface == IF_STREAMING) {
+        usb_video_set_streaming_altset(dev, value);
+    }
+}
+
+static void usb_video_handle_reset(USBDevice *dev)
+{
+    USBVideoState *s = USB_VIDEO(dev);
+    USBBus *bus = usb_bus_from_device(dev);
+    Error *local_err = 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 = USB_VIDEO(dev);
+    USBBus *bus = usb_bus_from_device(dev);
+    QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov;
+    VideoImagePayloadHeader header;
+    int len;
+
+    if (s->streaming_altset != ALTSET_STREAMING) {
+        p->status = USB_RET_NAK;
+        return;
+    }
+
+    /* TODO PresentationTime & scrSourceClock support */
+    header.bmHeaderInfo = PAYLOAD_HEADER_EOH;
+    header.bmHeaderInfo |= s->fid ? PAYLOAD_HEADER_FID : 0;
+    header.bHeaderLength = 2;
+    if (p->actual_length + header.bHeaderLength > iov->size) {
+        p->status = USB_RET_STALL;
+        return;
+    }
+
+    len = qemu_camera_stream_length(s->camera);
+    if (!len) {
+        p->status = USB_RET_NAK;
+        return;
+    }
+
+    if (len < iov->size - header.bHeaderLength) {
+        /*
+         * if we can take all of the remained data, mark EOF in payload header,
+         * also change fid state.
+         */
+        header.bmHeaderInfo |= PAYLOAD_HEADER_EOF;
+        s->fid = !s->fid;
+    }
+
+    /* firstly, copy payload header */
+    usb_packet_copy(p, &header, header.bHeaderLength);
+
+    /* then, copy payload data */
+    len = qemu_camera_stream_read(s->camera, iov->iov, iov->niov,
+              p->actual_length, iov->size - p->actual_length);
+    p->actual_length += len;
+
+    p->status = 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 = USB_VIDEO(dev);
+    USBBus *bus = usb_bus_from_device(dev);
+    USBVideoControlStats *usb_status = NULL;
+    QEMUIOVector *iov = p->combined ? &p->combined->iov : &p->iov;
+    int len = 0;
+
+    if (QTAILQ_EMPTY(&s->control_status)) {
+        p->status = USB_RET_NAK;
+        goto out;
+    }
+
+    usb_status = QTAILQ_FIRST(&s->control_status);
+    QTAILQ_REMOVE(&s->control_status, usb_status, list);
+    len = MIN(5 + usb_status->size, iov->size); /* see VideoControlStatus */
+    usb_packet_copy(p, &usb_status->status, len);
+    p->status = 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 == USB_TOKEN_IN) && (p->ep->nr == EP_STREAMING)) {
+        usb_video_handle_streaming_in(dev, p);
+        return;
+    } else if ((p->pid == USB_TOKEN_IN) && (p->ep->nr == EP_CONTROL)) {
+        usb_video_handle_control_in(dev, p);
+        return;
+    }
+
+    p->status = USB_RET_STALL;
+}
+
+static void usb_video_unrealize(USBDevice *dev)
+{
+}
+
+static int usb_video_build_vc(USBDevice *dev)
+{
+    USBVideoState *s = USB_VIDEO(dev);
+    USBBus *bus = usb_bus_from_device(dev);
+    Error *local_err = NULL;
+    USBDescIface *vc_iface;
+    USBDescOther *usb_desc;
+    QEMUCameraControl controls[QEMUCameraControlMax], *control;
+    USBVideoControlInfo *controlinfo;
+    uint32_t bmControl = 0;
+    uint8_t *bmControls = NULL;
+    int i, ncontrols, pucontrol;
+
+    vc_iface = &s->desc_ifaces[0]; /* see VideoControl Interface Descriptor */
+
+    /* search Processing Unit Descriptor, and build bmControls field */
+    for (i = 0; i < vc_iface->ndesc; i++) {
+        usb_desc = &vc_iface->descs[i];
+        if (usb_desc->data[2] == VC_PROCESSING_UNIT) {
+            bmControls = (uint8_t *)usb_desc->data + 8;
+        }
+    }
+
+    ncontrols = qemu_camera_enum_control(s->camera, controls,
+                                         ARRAY_SIZE(controls), &local_err);
+
+    for (i = 0; i < ncontrols; i++) {
+        uint8_t size = 0;
+        control = &controls[i];
+        bmControl |= usb_video_pu_control_bits(control->type);
+        pucontrol = usb_video_pu_control_type(control->type, &size);
+        assert(pucontrol < PU_MAX);
+        if (pucontrol) {
+            controlinfo = &s->pu_attrs[control->type];
+            controlinfo->selector = pucontrol;
+            controlinfo->caps = CONTROL_CAP_GET | CONTROL_CAP_SET
+                                    | CONTROL_CAP_ASYNCHRONOUS;
+            controlinfo->size = size;
+            controlinfo->value[ATTRIBUTE_DEF] = cpu_to_le32(control->def);
+            controlinfo->value[ATTRIBUTE_MIN] = cpu_to_le32(control->min);
+            controlinfo->value[ATTRIBUTE_MAX] = cpu_to_le32(control->max);
+            controlinfo->value[ATTRIBUTE_CUR] = cpu_to_le32(control->def);
+            controlinfo->value[ATTRIBUTE_RES] = cpu_to_le32(control->step);
+
+            trace_usb_video_pu(bus->busnr, dev->addr, pucontrol, size,
+                control->def, control->min, control->max, control->step);
+        }
+    }
+
+    if (bmControls) {
+        bmControl = cpu_to_le32(bmControl);
+        *bmControls = bmControl & 0xff;
+        *(bmControls + 1) = (bmControl >> 8) & 0xff;
+        *(bmControls + 2) = (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[] = {
+        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 = usb_video_pixfmt_to_vsfmt(pixfmt);
+    uint8_t *data = NULL;
+
+    uint8_t data_mjpeg[] = {
+        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[] = {
+        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[] = {
+        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 == QEMU_CAMERA_PIX_FMT_MJPEG) {
+        data = data_mjpeg;
+    } else if (pixfmt == QEMU_CAMERA_PIX_FMT_YUYV) {
+        data = data_uncompressed_yuy2;
+    } else if (pixfmt == QEMU_CAMERA_PIX_FMT_RGB565) {
+        data = 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 bDescriptorSubtype,
+                                    uint8_t bFrameIndex,
+                                    QEMUCameraFrameInterval *frmivals,
+                                    uint8_t nfrmivals)
+{
+    uint8_t bLength = VS_FRAME_MIN_LEN + nfrmivals * 4;
+    QEMUCameraFrameInterval *deffrmival = &frmivals[0];
+    struct FrameIntervalDiscrete *d = &deffrmival->d;
+    uint16_t wWidth = deffrmival->width;
+    uint16_t wHeight = deffrmival->height;
+    uint32_t dwMaxVideoFrameBufSize = wWidth * wHeight * 2;
+    uint32_t dwDefaultFrameInterval = 10000000 * d->numerator / d->denominator;
+    uint32_t *ival;
+    int index;
+
+    /* Class-specific VS Frame Descriptor */
+    uint8_t data[VS_FRAME_MAX_LEN] = {
+        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 = 0; index < nfrmivals; index++) {
+        ival = (uint32_t *)(data + VS_FRAME_MIN_LEN + 4 * index);
+        d = &frmivals[index].d;
+        *ival = 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 = 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 = 0;
+    uint32_t vs_length = VS_HEADER_LEN;
+
+    s->vs_descs = g_new0(USBDescOther, 1 + USB_VIDEO_PIX_FORMAT_MAX
+                        + USB_VIDEO_PIX_FORMAT_MAX * USB_VIDEO_FRAME_SIZE_MAX);
+    s->vs_data = 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_MAX);
+    usb_desc = s->vs_descs;
+    usb_desc->data = s->vs_data;
+
+    /* build desc video from template */
+    memcpy(s->desc_ifaces, desc_ifaces, sizeof(s->desc_ifaces));
+
+    s->desc_device_full = desc_device_full;
+    *(USBDescIface **)&(s->desc_device_full.confs[0].ifs) = s->desc_ifaces;
+
+    s->desc_device_high = desc_device_high;
+    *(USBDescIface **)&(s->desc_device_high.confs[0].ifs) = s->desc_ifaces;
+
+    s->desc_video = desc_video;
+    s->desc_video.full = &s->desc_device_full;
+    s->desc_video.high = &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 dynamically)
+     *    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 = 1; /* at least 1 header */
+
+    npixfmts = 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 = 0; pixfmtidx < npixfmts; pixfmtidx++) {
+        QEMUCameraFrameSize frmszs[USB_VIDEO_FRAME_SIZE_MAX], *frmsz;
+        uint8_t vsfrm = usb_video_pixfmt_to_vsfrm(pixfmts[pixfmtidx]);
+        int nfrmszs;
+
+        usb_desc = s->vs_descs + s->n_vs_descs++;
+        usb_desc->data = s->vs_data + vs_length;
+
+        nfrmszs = qemu_camera_enum_frame_size(s->camera, pixfmts[pixfmtidx],
+                      frmszs, ARRAY_SIZE(frmszs), errp);
+
+        vs_length += usb_video_vs_build_format(s->vs_data + vs_length,
+                         pixfmts[pixfmtidx], (uint8_t)pixfmtidx + 1,
+                         (uint8_t)nfrmszs);
+
+        for (frmszidx = 0; frmszidx < nfrmszs; frmszidx++) {
+            QEMUCameraFrameInterval frmivals[USB_VIDEO_FRAME_IVAL_MAX];
+            QEMUCameraFormat fmt;
+            int nfrmivals;
+
+            frmsz = &frmszs[frmszidx];
+            if (frmsz->type != QEMU_CAMERA_FRMSIZE_TYPE_DISCRETE) {
+                continue; /* TODO stepwise support */
+            }
+
+            fmt.pixel_format = frmsz->pixel_format;
+            fmt.width = frmsz->d.width;
+            fmt.height = frmsz->d.height;
+            nfrmivals = qemu_camera_enum_frame_interval(s->camera, &fmt,
+                            frmivals, ARRAY_SIZE(frmivals), errp);
+            if (!nfrmivals) {
+                continue;
+            }
+
+            if (dwMaxVideoFrameSize < fmt.height * fmt.width * 2) {
+                dwMaxVideoFrameSize = fmt.height * fmt.width * 2;
+            }
+
+            usb_desc = s->vs_descs + s->n_vs_descs++;
+            usb_desc->data = s->vs_data + vs_length;
+            vs_length += usb_video_vs_build_frame((uint8_t *)usb_desc->data,
+                                                  vsfrm, (uint8_t)frmszidx + 1,
+                                                  frmivals, (uint8_t)nfrmivals);
+        }
+    }
+
+    /* build VideoStreaming Interface Descriptor */
+    vs_iface = &s->desc_ifaces[1]; /* see VideoStreaming Interface Descriptor */
+    usb_video_vs_build_header(s->vs_data, vs_length);
+    vs_iface->ndesc = s->n_vs_descs;
+    vs_iface->descs = s->vs_descs;
+
+    /* keep align with VideoStreaming Interface Descriptor */
+    s->vsc_info = 0;
+    s->vsc_len = sizeof(*vsc);
+    vsc = s->vsc_attrs + ATTRIBUTE_DEF;
+    vsc->bFormatIndex = 1;
+    vsc->bFrameIndex = 1;
+    vsc->dwFrameInterval = cpu_to_le32(1000000); /* default 10 FPS */
+    vsc->wDelay = cpu_to_le16(32);
+    vsc->dwMaxVideoFrameSize = cpu_to_le32(dwMaxVideoFrameSize);
+    vsc->dwMaxPayloadTransferSize = cpu_to_le32(1024);
+    vsc->dwClockFrequency = 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 = USB_VIDEO(dev);
+
+    if (!s->terminal || strcmp(s->terminal, "camera")) {
+        error_setg(errp, "%s: support terminal camera only", TYPE_USB_VIDEO);
+        return;
+    }
+
+    s->camera = 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 = &s->desc_video;
+
+    usb_desc_create_serial(dev);
+    usb_desc_init(dev);
+    s->dev.opaque = s;
+}
+
+static Property usb_video_properties[] = {
+    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 = DEVICE_CLASS(klass);
+    USBDeviceClass *k = USB_DEVICE_CLASS(klass);
+
+    device_class_set_props(dc, usb_video_properties);
+    set_bit(DEVICE_CATEGORY_USB, dc->categories);
+    k->product_desc   = "QEMU USB Video Interface";
+    k->realize        = usb_video_realize;
+    k->handle_reset   = usb_video_handle_reset;
+    k->handle_control = usb_video_handle_control;
+    k->handle_data    = usb_video_handle_data;
+    k->unrealize      = usb_video_unrealize;
+    k->set_interface  = usb_video_set_interface;
+}
+
+static const TypeInfo usb_video_info = {
+    .name          = TYPE_USB_VIDEO,
+    .parent        = TYPE_USB_DEVICE,
+    .instance_size = sizeof(USBVideoState),
+    .class_init    = 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: files('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'))
 
 # 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) "dev %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 streaming in %d"
+usb_video_handle_control_in(int bus, int addr, int len) "dev %d:%u streaming 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, int 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, uint8_t bSelector, uint32_t value, uint8_t size) "dev %d:%u queue control status, 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 = 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
-- 
2.25.1



^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH 0/5] Introduce camera subsystem and USB video device
  2021-12-27 14:27 [PATCH 0/5] Introduce camera subsystem and USB video device zhenwei pi
                   ` (4 preceding siblings ...)
  2021-12-27 14:27 ` [PATCH 5/5] usb-video: Introduce USB video class zhenwei pi
@ 2022-01-04 13:39 ` Daniel P. Berrangé
  2022-01-05  7:03   ` zhenwei pi
  5 siblings, 1 reply; 12+ messages in thread
From: Daniel P. Berrangé @ 2022-01-04 13:39 UTC (permalink / raw)
  To: zhenwei pi
  Cc: peter.maydell, richard.henderson, qemu-devel, kraxel, pbonzini, eblake

On Mon, Dec 27, 2021 at 10:27:29PM +0800, zhenwei pi wrote:
> 1, The full picture of this patch set:
>    +---------+       +------------+     +---------------+
>    |UVC(done)|       |virtio(TODO)|     |other HW device|
>    +---------+       +------------+     +---------------+
>          |                 |                     |
>          |            +------------+             |
> 	 +------------+camera(done)+-------------+
>                       +----+-------+
>                            |
>          +-----------------+---------------------+
>          |                 |                     |
>   +------+------+     +----+-----+        +------+------+
>   |builtin(done)|     |v4l2(done)|        |other drivers|
>   +-------------+     +----------+        +-------------+
> 
> With this patch set, We can run a desktop VM (Ex Ubuntu-2004), several camera
> APPs(cheese, kamoso, guvcview and qcam) work fine.
> 
> Some works still in working:
>   1, hot-plug
>   2, compat with live migration
>   3, several actions defined in UVC SPEC
> 
> Zhenwei Pi (5):
>   camera: Introduce camera subsystem and builtin driver
>   camera: v4l2: Introduce v4l2 camera driver
>   usb: Introduce video&mescellaneous
>   usb: allow max 8192 bytes for desc
>   usb-video: Introduce USB video class
> 
>  camera/builtin.c        |  717 ++++++++++++++++++++
>  camera/camera-int.h     |   19 +
>  camera/camera.c         |  522 +++++++++++++++
>  camera/meson.build      |   20 +
>  camera/trace-events     |   28 +
>  camera/trace.h          |    1 +
>  camera/v4l2.c           |  637 ++++++++++++++++++
>  hw/usb/Kconfig          |    5 +
>  hw/usb/desc.c           |   15 +-
>  hw/usb/desc.h           |    1 +
>  hw/usb/dev-video.c      | 1395 +++++++++++++++++++++++++++++++++++++++
>  hw/usb/meson.build      |    1 +
>  hw/usb/trace-events     |   11 +
>  include/camera/camera.h |  238 +++++++
>  include/hw/usb.h        |    2 +
>  include/hw/usb/video.h  |  303 +++++++++
>  meson.build             |   20 +-
>  meson_options.txt       |    3 +
>  qapi/camera.json        |  101 +++
>  qapi/meson.build        |    1 +
>  qapi/qapi-schema.json   |    1 +
>  qemu-options.hx         |   13 +
>  softmmu/vl.c            |    4 +

There's no MAINTAINERS file update here.

As a general rule, if you are introducing an entire new subsystem
into the QEMU codebase, it is expected someone will be nominated
as the maintainer for the new subsystem. Usually the person adding
it will themselves volunteer to be the maintainer.

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 4/5] usb: allow max 8192 bytes for desc
  2021-12-27 14:27 ` [PATCH 4/5] usb: allow max 8192 bytes for desc zhenwei pi
@ 2022-01-04 15:22   ` Philippe Mathieu-Daudé
  2022-01-05  7:25     ` zhenwei pi
  2022-01-11 12:37   ` Daniel P. Berrangé
  1 sibling, 1 reply; 12+ messages in thread
From: Philippe Mathieu-Daudé @ 2022-01-04 15:22 UTC (permalink / raw)
  To: zhenwei pi, peter.maydell, richard.henderson, kraxel, eblake, pbonzini
  Cc: qemu-devel

On 27/12/21 15:27, zhenwei pi wrote:
> A device of USB video class usually uses larger desc structure, so
> use larger buffer to avoid failure.
> 
> Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
> ---
>   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 = (dev->flags & (1 << USB_DEV_FLAG_MSOS_DESC_IN_USE));
>       const USBDesc *desc = usb_device_get_usb_desc(dev);
>       const USBDescDevice *other_dev;
> -    uint8_t buf[256];
> +    size_t buflen = USB_DESC_MAX_LEN;
> +    g_autofree uint8_t *buf = g_malloc(buflen);

Do we want to have a per-device desc_size (in USBDevice, default to
256, video devices set it to 8K)?

How "hot" is this path? Could we keep 8K on the stack?

> 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;
>   };
>   
> +#define USB_DESC_MAX_LEN    8192
>   #define USB_DESC_FLAG_SUPER (1 << 1)
>   
>   /* little helpers */



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Re: [PATCH 0/5] Introduce camera subsystem and USB video device
  2022-01-04 13:39 ` [PATCH 0/5] Introduce camera subsystem and USB video device Daniel P. Berrangé
@ 2022-01-05  7:03   ` zhenwei pi
  0 siblings, 0 replies; 12+ messages in thread
From: zhenwei pi @ 2022-01-05  7:03 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: peter.maydell, richard.henderson, qemu-devel, kraxel, pbonzini, eblake


On 1/4/22 9:39 PM, Daniel P. Berrangé wrote:
> On Mon, Dec 27, 2021 at 10:27:29PM +0800, zhenwei pi wrote:
>> 1, The full picture of this patch set:
>>     +---------+       +------------+     +---------------+
>>     |UVC(done)|       |virtio(TODO)|     |other HW device|
>>     +---------+       +------------+     +---------------+
>>           |                 |                     |
>>           |            +------------+             |
>> 	 +------------+camera(done)+-------------+
>>                        +----+-------+
>>                             |
>>           +-----------------+---------------------+
>>           |                 |                     |
>>    +------+------+     +----+-----+        +------+------+
>>    |builtin(done)|     |v4l2(done)|        |other drivers|
>>    +-------------+     +----------+        +-------------+
>>
>> With this patch set, We can run a desktop VM (Ex Ubuntu-2004), several camera
>> APPs(cheese, kamoso, guvcview and qcam) work fine.
>>
>> Some works still in working:
>>    1, hot-plug
>>    2, compat with live migration
>>    3, several actions defined in UVC SPEC
>>
>> Zhenwei Pi (5):
>>    camera: Introduce camera subsystem and builtin driver
>>    camera: v4l2: Introduce v4l2 camera driver
>>    usb: Introduce video&mescellaneous
>>    usb: allow max 8192 bytes for desc
>>    usb-video: Introduce USB video class
>>
>>   camera/builtin.c        |  717 ++++++++++++++++++++
>>   camera/camera-int.h     |   19 +
>>   camera/camera.c         |  522 +++++++++++++++
>>   camera/meson.build      |   20 +
>>   camera/trace-events     |   28 +
>>   camera/trace.h          |    1 +
>>   camera/v4l2.c           |  637 ++++++++++++++++++
>>   hw/usb/Kconfig          |    5 +
>>   hw/usb/desc.c           |   15 +-
>>   hw/usb/desc.h           |    1 +
>>   hw/usb/dev-video.c      | 1395 +++++++++++++++++++++++++++++++++++++++
>>   hw/usb/meson.build      |    1 +
>>   hw/usb/trace-events     |   11 +
>>   include/camera/camera.h |  238 +++++++
>>   include/hw/usb.h        |    2 +
>>   include/hw/usb/video.h  |  303 +++++++++
>>   meson.build             |   20 +-
>>   meson_options.txt       |    3 +
>>   qapi/camera.json        |  101 +++
>>   qapi/meson.build        |    1 +
>>   qapi/qapi-schema.json   |    1 +
>>   qemu-options.hx         |   13 +
>>   softmmu/vl.c            |    4 +
> 
> There's no MAINTAINERS file update here.
> 
> As a general rule, if you are introducing an entire new subsystem
> into the QEMU codebase, it is expected someone will be nominated
> as the maintainer for the new subsystem. Usually the person adding
> it will themselves volunteer to be the maintainer.
> 
> Regards,
> Daniel
> 
OK, thanks. I have a plan to add two parts in next version:
1, camera subsystem, include folder qemu/camera and qemu/include/camera/
2, UVC, include qemu/hw/usb/dev-video.c and qemu/include/hw/usb/video.h


-- 
zhenwei pi


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: Re: [PATCH 4/5] usb: allow max 8192 bytes for desc
  2022-01-04 15:22   ` Philippe Mathieu-Daudé
@ 2022-01-05  7:25     ` zhenwei pi
  2022-01-11 13:00       ` Philippe Mathieu-Daudé
  0 siblings, 1 reply; 12+ messages in thread
From: zhenwei pi @ 2022-01-05  7:25 UTC (permalink / raw)
  To: Philippe Mathieu-Daudé,
	peter.maydell, richard.henderson, kraxel, eblake, pbonzini
  Cc: qemu-devel


On 1/4/22 11:22 PM, Philippe Mathieu-Daudé wrote:
> On 27/12/21 15:27, zhenwei pi wrote:
>> A device of USB video class usually uses larger desc structure, so
>> use larger buffer to avoid failure.
>>
>> Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
>> ---
>>   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 = (dev->flags & (1 << USB_DEV_FLAG_MSOS_DESC_IN_USE));
>>       const USBDesc *desc = usb_device_get_usb_desc(dev);
>>       const USBDescDevice *other_dev;
>> -    uint8_t buf[256];
>> +    size_t buflen = USB_DESC_MAX_LEN;
>> +    g_autofree uint8_t *buf = g_malloc(buflen);
> 
> Do we want to have a per-device desc_size (in USBDevice, default to
> 256, video devices set it to 8K)?
> 
> How "hot" is this path? Could we keep 8K on the stack?
> 
It's an unlikely code path:
1, During guest startup, guest tries to probe device.
2, run 'lsusb' command in guest

Keeping 8K on the stack also seems OK.

>> 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;
>>   };
>> +#define USB_DESC_MAX_LEN    8192
>>   #define USB_DESC_FLAG_SUPER (1 << 1)
>>   /* little helpers */
> 

-- 
zhenwei pi


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 4/5] usb: allow max 8192 bytes for desc
  2021-12-27 14:27 ` [PATCH 4/5] usb: allow max 8192 bytes for desc zhenwei pi
  2022-01-04 15:22   ` Philippe Mathieu-Daudé
@ 2022-01-11 12:37   ` Daniel P. Berrangé
  1 sibling, 0 replies; 12+ messages in thread
From: Daniel P. Berrangé @ 2022-01-11 12:37 UTC (permalink / raw)
  To: zhenwei pi
  Cc: peter.maydell, richard.henderson, qemu-devel, kraxel, pbonzini, eblake

On Mon, Dec 27, 2021 at 10:27:33PM +0800, zhenwei pi wrote:
> A device of USB video class usually uses larger desc structure, so
> use larger buffer to avoid failure.
> 
> Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
> ---
>  hw/usb/desc.c | 15 ++++++++-------
>  hw/usb/desc.h |  1 +
>  2 files changed, 9 insertions(+), 7 deletions(-)

Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 4/5] usb: allow max 8192 bytes for desc
  2022-01-05  7:25     ` zhenwei pi
@ 2022-01-11 13:00       ` Philippe Mathieu-Daudé
  0 siblings, 0 replies; 12+ messages in thread
From: Philippe Mathieu-Daudé @ 2022-01-11 13:00 UTC (permalink / raw)
  To: zhenwei pi, peter.maydell, richard.henderson, kraxel, eblake, pbonzini
  Cc: qemu-devel

On 1/5/22 08:25, zhenwei pi wrote:
> 
> On 1/4/22 11:22 PM, Philippe Mathieu-Daudé wrote:
>> On 27/12/21 15:27, zhenwei pi wrote:
>>> A device of USB video class usually uses larger desc structure, so
>>> use larger buffer to avoid failure.
>>>
>>> Signed-off-by: zhenwei pi <pizhenwei@bytedance.com>
>>> ---
>>>   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 = (dev->flags & (1 << USB_DEV_FLAG_MSOS_DESC_IN_USE));
>>>       const USBDesc *desc = usb_device_get_usb_desc(dev);
>>>       const USBDescDevice *other_dev;
>>> -    uint8_t buf[256];
>>> +    size_t buflen = USB_DESC_MAX_LEN;
>>> +    g_autofree uint8_t *buf = g_malloc(buflen);
>>
>> Do we want to have a per-device desc_size (in USBDevice, default to
>> 256, video devices set it to 8K)?
>>
>> How "hot" is this path? Could we keep 8K on the stack?
>>
> It's an unlikely code path:
> 1, During guest startup, guest tries to probe device.
> 2, run 'lsusb' command in guest

If you have to respin, do you mind adding this 3 lines
in the description? Anyhow:

Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>


^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2022-01-11 13:31 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-12-27 14:27 [PATCH 0/5] Introduce camera subsystem and USB video device zhenwei pi
2021-12-27 14:27 ` [PATCH 1/5] camera: Introduce camera subsystem and builtin driver zhenwei pi
2021-12-27 14:27 ` [PATCH 2/5] camera: v4l2: Introduce v4l2 camera driver zhenwei pi
2021-12-27 14:27 ` [PATCH 3/5] usb: Introduce video&mescellaneous zhenwei pi
2021-12-27 14:27 ` [PATCH 4/5] usb: allow max 8192 bytes for desc zhenwei pi
2022-01-04 15:22   ` Philippe Mathieu-Daudé
2022-01-05  7:25     ` zhenwei pi
2022-01-11 13:00       ` Philippe Mathieu-Daudé
2022-01-11 12:37   ` Daniel P. Berrangé
2021-12-27 14:27 ` [PATCH 5/5] usb-video: Introduce USB video class zhenwei pi
2022-01-04 13:39 ` [PATCH 0/5] Introduce camera subsystem and USB video device Daniel P. Berrangé
2022-01-05  7:03   ` zhenwei pi

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.