[RFC 6/6] spi: spidev: Add userspace driver support

Noralf Trønnes noralf at tronnes.org
Wed Jan 4 13:34:42 UTC 2017


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



More information about the dri-devel mailing list