diff options
author | Vasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com> | 2021-11-22 12:38:06 +0100 |
---|---|---|
committer | Vasyl Vavrychuk <vasyl.vavrychuk@opensynergy.com> | 2021-12-07 20:47:22 +0100 |
commit | 87b7cce6fb8a0afde96227423b3385d36e6fe0a1 (patch) | |
tree | 052311f8fd70ed9ed4efa0fc2f43bc2d5c452b85 /meta-egvirt/recipes-kernel/kernel-module-virtio-video/files/virtio_video_caps.c | |
parent | 3c2b0cdba520c260a4342d9b968d9efb7892a643 (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.c | 460 |
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); +} |