summaryrefslogtreecommitdiffstats
path: root/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_caps.c
diff options
context:
space:
mode:
authorVasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com>2021-11-22 12:38:06 +0100
committerVasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com>2021-12-07 20:47:22 +0100
commit87b7cce6fb8a0afde96227423b3385d36e6fe0a1 (patch)
tree052311f8fd70ed9ed4efa0fc2f43bc2d5c452b85 /meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_caps.c
parent3c2b0cdba520c260a4342d9b968d9efb7892a643 (diff)
virtualization: Add virtio-video driver as external module.
This driver should conform WIP spec v3 [1] with some updates for spec v4 [2], and, some unspecified features such as VIRTIO_VIDEO_DEVICE_CAMERA. Imported from internal OpenSynergy's revision: bcc33b6b9e0156b381a70c54d2df02c57b63d270 Kernel was configured with necessary features for this driver: enable MEDIA_SUPPORT disable MEDIA_SUBDRV_AUTOSELECT enable MEDIA_PLATFORM_SUPPORT enable VIDEO_VIRTIO Keep driver as an external module to simplify future updates. [1]: https://lists.oasis-open.org/archives/virtio-dev/202002/msg00002.html [2]: https://lists.oasis-open.org/archives/virtio-dev/202006/msg00072.html Bug-AGL: SPEC-4148 Change-Id: Iea339194b22443f67b3e2ffddca84118357a2f15 Signed-off-by: Vasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com>
Diffstat (limited to 'meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_caps.c')
-rw-r--r--meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_caps.c460
1 files changed, 460 insertions, 0 deletions
diff --git a/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_caps.c b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_caps.c
new file mode 100644
index 00000000..fc815b18
--- /dev/null
+++ b/meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_caps.c
@@ -0,0 +1,460 @@
+// 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "virtio_video.h"
+
+static void virtio_video_free_frame_rates(struct video_format_frame *frame)
+{
+ kfree(frame->frame_rates);
+}
+
+static void virtio_video_free_frames(struct video_format *fmt)
+{
+ size_t idx = 0;
+
+ for (idx = 0; idx < fmt->desc.num_frames; idx++)
+ virtio_video_free_frame_rates(&fmt->frames[idx]);
+ kfree(fmt->frames);
+}
+
+static void virtio_video_free_fmt(struct list_head *fmts_list)
+{
+ struct video_format *fmt, *tmp;
+
+ list_for_each_entry_safe(fmt, tmp, fmts_list, formats_list_entry) {
+ list_del(&fmt->formats_list_entry);
+ virtio_video_free_frames(fmt);
+ kfree(fmt);
+ }
+}
+
+static void virtio_video_free_fmts(struct virtio_video_device *vvd)
+{
+ virtio_video_free_fmt(&vvd->input_fmt_list);
+ virtio_video_free_fmt(&vvd->output_fmt_list);
+}
+
+static void virtio_video_copy_fmt_range(struct virtio_video_format_range *d_rge,
+ struct virtio_video_format_range *s_rge)
+{
+ d_rge->min = le32_to_cpu(s_rge->min);
+ d_rge->max = le32_to_cpu(s_rge->max);
+ d_rge->step = le32_to_cpu(s_rge->step);
+}
+
+static size_t
+virtio_video_parse_virtio_frame_rate(struct virtio_video_device *vvd,
+ struct virtio_video_format_range *f_rate,
+ void *buf)
+{
+ struct virtio_video_format_range *virtio_frame_rate;
+
+ virtio_frame_rate = buf;
+ virtio_video_copy_fmt_range(f_rate, virtio_frame_rate);
+
+ return sizeof(struct virtio_video_format_range);
+}
+
+static size_t virtio_video_parse_virtio_frame(struct virtio_video_device *vvd,
+ struct video_format_frame *frm,
+ void *buf)
+{
+ struct virtio_video_format_frame *virtio_frame;
+ struct virtio_video_format_frame *frame = &frm->frame;
+ struct virtio_video_format_range *rate;
+ size_t idx, offset, extra_size;
+
+ virtio_frame = buf;
+
+ virtio_video_copy_fmt_range(&frame->width, &virtio_frame->width);
+ virtio_video_copy_fmt_range(&frame->height, &virtio_frame->height);
+
+ frame->num_rates = le32_to_cpu(virtio_frame->num_rates);
+ frm->frame_rates = kcalloc(frame->num_rates,
+ sizeof(struct virtio_video_format_range),
+ GFP_KERNEL);
+
+ offset = sizeof(struct virtio_video_format_frame);
+ for (idx = 0; idx < frame->num_rates; idx++) {
+ rate = &frm->frame_rates[idx];
+ extra_size =
+ virtio_video_parse_virtio_frame_rate(vvd, rate,
+ buf + offset);
+ if (extra_size == 0) {
+ kfree(frm->frame_rates);
+ v4l2_err(&vvd->v4l2_dev,
+ "failed to parse frame rate\n");
+ return 0;
+ }
+ offset += extra_size;
+ }
+
+ return offset;
+}
+
+static size_t virtio_video_parse_virtio_fmt(struct virtio_video_device *vvd,
+ struct video_format *fmt, void *buf)
+{
+ struct virtio_video_format_desc *virtio_fmt_desc;
+ struct virtio_video_format_desc *fmt_desc;
+ struct video_format_frame *frame;
+ size_t idx, offset, extra_size;
+
+ virtio_fmt_desc = buf;
+ fmt_desc = &fmt->desc;
+
+ fmt_desc->format =
+ virtio_video_format_to_v4l2
+ (le32_to_cpu(virtio_fmt_desc->format));
+ fmt_desc->mask = le64_to_cpu(virtio_fmt_desc->mask);
+ fmt_desc->planes_layout = le32_to_cpu(virtio_fmt_desc->planes_layout);
+
+ fmt_desc->num_frames = le32_to_cpu(virtio_fmt_desc->num_frames);
+ fmt->frames = kcalloc(fmt_desc->num_frames,
+ sizeof(struct video_format_frame),
+ GFP_KERNEL);
+
+ offset = sizeof(struct virtio_video_format_desc);
+ for (idx = 0; idx < fmt_desc->num_frames; idx++) {
+ frame = &fmt->frames[idx];
+ extra_size =
+ virtio_video_parse_virtio_frame(vvd, frame,
+ buf + offset);
+ if (extra_size == 0) {
+ kfree(fmt->frames);
+ v4l2_err(&vvd->v4l2_dev, "failed to parse frame\n");
+ return 0;
+ }
+ offset += extra_size;
+ }
+
+ return offset;
+}
+
+int virtio_video_parse_virtio_capability(struct virtio_video_device *vvd,
+ void *resp_buf,
+ struct list_head *ret_fmt_list,
+ uint32_t *ret_num_fmts)
+{
+ struct virtio_video_query_capability_resp *resp = resp_buf;
+ struct video_format *fmt;
+ uint32_t fmt_count;
+ int fmt_idx;
+ size_t offset;
+ int ret;
+
+ if (!resp || ret_fmt_list == NULL || ret_num_fmts == NULL) {
+ v4l2_err(&vvd->v4l2_dev, "invalid arguments!\n");
+ return -EINVAL;
+ }
+
+ if (le32_to_cpu(resp->num_descs) <= 0) {
+ v4l2_err(&vvd->v4l2_dev, "invalid capability response\n");
+ return -EINVAL;
+ }
+
+ fmt_count = le32_to_cpu(resp->num_descs);
+ offset = sizeof(struct virtio_video_query_capability_resp);
+
+ for (fmt_idx = 0; fmt_idx < fmt_count; fmt_idx++) {
+ size_t fmt_size = 0;
+
+ fmt = kzalloc(sizeof(*fmt), GFP_KERNEL);
+ if (!fmt) {
+ ret = -ENOMEM;
+ goto alloc_err;
+ }
+
+ fmt_size = virtio_video_parse_virtio_fmt(vvd, fmt,
+ resp_buf + offset);
+ if (fmt_size == 0) {
+ v4l2_err(&vvd->v4l2_dev, "failed to parse fmt\n");
+ ret = -ENOENT;
+ goto parse_fmt_err;
+ }
+ offset += fmt_size;
+ list_add(&fmt->formats_list_entry, ret_fmt_list);
+ }
+
+ *ret_num_fmts = fmt_count;
+ return 0;
+
+parse_fmt_err:
+ kfree(fmt);
+alloc_err:
+ virtio_video_free_fmts(vvd);
+ return ret;
+}
+
+int virtio_video_parse_virtio_capabilities(struct virtio_video_device *vvd,
+ void *input_buf, void *output_buf)
+{
+ int ret;
+
+ if (input_buf) {
+ ret = virtio_video_parse_virtio_capability(vvd, input_buf,
+ &vvd->input_fmt_list,
+ &vvd->num_input_fmts);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev,
+ "Failed to parse input capability: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ if (output_buf) {
+ ret = virtio_video_parse_virtio_capability(vvd, output_buf,
+ &vvd->output_fmt_list,
+ &vvd->num_output_fmts);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev,
+ "Failed to parse output capability: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+void virtio_video_clean_capability(struct virtio_video_device *vvd)
+{
+ virtio_video_free_fmts(vvd);
+}
+
+static void
+virtio_video_free_control_fmt_data(struct video_control_fmt_data *data)
+{
+ kfree(data->entries);
+ kfree(data);
+}
+
+static void virtio_video_free_control_formats(struct virtio_video_device *vvd)
+{
+ struct video_control_format *c_fmt, *tmp;
+
+ list_for_each_entry_safe(c_fmt, tmp, &vvd->controls_fmt_list,
+ controls_list_entry) {
+ list_del(&c_fmt->controls_list_entry);
+ virtio_video_free_control_fmt_data(c_fmt->profile);
+ virtio_video_free_control_fmt_data(c_fmt->level);
+ kfree(c_fmt);
+ }
+}
+
+static int virtio_video_parse_control_levels(struct virtio_video_device *vvd,
+ struct video_control_format *fmt)
+{
+ int idx, ret;
+ struct virtio_video_query_control_resp *resp_buf;
+ struct virtio_video_query_control_resp_level *l_resp_buf;
+ struct video_control_fmt_data *level;
+ enum virtio_video_format virtio_format;
+ uint32_t *virtio_levels;
+ uint32_t num_levels, mask = 0;
+ int max = 0, min = UINT_MAX;
+ size_t resp_size;
+
+ resp_size = vvd->max_resp_len;
+
+ virtio_format = virtio_video_v4l2_format_to_virtio(fmt->format);
+
+ resp_buf = kzalloc(resp_size, GFP_KERNEL);
+ if (IS_ERR(resp_buf)) {
+ ret = PTR_ERR(resp_buf);
+ goto lvl_err;
+ }
+
+ ret = virtio_video_query_control_level(vvd, resp_buf, resp_size,
+ virtio_format);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "failed to query level\n");
+ goto lvl_err;
+ }
+
+ l_resp_buf = (void *)((char *)resp_buf + sizeof(*resp_buf));
+ num_levels = le32_to_cpu(l_resp_buf->num);
+ if (num_levels == 0)
+ goto lvl_err;
+
+ fmt->level = kzalloc(sizeof(*level), GFP_KERNEL);
+ if (!fmt->level) {
+ ret = -ENOMEM;
+ goto lvl_err;
+ }
+
+ level = fmt->level;
+ level->entries = kcalloc(num_levels, sizeof(uint32_t), GFP_KERNEL);
+ if (!level->entries) {
+ kfree(fmt->level);
+ ret = -ENOMEM;
+ goto lvl_err;
+ }
+
+ virtio_levels = (void *)((char *)l_resp_buf + sizeof(*l_resp_buf));
+
+ for (idx = 0; idx < num_levels; idx++) {
+ level->entries[idx] =
+ virtio_video_level_to_v4l2
+ (le32_to_cpu(virtio_levels[idx]));
+
+ mask = mask | (1 << level->entries[idx]);
+ if (level->entries[idx] > max)
+ max = level->entries[idx];
+ if (level->entries[idx] < min)
+ min = level->entries[idx];
+ }
+ level->min = min;
+ level->max = max;
+ level->num = num_levels;
+ level->skip_mask = ~mask;
+
+lvl_err:
+ kfree(resp_buf);
+
+ return ret;
+}
+
+static int virtio_video_parse_control_profiles(struct virtio_video_device *vvd,
+ struct video_control_format *fmt)
+{
+ int idx, ret;
+ struct virtio_video_query_control_resp *resp_buf;
+ struct virtio_video_query_control_resp_profile *p_resp_buf;
+ struct video_control_fmt_data *profile;
+ uint32_t virtio_format, num_profiles, mask = 0;
+ uint32_t *virtio_profiles;
+ int max = 0, min = UINT_MAX;
+ size_t resp_size;
+
+ resp_size = vvd->max_resp_len;
+ virtio_format = virtio_video_v4l2_format_to_virtio(fmt->format);
+ resp_buf = kzalloc(resp_size, GFP_KERNEL);
+ if (IS_ERR(resp_buf)) {
+ ret = PTR_ERR(resp_buf);
+ goto prf_err;
+ }
+
+ ret = virtio_video_query_control_profile(vvd, resp_buf, resp_size,
+ virtio_format);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev, "failed to query profile\n");
+ goto prf_err;
+ }
+
+ p_resp_buf = (void *)((char *)resp_buf + sizeof(*resp_buf));
+ num_profiles = le32_to_cpu(p_resp_buf->num);
+ if (num_profiles == 0)
+ goto prf_err;
+
+ fmt->profile = kzalloc(sizeof(*profile), GFP_KERNEL);
+ if (!fmt->profile) {
+ ret = -ENOMEM;
+ goto prf_err;
+ }
+
+ profile = fmt->profile;
+ profile->entries = kcalloc(num_profiles, sizeof(uint32_t), GFP_KERNEL);
+ if (!profile->entries) {
+ kfree(fmt->profile);
+ ret = -ENOMEM;
+ goto prf_err;
+ }
+
+ virtio_profiles = (void *)((char *)p_resp_buf + sizeof(*p_resp_buf));
+
+ for (idx = 0; idx < num_profiles; idx++) {
+ profile->entries[idx] =
+ virtio_video_profile_to_v4l2
+ (le32_to_cpu(virtio_profiles[idx]));
+
+ mask = mask | (1 << profile->entries[idx]);
+ if (profile->entries[idx] > max)
+ max = profile->entries[idx];
+ if (profile->entries[idx] < min)
+ min = profile->entries[idx];
+ }
+ profile->min = min;
+ profile->max = max;
+ profile->num = num_profiles;
+ profile->skip_mask = ~mask;
+
+prf_err:
+ kfree(resp_buf);
+
+ return ret;
+}
+
+int virtio_video_parse_virtio_control(struct virtio_video_device *vvd)
+{
+ struct video_format *fmt;
+ struct video_control_format *c_fmt;
+ uint32_t virtio_format;
+ int ret;
+
+ list_for_each_entry(fmt, &vvd->output_fmt_list, formats_list_entry) {
+ virtio_format =
+ virtio_video_v4l2_format_to_virtio(fmt->desc.format);
+ if (virtio_format < VIRTIO_VIDEO_FORMAT_CODED_MIN ||
+ virtio_format > VIRTIO_VIDEO_FORMAT_CODED_MAX)
+ continue;
+
+ c_fmt = kzalloc(sizeof(*c_fmt), GFP_KERNEL);
+ if (!c_fmt) {
+ ret = -ENOMEM;
+ goto parse_ctrl_alloc_err;
+ }
+
+ c_fmt->format = fmt->desc.format;
+
+ ret = virtio_video_parse_control_profiles(vvd, c_fmt);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev,
+ "failed to parse control profile\n");
+ goto parse_ctrl_prf_err;
+ }
+
+ ret = virtio_video_parse_control_levels(vvd, c_fmt);
+ if (ret) {
+ v4l2_err(&vvd->v4l2_dev,
+ "failed to parse control level\n");
+ goto parse_ctrl_lvl_err;
+ }
+ list_add(&c_fmt->controls_list_entry, &vvd->controls_fmt_list);
+ }
+ return 0;
+
+parse_ctrl_lvl_err:
+ virtio_video_free_control_fmt_data(c_fmt->profile);
+parse_ctrl_prf_err:
+ kfree(c_fmt);
+parse_ctrl_alloc_err:
+ virtio_video_free_control_formats(vvd);
+
+ return ret;
+}
+
+void virtio_video_clean_control(struct virtio_video_device *vvd)
+{
+ virtio_video_free_control_formats(vvd);
+}