All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Noralf Trønnes" <noralf@tronnes.org>
To: dri-devel@lists.freedesktop.org
Subject: [RFC 6/6] spi: spidev: Add userspace driver support
Date: Wed,  4 Jan 2017 14:34:42 +0100	[thread overview]
Message-ID: <20170104133442.4534-7-noralf@tronnes.org> (raw)
In-Reply-To: <20170104133442.4534-1-noralf@tronnes.org>

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@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

_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

  parent reply	other threads:[~2017-01-04 13:45 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-01-04 13:34 [RFC 0/6] drm: Add support for userspace drivers Noralf Trønnes
2017-01-04 13:34 ` [RFC 1/6] drm/modes: Export drm_mode_convert_umode() Noralf Trønnes
2017-01-04 13:34 ` [RFC 2/6] drm: Add support for userspace drivers Noralf Trønnes
2017-01-04 13:34 ` [RFC 3/6] dma-buf: Support generic userspace allocations Noralf Trønnes
2017-01-04 15:08   ` Daniel Vetter
2017-01-04 13:34 ` [RFC 4/6] spi: Let clients do scatter/gather transfers Noralf Trønnes
2017-01-04 13:34 ` [RFC 5/6] spi: spidev: Add dma-buf support Noralf Trønnes
2017-01-04 13:34 ` Noralf Trønnes [this message]
2017-01-04 15:06 ` [RFC 0/6] drm: Add support for userspace drivers Daniel Vetter
2017-01-04 15:15   ` Martin Peres

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170104133442.4534-7-noralf@tronnes.org \
    --to=noralf@tronnes.org \
    --cc=dri-devel@lists.freedesktop.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.