1538 lines
38 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) Rockchip Electronics Co., Ltd.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#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/slab.h>
#include <linux/jiffies.h>
#include <linux/kfifo.h>
#include <linux/debugfs.h>
#include <linux/random.h>
#include <linux/sync_file.h>
#include <linux/sched/task.h>
#include <linux/sched/clock.h>
#include <asm-generic/bug.h>
#include "rkvtunnel.h"
#define DEVICE_NAME "rkvtunnel"
#define RKVT_MAX_NAME_LENGTH 128
#define RKVT_POOL_SIZE 32
#define RKVT_MAX_WAIT_MS 4
#define RKVT_FENCE_WAIT_MS 3000
#define RKVT_DBG_USER (1U << 0)
#define RKVT_DBG_BUFFERS (1U << 1)
#define RKVT_DBG_CMD (1U << 2)
#define RKVT_DBG_FILE (1U << 3)
#define rkvt_dbg(mask, x...)\
do { if (unlikely(vt_dev_dbg & mask)) pr_info(x); } while (0)
enum rkvt_buf_status_e {
RKVT_BUF_QUEUE,
RKVT_BUF_DEQUEUE,
RKVT_BUF_ACQUIRE,
RKVT_BUF_RELEASE,
RKVT_BUF_FREE,
RKVT_BUF_BUTT,
};
union rkvt_ioc_arg {
struct rkvt_alloc_id_data alloc_data;
struct rkvt_ctrl_data ctrl_data;
struct rkvt_buf_data buffer_data;
};
struct rkvt_dev {
struct device *dev;
struct miscdevice mdev;
struct mutex inst_lock; /* protect inst_list and ints_idr */
struct idr inst_idr;
struct list_head list_inst; /* manage all instances */
struct mutex session_lock; /* protect sessions */
struct list_head list_session;
char *dev_name;
int inst_id_generator;
atomic64_t cid_generator;
struct dentry *debug_root;
};
struct rkvt_session {
struct list_head dev_link;
struct rkvt_dev *vt_dev;
struct list_head list_inst; /* manage instance in session */
enum rkvt_caller_e caller;
pid_t pid;
char name[RKVT_MAX_NAME_LENGTH];
char disp_name[RKVT_MAX_NAME_LENGTH];
int disp_serial;
int cid;
struct task_struct *task;
struct dentry *debug_root;
};
struct rkvt_buffer {
struct file *file_buf[MAX_BUF_HANDLE_FDS];
int fds_pro[MAX_BUF_HANDLE_FDS];
int fds_con[MAX_BUF_HANDLE_FDS];
struct file *ready_render_fence;
struct dma_fence *rendered_fence;
struct rkvt_session *session_pro;
int cid_pro;
struct rkvt_buf_base base;
};
struct rkvt_instance {
struct kref ref;
int id;
struct rkvt_dev *vt_dev;
struct mutex lock;
struct list_head dev_link;
struct list_head session_link;
struct rkvt_session *consumer;
struct rkvt_session *producer;
wait_queue_head_t wait_consumer;
wait_queue_head_t wait_producer;
struct dentry *debug_root;
int fcount;
DECLARE_KFIFO_PTR(fifo_to_consumer, struct rkvt_buffer*);
DECLARE_KFIFO_PTR(fifo_to_producer, struct rkvt_buffer*);
struct rkvt_buffer vt_buffers[RKVT_POOL_SIZE];
atomic64_t buf_id_generator;
};
static unsigned int vt_dev_dbg;
module_param(vt_dev_dbg, uint, 0644);
MODULE_PARM_DESC(vt_dev_dbg, "bit switch for vt debug information");
static const char *
rkvt_dbg_buf_status_to_string(int status)
{
const char *status_str;
switch (status) {
case RKVT_BUF_QUEUE:
status_str = "queued";
break;
case RKVT_BUF_DEQUEUE:
status_str = "dequeued";
break;
case RKVT_BUF_ACQUIRE:
status_str = "acquired";
break;
case RKVT_BUF_RELEASE:
status_str = "released";
break;
case RKVT_BUF_FREE:
status_str = "free";
break;
default:
status_str = "unknown";
}
return status_str;
}
static int rkvt_dbg_instance_show(struct seq_file *s, void *unused)
{
struct rkvt_instance *inst = s->private;
int i;
int size_to_con;
int size_to_pro;
int ref_count;
mutex_lock(&inst->lock);
size_to_con = kfifo_len(&inst->fifo_to_consumer);
size_to_pro = kfifo_len(&inst->fifo_to_producer);
ref_count = kref_read(&inst->ref);
seq_printf(s, "tunnel (%p) id=%d, ref=%d, fcount=%d\n",
inst, inst->id, ref_count, inst->fcount);
seq_puts(s, "-----------------------------------------------\n");
if (inst->consumer)
seq_printf(s, "consumer session (%s) %p\n",
inst->consumer->disp_name, inst->consumer);
if (inst->producer)
seq_printf(s, "producer session (%s) %p\n",
inst->producer->disp_name, inst->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_puts(s, "-----------------------------------------------\n");
seq_puts(s, "buffers:\n");
for (i = 0; i < RKVT_POOL_SIZE; i++) {
struct rkvt_buffer *buffer = &inst->vt_buffers[i];
int status = buffer->base.buf_status;
seq_printf(s, " buffer produce_fd[0](%d) status(%s)\n",
buffer->fds_pro[0],
rkvt_dbg_buf_status_to_string(status));
}
seq_puts(s, "-----------------------------------------------\n");
mutex_unlock(&inst->lock);
return 0;
}
static int
rkvt_dbg_instance_open(struct inode *inode, struct file *file)
{
return single_open(file,
rkvt_dbg_instance_show,
inode->i_private);
}
static const struct file_operations dbg_instance_fops = {
.open = rkvt_dbg_instance_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int rkvt_dbg_session_show(struct seq_file *s, void *unused)
{
struct rkvt_session *session = s->private;
seq_printf(s, "session(%s) %p role %s cid %d\n",
session->disp_name, session,
session->caller == RKVT_CALLER_PRODUCER ?
"producer" : (session->caller == RKVT_CALLER_CONSUMER ?
"consumer" : "invalid"), session->cid);
seq_puts(s, "-----------------------------------------------\n");
return 0;
}
static int rkvt_dbg_session_open(struct inode *inode, struct file *file)
{
return single_open(file,
rkvt_dbg_session_show,
inode->i_private);
}
static const struct file_operations debug_session_fops = {
.open = rkvt_dbg_session_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __rkvt_close_fd(struct files_struct *files, unsigned int fd)
{
struct file *file;
struct fdtable *fdt;
spin_lock(&files->file_lock);
fdt = files_fdtable(files);
if (fd >= fdt->max_fds)
goto out_unlock;
file = fdt->fd[fd];
if (!file)
goto out_unlock;
rcu_assign_pointer(fdt->fd[fd], NULL);
spin_unlock(&files->file_lock);
put_unused_fd(fd);
return filp_close(file, files);
out_unlock:
spin_unlock(&files->file_lock);
return -EBADF;
}
static int rkvt_close_fd(struct rkvt_session *session, unsigned int fd)
{
int ret;
if (!session->task)
return -ESRCH;
ret = __rkvt_close_fd(session->task->files, fd);
if (unlikely(ret == -ERESTARTSYS ||
ret == -ERESTARTNOINTR ||
ret == -ERESTARTNOHAND ||
ret == -ERESTART_RESTARTBLOCK))
ret = -EINTR;
return ret;
}
/* The function is responsible for fifo_to_consumer fifo operation
* requires external use of rkvt_instance.lock protection
*/
static void rkvt_inst_clear_consumer(struct rkvt_instance *inst)
{
struct rkvt_buffer *buffer = NULL;
int i = 0;
if (!inst)
return;
while (kfifo_get(&inst->fifo_to_consumer, &buffer)) {
/* put file */
for (i = 0; i < buffer->base.num_fds; i++) {
if (buffer->file_buf[i]) {
fput(buffer->file_buf[i]);
buffer->file_buf[i] = NULL;
}
inst->fcount--;
}
if (buffer->ready_render_fence) {
fput(buffer->ready_render_fence);
buffer->ready_render_fence = NULL;
}
rkvt_dbg(RKVT_DBG_FILE,
"vt [%d] instance trim file(%p) buffer(%p) ino(%08lu) fcount=%d\n",
inst->id, buffer->file_buf, buffer,
buffer->file_buf[i] ?
file_inode(buffer->file_buf[i])->i_ino : 0,
inst->fcount);
if (inst->producer != NULL) {
buffer->base.buf_status = RKVT_BUF_RELEASE;
kfifo_put(&inst->fifo_to_producer, buffer);
wake_up_interruptible(&inst->wait_producer);
} else {
buffer->base.buf_status = RKVT_BUF_FREE;
}
}
}
/* The function is responsible for fifo_to_consumer fifo operation
* requires external use of rkvt_instance.lock protection.
*/
static void rkvt_inst_clear_producer(struct rkvt_instance *inst)
{
struct rkvt_buffer *buffer = NULL;
if (!inst)
return;
while (kfifo_get(&inst->fifo_to_producer, &buffer)) {
if (buffer->rendered_fence) {
dma_fence_put(buffer->rendered_fence);
buffer->rendered_fence = NULL;
}
buffer->base.buf_status = RKVT_BUF_FREE;
}
}
static void rkvt_inst_destroy(struct kref *kref)
{
struct rkvt_instance *inst =
container_of(kref, struct rkvt_instance, ref);
struct rkvt_dev *vt_dev = inst->vt_dev;
list_del_init(&inst->dev_link);
idr_remove(&vt_dev->inst_idr, inst->id);
rkvt_dbg(RKVT_DBG_USER, "vt [%d] destroy\n", inst->id);
mutex_lock(&inst->lock);
rkvt_inst_clear_consumer(inst);
rkvt_inst_clear_producer(inst);
kfifo_free(&inst->fifo_to_consumer);
kfifo_free(&inst->fifo_to_producer);
mutex_unlock(&inst->lock);
debugfs_remove_recursive(inst->debug_root);
devm_kfree(vt_dev->dev, inst);
}
static struct rkvt_instance *rkvt_inst_create(struct rkvt_dev *vt_dev)
{
struct rkvt_instance *inst;
int status;
int i;
inst = devm_kzalloc(vt_dev->dev, sizeof(*inst), GFP_KERNEL);
if (!inst)
return ERR_PTR(-ENOMEM);
inst->vt_dev = vt_dev;
mutex_init(&inst->lock);
INIT_LIST_HEAD(&inst->dev_link);
INIT_LIST_HEAD(&inst->session_link);
kref_init(&inst->ref);
status = kfifo_alloc(&inst->fifo_to_consumer,
RKVT_POOL_SIZE, GFP_KERNEL);
if (status)
goto setup_fail;
status = kfifo_alloc(&inst->fifo_to_producer,
RKVT_POOL_SIZE, GFP_KERNEL);
if (status)
goto fifo_alloc_fail;
init_waitqueue_head(&inst->wait_producer);
init_waitqueue_head(&inst->wait_consumer);
for (i = 0; i < RKVT_POOL_SIZE; i++)
inst->vt_buffers[i].base.buf_status = RKVT_BUF_FREE;
/* insert it to dev instances list */
mutex_lock(&vt_dev->inst_lock);
list_add_tail(&inst->dev_link, &vt_dev->list_inst);
mutex_unlock(&vt_dev->inst_lock);
return inst;
fifo_alloc_fail:
kfifo_free(&inst->fifo_to_consumer);
setup_fail:
devm_kfree(vt_dev->dev, inst);
return ERR_PTR(status);
}
/* The function protected by rkvt_dev.session_lock by caller */
static int
rkvt_get_session_serial(const struct list_head *sessions,
const unsigned char *name)
{
int serial = -1;
struct rkvt_session *session, *n;
list_for_each_entry_safe(session, n, sessions, dev_link) {
if (strcmp(session->name, name))
continue;
serial = max(serial, session->disp_serial);
}
return serial + 1;
}
/* The function protected by rkvt_instance.lock by caller */
static void
rkvt_session_trim_locked(struct rkvt_session *session, struct rkvt_instance *inst)
{
if (!session || !inst)
return;
if (inst->producer && inst->producer == session) {
rkvt_inst_clear_producer(inst);
inst->producer = NULL;
}
if (inst->consumer && inst->consumer == session) {
rkvt_inst_clear_consumer(inst);
inst->consumer = NULL;
}
}
static int rkvt_inst_trim(struct rkvt_session *session)
{
struct rkvt_dev *vt_dev = session->vt_dev;
struct rkvt_instance *inst, *n;
int i;
mutex_lock(&vt_dev->inst_lock);
list_for_each_entry_safe(inst, n, &vt_dev->list_inst, dev_link) {
mutex_lock(&inst->lock);
rkvt_session_trim_locked(session, inst);
if (!inst->consumer && !inst->producer) {
rkvt_inst_clear_producer(inst);
rkvt_inst_clear_consumer(inst);
for (i = 0; i < RKVT_POOL_SIZE; i++)
inst->vt_buffers[i].base.buf_status = RKVT_BUF_FREE;
}
mutex_unlock(&inst->lock);
}
mutex_unlock(&vt_dev->inst_lock);
return 0;
}
static struct rkvt_session *
rkvt_session_create(struct rkvt_dev *vt_dev, const char *name)
{
struct rkvt_session *session;
struct task_struct *task = NULL;
if (!name) {
dev_err(vt_dev->dev, "%s: Name can not be null\n", __func__);
return ERR_PTR(-EINVAL);
}
session = devm_kzalloc(vt_dev->dev, sizeof(*session), GFP_KERNEL);
if (!session)
return ERR_PTR(-ENOMEM);
get_task_struct(current->group_leader);
task_lock(current->group_leader);
session->pid = task_pid_nr(current->group_leader);
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->vt_dev = vt_dev;
session->task = task;
session->caller = RKVT_CALLER_BUTT;
INIT_LIST_HEAD(&session->dev_link);
INIT_LIST_HEAD(&session->list_inst);
snprintf(session->name, RKVT_MAX_NAME_LENGTH, "%s", name);
mutex_lock(&vt_dev->session_lock);
session->disp_serial = rkvt_get_session_serial(&vt_dev->list_session, name);
snprintf(session->disp_name, RKVT_MAX_NAME_LENGTH, "%s-%d",
name, session->disp_serial);
list_add_tail(&session->dev_link, &vt_dev->list_session);
/* add debug fs */
session->debug_root = debugfs_create_file(session->disp_name,
0664,
vt_dev->debug_root,
session,
&debug_session_fops);
mutex_unlock(&vt_dev->session_lock);
rkvt_dbg(RKVT_DBG_USER, "vt session %s create\n", session->disp_name);
return session;
}
static void rkvt_session_destroy(struct rkvt_session *session)
{
struct rkvt_dev *vt_dev = session->vt_dev;
struct rkvt_instance *inst = NULL;
rkvt_dbg(RKVT_DBG_USER, "vt session %s destroy\n", session->disp_name);
mutex_lock(&vt_dev->inst_lock);
while ((inst = list_first_entry_or_null(&session->list_inst,
struct rkvt_instance, session_link))) {
list_del_init(&inst->session_link);
kref_put(&inst->ref, rkvt_inst_destroy);
}
mutex_unlock(&vt_dev->inst_lock);
mutex_lock(&vt_dev->session_lock);
if (session->task)
put_task_struct(session->task);
list_del_init(&session->dev_link);
debugfs_remove_recursive(session->debug_root);
mutex_unlock(&vt_dev->session_lock);
rkvt_inst_trim(session);
devm_kfree(vt_dev->dev, session);
}
static int rkvt_open(struct inode *inode, struct file *filep)
{
struct miscdevice *miscdev = filep->private_data;
struct rkvt_dev *vt_dev = container_of(miscdev, struct rkvt_dev, mdev);
struct rkvt_session *session;
char debug_name[64];
snprintf(debug_name, sizeof(debug_name), "%u", task_pid_nr(current->group_leader));
session = rkvt_session_create(vt_dev, debug_name);
if (IS_ERR(session))
return PTR_ERR(session);
filep->private_data = session;
return 0;
}
static int rkvt_release(struct inode *inode, struct file *filep)
{
struct rkvt_session *session = filep->private_data;
rkvt_session_destroy(session);
filep->private_data = NULL;
return 0;
}
static int rkvt_get_connected_id(struct rkvt_dev *vt_dev)
{
return atomic64_inc_return(&vt_dev->cid_generator);
}
static struct rkvt_instance *
rkvt_inst_get_by_tid(struct rkvt_dev *vt_dev, int id)
{
struct rkvt_instance *inst;
mutex_lock(&vt_dev->inst_lock);
inst = idr_find(&vt_dev->inst_idr, id);
if (!inst) {
mutex_unlock(&vt_dev->inst_lock);
dev_err(vt_dev->dev, "find rkvt [%d] by device idr err, instance is null\n", id);
return NULL;
}
kref_get(&inst->ref);
mutex_unlock(&vt_dev->inst_lock);
return inst;
}
static void rkvt_inst_put(struct rkvt_instance *inst)
{
struct rkvt_dev *vt_dev;
if (!inst)
return;
vt_dev = inst->vt_dev;
mutex_lock(&vt_dev->inst_lock);
kref_put(&inst->ref, rkvt_inst_destroy);
mutex_unlock(&vt_dev->inst_lock);
}
static int
rkvt_connect_proc(struct rkvt_ctrl_data *data, struct rkvt_session *session)
{
struct rkvt_dev *vt_dev = session->vt_dev;
struct rkvt_instance *inst;
int ret = 0;
// ref get not put in function end, because connect need hold 1 refs.
inst = rkvt_inst_get_by_tid(vt_dev, data->vt_id);
if (!inst)
return -EINVAL;
mutex_lock(&inst->lock);
if (data->caller == RKVT_CALLER_PRODUCER) {
if (inst->producer && inst->producer != session) {
dev_err(vt_dev->dev, "Connect to rkvt [%d] err, already has producer\n",
data->vt_id);
ret = -EINVAL;
goto connect_fail;
}
inst->producer = session;
} else if (data->caller == RKVT_CALLER_CONSUMER) {
if (inst->consumer && inst->consumer != session) {
dev_err(vt_dev->dev, "Connect to rkvt [%d] err, already has consumer\n",
data->vt_id);
ret = -EINVAL;
goto connect_fail;
}
inst->consumer = session;
}
mutex_unlock(&inst->lock);
session->cid = rkvt_get_connected_id(vt_dev);
session->caller = data->caller;
rkvt_dbg(RKVT_DBG_USER, "rkvt [%d] %s-%d connect, instance ref %d\n",
inst->id,
data->caller == RKVT_CALLER_PRODUCER ? "producer" : "consumer",
session->pid,
kref_read(&inst->ref));
return 0;
connect_fail:
mutex_unlock(&inst->lock);
// ref put for rkvt_instance_get_by_tid
rkvt_inst_put(inst);
return ret;
}
static int
rkvt_disconnect_proc(struct rkvt_ctrl_data *data, struct rkvt_session *session)
{
struct rkvt_dev *vt_dev = session->vt_dev;
struct rkvt_instance *inst;
inst = rkvt_inst_get_by_tid(vt_dev, data->vt_id);
if (!inst)
return -EINVAL;
if (session->caller != data->caller)
goto session_invail;
mutex_lock(&inst->lock);
if (data->caller == RKVT_CALLER_PRODUCER) {
if (!inst->producer)
goto disconnect_fail;
if (inst->producer != session)
goto disconnect_fail;
rkvt_session_trim_locked(session, inst);
inst->producer = NULL;
wake_up_interruptible(&inst->wait_producer);
} else if (data->caller == RKVT_CALLER_CONSUMER) {
if (!inst->consumer)
goto disconnect_fail;
if (inst->consumer != session)
goto disconnect_fail;
rkvt_session_trim_locked(session, inst);
inst->consumer = NULL;
wake_up_interruptible(&inst->wait_consumer);
}
mutex_unlock(&inst->lock);
rkvt_dbg(RKVT_DBG_USER, "rkvt [%d] %s-%d disconnect, instance ref %d\n",
inst->id,
data->caller == RKVT_CALLER_PRODUCER ? "producer" : "consumer",
session->pid,
kref_read(&inst->ref));
// ref put for rkvt_instance_get_by_tid
rkvt_inst_put(inst);
// ref put for connect proc
rkvt_inst_put(inst);
session->cid = -1;
return 0;
disconnect_fail:
mutex_unlock(&inst->lock);
session_invail:
// ref put for rkvt_instance_get_by_tid
rkvt_inst_put(inst);
return -EINVAL;
}
static int
rkvt_reset_proc(struct rkvt_ctrl_data *data, struct rkvt_session *session)
{
struct rkvt_dev *vt_dev = session->vt_dev;
struct rkvt_instance *inst;
long long read_buf_id;
inst = rkvt_inst_get_by_tid(vt_dev, data->vt_id);
if (!inst)
return -EINVAL;
mutex_lock(&inst->lock);
rkvt_inst_clear_consumer(inst);
rkvt_inst_clear_producer(inst);
read_buf_id = atomic64_read(&inst->buf_id_generator);
read_buf_id += 0x100;
read_buf_id &= ~0xff;
atomic64_set(&inst->buf_id_generator, read_buf_id);
mutex_unlock(&inst->lock);
rkvt_inst_put(inst);
return 0;
}
static int
rkvt_has_consumer_proc(struct rkvt_ctrl_data *data, struct rkvt_session *session)
{
struct rkvt_dev *vt_dev = session->vt_dev;
struct rkvt_instance *inst;
inst = rkvt_inst_get_by_tid(vt_dev, data->vt_id);
if (!inst)
return -EINVAL;
mutex_lock(&inst->lock);
data->ctrl_data = inst->consumer != NULL ? 1 : 0;
mutex_unlock(&inst->lock);
rkvt_inst_put(inst);
return 0;
}
static int
rkvt_ctrl_proc(struct rkvt_ctrl_data *data, struct rkvt_session *session)
{
int id = data->vt_id;
int ret = 0;
if (id < 0)
return -EINVAL;
if (data->caller == RKVT_CALLER_BUTT)
return -EINVAL;
switch (data->ctrl_cmd) {
case RKVT_CTRL_CONNECT: {
ret = rkvt_connect_proc(data, session);
break;
}
case RKVT_CTRL_DISCONNECT: {
ret = rkvt_disconnect_proc(data, session);
break;
}
case RKVT_CTRL_RESET: {
ret = rkvt_reset_proc(data, session);
break;
}
case RKVT_CTRL_HAS_CONSUMER: {
ret = rkvt_has_consumer_proc(data, session);
break;
}
default:
pr_err("unknown rkvt cmd:%d\n", data->ctrl_cmd);
return -EINVAL;
}
return ret;
}
static struct
rkvt_buffer *rkvt_buf_get(struct rkvt_instance *inst, int key)
{
struct rkvt_buffer *buffer = NULL;
int i;
mutex_lock(&inst->lock);
for (i = 0; i < RKVT_POOL_SIZE; i++) {
buffer = &inst->vt_buffers[i];
if (buffer->base.buf_status == RKVT_BUF_ACQUIRE &&
buffer->fds_con[0] == key)
break;
}
mutex_unlock(&inst->lock);
return buffer;
}
static int
rkvt_has_buf(struct rkvt_instance *inst, enum rkvt_caller_e caller)
{
int ret = 0;
if (caller == RKVT_CALLER_PRODUCER)
ret = !kfifo_is_empty(&inst->fifo_to_producer);
else
ret = !kfifo_is_empty(&inst->fifo_to_consumer);
return ret;
}
static int
rkvt_query_buf_and_wait(struct rkvt_instance *inst,
enum rkvt_caller_e caller,
int timeout_ms)
{
int ret;
wait_queue_head_t *wait_queue;
if (caller == RKVT_CALLER_PRODUCER)
wait_queue = &inst->wait_producer;
else
wait_queue = &inst->wait_consumer;
if (caller == RKVT_CALLER_PRODUCER &&
!kfifo_is_empty(&inst->fifo_to_producer))
return 0;
if (caller == RKVT_CALLER_CONSUMER &&
!kfifo_is_empty(&inst->fifo_to_consumer))
return 0;
if (timeout_ms < 0)
wait_event_interruptible(*wait_queue,
rkvt_has_buf(inst, caller));
else if (timeout_ms > 0) {
ret = wait_event_interruptible_timeout(*wait_queue,
rkvt_has_buf(inst, caller),
msecs_to_jiffies(timeout_ms));
/* timeout */
if (ret == 0)
return -EAGAIN;
} else
return -EAGAIN;
if (caller == RKVT_CALLER_PRODUCER &&
kfifo_is_empty(&inst->fifo_to_producer))
return -EAGAIN;
if (caller == RKVT_CALLER_CONSUMER &&
kfifo_is_empty(&inst->fifo_to_consumer))
return -EAGAIN;
return 0;
}
static struct rkvt_buffer *rkvt_get_free_buf(struct rkvt_instance *inst)
{
struct rkvt_buffer *buffer = NULL;
int i, status;
mutex_lock(&inst->lock);
for (i = 0; i < RKVT_POOL_SIZE; i++) {
status = inst->vt_buffers[i].base.buf_status;
if (status == RKVT_BUF_FREE || status == RKVT_BUF_DEQUEUE) {
buffer = &inst->vt_buffers[i];
memset(buffer->file_buf, 0, sizeof(buffer->file_buf));
buffer->rendered_fence = NULL;
break;
}
}
mutex_unlock(&inst->lock);
return buffer;
}
static int
rkvt_queue_buf(struct rkvt_buf_data *data, struct rkvt_session *session)
{
struct rkvt_dev *vt_dev = session->vt_dev;
struct rkvt_instance *inst = NULL;
struct rkvt_buf_base *base = NULL;
struct rkvt_buffer *buffer = NULL;
int i;
int ret = 0;
inst = rkvt_inst_get_by_tid(vt_dev, data->vt_id);
if (!inst)
return -EINVAL;
if (!inst->producer || inst->producer != session) {
ret = -EINVAL;
goto queue_fail;
}
if ((data->base.num_fds > MAX_BUF_HANDLE_FDS) ||
(data->base.num_ints > MAX_BUF_HANDLE_INTS)) {
ret = -EINVAL;
goto queue_fail;
}
rkvt_dbg(RKVT_DBG_BUFFERS, "VTQB [%d] start\n", inst->id);
base = &data->base;
buffer = rkvt_get_free_buf(inst);
if (!buffer) {
dev_err(vt_dev->dev, "VTQB [%d] no unused buffer.\n", inst->id);
ret = -EINVAL;
goto queue_fail;
}
for (i = 0; i < base->num_fds; i++) {
buffer->fds_con[i] = -1;
buffer->fds_pro[i] = base->fds[i];
buffer->file_buf[i] = fget(base->fds[i]);
if (!buffer->file_buf[i]) {
ret = -EBADF;
goto buf_fget_fail;
}
inst->fcount++;
rkvt_dbg(RKVT_DBG_FILE,
"VTQB [%d] fget file(%p) buf(%p) buf session(%p) ino(%08lu) fcount=%d\n",
inst->id, buffer->file_buf[i], buffer, buffer->session_pro,
buffer->file_buf[i] ? file_inode(buffer->file_buf[i])->i_ino : 0,
inst->fcount);
}
if (base->fence_fd >= 0)
buffer->ready_render_fence = fget(base->fence_fd);
// buffer id is empty, generate a new id
if (base->buffer_id == 0)
base->buffer_id = atomic64_inc_return(&inst->buf_id_generator);
buffer->base = *base;
buffer->base.buf_status = RKVT_BUF_QUEUE;
buffer->session_pro = session;
buffer->cid_pro = session->cid;
mutex_lock(&inst->lock);
if (inst->consumer) {
kfifo_put(&inst->fifo_to_consumer, buffer);
} else {
for (i = 0; i < buffer->base.num_fds; i++) {
if (buffer->file_buf[i]) {
fput(buffer->file_buf[i]);
buffer->file_buf[i] = NULL;
}
inst->fcount--;
}
if (buffer->ready_render_fence) {
fput(buffer->ready_render_fence);
buffer->ready_render_fence = NULL;
}
buffer->base.buf_status = RKVT_BUF_RELEASE;
kfifo_put(&inst->fifo_to_producer, buffer);
}
mutex_unlock(&inst->lock);
if (inst->consumer)
wake_up_interruptible(&inst->wait_consumer);
else if (inst->producer)
wake_up_interruptible(&inst->wait_producer);
rkvt_dbg(RKVT_DBG_BUFFERS, "VTQB [%d] pfd[0]:%d end\n", inst->id, buffer->fds_pro[0]);
queue_fail:
rkvt_inst_put(inst);
return ret;
buf_fget_fail:
for (i = 0; i < base->num_fds; i++) {
if (buffer->file_buf[i]) {
fput(buffer->file_buf[i]);
buffer->file_buf[i] = NULL;
inst->fcount--;
}
}
rkvt_inst_put(inst);
return ret;
}
static int
rkvt_deque_buf(struct rkvt_buf_data *data, struct rkvt_session *session)
{
struct rkvt_dev *vt_dev = session->vt_dev;
struct rkvt_instance *inst = NULL;
struct rkvt_buffer *buffer = NULL;
int ret = 0;
unsigned long long cur_time, wait_time;
int i;
inst = rkvt_inst_get_by_tid(vt_dev, data->vt_id);
if (!inst)
return -EINVAL;
if (!inst->producer || inst->producer != session) {
ret = -EINVAL;
goto deque_fail;
}
/* empty need wait */
ret = rkvt_query_buf_and_wait(inst,
RKVT_CALLER_PRODUCER,
data->timeout_ms);
if (ret)
goto deque_fail;
mutex_lock(&inst->lock);
ret = kfifo_get(&inst->fifo_to_producer, &buffer);
if (!ret || !buffer) {
dev_err(vt_dev->dev, "VTDB [%d] got null buffer ret(%d)\n", inst->id, ret);
mutex_unlock(&inst->lock);
ret = -EAGAIN;
goto deque_fail;
}
mutex_unlock(&inst->lock);
/* it's previous connect buffer */
if (buffer->cid_pro != session->cid) {
if (buffer->rendered_fence) {
dma_fence_put(buffer->rendered_fence);
buffer->rendered_fence = NULL;
}
ret = -EAGAIN;
goto deque_fail;
}
if (buffer->rendered_fence) {
cur_time = sched_clock();
ret = dma_fence_wait_timeout(buffer->rendered_fence, false,
msecs_to_jiffies(RKVT_FENCE_WAIT_MS));
wait_time = sched_clock() - cur_time;
rkvt_dbg(RKVT_DBG_BUFFERS,
"VTDB [%d] pfd[0]:%d rendered fence:%p fence_wait time %llu\n",
inst->id, buffer->fds_pro[0], buffer->rendered_fence, wait_time);
if (ret < 0)
dev_err(vt_dev->dev, "VTDB [%d] wait fence timeout\n", inst->id);
dma_fence_put(buffer->rendered_fence);
buffer->rendered_fence = NULL;
}
for (i = 0; i < buffer->base.num_fds; i++)
rkvt_dbg(RKVT_DBG_FILE,
"VTDB [%d] fget file(%p) buf(%p) buf session(%p) ino(%08lu) fcount=%d\n",
inst->id, buffer->file_buf[i],
buffer, buffer->session_pro,
buffer->file_buf[i] ? file_inode(buffer->file_buf[i])->i_ino : 0,
inst->fcount);
buffer->base.vt_id = inst->id;
/* return the buffer */
data->base = buffer->base;
buffer->base.buf_status = RKVT_BUF_DEQUEUE;
rkvt_dbg(RKVT_DBG_BUFFERS, "VTDB [%d] end pfd[0]:%d\n", inst->id, buffer->fds_pro[0]);
deque_fail:
rkvt_inst_put(inst);
return ret;
}
static int
rkvt_acquire_buf(struct rkvt_buf_data *data, struct rkvt_session *session)
{
struct rkvt_dev *vt_dev = session->vt_dev;
struct rkvt_instance *inst = NULL;
struct rkvt_buffer *buffer = NULL;
int fd, ret = -1;
int i;
inst = rkvt_inst_get_by_tid(vt_dev, data->vt_id);
if (!inst)
return -EINVAL;
if (!inst->consumer || inst->consumer != session) {
ret = -EINVAL;
goto acquire_fail;
}
if ((data->base.num_fds > MAX_BUF_HANDLE_FDS) ||
(data->base.num_ints > MAX_BUF_HANDLE_INTS)) {
ret = -EINVAL;
goto acquire_fail;
}
/* empty need wait */
ret = rkvt_query_buf_and_wait(inst,
RKVT_CALLER_CONSUMER,
data->timeout_ms);
if (ret)
goto acquire_fail;
mutex_lock(&inst->lock);
ret = kfifo_get(&inst->fifo_to_consumer, &buffer);
mutex_unlock(&inst->lock);
if (!ret || !buffer) {
dev_err(vt_dev->dev, "VTAB [%d] got null buffer\n", inst->id);
ret = -EAGAIN;
goto acquire_fail;
}
/* get the fd in consumer */
for (i = 0; i < buffer->base.num_fds; i++) {
if (buffer->fds_con[i] <= 0) {
fd = get_unused_fd_flags(O_CLOEXEC);
if (fd < 0)
goto no_memory;
fd_install(fd, buffer->file_buf[i]);
buffer->fds_con[i] = fd;
buffer->base.fds[i] = fd;
}
}
if (buffer->ready_render_fence) {
fd = get_unused_fd_flags(O_CLOEXEC);
if (fd < 0)
goto no_memory;
fd_install(fd, buffer->ready_render_fence);
buffer->base.fence_fd = fd;
buffer->ready_render_fence = NULL;
} else {
buffer->base.fence_fd = -1;
}
buffer->base.vt_id = inst->id;
data->base = buffer->base;
buffer->base.buf_status = RKVT_BUF_ACQUIRE;
rkvt_dbg(RKVT_DBG_BUFFERS, "VTAB [%d] pfd[0](%d) buf(%p) buf session(%p)\n",
inst->id, buffer->fds_pro[0], buffer, buffer->session_pro);
rkvt_inst_put(inst);
return 0;
no_memory:
pr_info("VTAB [%d] install fd error\n", inst->id);
mutex_lock(&inst->lock);
for (i = 0; i < buffer->base.num_fds; i++) {
rkvt_dbg(RKVT_DBG_FILE,
"VTAB [%d] install fd error file(%p) buf(%p) ino(%08lu) fcount=%d\n",
inst->id, buffer->file_buf[i], buffer,
file_inode(buffer->file_buf[i])->i_ino, inst->fcount);
if (buffer->file_buf[i]) {
fput(buffer->file_buf[i]);
buffer->file_buf[i] = NULL;
inst->fcount--;
}
}
if (buffer->ready_render_fence) {
fput(buffer->ready_render_fence);
buffer->ready_render_fence = NULL;
}
buffer->base.buf_status = RKVT_BUF_RELEASE;
kfifo_put(&inst->fifo_to_producer, buffer);
mutex_unlock(&inst->lock);
if (inst->producer)
wake_up_interruptible(&inst->wait_producer);
ret = -ENOMEM;
acquire_fail:
rkvt_inst_put(inst);
return ret;
}
static int
rkvt_release_buf(struct rkvt_buf_data *data, struct rkvt_session *session)
{
struct rkvt_dev *vt_dev = session->vt_dev;
struct rkvt_instance *inst = NULL;
struct rkvt_buf_base *buf_base = NULL;
struct rkvt_buffer *buffer = NULL;
int i;
int ret = 0;
long long read_buf_id;
inst = rkvt_inst_get_by_tid(vt_dev, data->vt_id);
if (!inst)
return -EINVAL;
if (!inst->consumer || inst->consumer != session) {
ret = -EINVAL;
goto release_fail;
}
buf_base = &data->base;
buffer = rkvt_buf_get(inst, buf_base->fds[0]);
if (!buffer) {
ret = -EINVAL;
goto release_fail;
}
if (buf_base->fence_fd >= 0)
buffer->rendered_fence = sync_file_get_fence(buf_base->fence_fd);
if (!buffer->rendered_fence)
rkvt_dbg(RKVT_DBG_BUFFERS, "VTRB [%d] rendered fence file is null\n", inst->id);
/* close the fds in consumer side */
for (i = 0; i < buf_base->num_fds; i++) {
rkvt_dbg(RKVT_DBG_FILE,
"VTRB [%d] file(%p) buf(%p) buf session(%p) ino(%08lu) fcount=%d\n",
inst->id, buffer->file_buf[i], buffer, buffer->session_pro,
buffer->file_buf[i] ? file_inode(buffer->file_buf[i])->i_ino : 0,
inst->fcount);
rkvt_close_fd(session, buffer->fds_con[i]);
inst->fcount--;
buffer->base.fds[i] = buffer->fds_pro[i];
}
if (buffer->ready_render_fence) {
fput(buffer->ready_render_fence);
buffer->ready_render_fence = NULL;
}
buffer->base.crop = buf_base->crop;
buffer->base.buf_status = RKVT_BUF_RELEASE;
mutex_lock(&inst->lock);
read_buf_id = atomic64_read(&inst->buf_id_generator);
/* if producer has disconnect */
if (!inst->producer) {
rkvt_dbg(RKVT_DBG_BUFFERS, "VTRB [%d], buffer no producer\n", inst->id);
buffer->base.buf_status = RKVT_BUF_FREE;
} else if ((buffer->base.buffer_id >> 8) != (read_buf_id >> 8)) {
dev_err(vt_dev->dev, "VTRB [%d] generation is different. cur(%lld) VS exp(%lld)\n",
inst->id, buffer->base.buffer_id >> 8, read_buf_id >> 8);
buffer->base.buf_status = RKVT_BUF_FREE;
} else {
if (buffer->session_pro &&
buffer->session_pro != inst->producer) {
rkvt_dbg(RKVT_DBG_BUFFERS,
"VTRB [%d] producer not valid, producer(%p), buf session(%p)\n",
inst->id, inst->producer, buffer->session_pro);
buffer->base.buf_status = RKVT_BUF_FREE;
}
kfifo_put(&inst->fifo_to_producer, buffer);
}
mutex_unlock(&inst->lock);
if (inst->producer)
wake_up_interruptible(&inst->wait_producer);
rkvt_dbg(RKVT_DBG_BUFFERS, "VTRB [%d] pfd[0]:%d end\n", inst->id, buffer->fds_pro[0]);
release_fail:
rkvt_inst_put(inst);
return ret;
}
static int
rkvt_cancel_buf(struct rkvt_buf_data *data, struct rkvt_session *session)
{
struct rkvt_dev *vt_dev = session->vt_dev;
struct rkvt_instance *inst = NULL;
struct rkvt_buf_base *buf_base = NULL;
struct rkvt_buffer *buffer = NULL;
int i;
inst = rkvt_inst_get_by_tid(vt_dev, data->vt_id);
if (!inst)
return -EINVAL;
if (!inst->producer || inst->producer != session) {
rkvt_inst_put(inst);
return -EINVAL;
}
rkvt_dbg(RKVT_DBG_BUFFERS, "VTCB [%d] start\n", inst->id);
buf_base = &data->base;
buffer = rkvt_get_free_buf(inst);
if (!buffer) {
dev_err(vt_dev->dev, "VTCB [%d] no unused buffer.\n", inst->id);
rkvt_inst_put(inst);
return -EINVAL;
}
for (i = 0; i < buf_base->num_fds; i++) {
buffer->fds_con[i] = -1;
buffer->fds_pro[i] = buf_base->fds[i];
rkvt_dbg(RKVT_DBG_FILE,
"VTCB [%d] fget file(%p) buf(%p) buf session(%p) fcount=%d\n",
inst->id, buffer->file_buf[i], buffer,
buffer->session_pro, inst->fcount);
}
// buffer id is empty, generate a new id
if (buf_base->buffer_id == 0)
buf_base->buffer_id = atomic64_inc_return(&inst->buf_id_generator);
buffer->base = *buf_base;
buffer->base.buf_status = RKVT_BUF_RELEASE;
buffer->session_pro = session;
buffer->cid_pro = session->cid;
mutex_lock(&inst->lock);
kfifo_put(&inst->fifo_to_producer, buffer);
mutex_unlock(&inst->lock);
if (inst->producer)
wake_up_interruptible(&inst->wait_producer);
rkvt_dbg(RKVT_DBG_BUFFERS, "VTCB [%d] pfd[0]:%d end\n", inst->id, buffer->fds_pro[0]);
rkvt_inst_put(inst);
return 0;
}
static unsigned int rkvt_ioctl_dir(unsigned int cmd)
{
switch (cmd) {
case RKVT_IOC_ALLOC_ID:
case RKVT_IOC_DEQUE_BUF:
case RKVT_IOC_ACQUIRE_BUF:
case RKVT_IOC_CTRL:
return _IOC_READ;
case RKVT_IOC_QUEUE_BUF:
case RKVT_IOC_RELEASE_BUF:
case RKVT_IOC_CANCEL_BUF:
case RKVT_IOC_FREE_ID:
return _IOC_WRITE;
default:
return _IOC_DIR(cmd);
}
}
static long rkvt_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
int ret = 0;
union rkvt_ioc_arg data;
struct rkvt_session *session = filep->private_data;
unsigned int dir = rkvt_ioctl_dir(cmd);
struct rkvt_dev *vt_dev = session->vt_dev;
struct rkvt_instance *inst = NULL;
rkvt_dbg(RKVT_DBG_CMD, "rkvt ioctl cmd 0x%x size %d in\n", cmd, _IOC_SIZE(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 RKVT_IOC_ALLOC_ID: {
char name[64];
inst = rkvt_inst_create(session->vt_dev);
if (IS_ERR(inst))
return PTR_ERR(inst);
mutex_lock(&vt_dev->inst_lock);
++vt_dev->inst_id_generator;
ret = idr_alloc(&vt_dev->inst_idr, inst,
vt_dev->inst_id_generator, 0, GFP_KERNEL);
mutex_unlock(&vt_dev->inst_lock);
if (ret < 0) {
rkvt_inst_put(inst);
return ret;
}
inst->id = ret;
snprintf(name, sizeof(name), "instance-%d", inst->id);
inst->debug_root =
debugfs_create_file(name, 0664, vt_dev->debug_root,
inst, &dbg_instance_fops);
mutex_lock(&vt_dev->inst_lock);
list_add_tail(&inst->session_link, &session->list_inst);
mutex_unlock(&vt_dev->inst_lock);
data.alloc_data.vt_id = inst->id;
rkvt_dbg(RKVT_DBG_USER, "rkvt alloc instance [%d], ref %d\n",
inst->id, kref_read(&inst->ref));
break;
}
case RKVT_IOC_FREE_ID: {
inst = rkvt_inst_get_by_tid(vt_dev, data.alloc_data.vt_id);
/* to do free id operation check */
if (!inst) {
dev_err(vt_dev->dev, "destroy unknown videotunnel instance:%d\n",
data.alloc_data.vt_id);
ret = -EINVAL;
} else {
rkvt_dbg(RKVT_DBG_USER, "rkvt free instance [%d], ref %d\n",
inst->id, kref_read(&inst->ref));
mutex_lock(&vt_dev->inst_lock);
list_del_init(&inst->session_link);
mutex_unlock(&vt_dev->inst_lock);
// ref put for rkvt_instance_get_by_tid
rkvt_inst_put(inst);
// ref put for kref_init in rkvt_inst_create
rkvt_inst_put(inst);
}
break;
}
case RKVT_IOC_CTRL:
ret = rkvt_ctrl_proc(&data.ctrl_data, session);
break;
case RKVT_IOC_QUEUE_BUF:
ret = rkvt_queue_buf(&data.buffer_data, session);
break;
case RKVT_IOC_DEQUE_BUF:
ret = rkvt_deque_buf(&data.buffer_data, session);
break;
case RKVT_IOC_RELEASE_BUF:
ret = rkvt_release_buf(&data.buffer_data, session);
break;
case RKVT_IOC_ACQUIRE_BUF:
ret = rkvt_acquire_buf(&data.buffer_data, session);
break;
case RKVT_IOC_CANCEL_BUF:
ret = rkvt_cancel_buf(&data.buffer_data, session);
break;
default:
dev_err(vt_dev->dev, "%s: cmd 0x%x not found.\n", __func__, cmd);
return -ENOTTY;
}
if (dir & _IOC_READ) {
if (copy_to_user((void __user *)arg, &data, _IOC_SIZE(cmd)))
return -EFAULT;
}
return ret;
}
static const struct file_operations vt_fops = {
.owner = THIS_MODULE,
.open = rkvt_open,
.release = rkvt_release,
.unlocked_ioctl = rkvt_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = rkvt_ioctl,
#endif
};
static int rkvt_probe(struct platform_device *pdev)
{
int ret;
struct device *dev = &pdev->dev;
struct rkvt_dev *vdev = NULL;
dev_info(dev, "probe start\n");
vdev = devm_kzalloc(dev, sizeof(*vdev), GFP_KERNEL);
if (!vdev)
return -ENOMEM;
vdev->dev = dev;
vdev->dev_name = DEVICE_NAME;
vdev->mdev.minor = MISC_DYNAMIC_MINOR;
vdev->mdev.name = DEVICE_NAME;
vdev->mdev.fops = &vt_fops;
platform_set_drvdata(pdev, vdev);
ret = misc_register(&vdev->mdev);
if (ret) {
dev_err(dev, "misc_register fail.\n");
return ret;
}
mutex_init(&vdev->inst_lock);
mutex_init(&vdev->session_lock);
idr_init(&vdev->inst_idr);
atomic64_set(&vdev->cid_generator, 0);
INIT_LIST_HEAD(&vdev->list_inst);
INIT_LIST_HEAD(&vdev->list_session);
vdev->debug_root = debugfs_create_dir(DEVICE_NAME, NULL);
if (!vdev->debug_root)
dev_err(dev, "failed to create debugfs root directory.\n");
dev_info(dev, "probe success\n");
return 0;
}
static int rkvt_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rkvt_dev *vdev = platform_get_drvdata(pdev);
dev_info(dev, "remove device\n");
idr_destroy(&vdev->inst_idr);
debugfs_remove_recursive(vdev->debug_root);
misc_deregister(&vdev->mdev);
return 0;
}
static const struct of_device_id rk_vt_match[] = {
{
.compatible = "rockchip,video-tunnel",
},
{ },
};
static struct platform_driver rk_vt_driver = {
.probe = rkvt_probe,
.remove = rkvt_remove,
.driver = {
.name = "rk_videotunnel_driver",
.owner = THIS_MODULE,
.of_match_table = rk_vt_match,
},
};
module_platform_driver(rk_vt_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ROCKCHIP videotunnel driver");