From 7888fc0b3d07151ddefe8e388c557554c3679745 Mon Sep 17 00:00:00 2001 From: Dmitry Osipenko <dmitry.osipenko@collabora.com> Date: Sun, 28 Aug 2022 22:51:30 +0300 Subject: [PATCH] virtio: Add virtio-camera Signed-off-by: Dmitry Osipenko <dmitry.osipenko@collabora.com> --- hw/virtio/Kconfig | 5 + hw/virtio/meson.build | 5 + hw/virtio/virtio-camera.c | 668 ++++++++++++++++++ hw/virtio/virtio.c | 3 +- include/hw/virtio/virtio-camera.h | 56 ++ .../standard-headers/linux/virtio_camera.h | 84 +++ include/standard-headers/linux/virtio_ids.h | 1 + meson.build | 4 + 8 files changed, 825 insertions(+), 1 deletion(-) create mode 100644 hw/virtio/virtio-camera.c create mode 100644 include/hw/virtio/virtio-camera.h create mode 100644 include/standard-headers/linux/virtio_camera.h diff --git a/hw/virtio/Kconfig b/hw/virtio/Kconfig index e9ecae1f50a..a966325a878 100644 --- a/hw/virtio/Kconfig +++ b/hw/virtio/Kconfig @@ -30,6 +30,11 @@ config VIRTIO_BALLOON default y depends on VIRTIO +config VIRTIO_CAMERA + bool + default y + depends on VIRTIO + config VIRTIO_CRYPTO bool default y diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build index 7e8877fd64e..07b3e3c20c8 100644 --- a/hw/virtio/meson.build +++ b/hw/virtio/meson.build @@ -30,6 +30,11 @@ virtio_ss.add(when: 'CONFIG_VIRTIO_MEM', if_true: files('virtio-mem.c')) virtio_ss.add(when: 'CONFIG_VHOST_USER_I2C', if_true: files('vhost-user-i2c.c')) virtio_ss.add(when: 'CONFIG_VHOST_USER_RNG', if_true: files('vhost-user-rng.c')) +if libv4l2.found() + virtio_ss.add(declare_dependency(dependencies: [libv4l2])) + virtio_ss.add(when: 'CONFIG_VIRTIO_CAMERA', if_true: files('virtio-camera.c')) +endif + virtio_pci_ss = ss.source_set() virtio_pci_ss.add(when: 'CONFIG_VHOST_VSOCK', if_true: files('vhost-vsock-pci.c')) virtio_pci_ss.add(when: 'CONFIG_VHOST_USER_VSOCK', if_true: files('vhost-user-vsock-pci.c')) diff --git a/hw/virtio/virtio-camera.c b/hw/virtio/virtio-camera.c new file mode 100644 index 00000000000..65fad97d0a7 --- /dev/null +++ b/hw/virtio/virtio-camera.c @@ -0,0 +1,668 @@ +/* + * Virtio camera Support + * + * Copyright © 2022 Collabora, Ltd. + * + * Authors: + * Dmitry Osipenko <dmitry.osipenko@collabora.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#include "qemu/osdep.h" +#include "qemu/iov.h" +#include "qemu/main-loop.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "sysemu/dma.h" + +#include "hw/virtio/virtio.h" +#include "hw/virtio/virtio-access.h" +#include "hw/virtio/virtio-camera.h" +#include "standard-headers/linux/virtio_ids.h" + +#include "hw/qdev-properties.h" + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/mman.h> + +#include <linux/videodev2.h> +#include <libv4l2.h> + +struct virtio_camera_test_command { + uint64_t command; +}; + +static int virtio_camera_v4l_ioctl(int fh, int request, void *arg) +{ + int ret; + + do { + ret = ioctl(fh, request, arg); + } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); + + return ret; +} + +static void virtio_camera_destroy_v4l_buffers(VirtIOCamera *vcam) +{ + struct v4l2_requestbuffers req = {}; + unsigned int i; + + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + req.count = 0; + + virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_REQBUFS, &req); + + for (i = 0; i < VIRTIO_CAMERA_NUM_V4L2_BUFFERS; i++) { + if (!vcam->v4l2.buffer_data[i]) + continue; + + v4l2_munmap(vcam->v4l2.buffer_data[i], vcam->v4l2.buffer_size[i]); + vcam->v4l2.buffer_data[i] = NULL; + vcam->v4l2.buffer_size[i] = 0; + } +} + +static int virtio_camera_alloc_v4l_buffers(VirtIOCamera *vcam) +{ + struct v4l2_requestbuffers req = {}; + unsigned int i; + void *data; + int err; + + req.count = VIRTIO_CAMERA_NUM_V4L2_BUFFERS; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + + err = virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_REQBUFS, &req); + if (err) + return err; + + for (i = 0; i < VIRTIO_CAMERA_NUM_V4L2_BUFFERS; i++) { + struct v4l2_buffer buffer = {}; + + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + buffer.index = i; + + err = virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_QUERYBUF, &buffer); + if (err) { + virtio_camera_destroy_v4l_buffers(vcam); + return err; + } + + data = v4l2_mmap(NULL, buffer.length, PROT_READ, MAP_SHARED, + vcam->v4l2.fd, buffer.m.offset); + if (data == MAP_FAILED) { + virtio_camera_destroy_v4l_buffers(vcam); + return -ENOMEM; + } + + vcam->v4l2.buffer_data[i] = data; + vcam->v4l2.buffer_size[i] = buffer.length; + } + + return 0; +} + +static int virtio_camera_enqueue_v4l_buffers(VirtIOCamera *vcam) +{ + unsigned int i; + int err; + + for (i = 0; i < VIRTIO_CAMERA_NUM_V4L2_BUFFERS; i++) { + struct v4l2_buffer buffer = {}; + + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.memory = V4L2_MEMORY_MMAP; + buffer.index = i; + + err = virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_QBUF, &buffer); + if (err) + return err; + } + + return 0; +} + +static struct virtio_camera_mem_buffer * +virtio_camera_mem_buf_by_uuid(VirtIOCamera *vcam, void *uuid) +{ + struct virtio_camera_mem_buffer *mem_buf; + + QTAILQ_FOREACH(mem_buf, &vcam->buflist, node) { + if (!memcmp(mem_buf->uuid.data, uuid, sizeof(mem_buf->uuid.data))) + return mem_buf; + } + + return NULL; +} + +static void virtio_camera_push_resp(VirtIOCamera *vcam, + VirtQueueElement **elemp, + struct virtio_camera_op_ctrl_req *resp) +{ + VirtQueueElement *elem = *elemp; + + if (iov_from_buf(elem->in_sg, elem->in_num, 0, + resp, sizeof(*resp)) != sizeof(*resp)) { + virtio_error(VIRTIO_DEVICE(vcam), "virtio-camera invalid response size"); + virtqueue_detach_element(vcam->ctrl_vq, elem, 0); + } else { + virtqueue_push(vcam->ctrl_vq, elem, sizeof(*resp)); + } + + virtio_notify(VIRTIO_DEVICE(vcam), vcam->ctrl_vq); + + g_free(elem); + *elemp = NULL; +} + +static void virtio_camera_v4l_event(void *opaque) +{ + struct virtio_camera_mem_buffer *mem_buf; + VirtIOCamera *vcam = opaque; + int err; + + if (!vcam->streaming) + return; + + while (true) { + struct virtio_camera_op_ctrl_req resp = {}; + struct v4l2_buffer buffer = {}; + + buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buffer.index = vcam->v4l2.queue_buffer_index; + buffer.memory = V4L2_MEMORY_MMAP; + + err = ioctl(vcam->v4l2.fd, VIDIOC_DQBUF, &buffer); + if (err) + break; + + mem_buf = QTAILQ_FIRST(&vcam->capturelist); + if (mem_buf) { + iov_from_buf(mem_buf->iov, mem_buf->num_entries, 0, + vcam->v4l2.buffer_data[buffer.index], + buffer.bytesused); + + QTAILQ_REMOVE(&vcam->capturelist, mem_buf, capture_node); + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_OK_NODATA); + virtio_camera_push_resp(vcam, &mem_buf->capture_elem, &resp); + } + + virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_QBUF, &buffer); + + vcam->v4l2.queue_buffer_index = (vcam->v4l2.queue_buffer_index + 1) % + VIRTIO_CAMERA_NUM_V4L2_BUFFERS; + } +} + +static void virtio_camera_ctrl_handle(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIOCamera *vcam = VIRTIO_CAMERA(vdev); + VirtQueueElement *elem = NULL; + + while (virtio_queue_ready(vq)) { + enum v4l2_buf_type buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + struct virtio_camera_mem_buffer *mem_buf, *mem_buf_tmp; + struct virtio_camera_mem_entry *ents; + struct virtio_camera_op_ctrl_req ctrl = {}; + struct virtio_camera_op_ctrl_req resp = {}; + struct v4l2_frmsizeenum frmsize = {}; + struct v4l2_fmtdesc format_desc = {}; + struct v4l2_format format = {}; + uint64_t num_entries, size; + int v4l_err = 0; + unsigned int i; + uint32_t cmd; + hwaddr hwlen; + + elem = virtqueue_pop(vq, sizeof(VirtQueueElement)); + if (!elem) + break; + + if (elem->out_num < 1 || elem->in_num < 1) { + virtio_error(vdev, "%s: ctrl missing headers", __func__); + virtqueue_detach_element(vq, elem, 0); + break; + } + + if (iov_to_buf(elem->out_sg, elem->out_num, 0, + &ctrl, sizeof(ctrl)) != sizeof(ctrl)) { + virtio_error(vdev, "%s: virtio-camera invalid control size", __func__); + virtqueue_detach_element(vq, elem, 0); + break; + } + + format.type = buf_type; + format.fmt.pix.field = V4L2_FIELD_NONE; + + cmd = le32_to_cpu(ctrl.header.cmd); + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_OK_NODATA); + + switch (cmd) { + case VIRTIO_CAMERA_CMD_GET_FORMAT: + v4l_err = virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_G_FMT, &format); + if (v4l_err) + break; + + resp.u.format.size.width = cpu_to_le32(format.fmt.pix.width); + resp.u.format.size.height = cpu_to_le32(format.fmt.pix.height); + resp.u.format.size.stride = cpu_to_le32(format.fmt.pix.bytesperline); + resp.u.format.pixelformat = cpu_to_le32(format.fmt.pix.pixelformat); + resp.u.format.size.sizeimage = cpu_to_le32(format.fmt.pix.sizeimage); + break; + + case VIRTIO_CAMERA_CMD_TRY_FORMAT: + format.fmt.pix.width = le32_to_cpu(ctrl.u.format.size.width); + format.fmt.pix.height = le32_to_cpu(ctrl.u.format.size.height); + format.fmt.pix.bytesperline = le32_to_cpu(ctrl.u.format.size.stride); + format.fmt.pix.pixelformat = le32_to_cpu(ctrl.u.format.pixelformat); + + v4l_err = virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_TRY_FMT, &format); + if (v4l_err) + break; + + resp.u.format.size.width = cpu_to_le32(format.fmt.pix.width); + resp.u.format.size.height = cpu_to_le32(format.fmt.pix.height); + resp.u.format.size.stride = cpu_to_le32(format.fmt.pix.bytesperline); + resp.u.format.pixelformat = cpu_to_le32(format.fmt.pix.pixelformat); + resp.u.format.size.sizeimage = cpu_to_le32(format.fmt.pix.sizeimage); + break; + + case VIRTIO_CAMERA_CMD_SET_FORMAT: + format.fmt.pix.width = le32_to_cpu(ctrl.u.format.size.width); + format.fmt.pix.height = le32_to_cpu(ctrl.u.format.size.height); + format.fmt.pix.bytesperline = le32_to_cpu(ctrl.u.format.size.stride); + format.fmt.pix.pixelformat = le32_to_cpu(ctrl.u.format.pixelformat); + + v4l_err = virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_S_FMT, + &format); + if (v4l_err) + break; + + resp.u.format.size.width = cpu_to_le32(format.fmt.pix.width); + resp.u.format.size.height = cpu_to_le32(format.fmt.pix.height); + resp.u.format.size.stride = cpu_to_le32(format.fmt.pix.bytesperline); + resp.u.format.pixelformat = cpu_to_le32(format.fmt.pix.pixelformat); + resp.u.format.size.sizeimage = cpu_to_le32(format.fmt.pix.sizeimage); + break; + + case VIRTIO_CAMERA_CMD_ENUM_FORMAT: + format_desc.index = le32_to_cpu(ctrl.header.index); + format_desc.type = buf_type; + + v4l_err = virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_ENUM_FMT, + &format_desc); + if (v4l_err) + break; + + resp.u.format.pixelformat = cpu_to_le32(format_desc.pixelformat); + break; + + case VIRTIO_CAMERA_CMD_ENUM_SIZE: + frmsize.index = le32_to_cpu(ctrl.header.index); + frmsize.pixel_format = le32_to_cpu(ctrl.u.format.pixelformat); + + v4l_err = virtio_camera_v4l_ioctl(vcam->v4l2.fd, + VIDIOC_ENUM_FRAMESIZES, + &frmsize); + if (v4l_err) + break; + + switch (frmsize.type) { + case V4L2_FRMSIZE_TYPE_DISCRETE: + resp.u.format.size.min_width = cpu_to_le32(frmsize.discrete.width); + resp.u.format.size.min_height = cpu_to_le32(frmsize.discrete.height); + resp.u.format.size.max_width = resp.u.format.size.min_width; + resp.u.format.size.max_height = resp.u.format.size.min_height; + break; + + case V4L2_FRMSIZE_TYPE_CONTINUOUS: + resp.u.format.size.min_width = cpu_to_le32(frmsize.stepwise.min_width); + resp.u.format.size.min_height = cpu_to_le32(frmsize.stepwise.min_height); + resp.u.format.size.max_width = cpu_to_le32(frmsize.stepwise.max_width); + resp.u.format.size.max_height = cpu_to_le32(frmsize.stepwise.max_height); + resp.u.format.size.step_width = cpu_to_le32(1); + resp.u.format.size.step_height = cpu_to_le32(1); + break; + + case V4L2_FRMSIZE_TYPE_STEPWISE: + resp.u.format.size.min_width = cpu_to_le32(frmsize.stepwise.min_width); + resp.u.format.size.min_height = cpu_to_le32(frmsize.stepwise.min_height); + resp.u.format.size.max_width = cpu_to_le32(frmsize.stepwise.max_width); + resp.u.format.size.max_height = cpu_to_le32(frmsize.stepwise.max_height); + resp.u.format.size.step_width = cpu_to_le32(frmsize.stepwise.step_width); + resp.u.format.size.step_height = cpu_to_le32(frmsize.stepwise.step_height); + break; + } + + break; + + case VIRTIO_CAMERA_CMD_CREATE_BUFFER: + num_entries = le32_to_cpu(ctrl.u.buffer.num_entries); + + ents = g_new(typeof(*ents), num_entries); + if (!ents) { + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_ERR_OUT_OF_MEMORY); + break; + } + + size = sizeof(*ents) * num_entries; + if (iov_to_buf(elem->out_sg, elem->out_num, + sizeof(ctrl), ents, size) != size) { + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_ERR_UNSPEC); + g_free(ents); + break; + } + + size = sizeof(*mem_buf) + sizeof(struct iovec) * num_entries; + if (size > G_MAXSIZE) { + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_ERR_OUT_OF_MEMORY); + g_free(ents); + break; + } + + mem_buf = g_malloc0(size); + if (!mem_buf) { + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_ERR_OUT_OF_MEMORY); + g_free(ents); + break; + } + + for (i = 0; i < num_entries; i++) { + uint64_t addr = le64_to_cpu(ents[i].addr); + uint32_t length = le32_to_cpu(ents[i].length); + + hwlen = length; + mem_buf->iov[i].iov_base = dma_memory_map(vdev->dma_as, addr, &hwlen, + DMA_DIRECTION_FROM_DEVICE, + MEMTXATTRS_UNSPECIFIED); + + if (!mem_buf->iov[i].iov_base) { + while (i--) + dma_memory_unmap(vdev->dma_as, + mem_buf->iov[i].iov_base, + mem_buf->iov[i].iov_len, + DMA_DIRECTION_FROM_DEVICE, + mem_buf->iov[i].iov_len); + + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_ERR_UNSPEC); + g_free(mem_buf); + g_free(ents); + break; + } + + mem_buf->iov[i].iov_len = length; + } + + g_free(ents); + + mem_buf->num_entries = num_entries; + qemu_uuid_generate(&mem_buf->uuid); + + QEMU_BUILD_BUG_ON(sizeof(mem_buf->uuid.data) != + sizeof(resp.u.buffer.uuid)); + + memcpy(resp.u.buffer.uuid, mem_buf->uuid.data, + sizeof(mem_buf->uuid)); + + QTAILQ_INSERT_TAIL(&vcam->buflist, mem_buf, node); + break; + + case VIRTIO_CAMERA_CMD_DESTROY_BUFFER: + mem_buf = virtio_camera_mem_buf_by_uuid(vcam, ctrl.u.buffer.uuid); + if (!mem_buf) { + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_ERR_UNSPEC); + break; + } + + if (mem_buf->capture_elem) { + QTAILQ_REMOVE(&vcam->capturelist, mem_buf, capture_node); + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_ERR_UNSPEC); + virtio_camera_push_resp(vcam, &mem_buf->capture_elem, &resp); + } + + while (mem_buf->num_entries--) + dma_memory_unmap(vdev->dma_as, + mem_buf->iov[mem_buf->num_entries].iov_base, + mem_buf->iov[mem_buf->num_entries].iov_len, + DMA_DIRECTION_FROM_DEVICE, + mem_buf->iov[mem_buf->num_entries].iov_len); + + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_OK_NODATA); + QTAILQ_REMOVE(&vcam->buflist, mem_buf, node); + g_free(mem_buf); + break; + + case VIRTIO_CAMERA_CMD_ENQUEUE_BUFFER: + mem_buf = virtio_camera_mem_buf_by_uuid(vcam, ctrl.u.buffer.uuid); + if (!mem_buf) { + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_ERR_UNSPEC); + break; + } + + if (mem_buf->capture_elem) { + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_ERR_BUSY); + break; + } + + QTAILQ_INSERT_TAIL(&vcam->capturelist, mem_buf, capture_node); + mem_buf->capture_elem = elem; + elem = NULL; + break; + + case VIRTIO_CAMERA_CMD_STREAM_ON: + if (vcam->streaming) + break; + + v4l_err = virtio_camera_alloc_v4l_buffers(vcam); + if (v4l_err) + break; + + v4l_err = virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_STREAMON, + &buf_type); + if (v4l_err) { + virtio_camera_destroy_v4l_buffers(vcam); + break; + } + + v4l_err = virtio_camera_enqueue_v4l_buffers(vcam); + if (v4l_err) { + virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_STREAMOFF, + &buf_type); + virtio_camera_destroy_v4l_buffers(vcam); + break; + } + + qemu_set_fd_handler(vcam->v4l2.fd, virtio_camera_v4l_event, NULL, + vcam); + + vcam->streaming = true; + break; + + case VIRTIO_CAMERA_CMD_STREAM_OFF: + if (!vcam->streaming) + break; + + v4l_err = virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_STREAMOFF, + &buf_type); + if (v4l_err) + break; + + virtio_camera_destroy_v4l_buffers(vcam); + + qemu_set_fd_handler(vcam->v4l2.fd, NULL, NULL, vcam); + + QTAILQ_FOREACH_SAFE(mem_buf, &vcam->capturelist, capture_node, + mem_buf_tmp) { + QTAILQ_REMOVE(&vcam->capturelist, mem_buf, capture_node); + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_ERR_UNSPEC); + virtio_camera_push_resp(vcam, &mem_buf->capture_elem, &resp); + } + + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_OK_NODATA); + vcam->streaming = false; + break; + + default: + virtio_error(vdev, "%s: invalid command: %u", __func__, cmd); + break; + } + + if (v4l_err) { + fprintf(stderr, "%s: v4l2 error, command %u: %s\n", + __func__, cmd, strerror(errno)); + + resp.header.cmd = cpu_to_le32(VIRTIO_CAMERA_CMD_RESP_ERR_UNSPEC); + } + + if (elem) + virtio_camera_push_resp(vcam, &elem, &resp); + } + + g_free(elem); +} + +static int virtio_camera_v4l_init(VirtIOCamera *vcam, Error **errp) +{ + struct v4l2_format format = {}; + struct v4l2_capability cap; + int err; + + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vcam->v4l2.fd = v4l2_open(vcam->v4l2.dev_path, O_RDWR | O_NONBLOCK, 0); + if (vcam->v4l2.fd < 0) { + error_setg(errp, "failed to open V4L2 device: %s\n", strerror(errno)); + return vcam->v4l2.fd; + } + + /* whoever sets the format first gets the exclusive control */ + err = virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_G_FMT, &format); + if (err) { + error_setg(errp, "failed to get V4L2 format: %s\n", strerror(errno)); + return err; + } + + /* take exclusive control over the camera device */ + err = virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_S_FMT, &format); + if (err) { + error_setg(errp, "failed to set V4L2 format: %s\n", strerror(errno)); + return err; + } + + err = virtio_camera_v4l_ioctl(vcam->v4l2.fd, VIDIOC_QUERYCAP, &cap); + if (err) { + error_setg(errp, "failed to get V4L2 capability: %s\n", strerror(errno)); + return err; + } + + strncpy((char*)vcam->config.name, (char*)cap.card, + sizeof(vcam->config.name) - 1); + + return 0; +} + +static void virtio_camera_v4l_deinit(VirtIOCamera *vcam) +{ + close(vcam->v4l2.fd); +} + +static void virtio_camera_device_realize(DeviceState *dev, Error **errp) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + VirtIOCamera *vcam = VIRTIO_CAMERA(dev); + int err; + + QTAILQ_INIT(&vcam->buflist); + QTAILQ_INIT(&vcam->capturelist); + + err = virtio_camera_v4l_init(vcam, errp); + if (err) + return; + + virtio_init(vdev, VIRTIO_ID_CAMERA, sizeof(vcam->config)); + vcam->ctrl_vq = virtio_add_queue(vdev, 32, virtio_camera_ctrl_handle); +} + +static void virtio_camera_device_unrealize(DeviceState *dev) +{ + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + VirtIOCamera *vcam = VIRTIO_CAMERA(dev); + + virtio_camera_v4l_deinit(vcam); + virtio_delete_queue(vcam->ctrl_vq); + virtio_cleanup(vdev); +} + +static void virtio_camera_get_config(VirtIODevice *vdev, uint8_t *config) +{ + VirtIOCamera *vcam = VIRTIO_CAMERA(vdev); + + memcpy(config, &vcam->config, sizeof(vcam->config)); +} + +static uint64_t virtio_camera_get_features(VirtIODevice *vdev, + uint64_t features, + Error **errp) +{ + return features; +} + +static void virtio_camera_reset(VirtIODevice *vdev) +{ +} + +static const VMStateDescription vmstate_virtio_camera = { + .name = "virtio-camera", + .unmigratable = 1, + .minimum_version_id = 1, + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_VIRTIO_DEVICE, + VMSTATE_END_OF_LIST() + }, +}; + +static Property virtio_camera_properties[] = { + DEFINE_PROP_STRING("v4ldev", VirtIOCamera, v4l2.dev_path), + DEFINE_PROP_END_OF_LIST(), +}; + +static void virtio_camera_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_virtio_camera; + + vdc->realize = virtio_camera_device_realize; + vdc->unrealize = virtio_camera_device_unrealize; + + vdc->get_config = virtio_camera_get_config; + vdc->get_features = virtio_camera_get_features; + vdc->reset = virtio_camera_reset; + + device_class_set_props(dc, virtio_camera_properties); +} + +static const TypeInfo virtio_camera_info = { + .name = TYPE_VIRTIO_CAMERA, + .parent = TYPE_VIRTIO_DEVICE, + .instance_size = sizeof(VirtIOCamera), + .class_init = virtio_camera_class_init, +}; +module_obj(TYPE_VIRTIO_CAMERA); + +static void virtio_camera_register_types(void) +{ + type_register_static(&virtio_camera_info); +} + +type_init(virtio_camera_register_types) diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c index 5d607aeaa05..e782b6b0824 100644 --- a/hw/virtio/virtio.c +++ b/hw/virtio/virtio.c @@ -171,7 +171,8 @@ const char *virtio_device_names[] = { [VIRTIO_ID_PARAM_SERV] = "virtio-param-serv", [VIRTIO_ID_AUDIO_POLICY] = "virtio-audio-pol", [VIRTIO_ID_BT] = "virtio-bluetooth", - [VIRTIO_ID_GPIO] = "virtio-gpio" + [VIRTIO_ID_GPIO] = "virtio-gpio", + [VIRTIO_ID_CAMERA] = "virtio-camera", }; static const char *virtio_id_to_name(uint16_t device_id) diff --git a/include/hw/virtio/virtio-camera.h b/include/hw/virtio/virtio-camera.h new file mode 100644 index 00000000000..f1fb93cc7f5 --- /dev/null +++ b/include/hw/virtio/virtio-camera.h @@ -0,0 +1,56 @@ +/* + * Virtio Camera Device + * + * Copyright © 2022 Collabora, Ltd. + * + * This work is licensed under the terms of the GNU GPL, version 2. + * See the COPYING file in the top-level directory. + */ + +#ifndef QEMU_VIRTIO_CAMERA_H +#define QEMU_VIRTIO_CAMERA_H + +#include "qemu/queue.h" +#include "qemu/uuid.h" +#include "hw/virtio/virtio.h" +#include "qom/object.h" + +#include "standard-headers/linux/virtio_camera.h" + +#define TYPE_VIRTIO_CAMERA "virtio-camera-device" +OBJECT_DECLARE_SIMPLE_TYPE(VirtIOCamera, VIRTIO_CAMERA) + +#define VIRTIO_CAMERA_GET_PARENT_CLASS(obj) \ + OBJECT_GET_PARENT_CLASS(obj, TYPE_VIRTIO_CAMERA) + +#define VIRTIO_CAMERA_NUM_V4L2_BUFFERS 3 + +struct virtio_camera_v4l2 { + void *buffer_data[VIRTIO_CAMERA_NUM_V4L2_BUFFERS]; + size_t buffer_size[VIRTIO_CAMERA_NUM_V4L2_BUFFERS]; + unsigned int queue_buffer_index; + char *dev_path; + int fd; +}; + +struct virtio_camera_mem_buffer { + QTAILQ_ENTRY(virtio_camera_mem_buffer) node; + QTAILQ_ENTRY(virtio_camera_mem_buffer) capture_node; + QemuUUID uuid; + VirtQueueElement *capture_elem; + unsigned int num_entries; + struct iovec iov[]; +}; + +struct VirtIOCamera { + VirtIODevice parent_obj; + VirtQueue *ctrl_vq; + struct virtio_camera_v4l2 v4l2; + struct virtio_camera_config config; + QTAILQ_HEAD(, virtio_camera_mem_buffer) buflist; + QTAILQ_HEAD(, virtio_camera_mem_buffer) capturelist; + uint64_t mem_buf_uuid; + bool streaming; +}; + +#endif diff --git a/include/standard-headers/linux/virtio_camera.h b/include/standard-headers/linux/virtio_camera.h new file mode 100644 index 00000000000..704dd62c787 --- /dev/null +++ b/include/standard-headers/linux/virtio_camera.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ +/* + * Virtio Camera Device + * + * Copyright © 2022 Collabora, Ltd. + */ + +#ifndef _LINUX_VIRTIO_CAMERA_H +#define _LINUX_VIRTIO_CAMERA_H + +#include "standard-headers/linux/types.h" + +enum virtio_camera_ctrl_type { + VIRTIO_CAMERA_CMD_GET_FORMAT = 0x1, + VIRTIO_CAMERA_CMD_SET_FORMAT, + VIRTIO_CAMERA_CMD_TRY_FORMAT, + VIRTIO_CAMERA_CMD_ENUM_FORMAT, + VIRTIO_CAMERA_CMD_ENUM_SIZE, + VIRTIO_CAMERA_CMD_CREATE_BUFFER, + VIRTIO_CAMERA_CMD_DESTROY_BUFFER, + VIRTIO_CAMERA_CMD_ENQUEUE_BUFFER, + VIRTIO_CAMERA_CMD_STREAM_ON, + VIRTIO_CAMERA_CMD_STREAM_OFF, + + VIRTIO_CAMERA_CMD_RESP_OK_NODATA = 0x100, + + VIRTIO_CAMERA_CMD_RESP_ERR_UNSPEC = 0x200, + VIRTIO_CAMERA_CMD_RESP_ERR_BUSY = 0x201, + VIRTIO_CAMERA_CMD_RESP_ERR_OUT_OF_MEMORY = 0x202, +}; + +struct virtio_camera_config { + uint8_t name[256]; +}; + +struct virtio_camera_mem_entry { + uint64_t addr; + uint32_t length; +}; + +struct virtio_camera_ctrl_hdr { + uint32_t cmd; + uint32_t index; +}; + +struct virtio_camera_format_size { + union { + uint32_t min_width; + uint32_t width; + }; + uint32_t max_width; + uint32_t step_width; + + union { + uint32_t min_height; + uint32_t height; + }; + uint32_t max_height; + uint32_t step_height; + uint32_t stride; + uint32_t sizeimage; +}; + +struct virtio_camera_req_format { + uint32_t pixelformat; + struct virtio_camera_format_size size; +}; + +struct virtio_camera_req_buffer { + uint32_t num_entries; + uint8_t uuid[16]; +}; + +struct virtio_camera_op_ctrl_req { + struct virtio_camera_ctrl_hdr header; + + union { + struct virtio_camera_req_format format; + struct virtio_camera_req_buffer buffer; + uint64_t padding[3]; + } u; +}; + +#endif diff --git a/include/standard-headers/linux/virtio_ids.h b/include/standard-headers/linux/virtio_ids.h index 80d76b75bcc..39b2b5344da 100644 --- a/include/standard-headers/linux/virtio_ids.h +++ b/include/standard-headers/linux/virtio_ids.h @@ -68,6 +68,7 @@ #define VIRTIO_ID_AUDIO_POLICY 39 /* virtio audio policy */ #define VIRTIO_ID_BT 40 /* virtio bluetooth */ #define VIRTIO_ID_GPIO 41 /* virtio gpio */ +#define VIRTIO_ID_CAMERA 42 /* virtio camera */ /* * Virtio Transitional IDs diff --git a/meson.build b/meson.build index 20fddbd707c..33b16be57f9 100644 --- a/meson.build +++ b/meson.build @@ -2782,6 +2782,9 @@ config_host_data.set('CONFIG_CAPSTONE', capstone.found()) config_host_data.set('CONFIG_FDT', fdt.found()) config_host_data.set('CONFIG_SLIRP', slirp.found()) +libv4l2 = dependency('libv4l2', required: true, method: 'pkg-config', + kwargs: static_kwargs) + ##################### # Generated sources # ##################### @@ -3981,6 +3984,7 @@ summary_info += {'capstone': capstone} summary_info += {'libpmem support': libpmem} summary_info += {'libdaxctl support': libdaxctl} summary_info += {'libudev': libudev} +summary_info += {'libv4l2 support': libv4l2} # Dummy dependency, keep .found() summary_info += {'FUSE lseek': fuse_lseek.found()} summary_info += {'selinux': selinux} -- GitLab