/* * PRU Remote Processor Messaging Driver * * Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com/ * Jason Reeder * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #define PRU_MAX_DEVICES (8) /* Matches the definition in virtio_rpmsg_bus.c */ #define RPMSG_BUF_SIZE (512) #define MAX_FIFO_MSG (32) #define FIFO_MSG_SIZE RPMSG_BUF_SIZE /** * struct rpmsg_pru_dev - Structure that contains the per-device data * @rpdev: rpmsg channel device that is associated with this rpmsg_pru device * @dev: device * @cdev: character device * @locked: boolean used to determine whether or not the device file is in use * @devt: dev_t structure for the rpmsg_pru device * @msg_fifo: kernel fifo used to buffer the messages between userspace and PRU * @msg_len: array storing the lengths of each message in the kernel fifo * @msg_idx_rd: kernel fifo read index * @msg_idx_wr: kernel fifo write index * @wait_list: wait queue used to implement the poll operation of the character * device * * Each rpmsg_pru device provides an interface, using an rpmsg channel (rpdev), * between a user space character device (cdev) and a PRU core. A kernel fifo * (msg_fifo) is used to buffer the messages in the kernel that are * being passed between the character device and the PRU. */ struct rpmsg_pru_dev { struct rpmsg_channel *rpdev; struct device *dev; struct cdev cdev; bool locked; dev_t devt; struct kfifo msg_fifo; u32 msg_len[MAX_FIFO_MSG]; int msg_idx_rd; int msg_idx_wr; wait_queue_head_t wait_list; }; static struct class *rpmsg_pru_class; static dev_t rpmsg_pru_devt; static DEFINE_MUTEX(rpmsg_pru_lock); static DEFINE_IDR(rpmsg_pru_minors); static int rpmsg_pru_open(struct inode *inode, struct file *filp) { struct rpmsg_pru_dev *prudev; int ret = -EACCES; prudev = container_of(inode->i_cdev, struct rpmsg_pru_dev, cdev); mutex_lock(&rpmsg_pru_lock); if (!prudev->locked) { prudev->locked = true; filp->private_data = prudev; ret = 0; } mutex_unlock(&rpmsg_pru_lock); if (ret) dev_err(prudev->dev, "Device already open\n"); return ret; } static int rpmsg_pru_release(struct inode *inode, struct file *filp) { struct rpmsg_pru_dev *prudev; prudev = container_of(inode->i_cdev, struct rpmsg_pru_dev, cdev); mutex_lock(&rpmsg_pru_lock); prudev->locked = false; mutex_unlock(&rpmsg_pru_lock); return 0; } static ssize_t rpmsg_pru_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { int ret; u32 length; struct rpmsg_pru_dev *prudev; prudev = filp->private_data; if (kfifo_is_empty(&prudev->msg_fifo) && (filp->f_flags & O_NONBLOCK)) return -EAGAIN; ret = wait_event_interruptible(prudev->wait_list, !kfifo_is_empty(&prudev->msg_fifo)); if (ret) return -EINTR; ret = kfifo_to_user(&prudev->msg_fifo, buf, prudev->msg_len[prudev->msg_idx_rd], &length); prudev->msg_idx_rd = (prudev->msg_idx_rd + 1) % MAX_FIFO_MSG; return ret ? ret : length; } static ssize_t rpmsg_pru_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { int ret; struct rpmsg_pru_dev *prudev; static char rpmsg_pru_buf[RPMSG_BUF_SIZE]; prudev = filp->private_data; if (count > RPMSG_BUF_SIZE - sizeof(struct rpmsg_hdr)) { dev_err(prudev->dev, "Data too large for RPMsg Buffer\n"); return -EINVAL; } if (copy_from_user(rpmsg_pru_buf, buf, count)) { dev_err(prudev->dev, "Error copying buffer from user space"); return -EFAULT; } ret = rpmsg_send(prudev->rpdev, (void *)rpmsg_pru_buf, count); if (ret) dev_err(prudev->dev, "rpmsg_send failed: %d\n", ret); return ret ? ret : count; } static unsigned int rpmsg_pru_poll(struct file *filp, struct poll_table_struct *wait) { int mask; struct rpmsg_pru_dev *prudev; prudev = filp->private_data; poll_wait(filp, &prudev->wait_list, wait); mask = POLLOUT | POLLWRNORM; if (!kfifo_is_empty(&prudev->msg_fifo)) mask |= POLLIN | POLLRDNORM; return mask; } static const struct file_operations rpmsg_pru_fops = { .owner = THIS_MODULE, .open = rpmsg_pru_open, .release = rpmsg_pru_release, .read = rpmsg_pru_read, .write = rpmsg_pru_write, .poll = rpmsg_pru_poll, }; static void rpmsg_pru_cb(struct rpmsg_channel *rpdev, void *data, int len, void *priv, u32 src) { u32 length; struct rpmsg_pru_dev *prudev; prudev = dev_get_drvdata(&rpdev->dev); if (kfifo_avail(&prudev->msg_fifo) < len) { dev_err(&rpdev->dev, "Not enough space on the FIFO\n"); return; } if ((prudev->msg_idx_wr + 1) % MAX_FIFO_MSG == prudev->msg_idx_rd) { dev_err(&rpdev->dev, "Message length table is full\n"); return; } length = kfifo_in(&prudev->msg_fifo, data, len); prudev->msg_len[prudev->msg_idx_wr] = length; prudev->msg_idx_wr = (prudev->msg_idx_wr + 1) % MAX_FIFO_MSG; wake_up_interruptible(&prudev->wait_list); } static int rpmsg_pru_probe(struct rpmsg_channel *rpdev) { int ret; struct rpmsg_pru_dev *prudev; int minor_got; prudev = devm_kzalloc(&rpdev->dev, sizeof(*prudev), GFP_KERNEL); if (!prudev) return -ENOMEM; mutex_lock(&rpmsg_pru_lock); minor_got = idr_alloc(&rpmsg_pru_minors, prudev, 0, PRU_MAX_DEVICES, GFP_KERNEL); mutex_unlock(&rpmsg_pru_lock); if (minor_got < 0) { ret = minor_got; dev_err(&rpdev->dev, "Failed to get a minor number for the rpmsg_pru device: %d\n", ret); goto fail_alloc_minor; } prudev->devt = MKDEV(MAJOR(rpmsg_pru_devt), minor_got); cdev_init(&prudev->cdev, &rpmsg_pru_fops); prudev->cdev.owner = THIS_MODULE; ret = cdev_add(&prudev->cdev, prudev->devt, 1); if (ret) { dev_err(&rpdev->dev, "Unable to add cdev for the rpmsg_pru device\n"); goto fail_add_cdev; } prudev->dev = device_create(rpmsg_pru_class, &rpdev->dev, prudev->devt, NULL, "rpmsg_pru%d", rpdev->dst); if (IS_ERR(prudev->dev)) { dev_err(&rpdev->dev, "Unable to create the rpmsg_pru device\n"); ret = PTR_ERR(prudev->dev); goto fail_create_device; } prudev->rpdev = rpdev; ret = kfifo_alloc(&prudev->msg_fifo, MAX_FIFO_MSG * FIFO_MSG_SIZE, GFP_KERNEL); if (ret) { dev_err(&rpdev->dev, "Unable to allocate fifo for the rpmsg_pru device\n"); goto fail_alloc_fifo; } init_waitqueue_head(&prudev->wait_list); dev_set_drvdata(&rpdev->dev, prudev); dev_info(&rpdev->dev, "new rpmsg_pru device: /dev/rpmsg_pru%d", rpdev->dst); return 0; fail_alloc_fifo: device_destroy(rpmsg_pru_class, prudev->devt); fail_create_device: cdev_del(&prudev->cdev); fail_add_cdev: mutex_lock(&rpmsg_pru_lock); idr_remove(&rpmsg_pru_minors, minor_got); mutex_unlock(&rpmsg_pru_lock); fail_alloc_minor: return ret; } static void rpmsg_pru_remove(struct rpmsg_channel *rpdev) { struct rpmsg_pru_dev *prudev; prudev = dev_get_drvdata(&rpdev->dev); kfifo_free(&prudev->msg_fifo); device_destroy(rpmsg_pru_class, prudev->devt); cdev_del(&prudev->cdev); mutex_lock(&rpmsg_pru_lock); idr_remove(&rpmsg_pru_minors, MINOR(prudev->devt)); mutex_unlock(&rpmsg_pru_lock); } /* .name matches on RPMsg Channels and causes a probe */ static const struct rpmsg_device_id rpmsg_driver_pru_id_table[] = { { .name = "rpmsg-pru" }, { }, }; MODULE_DEVICE_TABLE(rpmsg, rpmsg_driver_pru_id_table); static struct rpmsg_driver rpmsg_pru_driver = { .drv.name = KBUILD_MODNAME, .drv.owner = THIS_MODULE, .id_table = rpmsg_driver_pru_id_table, .probe = rpmsg_pru_probe, .callback = rpmsg_pru_cb, .remove = rpmsg_pru_remove, }; static int __init rpmsg_pru_init(void) { int ret; rpmsg_pru_class = class_create(THIS_MODULE, "rpmsg_pru"); if (IS_ERR(rpmsg_pru_class)) { pr_err("Unable to create class\n"); ret = PTR_ERR(rpmsg_pru_class); goto fail_create_class; } ret = alloc_chrdev_region(&rpmsg_pru_devt, 0, PRU_MAX_DEVICES, "rpmsg_pru"); if (ret) { pr_err("Unable to allocate chrdev region\n"); goto fail_alloc_region; } ret = register_rpmsg_driver(&rpmsg_pru_driver); if (ret) { pr_err("Unable to register rpmsg driver"); goto fail_register_rpmsg_driver; } return 0; fail_register_rpmsg_driver: unregister_chrdev_region(rpmsg_pru_devt, PRU_MAX_DEVICES); fail_alloc_region: class_destroy(rpmsg_pru_class); fail_create_class: return ret; } static void __exit rpmsg_pru_exit(void) { unregister_rpmsg_driver(&rpmsg_pru_driver); idr_destroy(&rpmsg_pru_minors); mutex_destroy(&rpmsg_pru_lock); class_destroy(rpmsg_pru_class); unregister_chrdev_region(rpmsg_pru_devt, PRU_MAX_DEVICES); } module_init(rpmsg_pru_init); module_exit(rpmsg_pru_exit); MODULE_AUTHOR("Jason Reeder "); MODULE_ALIAS("rpmsg:rpmsg-pru"); MODULE_DESCRIPTION("PRU Remote Processor Messaging Driver"); MODULE_LICENSE("GPL v2");