From da317c67760aa3ac7ecc910d41f44c7a89b89fa0 Mon Sep 17 00:00:00 2001 From: Rimon Xu Date: Fri, 9 Dec 2022 16:20:19 +0800 Subject: [PATCH] video: rockchip: vtunnel: add video tunnel driver Signed-off-by: Rimon Xu Change-Id: Ic621f6fcbc4924bd5567c9161f358e9365830618 --- drivers/video/rockchip/Kconfig | 1 + drivers/video/rockchip/Makefile | 1 + drivers/video/rockchip/vtunnel/Kconfig | 12 + drivers/video/rockchip/vtunnel/Makefile | 3 + drivers/video/rockchip/vtunnel/rkvtunnel.c | 1523 ++++++++++++++++++++ drivers/video/rockchip/vtunnel/rkvtunnel.h | 81 ++ 6 files changed, 1621 insertions(+) create mode 100644 drivers/video/rockchip/vtunnel/Kconfig create mode 100644 drivers/video/rockchip/vtunnel/Makefile create mode 100644 drivers/video/rockchip/vtunnel/rkvtunnel.c create mode 100644 drivers/video/rockchip/vtunnel/rkvtunnel.h diff --git a/drivers/video/rockchip/Kconfig b/drivers/video/rockchip/Kconfig index 2b5a072d2cdd..571d34c08603 100644 --- a/drivers/video/rockchip/Kconfig +++ b/drivers/video/rockchip/Kconfig @@ -7,3 +7,4 @@ source "drivers/video/rockchip/iep/Kconfig" source "drivers/video/rockchip/mpp/Kconfig" source "drivers/video/rockchip/dvbm/Kconfig" source "drivers/video/rockchip/vehicle/Kconfig" +source "drivers/video/rockchip/vtunnel/Kconfig" diff --git a/drivers/video/rockchip/Makefile b/drivers/video/rockchip/Makefile index 0b8a7bbb95e6..b13d93a1cd6f 100644 --- a/drivers/video/rockchip/Makefile +++ b/drivers/video/rockchip/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_IEP) += iep/ obj-$(CONFIG_ROCKCHIP_MPP_SERVICE) += mpp/ obj-$(CONFIG_ROCKCHIP_DVBM) += dvbm/ obj-$(CONFIG_VIDEO_REVERSE_IMAGE) += vehicle/ +obj-$(CONFIG_ROCKCHIP_VIDEO_TUNNEL) += vtunnel/ diff --git a/drivers/video/rockchip/vtunnel/Kconfig b/drivers/video/rockchip/vtunnel/Kconfig new file mode 100644 index 000000000000..49ad2a592bb0 --- /dev/null +++ b/drivers/video/rockchip/vtunnel/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: (GPL-2.0+ OR MIT) + +menu "Rockchip video tunnel support" + +config ROCKCHIP_VIDEO_TUNNEL + tristate "Rockchip video tunnel device support" + depends on ARCH_ROCKCHIP + default n + help + Rockchip videotunnel device support. + +endmenu diff --git a/drivers/video/rockchip/vtunnel/Makefile b/drivers/video/rockchip/vtunnel/Makefile new file mode 100644 index 000000000000..fdfd79a1c0a2 --- /dev/null +++ b/drivers/video/rockchip/vtunnel/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_ROCKCHIP_VIDEO_TUNNEL) += rkvtunnel.o diff --git a/drivers/video/rockchip/vtunnel/rkvtunnel.c b/drivers/video/rockchip/vtunnel/rkvtunnel.c new file mode 100644 index 000000000000..238a6516fc32 --- /dev/null +++ b/drivers/video/rockchip/vtunnel/rkvtunnel.c @@ -0,0 +1,1523 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) Rockchip Electronics Co., Ltd. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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; + atomic64_t buf_id_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]; +}; + +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 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(&vt_dev->buf_id_generator); + read_buf_id += 0x100; + read_buf_id &= ~0xff; + atomic64_set(&vt_dev->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); + 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(&vt_dev->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->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 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(&vt_dev->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); + 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(&vt_dev->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"); diff --git a/drivers/video/rockchip/vtunnel/rkvtunnel.h b/drivers/video/rockchip/vtunnel/rkvtunnel.h new file mode 100644 index 000000000000..1781aa785bc0 --- /dev/null +++ b/drivers/video/rockchip/vtunnel/rkvtunnel.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ +/* + * Copyright (c) 2022 Rockchip Electronics Co., Ltd + */ +#ifndef __ROCKCHIP_VIDEO_TUNNEL_H__ +#define __ROCKCHIP_VIDEO_TUNNEL_H__ + +#include +#include + +#define MAX_BUF_HANDLE_FDS 16 +#define MAX_BUF_HANDLE_INTS 128 + +#define RKVT_IOC_MAGIC 'V' +#define RKVT_IOWR(nr, type) _IOWR(RKVT_IOC_MAGIC, nr, type) + +#define RKVT_IOC_ALLOC_ID RKVT_IOWR(0x0, struct rkvt_alloc_id_data) +#define RKVT_IOC_FREE_ID RKVT_IOWR(0x1, struct rkvt_alloc_id_data) +#define RKVT_IOC_CTRL RKVT_IOWR(0x2, struct rkvt_ctrl_data) +#define RKVT_IOC_QUEUE_BUF RKVT_IOWR(0x3, struct rkvt_buf_data) +#define RKVT_IOC_DEQUE_BUF RKVT_IOWR(0x4, struct rkvt_buf_data) +#define RKVT_IOC_CANCEL_BUF RKVT_IOWR(0x5, struct rkvt_buf_data) +#define RKVT_IOC_ACQUIRE_BUF RKVT_IOWR(0x6, struct rkvt_buf_data) +#define RKVT_IOC_RELEASE_BUF RKVT_IOWR(0x7, struct rkvt_buf_data) + +// caller type +enum rkvt_caller_e { + RKVT_CALLER_PRODUCER, + RKVT_CALLER_CONSUMER, + RKVT_CALLER_BUTT, +}; + +// video tunnel caller control +enum rkvt_ctrl_cmd_e { + RKVT_CTRL_CONNECT, + RKVT_CTRL_DISCONNECT, + RKVT_CTRL_RESET, + RKVT_CTRL_HAS_CONSUMER, + RKVT_CTRL_BUTT, +}; + +struct rkvt_alloc_id_data { + int vt_id; +}; + +struct rkvt_ctrl_data { + int vt_id; + enum rkvt_caller_e caller; + enum rkvt_ctrl_cmd_e ctrl_cmd; + int ctrl_data; +}; + +struct rkvt_rect { + int left; + int top; + int right; + int bottom; +}; + +struct rkvt_buf_base { + int vt_id; + int fence_fd; + int buf_status; + int num_fds; /* number of file-descriptors at &data[0] */ + int num_ints; /* number of ints at &data[numFds] */ + int reserved; + int fds[MAX_BUF_HANDLE_FDS]; + int ints[MAX_BUF_HANDLE_INTS]; + int64_t priv_data; + uint64_t expected_present_time; + uint64_t buffer_id; + struct rkvt_rect crop; +}; + +struct rkvt_buf_data { + int vt_id; + int timeout_ms; /* 0: non block, negative: block, other: timeout ms */ + struct rkvt_buf_base base; +}; + +#endif