// 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
#include
#include
#include "virtio_video.h"
enum video_stream_state virtio_video_state(struct virtio_video_stream *stream)
{
return atomic_read(&stream->state);
}
void virtio_video_state_reset(struct virtio_video_stream *stream)
{
atomic_set(&stream->state, STREAM_STATE_IDLE);
}
void virtio_video_state_update(struct virtio_video_stream *stream,
enum video_stream_state new_state)
{
enum video_stream_state prev_state;
do {
prev_state = atomic_read(&stream->state);
if (prev_state == STREAM_STATE_ERROR)
return;
} while (atomic_cmpxchg(&stream->state, prev_state, new_state) !=
prev_state);
}
int virtio_video_pending_buf_list_empty(struct virtio_video_device *vvd)
{
int ret = 0;
if (vvd->is_m2m_dev) {
v4l2_err(&vvd->v4l2_dev, "Unexpected call for m2m device!\n");
return -EPERM;
}
spin_lock(&vvd->pending_buf_list_lock);
if (list_empty(&vvd->pending_buf_list))
ret = 1;
spin_unlock(&vvd->pending_buf_list_lock);
return ret;
}
int virtio_video_pending_buf_list_pop(struct virtio_video_device *vvd,
struct virtio_video_buffer **virtio_vb)
{
struct virtio_video_buffer *retbuf;
if (vvd->is_m2m_dev) {
v4l2_err(&vvd->v4l2_dev, "Unexpected call for m2m device!\n");
return -EPERM;
}
spin_lock(&vvd->pending_buf_list_lock);
if (list_empty(&vvd->pending_buf_list)) {
spin_unlock(&vvd->pending_buf_list_lock);
return -EAGAIN;
}
retbuf = list_first_entry(&vvd->pending_buf_list,
struct virtio_video_buffer, list);
spin_unlock(&vvd->pending_buf_list_lock);
*virtio_vb = retbuf;
return 0;
}
int virtio_video_pending_buf_list_add(struct virtio_video_device *vvd,
struct virtio_video_buffer *virtio_vb)
{
if (vvd->is_m2m_dev) {
v4l2_err(&vvd->v4l2_dev, "Unexpected call for m2m device!\n");
return -EPERM;
}
spin_lock(&vvd->pending_buf_list_lock);
list_add_tail(&virtio_vb->list, &vvd->pending_buf_list);
spin_unlock(&vvd->pending_buf_list_lock);
return 0;
}
int virtio_video_pending_buf_list_del(struct virtio_video_device *vvd,
struct virtio_video_buffer *virtio_vb)
{
struct virtio_video_buffer *vb, *vb_tmp;
int ret = -EINVAL;
if (vvd->is_m2m_dev) {
v4l2_err(&vvd->v4l2_dev, "Unexpected call for m2m device!\n");
return -EPERM;
}
spin_lock(&vvd->pending_buf_list_lock);
if (list_empty(&vvd->pending_buf_list)) {
spin_unlock(&vvd->pending_buf_list_lock);
return -EAGAIN;
}
list_for_each_entry_safe(vb, vb_tmp, &vvd->pending_buf_list, list) {
if (vb->resource_id == virtio_vb->resource_id) {
list_del(&vb->list);
ret = 0;
break;
}
}
spin_unlock(&vvd->pending_buf_list_lock);
return ret;
}
int virtio_video_queue_setup(struct vb2_queue *vq, unsigned int *num_buffers,
unsigned int *num_planes, unsigned int sizes[],
struct device *alloc_devs[])
{
int i;
struct virtio_video_stream *stream = vb2_get_drv_priv(vq);
struct video_format_info *p_info;
if (virtio_video_state(stream) == STREAM_STATE_ERROR)
return -EIO;
if (*num_planes)
return 0;
if (V4L2_TYPE_IS_OUTPUT(vq->type))
p_info = &stream->in_info;
else
p_info = &stream->out_info;
*num_planes = p_info->num_planes;
for (i = 0; i < p_info->num_planes; i++)
sizes[i] = p_info->plane_format[i].plane_size;
return 0;
}
static unsigned int
build_virtio_video_sglist_contig(struct virtio_video_resource_sg_list *sgl,
struct vb2_buffer *vb, unsigned int plane)
{
sgl->entries[0].addr = cpu_to_le64(vb2_dma_contig_plane_dma_addr(vb, plane));
sgl->entries[0].length = cpu_to_le32(vb->planes[plane].length);
sgl->num_entries = 1;
return VIRTIO_VIDEO_RESOURCE_SG_SIZE(1);
}
static unsigned int
build_virtio_video_sglist(struct virtio_video_resource_sg_list *sgl,
struct vb2_buffer *vb, unsigned int plane,
bool has_iommu)
{
int i;
struct scatterlist *sg;
struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, plane);
for_each_sg(sgt->sgl, sg, sgt->nents, i) {
sgl->entries[i].addr = cpu_to_le64(has_iommu
? sg_dma_address(sg)
: sg_phys(sg));
sgl->entries[i].length = cpu_to_le32(sg->length);
}
sgl->num_entries = sgt->nents;
return VIRTIO_VIDEO_RESOURCE_SG_SIZE(sgt->nents);
}
int virtio_video_buf_init(struct vb2_buffer *vb)
{
int ret = 0;
void *buf;
size_t buf_size = 0;
struct virtio_video_resource_sg_list *sg_list;
unsigned int i, offset = 0, resource_id, nents = 0;
struct vb2_queue *vq = vb->vb2_queue;
enum v4l2_buf_type queue_type = vq->type;
struct virtio_video_stream *stream = vb2_get_drv_priv(vq);
struct virtio_video_buffer *virtio_vb = to_virtio_vb(vb);
struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
if (vvd->supp_non_contig) {
for (i = 0; i < vb->num_planes; i++) {
nents = vb2_dma_sg_plane_desc(vb, i)->nents;
buf_size += VIRTIO_VIDEO_RESOURCE_SG_SIZE(nents);
}
buf = kcalloc(1, buf_size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
for (i = 0; i < vb->num_planes; i++) {
sg_list = buf + offset;
offset += build_virtio_video_sglist(sg_list, vb, i,
vvd->has_iommu);
}
} else {
buf_size = vb->num_planes * VIRTIO_VIDEO_RESOURCE_SG_SIZE(nents);
buf = kcalloc(1, buf_size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
for (i = 0; i < vb->num_planes; i++) {
sg_list = buf + offset;
offset += build_virtio_video_sglist_contig(sg_list,
vb, i);
}
}
virtio_video_resource_id_get(vvd, &resource_id);
ret = virtio_video_cmd_resource_attach(vvd, stream->stream_id,
resource_id,
to_virtio_queue_type(queue_type),
buf, buf_size);
if (ret) {
virtio_video_resource_id_put(vvd, resource_id);
kfree(buf);
return ret;
}
virtio_vb->queued = false;
virtio_vb->resource_id = resource_id;
return 0;
}
void virtio_video_buf_cleanup(struct vb2_buffer *vb)
{
struct virtio_video_stream *stream = vb2_get_drv_priv(vb->vb2_queue);
struct virtio_video_buffer *virtio_vb = to_virtio_vb(vb);
struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
virtio_video_resource_id_put(vvd, virtio_vb->resource_id);
}
void virtio_video_buf_queue(struct vb2_buffer *vb)
{
int i, ret;
struct virtio_video_buffer *virtio_vb;
uint32_t data_size[VB2_MAX_PLANES] = {0};
struct virtio_video_stream *stream = vb2_get_drv_priv(vb->vb2_queue);
struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
for (i = 0; i < vb->num_planes; ++i)
data_size[i] = vb->planes[i].bytesused;
virtio_vb = to_virtio_vb(vb);
if (!vvd->is_m2m_dev)
virtio_video_pending_buf_list_add(vvd, virtio_vb);
ret = virtio_video_cmd_resource_queue(vvd, stream->stream_id,
virtio_vb, data_size,
vb->num_planes,
to_virtio_queue_type(vb->type));
if (ret) {
v4l2_err(&vvd->v4l2_dev, "failed to queue buffer\n");
return;
}
virtio_vb->queued = true;
}
int virtio_video_qbuf(struct file *file, void *priv,
struct v4l2_buffer *buf)
{
struct virtio_video_stream *stream = file2stream(file);
struct virtio_video_device *vvd = video_drvdata(file);
if (virtio_video_state(stream) == STREAM_STATE_ERROR)
return -EIO;
if (vvd->is_m2m_dev)
return v4l2_m2m_ioctl_qbuf(file, priv, buf);
return vb2_ioctl_qbuf(file, priv, buf);
}
int virtio_video_dqbuf(struct file *file, void *priv,
struct v4l2_buffer *buf)
{
struct virtio_video_stream *stream = file2stream(file);
struct virtio_video_device *vvd = video_drvdata(file);
if (virtio_video_state(stream) == STREAM_STATE_ERROR)
return -EIO;
if (vvd->is_m2m_dev)
return v4l2_m2m_ioctl_dqbuf(file, priv, buf);
return vb2_ioctl_dqbuf(file, priv, buf);
}
int virtio_video_querycap(struct file *file, void *fh,
struct v4l2_capability *cap)
{
struct video_device *video_dev = video_devdata(file);
struct virtio_video_device *vvd = video_drvdata(file);
if (strscpy(cap->driver, DRIVER_NAME, sizeof(cap->driver)) < 0)
v4l2_err(&vvd->v4l2_dev, "failed to copy driver name\n");
if (strscpy(cap->card, video_dev->name, sizeof(cap->card)) < 0)
v4l2_err(&vvd->v4l2_dev, "failed to copy card name\n");
snprintf(cap->bus_info, sizeof(cap->bus_info), "virtio:%s",
video_dev->name);
cap->device_caps = video_dev->device_caps;
return 0;
}
int virtio_video_enum_framesizes(struct file *file, void *fh,
struct v4l2_frmsizeenum *f)
{
struct virtio_video_stream *stream = file2stream(file);
struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
struct video_format *fmt;
struct video_format_frame *frm;
struct virtio_video_format_frame *frame;
int idx = f->index;
fmt = virtio_video_find_video_format(&vvd->input_fmt_list,
f->pixel_format);
if (fmt == NULL)
fmt = virtio_video_find_video_format(&vvd->output_fmt_list,
f->pixel_format);
if (fmt == NULL)
return -EINVAL;
if (idx >= fmt->desc.num_frames)
return -EINVAL;
frm = &fmt->frames[idx];
frame = &frm->frame;
if (frame->width.min == frame->width.max &&
frame->height.min == frame->height.max) {
f->type = V4L2_FRMSIZE_TYPE_DISCRETE;
f->discrete.width = frame->width.min;
f->discrete.height = frame->height.min;
return 0;
}
f->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
f->stepwise.min_width = frame->width.min;
f->stepwise.max_width = frame->width.max;
f->stepwise.min_height = frame->height.min;
f->stepwise.max_height = frame->height.max;
f->stepwise.step_width = frame->width.step;
f->stepwise.step_height = frame->height.step;
return 0;
}
static bool in_stepped_interval(struct virtio_video_format_range range,
uint32_t point)
{
if (point < range.min || point > range.max)
return false;
if (range.step == 0 && range.min == range.max && range.min == point)
return true;
if (range.step != 0 && (point - range.min) % range.step == 0)
return true;
return false;
}
int virtio_video_enum_framemintervals(struct file *file, void *fh,
struct v4l2_frmivalenum *f)
{
struct virtio_video_stream *stream = file2stream(file);
struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
struct video_format *fmt;
struct video_format_frame *frm;
struct virtio_video_format_frame *frame;
struct virtio_video_format_range *frate;
int idx = f->index;
int f_idx;
fmt = virtio_video_find_video_format(&vvd->input_fmt_list,
f->pixel_format);
if (fmt == NULL)
fmt = virtio_video_find_video_format(&vvd->output_fmt_list,
f->pixel_format);
if (fmt == NULL)
return -EINVAL;
for (f_idx = 0; f_idx <= fmt->desc.num_frames; f_idx++) {
frm = &fmt->frames[f_idx];
frame = &frm->frame;
if (in_stepped_interval(frame->width, f->width) &&
in_stepped_interval(frame->height, f->height))
break;
}
if (frame == NULL || f->index >= frame->num_rates)
return -EINVAL;
frate = &frm->frame_rates[idx];
if (frate->max == frate->min) {
f->type = V4L2_FRMIVAL_TYPE_DISCRETE;
f->discrete.numerator = 1;
f->discrete.denominator = frate->max;
} else {
f->stepwise.min.numerator = 1;
f->stepwise.min.denominator = frate->max;
f->stepwise.max.numerator = 1;
f->stepwise.max.denominator = frate->min;
f->stepwise.step.numerator = 1;
f->stepwise.step.denominator = frate->step;
if (frate->step == 1)
f->type = V4L2_FRMIVAL_TYPE_CONTINUOUS;
else
f->type = V4L2_FRMIVAL_TYPE_STEPWISE;
}
return 0;
}
int virtio_video_stream_get_params(struct virtio_video_device *vvd,
struct virtio_video_stream *stream)
{
int ret;
if (vvd->is_m2m_dev) {
ret = virtio_video_cmd_get_params(vvd, stream,
VIRTIO_VIDEO_QUEUE_TYPE_INPUT);
if (ret) {
v4l2_err(&vvd->v4l2_dev,
"failed to get stream in params\n");
goto err_get_parms;
}
}
ret = virtio_video_cmd_get_params(vvd, stream,
VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT);
if (ret)
v4l2_err(&vvd->v4l2_dev, "failed to get stream out params\n");
err_get_parms:
return ret;
}
int virtio_video_stream_get_controls(struct virtio_video_device *vvd,
struct virtio_video_stream *stream)
{
int ret;
ret = virtio_video_cmd_get_control(vvd, stream,
VIRTIO_VIDEO_CONTROL_PROFILE);
if (ret) {
v4l2_err(&vvd->v4l2_dev, "failed to get stream profile\n");
goto err_get_ctrl;
}
ret = virtio_video_cmd_get_control(vvd, stream,
VIRTIO_VIDEO_CONTROL_LEVEL);
if (ret) {
v4l2_err(&vvd->v4l2_dev, "failed to get stream level\n");
goto err_get_ctrl;
}
ret = virtio_video_cmd_get_control(vvd, stream,
VIRTIO_VIDEO_CONTROL_BITRATE);
if (ret)
v4l2_err(&vvd->v4l2_dev, "failed to get stream bitrate\n");
err_get_ctrl:
return ret;
}
int virtio_video_g_fmt(struct file *file, void *fh, struct v4l2_format *f)
{
struct video_format_info *info;
struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
struct virtio_video_stream *stream = file2stream(file);
if (!V4L2_TYPE_IS_OUTPUT(f->type))
info = &stream->out_info;
else
info = &stream->in_info;
virtio_video_format_from_info(info, pix_mp);
return 0;
}
int virtio_video_s_fmt(struct file *file, void *fh, struct v4l2_format *f)
{
int i, ret;
struct virtio_video_stream *stream = file2stream(file);
struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
struct video_format_info info;
struct video_format_info *p_info;
uint32_t queue;
ret = virtio_video_try_fmt(stream, f);
if (ret)
return ret;
if (V4L2_TYPE_IS_OUTPUT(f->type)) {
virtio_video_format_fill_default_info(&info, &stream->in_info);
queue = VIRTIO_VIDEO_QUEUE_TYPE_INPUT;
} else {
virtio_video_format_fill_default_info(&info, &stream->out_info);
queue = VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT;
}
info.frame_width = pix_mp->width;
info.frame_height = pix_mp->height;
info.num_planes = pix_mp->num_planes;
info.fourcc_format = pix_mp->pixelformat;
for (i = 0; i < info.num_planes; i++) {
info.plane_format[i].stride =
pix_mp->plane_fmt[i].bytesperline;
info.plane_format[i].plane_size =
pix_mp->plane_fmt[i].sizeimage;
}
virtio_video_cmd_set_params(vvd, stream, &info, queue);
virtio_video_stream_get_params(vvd, stream);
if (V4L2_TYPE_IS_OUTPUT(f->type))
p_info = &stream->in_info;
else
p_info = &stream->out_info;
virtio_video_format_from_info(p_info, pix_mp);
return 0;
}
int virtio_video_g_selection(struct file *file, void *fh,
struct v4l2_selection *sel)
{
struct video_format_info *info;
struct virtio_video_stream *stream = file2stream(file);
struct virtio_video_device *vvd = video_drvdata(file);
switch (vvd->type) {
case VIRTIO_VIDEO_DEVICE_ENCODER:
if (!V4L2_TYPE_IS_OUTPUT(sel->type))
return -EINVAL;
info = &stream->in_info;
break;
case VIRTIO_VIDEO_DEVICE_DECODER:
case VIRTIO_VIDEO_DEVICE_CAMERA:
if (V4L2_TYPE_IS_OUTPUT(sel->type))
return -EINVAL;
info = &stream->out_info;
break;
default:
v4l2_err(&vvd->v4l2_dev, "unsupported device type\n");
return -EINVAL;
}
switch (sel->target) {
case V4L2_SEL_TGT_COMPOSE:
case V4L2_SEL_TGT_COMPOSE_BOUNDS:
case V4L2_SEL_TGT_COMPOSE_PADDED:
case V4L2_SEL_TGT_COMPOSE_DEFAULT:
sel->r.width = info->frame_width;
sel->r.height = info->frame_height;
break;
case V4L2_SEL_TGT_CROP_BOUNDS:
case V4L2_SEL_TGT_CROP_DEFAULT:
case V4L2_SEL_TGT_CROP:
sel->r.top = info->crop.top;
sel->r.left = info->crop.left;
sel->r.width = info->frame_width;
sel->r.height = info->frame_height;
break;
default:
v4l2_dbg(1, vvd->debug, &vvd->v4l2_dev,
"unsupported/invalid selection target: %d\n",
sel->target);
return -EINVAL;
}
return 0;
}
int virtio_video_try_fmt(struct virtio_video_stream *stream,
struct v4l2_format *f)
{
int i, idx = 0;
struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
struct video_format *fmt;
bool found = false;
struct video_format_frame *frm;
struct virtio_video_format_frame *frame;
if (virtio_video_state(stream) == STREAM_STATE_ERROR)
return -EIO;
if (V4L2_TYPE_IS_OUTPUT(f->type))
fmt = virtio_video_find_video_format(&vvd->input_fmt_list,
pix_mp->pixelformat);
else
fmt = virtio_video_find_video_format(&vvd->output_fmt_list,
pix_mp->pixelformat);
if (!fmt) {
if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
virtio_video_format_from_info(&stream->out_info,
pix_mp);
else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
virtio_video_format_from_info(&stream->in_info,
pix_mp);
else
return -EINVAL;
return 0;
}
for (i = 0; i < fmt->desc.num_frames && !found; i++) {
frm = &fmt->frames[i];
frame = &frm->frame;
if (!within_range(frame->width.min, pix_mp->width,
frame->width.max))
continue;
if (!within_range(frame->height.min, pix_mp->height,
frame->height.max))
continue;
idx = i;
/*
* Try to find a more suitable frame size. Go with the current
* one otherwise.
*/
if (needs_alignment(pix_mp->width, frame->width.step))
continue;
if (needs_alignment(pix_mp->height, frame->height.step))
continue;
stream->current_frame = frm;
found = true;
}
if (!found) {
frm = &fmt->frames[idx];
if (!frm)
return -EINVAL;
frame = &frm->frame;
pix_mp->width = clamp(pix_mp->width, frame->width.min,
frame->width.max);
if (frame->width.step != 0)
pix_mp->width = ALIGN(pix_mp->width, frame->width.step);
pix_mp->height = clamp(pix_mp->height, frame->height.min,
frame->height.max);
if (frame->height.step != 0)
pix_mp->height = ALIGN(pix_mp->height,
frame->height.step);
stream->current_frame = frm;
}
return 0;
}
static int virtio_video_queue_free(struct virtio_video_device *vvd,
struct virtio_video_stream *stream,
enum v4l2_buf_type type)
{
int ret;
enum virtio_video_queue_type queue_type = to_virtio_queue_type(type);
ret = virtio_video_cmd_queue_detach_resources(vvd, stream, queue_type);
if (ret) {
v4l2_warn(&vvd->v4l2_dev,
"failed to destroy resources\n");
return ret;
}
return 0;
}
int virtio_video_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *rb)
{
int ret;
struct virtio_video_stream *stream = file2stream(file);
struct v4l2_m2m_ctx *m2m_ctx = stream->fh.m2m_ctx;
struct virtio_video_device *vvd = video_drvdata(file);
struct video_device *vdev = video_devdata(file);
struct vb2_queue *vq;
if (vvd->is_m2m_dev)
vq = v4l2_m2m_get_vq(m2m_ctx, rb->type);
else
vq = vdev->queue;
if (rb->count == 0) {
ret = virtio_video_queue_free(vvd, stream, vq->type);
if (ret < 0)
return ret;
}
if (vvd->is_m2m_dev)
return v4l2_m2m_reqbufs(file, m2m_ctx, rb);
else
return vb2_ioctl_reqbufs(file, priv, rb);
}
int virtio_video_subscribe_event(struct v4l2_fh *fh,
const struct v4l2_event_subscription *sub)
{
switch (sub->type) {
case V4L2_EVENT_SOURCE_CHANGE:
return v4l2_src_change_event_subscribe(fh, sub);
default:
return v4l2_ctrl_subscribe_event(fh, sub);
}
}
void virtio_video_queue_eos_event(struct virtio_video_stream *stream)
{
static const struct v4l2_event eos_event = {
.type = V4L2_EVENT_EOS
};
v4l2_event_queue_fh(&stream->fh, &eos_event);
}
void virtio_video_queue_res_chg_event(struct virtio_video_stream *stream)
{
static const struct v4l2_event ev_src_ch = {
.type = V4L2_EVENT_SOURCE_CHANGE,
.u.src_change.changes =
V4L2_EVENT_SRC_CH_RESOLUTION,
};
v4l2_event_queue_fh(&stream->fh, &ev_src_ch);
}
void virtio_video_handle_error(struct virtio_video_stream *stream)
{
struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
if (vvd->is_m2m_dev)
virtio_video_queue_release_buffers
(stream, VIRTIO_VIDEO_QUEUE_TYPE_INPUT);
virtio_video_queue_release_buffers
(stream, VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT);
}
int virtio_video_queue_release_buffers(struct virtio_video_stream *stream,
enum virtio_video_queue_type queue_type)
{
int ret;
struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
struct vb2_v4l2_buffer *v4l2_vb;
struct virtio_video_buffer *vvb;
ret = virtio_video_cmd_queue_clear(vvd, stream, queue_type);
if (ret) {
v4l2_err(&vvd->v4l2_dev, "failed to clear queue\n");
return ret;
}
if (!vvd->is_m2m_dev) {
while (!virtio_video_pending_buf_list_pop(vvd, &vvb) && vvb) {
v4l2_vb = &vvb->v4l2_m2m_vb.vb;
v4l2_m2m_buf_done(v4l2_vb, VB2_BUF_STATE_ERROR);
}
return 0;
}
for (;;) {
if (queue_type == VIRTIO_VIDEO_QUEUE_TYPE_INPUT)
v4l2_vb = v4l2_m2m_src_buf_remove(stream->fh.m2m_ctx);
else
v4l2_vb = v4l2_m2m_dst_buf_remove(stream->fh.m2m_ctx);
if (!v4l2_vb)
break;
v4l2_m2m_buf_done(v4l2_vb, VB2_BUF_STATE_ERROR);
}
return 0;
}
void virtio_video_buf_done(struct virtio_video_buffer *virtio_vb,
uint32_t flags, uint64_t timestamp,
uint32_t data_sizes[])
{
int i;
enum vb2_buffer_state done_state = VB2_BUF_STATE_DONE;
struct vb2_v4l2_buffer *v4l2_vb = &virtio_vb->v4l2_m2m_vb.vb;
struct vb2_buffer *vb = &v4l2_vb->vb2_buf;
struct vb2_queue *vb2_queue = vb->vb2_queue;
struct virtio_video_stream *stream = vb2_get_drv_priv(vb2_queue);
struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
struct video_format_info *p_info;
virtio_vb->queued = false;
if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_ERR)
done_state = VB2_BUF_STATE_ERROR;
if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_KEY_FRAME)
v4l2_vb->flags |= V4L2_BUF_FLAG_KEYFRAME;
if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_BFRAME)
v4l2_vb->flags |= V4L2_BUF_FLAG_BFRAME;
if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_PFRAME)
v4l2_vb->flags |= V4L2_BUF_FLAG_PFRAME;
if (flags & VIRTIO_VIDEO_DEQUEUE_FLAG_EOS) {
v4l2_vb->flags |= V4L2_BUF_FLAG_LAST;
virtio_video_state_update(stream, STREAM_STATE_STOPPED);
virtio_video_queue_eos_event(stream);
}
if ((flags & VIRTIO_VIDEO_DEQUEUE_FLAG_ERR) ||
(flags & VIRTIO_VIDEO_DEQUEUE_FLAG_EOS)) {
vb->planes[0].bytesused = 0;
if (!vvd->is_m2m_dev)
virtio_video_pending_buf_list_del(vvd, virtio_vb);
v4l2_m2m_buf_done(v4l2_vb, done_state);
return;
}
if (!V4L2_TYPE_IS_OUTPUT(vb2_queue->type)) {
switch (vvd->type) {
case VIRTIO_VIDEO_DEVICE_ENCODER:
for (i = 0; i < vb->num_planes; i++)
vb->planes[i].bytesused =
le32_to_cpu(data_sizes[i]);
break;
case VIRTIO_VIDEO_DEVICE_CAMERA:
case VIRTIO_VIDEO_DEVICE_DECODER:
p_info = &stream->out_info;
for (i = 0; i < p_info->num_planes; i++)
vb->planes[i].bytesused =
p_info->plane_format[i].plane_size;
break;
}
vb->timestamp = timestamp;
}
if (!vvd->is_m2m_dev)
virtio_video_pending_buf_list_del(vvd, virtio_vb);
v4l2_m2m_buf_done(v4l2_vb, done_state);
}
static int virtio_video_set_device_busy(struct virtio_video_device *vvd)
{
struct video_device *vd = &vvd->video_dev;
int ret = 0;
/* Multiple open is allowed for m2m device */
if (vvd->is_m2m_dev)
return 0;
mutex_lock(vd->lock);
if (vvd->device_busy)
ret = -EBUSY;
else
vvd->device_busy = true;
mutex_unlock(vd->lock);
return ret;
}
static void virtio_video_clear_device_busy(struct virtio_video_device *vvd,
struct mutex *lock)
{
/* Nothing to do for m2m device */
if (vvd->is_m2m_dev)
return;
if (lock)
mutex_lock(lock);
vvd->device_busy = false;
if (lock)
mutex_unlock(lock);
}
static int virtio_video_device_open(struct file *file)
{
int ret;
uint32_t stream_id;
char name[TASK_COMM_LEN];
struct virtio_video_stream *stream;
struct video_format *default_fmt;
enum virtio_video_format format;
struct video_device *video_dev = video_devdata(file);
struct virtio_video_device *vvd = video_drvdata(file);
ret = virtio_video_set_device_busy(vvd);
if (ret) {
v4l2_err(&vvd->v4l2_dev, "device already in use.\n");
return ret;
}
default_fmt = list_first_entry_or_null(vvd->ops->get_fmt_list(vvd),
struct video_format,
formats_list_entry);
if (!default_fmt) {
v4l2_err(&vvd->v4l2_dev, "device failed to start\n");
ret = -EIO;
goto err;
}
stream = kzalloc(sizeof(*stream), GFP_KERNEL);
if (!stream) {
ret = -ENOMEM;
goto err;
}
get_task_comm(name, current);
format = virtio_video_v4l2_format_to_virtio(default_fmt->desc.format);
virtio_video_stream_id_get(vvd, stream, &stream_id);
ret = virtio_video_cmd_stream_create(vvd, stream_id, format, name);
if (ret) {
v4l2_err(&vvd->v4l2_dev, "failed to create stream\n");
goto err_stream_create;
}
stream->video_dev = video_dev;
stream->stream_id = stream_id;
virtio_video_state_reset(stream);
ret = virtio_video_stream_get_params(vvd, stream);
if (ret)
goto err_stream_create;
if (format >= VIRTIO_VIDEO_FORMAT_CODED_MIN &&
format <= VIRTIO_VIDEO_FORMAT_CODED_MAX) {
ret = virtio_video_stream_get_controls(vvd, stream);
if (ret)
goto err_stream_create;
}
mutex_init(&stream->vq_mutex);
v4l2_fh_init(&stream->fh, video_dev);
stream->fh.ctrl_handler = &stream->ctrl_handler;
if (vvd->is_m2m_dev) {
stream->fh.m2m_ctx = v4l2_m2m_ctx_init(vvd->m2m_dev, stream,
vvd->ops->init_queues);
if (IS_ERR(stream->fh.m2m_ctx)) {
ret = PTR_ERR(stream->fh.m2m_ctx);
goto err_init_ctx;
}
v4l2_m2m_set_src_buffered(stream->fh.m2m_ctx, true);
v4l2_m2m_set_dst_buffered(stream->fh.m2m_ctx, true);
} else {
vvd->ops->init_queues(stream, NULL, &vvd->vb2_output_queue);
/* Video dev queue is required for vb2 ioctl wrappers */
video_dev->queue = &vvd->vb2_output_queue;
}
file->private_data = &stream->fh;
v4l2_fh_add(&stream->fh);
if (vvd->ops->init_ctrls) {
ret = vvd->ops->init_ctrls(stream);
if (ret) {
v4l2_err(&vvd->v4l2_dev, "failed to init controls\n");
goto err_init_ctrls;
}
}
return 0;
err_init_ctrls:
v4l2_fh_del(&stream->fh);
mutex_lock(video_dev->lock);
if (vvd->is_m2m_dev)
v4l2_m2m_ctx_release(stream->fh.m2m_ctx);
mutex_unlock(video_dev->lock);
err_init_ctx:
v4l2_fh_exit(&stream->fh);
err_stream_create:
virtio_video_stream_id_put(vvd, stream_id);
kfree(stream);
err:
virtio_video_clear_device_busy(vvd, video_dev->lock);
return ret;
}
static int virtio_video_device_release(struct file *file)
{
struct virtio_video_stream *stream = file2stream(file);
struct video_device *video_dev = video_devdata(file);
struct virtio_video_device *vvd = video_drvdata(file);
mutex_lock(video_dev->lock);
v4l2_fh_del(&stream->fh);
if (vvd->is_m2m_dev) {
v4l2_m2m_ctx_release(stream->fh.m2m_ctx);
} else if (file->private_data == video_dev->queue->owner) {
vb2_queue_release(&vvd->vb2_output_queue);
video_dev->queue->owner = NULL;
}
v4l2_fh_exit(&stream->fh);
virtio_video_cmd_stream_destroy(vvd, stream->stream_id);
virtio_video_stream_id_put(vvd, stream->stream_id);
kfree(stream);
/* Mutex already locked here, passing NULL */
virtio_video_clear_device_busy(vvd, NULL);
mutex_unlock(video_dev->lock);
return 0;
}
static const struct v4l2_file_operations virtio_video_device_m2m_fops = {
.owner = THIS_MODULE,
.open = virtio_video_device_open,
.release = virtio_video_device_release,
.poll = v4l2_m2m_fop_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = v4l2_m2m_fop_mmap,
};
static const struct v4l2_file_operations virtio_video_device_fops = {
.owner = THIS_MODULE,
.open = virtio_video_device_open,
.release = virtio_video_device_release,
.poll = vb2_fop_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = vb2_fop_mmap,
};
static void virtio_video_device_run(void *priv)
{
}
static void virtio_video_device_job_abort(void *priv)
{
struct virtio_video_stream *stream = priv;
struct virtio_video_device *vvd = to_virtio_vd(stream->video_dev);
v4l2_m2m_job_finish(vvd->m2m_dev, stream->fh.m2m_ctx);
}
static const struct v4l2_m2m_ops virtio_video_device_m2m_ops = {
.device_run = virtio_video_device_run,
.job_abort = virtio_video_device_job_abort,
};
static int virtio_video_device_register(struct virtio_video_device *vvd)
{
int ret;
struct video_device *vd;
vd = &vvd->video_dev;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)
ret = video_register_device(vd, VFL_TYPE_VIDEO, vvd->vid_dev_nr);
#else
ret = video_register_device(vd, VFL_TYPE_GRABBER, vvd->vid_dev_nr);
#endif
if (ret) {
v4l2_err(&vvd->v4l2_dev, "failed to register video device\n");
return ret;
}
v4l2_info(&vvd->v4l2_dev, "Device '%s' registered as /dev/video%d\n",
vd->name, vd->num);
return 0;
}
static void virtio_video_device_unregister(struct virtio_video_device *vvd)
{
video_unregister_device(&vvd->video_dev);
}
static int
virtio_video_query_capability(struct virtio_video_device *vvd,
void *resp_buf,
enum virtio_video_queue_type queue_type)
{
int ret;
int resp_size = vvd->max_caps_len;
ret = virtio_video_cmd_query_capability(vvd, resp_buf, resp_size,
queue_type);
if (ret)
v4l2_err(&vvd->v4l2_dev, "failed to query capability\n");
return ret;
}
int virtio_video_device_init(struct virtio_video_device *vvd)
{
int ret;
int vfl_dir;
u32 dev_caps;
struct video_device *vd;
struct v4l2_m2m_dev *m2m_dev;
const struct v4l2_file_operations *fops;
void *input_resp_buf, *output_resp_buf;
output_resp_buf = kzalloc(vvd->max_caps_len, GFP_KERNEL);
if (!output_resp_buf)
return -ENOMEM;
ret = virtio_video_query_capability(vvd, output_resp_buf,
VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT);
if (ret) {
v4l2_err(&vvd->v4l2_dev, "failed to get output caps\n");
goto err_output_cap;
}
if (vvd->is_m2m_dev) {
input_resp_buf = kzalloc(vvd->max_caps_len, GFP_KERNEL);
if (!input_resp_buf) {
ret = -ENOMEM;
goto err_input_buf;
}
ret = virtio_video_query_capability(vvd, input_resp_buf,
VIRTIO_VIDEO_QUEUE_TYPE_INPUT);
if (ret) {
v4l2_err(&vvd->v4l2_dev, "failed to get input caps\n");
goto err_input_cap;
}
m2m_dev = v4l2_m2m_init(&virtio_video_device_m2m_ops);
if (IS_ERR(m2m_dev)) {
v4l2_err(&vvd->v4l2_dev, "failed to init m2m device\n");
ret = PTR_ERR(m2m_dev);
goto err_m2m_dev;
}
vfl_dir = VFL_DIR_M2M;
fops = &virtio_video_device_m2m_fops;
dev_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
} else {
input_resp_buf = NULL;
m2m_dev = NULL;
vfl_dir = VFL_DIR_RX;
fops = &virtio_video_device_fops;
dev_caps = V4L2_CAP_STREAMING;
if (vvd->is_mplane_cam)
dev_caps |= V4L2_CAP_VIDEO_CAPTURE_MPLANE;
else
dev_caps |= V4L2_CAP_VIDEO_CAPTURE;
}
vvd->m2m_dev = m2m_dev;
mutex_init(&vvd->video_dev_mutex);
vd = &vvd->video_dev;
vd->lock = &vvd->video_dev_mutex;
vd->v4l2_dev = &vvd->v4l2_dev;
vd->vfl_dir = vfl_dir;
vd->ioctl_ops = NULL;
vd->fops = fops;
vd->device_caps = dev_caps;
vd->release = video_device_release_empty;
/* Use the selection API instead */
v4l2_disable_ioctl(vd, VIDIOC_CROPCAP);
v4l2_disable_ioctl(vd, VIDIOC_G_CROP);
video_set_drvdata(vd, vvd);
INIT_LIST_HEAD(&vvd->input_fmt_list);
INIT_LIST_HEAD(&vvd->output_fmt_list);
INIT_LIST_HEAD(&vvd->controls_fmt_list);
INIT_LIST_HEAD(&vvd->pending_buf_list);
vvd->num_output_fmts = 0;
vvd->num_input_fmts = 0;
switch (vvd->type) {
case VIRTIO_VIDEO_DEVICE_CAMERA:
virtio_video_cam_init(vvd);
break;
case VIRTIO_VIDEO_DEVICE_ENCODER:
virtio_video_enc_init(vvd);
break;
case VIRTIO_VIDEO_DEVICE_DECODER:
default:
virtio_video_dec_init(vvd);
break;
}
ret = virtio_video_parse_virtio_capabilities(vvd, input_resp_buf,
output_resp_buf);
if (ret) {
v4l2_err(&vvd->v4l2_dev, "failed to parse a function\n");
goto parse_cap_err;
}
ret = virtio_video_parse_virtio_control(vvd);
if (ret) {
v4l2_err(&vvd->v4l2_dev, "failed to query controls\n");
goto parse_ctrl_err;
}
ret = virtio_video_device_register(vvd);
if (ret) {
v4l2_err(&vvd->v4l2_dev,
"failed to init virtio video device\n");
goto register_err;
}
goto out_cleanup;
register_err:
virtio_video_clean_control(vvd);
parse_ctrl_err:
virtio_video_clean_capability(vvd);
parse_cap_err:
if (vvd->is_m2m_dev)
v4l2_m2m_release(vvd->m2m_dev);
err_m2m_dev:
err_input_cap:
out_cleanup:
if (vvd->is_m2m_dev)
kfree(input_resp_buf);
err_input_buf:
err_output_cap:
kfree(output_resp_buf);
return ret;
}
void virtio_video_device_deinit(struct virtio_video_device *vvd)
{
vvd->commandq.ready = false;
vvd->eventq.ready = false;
virtio_video_device_unregister(vvd);
if (vvd->is_m2m_dev)
v4l2_m2m_release(vvd->m2m_dev);
virtio_video_clean_control(vvd);
virtio_video_clean_capability(vvd);
}