| /* Copyright (c) 2012, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * 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. |
| * |
| */ |
| #include <linux/hrtimer.h> |
| #include <linux/time.h> |
| #include <linux/list.h> |
| #include <media/videobuf2-core.h> |
| #include "enc-subdev.h" |
| #include "vsg-subdev.h" |
| #include "wfd-util.h" |
| |
| #define DEFAULT_FRAME_INTERVAL (66*NSEC_PER_MSEC) |
| #define DEFAULT_MAX_FRAME_INTERVAL (1*NSEC_PER_SEC) |
| #define DEFAULT_MODE ((enum vsg_modes)VSG_MODE_CFR) |
| #define MAX_BUFS_BUSY_WITH_ENC 5 |
| |
| static int vsg_release_input_buffer(struct vsg_context *context, |
| struct vsg_buf_info *buf) |
| { |
| WFD_MSG_DBG("Releasing frame with ts %lld ms, paddr %p\n", |
| timespec_to_ns(&buf->time), |
| (void *)buf->mdp_buf_info.paddr); |
| |
| if (buf->flags & VSG_NEVER_RELEASE) |
| WFD_MSG_WARN("Warning releasing buffer that's" |
| "not supposed to be released\n"); |
| |
| return context->vmops.release_input_frame(context->vmops.cbdata, |
| buf); |
| |
| } |
| |
| static int vsg_encode_frame(struct vsg_context *context, |
| struct vsg_buf_info *buf) |
| { |
| WFD_MSG_DBG("Encoding frame with ts %lld ms, paddr %p\n", |
| timespec_to_ns(&buf->time), |
| (void *)buf->mdp_buf_info.paddr); |
| |
| return context->vmops.encode_frame(context->vmops.cbdata, |
| buf); |
| } |
| |
| static void vsg_set_last_buffer(struct vsg_context *context, |
| struct vsg_buf_info *buf) |
| { |
| if (buf->flags & VSG_NEVER_SET_LAST_BUFFER) |
| WFD_MSG_WARN("Shouldn't be setting this to last buffer\n"); |
| |
| context->last_buffer = buf; |
| |
| WFD_MSG_DBG("Setting last buffer to paddr %p\n", |
| (void *)buf->mdp_buf_info.paddr); |
| } |
| |
| static void vsg_encode_helper_func(struct work_struct *task) |
| { |
| struct vsg_encode_work *work = |
| container_of(task, struct vsg_encode_work, work); |
| |
| /* |
| * Note: don't need to lock for context below as we only |
| * access fields that are "static". |
| */ |
| int rc = vsg_encode_frame(work->context, work->buf); |
| if (rc < 0) { |
| mutex_lock(&work->context->mutex); |
| work->context->state = VSG_STATE_ERROR; |
| mutex_unlock(&work->context->mutex); |
| } |
| kfree(work); |
| } |
| |
| static void vsg_work_func(struct work_struct *task) |
| { |
| struct vsg_work *work = |
| container_of(task, struct vsg_work, work); |
| struct vsg_encode_work *encode_work; |
| struct vsg_context *context = work->context; |
| struct vsg_buf_info *buf_info = NULL, *temp = NULL; |
| int rc = 0, count = 0; |
| mutex_lock(&context->mutex); |
| |
| if (list_empty(&context->free_queue.node)) { |
| WFD_MSG_DBG("%s: queue empty doing nothing\n", __func__); |
| goto err_skip_encode; |
| } else if (context->state != VSG_STATE_STARTED) { |
| WFD_MSG_DBG("%s: vsg is stopped or in error state " |
| "doing nothing\n", __func__); |
| goto err_skip_encode; |
| } |
| |
| list_for_each_entry(temp, &context->busy_queue.node, node) { |
| if (++count > MAX_BUFS_BUSY_WITH_ENC) { |
| WFD_MSG_WARN("Skipping encode, too many " |
| "buffers with encoder"); |
| goto err_skip_encode; |
| } |
| } |
| |
| buf_info = list_first_entry(&context->free_queue.node, |
| struct vsg_buf_info, node); |
| list_del(&buf_info->node); |
| INIT_LIST_HEAD(&buf_info->node); |
| |
| ktime_get_ts(&buf_info->time); |
| hrtimer_forward_now(&context->threshold_timer, ns_to_ktime( |
| context->max_frame_interval)); |
| |
| temp = NULL; |
| list_for_each_entry(temp, &context->busy_queue.node, node) { |
| if (mdp_buf_info_equals(&temp->mdp_buf_info, |
| &buf_info->mdp_buf_info)) { |
| temp->flags |= VSG_NEVER_RELEASE; |
| } |
| } |
| |
| if (context->last_buffer && |
| mdp_buf_info_equals(&context->last_buffer->mdp_buf_info, |
| &buf_info->mdp_buf_info)) { |
| context->last_buffer->flags |= VSG_NEVER_RELEASE; |
| } |
| |
| encode_work = kmalloc(sizeof(*encode_work), GFP_KERNEL); |
| encode_work->buf = buf_info; |
| encode_work->context = context; |
| INIT_WORK(&encode_work->work, vsg_encode_helper_func); |
| rc = queue_work(context->work_queue, &encode_work->work); |
| if (!rc) { |
| WFD_MSG_ERR("Queueing buffer for encode failed\n"); |
| kfree(encode_work); |
| encode_work = NULL; |
| goto err_skip_encode; |
| } |
| |
| buf_info->flags |= VSG_BUF_BEING_ENCODED; |
| if (!(buf_info->flags & VSG_NEVER_SET_LAST_BUFFER)) { |
| if (context->last_buffer) { |
| struct vsg_buf_info *old_last_buffer = |
| context->last_buffer; |
| bool last_buf_with_us = old_last_buffer && |
| !(old_last_buffer->flags & |
| VSG_BUF_BEING_ENCODED); |
| bool can_release = old_last_buffer && |
| !(old_last_buffer->flags & |
| VSG_NEVER_RELEASE); |
| |
| if (old_last_buffer && last_buf_with_us |
| && can_release) { |
| vsg_release_input_buffer(context, |
| old_last_buffer); |
| kfree(old_last_buffer); |
| } |
| } |
| vsg_set_last_buffer(context, buf_info); |
| } |
| |
| list_add_tail(&buf_info->node, &context->busy_queue.node); |
| err_skip_encode: |
| mutex_unlock(&context->mutex); |
| kfree(work); |
| } |
| |
| static void vsg_timer_helper_func(struct work_struct *task) |
| { |
| struct vsg_work *work = |
| container_of(task, struct vsg_work, work); |
| struct vsg_work *new_work = NULL; |
| struct vsg_context *context = work->context; |
| int num_bufs_to_queue = 1, c = 0; |
| |
| mutex_lock(&context->mutex); |
| |
| if (context->state != VSG_STATE_STARTED) |
| goto err_locked; |
| |
| if (list_empty(&context->free_queue.node) |
| && context->last_buffer) { |
| struct vsg_buf_info *info = NULL, *buf_to_encode = NULL; |
| |
| if (context->mode == VSG_MODE_CFR) |
| num_bufs_to_queue = 1; |
| else if (context->mode == VSG_MODE_VFR) |
| num_bufs_to_queue = 2; |
| |
| for (c = 0; c < num_bufs_to_queue; ++c) { |
| info = kzalloc(sizeof(*info), GFP_KERNEL); |
| |
| if (!info) { |
| WFD_MSG_ERR("Couldn't allocate memory in %s\n", |
| __func__); |
| goto err_locked; |
| } |
| |
| buf_to_encode = context->last_buffer; |
| |
| info->mdp_buf_info = buf_to_encode->mdp_buf_info; |
| info->flags = 0; |
| INIT_LIST_HEAD(&info->node); |
| |
| list_add_tail(&info->node, &context->free_queue.node); |
| WFD_MSG_DBG("Regenerated frame with paddr %p\n", |
| (void *)info->mdp_buf_info.paddr); |
| } |
| } |
| |
| for (c = 0; c < num_bufs_to_queue; ++c) { |
| new_work = kzalloc(sizeof(*new_work), GFP_KERNEL); |
| if (!new_work) { |
| WFD_MSG_ERR("Unable to allocate memory" |
| "to queue buffer\n"); |
| goto err_locked; |
| } |
| |
| INIT_WORK(&new_work->work, vsg_work_func); |
| new_work->context = context; |
| queue_work(context->work_queue, &new_work->work); |
| } |
| |
| err_locked: |
| mutex_unlock(&context->mutex); |
| kfree(work); |
| } |
| |
| static enum hrtimer_restart vsg_threshold_timeout_func(struct hrtimer *timer) |
| { |
| struct vsg_context *context = NULL; |
| struct vsg_work *task = NULL; |
| |
| task = kzalloc(sizeof(*task), GFP_ATOMIC); |
| context = container_of(timer, struct vsg_context, |
| threshold_timer); |
| if (!task) { |
| WFD_MSG_ERR("Out of memory in %s", __func__); |
| goto threshold_err_bad_param; |
| } else if (!context) { |
| WFD_MSG_ERR("Context not proper in %s", __func__); |
| goto threshold_err_no_context; |
| } |
| |
| INIT_WORK(&task->work, vsg_timer_helper_func); |
| task->context = context; |
| |
| queue_work(context->work_queue, &task->work); |
| threshold_err_bad_param: |
| hrtimer_forward_now(&context->threshold_timer, ns_to_ktime( |
| context->max_frame_interval)); |
| return HRTIMER_RESTART; |
| threshold_err_no_context: |
| return HRTIMER_NORESTART; |
| } |
| |
| int vsg_init(struct v4l2_subdev *sd, u32 val) |
| { |
| return 0; |
| } |
| |
| static int vsg_open(struct v4l2_subdev *sd, void *arg) |
| { |
| struct vsg_context *context = NULL; |
| |
| if (!arg || !sd) |
| return -EINVAL; |
| |
| context = kzalloc(sizeof(*context), GFP_KERNEL); |
| INIT_LIST_HEAD(&context->free_queue.node); |
| INIT_LIST_HEAD(&context->busy_queue.node); |
| |
| context->vmops = *(struct vsg_msg_ops *)arg; |
| context->work_queue = create_singlethread_workqueue("v4l-vsg"); |
| |
| context->frame_interval = DEFAULT_FRAME_INTERVAL; |
| context->max_frame_interval = DEFAULT_MAX_FRAME_INTERVAL; |
| |
| hrtimer_init(&context->threshold_timer, CLOCK_MONOTONIC, |
| HRTIMER_MODE_REL); |
| context->threshold_timer.function = vsg_threshold_timeout_func; |
| |
| context->last_buffer = NULL; |
| context->mode = DEFAULT_MODE; |
| context->state = VSG_STATE_NONE; |
| mutex_init(&context->mutex); |
| |
| sd->dev_priv = context; |
| return 0; |
| } |
| |
| static int vsg_close(struct v4l2_subdev *sd) |
| { |
| struct vsg_context *context = NULL; |
| |
| if (!sd) |
| return -EINVAL; |
| |
| context = (struct vsg_context *)sd->dev_priv; |
| destroy_workqueue(context->work_queue); |
| kfree(context); |
| return 0; |
| } |
| |
| static int vsg_start(struct v4l2_subdev *sd) |
| { |
| struct vsg_context *context = NULL; |
| |
| if (!sd) { |
| WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); |
| return -EINVAL; |
| } |
| |
| context = (struct vsg_context *)sd->dev_priv; |
| |
| if (context->state == VSG_STATE_STARTED) { |
| WFD_MSG_ERR("VSG not stopped, start not allowed\n"); |
| return -EINPROGRESS; |
| } else if (context->state == VSG_STATE_ERROR) { |
| WFD_MSG_ERR("VSG in error state, not allowed to restart\n"); |
| return -ENOTRECOVERABLE; |
| } |
| |
| context->state = VSG_STATE_STARTED; |
| hrtimer_start(&context->threshold_timer, ns_to_ktime(context-> |
| max_frame_interval), HRTIMER_MODE_REL); |
| return 0; |
| } |
| |
| static int vsg_stop(struct v4l2_subdev *sd) |
| { |
| struct vsg_context *context = NULL; |
| |
| if (!sd) { |
| WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); |
| return -EINVAL; |
| } |
| |
| context = (struct vsg_context *)sd->dev_priv; |
| |
| mutex_lock(&context->mutex); |
| context->state = VSG_STATE_STOPPED; |
| { /*delete pending buffers as we're not going to encode them*/ |
| struct list_head *pos, *next; |
| list_for_each_safe(pos, next, &context->free_queue.node) { |
| struct vsg_buf_info *temp = |
| list_entry(pos, struct vsg_buf_info, node); |
| list_del(&temp->node); |
| kfree(temp); |
| } |
| } |
| |
| hrtimer_cancel(&context->threshold_timer); |
| |
| mutex_unlock(&context->mutex); |
| |
| flush_workqueue(context->work_queue); |
| return 0; |
| } |
| |
| static long vsg_queue_buffer(struct v4l2_subdev *sd, void *arg) |
| { |
| struct vsg_context *context = NULL; |
| struct vsg_buf_info *buf_info = kzalloc(sizeof(*buf_info), GFP_KERNEL); |
| int rc = 0; |
| bool push = false; |
| |
| if (!arg || !sd) { |
| WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); |
| rc = -EINVAL; |
| goto queue_err_bad_param; |
| } else if (!buf_info) { |
| WFD_MSG_ERR("ERROR, out of memory in %s\n", __func__); |
| rc = -ENOMEM; |
| goto queue_err_bad_param; |
| } |
| |
| context = (struct vsg_context *)sd->dev_priv; |
| mutex_lock(&context->mutex); |
| |
| *buf_info = *(struct vsg_buf_info *)arg; |
| INIT_LIST_HEAD(&buf_info->node); |
| buf_info->flags = 0; |
| ktime_get_ts(&buf_info->time); |
| |
| WFD_MSG_DBG("Queue frame with paddr %p\n", |
| (void *)buf_info->mdp_buf_info.paddr); |
| |
| { /*return pending buffers as we're not going to encode them*/ |
| struct list_head *pos, *next; |
| list_for_each_safe(pos, next, &context->free_queue.node) { |
| struct vsg_buf_info *temp = |
| list_entry(pos, struct vsg_buf_info, node); |
| bool is_last_buffer = context->last_buffer && |
| mdp_buf_info_equals( |
| &context->last_buffer->mdp_buf_info, |
| &temp->mdp_buf_info); |
| |
| list_del(&temp->node); |
| |
| if (!is_last_buffer && |
| !(temp->flags & VSG_NEVER_RELEASE)) { |
| vsg_release_input_buffer(context, temp); |
| kfree(temp); |
| } |
| } |
| } |
| |
| list_add_tail(&buf_info->node, &context->free_queue.node); |
| |
| if (context->mode == VSG_MODE_VFR) { |
| if (!context->last_buffer) |
| push = true; |
| else { |
| struct timespec diff = timespec_sub(buf_info->time, |
| context->last_buffer->time); |
| struct timespec temp = ns_to_timespec( |
| context->frame_interval); |
| |
| if (timespec_compare(&diff, &temp) >= 0) |
| push = true; |
| } |
| } else if (context->mode == VSG_MODE_CFR) { |
| if (!context->last_buffer) { |
| push = true; |
| /* |
| * We need to reset the timer after pushing the buffer |
| * otherwise, diff between two consecutive frames might |
| * be less than max_frame_interval (for just one sample) |
| */ |
| hrtimer_forward_now(&context->threshold_timer, |
| ns_to_ktime(context->max_frame_interval)); |
| } |
| } |
| |
| if (push) { |
| struct vsg_work *new_work = |
| kzalloc(sizeof(*new_work), GFP_KERNEL); |
| |
| INIT_WORK(&new_work->work, vsg_work_func); |
| new_work->context = context; |
| queue_work(context->work_queue, &new_work->work); |
| } |
| |
| mutex_unlock(&context->mutex); |
| queue_err_bad_param: |
| if (rc < 0) |
| kfree(buf_info); |
| |
| return rc; |
| } |
| |
| static long vsg_return_ip_buffer(struct v4l2_subdev *sd, void *arg) |
| { |
| struct vsg_context *context = NULL; |
| struct vsg_buf_info *buf_info, *last_buffer, |
| *expected_buffer; |
| int rc = 0; |
| |
| if (!arg || !sd) { |
| WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); |
| rc = -EINVAL; |
| goto return_ip_buf_err_bad_param; |
| } |
| |
| context = (struct vsg_context *)sd->dev_priv; |
| mutex_lock(&context->mutex); |
| buf_info = (struct vsg_buf_info *)arg; |
| last_buffer = context->last_buffer; |
| |
| expected_buffer = list_first_entry(&context->busy_queue.node, |
| struct vsg_buf_info, node); |
| |
| WFD_MSG_DBG("Return frame with paddr %p\n", |
| (void *)buf_info->mdp_buf_info.paddr); |
| |
| if (!expected_buffer) { |
| WFD_MSG_ERR("Unexpectedly received buffer from enc with " |
| "paddr %p\n", (void *)buf_info->mdp_buf_info.paddr); |
| goto return_ip_buf_bad_buf; |
| } |
| |
| expected_buffer->flags &= ~VSG_BUF_BEING_ENCODED; |
| if (mdp_buf_info_equals(&expected_buffer->mdp_buf_info, |
| &buf_info->mdp_buf_info)) { |
| bool is_same_buffer = context->last_buffer && |
| mdp_buf_info_equals( |
| &context->last_buffer->mdp_buf_info, |
| &expected_buffer->mdp_buf_info); |
| |
| list_del(&expected_buffer->node); |
| if (!is_same_buffer && |
| !(expected_buffer->flags & VSG_NEVER_RELEASE)) { |
| vsg_release_input_buffer(context, expected_buffer); |
| kfree(expected_buffer); |
| } |
| } else { |
| WFD_MSG_ERR("Returned buffer %p is not latest buffer, " |
| "expected %p\n", |
| (void *)buf_info->mdp_buf_info.paddr, |
| (void *)expected_buffer->mdp_buf_info.paddr); |
| rc = -EINVAL; |
| goto return_ip_buf_bad_buf; |
| } |
| |
| return_ip_buf_bad_buf: |
| mutex_unlock(&context->mutex); |
| return_ip_buf_err_bad_param: |
| return rc; |
| } |
| |
| static long vsg_set_frame_interval(struct v4l2_subdev *sd, void *arg) |
| { |
| struct vsg_context *context = NULL; |
| int64_t interval; |
| |
| if (!arg || !sd) { |
| WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); |
| return -EINVAL; |
| } |
| |
| context = (struct vsg_context *)sd->dev_priv; |
| interval = *(int64_t *)arg; |
| |
| if (interval <= 0) { |
| WFD_MSG_ERR("ERROR, invalid interval %lld into %s\n", |
| interval, __func__); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&context->mutex); |
| |
| context->frame_interval = interval; |
| if (interval > context->max_frame_interval) { |
| WFD_MSG_WARN("Changing max frame interval from %lld to %lld\n", |
| context->max_frame_interval, interval); |
| context->max_frame_interval = interval; |
| } |
| |
| mutex_unlock(&context->mutex); |
| return 0; |
| } |
| |
| static long vsg_get_frame_interval(struct v4l2_subdev *sd, void *arg) |
| { |
| struct vsg_context *context = NULL; |
| |
| if (!arg || !sd) { |
| WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); |
| return -EINVAL; |
| } |
| |
| context = (struct vsg_context *)sd->dev_priv; |
| mutex_lock(&context->mutex); |
| *(int64_t *)arg = context->frame_interval; |
| mutex_unlock(&context->mutex); |
| |
| return 0; |
| } |
| |
| static long vsg_set_max_frame_interval(struct v4l2_subdev *sd, void *arg) |
| { |
| struct vsg_context *context = NULL; |
| int64_t interval; |
| |
| if (!arg || !sd) { |
| WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); |
| return -EINVAL; |
| } |
| |
| context = (struct vsg_context *)sd->dev_priv; |
| interval = *(int64_t *)arg; |
| |
| if (interval <= 0) { |
| WFD_MSG_ERR("ERROR, invalid interval %lld into %s\n", |
| interval, __func__); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&context->mutex); |
| |
| context->max_frame_interval = interval; |
| if (interval < context->frame_interval) { |
| WFD_MSG_WARN("Changing frame interval from %lld to %lld\n", |
| context->frame_interval, interval); |
| context->frame_interval = interval; |
| } |
| |
| mutex_unlock(&context->mutex); |
| |
| return 0; |
| } |
| |
| static long vsg_get_max_frame_interval(struct v4l2_subdev *sd, void *arg) |
| { |
| struct vsg_context *context = NULL; |
| |
| if (!arg || !sd) { |
| WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); |
| return -EINVAL; |
| } |
| |
| context = (struct vsg_context *)sd->dev_priv; |
| mutex_lock(&context->mutex); |
| *(int64_t *)arg = context->max_frame_interval; |
| mutex_unlock(&context->mutex); |
| |
| return 0; |
| } |
| |
| static long vsg_set_mode(struct v4l2_subdev *sd, void *arg) |
| { |
| struct vsg_context *context = NULL; |
| enum vsg_modes *mode = NULL; |
| int rc = 0; |
| |
| if (!arg || !sd) { |
| WFD_MSG_ERR("ERROR, invalid arguments into %s\n", __func__); |
| rc = -EINVAL; |
| goto set_mode_err_bad_parm; |
| } |
| |
| context = (struct vsg_context *)sd->dev_priv; |
| mutex_lock(&context->mutex); |
| mode = arg; |
| |
| switch (*mode) { |
| case VSG_MODE_CFR: |
| context->max_frame_interval = context->frame_interval; |
| /*fall through*/ |
| case VSG_MODE_VFR: |
| context->mode = *mode; |
| break; |
| default: |
| context->mode = DEFAULT_MODE; |
| rc = -EINVAL; |
| goto set_mode_err_bad_mode; |
| break; |
| } |
| |
| set_mode_err_bad_mode: |
| mutex_unlock(&context->mutex); |
| set_mode_err_bad_parm: |
| return rc; |
| } |
| |
| long vsg_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg) |
| { |
| int rc = 0; |
| |
| WFD_MSG_DBG("VSG ioctl: %d\n", cmd); |
| if (sd == NULL) |
| return -EINVAL; |
| |
| switch (cmd) { |
| case VSG_OPEN: |
| rc = vsg_open(sd, arg); |
| break; |
| case VSG_CLOSE: |
| rc = vsg_close(sd); |
| break; |
| case VSG_START: |
| rc = vsg_start(sd); |
| break; |
| case VSG_STOP: |
| rc = vsg_stop(sd); |
| break; |
| case VSG_Q_BUFFER: |
| rc = vsg_queue_buffer(sd, arg); |
| break; |
| case VSG_RETURN_IP_BUFFER: |
| rc = vsg_return_ip_buffer(sd, arg); |
| break; |
| case VSG_GET_FRAME_INTERVAL: |
| rc = vsg_get_frame_interval(sd, arg); |
| break; |
| case VSG_SET_FRAME_INTERVAL: |
| rc = vsg_set_frame_interval(sd, arg); |
| break; |
| case VSG_GET_MAX_FRAME_INTERVAL: |
| rc = vsg_get_max_frame_interval(sd, arg); |
| break; |
| case VSG_SET_MAX_FRAME_INTERVAL: |
| rc = vsg_set_max_frame_interval(sd, arg); |
| break; |
| case VSG_SET_MODE: |
| rc = vsg_set_mode(sd, arg); |
| break; |
| default: |
| rc = -ENOTSUPP; |
| break; |
| } |
| |
| return rc; |
| } |