[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