From patchwork Wed Jan 4 13:34:42 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Noralf_Tr=C3=B8nnes?= X-Patchwork-Id: 9496883 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id ECCB3606DD for ; Wed, 4 Jan 2017 13:45:10 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id D855727F89 for ; Wed, 4 Jan 2017 13:45:10 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id CD6DC27F8D; Wed, 4 Jan 2017 13:45:10 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.2 required=2.0 tests=BAYES_00, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 1F5D327F89 for ; Wed, 4 Jan 2017 13:45:10 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 14EE26E70E; Wed, 4 Jan 2017 13:45:04 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org X-Greylist: delayed 586 seconds by postgrey-1.35 at gabe; Wed, 04 Jan 2017 13:45:01 UTC Received: from smtp.domeneshop.no (smtp.domeneshop.no [IPv6:2a01:5b40:0:3005::1]) by gabe.freedesktop.org (Postfix) with ESMTPS id E02666E70E for ; Wed, 4 Jan 2017 13:45:01 +0000 (UTC) Received: from 211.81-166-168.customer.lyse.net ([81.166.168.211]:42458 helo=localhost.localdomain) by smtp.domeneshop.no with esmtpsa (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1cOlik-00006b-2R; Wed, 04 Jan 2017 14:35:14 +0100 From: =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= 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 Message-Id: <20170104133442.4534-7-noralf@tronnes.org> X-Mailer: git-send-email 2.10.2 In-Reply-To: <20170104133442.4534-1-noralf@tronnes.org> References: <20170104133442.4534-1-noralf@tronnes.org> MIME-Version: 1.0 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Virus-Scanned: ClamAV using ClamSMTP 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 --- drivers/spi/spidev.c | 289 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 283 insertions(+), 6 deletions(-) -- 2.10.2 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 #include #include +#include #include #include #include #include #include +#include #include #include @@ -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);