| From patchwork Fri May 12 19:38:18 2017 |
| Content-Type: text/plain; charset="utf-8" |
| MIME-Version: 1.0 |
| Content-Transfer-Encoding: 7bit |
| Subject: [linux,dev-4.10,1/3] drivers: fsi: sbefifo: Add in-kernel API |
| From: eajames@linux.vnet.ibm.com |
| X-Patchwork-Id: 761836 |
| Message-Id: <1494617900-32369-2-git-send-email-eajames@linux.vnet.ibm.com> |
| To: openbmc@lists.ozlabs.org |
| Cc: "Edward A. James" <eajames@us.ibm.com>, bradleyb@fuzziesquirrel.com, |
| cbostic@linux.vnet.ibm.com |
| Date: Fri, 12 May 2017 14:38:18 -0500 |
| |
| From: "Edward A. James" <eajames@us.ibm.com> |
| |
| Add exported functions to the SBEFIFO driver to open/write/read/close |
| from within the kernel. |
| |
| Signed-off-by: Edward A. James <eajames@us.ibm.com> |
| --- |
| drivers/fsi/fsi-sbefifo.c | 161 +++++++++++++++++++++++++++++++++++--------- |
| include/linux/fsi-sbefifo.h | 30 +++++++++ |
| 2 files changed, 161 insertions(+), 30 deletions(-) |
| create mode 100644 include/linux/fsi-sbefifo.h |
| |
| diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c |
| index b49aec2..56e6331 100644 |
| --- a/drivers/fsi/fsi-sbefifo.c |
| +++ b/drivers/fsi/fsi-sbefifo.c |
| @@ -15,9 +15,12 @@ |
| #include <linux/errno.h> |
| #include <linux/idr.h> |
| #include <linux/fsi.h> |
| +#include <linux/fsi-sbefifo.h> |
| #include <linux/list.h> |
| #include <linux/miscdevice.h> |
| #include <linux/module.h> |
| +#include <linux/of.h> |
| +#include <linux/of_platform.h> |
| #include <linux/poll.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| @@ -82,6 +85,7 @@ struct sbefifo_client { |
| struct list_head xfrs; |
| struct sbefifo *dev; |
| struct kref kref; |
| + unsigned long f_flags; |
| }; |
| |
| static struct list_head sbefifo_fifos; |
| @@ -506,6 +510,7 @@ static int sbefifo_open(struct inode *inode, struct file *file) |
| return -ENOMEM; |
| |
| file->private_data = client; |
| + client->f_flags = file->f_flags; |
| |
| return 0; |
| } |
| @@ -530,24 +535,18 @@ static unsigned int sbefifo_poll(struct file *file, poll_table *wait) |
| return mask; |
| } |
| |
| -static ssize_t sbefifo_read(struct file *file, char __user *buf, |
| - size_t len, loff_t *offset) |
| +static ssize_t sbefifo_read_common(struct sbefifo_client *client, |
| + char __user *ubuf, char *kbuf, size_t len) |
| { |
| - struct sbefifo_client *client = file->private_data; |
| struct sbefifo *sbefifo = client->dev; |
| struct sbefifo_xfr *xfr; |
| - ssize_t ret = 0; |
| size_t n; |
| - |
| - WARN_ON(*offset); |
| - |
| - if (!access_ok(VERIFY_WRITE, buf, len)) |
| - return -EFAULT; |
| + ssize_t ret = 0; |
| |
| if ((len >> 2) << 2 != len) |
| return -EINVAL; |
| |
| - if ((file->f_flags & O_NONBLOCK) && !sbefifo_xfr_rsp_pending(client)) |
| + if ((client->f_flags & O_NONBLOCK) && !sbefifo_xfr_rsp_pending(client)) |
| return -EAGAIN; |
| |
| sbefifo_get_client(client); |
| @@ -566,10 +565,13 @@ static ssize_t sbefifo_read(struct file *file, char __user *buf, |
| |
| n = min_t(size_t, n, len); |
| |
| - if (copy_to_user(buf, READ_ONCE(client->rbuf.rpos), n)) { |
| - sbefifo_put_client(client); |
| - return -EFAULT; |
| - } |
| + if (ubuf) { |
| + if (copy_to_user(ubuf, READ_ONCE(client->rbuf.rpos), n)) { |
| + sbefifo_put_client(client); |
| + return -EFAULT; |
| + } |
| + } else |
| + memcpy(kbuf, READ_ONCE(client->rbuf.rpos), n); |
| |
| if (sbefifo_buf_readnb(&client->rbuf, n)) { |
| xfr = sbefifo_client_next_xfr(client); |
| @@ -592,20 +594,28 @@ static ssize_t sbefifo_read(struct file *file, char __user *buf, |
| return n; |
| } |
| |
| -static ssize_t sbefifo_write(struct file *file, const char __user *buf, |
| +static ssize_t sbefifo_read(struct file *file, char __user *buf, |
| size_t len, loff_t *offset) |
| { |
| struct sbefifo_client *client = file->private_data; |
| - struct sbefifo *sbefifo = client->dev; |
| - struct sbefifo_xfr *xfr; |
| - ssize_t ret = 0; |
| - size_t n; |
| |
| WARN_ON(*offset); |
| |
| - if (!access_ok(VERIFY_READ, buf, len)) |
| + if (!access_ok(VERIFY_WRITE, buf, len)) |
| return -EFAULT; |
| |
| + return sbefifo_read_common(client, buf, NULL, len); |
| +} |
| + |
| +static ssize_t sbefifo_write_common(struct sbefifo_client *client, |
| + const char __user *ubuf, const char *kbuf, |
| + size_t len) |
| +{ |
| + struct sbefifo *sbefifo = client->dev; |
| + struct sbefifo_xfr *xfr; |
| + ssize_t ret = 0; |
| + size_t n; |
| + |
| if ((len >> 2) << 2 != len) |
| return -EINVAL; |
| |
| @@ -617,7 +627,7 @@ static ssize_t sbefifo_write(struct file *file, const char __user *buf, |
| spin_lock_irq(&sbefifo->lock); |
| xfr = sbefifo_next_xfr(sbefifo); |
| |
| - if ((file->f_flags & O_NONBLOCK) && xfr && n < len) { |
| + if ((client->f_flags & O_NONBLOCK) && xfr && n < len) { |
| spin_unlock_irq(&sbefifo->lock); |
| return -EAGAIN; |
| } |
| @@ -657,18 +667,25 @@ static ssize_t sbefifo_write(struct file *file, const char __user *buf, |
| |
| n = min_t(size_t, n, len); |
| |
| - if (copy_from_user(READ_ONCE(client->wbuf.wpos), buf, n)) { |
| - set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags); |
| - sbefifo_get(sbefifo); |
| - if (mod_timer(&sbefifo->poll_timer, jiffies)) |
| - sbefifo_put(sbefifo); |
| - sbefifo_put_client(client); |
| - return -EFAULT; |
| + if (ubuf) { |
| + if (copy_from_user(READ_ONCE(client->wbuf.wpos), ubuf, |
| + n)) { |
| + set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags); |
| + sbefifo_get(sbefifo); |
| + if (mod_timer(&sbefifo->poll_timer, jiffies)) |
| + sbefifo_put(sbefifo); |
| + sbefifo_put_client(client); |
| + return -EFAULT; |
| + } |
| + |
| + ubuf += n; |
| + } else { |
| + memcpy(READ_ONCE(client->wbuf.wpos), kbuf, n); |
| + kbuf += n; |
| } |
| |
| sbefifo_buf_wrotenb(&client->wbuf, n); |
| len -= n; |
| - buf += n; |
| ret += n; |
| |
| /* |
| @@ -685,6 +702,19 @@ static ssize_t sbefifo_write(struct file *file, const char __user *buf, |
| return ret; |
| } |
| |
| +static ssize_t sbefifo_write(struct file *file, const char __user *buf, |
| + size_t len, loff_t *offset) |
| +{ |
| + struct sbefifo_client *client = file->private_data; |
| + |
| + WARN_ON(*offset); |
| + |
| + if (!access_ok(VERIFY_READ, buf, len)) |
| + return -EFAULT; |
| + |
| + return sbefifo_write_common(client, buf, NULL, len); |
| +} |
| + |
| static int sbefifo_release(struct inode *inode, struct file *file) |
| { |
| struct sbefifo_client *client = file->private_data; |
| @@ -704,12 +734,68 @@ static int sbefifo_release(struct inode *inode, struct file *file) |
| .release = sbefifo_release, |
| }; |
| |
| +struct sbefifo_client *sbefifo_drv_open(struct device *dev, |
| + unsigned long flags) |
| +{ |
| + struct sbefifo_client *client = NULL; |
| + struct sbefifo *sbefifo; |
| + struct fsi_device *fsi_dev = to_fsi_dev(dev); |
| + |
| + list_for_each_entry(sbefifo, &sbefifo_fifos, link) { |
| + if (sbefifo->fsi_dev != fsi_dev) |
| + continue; |
| + |
| + client = sbefifo_new_client(sbefifo); |
| + if (client) |
| + client->f_flags = flags; |
| + } |
| + |
| + return client; |
| +} |
| +EXPORT_SYMBOL_GPL(sbefifo_drv_open); |
| + |
| +int sbefifo_drv_read(struct sbefifo_client *client, char *buf, size_t len) |
| +{ |
| + return sbefifo_read_common(client, NULL, buf, len); |
| +} |
| +EXPORT_SYMBOL_GPL(sbefifo_drv_read); |
| + |
| +int sbefifo_drv_write(struct sbefifo_client *client, const char *buf, |
| + size_t len) |
| +{ |
| + return sbefifo_write_common(client, NULL, buf, len); |
| +} |
| +EXPORT_SYMBOL_GPL(sbefifo_drv_write); |
| + |
| +void sbefifo_drv_release(struct sbefifo_client *client) |
| +{ |
| + if (!client) |
| + return; |
| + |
| + sbefifo_put_client(client); |
| +} |
| +EXPORT_SYMBOL_GPL(sbefifo_drv_release); |
| + |
| +static int sbefifo_unregister_child(struct device *dev, void *data) |
| +{ |
| + struct platform_device *child = to_platform_device(dev); |
| + |
| + of_device_unregister(child); |
| + if (dev->of_node) |
| + of_node_clear_flag(dev->of_node, OF_POPULATED); |
| + |
| + return 0; |
| +} |
| + |
| static int sbefifo_probe(struct device *dev) |
| { |
| struct fsi_device *fsi_dev = to_fsi_dev(dev); |
| struct sbefifo *sbefifo; |
| + struct device_node *np; |
| + struct platform_device *child; |
| + char child_name[32]; |
| u32 sts; |
| - int ret; |
| + int ret, child_idx = 0; |
| |
| dev_info(dev, "Found sbefifo device\n"); |
| sbefifo = kzalloc(sizeof(*sbefifo), GFP_KERNEL); |
| @@ -750,6 +836,18 @@ static int sbefifo_probe(struct device *dev) |
| init_waitqueue_head(&sbefifo->wait); |
| INIT_LIST_HEAD(&sbefifo->xfrs); |
| |
| + if (dev->of_node) { |
| + /* create platform devs for dts child nodes (occ, etc) */ |
| + for_each_child_of_node(dev->of_node, np) { |
| + snprintf(child_name, sizeof(child_name), "%s-dev%d", |
| + sbefifo->name, child_idx++); |
| + child = of_platform_device_create(np, child_name, dev); |
| + if (!child) |
| + dev_warn(&sbefifo->fsi_dev->dev, |
| + "failed to create child node dev\n"); |
| + } |
| + } |
| + |
| /* This bit of silicon doesn't offer any interrupts... */ |
| setup_timer(&sbefifo->poll_timer, sbefifo_poll_timer, |
| (unsigned long)sbefifo); |
| @@ -767,6 +865,9 @@ static int sbefifo_remove(struct device *dev) |
| list_for_each_entry_safe(sbefifo, sbefifo_tmp, &sbefifo_fifos, link) { |
| if (sbefifo->fsi_dev != fsi_dev) |
| continue; |
| + |
| + device_for_each_child(dev, NULL, sbefifo_unregister_child); |
| + |
| misc_deregister(&sbefifo->mdev); |
| list_del(&sbefifo->link); |
| ida_simple_remove(&sbefifo_ida, sbefifo->idx); |
| diff --git a/include/linux/fsi-sbefifo.h b/include/linux/fsi-sbefifo.h |
| new file mode 100644 |
| index 0000000..1b46c63 |
| --- /dev/null |
| +++ b/include/linux/fsi-sbefifo.h |
| @@ -0,0 +1,30 @@ |
| +/* |
| + * SBEFIFO FSI Client device driver |
| + * |
| + * Copyright (C) IBM Corporation 2017 |
| + * |
| + * This program is free software; you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License version 2 as |
| + * published by the Free Software Foundation. |
| + * |
| + * This program is distributed in the hope that it will be useful, |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + * MERGCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| + * GNU General Public License for more details. |
| + */ |
| + |
| +#ifndef __FSI_SBEFIFO_H__ |
| +#define __FSI_SBEFIFO_H__ |
| + |
| +struct device; |
| +struct sbefifo_client; |
| + |
| +extern struct sbefifo_client *sbefifo_drv_open(struct device *dev, |
| + unsigned long flags); |
| +extern int sbefifo_drv_read(struct sbefifo_client *client, char *buf, |
| + size_t len); |
| +extern int sbefifo_drv_write(struct sbefifo_client *client, const char *buf, |
| + size_t len); |
| +extern void sbefifo_drv_release(struct sbefifo_client *client); |
| + |
| +#endif /* __FSI_SBEFIFO_H__ */ |