blob: fa68c1edbb72a2cc84e5d68aa0d20c3589ef3226 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* drivers/amlogic/media/video_processor/videotunnel/videotunnel.c
*
* Copyright (C) 2017 Amlogic, Inc. 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 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.
*
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/fdtable.h>
#include <linux/file.h>
#include <linux/freezer.h>
#include <linux/miscdevice.h>
#include <linux/of.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
#include <linux/dma-buf.h>
#include <linux/mm.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/debugfs.h>
#include <linux/random.h>
#include <linux/sched/clock.h>
#include <linux/amlogic/aml_sync_api.h>
#include <linux/sched/task.h>
#include <asm-generic/bug.h>
#include "videotunnel_priv.h"
#include "videotunnel.h"
#define DEVICE_NAME "videotunnel"
#define MAX_VIDEO_TUNNEL_ID 64
#define VT_CMD_FIFO_SIZE 128
static struct vt_dev *vdev;
static struct mutex debugfs_mutex;
static struct vt_instance dummy_instance;
enum {
VT_DEBUG_NONE = 0,
VT_DEBUG_USER = 1U << 0,
VT_DEBUG_BUFFERS = 1U << 1,
VT_DEBUG_CMD = 1U << 2,
VT_DEBUG_FILE = 1U << 3,
VT_DEBUG_VSYNC = 1U << 4,
};
static u32 vt_debug_mask = VT_DEBUG_USER;
module_param_named(debug_mask, vt_debug_mask, uint, 0644);
#define vt_debug(mask, x...) \
do { \
if (vt_debug_mask & (mask)) \
pr_info(x); \
} while (0)
static const char *vt_debug_buffer_status_to_string(int status)
{
const char *status_str;
switch (status) {
case VT_BUFFER_QUEUE:
status_str = "queued";
break;
case VT_BUFFER_DEQUEUE:
status_str = "dequeued";
break;
case VT_BUFFER_ACQUIRE:
status_str = "acquired";
break;
case VT_BUFFER_RELEASE:
status_str = "released";
break;
case VT_BUFFER_FREE:
status_str = "free";
break;
default:
status_str = "unknown";
}
return status_str;
}
static const char *vt_debug_mode_status_to_string(int status)
{
const char *status_str;
switch (status) {
case VT_MODE_BLOCK:
status_str = "BLOCK";
break;
case VT_MODE_NONE_BLOCK:
status_str = "NONE BLOCK";
break;
case VT_MODE_GAME:
status_str = "GAME";
break;
default:
status_str = "unknown";
}
return status_str;
}
static int vt_debug_instance_show(struct seq_file *s, void *unused)
{
struct vt_instance *instance = s->private;
int i;
int size_to_con;
int size_to_pro;
int size_cmd;
int ref_count;
mutex_lock(&debugfs_mutex);
mutex_lock(&instance->lock);
size_to_con = kfifo_len(&instance->fifo_to_consumer);
size_to_pro = kfifo_len(&instance->fifo_to_producer);
size_cmd = kfifo_len(&instance->fifo_cmd);
ref_count = atomic_read(&instance->ref.refcount.refs);
seq_printf(s, "tunnel (%p) id=%d, ref=%d, fcount=%d, mode=%s\n",
instance,
instance->id,
ref_count,
instance->fcount,
vt_debug_mode_status_to_string(instance->mode));
seq_puts(s, "-----------------------------------------------\n");
if (instance->consumer)
seq_printf(s, "consumer session (%s) %p\n",
instance->consumer->display_name,
instance->consumer);
if (instance->producer)
seq_printf(s, "producer session (%s) %p\n",
instance->producer->display_name,
instance->producer);
seq_puts(s, "-----------------------------------------------\n");
seq_printf(s, "to consumer fifo size:%d\n", size_to_con);
seq_printf(s, "to producer fifo size:%d\n", size_to_pro);
seq_printf(s, "cmd fifo size:%d\n", size_cmd);
seq_puts(s, "-----------------------------------------------\n");
seq_puts(s, "buffers:\n");
for (i = 0; i < VT_POOL_SIZE; i++) {
struct vt_buffer *buffer = &instance->vt_buffers[i];
int status = buffer->item.buffer_status;
if (status == VT_BUFFER_QUEUE || status == VT_BUFFER_ACQUIRE ||
status == VT_BUFFER_RELEASE)
seq_printf(s, " buffer produce_fd(%d) status(%s) timestamp(%lld)\n",
buffer->buffer_fd_pro,
vt_debug_buffer_status_to_string(status),
buffer->item.time_stamp);
}
seq_puts(s, "-----------------------------------------------\n");
seq_printf(s, "total acquire: %ld\n", instance->state.acquire_count);
seq_printf(s, "total release: %ld (%ld+%ld(prev))\n",
instance->state.release_count +
instance->state.release_invalid,
instance->state.release_count,
instance->state.release_invalid);
seq_printf(s, "total queue: %ld\n", instance->state.queue_count);
seq_printf(s, "total dequeue: %ld (%ld+%ld(prev))\n",
instance->state.dequeue_count +
instance->state.dequeue_invalid,
instance->state.dequeue_count,
instance->state.dequeue_invalid);
seq_puts(s, "-----------------------------------------------\n");
mutex_unlock(&instance->lock);
mutex_unlock(&debugfs_mutex);
return 0;
}
static int vt_debug_instance_open(struct inode *inode, struct file *file)
{
return single_open(file,
vt_debug_instance_show,
inode->i_private);
}
static const struct file_operations debug_instance_fops = {
.owner = THIS_MODULE,
.open = vt_debug_instance_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int vt_debug_session_show(struct seq_file *s, void *unused)
{
struct vt_session *session = s->private;
mutex_lock(&debugfs_mutex);
seq_printf(s, "session(%s) %p\n",
session->display_name, session);
seq_printf(s, "role: %s cid: %ld\n",
session->role == VT_ROLE_PRODUCER ?
"producer" : (session->role == VT_ROLE_CONSUMER ?
"consumer" : "invalid"), session->cid);
seq_printf(s, "mode: %s\n",
vt_debug_mode_status_to_string(session->mode));
seq_puts(s, "-----------------------------------------------\n");
mutex_unlock(&debugfs_mutex);
return 0;
}
static int vt_debug_session_open(struct inode *inode, struct file *file)
{
return single_open(file,
vt_debug_session_show,
inode->i_private);
}
static const struct file_operations debug_session_fops = {
.owner = THIS_MODULE,
.open = vt_debug_session_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int vt_debug_state_show(struct seq_file *s, void *unused)
{
struct vt_state *state = s->private;
mutex_lock(&debugfs_mutex);
seq_puts(s, "-----------------------------------------------\n");
seq_puts(s, "fence status:\n");
seq_printf(s, "total fence fget: %ld\n", state->fence_get);
seq_printf(s, "total fence fput: %ld (%ld(dequeue)-%ld(null)+%ld(fput))\n",
state->dequeue_count - state->null_fence + state->fence_put,
state->dequeue_count, state->null_fence, state->fence_put);
seq_puts(s, "-----------------------------------------------\n");
seq_puts(s, "buffer status:\n");
seq_printf(s, "total acquire: %ld\n", state->acquire_count);
seq_printf(s, "total release: %ld (%ld+%ld(prev))\n",
state->release_count + state->release_invalid,
state->release_count, state->release_invalid);
seq_printf(s, "total queue: %ld\n", state->queue_count);
seq_printf(s, "total dequeue: %ld (%ld+%ld(prev))\n",
state->dequeue_count + state->dequeue_invalid,
state->dequeue_count, state->dequeue_invalid);
seq_puts(s, "-----------------------------------------------\n");
seq_printf(s, "total buffer fget: %ld\n", state->buffer_get);
seq_printf(s, "total buffer fput: %ld (%ld(fput)+%ld(close))\n",
state->buffer_put + state->buffer_close,
state->buffer_put, state->buffer_close);
seq_puts(s, "-----------------------------------------------\n");
mutex_unlock(&debugfs_mutex);
return 0;
}
static int vt_debug_state_open(struct inode *inode, struct file *file)
{
return single_open(file,
vt_debug_state_show,
inode->i_private);
}
static const struct file_operations debug_state_fops = {
.owner = THIS_MODULE,
.open = vt_debug_state_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int vt_debug_limit_show(struct seq_file *sf, void *data)
{
struct vt_dev *vdev = sf->private;
seq_puts(sf, "when the buffer size in vt rearch the limit size then you can dequeue\n");
seq_printf(sf, "current: %d\n", vdev->limit);
return 0;
}
static int vt_debug_limit_open(struct inode *inode, struct file *file)
{
return single_open(file,
vt_debug_limit_show,
inode->i_private);
}
static ssize_t vt_debug_limit_write(struct file *file, const char __user *ubuf,
size_t len, loff_t *offp)
{
char *buf = NULL;
int limits = 0;
struct seq_file *sf = file->private_data;
struct vt_dev *vdev = sf->private;
if (len <= 0)
return -EINVAL;
buf = kmalloc(len, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (copy_from_user(buf, ubuf, len)) {
kfree(buf);
return -EFAULT;
}
if (kstrtoint(buf, 0, &limits) == 0)
vdev->limit = (limits > 0) ? limits : 0;
kfree(buf);
return len;
}
static const struct file_operations debug_limit_fops = {
.owner = THIS_MODULE,
.open = vt_debug_limit_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.write = vt_debug_limit_write,
};
static int vt_close_fd(struct vt_session *session, unsigned int fd)
{
int ret;
if (!session->task)
return -ESRCH;
ret = __close_fd(session->task->files, fd);
/* can't restart close syscall because file table entry was cleared */
if (unlikely(ret == -ERESTARTSYS ||
ret == -ERESTARTNOINTR ||
ret == -ERESTARTNOHAND ||
ret == -ERESTART_RESTARTBLOCK))
ret = -EINTR;
return ret;
}
static void vt_instance_destroy(struct kref *kref)
{
struct vt_instance *instance =
container_of(kref, struct vt_instance, ref);
struct vt_dev *dev = instance->dev;
struct vt_buffer *buffer = NULL;
struct vt_cmd *vcmd = NULL;
int i;
mutex_lock(&debugfs_mutex);
mutex_lock(&dev->instance_lock);
rb_erase(&instance->node, &dev->instances);
if (idr_find(&dev->instance_idr, instance->id))
idr_remove(&dev->instance_idr, instance->id);
for (i = 0; i < instance->id; i++) {
/* remove the dummy pointer ID */
if (idr_find(&dev->instance_idr, i) == &dummy_instance)
idr_remove(&dev->instance_idr, i);
}
/* destroy fifo to conusmer */
mutex_lock(&instance->lock);
if (!kfifo_is_empty(&instance->fifo_to_consumer)) {
while (kfifo_get(&instance->fifo_to_consumer, &buffer)) {
/* put file */
if (buffer->file_buffer) {
fput(buffer->file_buffer);
instance->fcount--;
dev->state.buffer_put++;
instance->state.buffer_put++;
}
buffer->item.buffer_status = VT_BUFFER_FREE;
}
}
kfifo_free(&instance->fifo_to_consumer);
/* destroy fifo to producer */
if (!kfifo_is_empty(&instance->fifo_to_producer)) {
while (kfifo_get(&instance->fifo_to_producer, &buffer)) {
if (buffer->file_fence) {
fput(buffer->file_fence);
dev->state.fence_put++;
instance->state.fence_put++;
}
buffer->item.buffer_status = VT_BUFFER_FREE;
}
}
kfifo_free(&instance->fifo_to_producer);
mutex_unlock(&instance->lock);
/* destroy fifo cmd */
mutex_lock(&instance->cmd_lock);
if (!kfifo_is_empty(&instance->fifo_cmd)) {
while (kfifo_get(&instance->fifo_cmd, &vcmd))
kfree(vcmd);
}
kfifo_free(&instance->fifo_cmd);
mutex_unlock(&instance->cmd_lock);
debugfs_remove_recursive(instance->debug_root);
vt_debug(VT_DEBUG_USER, "vt [%d] destroy fcount:%d\n",
instance->id, instance->fcount);
kfree(instance);
mutex_unlock(&dev->instance_lock);
mutex_unlock(&debugfs_mutex);
}
static void vt_instance_get(struct vt_instance *instance)
{
kref_get(&instance->ref);
}
static int vt_instance_put(struct vt_instance *instance)
{
if (!instance)
return -ENOENT;
return kref_put(&instance->ref, vt_instance_destroy);
}
static struct vt_instance *vt_instance_create_lock(struct vt_dev *dev)
{
struct vt_instance *instance;
struct vt_instance *entry;
struct rb_node **p;
struct rb_node *parent = NULL;
int status;
int i;
instance = kzalloc(sizeof(*instance), GFP_KERNEL);
if (!instance)
return ERR_PTR(-ENOMEM);
instance->dev = dev;
instance->fcount = 0;
instance->mode = VT_MODE_NONE_BLOCK;
instance->used = false;
mutex_init(&instance->lock);
mutex_init(&instance->cmd_lock);
kref_init(&instance->ref);
status = kfifo_alloc(&instance->fifo_to_consumer,
VT_POOL_SIZE, GFP_KERNEL);
if (status)
goto setup_fail;
status = kfifo_alloc(&instance->fifo_to_producer,
VT_POOL_SIZE, GFP_KERNEL);
if (status)
goto setup_fail;
/* init fifo cmd */
status = kfifo_alloc(&instance->fifo_cmd, VT_CMD_FIFO_SIZE, GFP_KERNEL);
if (status)
goto setup_fail;
init_waitqueue_head(&instance->wait_producer);
init_waitqueue_head(&instance->wait_consumer);
init_waitqueue_head(&instance->wait_cmd);
/* set the buffer pool status to free */
for (i = 0; i < VT_POOL_SIZE; i++)
instance->vt_buffers[i].item.buffer_status = VT_BUFFER_FREE;
/* insert it to dev instances rb tree */
p = &dev->instances.rb_node;
while (*p) {
parent = *p;
entry = rb_entry(parent, struct vt_instance, node);
if (instance < entry)
p = &(*p)->rb_left;
else if (instance > entry)
p = &(*p)->rb_right;
else
break;
}
rb_link_node(&instance->node, parent, p);
rb_insert_color(&instance->node, &dev->instances);
return instance;
setup_fail:
kfree(instance);
return ERR_PTR(status);
}
static int vt_get_session_serial(const struct rb_root *root,
const char *name)
{
int serial = -1;
struct rb_node *node;
for (node = rb_first(root); node; node = rb_next(node)) {
struct vt_session *session =
rb_entry(node, struct vt_session, node);
if (strcmp(session->name, name))
continue;
serial = max(serial, session->display_serial);
}
return serial + 1;
}
static struct vt_session *vt_session_create_internal(struct vt_dev *dev,
const char *name)
{
struct vt_session *session;
struct task_struct *task = NULL;
struct rb_node **p;
struct rb_node *parent = NULL;
struct vt_session *entry;
if (!name) {
pr_err("%s: Name can not be null\n", __func__);
return ERR_PTR(-EINVAL);
}
session = kzalloc(sizeof(*session), GFP_KERNEL);
if (!session)
goto err_put_task_struct;
get_task_struct(current->group_leader);
task_lock(current->group_leader);
session->pid = task_pid_nr(current->group_leader);
/*
* don't bother to store task struct for kernel threads,
* they can't be killed anyway
*/
if (current->group_leader->flags & PF_KTHREAD) {
put_task_struct(current->group_leader);
task = NULL;
} else {
task = current->group_leader;
}
task_unlock(current->group_leader);
session->dev = dev;
session->task = task;
session->role = VT_ROLE_INVALID;
session->mode = VT_MODE_BLOCK;
session->cmd_status = 0;
init_waitqueue_head(&session->wait_producer);
init_waitqueue_head(&session->wait_consumer);
init_waitqueue_head(&session->wait_cmd);
session->name = kstrdup(name, GFP_KERNEL);
if (!session->name)
goto err_free_session;
down_write(&dev->session_lock);
session->display_serial =
vt_get_session_serial(&dev->sessions, name);
session->display_name = kasprintf(GFP_KERNEL, "%s-%d",
name, session->display_serial);
if (!session->display_name) {
up_write(&dev->session_lock);
goto err_free_session_name;
}
/* insert session to device rb tree */
p = &dev->sessions.rb_node;
while (*p) {
parent = *p;
entry = rb_entry(parent, struct vt_session, node);
if (session < entry)
p = &(*p)->rb_left;
else if (session > entry)
p = &(*p)->rb_right;
else
break;
}
rb_link_node(&session->node, parent, p);
rb_insert_color(&session->node, &dev->sessions);
/* add debug fs */
session->debug_root = debugfs_create_file(session->display_name,
0664,
dev->debug_root,
session,
&debug_session_fops);
up_write(&dev->session_lock);
vt_debug(VT_DEBUG_USER, "vt session %s create\n",
session->display_name);
return session;
err_free_session_name:
kfree(session->name);
err_free_session:
kfree(session);
err_put_task_struct:
if (task)
put_task_struct(current->group_leader);
return ERR_PTR(-ENOMEM);
}
/*
* when disconnect, release the buffer in session
*/
static void vt_session_trim_lock(struct vt_session *session,
struct vt_instance *instance)
{
struct vt_buffer *buffer = NULL;
if (!session || !instance)
return;
if (instance->producer && instance->producer == session) {
while (kfifo_get(&instance->fifo_to_producer, &buffer)) {
if (buffer->file_fence) {
fput(buffer->file_fence);
session->dev->state.fence_put++;
instance->state.fence_put++;
}
buffer->item.buffer_status = VT_BUFFER_FREE;
}
}
if (instance->consumer && instance->consumer == session) {
while (kfifo_get(&instance->fifo_to_consumer, &buffer)) {
if (buffer->file_buffer) {
fput(buffer->file_buffer);
instance->fcount--;
session->dev->state.buffer_put++;
instance->state.buffer_put++;
vt_debug(VT_DEBUG_FILE,
"vt [%d] session trim file(%p) buffer(%p) fcount=%d\n",
instance->id, buffer->file_buffer,
buffer, instance->fcount);
}
/* if still has producer, return the buffer to producer */
if (instance->producer) {
buffer->item.buffer_fd = buffer->buffer_fd_pro;
buffer->item.buffer_status = VT_BUFFER_RELEASE;
kfifo_put(&instance->fifo_to_producer, buffer);
vt_debug(VT_DEBUG_FILE,
"vt [%d] session trim buffer(%p) back to producer\n",
instance->id, buffer);
} else {
buffer->item.buffer_status = VT_BUFFER_FREE;
}
}
}
}
/*
* called when vt session released
* clean up instance connected session
*/
static int vt_instance_trim(struct vt_session *session)
{
struct vt_dev *dev = session->dev;
struct vt_instance *instance = NULL;
struct vt_buffer *buffer = NULL;
struct rb_node *n = NULL;
int i;
mutex_lock(&dev->instance_lock);
for (n = rb_first(&dev->instances); n; n = rb_next(n)) {
instance = rb_entry(n, struct vt_instance, node);
mutex_lock(&instance->lock);
if (instance->producer && instance->producer == session) {
while (kfifo_get(&instance->fifo_to_producer,
&buffer)) {
if (buffer->file_fence) {
fput(buffer->file_fence);
dev->state.fence_put++;
instance->state.fence_put++;
}
buffer->item.buffer_status = VT_BUFFER_FREE;
}
instance->producer = NULL;
instance->mode = VT_MODE_NONE_BLOCK;
}
if (instance->consumer && instance->consumer == session) {
while (kfifo_get(&instance->fifo_to_consumer,
&buffer)) {
if (buffer->file_buffer) {
fput(buffer->file_buffer);
instance->fcount--;
dev->state.buffer_put++;
instance->state.buffer_put++;
vt_debug(VT_DEBUG_FILE,
"vt [%d] instance trim file(%p) buffer(%p), fcount=%d\n",
instance->id,
buffer->file_buffer,
buffer, instance->fcount);
}
buffer->item.buffer_status = VT_BUFFER_FREE;
}
instance->consumer = NULL;
}
if (!instance->consumer && !instance->producer) {
while (kfifo_get(&instance->fifo_to_producer,
&buffer)) {
if (buffer->file_fence) {
fput(buffer->file_fence);
dev->state.fence_put++;
instance->state.fence_put++;
}
}
while (kfifo_get(&instance->fifo_to_consumer,
&buffer)) {
if (buffer->file_buffer) {
fput(buffer->file_buffer);
instance->fcount--;
dev->state.buffer_put++;
instance->state.buffer_put++;
vt_debug(VT_DEBUG_FILE,
"vt [%d] instance trim file(%p) buffer(%p) fcount=%d\n",
instance->id,
buffer->file_buffer,
buffer, instance->fcount);
}
}
/* set all instance buffer to free */
for (i = 0; i < VT_POOL_SIZE; i++)
instance->vt_buffers[i].item.buffer_status = VT_BUFFER_FREE;
/* reset status */
memset(&instance->state, 0, sizeof(instance->state));
instance->mode = VT_MODE_NONE_BLOCK;
}
mutex_unlock(&instance->lock);
}
mutex_unlock(&dev->instance_lock);
return 0;
}
void vt_session_destroy(struct vt_session *session)
{
struct vt_dev *dev = session->dev;
vt_debug(VT_DEBUG_USER, "vt session %s destroy\n",
session->display_name);
/* release dev session rb tree node */
down_write(&dev->session_lock);
if (session->task)
put_task_struct(session->task);
rb_erase(&session->node, &dev->sessions);
debugfs_remove_recursive(session->debug_root);
up_write(&dev->session_lock);
vt_instance_trim(session);
kfree(session->display_name);
kfree(session->name);
kfree(session);
}
EXPORT_SYMBOL(vt_session_destroy);
static int vt_open(struct inode *inode, struct file *filp)
{
struct miscdevice *miscdev = filp->private_data;
struct vt_dev *dev =
container_of(miscdev, struct vt_dev, mdev);
struct vt_session *session;
char debug_name[64];
snprintf(debug_name, 64, "%u", task_pid_nr(current->group_leader));
session = vt_session_create_internal(dev, debug_name);
if (IS_ERR(session))
return PTR_ERR(session);
filp->private_data = session;
return 0;
}
static int vt_release(struct inode *inode, struct file *filp)
{
struct vt_session *session = filp->private_data;
vt_session_destroy(session);
return 0;
}
static long vt_get_connected_id(void)
{
long cid;
cid = get_random_long();
if (cid == -1)
return 0;
return cid;
}
static int vt_alloc_id_process(struct vt_alloc_id_data *data,
struct vt_session *session)
{
int i;
int ret = 0;
char name[64];
struct vt_dev *dev = session->dev;
struct vt_instance *instance = NULL;
struct rb_node *n = NULL;
mutex_lock(&dev->instance_lock);
/* find an unused vt instance */
for (n = rb_first(&dev->instances); n; n = rb_next(n)) {
instance = rb_entry(n, struct vt_instance, node);
mutex_lock(&instance->lock);
/* no consumer and producer, not new alloced */
if (!instance->consumer &&
!instance->producer &&
instance->used) {
data->tunnel_id = instance->id;
instance->used = false;
vt_debug(VT_DEBUG_USER, "vt alloc find instance [%d], ref %d\n",
instance->id,
atomic_read(&instance->ref.refcount.refs));
mutex_unlock(&instance->lock);
mutex_unlock(&dev->instance_lock);
return 0;
}
mutex_unlock(&instance->lock);
}
/* not find, create one */
instance = vt_instance_create_lock(session->dev);
if (IS_ERR(instance)) {
mutex_unlock(&dev->instance_lock);
return PTR_ERR(instance);
}
for (i = 0; i < MAX_VIDEO_TUNNEL_ID; i++) {
/* remove the dummy pointer ID */
if (idr_find(&dev->instance_idr, i) == &dummy_instance)
idr_remove(&dev->instance_idr, i);
}
ret = idr_alloc(&dev->instance_idr, instance, 0, MAX_VIDEO_TUNNEL_ID, GFP_KERNEL);
/* allocate ID failed */
if (ret < 0) {
mutex_unlock(&dev->instance_lock);
vt_debug(VT_DEBUG_USER, "vt alloc instance [%d] idr alloc failed ret %d\n",
instance->id, ret);
vt_instance_put(instance);
return ret;
}
instance->id = ret;
snprintf(name, 64, "instance-%d", instance->id);
instance->debug_root =
debugfs_create_file(name, 0664, dev->debug_root,
instance, &debug_instance_fops);
data->tunnel_id = instance->id;
mutex_unlock(&dev->instance_lock);
vt_debug(VT_DEBUG_USER, "vt alloc instance [%d], ref %d\n",
instance->id,
atomic_read(&instance->ref.refcount.refs));
return 0;
}
static int vt_free_id_process(struct vt_alloc_id_data *data,
struct vt_session *session)
{
int ret = 0;
struct vt_dev *dev = session->dev;
struct vt_instance *instance = NULL;
instance = idr_find(&dev->instance_idr,
data->tunnel_id);
/* to do free id operation check */
if (!instance) {
pr_err("destroy unknown videotunnel instance:%d\n",
data->tunnel_id);
ret = -EINVAL;
} else {
vt_debug(VT_DEBUG_USER, "vt free instance [%d], ref %d\n",
instance->id,
atomic_read(&instance->ref.refcount.refs));
ret = vt_instance_put(instance);
}
return ret;
}
static int vt_connect_process(struct vt_ctrl_data *data,
struct vt_session *session)
{
struct vt_dev *dev = session->dev;
struct vt_instance *instance;
struct vt_instance *replace;
struct vt_cmd *cmd;
int id = data->tunnel_id;
int ret = 0;
char name[64];
mutex_lock(&dev->instance_lock);
instance = idr_find(&dev->instance_idr, id);
if (!instance || instance == &dummy_instance) {
if (!instance) {
while ((ret = idr_alloc(&dev->instance_idr,
&dummy_instance, 0,
MAX_VIDEO_TUNNEL_ID,
GFP_KERNEL))
<= id) {
if (ret == id) {
break;
} else if (ret < 0) {
pr_err("Connect to vt [%d] idr alloc fail:%d\n",
id, ret);
mutex_unlock(&dev->instance_lock);
return ret;
}
}
}
instance = vt_instance_create_lock(dev);
if (IS_ERR(instance)) {
mutex_unlock(&dev->instance_lock);
return PTR_ERR(instance);
}
replace = idr_replace(&dev->instance_idr, instance, id);
if (IS_ERR(replace)) {
mutex_unlock(&dev->instance_lock);
vt_instance_put(instance);
return PTR_ERR(replace);
}
if (!replace)
vt_instance_put(replace);
instance->id = id;
snprintf(name, 64, "instance-%d", instance->id);
instance->debug_root =
debugfs_create_file(name, 0664, dev->debug_root,
instance,
&debug_instance_fops);
vt_debug(VT_DEBUG_USER, "vt [%d] create\n", instance->id);
} else {
vt_instance_get(instance);
}
mutex_unlock(&dev->instance_lock);
mutex_lock(&instance->lock);
if (data->role == VT_ROLE_PRODUCER) {
if (instance->producer &&
instance->producer != session) {
mutex_unlock(&instance->lock);
vt_instance_put(instance);
pr_err("Connect to vt [%d] err, already has producer\n",
id);
return -EINVAL;
}
instance->producer = session;
} else if (data->role == VT_ROLE_CONSUMER) {
if (instance->consumer &&
instance->consumer != session) {
mutex_unlock(&instance->lock);
vt_instance_put(instance);
pr_err("Connect to vt [%d] err, already has consumer\n",
id);
return -EINVAL;
}
/* consumer connect, send game mode cmd if needed */
if (instance->mode == VT_MODE_GAME) {
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
if (!cmd) {
mutex_unlock(&instance->lock);
vt_instance_put(instance);
pr_err("Connect to vt [%d] err, resend cmd fail\n",
id);
return -ENOMEM;
}
cmd->cmd = VT_VIDEO_SET_GAME_MODE;
cmd->cmd_data = 1;
mutex_lock(&instance->cmd_lock);
kfifo_put(&instance->fifo_cmd, cmd);
session->cmd_status++;
vt_debug(VT_DEBUG_CMD, "vt [%d] resend cmd:%d data:%d\n",
instance->id, cmd->cmd, cmd->cmd_data);
mutex_unlock(&instance->cmd_lock);
}
instance->consumer = session;
}
session->cid = vt_get_connected_id();
session->role = data->role;
instance->used = true;
vt_debug(VT_DEBUG_USER, "vt [%d] %s-%d connect, instance ref %d\n",
instance->id,
data->role == VT_ROLE_PRODUCER ? "producer" : "consumer",
session->pid,
atomic_read(&instance->ref.refcount.refs));
mutex_unlock(&instance->lock);
return 0;
}
static int vt_disconnect_process(struct vt_ctrl_data *data,
struct vt_session *session)
{
struct vt_dev *dev = session->dev;
struct vt_instance *instance;
int id = data->tunnel_id;
/* find the instance with the tunnel_id */
instance = idr_find(&dev->instance_idr, id);
if (!instance || session->role != data->role)
goto find_fail;
mutex_lock(&instance->lock);
if (data->role == VT_ROLE_PRODUCER) {
if (!instance->producer)
goto disconnect_fail;
if (instance->producer != session)
goto disconnect_fail;
vt_session_trim_lock(session, instance);
instance->producer = NULL;
instance->mode = VT_MODE_NONE_BLOCK;
} else if (data->role == VT_ROLE_CONSUMER) {
if (!instance->consumer)
goto disconnect_fail;
if (instance->consumer != session)
goto disconnect_fail;
vt_session_trim_lock(session, instance);
instance->consumer = NULL;
}
vt_debug(VT_DEBUG_USER, "vt [%d] %s-%d disconnect, instance ref %d, fcount %d\n",
instance->id,
data->role == VT_ROLE_PRODUCER ? "producer" : "consumer",
session->pid,
atomic_read(&instance->ref.refcount.refs),
instance->fcount);
mutex_unlock(&instance->lock);
vt_instance_put(instance);
session->cid = -1;
return 0;
disconnect_fail:
mutex_unlock(&instance->lock);
find_fail:
return -EINVAL;
}
static int vt_send_cmd_process(struct vt_ctrl_data *data,
struct vt_session *session)
{
struct vt_dev *dev = session->dev;
struct vt_instance *instance;
struct vt_cmd *cmd;
int id = data->tunnel_id;
instance = idr_find(&dev->instance_idr, id);
if (!instance || session->role != VT_ROLE_PRODUCER)
return -EINVAL;
cmd = kzalloc(sizeof(*cmd), GFP_KERNEL);
if (!cmd)
return -ENOMEM;
mutex_lock(&instance->cmd_lock);
cmd->cmd = data->video_cmd;
cmd->cmd_data = data->video_cmd_data;
cmd->client_id = session->pid;
cmd->source_crop = data->source_crop;
if (cmd->cmd == VT_VIDEO_SET_GAME_MODE)
instance->mode =
cmd->cmd_data ? VT_MODE_GAME : VT_MODE_NONE_BLOCK;
kfifo_put(&instance->fifo_cmd, cmd);
vt_debug(VT_DEBUG_CMD, "vt [%d] send cmd:%d ", instance->id, cmd->cmd);
if (cmd->cmd == VT_VIDEO_SET_SOURCE_CROP)
vt_debug(VT_DEBUG_CMD, "source crop (%d %d %d %d)\n",
cmd->source_crop.left, cmd->source_crop.top,
cmd->source_crop.right, cmd->source_crop.bottom);
else
vt_debug(VT_DEBUG_CMD, "data:%d\n", cmd->cmd_data);
mutex_unlock(&instance->cmd_lock);
mutex_lock(&instance->lock);
wake_up_interruptible(&instance->wait_cmd);
if (instance->consumer) {
instance->consumer->cmd_status++;
wake_up_interruptible(&instance->consumer->wait_cmd);
}
mutex_unlock(&instance->lock);
return 0;
}
static int vt_has_cmd(struct vt_instance *instance)
{
int ret = !kfifo_is_empty(&instance->fifo_cmd);
return ret;
}
static int vt_recv_cmd_process(struct vt_ctrl_data *data,
struct vt_session *session)
{
struct vt_dev *dev = session->dev;
struct vt_instance *instance;
struct vt_cmd *vcmd = NULL;
int id = data->tunnel_id;
int ret;
instance = idr_find(&dev->instance_idr, id);
if (!instance || session->role != VT_ROLE_CONSUMER)
return -EINVAL;
/* empty need wait */
if (kfifo_is_empty(&instance->fifo_cmd)) {
if (session->mode != VT_MODE_BLOCK)
return -EAGAIN;
ret = wait_event_interruptible_timeout(instance->wait_cmd,
vt_has_cmd(instance),
msecs_to_jiffies(VT_CMD_WAIT_MS));
/* timeout */
if (ret == 0)
return -EAGAIN;
}
mutex_lock(&instance->cmd_lock);
ret = kfifo_get(&instance->fifo_cmd, &vcmd);
mutex_unlock(&instance->cmd_lock);
if (!ret || !vcmd) {
pr_err("vt [%d] recv cmd got null\n", instance->id);
return -EAGAIN;
}
vt_debug(VT_DEBUG_CMD, "vt [%d] recv cmd:%d ", instance->id, vcmd->cmd);
if (vcmd->cmd == VT_VIDEO_SET_SOURCE_CROP)
vt_debug(VT_DEBUG_CMD, "source crop (%d %d %d %d)\n",
vcmd->source_crop.left, vcmd->source_crop.top,
vcmd->source_crop.right, vcmd->source_crop.bottom);
else
vt_debug(VT_DEBUG_CMD, "data:%d\n", vcmd->cmd_data);
data->video_cmd = vcmd->cmd;
data->video_cmd_data = vcmd->cmd_data;
data->client_id = vcmd->client_id;
data->source_crop = vcmd->source_crop;
if (vcmd->cmd == VT_VIDEO_SET_GAME_MODE) {
if (!vcmd->cmd_data)
session->mode = VT_MODE_NONE_BLOCK;
else
session->mode = VT_MODE_GAME;
vt_debug(VT_DEBUG_USER, "vt [%d] set mode to:%s\n",
instance->id,
vt_debug_mode_status_to_string(session->mode));
}
/* free the vt_cmd buffer allocated in vt_send_cmd_process() */
kfree(vcmd);
return 0;
}
/*
* buffer_or_cmd indicate poll buffer or cmd, 1 is buffer and 0 is cmd
*/
static int vt_poll_ready(struct vt_session *session, int buffer_or_cmd)
{
struct vt_dev *dev = session->dev;
struct vt_instance *instance = NULL;
struct rb_node *n = NULL;
int size = 0;
mutex_lock(&dev->instance_lock);
for (n = rb_first(&dev->instances); n; n = rb_next(n)) {
instance = rb_entry(n, struct vt_instance, node);
mutex_lock(&instance->lock);
if (instance->producer && instance->producer == session) {
size += kfifo_len(&instance->fifo_to_producer);
} else if (instance->consumer &&
instance->consumer == session) {
if (buffer_or_cmd == 1 && session->mode == VT_MODE_GAME)
size += kfifo_len(&instance->fifo_to_consumer);
if (buffer_or_cmd == 0)
size += kfifo_len(&instance->fifo_cmd);
}
mutex_unlock(&instance->lock);
}
mutex_unlock(&dev->instance_lock);
return size;
}
static int vt_poll_cmd_process(struct vt_ctrl_data *data,
struct vt_session *session)
{
int time_out = data->video_cmd_data;
int ret = 0;
if (vt_poll_ready(session, 0) > 0)
return POLLIN | POLLRDNORM;
/* no ready cmd */
session->cmd_status = 0;
ret = wait_event_interruptible_timeout(session->wait_cmd,
session->cmd_status > 0,
msecs_to_jiffies(time_out));
/* timeout */
if (ret == 0)
return 0;
if (vt_poll_ready(session, 0) > 0)
return POLLIN | POLLRDNORM;
else
return -EAGAIN;
}
static int vt_cancel_buffer_process(struct vt_ctrl_data *data,
struct vt_session *session)
{
struct vt_dev *dev = session->dev;
struct vt_instance *instance = NULL;
struct vt_buffer *buffer = NULL;
int id = data->tunnel_id;
instance = idr_find(&dev->instance_idr, id);
if (!instance || !instance->producer)
return -EINVAL;
if (instance->producer && instance->producer != session)
return -EINVAL;
mutex_lock(&instance->lock);
while (kfifo_get(&instance->fifo_to_consumer, &buffer)) {
if (buffer->file_buffer) {
fput(buffer->file_buffer);
instance->fcount--;
buffer->item.buffer_status = VT_BUFFER_RELEASE;
vt_debug(VT_DEBUG_FILE,
"vt [%d] cancel buffer file(%p) buffer(%p), fcount=%d\n",
instance->id,
buffer->file_buffer,
buffer, instance->fcount);
}
kfifo_put(&instance->fifo_to_producer, buffer);
}
mutex_unlock(&instance->lock);
return 0;
}
static int vt_ctrl_process(struct vt_ctrl_data *data,
struct vt_session *session)
{
int id = data->tunnel_id;
int ret = 0;
if (id < 0 || id > MAX_VIDEO_TUNNEL_ID)
return -EINVAL;
if (data->role == VT_ROLE_INVALID)
return -EINVAL;
switch (data->ctrl_cmd) {
case VT_CTRL_CONNECT: {
ret = vt_connect_process(data, session);
break;
}
case VT_CTRL_DISCONNECT: {
ret = vt_disconnect_process(data, session);
break;
}
case VT_CTRL_SEND_CMD: {
ret = vt_send_cmd_process(data, session);
break;
}
case VT_CTRL_RECV_CMD: {
ret = vt_recv_cmd_process(data, session);
break;
}
case VT_CTRL_SET_NONBLOCK_MODE: {
session->mode = VT_MODE_NONE_BLOCK;
break;
}
case VT_CTRL_SET_BLOCK_MODE: {
session->mode = VT_MODE_BLOCK;
break;
}
case VT_CTRL_POLL_CMD: {
ret = vt_poll_cmd_process(data, session);
break;
}
case VT_CTRL_CANCEL_BUFFER: {
ret = vt_cancel_buffer_process(data, session);
break;
}
default:
pr_err("unknown videotunnel cmd:%d\n", data->ctrl_cmd);
return -EINVAL;
}
return ret;
}
static struct vt_buffer *vt_buffer_get_locked(struct vt_instance *instance, int key)
{
struct vt_buffer *buffer = NULL;
int i;
for (i = 0; i < VT_POOL_SIZE; i++) {
buffer = &instance->vt_buffers[i];
if (buffer->item.buffer_status == VT_BUFFER_ACQUIRE &&
buffer->buffer_fd_con == key)
return buffer;
}
return NULL;
}
static int vt_has_buffer(struct vt_instance *instance, enum vt_role_e role)
{
int ret = 0;
if (role == VT_ROLE_PRODUCER)
ret = !kfifo_is_empty(&instance->fifo_to_producer);
else
ret = !kfifo_is_empty(&instance->fifo_to_consumer);
return ret;
}
static struct vt_buffer *vt_get_free_buffer(struct vt_instance *instance)
{
struct vt_buffer *buffer = NULL;
int i, status;
mutex_lock(&instance->lock);
for (i = 0; i < VT_POOL_SIZE; i++) {
status = instance->vt_buffers[i].item.buffer_status;
if (status == VT_BUFFER_FREE || status == VT_BUFFER_DEQUEUE) {
buffer = &instance->vt_buffers[i];
buffer->file_buffer = NULL;
buffer->file_fence = NULL;
buffer->buffer_fd_con = -1;
break;
}
}
mutex_unlock(&instance->lock);
return buffer;
}
static int vt_queue_buffer_process(struct vt_buffer_data *data,
struct vt_session *session,
struct file *vt_buffer_file)
{
struct vt_dev *dev = session->dev;
struct vt_instance *instance =
idr_find(&dev->instance_idr, data->tunnel_id);
struct vt_buffer *buffer = NULL;
if (!instance || !instance->producer)
return -EINVAL;
if (instance->producer && instance->producer != session)
return -EINVAL;
/* in game mode, but has no consumer */
if (instance->mode == VT_MODE_GAME && !instance->consumer) {
vt_debug(VT_DEBUG_BUFFERS,
"vt [%d] game mode, but no consumer\n", instance->id);
return -ENOTCONN;
}
vt_debug(VT_DEBUG_BUFFERS,
"vt [%d] queuebuffer start\n", instance->id);
buffer = vt_get_free_buffer(instance);
if (!buffer)
return -ENOMEM;
if (vt_buffer_file) {
buffer->file_buffer = vt_buffer_file;
get_file(buffer->file_buffer);
} else {
buffer->file_buffer = fget(data->buffer_fd);
}
if (!buffer->file_buffer)
return -EBADF;
mutex_lock(&instance->lock);
instance->fcount++;
dev->state.buffer_get++;
instance->state.buffer_get++;
buffer->buffer_fd_pro = data->buffer_fd;
buffer->buffer_fd_con = -1;
buffer->session_pro = session;
buffer->cid_pro = session->cid;
buffer->item = *data;
buffer->item.buffer_status = VT_BUFFER_QUEUE;
vt_debug(VT_DEBUG_FILE,
"vt [%d] queuebuffer fget file(%p) buffer(%p) buffer session(%p) fcount=%d\n",
instance->id, buffer->file_buffer,
buffer, buffer->session_pro, instance->fcount);
kfifo_put(&instance->fifo_to_consumer, buffer);
if (instance->consumer) {
wake_up_interruptible(&instance->wait_consumer);
/* run in game mode */
if (instance->consumer->mode == VT_MODE_GAME)
wake_up_interruptible(&instance->consumer->wait_consumer);
}
mutex_unlock(&instance->lock);
dev->state.queue_count++;
instance->state.queue_count++;
vt_debug(VT_DEBUG_BUFFERS,
"vt [%d] queuebuffer pfd: %d, buffer(%p) buffer file(%p) timestamp(%lld), now(%lld)\n",
instance->id, buffer->buffer_fd_pro,
buffer, buffer->file_buffer,
buffer->item.time_stamp, ktime_to_us(ktime_get()));
return 0;
}
static int vt_instance_buffer_size(struct vt_instance *instance)
{
int size = 0;
int i;
mutex_lock(&instance->lock);
for (i = 0; i < VT_POOL_SIZE; i++) {
struct vt_buffer *buffer = &instance->vt_buffers[i];
int status = buffer->item.buffer_status;
if (status == VT_BUFFER_QUEUE || status == VT_BUFFER_ACQUIRE ||
status == VT_BUFFER_RELEASE)
size++;
}
mutex_unlock(&instance->lock);
return size;
}
static int vt_dequeue_buffer_process(struct vt_buffer_data *data,
struct vt_session *session,
struct file **vt_buffer_file,
struct file **vt_fence_file)
{
struct vt_dev *dev = session->dev;
struct vt_instance *instance =
idr_find(&dev->instance_idr, data->tunnel_id);
struct vt_buffer *buffer = NULL;
int ret = -1;
int fd = -1;
if (!instance || !instance->producer)
return -EINVAL;
if (instance->producer && instance->producer != session)
return -EINVAL;
/* empty need wait */
if (kfifo_is_empty(&instance->fifo_to_producer)) {
ret =
wait_event_interruptible_timeout(instance->wait_producer,
vt_has_buffer(instance, VT_ROLE_PRODUCER),
msecs_to_jiffies(VT_MAX_WAIT_MS));
/* timeout */
if (ret == 0) {
if (!instance->consumer) {
vt_debug(VT_DEBUG_BUFFERS,
"vt [%d] dequeue buffer, no consumer\n",
instance->id);
return -ENOTCONN;
} else {
return -EAGAIN;
}
}
}
if (vt_instance_buffer_size(instance) < dev->limit)
return -EAGAIN;
mutex_lock(&instance->lock);
ret = kfifo_get(&instance->fifo_to_producer, &buffer);
if (!ret || !buffer) {
pr_err("vt [%d] dequeue buffer got null buffer ret(%d)\n",
instance->id, ret);
mutex_unlock(&instance->lock);
return -EAGAIN;
}
buffer->item.buffer_status = VT_BUFFER_DEQUEUE;
/* it's previous connect buffer */
if (buffer->cid_pro != session->cid) {
if (buffer->file_fence) {
fput(buffer->file_fence);
dev->state.fence_put++;
instance->state.fence_put++;
}
dev->state.dequeue_invalid++;
instance->state.dequeue_invalid++;
pr_info("vt [%d] dequeuebuffer, previous connect buffer!!\n",
instance->id);
mutex_unlock(&instance->lock);
return -EAGAIN;
}
/* only install fence fd if vt_fence_file is null */
if (buffer->file_fence && !vt_fence_file) {
fd = get_unused_fd_flags(O_CLOEXEC);
if (fd < 0) {
pr_info("vt [%d] dequeuebuffer install fence fd error, Suspected fd leak!!\n",
instance->id);
/* could not get unused fd, put the file fence */
fput(buffer->file_fence);
dev->state.fence_put++;
instance->state.fence_put++;
mutex_unlock(&instance->lock);
return -ENOMEM;
}
fd_install(fd, buffer->file_fence);
vt_debug(VT_DEBUG_FILE,
"vt [%d] dequeubuffer fence file(%p) install fence fd(%d) buffer(%p)\n",
instance->id, buffer->file_fence, fd, buffer);
}
buffer->item.fence_fd = fd;
buffer->item.buffer_fd = buffer->buffer_fd_pro;
buffer->item.tunnel_id = instance->id;
buffer->item.buffer_status = VT_BUFFER_DEQUEUE;
if (vt_buffer_file)
*vt_buffer_file = buffer->file_buffer;
if (vt_fence_file)
*vt_fence_file = buffer->file_fence;
/* return the buffer */
*data = buffer->item;
mutex_unlock(&instance->lock);
dev->state.dequeue_count++;
instance->state.dequeue_count++;
vt_debug(VT_DEBUG_BUFFERS, "vt [%d] dequeuebuffer buffer(%p) end pfd(%d) cfd(%d) fence fd(%d) timestamp(%lld)\n",
instance->id, buffer, buffer->buffer_fd_pro, buffer->buffer_fd_con,
buffer->item.fence_fd, buffer->item.time_stamp);
return 0;
}
static int vt_acquire_buffer_process(struct vt_buffer_data *data,
struct vt_session *session)
{
struct vt_dev *dev = session->dev;
struct vt_instance *instance =
idr_find(&dev->instance_idr, data->tunnel_id);
struct vt_buffer *buffer = NULL;
int fd, ret = -1;
if (!instance || !instance->consumer)
return -EINVAL;
if (instance->consumer && instance->consumer != session)
return -EINVAL;
/* empty need wait */
if (kfifo_is_empty(&instance->fifo_to_consumer)) {
if (session->mode != VT_MODE_BLOCK)
return -EAGAIN;
ret = wait_event_interruptible_timeout(instance->wait_consumer,
vt_has_buffer(instance, VT_ROLE_CONSUMER),
msecs_to_jiffies(VT_MAX_WAIT_MS));
/* timeout */
if (ret == 0)
return -EAGAIN;
}
mutex_lock(&instance->lock);
ret = kfifo_get(&instance->fifo_to_consumer, &buffer);
if (!ret || !buffer) {
pr_err("vt [%d] acquirebuffer got null buffer\n", instance->id);
mutex_unlock(&instance->lock);
return -EAGAIN;
}
/* get the fd in consumer */
if (buffer->buffer_fd_con < 0) {
fd = get_unused_fd_flags(O_CLOEXEC);
if (fd < 0) {
/* back to producer */
pr_info("vt [%d] acquirebuffer install fd error\n",
instance->id);
buffer->item.buffer_status = VT_BUFFER_RELEASE;
if (buffer->file_buffer) {
fput(buffer->file_buffer);
instance->fcount--;
dev->state.buffer_put++;
instance->state.buffer_put++;
vt_debug(VT_DEBUG_FILE,
"vt [%d] acuqirebuffer install fd error file(%p) buffer(%p) fcount=%d\n",
instance->id, buffer->file_buffer,
buffer, instance->fcount);
}
kfifo_put(&instance->fifo_to_producer, buffer);
mutex_unlock(&instance->lock);
return -ENOMEM;
}
fd_install(fd, buffer->file_buffer);
buffer->buffer_fd_con = fd;
vt_debug(VT_DEBUG_BUFFERS,
"vt [%d] acquirebuffer install buffer fd:%d\n",
instance->id, fd);
}
buffer->item.buffer_fd = buffer->buffer_fd_con;
buffer->item.tunnel_id = instance->id;
buffer->item.buffer_status = VT_BUFFER_ACQUIRE;
/* return the buffer */
*data = buffer->item;
mutex_unlock(&instance->lock);
dev->state.acquire_count++;
instance->state.acquire_count++;
vt_debug(VT_DEBUG_BUFFERS,
"vt [%d] acquirebuffer pfd: %d, cfd: %d, buffer(%p) buffer file(%p), timestamp(%lld), now(%lld)\n",
instance->id, buffer->buffer_fd_pro,
buffer->buffer_fd_con, buffer, buffer->file_buffer,
buffer->item.time_stamp, ktime_to_us(ktime_get()));
return 0;
}
static int vt_release_buffer_process(struct vt_buffer_data *data,
struct vt_session *session)
{
struct vt_dev *dev = session->dev;
struct vt_instance *instance =
idr_find(&dev->instance_idr, data->tunnel_id);
struct vt_buffer *buffer = NULL;
if (!instance || !instance->consumer)
return -EINVAL;
if (instance->consumer && instance->consumer != session)
return -EINVAL;
if (data->buffer_fd < 0)
return -EINVAL;
mutex_lock(&instance->lock);
buffer = vt_buffer_get_locked(instance, data->buffer_fd);
if (!buffer) {
pr_err("vt [%d] releasebuffer cann't find buffer:%d\n",
instance->id, data->buffer_fd);
mutex_unlock(&instance->lock);
return -EINVAL;
}
/* close the fd in consumer side */
vt_close_fd(session, buffer->buffer_fd_con);
instance->fcount--;
dev->state.buffer_close++;
instance->state.buffer_close++;
vt_debug(VT_DEBUG_FILE,
"vt [%d] releasebuffer file(%p) buffer(%p) buffer sesion(%p) fcount=%d\n",
instance->id, buffer->file_buffer, buffer,
buffer->session_pro, instance->fcount);
buffer->item.buffer_fd = buffer->buffer_fd_pro;
buffer->item.buffer_status = VT_BUFFER_RELEASE;
/* if producer has disconnect */
if (!instance->producer || (buffer->session_pro &&
buffer->session_pro != instance->producer)) {
vt_debug(VT_DEBUG_BUFFERS,
"vt [%d] releasebuffer buffer, no producer or previous producer\n",
instance->id);
buffer->item.buffer_status = VT_BUFFER_FREE;
dev->state.release_invalid++;
instance->state.release_invalid++;
mutex_unlock(&instance->lock);
return 0;
}
buffer->file_fence = NULL;
if (data->fence_fd >= 0) {
buffer->file_fence = fget(data->fence_fd);
dev->state.fence_get++;
instance->state.fence_get++;
} else {
dev->state.null_fence++;
instance->state.null_fence++;
}
if (!buffer->file_fence)
vt_debug(VT_DEBUG_BUFFERS,
"vt [%d] releasebuffer fence file is null\n",
instance->id);
else
vt_debug(VT_DEBUG_FILE,
"vt [%d] releasebuffer fence file(%p) fence fd(%d)\n",
instance->id, buffer->file_fence, data->fence_fd);
kfifo_put(&instance->fifo_to_producer, buffer);
mutex_unlock(&instance->lock);
if (instance->producer)
wake_up_interruptible(&instance->wait_producer);
vt_debug(VT_DEBUG_BUFFERS,
"vt [%d] releasebuffer pfd: %d, cfd: %d, buffer(%p) buffer file(%p) timestamp(%lld)\n",
instance->id, buffer->buffer_fd_pro,
buffer->buffer_fd_con, buffer, buffer->file_buffer, buffer->item.time_stamp);
dev->state.release_count++;
instance->state.release_count++;
return 0;
}
static int vt_set_vsync_info(struct vt_display_vsync *data,
struct vt_session *session)
{
struct vt_dev *dev = session->dev;
if (session->role != VT_ROLE_CONSUMER || !dev)
return -EINVAL;
mutex_lock(&dev->vsync_lock);
dev->vsync_timestamp = data->timestamp;
dev->vsync_period = data->period;
mutex_unlock(&dev->vsync_lock);
vt_debug(VT_DEBUG_VSYNC,
"vt set vsync timestamp:%llu period:%u\n",
data->timestamp, data->period);
return 0;
}
static int vt_get_vsync_info(struct vt_display_vsync *data,
struct vt_session *session)
{
struct vt_dev *dev = session->dev;
if (session->role != VT_ROLE_PRODUCER || !dev)
return -EINVAL;
mutex_lock(&dev->vsync_lock);
data->timestamp = dev->vsync_timestamp;
data->period = dev->vsync_period;
mutex_unlock(&dev->vsync_lock);
vt_debug(VT_DEBUG_VSYNC,
"vt [%d] get vsync timestamp:%llu period:%u\n",
data->tunnel_id, data->timestamp, data->period);
return 0;
}
static unsigned int vt_ioctl_dir(unsigned int cmd)
{
switch (cmd) {
case VT_IOC_ALLOC_ID:
case VT_IOC_DEQUEUE_BUFFER:
case VT_IOC_ACQUIRE_BUFFER:
case VT_IOC_CTRL:
case VT_IOC_GET_VSYNCTIME:
return _IOC_READ;
case VT_IOC_QUEUE_BUFFER:
case VT_IOC_RELEASE_BUFFER:
case VT_IOC_FREE_ID:
case VT_IOC_SET_VSYNCTIME:
return _IOC_WRITE;
default:
return _IOC_DIR(cmd);
}
}
static long vt_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
union vt_ioctl_arg data;
struct vt_session *session = filp->private_data;
unsigned int dir = vt_ioctl_dir(cmd);
if (_IOC_SIZE(cmd) > sizeof(data))
return -EINVAL;
if (copy_from_user(&data, (void __user *)arg, _IOC_SIZE(cmd)))
return -EFAULT;
switch (cmd) {
case VT_IOC_ALLOC_ID: {
ret = vt_alloc_id_process(&data.alloc_data, session);
break;
}
case VT_IOC_FREE_ID: {
ret = vt_free_id_process(&data.alloc_data, session);
break;
}
case VT_IOC_CTRL:
ret = vt_ctrl_process(&data.ctrl_data, session);
break;
case VT_IOC_QUEUE_BUFFER:
ret = vt_queue_buffer_process(&data.buffer_data, session, NULL);
break;
case VT_IOC_DEQUEUE_BUFFER:
ret = vt_dequeue_buffer_process(&data.buffer_data,
session,
NULL,
NULL);
break;
case VT_IOC_RELEASE_BUFFER:
ret = vt_release_buffer_process(&data.buffer_data, session);
break;
case VT_IOC_ACQUIRE_BUFFER:
ret = vt_acquire_buffer_process(&data.buffer_data, session);
break;
case VT_IOC_SET_VSYNCTIME:
ret = vt_set_vsync_info(&data.vsync_data, session);
break;
case VT_IOC_GET_VSYNCTIME:
ret = vt_get_vsync_info(&data.vsync_data, session);
break;
default:
return -ENOTTY;
}
if (dir & _IOC_READ) {
if (copy_to_user((void __user *)arg, &data, _IOC_SIZE(cmd)))
return -EFAULT;
}
return ret;
}
/*
* for producer side, support poll buffer
* for consumer side, now only support poll cmd
*/
static __poll_t vt_poll(struct file *filp, struct poll_table_struct *wait)
{
struct vt_session *session = filp->private_data;
/* not connected */
if (session->role == VT_ROLE_INVALID)
return POLLERR;
if (session->role == VT_ROLE_PRODUCER)
poll_wait(filp, &session->wait_producer, wait);
else if (session->role == VT_ROLE_CONSUMER) {
/* not game mode */
if (session->mode != VT_MODE_GAME)
return POLLERR;
poll_wait(filp, &session->wait_consumer, wait);
}
if (vt_poll_ready(session, 1) > 0)
return POLLIN | POLLRDNORM;
else
return 0;
}
struct vt_session *vt_session_create(const char *name)
{
return vt_session_create_internal(vdev, name);
}
EXPORT_SYMBOL(vt_session_create);
int vt_alloc_id(struct vt_session *session, int *tunnel_id)
{
int ret = 0;
struct vt_alloc_id_data data = { 0 };
ret = vt_alloc_id_process(&data, session);
if (ret < 0)
return ret;
*tunnel_id = data.tunnel_id;
return ret;
}
EXPORT_SYMBOL(vt_alloc_id);
int vt_free_id(struct vt_session *session, int tunnel_id)
{
struct vt_alloc_id_data data = { 0 };
data.tunnel_id = tunnel_id;
return vt_free_id_process(&data, session);
}
EXPORT_SYMBOL(vt_free_id);
int vt_producer_connect(struct vt_session *session, int tunnel_id)
{
struct vt_ctrl_data data = { 0 };
data.tunnel_id = tunnel_id;
data.role = VT_ROLE_PRODUCER;
return vt_connect_process(&data, session);
}
EXPORT_SYMBOL(vt_producer_connect);
int vt_producer_disconnect(struct vt_session *session, int tunnel_id)
{
struct vt_ctrl_data data = { 0 };
data.tunnel_id = tunnel_id;
data.role = VT_ROLE_PRODUCER;
return vt_disconnect_process(&data, session);
}
EXPORT_SYMBOL(vt_producer_disconnect);
int vt_queue_buffer(struct vt_session *session, int tunnel_id,
struct file *buffer_file, int fence_fd, int64_t time_stamp)
{
struct vt_buffer_data data = { 0 };
data.tunnel_id = tunnel_id;
data.buffer_fd = -1;
data.fence_fd = fence_fd;
data.time_stamp = time_stamp;
return vt_queue_buffer_process(&data, session, buffer_file);
}
EXPORT_SYMBOL(vt_queue_buffer);
int vt_dequeue_buffer(struct vt_session *session, int tunnel_id,
struct file **buffer_file, struct file **fence_file)
{
struct vt_buffer_data data = { 0 };
int ret;
data.tunnel_id = tunnel_id;
ret = vt_dequeue_buffer_process(&data, session, buffer_file, fence_file);
return ret;
}
EXPORT_SYMBOL(vt_dequeue_buffer);
int vt_send_cmd(struct vt_session *session, int tunnel_id,
enum vt_video_cmd_e cmd, int cmd_data)
{
struct vt_ctrl_data data = { 0 };
data.tunnel_id = tunnel_id;
data.video_cmd = cmd;
data.video_cmd_data = cmd_data;
return vt_send_cmd_process(&data, session);
}
EXPORT_SYMBOL(vt_send_cmd);
static const struct file_operations vt_fops = {
.owner = THIS_MODULE,
.open = vt_open,
.release = vt_release,
.unlocked_ioctl = vt_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = vt_ioctl,
#endif
.poll = vt_poll,
};
static int vt_probe(struct platform_device *pdev)
{
int ret;
vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
if (!vdev)
return -ENOMEM;
vdev->dev_name = DEVICE_NAME;
vdev->mdev.minor = MISC_DYNAMIC_MINOR;
vdev->mdev.name = DEVICE_NAME;
vdev->mdev.fops = &vt_fops;
ret = misc_register(&vdev->mdev);
if (ret) {
pr_err("videotunnel: misc_register fail.\n");
goto failed_alloc_dev;
}
mutex_init(&vdev->instance_lock);
mutex_init(&vdev->vsync_lock);
idr_init(&vdev->instance_idr);
vdev->instances = RB_ROOT;
init_rwsem(&vdev->session_lock);
vdev->sessions = RB_ROOT;
mutex_init(&debugfs_mutex);
vdev->debug_root = debugfs_create_dir("videotunnel", NULL);
if (!vdev->debug_root) {
pr_err("videotunnel: failed to create debugfs root directory.\n");
} else {
vdev->state.debug_root =
debugfs_create_file("state", 0664, vdev->debug_root,
&vdev->state,
&debug_state_fops);
debugfs_create_file("limit", 0644, vdev->debug_root,
vdev,
&debug_limit_fops);
}
return 0;
failed_alloc_dev:
kfree(vdev);
return ret;
}
static int vt_remove(struct platform_device *pdev)
{
idr_destroy(&vdev->instance_idr);
debugfs_remove_recursive(vdev->debug_root);
misc_deregister(&vdev->mdev);
kfree(vdev);
return 0;
}
static const struct of_device_id meson_vt_match[] = {
{.compatible = "amlogic, meson_videotunnel"},
{},
};
static struct platform_driver meson_vt_driver = {
.driver = {
.name = "meson_videotunnel_driver",
.owner = THIS_MODULE,
.of_match_table = meson_vt_match,
},
.probe = vt_probe,
.remove = vt_remove,
};
int __init meson_videotunnel_init(void)
{
pr_info("videotunnel init\n");
if (platform_driver_register(&meson_vt_driver)) {
pr_err("failed to register videotunnel\n");
return -ENODEV;
}
return 0;
}
void __exit meson_videotunnel_exit(void)
{
platform_driver_unregister(&meson_vt_driver);
}