// SPDX-License-Identifier: GPL-2.0 /* * drivers/rpbuf/rpbuf_dev.c * * (C) Copyright 2020-2025 * Allwinner Technology Co., Ltd. * Junyan Lin * * RPBuf devices. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * */ #define pr_fmt(fmt) "[RPBUF](%s:%d) " fmt, __func__, __LINE__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rpbuf_internal.h" #define RPBUF_DEV_MAX (MINORMASK + 1) #define RPBUF_DEV_WAIT_TIMEOUT_MSEC 15000 static dev_t rpbuf_major; static struct class *rpbuf_class; static DEFINE_IDA(rpbuf_minor_ida); #define dev_to_ctrl_dev(dev) container_of(dev, struct rpbuf_ctrl_dev, dev) #define cdev_to_ctrl_dev(i_cdev) container_of(i_cdev, struct rpbuf_ctrl_dev, cdev) #define dev_to_buf_dev(dev) container_of(dev, struct rpbuf_buf_dev, dev) #define cdev_to_buf_dev(i_cdev) container_of(i_cdev, struct rpbuf_buf_dev, cdev) #define RPBUF_CTRL_DEV_IOCTL_CMD_VALID(cmd) (_IOC_TYPE((cmd)) == RPBUF_CTRL_DEV_IOCTL_MAGIC) #define RPBUF_BUF_DEV_IOCTL_CMD_VALID(cmd) (_IOC_TYPE((cmd)) == RPBUF_BUF_DEV_IOCTL_MAGIC) struct rpbuf_ctrl_dev { struct rpbuf_controller *controller; struct cdev cdev; struct device dev; struct list_head buf_devs; struct mutex buf_devs_lock; struct list_head list; }; struct rpbuf_buf_dev { struct rpbuf_buffer_info info; struct rpbuf_buffer *buffer; struct rpbuf_controller *controller; struct cdev cdev; struct device dev; atomic_t can_be_opened; struct completion available; int recv_data_offset; int recv_data_len; spinlock_t recv_lock; wait_queue_head_t recv_wq; struct list_head list; struct fasync_struct *async_queue; }; static LIST_HEAD(__rpbuf_ctrl_devs); static DEFINE_MUTEX(__rpbuf_ctrl_devs_lock); static inline struct rpbuf_ctrl_dev *rpbuf_find_ctrl_dev_by_id(int id) { struct rpbuf_ctrl_dev *ctrl_dev; list_for_each_entry(ctrl_dev, &__rpbuf_ctrl_devs, list) { if (ctrl_dev->dev.id == id) return ctrl_dev; } return NULL; } static inline struct rpbuf_buf_dev *rpbuf_find_buf_dev_by_name( struct list_head *buf_devs, const char *name) { struct rpbuf_buf_dev *buf_dev; list_for_each_entry(buf_dev, buf_devs, list) { if (0 == strncmp(buf_dev->info.name, name, RPBUF_NAME_SIZE)) return buf_dev; } return NULL; } static void rpbuf_buf_dev_buffer_avaiable_cb(struct rpbuf_buffer *buffer, void *priv) { struct rpbuf_buf_dev *buf_dev = priv; complete(&buf_dev->available); } static int rpbuf_buf_dev_buffer_rx_cb(struct rpbuf_buffer *buffer, void *data, int data_len, void *priv) { struct rpbuf_buf_dev *buf_dev = priv; struct device *dev = &buf_dev->dev; spin_lock(&buf_dev->recv_lock); if (buf_dev->recv_data_offset >= 0 || buf_dev->recv_data_len >= 0) dev_warn(dev, "previous recv data not treated\n"); buf_dev->recv_data_offset = data - buffer->va; buf_dev->recv_data_len = data_len; spin_unlock(&buf_dev->recv_lock); wake_up_interruptible(&buf_dev->recv_wq); kill_fasync(&buf_dev->async_queue, SIGIO, POLLIN | POLLRDNORM); return 0; } static int rpbuf_buf_dev_buffer_destroyed_cb(struct rpbuf_buffer *buffer, void *priv) { struct rpbuf_buf_dev *buf_dev = priv; struct device *dev = &buf_dev->dev; dev_dbg(dev, "%s:%d\n", __func__, __LINE__); kill_fasync(&buf_dev->async_queue, SIGIO, POLLHUP | POLLRDNORM); return 0; } static const struct rpbuf_buffer_cbs rpbuf_buf_dev_buffer_cbs = { .available_cb = rpbuf_buf_dev_buffer_avaiable_cb, .rx_cb = rpbuf_buf_dev_buffer_rx_cb, .destroyed_cb = rpbuf_buf_dev_buffer_destroyed_cb, }; static int rpbuf_buf_dev_open(struct inode *inode, struct file *file) { struct rpbuf_buf_dev *buf_dev = cdev_to_buf_dev(inode->i_cdev); struct device *dev = &buf_dev->dev; struct rpbuf_buffer *buffer; int ret; nonseekable_open(inode, file); if (!atomic_dec_and_test(&buf_dev->can_be_opened)) { atomic_inc(&buf_dev->can_be_opened); dev_err(dev, "buffer %s already in use\n", buf_dev->info.name); ret = -EBUSY; goto err_out; } get_device(dev); reinit_completion(&buf_dev->available); buffer = rpbuf_alloc_buffer(buf_dev->controller, buf_dev->info.name, buf_dev->info.len, NULL, &rpbuf_buf_dev_buffer_cbs, (void *)buf_dev); if (!buffer) { dev_err(dev, "rpbuf_alloc_buffer for %s failed\n", buf_dev->info.name); ret = -ENOENT; goto err_put_device; } buf_dev->buffer = buffer; ret = wait_for_completion_killable_timeout(&buf_dev->available, msecs_to_jiffies(RPBUF_DEV_WAIT_TIMEOUT_MSEC)); if (ret == -ERESTARTSYS) { dev_info(dev, "(%s:%d) kill signal occurs\n", __func__, __LINE__); goto err_free_buffer; } else if (ret < 0) { dev_err(dev, "wait for buffer %s available error (ret: %d)\n", buf_dev->info.name, ret); ret = -EIO; goto err_free_buffer; } else if (ret == 0) { dev_err(dev, "wait for buffer %s available timeout\n", buf_dev->info.name); ret = -EIO; goto err_free_buffer; } file->private_data = buf_dev; return 0; err_free_buffer: buf_dev->buffer = NULL; rpbuf_free_buffer(buffer); err_put_device: put_device(dev); atomic_inc(&buf_dev->can_be_opened); err_out: return ret; } static int rpbuf_buf_dev_release(struct inode *inode, struct file *file) { struct rpbuf_buf_dev *buf_dev = cdev_to_buf_dev(inode->i_cdev); struct device *dev = &buf_dev->dev; struct rpbuf_buffer *buffer; int ret; buffer = buf_dev->buffer; if (buffer) { ret = rpbuf_free_buffer(buffer); if (ret < 0) { dev_err(dev, "rpbuf_free_buffer for %s failed\n", buf_dev->info.name); goto err_out; } buf_dev->buffer = NULL; } put_device(&buf_dev->dev); atomic_inc(&buf_dev->can_be_opened); if (buf_dev->async_queue) fasync_helper(-1, file, 0, &buf_dev->async_queue); return 0; err_out: return ret; } static int rpbuf_buf_dev_mmap_default(struct rpbuf_buf_dev *buf_dev, struct vm_area_struct *vma) { struct rpbuf_buffer *buffer = buf_dev->buffer; struct device *dev = &buf_dev->dev; int ret; ret = remap_pfn_range(vma, vma->vm_start, (unsigned long)(buffer->pa) >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot); if (ret < 0) { dev_err(dev, "remap_pfn_rag failed (ret: %d)\n", ret); goto err_out; } return 0; err_out: return ret; } static int rpbuf_buf_dev_mmap(struct file *file, struct vm_area_struct *vma) { struct rpbuf_buf_dev *buf_dev = file->private_data; struct rpbuf_buffer *buffer = buf_dev->buffer; struct rpbuf_controller *controller = buffer->controller; struct rpbuf_link *link = controller->link; int ret; if (buffer->ops && buffer->ops->buf_dev_mmap) return buffer->ops->buf_dev_mmap(buffer, buffer->priv, vma); mutex_lock(&link->controller_lock); if (controller->ops && controller->ops->buf_dev_mmap) { ret = controller->ops->buf_dev_mmap(controller, buffer, controller->priv, vma); mutex_unlock(&link->controller_lock); return ret; } mutex_unlock(&link->controller_lock); return rpbuf_buf_dev_mmap_default(buf_dev, vma); } static int rpbuf_buf_dev_fasync(int fd, struct file *file, int mode) { struct rpbuf_buf_dev *buf_dev = file->private_data; return fasync_helper(fd, file, mode, &buf_dev->async_queue); } static unsigned int rpbuf_buf_dev_poll(struct file *file, struct poll_table_struct *wait) { struct rpbuf_buf_dev *buf_dev = file->private_data; unsigned int mask = 0; poll_wait(file, &buf_dev->recv_wq, wait); if (buf_dev->recv_data_offset > 0 && buf_dev->recv_data_len > 0) mask |= POLLIN | POLLRDNORM; return mask; } static int rpbuf_buf_dev_ioctl_check_avail(struct rpbuf_buf_dev *buf_dev, void __user *argp) { u8 is_available = rpbuf_buffer_is_available(buf_dev->buffer); put_user(is_available, (u8 __user *)argp); return 0; } static int rpbuf_buf_dev_ioctl_transmit_buf(struct rpbuf_buf_dev *buf_dev, void __user *argp) { struct device *dev = &buf_dev->dev; struct rpbuf_buffer_xfer __user *buf_xfer_user = argp; struct rpbuf_buffer_xfer buf_xfer; struct rpbuf_buffer *buffer = buf_dev->buffer; int ret; if (copy_from_user(&buf_xfer, buf_xfer_user, sizeof(struct rpbuf_buffer_xfer))) { dev_err(dev, "copy_from_user rpbuf_buffer_xfer failed\n"); ret = -EIO; goto err_out; } ret = rpbuf_transmit_buffer(buffer, buf_xfer.offset, buf_xfer.data_len); if (ret < 0) { dev_err(dev, "transmit to remote failed\n"); goto err_out; } return 0; err_out: return ret; } static int rpbuf_buf_dev_ioctl_receive_buf(struct rpbuf_buf_dev *buf_dev, void __user *argp) { struct device *dev = &buf_dev->dev; struct rpbuf_buffer_xfer __user *buf_xfer_user = argp; struct rpbuf_buffer_xfer buf_xfer; unsigned long flags; int timeout_ms; long timeout_jiffies; long remain; int ret; if (copy_from_user(&buf_xfer, buf_xfer_user, sizeof(struct rpbuf_buffer_xfer))) { dev_err(dev, "copy_from_user rpbuf_buffer_xfer failed\n"); ret = -EIO; goto err_out; } timeout_ms = buf_xfer.timeout_ms; spin_lock_irqsave(&buf_dev->recv_lock, flags); /* Wait for new received data */ while ((buf_dev->recv_data_offset < 0 || buf_dev->recv_data_len < 0) && buf_xfer.timeout_ms != 0) { spin_unlock_irqrestore(&buf_dev->recv_lock, flags); if (buf_xfer.timeout_ms < 0) { ret = wait_event_interruptible(buf_dev->recv_wq, (buf_dev->recv_data_offset >= 0 && buf_dev->recv_data_len >= 0)); if (ret) { dev_info(dev, "(%s:%d) signal occurs\n", __func__, __LINE__); ret = -ERESTARTSYS; goto err_out; } } else { timeout_jiffies = msecs_to_jiffies(timeout_ms); remain = wait_event_interruptible_timeout( buf_dev->recv_wq, (buf_dev->recv_data_offset >= 0 && buf_dev->recv_data_len >= 0), timeout_jiffies); if (remain < 0) { dev_info(dev, "(%s:%d) signal occurs (remain: %ld)\n", __func__, __LINE__, remain); ret = remain; goto err_out; } else if (remain == 0) { dev_dbg(dev, "(%s:%d) %dms elapsed\n", __func__, __LINE__, timeout_ms); timeout_ms = -ETIMEDOUT; spin_lock_irqsave(&buf_dev->recv_lock, flags); break; } else { timeout_ms = jiffies_to_msecs(remain); } } spin_lock_irqsave(&buf_dev->recv_lock, flags); } buf_xfer.offset = buf_dev->recv_data_offset; buf_xfer.data_len = buf_dev->recv_data_len; buf_xfer.timeout_ms = timeout_ms; buf_dev->recv_data_offset = -1; buf_dev->recv_data_len = -1; spin_unlock_irqrestore(&buf_dev->recv_lock, flags); if (copy_to_user(buf_xfer_user, &buf_xfer, sizeof(struct rpbuf_buffer_xfer))) { dev_err(dev, "copy_to_user rpbuf_buffer_xfer failed\n"); ret = -EIO; goto err_out; } return 0; err_out: return ret; } static int rpbuf_buf_dev_ioctl_set_sync_buf(struct rpbuf_buf_dev *buf_dev, void __user *argp) { struct device *dev = &buf_dev->dev; uint32_t __user *sync_user = argp; uint32_t is_sync; int ret = 0; if (copy_from_user(&is_sync, sync_user, sizeof(uint32_t))) { dev_err(dev, "copy_from_user uint32_t failed\n"); ret = -EIO; goto err_out; } if (is_sync) ret = rpbuf_buffer_set_sync(buf_dev->buffer, true); else ret = rpbuf_buffer_set_sync(buf_dev->buffer, false); err_out: return ret; } static long rpbuf_buf_dev_ioctl(struct file *file, unsigned int cmd, void __user *argp) { struct rpbuf_buf_dev *buf_dev = file->private_data; struct device *dev = &buf_dev->dev; int ret; if (!RPBUF_BUF_DEV_IOCTL_CMD_VALID(cmd)) return -EINVAL; switch (cmd) { case RPBUF_BUF_DEV_IOCTL_CHECK_AVAIL: ret = rpbuf_buf_dev_ioctl_check_avail(buf_dev, argp); break; case RPBUF_BUF_DEV_IOCTL_TRANSMIT_BUF: ret = rpbuf_buf_dev_ioctl_transmit_buf(buf_dev, argp); break; case RPBUF_BUF_DEV_IOCTL_RECEIVE_BUF: ret = rpbuf_buf_dev_ioctl_receive_buf(buf_dev, argp); break; case RPBUF_BUF_DEV_IOCTL_SET_SYNC_BUF: ret = rpbuf_buf_dev_ioctl_set_sync_buf(buf_dev, argp); break; default: dev_err(dev, "invalid rpbuf buf_dev ioctl cmd: 0x%x\n", cmd); return -EINVAL; } return ret; } static long rpbuf_buf_dev_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; return rpbuf_buf_dev_ioctl(file, cmd, argp); } #ifdef CONFIG_COMPAT static long rpbuf_buf_dev_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = compat_ptr(arg); return rpbuf_buf_dev_ioctl(file, cmd, argp); } #endif /* CONFIG_COMPAT */ static const struct file_operations rpbuf_buf_dev_fops = { .owner = THIS_MODULE, .open = rpbuf_buf_dev_open, .release = rpbuf_buf_dev_release, .llseek = no_llseek, .mmap = rpbuf_buf_dev_mmap, .fasync = rpbuf_buf_dev_fasync, .poll = rpbuf_buf_dev_poll, .unlocked_ioctl = rpbuf_buf_dev_unlocked_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = rpbuf_buf_dev_compat_ioctl, #endif }; static void rpbuf_buf_dev_device_release(struct device *dev) { struct rpbuf_buf_dev *buf_dev = dev_to_buf_dev(dev); dev_dbg(dev->parent, "%s:%d\n", __func__, __LINE__); ida_simple_remove(&rpbuf_minor_ida, MINOR(dev->devt)); cdev_del(&buf_dev->cdev); devm_kfree(dev->parent, buf_dev); } static int rpbuf_ctrl_dev_ioctl_create_buf(struct rpbuf_ctrl_dev *ctrl_dev, struct rpbuf_buf_dev **buf_dev_created, void __user *argp) { struct device *dev = &ctrl_dev->dev; struct rpbuf_buffer_info __user *buf_info_user = argp; struct rpbuf_buffer_info buf_info; struct rpbuf_buf_dev *buf_dev; int ret; if (copy_from_user(&buf_info, buf_info_user, sizeof(struct rpbuf_buffer_info))) { dev_err(dev, "copy_from_user rpbuf_buffer_info failed\n"); ret = -EIO; goto err_out; } mutex_lock(&ctrl_dev->buf_devs_lock); buf_dev = rpbuf_find_buf_dev_by_name(&ctrl_dev->buf_devs, buf_info.name); if (buf_dev) { dev_err(dev, "buf_dev named %s already exists\n", buf_info.name); mutex_unlock(&ctrl_dev->buf_devs_lock); ret = -EEXIST; goto err_out; } buf_dev = devm_kzalloc(dev, sizeof(struct rpbuf_buf_dev), GFP_KERNEL); if (!buf_dev) { dev_err(dev, "kzalloc for rpbuf_buf_dev failed\n"); mutex_unlock(&ctrl_dev->buf_devs_lock); ret = -ENOMEM; goto err_out; } memcpy(&buf_dev->info, &buf_info, sizeof(struct rpbuf_buffer_info)); buf_dev->controller = ctrl_dev->controller; atomic_set(&buf_dev->can_be_opened, 1); init_completion(&buf_dev->available); spin_lock_init(&buf_dev->recv_lock); init_waitqueue_head(&buf_dev->recv_wq); buf_dev->recv_data_offset = -1; buf_dev->recv_data_len = -1; device_initialize(&buf_dev->dev); buf_dev->dev.parent = dev; buf_dev->dev.class = rpbuf_class; cdev_init(&buf_dev->cdev, &rpbuf_buf_dev_fops); buf_dev->cdev.owner = THIS_MODULE; ret = ida_simple_get(&rpbuf_minor_ida, 0, RPBUF_DEV_MAX, GFP_KERNEL); if (ret < 0) { dev_err(dev, "ida_simple_get for rpbuf minor failed\n"); mutex_unlock(&ctrl_dev->buf_devs_lock); goto err_put_device; } buf_dev->dev.devt = MKDEV(MAJOR(rpbuf_major), ret); dev_set_name(&buf_dev->dev, "rpbuf-%s", buf_dev->info.name); ret = cdev_add(&buf_dev->cdev, buf_dev->dev.devt, 1); if (ret < 0) { dev_err(dev, "cdev_add for rpbuf buf_dev failed\n"); mutex_unlock(&ctrl_dev->buf_devs_lock); goto err_remove_minor_ida; } /* We can now rely on the release function for cleanup */ buf_dev->dev.release = rpbuf_buf_dev_device_release; ret = device_add(&buf_dev->dev); if (ret < 0) { dev_err(dev, "device_add for rpbuf buf_dev failed\n"); mutex_unlock(&ctrl_dev->buf_devs_lock); put_device(&buf_dev->dev); goto err_out; } list_add_tail(&buf_dev->list, &ctrl_dev->buf_devs); *buf_dev_created = buf_dev; mutex_unlock(&ctrl_dev->buf_devs_lock); dev_dbg(dev, "%s:%d\n", __func__, __LINE__); return 0; err_remove_minor_ida: ida_simple_remove(&rpbuf_minor_ida, MINOR(buf_dev->dev.devt)); err_put_device: put_device(&buf_dev->dev); devm_kfree(dev, buf_dev); err_out: return ret; } static int rpbuf_ctrl_dev_ioctl_destroy_buf(struct rpbuf_ctrl_dev *ctrl_dev, void __user *argp) { struct device *dev = &ctrl_dev->dev; struct rpbuf_buffer_info __user *buf_info_user = argp; struct rpbuf_buffer_info buf_info; struct rpbuf_buf_dev *buf_dev; int ret; if (copy_from_user(&buf_info, buf_info_user, sizeof(struct rpbuf_buffer_info))) { dev_err(dev, "copy_from_user rpbuf_buffer_info failed\n"); ret = -EIO; goto err_out; } mutex_lock(&ctrl_dev->buf_devs_lock); buf_dev = rpbuf_find_buf_dev_by_name(&ctrl_dev->buf_devs, buf_info.name); if (!buf_dev) { dev_err(dev, "buf_dev named %s not found\n", buf_info.name); mutex_unlock(&ctrl_dev->buf_devs_lock); ret = -ENOENT; goto err_out; } list_del(&buf_dev->list); mutex_unlock(&ctrl_dev->buf_devs_lock); dev_dbg(dev, "%s:%d\n", __func__, __LINE__); device_del(&buf_dev->dev); put_device(&buf_dev->dev); return 0; err_out: return ret; } static long rpbuf_ctrl_dev_ioctl(struct file *file, unsigned int cmd, void __user *argp) { struct rpbuf_ctrl_dev *ctrl_dev = cdev_to_ctrl_dev(file->f_inode->i_cdev); struct rpbuf_buf_dev *buf_dev; struct device *dev = &ctrl_dev->dev; int ret; if (!RPBUF_CTRL_DEV_IOCTL_CMD_VALID(cmd)) return -EINVAL; switch (cmd) { case RPBUF_CTRL_DEV_IOCTL_CREATE_BUF: ret = rpbuf_ctrl_dev_ioctl_create_buf(ctrl_dev, &buf_dev, argp); if (ret < 0) return ret; file->private_data = buf_dev; break; case RPBUF_CTRL_DEV_IOCTL_DESTROY_BUF: ret = rpbuf_ctrl_dev_ioctl_destroy_buf(ctrl_dev, argp); if (ret < 0) return ret; file->private_data = NULL; break; default: dev_err(dev, "invalid rpbuf ctrl_dev ioctl cmd: 0x%x\n", cmd); return -EINVAL; } return ret; } static long rpbuf_ctrl_dev_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; return rpbuf_ctrl_dev_ioctl(file, cmd, argp); } #ifdef CONFIG_COMPAT static long rpbuf_ctrl_dev_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = compat_ptr(arg); return rpbuf_ctrl_dev_ioctl(file, cmd, argp); } #endif /* CONFIG_COMPAT */ static int rpbuf_ctrl_dev_open(struct inode *inode, struct file *file) { struct rpbuf_ctrl_dev *ctrl_dev = cdev_to_ctrl_dev(inode->i_cdev); nonseekable_open(inode, file); get_device(&ctrl_dev->dev); return 0; } static int rpbuf_ctrl_dev_release(struct inode *inode, struct file *file) { struct rpbuf_ctrl_dev *ctrl_dev = cdev_to_ctrl_dev(inode->i_cdev); struct rpbuf_buf_dev *buf_dev = file->private_data; if (buf_dev) { /* * User space program forgot to call ioctl(RPBUF_CTRL_DEV_IOCTL_DESTROY_BUF) * to destroy buf_dev. Now we destroy it manually. */ mutex_lock(&ctrl_dev->buf_devs_lock); list_del(&buf_dev->list); mutex_unlock(&ctrl_dev->buf_devs_lock); device_del(&buf_dev->dev); put_device(&buf_dev->dev); file->private_data = NULL; } put_device(&ctrl_dev->dev); return 0; } static const struct file_operations rpbuf_ctrl_dev_fops = { .owner = THIS_MODULE, .open = rpbuf_ctrl_dev_open, .release = rpbuf_ctrl_dev_release, .unlocked_ioctl = rpbuf_ctrl_dev_unlocked_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = rpbuf_ctrl_dev_compat_ioctl, #endif .llseek = no_llseek, }; static void rpbuf_ctrl_dev_device_release(struct device *dev) { struct rpbuf_ctrl_dev *ctrl_dev = dev_to_ctrl_dev(dev); dev_dbg(dev->parent, "%s:%d\n", __func__, __LINE__); mutex_destroy(&ctrl_dev->buf_devs_lock); ida_simple_remove(&rpbuf_minor_ida, MINOR(dev->devt)); cdev_del(&ctrl_dev->cdev); devm_kfree(dev->parent, ctrl_dev); } int rpbuf_register_ctrl_dev(struct device *dev, int id, struct rpbuf_controller *controller) { struct rpbuf_ctrl_dev *ctrl_dev; int ret; if (IS_ERR_OR_NULL(dev) || IS_ERR_OR_NULL(controller)) { pr_err("invalid arguments\n"); ret = -EINVAL; goto err_out; } mutex_lock(&__rpbuf_ctrl_devs_lock); ctrl_dev = rpbuf_find_ctrl_dev_by_id(id); if (ctrl_dev) { dev_err(dev, "ctrl_dev with id %d already exists\n", id); mutex_unlock(&__rpbuf_ctrl_devs_lock); ret = -EEXIST; goto err_out; } ctrl_dev = devm_kzalloc(dev, sizeof(struct rpbuf_ctrl_dev), GFP_KERNEL); if (!ctrl_dev) { dev_err(dev, "kzalloc for rpbuf_ctrl_dev failed\n"); mutex_unlock(&__rpbuf_ctrl_devs_lock); ret = -ENOMEM; goto err_out; } ctrl_dev->controller = controller; device_initialize(&ctrl_dev->dev); ctrl_dev->dev.parent = dev; ctrl_dev->dev.class = rpbuf_class; cdev_init(&ctrl_dev->cdev, &rpbuf_ctrl_dev_fops); ctrl_dev->cdev.owner = THIS_MODULE; ret = ida_simple_get(&rpbuf_minor_ida, 0, RPBUF_DEV_MAX, GFP_KERNEL); if (ret < 0) { dev_err(dev, "ida_simple_get for rpbuf minor failed\n"); mutex_unlock(&__rpbuf_ctrl_devs_lock); goto err_free_ctrl_dev; } ctrl_dev->dev.devt = MKDEV(MAJOR(rpbuf_major), ret); ctrl_dev->dev.id = id; dev_set_name(&ctrl_dev->dev, "rpbuf_ctrl%d", id); ret = cdev_add(&ctrl_dev->cdev, ctrl_dev->dev.devt, 1); if (ret < 0) { dev_err(dev, "cdev_add for rpbuf ctrl_dev failed\n"); mutex_unlock(&__rpbuf_ctrl_devs_lock); goto err_remove_minor_ida; } INIT_LIST_HEAD(&ctrl_dev->buf_devs); mutex_init(&ctrl_dev->buf_devs_lock); /* We can now rely on the release function for cleanup */ ctrl_dev->dev.release = rpbuf_ctrl_dev_device_release; ret = device_add(&ctrl_dev->dev); if (ret < 0) { dev_err(dev, "device_add for rpbuf ctrl_dev failed\n"); mutex_unlock(&__rpbuf_ctrl_devs_lock); put_device(&ctrl_dev->dev); goto err_out; } list_add_tail(&ctrl_dev->list, &__rpbuf_ctrl_devs); mutex_unlock(&__rpbuf_ctrl_devs_lock); dev_dbg(dev, "%s:%d\n", __func__, __LINE__); return 0; err_remove_minor_ida: ida_simple_remove(&rpbuf_minor_ida, MINOR(ctrl_dev->dev.devt)); err_free_ctrl_dev: put_device(&ctrl_dev->dev); devm_kfree(dev, ctrl_dev); err_out: return ret; } EXPORT_SYMBOL(rpbuf_register_ctrl_dev); int rpbuf_unregister_ctrl_dev(struct device *dev, int id) { struct rpbuf_ctrl_dev *ctrl_dev; struct rpbuf_buf_dev *buf_dev; struct rpbuf_buf_dev *tmp; int ret; if (IS_ERR_OR_NULL(dev)) { pr_err("invalid arguments\n"); ret = -EINVAL; goto err_out; } mutex_lock(&__rpbuf_ctrl_devs_lock); ctrl_dev = rpbuf_find_ctrl_dev_by_id(id); if (!ctrl_dev) { dev_err(dev, "ctrl_dev with id %d not found\n", id); mutex_unlock(&__rpbuf_ctrl_devs_lock); ret = -ENOENT; goto err_out; } list_del(&ctrl_dev->list); mutex_unlock(&__rpbuf_ctrl_devs_lock); dev_dbg(dev, "%s:%d\n", __func__, __LINE__); list_for_each_entry_safe(buf_dev, tmp, &ctrl_dev->buf_devs, list) { list_del(&buf_dev->list); device_del(&buf_dev->dev); put_device(&buf_dev->dev); } device_del(&ctrl_dev->dev); put_device(&ctrl_dev->dev); return 0; err_out: return ret; } EXPORT_SYMBOL(rpbuf_unregister_ctrl_dev); static int rpbuf_dev_init(void) { int ret; ret = alloc_chrdev_region(&rpbuf_major, 0, RPBUF_DEV_MAX, "rpbuf"); if (ret < 0) { pr_err("rpbuf: failed to allocate char dev region\n"); goto err_out; } rpbuf_class = class_create(THIS_MODULE, "rpbuf"); if (IS_ERR(rpbuf_class)) { pr_err("failed to create rpbuf class\n"); ret = PTR_ERR(rpbuf_class); goto err_unregister_chrdev_region; } return 0; err_unregister_chrdev_region: unregister_chrdev_region(rpbuf_major, RPBUF_DEV_MAX); err_out: return ret; } postcore_initcall(rpbuf_dev_init); static void rpbuf_dev_exit(void) { class_destroy(rpbuf_class); unregister_chrdev_region(rpbuf_major, RPBUF_DEV_MAX); } module_exit(rpbuf_dev_exit); MODULE_DESCRIPTION("RPBuf devices"); MODULE_AUTHOR("Junyan Lin "); MODULE_LICENSE("GPL v2"); MODULE_VERSION("1.0.0");