Add support for spi userspace drivers backed by it's own spi_driver. Userspace driver usage: Open /dev/spidev Write a string containing driver name and optional DT compatible. This registers a spidev spi_driver. Read/poll to receive notice when devices have been bound/probed. The driver now uses /dev/spidevN.N as normal to access the device. When the file is closed, the spi_driver is unregistered. Signed-off-by: Noralf Trønnes <noralf@xxxxxxxxxxx> --- drivers/spi/spidev.c | 289 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 283 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c index 35e6377..b8f3559 100644 --- a/drivers/spi/spidev.c +++ b/drivers/spi/spidev.c @@ -26,11 +26,13 @@ #include <linux/err.h> #include <linux/list.h> #include <linux/errno.h> +#include <linux/miscdevice.h> #include <linux/mutex.h> #include <linux/slab.h> #include <linux/compat.h> #include <linux/of.h> #include <linux/of_device.h> +#include <linux/poll.h> #include <linux/acpi.h> #include <linux/spi/spi.h> @@ -99,6 +101,20 @@ struct spidev_dmabuf { unsigned int nents; }; +struct spidev_drv { + struct spi_driver spidrv; + struct mutex event_lock; + struct list_head events; + wait_queue_head_t waitq; + struct completion completion; +}; + +struct spidev_drv_event { + struct list_head list; + u8 bus_num; + u8 chip_select; +}; + static LIST_HEAD(device_list); static DEFINE_MUTEX(device_list_lock); @@ -994,6 +1010,254 @@ static struct spi_driver spidev_spi_driver = { /*-------------------------------------------------------------------------*/ +static int spidev_drv_probe(struct spi_device *spi) +{ + struct spi_driver *spidrv = to_spi_driver(spi->dev.driver); + struct spidev_drv *sdrv = container_of(spidrv, struct spidev_drv, + spidrv); + struct spidev_drv_event *new_device; + int ret; + + ret = spidev_probe(spi); + if (ret) + return ret; + + ret = mutex_lock_interruptible(&sdrv->event_lock); + if (ret) + goto out; + + new_device = kzalloc(sizeof(*new_device), GFP_KERNEL); + if (new_device) { + new_device->bus_num = spi->master->bus_num; + new_device->chip_select = spi->chip_select; + list_add_tail(&new_device->list, &sdrv->events); + } else { + ret = -ENOMEM; + } + + mutex_unlock(&sdrv->event_lock); + + wake_up_interruptible(&sdrv->waitq); +out: + if (ret) + dev_err(&spi->dev, "Failed to add event %d\n", ret); + + return 0; +} + +static int spidev_drv_remove(struct spi_device *spi) +{ + int ret; + + ret = spidev_remove(spi); + if (ret) + return ret; + + return 0; +} + +static ssize_t spidev_drv_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + char *str, *token, *drvname, *compatible; + struct of_device_id *of_ids = NULL; + struct spidev_drv *sdrv = NULL; + struct spi_driver *spidrv; + unsigned int i; + int status; + + if (file->private_data) + return -EBUSY; + + if (!count) + return 0; + + if (count == 1) + return -EINVAL; + + str = strndup_user(buffer, count); + if (IS_ERR(str)) + return PTR_ERR(str); + + for (i = 0, token = str; *token; token++) + if (*token == '\n') + i++; + + if (i > 1) { + status = -EINVAL; + goto err_free; + } + + drvname = str; + if (i) { + strsep(&str, "\n"); + compatible = str; + } else { + compatible = NULL; + } + + if (compatible && strlen(compatible) > 127) { + status = -EINVAL; + goto err_free; + } + +pr_info("spidev: Add driver '%s', compatible='%s'\n", drvname, compatible); + + sdrv = kzalloc(sizeof(*sdrv), GFP_KERNEL); + if (!sdrv) { + status = -ENOMEM; + goto err_free; + } + + INIT_LIST_HEAD(&sdrv->events); + mutex_init(&sdrv->event_lock); + init_waitqueue_head(&sdrv->waitq); + + spidrv = &sdrv->spidrv; + spidrv->driver.name = drvname; + spidrv->probe = spidev_drv_probe; + spidrv->remove = spidev_drv_remove; + + if (compatible) { + /* the second blank entry is the sentinel */ + of_ids = kcalloc(2, sizeof(*of_ids), GFP_KERNEL); + if (!of_ids) { + status = -ENOMEM; + goto err_free; + } + strcpy(of_ids[0].compatible, compatible); + spidrv->driver.of_match_table = of_ids; + } + + status = spi_register_driver(spidrv); + if (status < 0) + goto err_free; + + file->private_data = sdrv; + + return count; + +err_free: + kfree(sdrv); + kfree(of_ids); + kfree(str); + + return status; +} + +static ssize_t spidev_drv_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct spidev_drv *sdrv = file->private_data; + struct spidev_drv_event *new_device; + char str[32]; + ssize_t ret; + + if (!sdrv) + return -ENODEV; + + if (!count) + return 0; + + do { + ret = mutex_lock_interruptible(&sdrv->event_lock); + if (ret) + return ret; + + if (list_empty(&sdrv->events)) { + if (file->f_flags & O_NONBLOCK) + ret = -EAGAIN; + } else { + new_device = list_first_entry(&sdrv->events, + struct spidev_drv_event, + list); + ret = scnprintf(str, sizeof(str) - 1, "spidev%u.%u", + new_device->bus_num, + new_device->chip_select); + if (ret < 0) + goto unlock; + + str[ret++] = '\0'; + + if (ret > count) { + ret = -EINVAL; + goto unlock; + } else if (copy_to_user(buffer, str, ret)) { + ret = -EFAULT; + goto unlock; + } + + list_del(&new_device->list); + kfree(new_device); + } +unlock: + mutex_unlock(&sdrv->event_lock); + + if (ret) + break; + + if (!(file->f_flags & O_NONBLOCK)) + ret = wait_event_interruptible(sdrv->waitq, + !list_empty(&sdrv->events)); + } while (ret == 0); + + return ret; +} + +static unsigned int spidev_drv_poll(struct file *file, poll_table *wait) +{ + struct spidev_drv *sdrv = file->private_data; + + poll_wait(file, &sdrv->waitq, wait); + + if (!list_empty(&sdrv->events)) + return POLLIN | POLLRDNORM; + + return 0; +} + +static int spidev_drv_open(struct inode *inode, struct file *file) +{ + file->private_data = NULL; + nonseekable_open(inode, file); + + return 0; +} + +static int spidev_drv_release(struct inode *inode, struct file *file) +{ + struct spidev_drv *sdrv = file->private_data; + struct spidev_drv_event *entry, *tmp; + + if (sdrv) { + spi_unregister_driver(&sdrv->spidrv); + list_for_each_entry_safe(entry, tmp, &sdrv->events, list) + kfree(entry); + kfree(sdrv->spidrv.driver.name); + kfree(sdrv); + } + + return 0; +} + +static const struct file_operations spidev_drv_fops = { + .owner = THIS_MODULE, + .open = spidev_drv_open, + .release = spidev_drv_release, + .read = spidev_drv_read, + .write = spidev_drv_write, + .poll = spidev_drv_poll, + .llseek = no_llseek, +}; + +static struct miscdevice spidev_misc = { + .fops = &spidev_drv_fops, + .minor = MISC_DYNAMIC_MINOR, + .name = "spidev", +}; + +/*-------------------------------------------------------------------------*/ + static int __init spidev_init(void) { int status; @@ -1009,21 +1273,34 @@ static int __init spidev_init(void) spidev_class = class_create(THIS_MODULE, "spidev"); if (IS_ERR(spidev_class)) { - unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); - return PTR_ERR(spidev_class); + status = PTR_ERR(spidev_class); + goto err_unreg_chardev; } status = spi_register_driver(&spidev_spi_driver); - if (status < 0) { - class_destroy(spidev_class); - unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); - } + if (status < 0) + goto err_destroy_class; + + status = misc_register(&spidev_misc); + if (status < 0) + goto err_unreg_driver; + + return 0; + +err_unreg_driver: + spi_unregister_driver(&spidev_spi_driver); +err_destroy_class: + class_destroy(spidev_class); +err_unreg_chardev: + unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); + return status; } module_init(spidev_init); static void __exit spidev_exit(void) { + misc_deregister(&spidev_misc); spi_unregister_driver(&spidev_spi_driver); class_destroy(spidev_class); unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); -- 2.10.2 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel