// SPDX-License-Identifier: GPL-2.0+
/* Driver for virtio video device.
*
* Copyright 2020 OpenSynergy GmbH.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
#include
#include
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,10,0)
#include
#else
#include
#endif
#include "virtio_video.h"
static unsigned int debug;
module_param(debug, uint, 0644);
static unsigned int use_dma_mem;
module_param(use_dma_mem, uint, 0644);
MODULE_PARM_DESC(use_dma_mem, "Try to allocate buffers from the DMA zone");
static int vid_nr_dec = -1;
module_param(vid_nr_dec, int, 0644);
MODULE_PARM_DESC(vid_nr_dec, "videoN start number, -1 is autodetect");
static int vid_nr_enc = -1;
module_param(vid_nr_enc, int, 0644);
MODULE_PARM_DESC(vid_nr_enc, "videoN start number, -1 is autodetect");
static int vid_nr_cam = -1;
module_param(vid_nr_cam, int, 0644);
MODULE_PARM_DESC(vid_nr_cam, "videoN start number, -1 is autodetect");
static bool mplane_cam = true;
module_param(mplane_cam, bool, 0644);
MODULE_PARM_DESC(mplane_cam,
"1 (default) - multiplanar camera, 0 - single planar camera");
static int virtio_video_probe(struct virtio_device *vdev)
{
int ret;
struct virtio_video_device *vvd;
struct virtqueue *vqs[2];
struct device *dev = &vdev->dev;
struct device *pdev = dev->parent;
static const char * const names[] = { "commandq", "eventq" };
static vq_callback_t *callbacks[] = {
virtio_video_cmd_cb,
virtio_video_event_cb
};
if (!virtio_has_feature(vdev, VIRTIO_VIDEO_F_RESOURCE_GUEST_PAGES)) {
dev_err(dev, "device must support guest allocated buffers\n");
return -ENODEV;
}
vvd = devm_kzalloc(dev, sizeof(*vvd), GFP_KERNEL);
if (!vvd)
return -ENOMEM;
vvd->is_m2m_dev = true;
switch (vdev->id.device) {
case VIRTIO_ID_VIDEO_CAM:
vvd->is_m2m_dev = false;
vvd->vid_dev_nr = vid_nr_cam;
vvd->is_mplane_cam = mplane_cam;
vvd->type = VIRTIO_VIDEO_DEVICE_CAMERA;
break;
case VIRTIO_ID_VIDEO_ENC:
vvd->vid_dev_nr = vid_nr_enc;
vvd->type = VIRTIO_VIDEO_DEVICE_ENCODER;
break;
case VIRTIO_ID_VIDEO_DEC:
default:
vvd->vid_dev_nr = vid_nr_dec;
vvd->type = VIRTIO_VIDEO_DEVICE_DECODER;
break;
}
vvd->vdev = vdev;
vvd->debug = debug;
vvd->use_dma_mem = use_dma_mem;
vdev->priv = vvd;
spin_lock_init(&vvd->pending_buf_list_lock);
spin_lock_init(&vvd->resource_idr_lock);
idr_init(&vvd->resource_idr);
spin_lock_init(&vvd->stream_idr_lock);
idr_init(&vvd->stream_idr);
init_waitqueue_head(&vvd->wq);
if (virtio_has_feature(vdev, VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG))
vvd->supp_non_contig = true;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,9,0)
vvd->has_iommu = !virtio_has_dma_quirk(vdev);
#else
vvd->has_iommu = !virtio_has_iommu_quirk(vdev);
#endif
if (!dev->dma_ops)
set_dma_ops(dev, pdev->dma_ops);
/*
* Set it to coherent_dma_mask by default if the architecture
* code has not set it.
*/
if (!dev->dma_mask)
dev->dma_mask = &dev->coherent_dma_mask;
dma_set_mask(dev, *pdev->dma_mask);
dev_set_name(dev, "%s.%i", DRIVER_NAME, vdev->index);
ret = v4l2_device_register(dev, &vvd->v4l2_dev);
if (ret)
goto err_v4l2_reg;
spin_lock_init(&vvd->commandq.qlock);
init_waitqueue_head(&vvd->commandq.reclaim_queue);
INIT_WORK(&vvd->eventq.work, virtio_video_process_events);
INIT_LIST_HEAD(&vvd->pending_vbuf_list);
ret = virtio_find_vqs(vdev, 2, vqs, callbacks, names, NULL);
if (ret) {
v4l2_err(&vvd->v4l2_dev, "failed to find virt queues\n");
goto err_vqs;
}
vvd->commandq.vq = vqs[0];
vvd->eventq.vq = vqs[1];
ret = virtio_video_alloc_vbufs(vvd);
if (ret) {
v4l2_err(&vvd->v4l2_dev, "failed to alloc vbufs\n");
goto err_vbufs;
}
virtio_cread(vdev, struct virtio_video_config, max_caps_length,
&vvd->max_caps_len);
if (!vvd->max_caps_len) {
v4l2_err(&vvd->v4l2_dev, "max_caps_len is zero\n");
ret = -EINVAL;
goto err_config;
}
virtio_cread(vdev, struct virtio_video_config, max_resp_length,
&vvd->max_resp_len);
if (!vvd->max_resp_len) {
v4l2_err(&vvd->v4l2_dev, "max_resp_len is zero\n");
ret = -EINVAL;
goto err_config;
}
ret = virtio_video_alloc_events(vvd);
if (ret)
goto err_events;
virtio_device_ready(vdev);
vvd->commandq.ready = true;
vvd->eventq.ready = true;
ret = virtio_video_device_init(vvd);
if (ret) {
v4l2_err(&vvd->v4l2_dev,
"failed to init virtio video\n");
goto err_init;
}
return 0;
err_init:
err_events:
err_config:
virtio_video_free_vbufs(vvd);
err_vbufs:
vdev->config->del_vqs(vdev);
err_vqs:
v4l2_device_unregister(&vvd->v4l2_dev);
err_v4l2_reg:
devm_kfree(&vdev->dev, vvd);
return ret;
}
static void virtio_video_remove(struct virtio_device *vdev)
{
struct virtio_video_device *vvd = vdev->priv;
virtio_video_device_deinit(vvd);
virtio_video_free_vbufs(vvd);
vdev->config->del_vqs(vdev);
v4l2_device_unregister(&vvd->v4l2_dev);
devm_kfree(&vdev->dev, vvd);
}
static struct virtio_device_id id_table[] = {
{ VIRTIO_ID_VIDEO_DEC, VIRTIO_DEV_ANY_ID },
{ VIRTIO_ID_VIDEO_ENC, VIRTIO_DEV_ANY_ID },
{ VIRTIO_ID_VIDEO_CAM, VIRTIO_DEV_ANY_ID },
{ 0 },
};
static unsigned int features[] = {
VIRTIO_VIDEO_F_RESOURCE_GUEST_PAGES,
VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG,
};
static struct virtio_driver virtio_video_driver = {
.feature_table = features,
.feature_table_size = ARRAY_SIZE(features),
.driver.name = DRIVER_NAME,
.driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = virtio_video_probe,
.remove = virtio_video_remove,
};
module_virtio_driver(virtio_video_driver);
MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_DESCRIPTION("virtio video driver");
MODULE_AUTHOR("Dmitry Sepp ");
MODULE_AUTHOR("Kiran Pawar ");
MODULE_AUTHOR("Nikolay Martyanov ");
MODULE_AUTHOR("Samiullah Khawaja ");
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL");