1538 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			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");
 |