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 at tronnes.org>
---
 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

Reply via email to