From: "Edward A. James" <eajames@xxxxxxxxxx> Add a miscdevice for the sbefifo to allow userspace access through standard file operations. Signed-off-by: Edward A. James <eajames@xxxxxxxxxx> --- 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 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html