// 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; 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");