// SPDX-License-Identifier: GPL-2.0+ /* Driver for virtio video device. * * Copyright 2020 OpenSynergy GmbH. * * Based on drivers/gpu/drm/virtio/virtgpu_vq.c * Copyright (C) 2015 Red Hat, Inc. * * 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 "virtio_video.h" #define MAX_INLINE_CMD_SIZE 298 #define MAX_INLINE_RESP_SIZE 298 #define VBUFFER_SIZE (sizeof(struct virtio_video_vbuffer) \ + MAX_INLINE_CMD_SIZE \ + MAX_INLINE_RESP_SIZE) static int virtio_video_queue_event_buffer(struct virtio_video_device *vvd, struct virtio_video_event *evt); static void virtio_video_handle_event(struct virtio_video_device *vvd, struct virtio_video_event *evt); void virtio_video_resource_id_get(struct virtio_video_device *vvd, uint32_t *id) { int handle; idr_preload(GFP_KERNEL); spin_lock(&vvd->resource_idr_lock); handle = idr_alloc(&vvd->resource_idr, NULL, 1, 0, GFP_NOWAIT); spin_unlock(&vvd->resource_idr_lock); idr_preload_end(); *id = handle; } void virtio_video_resource_id_put(struct virtio_video_device *vvd, uint32_t id) { spin_lock(&vvd->resource_idr_lock); idr_remove(&vvd->resource_idr, id); spin_unlock(&vvd->resource_idr_lock); } void virtio_video_stream_id_get(struct virtio_video_device *vvd, struct virtio_video_stream *stream, uint32_t *id) { int handle; idr_preload(GFP_KERNEL); spin_lock(&vvd->stream_idr_lock); handle = idr_alloc(&vvd->stream_idr, stream, 1, 0, 0); spin_unlock(&vvd->stream_idr_lock); idr_preload_end(); *id = handle; } void virtio_video_stream_id_put(struct virtio_video_device *vvd, uint32_t id) { spin_lock(&vvd->stream_idr_lock); idr_remove(&vvd->stream_idr, id); spin_unlock(&vvd->stream_idr_lock); } static bool vbuf_is_pending(struct virtio_video_device *vvd, struct virtio_video_vbuffer *vbuf) { struct virtio_video_vbuffer *entry; list_for_each_entry(entry, &vvd->pending_vbuf_list, pending_list_entry) { if (entry == vbuf && entry->id == vbuf->id) return true; } return false; } static void free_vbuf(struct virtio_video_device *vvd, struct virtio_video_vbuffer *vbuf) { list_del(&vbuf->pending_list_entry); kfree(vbuf->data_buf); kmem_cache_free(vvd->vbufs, vbuf); } void virtio_video_cmd_cb(struct virtqueue *vq) { struct virtio_video_device *vvd = vq->vdev->priv; struct virtio_video_vbuffer *vbuf; unsigned long flags; unsigned int len; spin_lock_irqsave(&vvd->commandq.qlock, flags); while (vvd->commandq.ready) { virtqueue_disable_cb(vq); while ((vbuf = virtqueue_get_buf(vq, &len))) { if (!vbuf_is_pending(vvd, vbuf)) continue; if (vbuf->resp_cb) vbuf->resp_cb(vvd, vbuf); if (vbuf->is_sync) complete(&vbuf->reclaimed); else free_vbuf(vvd, vbuf); } if (unlikely(virtqueue_is_broken(vq))) break; if (virtqueue_enable_cb(vq)) break; } spin_unlock_irqrestore(&vvd->commandq.qlock, flags); wake_up(&vvd->commandq.reclaim_queue); } void virtio_video_process_events(struct work_struct *work) { struct virtio_video_device *vvd = container_of(work, struct virtio_video_device, eventq.work); struct virtqueue *vq = vvd->eventq.vq; struct virtio_video_event *evt; unsigned int len; while (vvd->eventq.ready) { virtqueue_disable_cb(vq); while ((evt = virtqueue_get_buf(vq, &len))) { virtio_video_handle_event(vvd, evt); virtio_video_queue_event_buffer(vvd, evt); } if (unlikely(virtqueue_is_broken(vq))) break; if (virtqueue_enable_cb(vq)) break; } } void virtio_video_event_cb(struct virtqueue *vq) { struct virtio_video_device *vvd = vq->vdev->priv; schedule_work(&vvd->eventq.work); } static struct virtio_video_vbuffer * virtio_video_get_vbuf(struct virtio_video_device *vvd, int size, int resp_size, void *resp_buf, virtio_video_resp_cb resp_cb) { struct virtio_video_vbuffer *vbuf; vbuf = kmem_cache_alloc(vvd->vbufs, GFP_KERNEL); if (!vbuf) return ERR_PTR(-ENOMEM); memset(vbuf, 0, VBUFFER_SIZE); BUG_ON(size > MAX_INLINE_CMD_SIZE); vbuf->buf = (void *)vbuf + sizeof(*vbuf); vbuf->size = size; vbuf->resp_cb = resp_cb; vbuf->resp_size = resp_size; if (resp_size <= MAX_INLINE_RESP_SIZE && !resp_buf) vbuf->resp_buf = (void *)vbuf->buf + size; else vbuf->resp_buf = resp_buf; BUG_ON(!vbuf->resp_buf); return vbuf; } int virtio_video_alloc_vbufs(struct virtio_video_device *vvd) { vvd->vbufs = kmem_cache_create("virtio-video-vbufs", VBUFFER_SIZE, __alignof__(struct virtio_video_vbuffer), 0, NULL); if (!vvd->vbufs) return -ENOMEM; return 0; } void virtio_video_free_vbufs(struct virtio_video_device *vvd) { struct virtio_video_vbuffer *vbuf; /* Release command buffers. Operation on vbufs here is lock safe, since before device was deinitialized and queues was stopped (in not ready state) */ while ((vbuf = virtqueue_detach_unused_buf(vvd->commandq.vq))) { if (vbuf_is_pending(vvd, vbuf)) free_vbuf(vvd, vbuf); } kmem_cache_destroy(vvd->vbufs); vvd->vbufs = NULL; /* Release event buffers */ while (virtqueue_detach_unused_buf(vvd->eventq.vq)); kfree(vvd->evts); vvd->evts = NULL; } static void *virtio_video_alloc_req(struct virtio_video_device *vvd, struct virtio_video_vbuffer **vbuffer_p, int size) { struct virtio_video_vbuffer *vbuf; vbuf = virtio_video_get_vbuf(vvd, size, sizeof(struct virtio_video_cmd_hdr), NULL, NULL); if (IS_ERR(vbuf)) { *vbuffer_p = NULL; return ERR_CAST(vbuf); } *vbuffer_p = vbuf; return vbuf->buf; } static void * virtio_video_alloc_req_resp(struct virtio_video_device *vvd, virtio_video_resp_cb cb, struct virtio_video_vbuffer **vbuffer_p, int req_size, int resp_size, void *resp_buf) { struct virtio_video_vbuffer *vbuf; vbuf = virtio_video_get_vbuf(vvd, req_size, resp_size, resp_buf, cb); if (IS_ERR(vbuf)) { *vbuffer_p = NULL; return ERR_CAST(vbuf); } *vbuffer_p = vbuf; return vbuf->buf; } static int virtio_video_queue_cmd_buffer(struct virtio_video_device *vvd, struct virtio_video_vbuffer *vbuf) { unsigned long flags; struct virtqueue *vq = vvd->commandq.vq; struct scatterlist *sgs[3], vreq, vout, vresp; int outcnt = 0, incnt = 0; int ret; if (!vvd->commandq.ready) return -ENODEV; spin_lock_irqsave(&vvd->commandq.qlock, flags); vbuf->id = vvd->vbufs_sent++; list_add_tail(&vbuf->pending_list_entry, &vvd->pending_vbuf_list); sg_init_one(&vreq, vbuf->buf, vbuf->size); sgs[outcnt + incnt] = &vreq; outcnt++; if (vbuf->data_size) { sg_init_one(&vout, vbuf->data_buf, vbuf->data_size); sgs[outcnt + incnt] = &vout; outcnt++; } if (vbuf->resp_size) { sg_init_one(&vresp, vbuf->resp_buf, vbuf->resp_size); sgs[outcnt + incnt] = &vresp; incnt++; } retry: ret = virtqueue_add_sgs(vq, sgs, outcnt, incnt, vbuf, GFP_ATOMIC); if (ret == -ENOSPC) { spin_unlock_irqrestore(&vvd->commandq.qlock, flags); wait_event(vvd->commandq.reclaim_queue, vq->num_free); spin_lock_irqsave(&vvd->commandq.qlock, flags); goto retry; } else { virtqueue_kick(vq); } spin_unlock_irqrestore(&vvd->commandq.qlock, flags); return ret; } static int virtio_video_queue_cmd_buffer_sync(struct virtio_video_device *vvd, struct virtio_video_vbuffer *vbuf) { int ret; unsigned long rem; unsigned long flags; vbuf->is_sync = true; init_completion(&vbuf->reclaimed); ret = virtio_video_queue_cmd_buffer(vvd, vbuf); if (ret) return ret; rem = wait_for_completion_timeout(&vbuf->reclaimed, 5 * HZ); if (rem == 0) ret = -ETIMEDOUT; spin_lock_irqsave(&vvd->commandq.qlock, flags); if (vbuf_is_pending(vvd, vbuf)) free_vbuf(vvd, vbuf); spin_unlock_irqrestore(&vvd->commandq.qlock, flags); return ret; } static int virtio_video_queue_event_buffer(struct virtio_video_device *vvd, struct virtio_video_event *evt) { int ret; struct scatterlist sg; struct virtqueue *vq = vvd->eventq.vq; memset(evt, 0, sizeof(struct virtio_video_event)); sg_init_one(&sg, evt, sizeof(struct virtio_video_event)); ret = virtqueue_add_inbuf(vq, &sg, 1, evt, GFP_KERNEL); if (ret) { v4l2_err(&vvd->v4l2_dev, "failed to queue event buffer\n"); return ret; } virtqueue_kick(vq); return 0; } static void virtio_video_handle_event(struct virtio_video_device *vvd, struct virtio_video_event *evt) { int ret; struct virtio_video_stream *stream; uint32_t stream_id = evt->stream_id; struct video_device *vd = &vvd->video_dev; mutex_lock(vd->lock); stream = idr_find(&vvd->stream_idr, stream_id); if (!stream) { v4l2_warn(&vvd->v4l2_dev, "stream_id=%u not found for event\n", stream_id); mutex_unlock(vd->lock); return; } switch (le32_to_cpu(evt->event_type)) { case VIRTIO_VIDEO_EVENT_DECODER_RESOLUTION_CHANGED: v4l2_dbg(1, vvd->debug, &vvd->v4l2_dev, "stream_id=%u: resolution change event\n", stream_id); virtio_video_cmd_get_params(vvd, stream, VIRTIO_VIDEO_QUEUE_TYPE_OUTPUT); virtio_video_queue_res_chg_event(stream); if (virtio_video_state(stream) == STREAM_STATE_INIT) { virtio_video_state_update(stream, STREAM_STATE_DYNAMIC_RES_CHANGE); wake_up(&vvd->wq); } break; case VIRTIO_VIDEO_EVENT_ERROR: v4l2_err(&vvd->v4l2_dev, "stream_id=%i: error event\n", stream_id); virtio_video_state_update(stream, STREAM_STATE_ERROR); virtio_video_handle_error(stream); break; default: v4l2_warn(&vvd->v4l2_dev, "stream_id=%i: unknown event\n", stream_id); break; } mutex_unlock(vd->lock); } int virtio_video_alloc_events(struct virtio_video_device *vvd) { int ret; size_t i; struct virtio_video_event *evts; size_t num = vvd->eventq.vq->num_free; evts = kzalloc(num * sizeof(struct virtio_video_event), GFP_KERNEL); if (!evts) { v4l2_err(&vvd->v4l2_dev, "failed to alloc event buffers!!!\n"); return -ENOMEM; } vvd->evts = evts; for (i = 0; i < num; i++) { ret = virtio_video_queue_event_buffer(vvd, &evts[i]); if (ret) { v4l2_err(&vvd->v4l2_dev, "failed to queue event buffer\n"); return ret; } } return 0; } // TODO: replace virtio_video_cmd_hdr accoring to specification v4 int virtio_video_cmd_stream_create(struct virtio_video_device *vvd, uint32_t stream_id, enum virtio_video_format format, const char *tag) { struct virtio_video_stream_create *req_p; struct virtio_video_vbuffer *vbuf; req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_STREAM_CREATE); req_p->hdr.stream_id = cpu_to_le32(stream_id); req_p->in_mem_type = cpu_to_le32(VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES); req_p->out_mem_type = cpu_to_le32(VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES); req_p->coded_format = cpu_to_le32(format); if (strscpy(req_p->tag, tag, sizeof(req_p->tag) - 1) < 0) v4l2_err(&vvd->v4l2_dev, "failed to copy stream tag\n"); req_p->tag[sizeof(req_p->tag) - 1] = 0; return virtio_video_queue_cmd_buffer(vvd, vbuf); } // TODO: replace virtio_video_cmd_hdr accoring to specification v4 int virtio_video_cmd_stream_destroy(struct virtio_video_device *vvd, uint32_t stream_id) { struct virtio_video_stream_destroy *req_p; struct virtio_video_vbuffer *vbuf; req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_STREAM_DESTROY); req_p->hdr.stream_id = cpu_to_le32(stream_id); return virtio_video_queue_cmd_buffer(vvd, vbuf); } // TODO: replace virtio_video_cmd_hdr accoring to specification v4 int virtio_video_cmd_stream_drain(struct virtio_video_device *vvd, uint32_t stream_id) { struct virtio_video_stream_drain *req_p; struct virtio_video_vbuffer *vbuf; req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_STREAM_DRAIN); req_p->hdr.stream_id = cpu_to_le32(stream_id); return virtio_video_queue_cmd_buffer(vvd, vbuf); } int virtio_video_cmd_resource_attach(struct virtio_video_device *vvd, uint32_t stream_id, uint32_t resource_id, enum virtio_video_queue_type queue_type, void *buf, size_t buf_size) { struct virtio_video_resource_attach *req_p; struct virtio_video_vbuffer *vbuf; req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->cmd_type = cpu_to_le32(VIRTIO_VIDEO_CMD_RESOURCE_ATTACH); req_p->stream_id = cpu_to_le32(stream_id); req_p->queue_type = cpu_to_le32(queue_type); req_p->resource_id = cpu_to_le32(resource_id); vbuf->data_buf = buf; vbuf->data_size = buf_size; return virtio_video_queue_cmd_buffer(vvd, vbuf); } int virtio_video_cmd_queue_detach_resources(struct virtio_video_device *vvd, struct virtio_video_stream *stream, enum virtio_video_queue_type queue_type) { int ret; struct virtio_video_queue_detach_resources *req_p; struct virtio_video_vbuffer *vbuf; req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->cmd_type = cpu_to_le32(VIRTIO_VIDEO_CMD_QUEUE_DETACH_RESOURCES); req_p->stream_id = cpu_to_le32(stream->stream_id); req_p->queue_type = cpu_to_le32(queue_type); ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); if (ret == -ETIMEDOUT) v4l2_err(&vvd->v4l2_dev, "timed out waiting for resource destruction for %s\n", (queue_type == VIRTIO_VIDEO_QUEUE_TYPE_INPUT) ? "OUTPUT" : "CAPTURE"); return ret; } static void virtio_video_cmd_resource_queue_cb(struct virtio_video_device *vvd, struct virtio_video_vbuffer *vbuf) { uint32_t flags; uint64_t timestamp; struct virtio_video_buffer *virtio_vb = vbuf->priv; struct virtio_video_resource_queue_resp *resp = (struct virtio_video_resource_queue_resp *)vbuf->resp_buf; flags = le32_to_cpu(resp->flags); timestamp = le64_to_cpu(resp->timestamp); virtio_video_buf_done(virtio_vb, flags, timestamp, resp->data_sizes); } int virtio_video_cmd_resource_queue(struct virtio_video_device *vvd, uint32_t stream_id, struct virtio_video_buffer *virtio_vb, uint32_t data_size[], uint8_t num_data_size, enum virtio_video_queue_type queue_type) { uint8_t i; struct virtio_video_resource_queue *req_p; struct virtio_video_resource_queue_resp *resp_p; struct virtio_video_vbuffer *vbuf; size_t resp_size = sizeof(struct virtio_video_resource_queue_resp); req_p = virtio_video_alloc_req_resp(vvd, &virtio_video_cmd_resource_queue_cb, &vbuf, sizeof(*req_p), resp_size, NULL); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->cmd_type = cpu_to_le32(VIRTIO_VIDEO_CMD_RESOURCE_QUEUE); req_p->stream_id = cpu_to_le32(stream_id); req_p->queue_type = cpu_to_le32(queue_type); req_p->resource_id = cpu_to_le32(virtio_vb->resource_id); req_p->flags = 0; req_p->timestamp = cpu_to_le64(virtio_vb->v4l2_m2m_vb.vb.vb2_buf.timestamp); for (i = 0; i < num_data_size; ++i) req_p->data_sizes[i] = cpu_to_le32(data_size[i]); resp_p = (struct virtio_video_resource_queue_resp *)vbuf->resp_buf; memset(resp_p, 0, sizeof(*resp_p)); vbuf->priv = virtio_vb; return virtio_video_queue_cmd_buffer(vvd, vbuf); } // TODO: replace virtio_video_cmd_hdr accoring to specification v4 int virtio_video_cmd_queue_clear(struct virtio_video_device *vvd, struct virtio_video_stream *stream, enum virtio_video_queue_type queue_type) { int ret; struct virtio_video_queue_clear *req_p; struct virtio_video_vbuffer *vbuf; req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_QUEUE_CLEAR); req_p->hdr.stream_id = cpu_to_le32(stream->stream_id); req_p->queue_type = cpu_to_le32(queue_type); ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); if (ret == -ETIMEDOUT) v4l2_err(&vvd->v4l2_dev, "timed out waiting for %s queue clear\n", (queue_type == VIRTIO_VIDEO_QUEUE_TYPE_INPUT) ? "OUTPUT" : "CAPTURE"); return ret; } // TODO: replace virtio_video_cmd_hdr accoring to specification v4 int virtio_video_cmd_query_capability(struct virtio_video_device *vvd, void *resp_buf, size_t resp_size, enum virtio_video_queue_type queue_type) { int ret; struct virtio_video_query_capability *req_p; struct virtio_video_vbuffer *vbuf; req_p = virtio_video_alloc_req_resp(vvd, NULL, &vbuf, sizeof(*req_p), resp_size, resp_buf); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_QUERY_CAPABILITY); req_p->queue_type = cpu_to_le32(queue_type); ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); if (ret == -ETIMEDOUT) v4l2_err(&vvd->v4l2_dev, "timed out waiting for capabilities for %s\n", (queue_type == VIRTIO_VIDEO_QUEUE_TYPE_INPUT) ? "OUTPUT" : "CAPTURE"); return ret; } // TODO: replace virtio_video_cmd_hdr accoring to specification v4 int virtio_video_query_control_level(struct virtio_video_device *vvd, void *resp_buf, size_t resp_size, enum virtio_video_format format) { int ret; struct virtio_video_query_control *req_p; struct virtio_video_query_control_level *ctrl_l; struct virtio_video_vbuffer *vbuf; uint32_t req_size = 0; req_size = sizeof(struct virtio_video_query_control) + sizeof(struct virtio_video_query_control_level); req_p = virtio_video_alloc_req_resp(vvd, NULL, &vbuf, req_size, resp_size, resp_buf); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_QUERY_CONTROL); req_p->control = cpu_to_le32(VIRTIO_VIDEO_CONTROL_LEVEL); ctrl_l = (void *)((char *)req_p + sizeof(struct virtio_video_query_control)); ctrl_l->format = cpu_to_le32(format); ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); if (ret == -ETIMEDOUT) v4l2_err(&vvd->v4l2_dev, "timed out waiting for level query\n"); return ret; } // TODO: replace virtio_video_cmd_hdr accoring to specification v4 int virtio_video_query_control_profile(struct virtio_video_device *vvd, void *resp_buf, size_t resp_size, enum virtio_video_format format) { int ret; struct virtio_video_query_control *req_p; struct virtio_video_query_control_profile *ctrl_p; struct virtio_video_vbuffer *vbuf; uint32_t req_size = 0; req_size = sizeof(struct virtio_video_query_control) + sizeof(struct virtio_video_query_control_profile); req_p = virtio_video_alloc_req_resp(vvd, NULL, &vbuf, req_size, resp_size, resp_buf); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_QUERY_CONTROL); req_p->control = cpu_to_le32(VIRTIO_VIDEO_CONTROL_PROFILE); ctrl_p = (void *)((char *)req_p + sizeof(struct virtio_video_query_control)); ctrl_p->format = cpu_to_le32(format); ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); if (ret == -ETIMEDOUT) v4l2_err(&vvd->v4l2_dev, "timed out waiting for profile query\n"); return ret; } static void virtio_video_cmd_get_params_cb(struct virtio_video_device *vvd, struct virtio_video_vbuffer *vbuf) { int i; struct virtio_video_get_params_resp *resp = (struct virtio_video_get_params_resp *)vbuf->resp_buf; struct virtio_video_params *params = &resp->params; struct virtio_video_stream *stream = vbuf->priv; enum virtio_video_queue_type queue_type; struct video_format_info *format_info; queue_type = le32_to_cpu(params->queue_type); if (queue_type == VIRTIO_VIDEO_QUEUE_TYPE_INPUT) format_info = &stream->in_info; else format_info = &stream->out_info; format_info->frame_rate = le32_to_cpu(params->frame_rate); format_info->frame_width = le32_to_cpu(params->frame_width); format_info->frame_height = le32_to_cpu(params->frame_height); format_info->min_buffers = le32_to_cpu(params->min_buffers); format_info->max_buffers = le32_to_cpu(params->max_buffers); format_info->fourcc_format = virtio_video_format_to_v4l2(le32_to_cpu(params->format)); format_info->crop.top = le32_to_cpu(params->crop.top); format_info->crop.left = le32_to_cpu(params->crop.left); format_info->crop.width = le32_to_cpu(params->crop.width); format_info->crop.height = le32_to_cpu(params->crop.height); format_info->num_planes = le32_to_cpu(params->num_planes); for (i = 0; i < le32_to_cpu(params->num_planes); i++) { struct virtio_video_plane_format *plane_formats = ¶ms->plane_formats[i]; struct video_plane_format *plane_format = &format_info->plane_format[i]; plane_format->plane_size = le32_to_cpu(plane_formats->plane_size); plane_format->stride = le32_to_cpu(plane_formats->stride); } } // TODO: replace virtio_video_cmd_hdr accoring to specification v4 int virtio_video_cmd_get_params(struct virtio_video_device *vvd, struct virtio_video_stream *stream, enum virtio_video_queue_type queue_type) { int ret; struct virtio_video_get_params *req_p; struct virtio_video_vbuffer *vbuf; struct virtio_video_get_params_resp *resp_p; size_t resp_size = sizeof(struct virtio_video_get_params_resp); req_p = virtio_video_alloc_req_resp(vvd, &virtio_video_cmd_get_params_cb, &vbuf, sizeof(*req_p), resp_size, NULL); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_GET_PARAMS); req_p->hdr.stream_id = cpu_to_le32(stream->stream_id); req_p->queue_type = cpu_to_le32(queue_type); resp_p = (struct virtio_video_get_params_resp *)vbuf->resp_buf; vbuf->priv = stream; ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); if (ret == -ETIMEDOUT) v4l2_err(&vvd->v4l2_dev, "timed out waiting for get_params\n"); return ret; } // TODO: replace virtio_video_cmd_hdr accoring to specification v4 int virtio_video_cmd_set_params(struct virtio_video_device *vvd, struct virtio_video_stream *stream, struct video_format_info *format_info, enum virtio_video_queue_type queue_type) { int i; struct virtio_video_set_params *req_p; struct virtio_video_vbuffer *vbuf; req_p = virtio_video_alloc_req(vvd, &vbuf, sizeof(*req_p)); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_SET_PARAMS); req_p->hdr.stream_id = cpu_to_le32(stream->stream_id); req_p->params.queue_type = cpu_to_le32(queue_type); req_p->params.frame_rate = cpu_to_le32(format_info->frame_rate); req_p->params.frame_width = cpu_to_le32(format_info->frame_width); req_p->params.frame_height = cpu_to_le32(format_info->frame_height); req_p->params.format = virtio_video_v4l2_format_to_virtio( cpu_to_le32(format_info->fourcc_format)); req_p->params.min_buffers = cpu_to_le32(format_info->min_buffers); req_p->params.max_buffers = cpu_to_le32(format_info->max_buffers); req_p->params.num_planes = cpu_to_le32(format_info->num_planes); for (i = 0; i < format_info->num_planes; i++) { struct virtio_video_plane_format *plane_formats = &req_p->params.plane_formats[i]; struct video_plane_format *plane_format = &format_info->plane_format[i]; plane_formats->plane_size = cpu_to_le32(plane_format->plane_size); plane_formats->stride = cpu_to_le32(plane_format->stride); } return virtio_video_queue_cmd_buffer(vvd, vbuf); } static void virtio_video_cmd_get_ctrl_profile_cb(struct virtio_video_device *vvd, struct virtio_video_vbuffer *vbuf) { struct virtio_video_get_control_resp *resp = (struct virtio_video_get_control_resp *)vbuf->resp_buf; struct virtio_video_control_val_profile *resp_p = NULL; struct virtio_video_stream *stream = vbuf->priv; struct video_control_info *control = &stream->control; resp_p = (void *)((char *)resp + sizeof(struct virtio_video_get_control_resp)); control->profile = le32_to_cpu(resp_p->profile); } static void virtio_video_cmd_get_ctrl_level_cb(struct virtio_video_device *vvd, struct virtio_video_vbuffer *vbuf) { struct virtio_video_get_control_resp *resp = (struct virtio_video_get_control_resp *)vbuf->resp_buf; struct virtio_video_control_val_level *resp_p; struct virtio_video_stream *stream = vbuf->priv; struct video_control_info *control = &stream->control; resp_p = (void *)((char *)resp + sizeof(struct virtio_video_get_control_resp)); control->level = le32_to_cpu(resp_p->level); } static void virtio_video_cmd_get_ctrl_bitrate_cb(struct virtio_video_device *vvd, struct virtio_video_vbuffer *vbuf) { struct virtio_video_get_control_resp *resp = (struct virtio_video_get_control_resp *)vbuf->resp_buf; struct virtio_video_control_val_bitrate *resp_p = NULL; struct virtio_video_stream *stream = vbuf->priv; struct video_control_info *control = &stream->control; resp_p = (void *)((char *) resp + sizeof(struct virtio_video_get_control_resp)); control->bitrate = le32_to_cpu(resp_p->bitrate); } // TODO: replace virtio_video_cmd_hdr accoring to specification v4 int virtio_video_cmd_get_control(struct virtio_video_device *vvd, struct virtio_video_stream *stream, enum virtio_video_control_type control) { int ret; struct virtio_video_get_control *req_p; struct virtio_video_get_control_resp *resp_p; struct virtio_video_vbuffer *vbuf; size_t resp_size = sizeof(struct virtio_video_get_control_resp); virtio_video_resp_cb cb; switch (control) { case VIRTIO_VIDEO_CONTROL_PROFILE: resp_size += sizeof(struct virtio_video_control_val_profile); cb = &virtio_video_cmd_get_ctrl_profile_cb; break; case VIRTIO_VIDEO_CONTROL_LEVEL: resp_size += sizeof(struct virtio_video_control_val_level); cb = &virtio_video_cmd_get_ctrl_level_cb; break; case VIRTIO_VIDEO_CONTROL_BITRATE: resp_size += sizeof(struct virtio_video_control_val_bitrate); cb = &virtio_video_cmd_get_ctrl_bitrate_cb; break; default: return -EINVAL; } req_p = virtio_video_alloc_req_resp(vvd, cb, &vbuf, sizeof(*req_p), resp_size, NULL); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_GET_CONTROL); req_p->hdr.stream_id = cpu_to_le32(stream->stream_id); req_p->control = cpu_to_le32(control); resp_p = (struct virtio_video_get_control_resp *)vbuf->resp_buf; vbuf->priv = stream; ret = virtio_video_queue_cmd_buffer_sync(vvd, vbuf); if (ret == -ETIMEDOUT) v4l2_err(&vvd->v4l2_dev, "timed out waiting for get_control\n"); return ret; } // TODO: replace virtio_video_cmd_hdr accoring to specification v4 int virtio_video_cmd_set_control(struct virtio_video_device *vvd, uint32_t stream_id, enum virtio_video_control_type control, uint32_t value) { struct virtio_video_set_control *req_p; struct virtio_video_vbuffer *vbuf; struct virtio_video_control_val_level *ctrl_l; struct virtio_video_control_val_profile *ctrl_p; struct virtio_video_control_val_bitrate *ctrl_b; size_t size; if (value == 0) return -EINVAL; switch (control) { case VIRTIO_VIDEO_CONTROL_PROFILE: size = sizeof(struct virtio_video_control_val_profile); break; case VIRTIO_VIDEO_CONTROL_LEVEL: size = sizeof(struct virtio_video_control_val_level); break; case VIRTIO_VIDEO_CONTROL_BITRATE: size = sizeof(struct virtio_video_control_val_bitrate); break; default: return -EINVAL; } req_p = virtio_video_alloc_req(vvd, &vbuf, size + sizeof(*req_p)); if (IS_ERR(req_p)) return PTR_ERR(req_p); req_p->hdr.type = cpu_to_le32(VIRTIO_VIDEO_CMD_SET_CONTROL); req_p->hdr.stream_id = cpu_to_le32(stream_id); req_p->control = cpu_to_le32(control); switch (control) { case VIRTIO_VIDEO_CONTROL_PROFILE: ctrl_p = (void *)((char *)req_p + sizeof(struct virtio_video_set_control)); ctrl_p->profile = cpu_to_le32(value); break; case VIRTIO_VIDEO_CONTROL_LEVEL: ctrl_l = (void *)((char *)req_p + sizeof(struct virtio_video_set_control)); ctrl_l->level = cpu_to_le32(value); break; case VIRTIO_VIDEO_CONTROL_BITRATE: ctrl_b = (void *)((char *)req_p + sizeof(struct virtio_video_set_control)); ctrl_b->bitrate = cpu_to_le32(value); break; } return virtio_video_queue_cmd_buffer(vvd, vbuf); }