blob: 8121471420c3a6b6e3c47ae959e49e9aeede024e [file] [log] [blame]
/* Copyright (c) 2012-2013, 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/bitmap.h>
#include <linux/completion.h>
#include <linux/ion.h>
#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <mach/iommu_domains.h>
#include <media/msm_vidc.h>
#include <media/v4l2-subdev.h>
#include "enc-subdev.h"
#include "wfd-util.h"
#define BUF_TYPE_OUTPUT V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE
#define BUF_TYPE_INPUT V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE
static struct ion_client *venc_ion_client;
static long venc_secure(struct v4l2_subdev *sd);
struct index_bitmap {
unsigned long *bitmap;
int size;
int size_bits; /*Size in bits, not necessarily size/8 */
};
struct venc_inst {
void *vidc_context;
struct mutex lock;
struct venc_msg_ops vmops;
struct mem_region registered_input_bufs, registered_output_bufs;
struct index_bitmap free_input_indices, free_output_indices;
int num_output_planes, num_input_planes;
struct task_struct *callback_thread;
bool callback_thread_running;
struct completion dq_complete, cmd_complete;
bool secure;
struct workqueue_struct *fill_buf_wq;
};
struct fill_buf_work {
struct venc_inst *inst;
struct mem_region *mregion;
struct work_struct work;
};
static const int subscribed_events[] = {
V4L2_EVENT_MSM_VIDC_CLOSE_DONE,
V4L2_EVENT_MSM_VIDC_FLUSH_DONE,
V4L2_EVENT_MSM_VIDC_SYS_ERROR,
};
int venc_load_fw(struct v4l2_subdev *sd)
{
/*No need to explicitly load the fw */
return 0;
}
int venc_init(struct v4l2_subdev *sd, u32 val)
{
if (!venc_ion_client)
venc_ion_client = msm_ion_client_create(-1, "wfd_enc_subdev");
return venc_ion_client ? 0 : -ENOMEM;
}
static int invalidate_cache(struct ion_client *client,
struct mem_region *mregion)
{
if (!client || !mregion) {
WFD_MSG_ERR(
"Failed to flush ion buffer: invalid client or region\n");
return -EINVAL;
} else if (!mregion->ion_handle) {
WFD_MSG_ERR(
"Failed to flush ion buffer: not an ion buffer\n");
return -EINVAL;
}
return msm_ion_do_cache_op(client,
mregion->ion_handle,
mregion->kvaddr,
mregion->size,
ION_IOC_INV_CACHES);
}
static int next_free_index(struct index_bitmap *index_bitmap)
{
int index = find_first_zero_bit(index_bitmap->bitmap,
index_bitmap->size_bits);
return (index >= index_bitmap->size_bits) ?
-1 : index;
}
static int mark_index_busy(struct index_bitmap *index_bitmap, int index)
{
if (index > index_bitmap->size_bits) {
WFD_MSG_WARN("Marking unknown index as busy\n");
return -EINVAL;
}
set_bit(index, index_bitmap->bitmap);
return 0;
}
static int mark_index_free(struct index_bitmap *index_bitmap, int index)
{
if (index > index_bitmap->size_bits) {
WFD_MSG_WARN("Marking unknown index as free\n");
return -EINVAL;
}
clear_bit(index, index_bitmap->bitmap);
return 0;
}
static int get_list_len(struct mem_region *list)
{
struct mem_region *curr = NULL;
int index = 0;
list_for_each_entry(curr, &list->list, list) {
++index;
}
return index;
}
static struct mem_region *get_registered_mregion(struct mem_region *list,
struct mem_region *mregion)
{
struct mem_region *curr = NULL;
list_for_each_entry(curr, &list->list, list) {
if (unlikely(mem_region_equals(curr, mregion)))
return curr;
}
return NULL;
}
static int venc_vidc_callback_thread(void *data)
{
struct venc_inst *inst = data;
WFD_MSG_DBG("Starting callback thread\n");
while (!kthread_should_stop()) {
bool dequeue_buf = false;
struct v4l2_buffer buffer = {0};
struct v4l2_event event = {0};
int num_planes = 0;
int flags = msm_vidc_wait(inst->vidc_context);
if (flags & POLLERR) {
WFD_MSG_ERR("Encoder reported error\n");
break;
}
if (flags & POLLPRI) {
bool bail_out = false;
msm_vidc_dqevent(inst->vidc_context, &event);
switch (event.type) {
case V4L2_EVENT_MSM_VIDC_CLOSE_DONE:
WFD_MSG_DBG("enc callback thread shutting " \
"down normally\n");
bail_out = true;
break;
case V4L2_EVENT_MSM_VIDC_SYS_ERROR:
inst->vmops.on_event(inst->vmops.cbdata,
VENC_EVENT_HARDWARE_ERROR);
bail_out = true;
break;
default:
WFD_MSG_INFO("Got unknown event %d, ignoring\n",
event.type);
}
complete_all(&inst->cmd_complete);
if (bail_out)
break;
}
if (flags & POLLIN || flags & POLLRDNORM) {
buffer.type = BUF_TYPE_OUTPUT;
dequeue_buf = true;
num_planes = inst->num_output_planes;
WFD_MSG_DBG("Output buffer ready!\n");
}
if (flags & POLLOUT || flags & POLLWRNORM) {
buffer.type = BUF_TYPE_INPUT;
dequeue_buf = true;
num_planes = inst->num_input_planes;
WFD_MSG_DBG("Input buffer ready!\n");
}
if (dequeue_buf) {
int rc = 0;
struct v4l2_plane *planes = NULL;
struct mem_region *curr = NULL, *mregion = NULL;
struct list_head *reg_bufs = NULL;
struct index_bitmap *bitmap = NULL;
planes = kzalloc(sizeof(*planes) * num_planes,
GFP_KERNEL);
buffer.m.planes = planes;
buffer.length = 1;
buffer.memory = V4L2_MEMORY_USERPTR;
rc = msm_vidc_dqbuf(inst->vidc_context, &buffer);
if (rc) {
WFD_MSG_ERR("Error dequeuing buffer " \
"from vidc: %d", rc);
goto abort_dequeue;
}
reg_bufs = buffer.type == BUF_TYPE_OUTPUT ?
&inst->registered_output_bufs.list :
&inst->registered_input_bufs.list;
bitmap = buffer.type == BUF_TYPE_OUTPUT ?
&inst->free_output_indices :
&inst->free_input_indices;
list_for_each_entry(curr, reg_bufs, list) {
if ((u32)curr->paddr ==
buffer.m.planes[0].m.userptr) {
mregion = curr;
break;
}
}
if (!mregion) {
WFD_MSG_ERR("Got done msg for unknown buf\n");
goto abort_dequeue;
}
if (buffer.type == BUF_TYPE_OUTPUT &&
inst->vmops.op_buffer_done) {
struct vb2_buffer *vb =
(struct vb2_buffer *)mregion->cookie;
vb->v4l2_buf.flags = buffer.flags;
vb->v4l2_buf.timestamp = buffer.timestamp;
vb->v4l2_planes[0].bytesused =
buffer.m.planes[0].bytesused;
/* Buffer is on its way to userspace, so
* invalidate the cache */
rc = invalidate_cache(venc_ion_client, mregion);
if (rc) {
WFD_MSG_WARN(
"Failed to invalidate cache %d\n",
rc);
/* Not fatal, move on */
}
inst->vmops.op_buffer_done(
inst->vmops.cbdata, 0, vb);
} else if (buffer.type == BUF_TYPE_INPUT &&
inst->vmops.ip_buffer_done) {
inst->vmops.ip_buffer_done(
inst->vmops.cbdata,
0, mregion);
}
complete_all(&inst->dq_complete);
mutex_lock(&inst->lock);
mark_index_free(bitmap, buffer.index);
mutex_unlock(&inst->lock);
abort_dequeue:
kfree(planes);
}
}
WFD_MSG_DBG("Exiting callback thread\n");
mutex_lock(&inst->lock);
inst->callback_thread_running = false;
mutex_unlock(&inst->lock);
return 0;
}
static long set_default_properties(struct venc_inst *inst)
{
struct v4l2_control ctrl = {0};
int rc;
/* Set the IDR period as 1. The venus core doesn't give
* the sps/pps for I-frames, only IDR. */
ctrl.id = V4L2_CID_MPEG_VIDC_VIDEO_IDR_PERIOD;
ctrl.value = 1;
rc = msm_vidc_s_ctrl(inst->vidc_context, &ctrl);
if (rc)
WFD_MSG_WARN("Failed to set IDR period\n");
/* Set the default rc mode to VBR/VFR, client can change later */
ctrl.id = V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL;
ctrl.value = V4L2_CID_MPEG_VIDC_VIDEO_RATE_CONTROL_VBR_VFR;
rc = msm_vidc_s_ctrl(inst->vidc_context, &ctrl);
if (rc)
WFD_MSG_WARN("Failed to set rc mode\n");
return 0;
}
static int subscribe_events(struct venc_inst *inst)
{
struct v4l2_event_subscription event = {0};
int c = 0, rc = 0;
for (c = 0; c < ARRAY_SIZE(subscribed_events); c++) {
event.type = subscribed_events[c];
rc = msm_vidc_subscribe_event(inst->vidc_context, &event);
if (rc) {
WFD_MSG_ERR("Failed to subscribe to event 0x%x\n",
subscribed_events[c]);
return rc;
}
}
return 0;
}
static void unsubscribe_events(struct venc_inst *inst)
{
struct v4l2_event_subscription event = {0};
int c = 0, rc = 0;
for (c = 0; c < ARRAY_SIZE(subscribed_events); c++) {
event.type = subscribed_events[c];
rc = msm_vidc_unsubscribe_event(inst->vidc_context, &event);
if (rc) {
/* Just log and ignore failiures */
WFD_MSG_WARN("Failed to unsubscribe to event 0x%x\n",
subscribed_events[c]);
}
}
}
static long venc_open(struct v4l2_subdev *sd, void *arg)
{
struct venc_inst *inst = NULL;
struct venc_msg_ops *vmops = arg;
int rc = 0;
if (!vmops) {
WFD_MSG_ERR("Callbacks required for %s\n", __func__);
rc = -EINVAL;
goto venc_open_fail;
} else if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
rc = -EINVAL;
goto venc_open_fail;
}
inst = kzalloc(sizeof(*inst), GFP_KERNEL);
if (!inst) {
WFD_MSG_ERR("Failed to allocate memory\n");
rc = -EINVAL;
goto venc_open_fail;
}
inst->vmops = *vmops;
inst->secure = vmops->secure; /* We need to inform vidc, but defer
until after s_fmt() */
INIT_LIST_HEAD(&inst->registered_output_bufs.list);
INIT_LIST_HEAD(&inst->registered_input_bufs.list);
init_completion(&inst->dq_complete);
init_completion(&inst->cmd_complete);
mutex_init(&inst->lock);
inst->fill_buf_wq = create_singlethread_workqueue("venc_vidc_ftb_wq");
if (!inst->fill_buf_wq) {
WFD_MSG_ERR("Failed to create ftb wq\n");
rc = -ENOMEM;
goto vidc_wq_create_fail;
}
inst->vidc_context = msm_vidc_open(MSM_VIDC_CORE_0, MSM_VIDC_ENCODER);
if (!inst->vidc_context) {
WFD_MSG_ERR("Failed to create vidc context\n");
rc = -ENXIO;
goto vidc_open_fail;
}
rc = subscribe_events(inst);
if (rc) {
WFD_MSG_ERR("Failed to subscribe to events\n");
goto vidc_subscribe_fail;
}
inst->callback_thread = kthread_run(venc_vidc_callback_thread, inst,
"venc_vidc_callback_thread");
if (IS_ERR(inst->callback_thread)) {
WFD_MSG_ERR("Failed to create callback thread\n");
rc = PTR_ERR(inst->callback_thread);
inst->callback_thread = NULL;
goto vidc_kthread_create_fail;
}
inst->callback_thread_running = true;
sd->dev_priv = inst;
vmops->cookie = inst;
return 0;
vidc_kthread_create_fail:
unsubscribe_events(inst);
vidc_subscribe_fail:
msm_vidc_close(inst->vidc_context);
vidc_open_fail:
destroy_workqueue(inst->fill_buf_wq);
vidc_wq_create_fail:
kfree(inst);
venc_open_fail:
return rc;
}
static long venc_close(struct v4l2_subdev *sd, void *arg)
{
struct venc_inst *inst = NULL;
struct v4l2_encoder_cmd enc_cmd = {0};
int rc = 0;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
rc = -EINVAL;
goto venc_close_fail;
}
inst = (struct venc_inst *)sd->dev_priv;
enc_cmd.cmd = V4L2_ENC_CMD_STOP;
msm_vidc_encoder_cmd(inst->vidc_context, &enc_cmd);
wait_for_completion(&inst->cmd_complete);
destroy_workqueue(inst->fill_buf_wq);
if (inst->callback_thread && inst->callback_thread_running)
kthread_stop(inst->callback_thread);
unsubscribe_events(inst);
rc = msm_vidc_close(inst->vidc_context);
if (rc)
WFD_MSG_WARN("Failed to close vidc context\n");
kfree(inst);
sd->dev_priv = inst = NULL;
venc_close_fail:
return rc;
}
static long venc_get_buffer_req(struct v4l2_subdev *sd, void *arg)
{
int rc = 0;
struct venc_inst *inst = NULL;
struct bufreq *bufreq = arg;
struct v4l2_requestbuffers v4l2_bufreq = {0};
struct v4l2_format v4l2_format = {0};
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
rc = -EINVAL;
goto venc_buf_req_fail;
} else if (!arg) {
WFD_MSG_ERR("Invalid buffer requirements\n");
rc = -EINVAL;
goto venc_buf_req_fail;
}
inst = (struct venc_inst *)sd->dev_priv;
/* Get buffer count */
v4l2_bufreq = (struct v4l2_requestbuffers) {
.count = bufreq->count,
.type = BUF_TYPE_OUTPUT,
.memory = V4L2_MEMORY_USERPTR,
};
rc = msm_vidc_reqbufs(inst->vidc_context, &v4l2_bufreq);
if (rc) {
WFD_MSG_ERR("Failed getting buffer requirements\n");
goto venc_buf_req_fail;
}
/* Get buffer size */
v4l2_format.type = BUF_TYPE_OUTPUT;
rc = msm_vidc_g_fmt(inst->vidc_context, &v4l2_format);
if (rc) {
WFD_MSG_ERR("Failed getting OP buffer size\n");
goto venc_buf_req_fail;
}
bufreq->count = v4l2_bufreq.count;
bufreq->size = v4l2_format.fmt.pix_mp.plane_fmt[0].sizeimage;
inst->free_output_indices.size_bits = bufreq->count;
inst->free_output_indices.size = roundup(bufreq->count,
sizeof(unsigned long)) / sizeof(unsigned long);
inst->free_output_indices.bitmap = kzalloc(inst->free_output_indices.
size, GFP_KERNEL);
venc_buf_req_fail:
return rc;
}
static long venc_set_buffer_req(struct v4l2_subdev *sd, void *arg)
{
int rc = 0;
struct venc_inst *inst = NULL;
struct bufreq *bufreq = arg;
struct v4l2_requestbuffers v4l2_bufreq = {0};
struct v4l2_format v4l2_format = {0};
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
rc = -EINVAL;
goto venc_buf_req_fail;
} else if (!arg) {
WFD_MSG_ERR("Invalid buffer requirements\n");
rc = -EINVAL;
goto venc_buf_req_fail;
}
inst = (struct venc_inst *)sd->dev_priv;
/* Attempt to set buffer count */
v4l2_bufreq = (struct v4l2_requestbuffers) {
.count = bufreq->count,
.type = BUF_TYPE_INPUT,
.memory = V4L2_MEMORY_USERPTR,
};
rc = msm_vidc_reqbufs(inst->vidc_context, &v4l2_bufreq);
if (rc) {
WFD_MSG_ERR("Failed getting buffer requirements");
goto venc_buf_req_fail;
}
/* Get buffer size */
v4l2_format.type = BUF_TYPE_INPUT;
rc = msm_vidc_g_fmt(inst->vidc_context, &v4l2_format);
if (rc) {
WFD_MSG_ERR("Failed getting OP buffer size\n");
goto venc_buf_req_fail;
}
bufreq->count = v4l2_bufreq.count;
bufreq->size = ALIGN(v4l2_format.fmt.pix_mp.plane_fmt[0].sizeimage,
inst->secure ? SZ_1M : SZ_4K);
inst->free_input_indices.size_bits = bufreq->count;
inst->free_input_indices.size = roundup(bufreq->count,
sizeof(unsigned long)) / sizeof(unsigned long);
inst->free_input_indices.bitmap = kzalloc(inst->free_input_indices.
size, GFP_KERNEL);
venc_buf_req_fail:
return rc;
}
static long venc_start(struct v4l2_subdev *sd)
{
struct venc_inst *inst = NULL;
int rc = 0;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
rc = -EINVAL;
goto venc_start_fail;
}
inst = (struct venc_inst *)sd->dev_priv;
if (set_default_properties(inst))
WFD_MSG_WARN("Couldn't set default properties\n");
rc = msm_vidc_streamon(inst->vidc_context, BUF_TYPE_OUTPUT);
if (rc) {
WFD_MSG_ERR("Failed to streamon vidc's output port");
goto venc_start_fail;
}
rc = msm_vidc_streamon(inst->vidc_context, BUF_TYPE_INPUT);
if (rc) {
WFD_MSG_ERR("Failed to streamon vidc's input port");
goto venc_start_fail;
}
venc_start_fail:
return rc;
}
static long venc_stop(struct v4l2_subdev *sd)
{
struct venc_inst *inst = NULL;
int rc = 0;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
rc = -EINVAL;
goto venc_stop_fail;
}
inst = (struct venc_inst *)sd->dev_priv;
flush_workqueue(inst->fill_buf_wq);
rc = msm_vidc_streamoff(inst->vidc_context, BUF_TYPE_INPUT);
if (rc) {
WFD_MSG_ERR("Failed to streamoff vidc's input port");
goto venc_stop_fail;
}
rc = msm_vidc_streamoff(inst->vidc_context, BUF_TYPE_OUTPUT);
if (rc) {
WFD_MSG_ERR("Failed to streamoff vidc's output port");
goto venc_stop_fail;
}
venc_stop_fail:
return rc;
}
static void populate_planes(struct v4l2_plane *planes, int num_planes,
void *userptr, int size)
{
int c = 0;
planes[0] = (struct v4l2_plane) {
.length = size,
.m.userptr = (int)userptr,
};
for (c = 1; c < num_planes - 1; ++c) {
planes[c] = (struct v4l2_plane) {
.length = 0,
.m.userptr = (int)NULL,
};
}
}
static long venc_set_input_buffer(struct v4l2_subdev *sd, void *arg)
{
int rc = 0;
struct venc_inst *inst = NULL;
struct v4l2_buffer buf = {0};
struct v4l2_plane *planes = NULL;
struct mem_region *mregion = arg;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
rc = -EINVAL;
goto set_input_buffer_fail;
} else if (!arg) {
WFD_MSG_ERR("Invalid input buffer\n");
rc = -EINVAL;
goto set_input_buffer_fail;
}
inst = (struct venc_inst *)sd->dev_priv;
if (get_registered_mregion(&inst->registered_input_bufs, mregion)) {
WFD_MSG_ERR("Duplicate input buffer\n");
rc = -EEXIST;
goto set_input_buffer_fail;
}
mregion = kzalloc(sizeof(*mregion), GFP_KERNEL);
planes = kzalloc(sizeof(*planes) * inst->num_input_planes, GFP_KERNEL);
if (!mregion || !planes)
return -ENOMEM;
*mregion = *(struct mem_region *)arg;
populate_planes(planes, inst->num_input_planes,
mregion->paddr, mregion->size);
buf = (struct v4l2_buffer) {
.index = get_list_len(&inst->registered_input_bufs),
.type = BUF_TYPE_INPUT,
.bytesused = 0,
.memory = V4L2_MEMORY_USERPTR,
.m.planes = planes,
.length = inst->num_input_planes,
};
WFD_MSG_DBG("Prepare %p with index, %d",
(void *)buf.m.planes[0].m.userptr, buf.index);
rc = msm_vidc_prepare_buf(inst->vidc_context, &buf);
if (rc) {
WFD_MSG_ERR("Failed to prepare input buffer\n");
goto set_input_buffer_fail;
}
list_add_tail(&mregion->list, &inst->registered_input_bufs.list);
kfree(planes);
return 0;
set_input_buffer_fail:
kfree(mregion);
kfree(planes);
return rc;
}
#ifdef CONFIG_MSM_WFD_DEBUG
static void *venc_map_kernel(struct ion_client *client,
struct ion_handle *handle)
{
return ion_map_kernel(client, handle);
}
static void venc_unmap_kernel(struct ion_client *client,
struct ion_handle *handle)
{
ion_unmap_kernel(client, handle);
}
#else
static void *venc_map_kernel(struct ion_client *client,
struct ion_handle *handle)
{
return NULL;
}
static void venc_unmap_kernel(struct ion_client *client,
struct ion_handle *handle)
{
return;
}
#endif
static int venc_map_user_to_kernel(struct venc_inst *inst,
struct mem_region *mregion)
{
int rc = 0;
unsigned long size = 0, align_req = 0, flags = 0;
int domain = 0, partition = 0;
if (!mregion) {
rc = -EINVAL;
goto venc_map_fail;
}
align_req = inst->secure ? SZ_1M : SZ_4K;
if (mregion->size % align_req != 0) {
WFD_MSG_ERR("Memregion not aligned to %ld\n", align_req);
rc = -EINVAL;
goto venc_map_fail;
}
mregion->ion_handle = ion_import_dma_buf(venc_ion_client, mregion->fd);
if (IS_ERR_OR_NULL(mregion->ion_handle)) {
rc = PTR_ERR(mregion->ion_handle);
WFD_MSG_ERR("Failed to get handle: %p, %d, %d, %d\n",
venc_ion_client, mregion->fd, mregion->offset, rc);
mregion->ion_handle = NULL;
goto venc_map_fail;
}
rc = ion_handle_get_flags(venc_ion_client, mregion->ion_handle, &flags);
if (rc) {
WFD_MSG_ERR("Failed to get ion flags %d\n", rc);
goto venc_map_fail;
}
mregion->kvaddr = inst->secure ? NULL :
venc_map_kernel(venc_ion_client, mregion->ion_handle);
if (inst->secure) {
rc = msm_ion_secure_buffer(venc_ion_client,
mregion->ion_handle, VIDEO_BITSTREAM, 0);
if (rc) {
WFD_MSG_ERR("Failed to secure output buffer\n");
goto venc_map_iommu_map_fail;
}
}
rc = msm_vidc_get_iommu_domain_partition(inst->vidc_context,
flags, BUF_TYPE_OUTPUT, &domain, &partition);
if (rc) {
WFD_MSG_ERR("Failed to get domain for output buffer\n");
goto venc_domain_fail;
}
rc = ion_map_iommu(venc_ion_client, mregion->ion_handle,
domain, partition, align_req, 0,
(unsigned long *)&mregion->paddr, &size, 0, 0);
if (rc) {
WFD_MSG_ERR("Failed to map into iommu\n");
goto venc_map_iommu_map_fail;
} else if (size < mregion->size) {
WFD_MSG_ERR("Failed to iommu map the correct size\n");
goto venc_map_iommu_size_fail;
}
return 0;
venc_map_iommu_size_fail:
ion_unmap_iommu(venc_ion_client, mregion->ion_handle,
domain, partition);
venc_domain_fail:
if (inst->secure)
msm_ion_unsecure_buffer(venc_ion_client, mregion->ion_handle);
venc_map_iommu_map_fail:
if (!inst->secure && !IS_ERR_OR_NULL(mregion->kvaddr))
venc_unmap_kernel(venc_ion_client, mregion->ion_handle);
venc_map_fail:
return rc;
}
static int venc_unmap_user_to_kernel(struct venc_inst *inst,
struct mem_region *mregion)
{
unsigned long flags = 0;
int domain = 0, partition = 0, rc = 0;
if (!mregion || !mregion->ion_handle)
return 0;
rc = ion_handle_get_flags(venc_ion_client, mregion->ion_handle, &flags);
if (rc) {
WFD_MSG_ERR("Failed to get ion flags %d\n", rc);
return rc;
}
rc = msm_vidc_get_iommu_domain_partition(inst->vidc_context,
flags, BUF_TYPE_OUTPUT, &domain, &partition);
if (rc) {
WFD_MSG_ERR("Failed to get domain for input buffer\n");
return rc;
}
if (mregion->paddr) {
ion_unmap_iommu(venc_ion_client, mregion->ion_handle,
domain, partition);
mregion->paddr = NULL;
}
if (!IS_ERR_OR_NULL(mregion->kvaddr)) {
venc_unmap_kernel(venc_ion_client, mregion->ion_handle);
mregion->kvaddr = NULL;
}
if (inst->secure)
msm_ion_unsecure_buffer(venc_ion_client, mregion->ion_handle);
ion_free(venc_ion_client, mregion->ion_handle);
return rc;
}
static long venc_set_output_buffer(struct v4l2_subdev *sd, void *arg)
{
int rc = 0;
struct venc_inst *inst = NULL;
struct v4l2_buffer buf = {0};
struct v4l2_plane *planes = NULL;
struct mem_region *mregion = arg;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
rc = -EINVAL;
goto venc_set_output_buffer_fail;
} else if (!mregion) {
WFD_MSG_ERR("Invalid output buffer\n");
rc = -EINVAL;
goto venc_set_output_buffer_fail;
}
inst = (struct venc_inst *)sd->dev_priv;
/* Check if buf already registered */
if (get_registered_mregion(&inst->registered_output_bufs, mregion)) {
WFD_MSG_ERR("Duplicate output buffer\n");
rc = -EEXIST;
goto venc_set_output_buffer_fail;
}
mregion = kzalloc(sizeof(*mregion), GFP_KERNEL);
planes = kzalloc(sizeof(*planes) * inst->num_output_planes, GFP_KERNEL);
if (!mregion || !planes) {
WFD_MSG_ERR("Failed to allocate memory\n");
goto venc_set_output_buffer_fail;
}
*mregion = *(struct mem_region *)arg;
INIT_LIST_HEAD(&mregion->list);
rc = venc_map_user_to_kernel(inst, mregion);
if (rc) {
WFD_MSG_ERR("Failed to map output buffer\n");
goto venc_set_output_buffer_map_fail;
}
populate_planes(planes, inst->num_output_planes,
mregion->paddr, mregion->size);
buf = (struct v4l2_buffer) {
.index = get_list_len(&inst->registered_output_bufs),
.type = BUF_TYPE_OUTPUT,
.bytesused = 0,
.memory = V4L2_MEMORY_USERPTR,
.m.planes = planes,
.length = inst->num_output_planes,
};
WFD_MSG_DBG("Prepare %p with index, %d",
(void *)buf.m.planes[0].m.userptr, buf.index);
rc = msm_vidc_prepare_buf(inst->vidc_context, &buf);
if (rc) {
WFD_MSG_ERR("Failed to prepare output buffer\n");
goto venc_set_output_buffer_prepare_fail;
}
list_add_tail(&mregion->list, &inst->registered_output_bufs.list);
kfree(planes);
return 0;
venc_set_output_buffer_prepare_fail:
venc_unmap_user_to_kernel(inst, mregion);
venc_set_output_buffer_map_fail:
kfree(mregion);
kfree(planes);
venc_set_output_buffer_fail:
return rc;
}
static long venc_set_format(struct v4l2_subdev *sd, void *arg)
{
struct venc_inst *inst = NULL;
struct v4l2_format *fmt = arg, temp;
int rc = 0, align_req = 0;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
rc = -EINVAL;
goto venc_set_format_fail;
} else if (!fmt) {
WFD_MSG_ERR("Invalid format\n");
rc = -EINVAL;
goto venc_set_format_fail;
} else if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
WFD_MSG_ERR("Invalid buffer type %d\n", fmt->type);
rc = -ENOTSUPP;
goto venc_set_format_fail;
}
inst = (struct venc_inst *)sd->dev_priv;
temp = (struct v4l2_format) {
.type = BUF_TYPE_OUTPUT,
.fmt.pix_mp = (struct v4l2_pix_format_mplane) {
.width = fmt->fmt.pix.width,
.height = fmt->fmt.pix.height,
.pixelformat = fmt->fmt.pix.pixelformat,
},
};
rc = msm_vidc_s_fmt(inst->vidc_context, &temp);
if (rc) {
WFD_MSG_ERR("Failed to format for output port\n");
goto venc_set_format_fail;
} else if (!temp.fmt.pix_mp.num_planes) {
WFD_MSG_ERR("No. of planes for output buffers make no sense\n");
rc = -EINVAL;
goto venc_set_format_fail;
}
align_req = inst->secure ? SZ_1M : SZ_4K;
fmt->fmt.pix.sizeimage = ALIGN(temp.fmt.pix_mp.plane_fmt[0].sizeimage,
align_req);
inst->num_output_planes = temp.fmt.pix_mp.num_planes;
temp.type = BUF_TYPE_INPUT;
temp.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;
rc = msm_vidc_s_fmt(inst->vidc_context, &temp);
inst->num_input_planes = temp.fmt.pix_mp.num_planes;
if (rc) {
WFD_MSG_ERR("Failed to format for input port\n");
goto venc_set_format_fail;
}
/* If the device was secured previously, we need to inform vidc _now_ */
if (inst->secure) {
rc = venc_secure(sd);
if (rc) {
WFD_MSG_ERR("Failed secure vidc\n");
goto venc_set_format_fail;
}
}
venc_set_format_fail:
return rc;
}
static long venc_set_framerate(struct v4l2_subdev *sd, void *arg)
{
struct venc_inst *inst = NULL;
struct v4l2_streamparm p = {0};
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
return -EINVAL;
} else if (!arg) {
WFD_MSG_ERR("Invalid framerate\n");
return -EINVAL;
}
inst = (struct venc_inst *)sd->dev_priv;
p.type = BUF_TYPE_INPUT;
p.parm.output.timeperframe = *(struct v4l2_fract *)arg;
return msm_vidc_s_parm(inst->vidc_context, &p);
}
static long fill_outbuf(struct venc_inst *inst, struct mem_region *mregion)
{
struct v4l2_buffer buffer = {0};
struct v4l2_plane plane = {0};
int index = 0, rc = 0;
if (!mregion) {
WFD_MSG_ERR("Output buffer not registered\n");
return -ENOENT;
}
plane = (struct v4l2_plane) {
.length = mregion->size,
.m.userptr = (u32)mregion->paddr,
};
while (true) {
mutex_lock(&inst->lock);
index = next_free_index(&inst->free_output_indices);
mutex_unlock(&inst->lock);
if (index < 0)
wait_for_completion(&inst->dq_complete);
else
break;
}
buffer = (struct v4l2_buffer) {
.index = index,
.type = BUF_TYPE_OUTPUT,
.memory = V4L2_MEMORY_USERPTR,
.m.planes = &plane,
.length = 1,
};
WFD_MSG_DBG("Fill buffer %p with index, %d",
(void *)buffer.m.planes[0].m.userptr, buffer.index);
rc = msm_vidc_qbuf(inst->vidc_context, &buffer);
if (!rc) {
mutex_lock(&inst->lock);
mark_index_busy(&inst->free_output_indices, index);
mutex_unlock(&inst->lock);
}
return rc;
}
static void fill_outbuf_helper(struct work_struct *work)
{
int rc;
struct fill_buf_work *fbw =
container_of(work, struct fill_buf_work, work);
rc = fill_outbuf(fbw->inst, fbw->mregion);
if (rc) {
struct vb2_buffer *vb = NULL;
WFD_MSG_ERR("Failed to fill buffer async\n");
vb = (struct vb2_buffer *)fbw->mregion->cookie;
vb->v4l2_buf.flags = 0;
vb->v4l2_buf.timestamp = ns_to_timeval(-1);
vb->v4l2_planes[0].bytesused = 0;
fbw->inst->vmops.op_buffer_done(
fbw->inst->vmops.cbdata, rc, vb);
}
kfree(fbw);
}
static long venc_fill_outbuf(struct v4l2_subdev *sd, void *arg)
{
struct fill_buf_work *fbw;
struct venc_inst *inst = NULL;
struct mem_region *mregion;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
return -EINVAL;
} else if (!arg) {
WFD_MSG_ERR("Invalid output buffer ot fill\n");
return -EINVAL;
}
inst = (struct venc_inst *)sd->dev_priv;
mregion = get_registered_mregion(&inst->registered_output_bufs, arg);
if (!mregion) {
WFD_MSG_ERR("Output buffer not registered\n");
return -ENOENT;
}
fbw = kzalloc(sizeof(*fbw), GFP_KERNEL);
if (!fbw) {
WFD_MSG_ERR("Couldn't allocate memory\n");
return -ENOMEM;
}
INIT_WORK(&fbw->work, fill_outbuf_helper);
fbw->inst = inst;
fbw->mregion = mregion;
/* XXX: The need for a wq to qbuf to vidc is necessitated as a
* workaround for a bug in the v4l2 framework. VIDIOC_QBUF from
* triggers a down_read(current->mm->mmap_sem). There is another
* _read(..) as msm_vidc_qbuf() depends on videobuf2 framework
* as well. However, a _write(..) after the first _read() by a
* different driver will prevent the second _read(...) from
* suceeding.
*
* As we can't modify the framework, we're working around by issue
* by queuing in a different thread effectively.
*/
queue_work(inst->fill_buf_wq, &fbw->work);
return 0;
}
static long venc_encode_frame(struct v4l2_subdev *sd, void *arg)
{
struct venc_inst *inst = NULL;
struct venc_buf_info *venc_buf = arg;
struct mem_region *mregion = NULL;
struct v4l2_buffer buffer = {0};
struct v4l2_plane plane = {0};
int index = 0, rc = 0;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
return -EINVAL;
} else if (!venc_buf) {
WFD_MSG_ERR("Invalid output buffer ot fill\n");
return -EINVAL;
}
inst = (struct venc_inst *)sd->dev_priv;
mregion = venc_buf->mregion;
plane = (struct v4l2_plane) {
.length = mregion->size,
.m.userptr = (u32)mregion->paddr,
.bytesused = mregion->size,
};
while (true) {
mutex_lock(&inst->lock);
index = next_free_index(&inst->free_input_indices);
mutex_unlock(&inst->lock);
if (index < 0)
wait_for_completion(&inst->dq_complete);
else
break;
}
buffer = (struct v4l2_buffer) {
.index = index,
.type = BUF_TYPE_INPUT,
.timestamp = ns_to_timeval(venc_buf->timestamp),
.memory = V4L2_MEMORY_USERPTR,
.m.planes = &plane,
.length = 1,
};
WFD_MSG_DBG("Encode buffer %p with index, %d",
(void *)buffer.m.planes[0].m.userptr, buffer.index);
rc = msm_vidc_qbuf(inst->vidc_context, &buffer);
if (!rc) {
mutex_lock(&inst->lock);
mark_index_busy(&inst->free_input_indices, index);
mutex_unlock(&inst->lock);
}
return rc;
}
static long venc_alloc_recon_buffers(struct v4l2_subdev *sd, void *arg)
{
/* vidc driver allocates internally on streamon */
return 0;
}
static long venc_free_buffer(struct venc_inst *inst, int type,
struct mem_region *to_free, bool unmap_user_buffer)
{
struct mem_region *mregion = NULL;
struct mem_region *buf_list = NULL;
if (type == BUF_TYPE_OUTPUT) {
buf_list = &inst->registered_output_bufs;
} else if (type == BUF_TYPE_INPUT) {
buf_list = &inst->registered_input_bufs;
} else {
WFD_MSG_ERR("Trying to free a buffer of unknown type\n");
return -EINVAL;
}
mregion = get_registered_mregion(buf_list, to_free);
if (!mregion) {
WFD_MSG_ERR("Buffer not registered, cannot free\n");
return -ENOENT;
}
if (unmap_user_buffer) {
int rc = venc_unmap_user_to_kernel(inst, mregion);
if (rc)
WFD_MSG_WARN("Unable to unmap user buffer\n");
}
list_del(&mregion->list);
kfree(mregion);
return 0;
}
static long venc_free_output_buffer(struct v4l2_subdev *sd, void *arg)
{
int rc = 0;
struct venc_inst *inst = NULL;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
rc = -EINVAL;
goto venc_free_output_buffer_fail;
} else if (!arg) {
WFD_MSG_ERR("Invalid output buffer\n");
rc = -EINVAL;
goto venc_free_output_buffer_fail;
}
inst = (struct venc_inst *)sd->dev_priv;
return venc_free_buffer(inst, BUF_TYPE_OUTPUT, arg, true);
venc_free_output_buffer_fail:
return rc;
}
static long venc_flush_buffers(struct v4l2_subdev *sd, void *arg)
{
struct venc_inst *inst = NULL;
struct v4l2_encoder_cmd enc_cmd = {0};
int rc = 0;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
rc = -EINVAL;
goto venc_flush_buffers_fail;
}
inst = (struct venc_inst *)sd->dev_priv;
flush_workqueue(inst->fill_buf_wq);
enc_cmd.cmd = V4L2_ENC_QCOM_CMD_FLUSH;
enc_cmd.flags = V4L2_QCOM_CMD_FLUSH_OUTPUT |
V4L2_QCOM_CMD_FLUSH_CAPTURE;
msm_vidc_encoder_cmd(inst->vidc_context, &enc_cmd);
wait_for_completion(&inst->cmd_complete);
venc_flush_buffers_fail:
return rc;
}
static long venc_free_input_buffer(struct v4l2_subdev *sd, void *arg)
{
int rc = 0;
struct venc_inst *inst = NULL;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
rc = -EINVAL;
goto venc_free_input_buffer_fail;
} else if (!arg) {
WFD_MSG_ERR("Invalid output buffer\n");
rc = -EINVAL;
goto venc_free_input_buffer_fail;
}
inst = (struct venc_inst *)sd->dev_priv;
return venc_free_buffer(inst, BUF_TYPE_INPUT, arg, false);
venc_free_input_buffer_fail:
return rc;
}
static long venc_free_recon_buffers(struct v4l2_subdev *sd, void *arg)
{
/* vidc driver takes care of this */
return 0;
}
static long venc_set_property(struct v4l2_subdev *sd, void *arg)
{
struct venc_inst *inst = NULL;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
return -EINVAL;
}
inst = (struct venc_inst *)sd->dev_priv;
return msm_vidc_s_ctrl(inst->vidc_context, (struct v4l2_control *)arg);
}
static long venc_get_property(struct v4l2_subdev *sd, void *arg)
{
struct venc_inst *inst = NULL;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
return -EINVAL;
}
inst = (struct venc_inst *)sd->dev_priv;
return msm_vidc_g_ctrl(inst->vidc_context, (struct v4l2_control *)arg);
}
long venc_mmap(struct v4l2_subdev *sd, void *arg)
{
struct mem_region_map *mmap = arg;
struct mem_region *mregion = NULL;
unsigned long size = 0, align_req = 0, flags = 0;
int domain = 0, partition = 0, rc = 0;
void *paddr = NULL;
struct venc_inst *inst = NULL;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
return -EINVAL;
} else if (!mmap || !mmap->mregion) {
WFD_MSG_ERR("Memregion required for %s\n", __func__);
return -EINVAL;
}
inst = (struct venc_inst *)sd->dev_priv;
mregion = mmap->mregion;
align_req = inst->secure ? SZ_1M : SZ_4K;
if (mregion->size % align_req != 0) {
WFD_MSG_ERR("Memregion not aligned to %ld\n", align_req);
rc = -EINVAL;
goto venc_map_bad_align;
}
rc = ion_handle_get_flags(mmap->ion_client, mregion->ion_handle,
&flags);
if (rc) {
WFD_MSG_ERR("Failed to get ion flags %d\n", rc);
goto venc_map_bad_align;
}
if (inst->secure) {
rc = msm_ion_secure_buffer(mmap->ion_client,
mregion->ion_handle, VIDEO_PIXEL, 0);
if (rc) {
WFD_MSG_ERR("Failed to secure input buffer\n");
goto venc_map_bad_align;
}
}
rc = msm_vidc_get_iommu_domain_partition(inst->vidc_context,
flags, BUF_TYPE_INPUT, &domain, &partition);
if (rc) {
WFD_MSG_ERR("Failed to get domain for output buffer\n");
goto venc_map_domain_fail;
}
rc = ion_map_iommu(mmap->ion_client, mregion->ion_handle,
domain, partition, align_req, 0,
(unsigned long *)&paddr, &size, 0, 0);
if (rc) {
WFD_MSG_ERR("Failed to get physical addr %d\n", rc);
paddr = NULL;
goto venc_map_bad_align;
} else if (size < mregion->size) {
WFD_MSG_ERR("Failed to map enough memory\n");
rc = -ENOMEM;
goto venc_map_iommu_size_fail;
}
mregion->paddr = paddr;
return rc;
venc_map_iommu_size_fail:
ion_unmap_iommu(venc_ion_client, mregion->ion_handle,
domain, partition);
venc_map_domain_fail:
if (inst->secure)
msm_ion_unsecure_buffer(mmap->ion_client, mregion->ion_handle);
venc_map_bad_align:
return rc;
}
long venc_munmap(struct v4l2_subdev *sd, void *arg)
{
struct mem_region_map *mmap = arg;
struct mem_region *mregion = NULL;
struct venc_inst *inst = NULL;
unsigned long flags = 0;
int domain = 0, partition = 0, rc = 0;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
return -EINVAL;
} else if (!mmap || !mmap->mregion) {
WFD_MSG_ERR("Memregion required for %s\n", __func__);
return -EINVAL;
}
inst = (struct venc_inst *)sd->dev_priv;
mregion = mmap->mregion;
rc = ion_handle_get_flags(mmap->ion_client,
mregion->ion_handle, &flags);
if (rc) {
WFD_MSG_ERR("Failed to get ion flags %d\n", rc);
return rc;
}
rc = msm_vidc_get_iommu_domain_partition(inst->vidc_context,
flags, BUF_TYPE_INPUT, &domain, &partition);
if (rc) {
WFD_MSG_ERR("Failed to get domain for input buffer\n");
return rc;
}
if (mregion->paddr) {
ion_unmap_iommu(mmap->ion_client, mregion->ion_handle,
domain, partition);
mregion->paddr = NULL;
}
if (inst->secure)
msm_ion_unsecure_buffer(mmap->ion_client, mregion->ion_handle);
return rc;
}
static long venc_set_framerate_mode(struct v4l2_subdev *sd,
void *arg)
{
/* TODO: Unsupported for now, but return false success
* to preserve binary compatibility for userspace apps
* across targets */
return 0;
}
static long venc_secure(struct v4l2_subdev *sd)
{
struct venc_inst *inst = NULL;
struct v4l2_control ctrl;
int rc = 0;
if (!sd) {
WFD_MSG_ERR("Subdevice required for %s\n", __func__);
return -EINVAL;
}
inst = sd->dev_priv;
if (!list_empty(&inst->registered_input_bufs.list) ||
!list_empty(&inst->registered_output_bufs.list)) {
WFD_MSG_ERR(
"Attempt to (un)secure encoder not allowed after registering buffers"
);
rc = -EEXIST;
}
ctrl.id = V4L2_CID_MPEG_VIDC_VIDEO_SECURE;
rc = msm_vidc_s_ctrl(inst->vidc_context, &ctrl);
if (rc) {
WFD_MSG_ERR("Failed to move vidc into secure mode\n");
goto secure_fail;
}
secure_fail:
return rc;
}
long venc_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
{
long rc = 0;
switch (cmd) {
case OPEN:
rc = venc_open(sd, arg);
break;
case CLOSE:
rc = venc_close(sd, arg);
break;
case ENCODE_START:
rc = venc_start(sd);
break;
case ENCODE_FRAME:
venc_encode_frame(sd, arg);
break;
case ENCODE_STOP:
rc = venc_stop(sd);
break;
case SET_PROP:
rc = venc_set_property(sd, arg);
break;
case GET_PROP:
rc = venc_get_property(sd, arg);
break;
case GET_BUFFER_REQ:
rc = venc_get_buffer_req(sd, arg);
break;
case SET_BUFFER_REQ:
rc = venc_set_buffer_req(sd, arg);
break;
case FREE_BUFFER:
break;
case FILL_OUTPUT_BUFFER:
rc = venc_fill_outbuf(sd, arg);
break;
case SET_FORMAT:
rc = venc_set_format(sd, arg);
break;
case SET_FRAMERATE:
rc = venc_set_framerate(sd, arg);
break;
case SET_INPUT_BUFFER:
rc = venc_set_input_buffer(sd, arg);
break;
case SET_OUTPUT_BUFFER:
rc = venc_set_output_buffer(sd, arg);
break;
case ALLOC_RECON_BUFFERS:
rc = venc_alloc_recon_buffers(sd, arg);
break;
case FREE_OUTPUT_BUFFER:
rc = venc_free_output_buffer(sd, arg);
break;
case FREE_INPUT_BUFFER:
rc = venc_free_input_buffer(sd, arg);
break;
case FREE_RECON_BUFFERS:
rc = venc_free_recon_buffers(sd, arg);
break;
case ENCODE_FLUSH:
rc = venc_flush_buffers(sd, arg);
break;
case ENC_MMAP:
rc = venc_mmap(sd, arg);
break;
case ENC_MUNMAP:
rc = venc_munmap(sd, arg);
break;
case SET_FRAMERATE_MODE:
rc = venc_set_framerate_mode(sd, arg);
break;
default:
WFD_MSG_ERR("Unknown ioctl %d to enc-subdev\n", cmd);
rc = -ENOTSUPP;
break;
}
return rc;
}