diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 7da4bcdd395d..36da6bea2e41 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -2206,8 +2206,12 @@ static int btintel_prepare_fw_download_tlv(struct hci_dev *hdev, if (err < 0) { if (err == -EALREADY) { /* Firmware has already been loaded */ - btintel_set_flag(hdev, INTEL_FIRMWARE_LOADED); - err = 0; + bt_dev_info(hdev, "Firmware already loaded"); + //btintel_set_flag(hdev, INTEL_FIRMWARE_LOADED); + btintel_reset_to_bootloader(hdev); + /*even this error report, the BT still working , + so 150ms delay is still needed */ + msleep(150); goto done; } diff --git a/drivers/media/common/videobuf2/videobuf2-dma-sg.c b/drivers/media/common/videobuf2/videobuf2-dma-sg.c index 6975a71d740f..91378725ae8a 100644 --- a/drivers/media/common/videobuf2/videobuf2-dma-sg.c +++ b/drivers/media/common/videobuf2/videobuf2-dma-sg.c @@ -21,6 +21,7 @@ #include #include #include +#include "linux/virtio_shm.h" static int debug; module_param(debug, int, 0644); @@ -105,6 +106,8 @@ static void *vb2_dma_sg_alloc(struct vb2_buffer *vb, struct device *dev, struct sg_table *sgt; int ret; int num_pages; + int i; + struct scatterlist *sg; if (WARN_ON(!dev) || WARN_ON(!size)) return ERR_PTR(-EINVAL); @@ -130,9 +133,25 @@ static void *vb2_dma_sg_alloc(struct vb2_buffer *vb, struct device *dev, if (!buf->pages) goto fail_pages_array_alloc; - ret = vb2_dma_sg_alloc_compacted(buf, vb->vb2_queue->gfp_flags); - if (ret) - goto fail_pages_alloc; + if (strcmp(dev->driver->name, "virtio-ivshmem") == 0 || + strcmp(dev->driver->name, "virtio-guest-shm") == 0) { + pr_err("%s: alloc memory from ivshmem begin\n", __func__); + for(i = 0; i < buf->num_pages; i++) { + buf->pages[i] = virtio_shmem_allocate_page(dev); + if (!buf->pages[i]) { + while(i > 0) { + virtio_shmem_free_page(dev, buf->pages[i-1]); + i--; + }; + goto fail_pages_array_alloc; + } + } + pr_err("%s: alloc memory from ivshmem end\n", __func__); + } else { + ret = vb2_dma_sg_alloc_compacted(buf, vb->vb2_queue->gfp_flags); + if (ret) + goto fail_pages_alloc; + } ret = sg_alloc_table_from_pages(buf->dma_sgt, buf->pages, buf->num_pages, 0, size, GFP_KERNEL); @@ -190,8 +209,17 @@ static void vb2_dma_sg_put(void *buf_priv) if (buf->vaddr) vm_unmap_ram(buf->vaddr, buf->num_pages); sg_free_table(buf->dma_sgt); - while (--i >= 0) - __free_page(buf->pages[i]); + + if (strcmp(buf->dev->driver->name, "virtio-ivshmem") == 0 || + strcmp(buf->dev->driver->name, "virtio-guest-shm") == 0) { + while (--i >= 0) { + virtio_shmem_free_page(buf->dev, buf->pages[i]); + }; + } else { + while (--i >= 0) + __free_page(buf->pages[i]); + } + kvfree(buf->pages); put_device(buf->dev); kfree(buf); diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index ee579916f874..b879976def34 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -85,5 +85,6 @@ source "drivers/media/platform/ti/Kconfig" source "drivers/media/platform/verisilicon/Kconfig" source "drivers/media/platform/via/Kconfig" source "drivers/media/platform/xilinx/Kconfig" +source "drivers/media/platform/virtio/Kconfig" endif # MEDIA_PLATFORM_DRIVERS diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile index 5453bb868e67..a38af4c06848 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -28,6 +28,7 @@ obj-y += ti/ obj-y += verisilicon/ obj-y += via/ obj-y += xilinx/ +obj-y += virtio/ # Please place here only ancillary drivers that aren't SoC-specific # Please keep it alphabetically sorted by Kconfig name diff --git a/drivers/media/platform/virtio/Kconfig b/drivers/media/platform/virtio/Kconfig new file mode 100644 index 000000000000..173230b0bea4 --- /dev/null +++ b/drivers/media/platform/virtio/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "VirtIO media platform drivers" + +config VIDEO_VIRTIO_CAMERA + tristate "VirtIO camera driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + depends on VIRTIO + select VIDEOBUF2_DMA_SG + select VIRTIO_DMA_SHARED_BUFFER + help + This driver provides support for VirtIO camera device. If you + choose 'M' here, this module will be called virtio_camera. diff --git a/drivers/media/platform/virtio/Makefile b/drivers/media/platform/virtio/Makefile new file mode 100644 index 000000000000..0fb500b75f37 --- /dev/null +++ b/drivers/media/platform/virtio/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_VIDEO_VIRTIO_CAMERA) += virtio-camera.o diff --git a/drivers/media/platform/virtio/virtio-camera.c b/drivers/media/platform/virtio/virtio-camera.c new file mode 100644 index 000000000000..8e15bc22c616 --- /dev/null +++ b/drivers/media/platform/virtio/virtio-camera.c @@ -0,0 +1,995 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + /* + * Driver for VirtIO camera device. + * + * Copyright © 2022 Collabora, Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define VQ_NAME_LEN 24 +/** + * struct virtio_camera_ctrl_req - The internal data for one virtio-camera request and response + * @ctrl: The request info, filled in frontend + * @resp: The response info, filled by backend + * @completion: the "completion" signal + * @vb: The specific buffer related to this request + */ +struct virtio_camera_ctrl_req { + struct virtio_camera_op_ctrl_req ctrl; + struct virtio_camera_op_ctrl_req resp; + struct completion completion; + struct vb2_buffer *vb; +}; + +/* Per-virtqueue state */ +struct virtio_camera_vq { + struct virtqueue *vq; + char name[VQ_NAME_LEN]; +}; + +/** + * struct virtio_camera_video - All internal data for one instance of video node + * @vdev: video device node structure + * @video_lock: ioctl serialization mutex + * @vb_queue: vb2 video capture queue + * @sequence: frame sequence counter + * @format: current v4l2 format + * @ctr_vqx: ctrl virtqueue + */ +struct virtio_camera_video { + struct video_device vdev; + struct mutex video_lock; + struct vb2_queue vb_queue; + unsigned int sequence; + struct v4l2_format format; + struct virtio_camera_vq *ctr_vqx; + unsigned int idx; +}; + +/** + * struct virtio_camera_buffer - the driver-specific buffer structure; + * driver uses this custom buffer structure type. The first field of the + * driver-specific buffer structure must be the subsystem-specific + * struct (vb2_v4l2_buffer in the case of V4L2). + * @vb: vb2_v4l2 buffer + * @uuid: ID info for this buffer + */ +struct virtio_camera_buffer { + struct vb2_v4l2_buffer vb; + u8 uuid[16]; +}; + +/** + * struct virtual_camera - All internal data for one instance of virtual camera + * @vnode: video nodes + * @nr_videos: number of videos; one virtual camera may contain multiple video streams + * @v4l2_lock: ioctl serialization mutex + * @v4l2_dev: top-level v4l2 device struct + * @mdev: top-level media device struct + */ +struct virtual_camera { + struct virtio_camera_video *vnodes; + int nr_videos; + struct mutex v4l2_lock; + struct v4l2_device v4l2_dev; + struct media_device mdev; +}; + +/** + * struct virtio_camera - All internal data for one instance of virtio camera + * @config: virtio camera configuration structure + * @vqs: used virtqueues + * @nvqs: number of virtqueues + * @virtual_cameras: virtual cameras; one virtio_camera may contain multiple virtual cameras + */ +struct virtio_camera { + struct virtio_camera_config config; + struct virtio_camera_vq *vqs; + unsigned int nvqs; + struct virtual_camera *virtual_cameras; +}; + +static inline struct virtio_camera_buffer * +vb_to_vcam_buf(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + return container_of(vbuf, struct virtio_camera_buffer, vb); +} + +static struct virtio_camera_ctrl_req * +virtio_camera_create_req(unsigned int cmd) +{ + struct virtio_camera_ctrl_req *vcam_req; + + vcam_req = kmalloc(sizeof(*vcam_req), GFP_KERNEL); + if (unlikely(vcam_req == NULL)) { + pr_err("virtio_camera: could not allocate integrity buffer\n"); + return NULL; + } + + vcam_req->ctrl.header.cmd = cmd; + vcam_req->vb = NULL; + init_completion(&vcam_req->completion); + + return vcam_req; +} + +static void virtio_camera_control_ack(struct virtqueue *vq) +{ + struct virtio_camera_ctrl_req *req; + struct vb2_v4l2_buffer *vbuf; + unsigned int len; + + while ((req = virtqueue_get_buf(vq, &len))) { + complete(&req->completion); + + if (req->vb) { + vbuf = to_vb2_v4l2_buffer(req->vb); + vbuf->sequence = req->resp.u.buffer.sequence; + vbuf->vb2_buf.timestamp = req->resp.u.buffer.timestamp; + vb2_buffer_done(req->vb, VB2_BUF_STATE_DONE); + pr_debug("virtio-camera: mark the buffer done. UUID is %d, ptr is %pK\n", + req->resp.u.buffer.uuid[0] + req->resp.u.buffer.uuid[1], req->vb); + + kfree(req); + } + } +} + +static int vcam_vq_request(struct virtio_camera_video *vnode, + struct virtio_camera_ctrl_req *req, + struct virtio_camera_mem_entry *ents, + unsigned int num_ents, + bool async) +{ + struct scatterlist vreq[3], *sgs[3]; + unsigned int num_sgs = 0; + int ret; + + memset(&req->resp, 0, sizeof(req->resp)); + + sg_init_one(&vreq[0], &req->ctrl, sizeof(req->ctrl)); + sgs[num_sgs++] = &vreq[0]; + + if (ents) { + sg_init_one(&vreq[1], ents, sizeof(*ents) * num_ents); + sgs[num_sgs++] = &vreq[1]; + } + sg_init_one(&vreq[2], &req->resp, sizeof(req->resp)); + sgs[num_sgs++] = &vreq[2]; + + ret = virtqueue_add_sgs(vnode->ctr_vqx->vq, sgs, num_sgs - 1, 1, req, GFP_KERNEL); + if (ret) + return ret; + + virtqueue_kick(vnode->ctr_vqx->vq); + + if (async) + return 0; + + wait_for_completion(&req->completion); + + memset(&req->ctrl, 0, sizeof(req->ctrl)); + + switch (req->resp.header.cmd) { + case VIRTIO_CAMERA_CMD_RESP_OK_NODATA: + ret = 0; + break; + + case VIRTIO_CAMERA_CMD_RESP_ERR_BUSY: + ret = -EBUSY; + break; + + case VIRTIO_CAMERA_CMD_RESP_ERR_OUT_OF_MEMORY: + ret = -ENOMEM; + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +int vcam_v4l2_fh_open(struct file *filp) +{ + struct virtio_camera_video *vnode; + struct virtio_camera_ctrl_req *vcam_req; + int err; + + vnode = video_drvdata(filp); + + vcam_req = virtio_camera_create_req(VIRTIO_CAMERA_CMD_FILE_OPEN); + if (unlikely(vcam_req == NULL)) + return -ENOMEM; + + err = vcam_vq_request(vnode, vcam_req, NULL, 0, false); + if (err) { + pr_err("virtio-camera: file handler open failed because of err response.\n"); + goto err_free; + } + + err = v4l2_fh_open(filp); + +err_free: + kfree(vcam_req); + return err; +} + +int vcam_v4l2_fh_release(struct file *filp) +{ + struct virtio_camera_video *vnode; + struct virtio_camera_ctrl_req *vcam_req; + int err; + + err = vb2_fop_release(filp); + if (err) + goto err; + + vnode = video_drvdata(filp); + vcam_req = virtio_camera_create_req(VIRTIO_CAMERA_CMD_FILE_CLOSE); + if (unlikely(vcam_req == NULL)) + return -ENOMEM; + + err = vcam_vq_request(vnode, vcam_req, NULL, 0, false); + if (err) + pr_err("virtio-camera: file handler release failed because of err response.\n"); + + kfree(vcam_req); +err: + return err; +} +static const struct v4l2_file_operations vcam_v4l2_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = vcam_v4l2_fh_open, + .release = vcam_v4l2_fh_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .read = vb2_fop_read, +}; + +static int vcam_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct virtio_camera_video *vnode = video_drvdata(file); + + strscpy(cap->bus_info, "platform:camera", sizeof(cap->bus_info)); + strscpy(cap->driver, "virtio-camera", sizeof(cap->driver)); + snprintf(cap->card, sizeof(cap->card), "virtio-camera%u", vnode->idx); + return 0; +} + +static int vcam_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct virtio_camera_video *vnode = video_drvdata(file); + struct virtio_camera_ctrl_req *vcam_req; + int err; + + vcam_req = virtio_camera_create_req(VIRTIO_CAMERA_CMD_ENUM_FORMAT); + if (unlikely(vcam_req == NULL)) + return -ENOMEM; + + vcam_req->ctrl.header.index = f->index; + + err = vcam_vq_request(vnode, vcam_req, NULL, 0, false); + if (err) + goto err_free; + + f->pixelformat = vcam_req->resp.u.format.pixelformat; + +err_free: + kfree(vcam_req); + return err; +} + +static int vcam_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct virtio_camera_video *vnode = video_drvdata(file); + struct virtio_camera_format_size *sz; + struct virtio_camera_ctrl_req *vcam_req; + int err; + + vcam_req = virtio_camera_create_req(VIRTIO_CAMERA_CMD_ENUM_SIZE); + if (unlikely(vcam_req == NULL)) + return -ENOMEM; + + vcam_req->ctrl.header.index = fsize->index; + vcam_req->ctrl.u.format.pixelformat = fsize->pixel_format; + + err = vcam_vq_request(vnode, vcam_req, NULL, 0, false); + if (err) + goto err_free; + + sz = &vcam_req->resp.u.format.size; + /* For simplify, now only support the DISCRETE format. */ + if (true) { + fsize->discrete.width = sz->width; + fsize->discrete.height = sz->height; + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + } else { + fsize->stepwise.min_width = sz->min_width; + fsize->stepwise.max_width = sz->max_width; + fsize->stepwise.min_height = sz->min_height; + fsize->stepwise.max_height = sz->max_height; + fsize->stepwise.step_width = sz->step_width; + fsize->stepwise.step_height = sz->max_height; + + if (sz->step_width == 1 && sz->step_height == 1) + fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + else + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + } + pr_debug("%s: fmt width is %d, height is %d\n", __func__, sz->width, sz->height); + +err_free: + kfree(vcam_req); + return err; +} + +static int vcam_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fintv) +{ + struct virtio_camera_video *vnode = video_drvdata(file); + struct virtio_camera_format_size *sz; + struct virtio_camera_ctrl_req *vcam_req; + int err = 0; + + vcam_req = virtio_camera_create_req(VIRTIO_CAMERA_CMD_ENUM_INTV); + if (unlikely(vcam_req == NULL)) + return -ENOMEM; + + vcam_req->ctrl.header.index = fintv->index; + vcam_req->ctrl.u.format.pixelformat = fintv->pixel_format; + vcam_req->ctrl.u.format.size.height = fintv->height; + vcam_req->ctrl.u.format.size.width = fintv->width; + + err = vcam_vq_request(vnode, vcam_req, NULL, 0, false); + sz = &vcam_req->resp.u.format.size; + if (err) + goto err_free; + + fintv->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fintv->discrete.denominator = sz->fps; + fintv->discrete.numerator = 1; + pr_debug("%s: idx is %d, denominator is %d, numerator is %d\n", __func__, + fintv->index, fintv->discrete.denominator, fintv->discrete.numerator); + +err_free: + kfree(vcam_req); + return err; +} + +static int vcam_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct virtio_camera_video *vnode = video_drvdata(file); + struct virtio_camera_ctrl_req *vcam_req; + int err; + + vcam_req = virtio_camera_create_req(VIRTIO_CAMERA_CMD_GET_FORMAT); + if (unlikely(vcam_req == NULL)) + return -ENOMEM; + + err = vcam_vq_request(vnode, vcam_req, NULL, 0, false); + if (err) + goto err_free; + + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + f->fmt.pix.flags = 0; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.width = vcam_req->resp.u.format.size.width; + f->fmt.pix.height = vcam_req->resp.u.format.size.height; + f->fmt.pix.pixelformat = vcam_req->resp.u.format.pixelformat; + f->fmt.pix.bytesperline = vcam_req->resp.u.format.size.stride; + f->fmt.pix.sizeimage = vcam_req->resp.u.format.size.sizeimage; + + /* TODO */ + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + + vnode->format = *f; + +err_free: + kfree(vcam_req); + return err; +} + +static int vcam_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct virtio_camera_video *vnode = video_drvdata(file); + struct virtio_camera_ctrl_req *vcam_req; + int err; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + vcam_req = virtio_camera_create_req(VIRTIO_CAMERA_CMD_SET_FORMAT); + if (unlikely(vcam_req == NULL)) + return -ENOMEM; + + vcam_req->ctrl.u.format.size.width = f->fmt.pix.width; + vcam_req->ctrl.u.format.size.height = f->fmt.pix.height; + vcam_req->ctrl.u.format.size.stride = f->fmt.pix.bytesperline; + vcam_req->ctrl.u.format.pixelformat = f->fmt.pix.pixelformat; + + err = vcam_vq_request(vnode, vcam_req, NULL, 0, false); + if (err) + goto err_free; + + f->fmt.pix.flags = 0; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.width = vcam_req->resp.u.format.size.width; + f->fmt.pix.height = vcam_req->resp.u.format.size.height; + f->fmt.pix.pixelformat = vcam_req->resp.u.format.pixelformat; + f->fmt.pix.bytesperline = vcam_req->resp.u.format.size.stride; + f->fmt.pix.sizeimage = vcam_req->resp.u.format.size.sizeimage; + + /* TODO */ + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + + vnode->format = *f; + +err_free: + kfree(vcam_req); + return err; +} + +static int vcam_try_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct virtio_camera_video *vnode = video_drvdata(file); + struct virtio_camera_ctrl_req *vcam_req; + int err; + + vcam_req = virtio_camera_create_req(VIRTIO_CAMERA_CMD_TRY_FORMAT); + if (unlikely(vcam_req == NULL)) + return -ENOMEM; + + vcam_req->ctrl.u.format.size.width = f->fmt.pix.width; + vcam_req->ctrl.u.format.size.height = f->fmt.pix.height; + vcam_req->ctrl.u.format.size.stride = f->fmt.pix.bytesperline; + vcam_req->ctrl.u.format.pixelformat = f->fmt.pix.pixelformat; + + err = vcam_vq_request(vnode, vcam_req, NULL, 0, false); + if (err) + goto err_free; + + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + f->fmt.pix.flags = 0; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.width = vcam_req->resp.u.format.size.width; + f->fmt.pix.height = vcam_req->resp.u.format.size.height; + f->fmt.pix.pixelformat = vcam_req->resp.u.format.pixelformat; + f->fmt.pix.bytesperline = vcam_req->resp.u.format.size.stride; + f->fmt.pix.sizeimage = vcam_req->resp.u.format.size.sizeimage; + + /* TODO */ + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; + +err_free: + kfree(vcam_req); + return err; +} + +static int vcam_enum_input(struct file *filp, void *p, + struct v4l2_input *input) +{ + if (input->index > 0) + return -EINVAL; + + strscpy(input->name, "virtio-camera0", sizeof(input->name)); + input->type = V4L2_INPUT_TYPE_CAMERA; + input->std = V4L2_STD_UNKNOWN; + input->status = 0; + + return 0; +} + +static int vcam_g_input(struct file *filp, void *p, unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int vcam_s_input(struct file *filp, void *p, unsigned int i) +{ + if (i) + return -EINVAL; + + return 0; +} + +static const struct v4l2_ioctl_ops vcam_ioctl_ops = { + .vidioc_querycap = vcam_querycap, + .vidioc_enum_fmt_vid_cap = vcam_enum_fmt, + /* Frame size and interval */ + .vidioc_enum_frameintervals = vcam_enum_frameintervals, + .vidioc_enum_framesizes = vcam_enum_framesizes, + .vidioc_g_fmt_vid_cap = vcam_g_fmt, + .vidioc_s_fmt_vid_cap = vcam_s_fmt, + .vidioc_try_fmt_vid_cap = vcam_try_fmt, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_enum_input = vcam_enum_input, + .vidioc_g_input = vcam_g_input, + .vidioc_s_input = vcam_s_input, +}; + +static int +vcam_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) + +{ + struct virtio_camera_video *vnode = vb2_get_drv_priv(vq); + unsigned int size = vnode->format.fmt.pix.sizeimage; + int ret; + + if (vq->num_buffers + *nbuffers < 2) + *nbuffers = 2 - vq->num_buffers; + + if (*num_planes) { + ret = sizes[0] < size ? -EINVAL : 0; + return ret; + } + + *num_planes = 1; + sizes[0] = size; + + return 0; +} + +static int vcam_buf_init(struct vb2_buffer *vb) +{ + struct virtio_camera_video *vnode = vb2_get_drv_priv(vb->vb2_queue); + struct virtio_camera_buffer *vbuf = vb_to_vcam_buf(vb); + struct virtio_camera_mem_entry *ents; + struct virtio_camera_ctrl_req *vcam_req; + struct scatterlist *sg; + struct sg_table *sgt; + unsigned int i; + int err; + + /* TODO */ + if (WARN_ON(vb->num_planes != 1)) + return -EINVAL; + + sgt = vb2_dma_sg_plane_desc(vb, 0); + ents = kmalloc_array(sgt->nents, sizeof(*ents), GFP_KERNEL); + if (!ents) + return -ENOMEM; + + for_each_sg(sgt->sgl, sg, sgt->nents, i) { + ents[i].addr = cpu_to_le64(sg_phys(sg)); + ents[i].length = cpu_to_le32(sg->length); + } + + vcam_req = virtio_camera_create_req(VIRTIO_CAMERA_CMD_CREATE_BUFFER); + if (unlikely(vcam_req == NULL)) { + pr_err("%s: Fail to init the virtio-camera request buffer\n", __func__); + err = -ENOMEM; + goto err_free; + } + + vcam_req->ctrl.u.buffer.num_entries = sgt->nents; + + err = vcam_vq_request(vnode, vcam_req, ents, sgt->nents, false); + if (err) + goto err_free; + + memcpy(vbuf->uuid, vcam_req->resp.u.buffer.uuid, sizeof(vbuf->uuid)); + + vcam_req->vb = vb; + +err_free: + kfree(ents); + kfree(vcam_req); + return err; +} + +static void vcam_buf_cleanup(struct vb2_buffer *vb) +{ + struct virtio_camera_video *vnode = vb2_get_drv_priv(vb->vb2_queue); + struct virtio_camera_buffer *vbuf = vb_to_vcam_buf(vb); + struct virtio_camera_ctrl_req *vcam_req; + int err; + + vcam_req = virtio_camera_create_req(VIRTIO_CAMERA_CMD_DESTROY_BUFFER); + if (unlikely(vcam_req == NULL)) { + pr_err("%s: Failed to clean vcam buffer\n", __func__); + return; + } + + memcpy(vcam_req->ctrl.u.buffer.uuid, vbuf->uuid, sizeof(vbuf->uuid)); + err = vcam_vq_request(vnode, vcam_req, NULL, 0, false); + if (err) { + pr_err("%s: Failed to deinit virtio-camera buffers, buffers may still be retained by backend\n", + __func__); + } + kfree(vcam_req); +} + +static int vcam_buf_prepare(struct vb2_buffer *vb) +{ + struct virtio_camera_video *vnode = vb2_get_drv_priv(vb->vb2_queue); + + vb2_set_plane_payload(vb, 0, vnode->format.fmt.pix.sizeimage); + + return 0; +} + +static void vcam_buf_queue(struct vb2_buffer *vb) +{ + struct virtio_camera_video *vnode = vb2_get_drv_priv(vb->vb2_queue); + struct virtio_camera_buffer *vbuf = vb_to_vcam_buf(vb); + struct virtio_camera_ctrl_req *vcam_req; + int err; + + vcam_req = virtio_camera_create_req(VIRTIO_CAMERA_CMD_ENQUEUE_BUFFER); + if (unlikely(vcam_req == NULL)) { + pr_err("%s: Failed to queue a vcam buffer\n", __func__); + return; + } + + memcpy(vcam_req->ctrl.u.buffer.uuid, vbuf->uuid, sizeof(vbuf->uuid)); + vcam_req->vb = vb; + err = vcam_vq_request(vnode, vcam_req, NULL, 0, true); + if (err) { + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + goto err_free; + } + + pr_debug("virtio-camera: qbuf, video idx is %d. UUID is %d, ptr is %pK\n", + vnode->idx, vcam_req->resp.u.buffer.uuid[0] + vcam_req->resp.u.buffer.uuid[1], + vcam_req->vb); + return; + +err_free: + kfree(vcam_req); +} + +static int vcam_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct virtio_camera_video *vnode = vb2_get_drv_priv(q); + struct virtio_camera_ctrl_req *vcam_req; + int err; + + vnode->sequence = 0; + vcam_req = virtio_camera_create_req(VIRTIO_CAMERA_CMD_STREAM_ON); + if (unlikely(vcam_req == NULL)) + return -ENOMEM; + + err = vcam_vq_request(vnode, vcam_req, NULL, 0, false); + if (err) + pr_err("%s: Failed to start streaming, err response\n", __func__); + + kfree(vcam_req); + return err; +} + +static void vcam_stop_streaming(struct vb2_queue *q) +{ + struct virtio_camera_video *vnode = vb2_get_drv_priv(q); + struct virtio_camera_ctrl_req *vcam_req; + int err; + + vcam_req = virtio_camera_create_req(VIRTIO_CAMERA_CMD_STREAM_OFF); + if (unlikely(vcam_req == NULL)) { + pr_err("%s: Failed to stop streaming\n", __func__); + return; + } + + /*TODO, mark all vcam buffers invalid when err occur*/ + err = vcam_vq_request(vnode, vcam_req, NULL, 0, false); + if (err) + pr_err("%s: Failed to stop streaming, err response\n", __func__); + + vb2_wait_for_all_buffers(q); + kfree(vcam_req); +} + +static const struct vb2_ops vcam_vb2_ops = { + .queue_setup = vcam_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_init = vcam_buf_init, + .buf_queue = vcam_buf_queue, + .buf_cleanup = vcam_buf_cleanup, + .buf_prepare = vcam_buf_prepare, + .start_streaming = vcam_start_streaming, + .stop_streaming = vcam_stop_streaming, +}; + +static void delete_vqs(void *data) +{ + struct virtio_device *vdev = data; + + vdev->config->del_vqs(vdev); +} + +/* Initialize virtqueues */ +static int virtio_camera_setup_vqs(struct virtio_device *vdev, + struct virtio_camera *vcam) +{ + struct virtqueue **vqs; + vq_callback_t **callbacks; + const char **names; + unsigned int i, nvqs = 0; + int ret = 0; + + if (vcam->config.num_virtual_cameras == 0) + return -EINVAL; + + for (i = 0; i < vcam->config.num_virtual_cameras; i++) + nvqs += vcam->config.nr_per_virtual_camera[i]; + + vcam->vqs = devm_kzalloc(&vdev->dev, + nvqs * sizeof(struct virtio_camera_vq), + GFP_KERNEL); + if (!vcam->vqs) { + pr_err("%s: virtio_camera failed alloc mem.\n", __func__); + return -ENOMEM; + } + + vcam->nvqs = nvqs; + + vqs = kmalloc_array(vcam->nvqs, sizeof(vqs[0]), GFP_KERNEL); + callbacks = kmalloc_array(vcam->nvqs, sizeof(callbacks[0]), + GFP_KERNEL); + names = kmalloc_array(vcam->nvqs, sizeof(names[0]), GFP_KERNEL); + if (!vqs || !callbacks || !names) { + pr_err("%s: virtio_camera failed alloc mem.\n", __func__); + ret = -ENOMEM; + goto out; + } + + /* Initialize the requests virtqueues */ + for (i = 0; i < vcam->nvqs; i++) { + snprintf(vcam->vqs[i].name, VQ_NAME_LEN, "control.%u", i); + callbacks[i] = virtio_camera_control_ack; + names[i] = vcam->vqs[i].name; + } + + ret = virtio_find_vqs(vdev, vcam->nvqs, vqs, callbacks, names, NULL); + if (ret < 0) { + pr_err("%s: virtio_camera failed find vqs.\n", __func__); + goto out; + } + + for (i = 0; i < vcam->nvqs; i++) { + vcam->vqs[i].vq = vqs[i]; + pr_debug("%s: setup vq %d, address is%pK\n", __func__, i, vqs[i]); + } + +out: + kfree(names); + kfree(callbacks); + kfree(vqs); + if (ret) + kfree(vcam->vqs); + return ret; +} + +static int virtio_camera_unregister_devs(struct virtio_camera *vcam, + int virt_cameras_idx, int vnodes_idx) +{ + struct virtual_camera *virt_camera; + unsigned int i, j; + + if (virt_cameras_idx >= vcam->config.num_virtual_cameras) + return -EINVAL; + + if (vnodes_idx >= vcam->virtual_cameras[virt_cameras_idx].nr_videos) + return -EINVAL; + + for (i = 0; i < virt_cameras_idx; i++) { + virt_camera = &vcam->virtual_cameras[i]; + for (j = 0; j < virt_camera->nr_videos; j++) + video_unregister_device(&virt_camera->vnodes[j].vdev); + v4l2_device_unregister(&virt_camera->v4l2_dev); + } + + virt_camera = &vcam->virtual_cameras[virt_cameras_idx]; + for (j = 0; j <= vnodes_idx; j++) + video_unregister_device(&virt_camera->vnodes[j].vdev); + v4l2_device_unregister(&vcam->virtual_cameras[i].v4l2_dev); + + return 0; +} + +static int virtio_camera_setup_vnode(struct virtio_device *vdev, + struct virtio_camera *vcam) +{ + struct virtual_camera *virt_camera; + struct virtio_camera_video *vnode; + unsigned int i, j, vq_idx = 0; + int err; + + vcam->virtual_cameras = devm_kzalloc(&vdev->dev, + vcam->config.num_virtual_cameras * sizeof(*virt_camera), + GFP_KERNEL); + if (!vcam->virtual_cameras) + return -ENOMEM; + + for (i = 0; i < vcam->config.num_virtual_cameras; i++) { + virt_camera = &vcam->virtual_cameras[i]; + virt_camera->vnodes = devm_kzalloc(&vdev->dev, + vcam->config.nr_per_virtual_camera[i] * sizeof(*vnode), + GFP_KERNEL); + if (!virt_camera->vnodes) + return -ENOMEM; + + virt_camera->nr_videos = vcam->config.nr_per_virtual_camera[i]; + mutex_init(&virt_camera->v4l2_lock); + media_device_init(&virt_camera->mdev); + + err = v4l2_device_register(&vdev->dev, &virt_camera->v4l2_dev); + if (err) { + if (i > 0) + virtio_camera_unregister_devs(vcam, i-1, virt_camera->nr_videos-1); + return dev_err_probe(&vdev->dev, err, "failed to register v4l2 device\n"); + } + + for (j = 0; j < vcam->config.nr_per_virtual_camera[i]; j++) { + vnode = &vcam->virtual_cameras[i].vnodes[0]; + video_set_drvdata(&vnode->vdev, vnode); + mutex_init(&vnode->video_lock); + vnode->vdev.queue = &vnode->vb_queue; + vnode->vdev.lock = &vnode->video_lock, + vnode->vdev.fops = &vcam_v4l2_fops, + vnode->vdev.vfl_dir = VFL_DIR_RX, + vnode->vdev.release = video_device_release_empty, + vnode->vdev.v4l2_dev = &vcam->virtual_cameras[i].v4l2_dev; + vnode->vdev.ioctl_ops = &vcam_ioctl_ops, + vnode->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + + vnode->vb_queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vnode->vb_queue.buf_struct_size = sizeof(struct virtio_camera_buffer); + vnode->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vnode->vb_queue.io_modes = VB2_MMAP | VB2_DMABUF; + vnode->vb_queue.mem_ops = &vb2_dma_sg_memops; + vnode->vb_queue.lock = &vnode->video_lock; + vnode->vb_queue.min_buffers_needed = 1; + vnode->vb_queue.gfp_flags = GFP_DMA32; + vnode->vb_queue.ops = &vcam_vb2_ops; + vnode->vb_queue.dev = vdev->dev.parent; + vnode->vb_queue.drv_priv = vnode; + + vnode->idx = i; + err = vb2_queue_init(&vnode->vb_queue); + if (err) { + virtio_camera_unregister_devs(vcam, i, j-1); + return dev_err_probe(&vdev->dev, err, "failed to init vb2 queue"); + } + + vnode->ctr_vqx = &vcam->vqs[vq_idx++]; + + err = video_register_device(&vnode->vdev, VFL_TYPE_VIDEO, -1); + + if (err) { + virtio_camera_unregister_devs(vcam, i, j-1); + return dev_err_probe(&vdev->dev, err, "failed to register video"); + } + } + } + + pr_info("%s: success register video devices\n", __func__); + return 0; +} + +static int virtio_camera_probe(struct virtio_device *vdev) +{ + struct virtio_camera *vcam; + int err, i; + + vcam = devm_kzalloc(&vdev->dev, sizeof(*vcam), GFP_KERNEL); + if (!vcam) + return -ENOMEM; + + vdev->priv = vcam; + + virtio_cread_bytes(vdev, 0, &vcam->config, sizeof(vcam->config)); + + if (vcam->config.num_virtual_cameras > ARRAY_SIZE(vcam->config.nr_per_virtual_camera)) { + pr_err("%s: nr cameras too large %d", __func__, vcam->config.num_virtual_cameras); + return -EINVAL; + } + + for (i = 0; i < vcam->config.num_virtual_cameras; i++) + vcam->config.nr_per_virtual_camera[i] = 1; + + err = virtio_camera_setup_vqs(vdev, vcam); + if (err) + goto out; + + err = virtio_camera_setup_vnode(vdev, vcam); + if (err) + goto out_vqs; + + err = devm_add_action_or_reset(&vdev->dev, delete_vqs, vdev); + if (err) + goto out_vqs; + + return 0; + +out_vqs: + vdev->config->reset(vdev); + vdev->config->del_vqs(vdev); + +out: + pr_err("Failed to probe virtio-camera device\n"); + vdev->priv = NULL; + return err; +} + +static void virtio_camera_remove(struct virtio_device *vdev) +{ + struct virtio_camera *vcam = vdev->priv; + int i, j; + + for (i = 0; i < vcam->config.num_virtual_cameras; i++) { + for (j = 0; j < vcam->config.nr_per_virtual_camera[i]; j++) + video_unregister_device(&vcam->virtual_cameras[i].vnodes[j].vdev); + + v4l2_device_unregister(&vcam->virtual_cameras[i].v4l2_dev); + } + + virtio_break_device(vdev); + vdev->config->reset(vdev); +} + +static const unsigned int features[] = { + /* none */ +}; + +static const struct virtio_device_id id_table[] = { + { VIRTIO_ID_CAMERA, VIRTIO_DEV_ANY_ID }, + {}, +}; +MODULE_DEVICE_TABLE(virtio, id_table); + +static struct virtio_driver virtio_camera_driver = { + .feature_table_size = ARRAY_SIZE(features), + .feature_table = features, + .probe = virtio_camera_probe, + .remove = virtio_camera_remove, + .driver.name = "virtio-camera", + .id_table = id_table, +}; +module_virtio_driver(virtio_camera_driver); + +MODULE_AUTHOR("Dmitry Osipenko "); +MODULE_AUTHOR("Zhangwei6 "); +MODULE_DESCRIPTION("virtio camera device driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index 2d572f6c8ec8..26da6b18ce63 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -562,6 +562,22 @@ static __poll_t uio_poll(struct file *filep, poll_table *wait) return 0; } +#ifdef CONFIG_PCI_MSI +static long uio_ioctl(struct file *filep, unsigned int cmd, unsigned long arg) +{ + struct uio_listener *listener = filep->private_data; + struct uio_device *idev = listener->dev; + + if (!idev->info) + return -EIO; + + if (!idev->info->ioctl) + return -ENOTTY; + + return idev->info->ioctl(idev->info, cmd, arg, (unsigned long)filep); +} +#endif + static ssize_t uio_read(struct file *filep, char __user *buf, size_t count, loff_t *ppos) { @@ -823,6 +839,9 @@ static const struct file_operations uio_fops = { .write = uio_write, .mmap = uio_mmap, .poll = uio_poll, +#ifdef CONFIG_PCI_MSI + .unlocked_ioctl = uio_ioctl, +#endif .fasync = uio_fasync, .llseek = noop_llseek, }; diff --git a/drivers/uio/uio_pci_generic.c b/drivers/uio/uio_pci_generic.c index e03f9b532a96..1a3379211a59 100644 --- a/drivers/uio/uio_pci_generic.c +++ b/drivers/uio/uio_pci_generic.c @@ -23,16 +23,176 @@ #include #include #include +#ifdef CONFIG_PCI_MSI +#include +#include +#include +#endif #define DRIVER_VERSION "0.01.0" #define DRIVER_AUTHOR "Michael S. Tsirkin " #define DRIVER_DESC "Generic UIO driver for PCI 2.3 devices" +#ifdef CONFIG_PCI_MSI +struct uio_eventfd_info { + unsigned long user; + struct list_head list; + struct eventfd_ctx *evt; +}; + +struct uio_msix_vector_event { + int irq; + spinlock_t list_lock; + struct list_head evt_list_head; +}; + +struct uio_msix_info { + struct msix_entry *entries; + struct uio_msix_vector_event *vector_evts; + int nvecs; +}; +#endif + struct uio_pci_generic_dev { struct uio_info info; struct pci_dev *pdev; +#ifdef CONFIG_PCI_MSI + struct uio_msix_info msix_info; +#endif }; +#ifdef CONFIG_PCI_MSI +static irqreturn_t uio_msix_handler(int irq, void *arg) +{ + struct uio_eventfd_info *evt_info; + struct uio_msix_vector_event *vector_evt; + + vector_evt = (struct uio_msix_vector_event *)arg; + spin_lock(&vector_evt->list_lock); + list_for_each_entry(evt_info, &vector_evt->evt_list_head, list) { + eventfd_signal(evt_info->evt, 1); + } + spin_unlock(&vector_evt->list_lock); + return IRQ_HANDLED; +} + +static int map_msix_eventfd(struct uio_pci_generic_dev *gdev, + unsigned long user, int fd, int vector) +{ + struct eventfd_ctx *evt; + struct uio_eventfd_info *evt_info; + struct uio_msix_vector_event *vector_evt; + + /* Passing -1 is used to disable interrupt */ + if (fd < 0) { + pci_disable_msi(gdev->pdev); + return 0; + } + + if (vector >= gdev->msix_info.nvecs) + return -EINVAL; + + evt = eventfd_ctx_fdget(fd); + if (!evt) + return -EINVAL; + + evt_info = kzalloc(sizeof(struct uio_eventfd_info), GFP_KERNEL); + if (!evt_info) { + eventfd_ctx_put(evt); + return -ENOMEM; + } + + evt_info->evt = evt; + evt_info->user = user; + + vector_evt = &(gdev->msix_info.vector_evts[vector]); + + spin_lock(&vector_evt->list_lock); + list_add(&evt_info->list, &vector_evt->evt_list_head); + spin_unlock(&vector_evt->list_lock); + + return 0; +} + +static int uio_msi_ioctl(struct uio_info *info, unsigned int cmd, + unsigned long arg, unsigned long user) +{ + struct uio_pci_generic_dev *gdev; + struct uio_msix_data data; + int err = -EOPNOTSUPP; + + gdev = container_of(info, struct uio_pci_generic_dev, info); + + switch (cmd) { + case UIO_MSIX_DATA: { + if (copy_from_user(&data, (void __user *)arg, sizeof(data))) + return -EFAULT; + + err = map_msix_eventfd(gdev, user, data.fd, data.vector); + break; + } + default: + pr_warn("Not support ioctl cmd: 0x%x\n", cmd); + break; + } + + return err; +} + +static int pci_generic_init_msix(struct uio_pci_generic_dev *gdev) +{ + unsigned char *buffer; + int i, j, irq, nvecs, ret; + struct uio_msix_vector_event *vector_evt; + + nvecs = pci_msix_vec_count(gdev->pdev); + if (!nvecs) + return -EINVAL; + + buffer = devm_kzalloc(&gdev->pdev->dev, nvecs * (sizeof(struct msix_entry) + + sizeof(struct uio_msix_vector_event)), GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + gdev->msix_info.entries = (struct msix_entry *)buffer; + gdev->msix_info.vector_evts = (struct uio_msix_vector_event *) + ((unsigned char *)buffer + nvecs * sizeof(struct msix_entry)); + gdev->msix_info.nvecs = nvecs; + + for (i = 0; i < nvecs; ++i) + gdev->msix_info.entries[i].entry = i; + + ret = pci_enable_msix_exact(gdev->pdev, gdev->msix_info.entries, nvecs); + if (ret) { + pr_err("Failed to enable UIO MSI-X, ret = %d.\n", ret); + kfree(buffer); + return ret; + } + + for (i = 0; i < nvecs; ++i) { + irq = gdev->msix_info.entries[i].vector; + vector_evt = &gdev->msix_info.vector_evts[i]; + vector_evt->irq = irq; + INIT_LIST_HEAD(&vector_evt->evt_list_head); + spin_lock_init(&vector_evt->list_lock); + + ret = request_irq(irq, uio_msix_handler, 0, "UIO IRQ", vector_evt); + if (ret) { + + for (j = 0; j < i - 1; j++) { + free_irq(gdev->msix_info.entries[j].vector, + &gdev->msix_info.vector_evts[j]); + } + + kfree(buffer); + pci_disable_msix(gdev->pdev); + return ret; + } + } + return 0; +} +#endif + static inline struct uio_pci_generic_dev * to_uio_pci_generic_dev(struct uio_info *info) { @@ -43,6 +203,24 @@ static int release(struct uio_info *info, struct inode *inode) { struct uio_pci_generic_dev *gdev = to_uio_pci_generic_dev(info); +#ifdef CONFIG_PCI_MSI + int i; + struct uio_eventfd_info *evt_info, *next; + struct uio_msix_vector_event *vector_evt; + + for (i = 0; i < gdev->msix_info.nvecs; ++i) { + vector_evt = &gdev->msix_info.vector_evts[i]; + spin_lock(&vector_evt->list_lock); + list_for_each_entry_safe(evt_info, next, &vector_evt->evt_list_head, list) { + if (evt_info->user == user) { + list_del(&evt_info->list); + eventfd_ctx_put(evt_info->evt); + kfree(evt_info); + } + } + spin_unlock(&vector_evt->list_lock); + } +#endif /* * This driver is insecure when used with devices doing DMA, but some  * people (mis)use it with such devices. @@ -93,14 +271,23 @@ static int probe(struct pci_dev *pdev, gdev->info.name = "uio_pci_generic"; gdev->info.version = DRIVER_VERSION; gdev->info.release = release; +#ifdef CONFIG_PCI_MSI + gdev->info.ioctl = uio_msi_ioctl; +#endif gdev->pdev = pdev; if (pdev->irq && (pdev->irq != IRQ_NOTCONNECTED)) { gdev->info.irq = pdev->irq; gdev->info.irq_flags = IRQF_SHARED; gdev->info.handler = irqhandler; } else { +#ifdef CONFIG_PCI_MSI + err = pci_generic_init_msix(gdev); + if (!err) + dev_notice(&pdev->dev, "MSIX is enabled for UIO device.\n"); +#else dev_warn(&pdev->dev, "No IRQ assigned to device: " "no support for interrupts?\n"); +#endif } uiomem = &gdev->info.mem[0]; @@ -133,13 +320,50 @@ static int probe(struct pci_dev *pdev, ++uiomem; } - return devm_uio_register_device(&pdev->dev, &gdev->info); + err = devm_uio_register_device(&pdev->dev, &gdev->info); + if (err) + return err; + +#ifdef CONFIG_PCI_MSI + pci_set_drvdata(pdev, gdev); +#endif + + return 0; +} + +#ifdef CONFIG_PCI_MSI +static void remove(struct pci_dev *pdev) +{ + int i; + struct uio_eventfd_info *evt_info, *next; + struct uio_msix_vector_event *vector_evt; + struct uio_pci_generic_dev *gdev = pci_get_drvdata(pdev); + + if (gdev->msix_info.entries != NULL) { + for (i = 0; i < gdev->msix_info.nvecs; i++) { + vector_evt = &gdev->msix_info.vector_evts[i]; + spin_lock(&vector_evt->list_lock); + list_for_each_entry_safe(evt_info, next, &vector_evt->evt_list_head, list) { + list_del(&evt_info->list); + eventfd_ctx_put(evt_info->evt); + kfree(evt_info); + } + spin_unlock(&vector_evt->list_lock); + free_irq(vector_evt->irq, vector_evt); + } + pci_disable_msix(pdev); + kfree(gdev->msix_info.entries); + } } +#endif static struct pci_driver uio_pci_driver = { .name = "uio_pci_generic", .id_table = NULL, /* only dynamic id's */ .probe = probe, +#ifdef CONFIG_PCI_MSI + .remove = remove, +#endif }; module_pci_driver(uio_pci_driver); diff --git a/drivers/virtio/virtio_shmem.c b/drivers/virtio/virtio_shmem.c index e74b13789b69..992b69c4cb68 100644 --- a/drivers/virtio/virtio_shmem.c +++ b/drivers/virtio/virtio_shmem.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "virtio_shmem.h" @@ -94,7 +95,11 @@ static bool vi_reg_write(struct virtio_shmem_device *vi_dev, unsigned int reg, virt_wmb(); vi_dev->notify_peer(vi_dev, 0); - + /* delay a short time for BE to update config space */ + if (vi_dev->vdev.id.device == VIRTIO_ID_INPUT && + hypervisor_is_type(X86_HYPER_QNX)) { + mdelay(1); + } return true; } diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c index 75e7fa18fd71..4a03678c80a2 100644 --- a/fs/fuse/virtio_fs.c +++ b/fs/fuse/virtio_fs.c @@ -32,8 +32,9 @@ static DEFINE_MUTEX(virtio_fs_mutex); static LIST_HEAD(virtio_fs_instances); enum { - VQ_HIPRIO, - VQ_REQUEST + VQ_HIPRIO = 0, + /* TODO add VQ_NOTIFICATION according to the virtio 1.2 spec. */ + VQ_REQUEST = 1, }; #define VQ_NAME_LEN 24 @@ -51,6 +52,7 @@ struct virtio_fs_vq { long in_flight; struct completion in_flight_zero; /* No inflight requests */ char name[VQ_NAME_LEN]; + struct workqueue_struct *done_wq; } ____cacheline_aligned_in_smp; /* A virtio-fs device instance */ @@ -59,6 +61,7 @@ struct virtio_fs { struct list_head list; /* on virtio_fs_instances */ char *tag; struct virtio_fs_vq *vqs; + struct virtio_fs_vq * __percpu *vq_proxy; unsigned int nvqs; /* number of virtqueues */ unsigned int num_request_queues; /* number of request queues */ struct dax_device *dax_dev; @@ -666,7 +669,7 @@ static void virtio_fs_vq_done(struct virtqueue *vq) dev_dbg(&vq->vdev->dev, "%s %s\n", __func__, fsvq->name); - schedule_work(&fsvq->done_work); + queue_work(fsvq->done_wq, &fsvq->done_work); } static void virtio_fs_init_vq(struct virtio_fs_vq *fsvq, char *name, @@ -696,6 +699,7 @@ static int virtio_fs_setup_vqs(struct virtio_device *vdev, struct virtqueue **vqs; vq_callback_t **callbacks; const char **names; + struct irq_affinity desc = { .pre_vectors = 1, .nr_sets = 1, }; unsigned int i; int ret = 0; @@ -704,11 +708,14 @@ static int virtio_fs_setup_vqs(struct virtio_device *vdev, if (fs->num_request_queues == 0) return -EINVAL; + fs->num_request_queues = min_t(unsigned int, nr_cpu_ids, + fs->num_request_queues); + fs->nvqs = VQ_REQUEST + fs->num_request_queues; fs->vqs = kcalloc(fs->nvqs, sizeof(fs->vqs[VQ_HIPRIO]), GFP_KERNEL); if (!fs->vqs) return -ENOMEM; - + pr_debug("virtio-fs: number of vqs: %d\n", fs->nvqs); vqs = kmalloc_array(fs->nvqs, sizeof(vqs[VQ_HIPRIO]), GFP_KERNEL); callbacks = kmalloc_array(fs->nvqs, sizeof(callbacks[VQ_HIPRIO]), GFP_KERNEL); @@ -722,6 +729,8 @@ static int virtio_fs_setup_vqs(struct virtio_device *vdev, callbacks[VQ_HIPRIO] = virtio_fs_vq_done; virtio_fs_init_vq(&fs->vqs[VQ_HIPRIO], "hiprio", VQ_HIPRIO); names[VQ_HIPRIO] = fs->vqs[VQ_HIPRIO].name; + fs->vqs[VQ_HIPRIO].done_wq = alloc_workqueue("fs_done_wq-%u",WQ_MEM_RECLAIM | WQ_UNBOUND, + 0, VQ_HIPRIO); /* Initialize the requests virtqueues */ for (i = VQ_REQUEST; i < fs->nvqs; i++) { @@ -731,14 +740,30 @@ static int virtio_fs_setup_vqs(struct virtio_device *vdev, virtio_fs_init_vq(&fs->vqs[i], vq_name, VQ_REQUEST); callbacks[i] = virtio_fs_vq_done; names[i] = fs->vqs[i].name; + fs->vqs[i].done_wq = alloc_workqueue("fs_done_wq-%u", WQ_MEM_RECLAIM | WQ_UNBOUND, + 0, i - VQ_REQUEST); } - ret = virtio_find_vqs(vdev, fs->nvqs, vqs, callbacks, names, NULL); + ret = virtio_find_vqs(vdev, fs->nvqs, vqs, callbacks, names, &desc); if (ret < 0) goto out; - for (i = 0; i < fs->nvqs; i++) + fs->vq_proxy = alloc_percpu(struct virtio_fs_vq *); + for (i = 0; i < fs->nvqs; i++) { + const struct cpumask *mask; + unsigned int cpu; + fs->vqs[i].vq = vqs[i]; + if (i == VQ_HIPRIO) + continue; + + mask = vdev->config->get_vq_affinity(vdev, i); + for_each_cpu(cpu, mask) { + struct virtio_fs_vq **cpu_vq = per_cpu_ptr(fs->vq_proxy, cpu); + *cpu_vq = &fs->vqs[i]; + pr_debug("virtio-fs: map cpu %d to vq%d\n", cpu, i); + } + } virtio_fs_start_all_queues(fs); out: @@ -927,6 +952,20 @@ static void virtio_fs_stop_all_queues(struct virtio_fs *fs) } } +static void virtio_fs_stop_all_wqs(struct virtio_fs *fs) +{ + struct virtio_fs_vq *fsvq; + int i; + + for (i = 0; i < fs->nvqs; i++) { + fsvq = &fs->vqs[i]; + if (fsvq->done_wq) { + drain_workqueue(fsvq->done_wq); + destroy_workqueue(fsvq->done_wq); + } + } +} + static void virtio_fs_remove(struct virtio_device *vdev) { struct virtio_fs *fs = vdev->priv; @@ -937,7 +976,9 @@ static void virtio_fs_remove(struct virtio_device *vdev) virtio_fs_stop_all_queues(fs); virtio_fs_drain_all_queues_locked(fs); virtio_reset_device(vdev); + free_percpu(fs->vq_proxy); virtio_fs_cleanup_vqs(vdev); + virtio_fs_stop_all_wqs(fs); vdev->priv = NULL; /* Put device reference on virtio_fs object */ @@ -1236,7 +1277,6 @@ static void virtio_fs_wake_pending_and_unlock(struct fuse_iqueue *fiq, bool sync) __releases(fiq->lock) { - unsigned int queue_id = VQ_REQUEST; /* TODO multiqueue */ struct virtio_fs *fs; struct fuse_req *req; struct virtio_fs_vq *fsvq; @@ -1256,7 +1296,7 @@ __releases(fiq->lock) req->in.h.nodeid, req->in.h.len, fuse_len_args(req->args->out_numargs, req->args->out_args)); - fsvq = &fs->vqs[queue_id]; + fsvq = this_cpu_read(*fs->vq_proxy); ret = virtio_fs_enqueue_req(fsvq, req, false); if (ret < 0) { if (ret == -ENOMEM || ret == -ENOSPC) { diff --git a/include/linux/uio_driver.h b/include/linux/uio_driver.h index 7f4e6c71c5d5..f0763397120f 100644 --- a/include/linux/uio_driver.h +++ b/include/linux/uio_driver.h @@ -47,6 +47,14 @@ struct uio_mem { #define MAX_UIO_MAPS 5 +#ifdef CONFIG_PCI_MSI +struct uio_msix_data { + int fd; + int vector; +}; +#define UIO_MSIX_DATA _IOW('u', 100, struct uio_msix_data) +#endif + struct uio_portio; /** @@ -112,6 +120,10 @@ struct uio_info { int (*open)(struct uio_info *info, struct inode *inode); int (*release)(struct uio_info *info, struct inode *inode); int (*irqcontrol)(struct uio_info *info, s32 irq_on); +#ifdef CONFIG_PCI_MSI + int (*ioctl)(struct uio_info *info, unsigned int cmd, + unsigned long arg, unsigned long user); +#endif ANDROID_KABI_RESERVE(1); }; diff --git a/include/uapi/linux/virtio_camera.h b/include/uapi/linux/virtio_camera.h new file mode 100644 index 000000000000..25b540bca7b3 --- /dev/null +++ b/include/uapi/linux/virtio_camera.h @@ -0,0 +1,92 @@ +/* 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 + +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_FILE_OPEN, + VIRTIO_CAMERA_CMD_FILE_CLOSE, + VIRTIO_CAMERA_CMD_ENUM_INTV, + + 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 { + __u8 name[256]; + __le32 num_virtual_cameras; + __le32 nr_per_virtual_camera[16]; +}; + +struct virtio_camera_mem_entry { + __le64 addr; + __le32 length; +}; + +struct virtio_camera_ctrl_hdr { + __le32 cmd; + __le32 index; +}; + +struct virtio_camera_format_size { + union { + __le32 min_width; + __le32 width; + }; + __le32 max_width; + __le32 step_width; + + union { + __le32 min_height; + __le32 height; + }; + __le32 max_height; + __le32 step_height; + __le32 stride; + __le32 sizeimage; + __le32 fps; +}; + +struct virtio_camera_req_format { + __le32 pixelformat; + struct virtio_camera_format_size size; +}; + +struct virtio_camera_req_buffer { + __le32 num_entries; + __u8 uuid[16]; + __le32 sequence; + __le64 timestamp; +}; + +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; + __le64 padding[3]; + } u; +}; + +#endif diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h index 2a974ca0c9fd..5f7074540bb5 100644 --- a/include/uapi/linux/virtio_ids.h +++ b/include/uapi/linux/virtio_ids.h @@ -69,6 +69,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 */ #define VIRTIO_ID_SPI 45 /* virtio spi */ /*