From: "Edward A. James" <[email protected]>

Add a miscdevice for the sbefifo to allow userspace access through
standard file operations.

Signed-off-by: Edward A. James <[email protected]>
---
 drivers/fsi/fsi-sbefifo.c | 355 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 355 insertions(+)

diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c
index 6ba190a..fed3739 100644
--- a/drivers/fsi/fsi-sbefifo.c
+++ b/drivers/fsi/fsi-sbefifo.c
@@ -19,6 +19,7 @@
 #include <linux/kernel.h>
 #include <linux/kref.h>
 #include <linux/list.h>
+#include <linux/miscdevice.h>
 #include <linux/module.h>
 #include <linux/poll.h>
 #include <linux/sched.h>
@@ -54,6 +55,7 @@
 struct sbefifo {
        struct timer_list poll_timer;
        struct fsi_device *fsi_dev;
+       struct miscdevice mdev;
        wait_queue_head_t wait;
        struct list_head xfrs;
        struct kref kref;
@@ -256,6 +258,99 @@ static void sbefifo_put(struct sbefifo *sbefifo)
        kref_put(&sbefifo->kref, sbefifo_free);
 }
 
+static struct sbefifo_xfr *sbefifo_enq_xfr(struct sbefifo_client *client)
+{
+       struct sbefifo *sbefifo = client->dev;
+       struct sbefifo_xfr *xfr;
+
+       if (READ_ONCE(sbefifo->rc))
+               return ERR_PTR(sbefifo->rc);
+
+       xfr = kzalloc(sizeof(*xfr), GFP_KERNEL);
+       if (!xfr)
+               return ERR_PTR(-ENOMEM);
+
+       xfr->rbuf = &client->rbuf;
+       xfr->wbuf = &client->wbuf;
+       list_add_tail(&xfr->xfrs, &sbefifo->xfrs);
+       list_add_tail(&xfr->client, &client->xfrs);
+
+       return xfr;
+}
+
+static bool sbefifo_xfr_rsp_pending(struct sbefifo_client *client)
+{
+       struct sbefifo_xfr *xfr = list_first_entry_or_null(&client->xfrs,
+                                                          struct sbefifo_xfr,
+                                                          client);
+
+       if (xfr && test_bit(SBEFIFO_XFR_RESP_PENDING, &xfr->flags))
+               return true;
+
+       return false;
+}
+
+static struct sbefifo_client *sbefifo_new_client(struct sbefifo *sbefifo)
+{
+       struct sbefifo_client *client;
+
+       client = kzalloc(sizeof(*client), GFP_KERNEL);
+       if (!client)
+               return NULL;
+
+       kref_init(&client->kref);
+       client->dev = sbefifo;
+       sbefifo_buf_init(&client->rbuf);
+       sbefifo_buf_init(&client->wbuf);
+       INIT_LIST_HEAD(&client->xfrs);
+
+       sbefifo_get(sbefifo);
+
+       return client;
+}
+
+static void sbefifo_client_release(struct kref *kref)
+{
+       struct sbefifo *sbefifo;
+       struct sbefifo_client *client;
+       struct sbefifo_xfr *xfr, *tmp;
+
+       client = container_of(kref, struct sbefifo_client, kref);
+       sbefifo = client->dev;
+
+       if (!READ_ONCE(sbefifo->rc)) {
+               list_for_each_entry_safe(xfr, tmp, &client->xfrs, client) {
+                       if (test_bit(SBEFIFO_XFR_COMPLETE, &xfr->flags)) {
+                               list_del(&xfr->client);
+                               kfree(xfr);
+                               continue;
+                       }
+
+                       /*
+                        * The client left with pending or running xfrs.
+                        * Cancel them.
+                        */
+                       set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags);
+                       sbefifo_get(sbefifo);
+                       if (mod_timer(&client->dev->poll_timer, jiffies))
+                               sbefifo_put(sbefifo);
+               }
+       }
+
+       sbefifo_put(sbefifo);
+       kfree(client);
+}
+
+static void sbefifo_get_client(struct sbefifo_client *client)
+{
+       kref_get(&client->kref);
+}
+
+static void sbefifo_put_client(struct sbefifo_client *client)
+{
+       kref_put(&client->kref, sbefifo_client_release);
+}
+
 static struct sbefifo_xfr *sbefifo_next_xfr(struct sbefifo *sbefifo)
 {
        struct sbefifo_xfr *xfr, *tmp;
@@ -430,6 +525,252 @@ static void sbefifo_poll_timer(unsigned long data)
        spin_unlock_irqrestore(&sbefifo->lock, flags);
 }
 
+static int sbefifo_open(struct inode *inode, struct file *file)
+{
+       struct sbefifo *sbefifo = container_of(file->private_data,
+                                              struct sbefifo, mdev);
+       struct sbefifo_client *client;
+       int ret;
+
+       ret = READ_ONCE(sbefifo->rc);
+       if (ret)
+               return ret;
+
+       client = sbefifo_new_client(sbefifo);
+       if (!client)
+               return -ENOMEM;
+
+       file->private_data = client;
+
+       return 0;
+}
+
+static unsigned int sbefifo_poll(struct file *file, poll_table *wait)
+{
+       struct sbefifo_client *client = file->private_data;
+       struct sbefifo *sbefifo = client->dev;
+       unsigned int mask = 0;
+
+       poll_wait(file, &sbefifo->wait, wait);
+
+       if (READ_ONCE(sbefifo->rc))
+               mask |= POLLERR;
+
+       if (sbefifo_buf_nbreadable(&client->rbuf))
+               mask |= POLLIN;
+
+       if (sbefifo_buf_nbwriteable(&client->wbuf))
+               mask |= POLLOUT;
+
+       return mask;
+}
+
+static bool sbefifo_read_ready(struct sbefifo *sbefifo,
+                              struct sbefifo_client *client, size_t *n,
+                              size_t *ret)
+{
+       struct sbefifo_xfr *xfr = list_first_entry_or_null(&client->xfrs,
+                                                          struct sbefifo_xfr,
+                                                          client);
+
+       *n = sbefifo_buf_nbreadable(&client->rbuf);
+       *ret = READ_ONCE(sbefifo->rc);
+
+       return *ret || *n ||
+               (xfr && test_bit(SBEFIFO_XFR_COMPLETE, &xfr->flags));
+}
+
+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;
+       size_t n;
+       ssize_t ret = 0;
+
+       if ((len >> 2) << 2 != len)
+               return -EINVAL;
+
+       if ((file->f_flags & O_NONBLOCK) && !sbefifo_xfr_rsp_pending(client))
+               return -EAGAIN;
+
+       sbefifo_get_client(client);
+       if (wait_event_interruptible(sbefifo->wait,
+                                    sbefifo_read_ready(sbefifo, client, &n,
+                                                       &ret))) {
+               ret = -ERESTARTSYS;
+               goto out;
+       }
+
+       if (ret) {
+               INIT_LIST_HEAD(&client->xfrs);
+               goto out;
+       }
+
+       n = min_t(size_t, n, len);
+
+       if (copy_to_user(buf, READ_ONCE(client->rbuf.rpos), n)) {
+               ret = -EFAULT;
+               goto out;
+       }
+
+       if (sbefifo_buf_readnb(&client->rbuf, n)) {
+               xfr = list_first_entry_or_null(&client->xfrs,
+                                              struct sbefifo_xfr, client);
+               if (!xfr) {
+                       /* should be impossible to not have an xfr here */
+                       WARN_ONCE(1, "no xfr in queue");
+                       ret = -EPROTO;
+                       goto out;
+               }
+
+               if (!test_bit(SBEFIFO_XFR_COMPLETE, &xfr->flags)) {
+                       /* Fill the read buffer back up. */
+                       sbefifo_get(sbefifo);
+                       if (mod_timer(&client->dev->poll_timer, jiffies))
+                               sbefifo_put(sbefifo);
+               } else {
+                       list_del(&xfr->client);
+                       kfree(xfr);
+                       wake_up_interruptible(&sbefifo->wait);
+               }
+       }
+
+       ret = n;
+
+out:
+       sbefifo_put_client(client);
+       return ret;
+}
+
+static bool sbefifo_write_ready(struct sbefifo *sbefifo,
+                               struct sbefifo_xfr *xfr,
+                               struct sbefifo_client *client, size_t *n)
+{
+       struct sbefifo_xfr *next = list_first_entry_or_null(&client->xfrs,
+                                                           struct sbefifo_xfr,
+                                                           client);
+
+       *n = sbefifo_buf_nbwriteable(&client->wbuf);
+       return READ_ONCE(sbefifo->rc) || (next == xfr && *n);
+}
+
+static ssize_t sbefifo_write(struct file *file, const char __user *buf,
+                            size_t len, loff_t *offset)
+{
+       unsigned long flags;
+       struct sbefifo_client *client = file->private_data;
+       struct sbefifo *sbefifo = client->dev;
+       struct sbefifo_xfr *xfr;
+       ssize_t ret = 0;
+       size_t n;
+
+       if ((len >> 2) << 2 != len)
+               return -EINVAL;
+
+       if (!len)
+               return 0;
+
+       sbefifo_get_client(client);
+       n = sbefifo_buf_nbwriteable(&client->wbuf);
+
+       spin_lock_irqsave(&sbefifo->lock, flags);
+       xfr = sbefifo_next_xfr(sbefifo);        /* next xfr to be executed */
+
+       if ((file->f_flags & O_NONBLOCK) && xfr && n < len) {
+               spin_unlock_irqrestore(&sbefifo->lock, flags);
+               ret = -EAGAIN;
+               goto out;
+       }
+
+       xfr = sbefifo_enq_xfr(client);          /* this xfr queued up */
+       if (IS_ERR(xfr)) {
+               spin_unlock_irqrestore(&sbefifo->lock, flags);
+               ret = PTR_ERR(xfr);
+               goto out;
+       }
+
+       spin_unlock_irqrestore(&sbefifo->lock, flags);
+
+       /*
+        * Partial writes are not really allowed in that EOT is sent exactly
+        * once per write.
+        */
+       while (len) {
+               if (wait_event_interruptible(sbefifo->wait,
+                                            sbefifo_write_ready(sbefifo, xfr,
+                                                                client,
+                                                                &n))) {
+                       set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags);
+                       sbefifo_get(sbefifo);
+                       if (mod_timer(&sbefifo->poll_timer, jiffies))
+                               sbefifo_put(sbefifo);
+
+                       ret = -ERESTARTSYS;
+                       goto out;
+               }
+
+               if (sbefifo->rc) {
+                       INIT_LIST_HEAD(&client->xfrs);
+                       ret = sbefifo->rc;
+                       goto out;
+               }
+
+               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);
+                       ret = -EFAULT;
+                       goto out;
+               }
+
+               buf += n;
+
+               sbefifo_buf_wrotenb(&client->wbuf, n);
+               len -= n;
+               ret += n;
+
+               /*
+                * Set this before starting timer to avoid race condition on
+                * this flag with the timer function writer.
+                */
+               if (!len)
+                       set_bit(SBEFIFO_XFR_WRITE_DONE, &xfr->flags);
+
+               /* Drain the write buffer. */
+               sbefifo_get(sbefifo);
+               if (mod_timer(&client->dev->poll_timer, jiffies))
+                       sbefifo_put(sbefifo);
+       }
+
+out:
+       sbefifo_put_client(client);
+       return ret;
+}
+
+static int sbefifo_release(struct inode *inode, struct file *file)
+{
+       struct sbefifo_client *client = file->private_data;
+       struct sbefifo *sbefifo = client->dev;
+
+       sbefifo_put_client(client);
+
+       return READ_ONCE(sbefifo->rc);
+}
+
+static const struct file_operations sbefifo_fops = {
+       .owner          = THIS_MODULE,
+       .open           = sbefifo_open,
+       .read           = sbefifo_read,
+       .write          = sbefifo_write,
+       .poll           = sbefifo_poll,
+       .release        = sbefifo_release,
+};
+
 static int sbefifo_request_reset(struct sbefifo *sbefifo)
 {
        int ret;
@@ -504,6 +845,18 @@ static int sbefifo_probe(struct device *dev)
        setup_timer(&sbefifo->poll_timer, sbefifo_poll_timer,
                    (unsigned long)sbefifo);
 
+       sbefifo->mdev.minor = MISC_DYNAMIC_MINOR;
+       sbefifo->mdev.fops = &sbefifo_fops;
+       sbefifo->mdev.name = sbefifo->name;
+       sbefifo->mdev.parent = dev;
+       ret = misc_register(&sbefifo->mdev);
+       if (ret) {
+               dev_err(dev, "failed to register miscdevice: %d\n", ret);
+               ida_simple_remove(&sbefifo_ida, sbefifo->idx);
+               sbefifo_put(sbefifo);
+               return ret;
+       }
+
        dev_set_drvdata(dev, sbefifo);
 
        return 0;
@@ -529,6 +882,8 @@ static int sbefifo_remove(struct device *dev)
 
        wake_up_all(&sbefifo->wait);
 
+       misc_deregister(&sbefifo->mdev);
+
        ida_simple_remove(&sbefifo_ida, sbefifo->idx);
 
        if (del_timer_sync(&sbefifo->poll_timer))
-- 
1.8.3.1

Reply via email to