[PATCH linux dev-4.10] drivers: fsi: Add FSI SBEFIFO driver

Joel Stanley joel at jms.id.au
Thu Jun 1 14:57:11 AEST 2017


On Thu, May 11, 2017 at 12:22 PM, Eddie James
<eajames at linux.vnet.ibm.com> wrote:
> From: "Edward A. James" <eajames at us.ibm.com>
>
> IBM POWER9 processors contain some embedded hardware and software bits
> collectively referred to as the self boot engine (SBE).  One role of
> the SBE is to act as a proxy that provides access to the registers of
> the POWER chip from other (embedded) systems.
>
> The POWER9 chip contains a hardware frontend for communicating with
> the SBE from remote systems called the SBEFIFO.  The SBEFIFO logic
> is contained within an FSI CFAM (see Documentation/fsi) and as such
> the driver implements an FSI bus device.
>
> The SBE expects to communicate using a defined wire protocol; however,
> the driver knows nothing of the protocol and only provides raw access
> to the fifo device to userspace applications wishing to communicate with
> the SBE using the wire protocol.
>
> The SBEFIFO consists of two hardware fifos.  The upstream fifo is used
> by the driver to transfer data to the SBE on the POWER chip, from the
> system hosting the driver.  The downstream fifo is used by the driver to
> transfer data from the SBE on the power chip to the system hosting the
> driver.
>
> Signed-off-by: Edward A. James <eajames at us.ibm.com>
> Signed-off-by: Brad Bishop <bradleyb at fuzziesquirrel.com>

Applied to dev-4.10.

Cheers,

Joel

> ---
>  drivers/fsi/Kconfig       |   5 +
>  drivers/fsi/Makefile      |   1 +
>  drivers/fsi/fsi-sbefifo.c | 824 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 830 insertions(+)
>  create mode 100644 drivers/fsi/fsi-sbefifo.c
>
> diff --git a/drivers/fsi/Kconfig b/drivers/fsi/Kconfig
> index fc031ac..39527fa 100644
> --- a/drivers/fsi/Kconfig
> +++ b/drivers/fsi/Kconfig
> @@ -31,6 +31,11 @@ config FSI_SCOM
>         ---help---
>         This option enables an FSI based SCOM device driver.
>
> +config FSI_SBEFIFO
> +       tristate "SBEFIFO FSI client device driver"
> +       ---help---
> +       This option enables an FSI based SBEFIFO device driver.
> +
>  endif
>
>  endmenu
> diff --git a/drivers/fsi/Makefile b/drivers/fsi/Makefile
> index 65eb99d..851182e 100644
> --- a/drivers/fsi/Makefile
> +++ b/drivers/fsi/Makefile
> @@ -3,3 +3,4 @@ obj-$(CONFIG_FSI) += fsi-core.o
>  obj-$(CONFIG_FSI_MASTER_HUB) += fsi-master-hub.o
>  obj-$(CONFIG_FSI_MASTER_GPIO) += fsi-master-gpio.o
>  obj-$(CONFIG_FSI_SCOM) += fsi-scom.o
> +obj-$(CONFIG_FSI_SBEFIFO) += fsi-sbefifo.o
> diff --git a/drivers/fsi/fsi-sbefifo.c b/drivers/fsi/fsi-sbefifo.c
> new file mode 100644
> index 0000000..b49aec2
> --- /dev/null
> +++ b/drivers/fsi/fsi-sbefifo.c
> @@ -0,0 +1,824 @@
> +/*
> + * 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.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/idr.h>
> +#include <linux/fsi.h>
> +#include <linux/list.h>
> +#include <linux/miscdevice.h>
> +#include <linux/module.h>
> +#include <linux/poll.h>
> +#include <linux/sched.h>
> +#include <linux/slab.h>
> +#include <linux/timer.h>
> +#include <linux/uaccess.h>
> +
> +/*
> + * The SBEFIFO is a pipe-like FSI device for communicating with
> + * the self boot engine on POWER processors.
> + */
> +
> +#define DEVICE_NAME            "sbefifo"
> +#define FSI_ENGID_SBE          0x22
> +#define SBEFIFO_BUF_CNT                32
> +
> +#define SBEFIFO_UP             0x00    /* Up register offset */
> +#define SBEFIFO_DWN            0x40    /* Down register offset */
> +
> +#define SBEFIFO_STS            0x04
> +#define   SBEFIFO_EMPTY                        BIT(20)
> +#define SBEFIFO_EOT_RAISE      0x08
> +#define   SBEFIFO_EOT_MAGIC            0xffffffff
> +#define SBEFIFO_EOT_ACK                0x14
> +
> +struct sbefifo {
> +       struct timer_list poll_timer;
> +       struct fsi_device *fsi_dev;
> +       struct miscdevice mdev;
> +       wait_queue_head_t wait;
> +       struct list_head link;
> +       struct list_head xfrs;
> +       struct kref kref;
> +       spinlock_t lock;
> +       char name[32];
> +       int idx;
> +       int rc;
> +};
> +
> +struct sbefifo_buf {
> +       u32 buf[SBEFIFO_BUF_CNT];
> +       unsigned long flags;
> +#define SBEFIFO_BUF_FULL               1
> +       u32 *rpos;
> +       u32 *wpos;
> +};
> +
> +struct sbefifo_xfr {
> +       struct sbefifo_buf *rbuf;
> +       struct sbefifo_buf *wbuf;
> +       struct list_head client;
> +       struct list_head xfrs;
> +       unsigned long flags;
> +#define SBEFIFO_XFR_WRITE_DONE         1
> +#define SBEFIFO_XFR_RESP_PENDING       2
> +#define SBEFIFO_XFR_COMPLETE           3
> +#define SBEFIFO_XFR_CANCEL             4
> +};
> +
> +struct sbefifo_client {
> +       struct sbefifo_buf rbuf;
> +       struct sbefifo_buf wbuf;
> +       struct list_head xfrs;
> +       struct sbefifo *dev;
> +       struct kref kref;
> +};
> +
> +static struct list_head sbefifo_fifos;
> +
> +static DEFINE_IDA(sbefifo_ida);
> +
> +static int sbefifo_inw(struct sbefifo *sbefifo, int reg, u32 *word)
> +{
> +       int rc;
> +       u32 raw_word;
> +
> +       rc = fsi_device_read(sbefifo->fsi_dev, reg, &raw_word,
> +                            sizeof(raw_word));
> +       if (rc)
> +               return rc;
> +
> +       *word = be32_to_cpu(raw_word);
> +       return 0;
> +}
> +
> +static int sbefifo_outw(struct sbefifo *sbefifo, int reg, u32 word)
> +{
> +       u32 raw_word = cpu_to_be32(word);
> +
> +       return fsi_device_write(sbefifo->fsi_dev, reg, &raw_word,
> +                               sizeof(raw_word));
> +}
> +
> +static int sbefifo_readw(struct sbefifo *sbefifo, u32 *word)
> +{
> +       return fsi_device_read(sbefifo->fsi_dev, SBEFIFO_DWN, word,
> +                              sizeof(*word));
> +}
> +
> +static int sbefifo_writew(struct sbefifo *sbefifo, u32 word)
> +{
> +       return fsi_device_write(sbefifo->fsi_dev, SBEFIFO_UP, &word,
> +                               sizeof(word));
> +}
> +
> +static int sbefifo_ack_eot(struct sbefifo *sbefifo)
> +{
> +       u32 discard;
> +       int ret;
> +
> +        /* Discard the EOT word. */
> +       ret = sbefifo_readw(sbefifo, &discard);
> +       if (ret)
> +               return ret;
> +
> +       return sbefifo_outw(sbefifo, SBEFIFO_DWN | SBEFIFO_EOT_ACK,
> +                       SBEFIFO_EOT_MAGIC);
> +}
> +
> +static size_t sbefifo_dev_nwreadable(u32 sts)
> +{
> +       static const u32 FIFO_NTRY_CNT_MSK = 0x000f0000;
> +       static const unsigned int FIFO_NTRY_CNT_SHIFT = 16;
> +
> +       return (sts & FIFO_NTRY_CNT_MSK) >> FIFO_NTRY_CNT_SHIFT;
> +}
> +
> +static size_t sbefifo_dev_nwwriteable(u32 sts)
> +{
> +       static const size_t FIFO_DEPTH = 8;
> +
> +       return FIFO_DEPTH - sbefifo_dev_nwreadable(sts);
> +}
> +
> +static void sbefifo_buf_init(struct sbefifo_buf *buf)
> +{
> +       WRITE_ONCE(buf->rpos, buf->buf);
> +       WRITE_ONCE(buf->wpos, buf->buf);
> +}
> +
> +static size_t sbefifo_buf_nbreadable(struct sbefifo_buf *buf)
> +{
> +       size_t n;
> +       u32 *rpos = READ_ONCE(buf->rpos);
> +       u32 *wpos = READ_ONCE(buf->wpos);
> +
> +       if (test_bit(SBEFIFO_BUF_FULL, &buf->flags))
> +               n = SBEFIFO_BUF_CNT;
> +       else if (rpos <= wpos)
> +               n = wpos - rpos;
> +       else
> +               n = (buf->buf + SBEFIFO_BUF_CNT) - rpos;
> +
> +       return n << 2;
> +}
> +
> +static size_t sbefifo_buf_nbwriteable(struct sbefifo_buf *buf)
> +{
> +       size_t n;
> +       u32 *rpos = READ_ONCE(buf->rpos);
> +       u32 *wpos = READ_ONCE(buf->wpos);
> +
> +       if (test_bit(SBEFIFO_BUF_FULL, &buf->flags))
> +               n = 0;
> +       else if (wpos < rpos)
> +               n = rpos - wpos;
> +       else
> +               n = (buf->buf + SBEFIFO_BUF_CNT) - wpos;
> +
> +       return n << 2;
> +}
> +
> +/*
> + * Update pointers and flags after doing a buffer read.  Return true if the
> + * buffer is now empty;
> + */
> +static bool sbefifo_buf_readnb(struct sbefifo_buf *buf, size_t n)
> +{
> +       u32 *rpos = READ_ONCE(buf->rpos);
> +       u32 *wpos = READ_ONCE(buf->wpos);
> +
> +       if (n)
> +               clear_bit(SBEFIFO_BUF_FULL, &buf->flags);
> +
> +       rpos += (n >> 2);
> +       if (rpos == buf->buf + SBEFIFO_BUF_CNT)
> +               rpos = buf->buf;
> +
> +       WRITE_ONCE(buf->rpos, rpos);
> +       return rpos == wpos;
> +}
> +
> +/*
> + * Update pointers and flags after doing a buffer write.  Return true if the
> + * buffer is now full.
> + */
> +static bool sbefifo_buf_wrotenb(struct sbefifo_buf *buf, size_t n)
> +{
> +       u32 *rpos = READ_ONCE(buf->rpos);
> +       u32 *wpos = READ_ONCE(buf->wpos);
> +
> +       wpos += (n >> 2);
> +       if (wpos == buf->buf + SBEFIFO_BUF_CNT)
> +               wpos = buf->buf;
> +       if (wpos == rpos)
> +               set_bit(SBEFIFO_BUF_FULL, &buf->flags);
> +
> +       WRITE_ONCE(buf->wpos, wpos);
> +       return rpos == wpos;
> +}
> +
> +static void sbefifo_free(struct kref *kref)
> +{
> +       struct sbefifo *sbefifo;
> +
> +       sbefifo = container_of(kref, struct sbefifo, kref);
> +       kfree(sbefifo);
> +}
> +
> +static void sbefifo_get(struct sbefifo *sbefifo)
> +{
> +       kref_get(&sbefifo->kref);
> +}
> +
> +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;
> +
> +       xfr = kzalloc(sizeof(*xfr), GFP_KERNEL);
> +       if (!xfr)
> +               return NULL;
> +
> +       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 struct sbefifo_xfr *sbefifo_client_next_xfr(
> +               struct sbefifo_client *client)
> +{
> +       if (list_empty(&client->xfrs))
> +               return NULL;
> +
> +       return container_of(client->xfrs.next, struct sbefifo_xfr,
> +                       client);
> +}
> +
> +static bool sbefifo_xfr_rsp_pending(struct sbefifo_client *client)
> +{
> +       struct sbefifo_xfr *xfr;
> +
> +       xfr = sbefifo_client_next_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_client *client;
> +       struct sbefifo_xfr *xfr;
> +
> +       client = container_of(kref, struct sbefifo_client, kref);
> +       list_for_each_entry(xfr, &client->xfrs, client) {
> +               /*
> +                * The client left with pending or running xfrs.
> +                * Cancel them.
> +                */
> +               set_bit(SBEFIFO_XFR_CANCEL, &xfr->flags);
> +               sbefifo_get(client->dev);
> +               if (mod_timer(&client->dev->poll_timer, jiffies))
> +                       sbefifo_put(client->dev);
> +       }
> +
> +       sbefifo_put(client->dev);
> +       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;
> +
> +       list_for_each_entry_safe(xfr, tmp, &sbefifo->xfrs, xfrs) {
> +               if (unlikely(test_bit(SBEFIFO_XFR_CANCEL, &xfr->flags))) {
> +                       /* Discard cancelled transfers. */
> +                       list_del(&xfr->xfrs);
> +                       kfree(xfr);
> +                       continue;
> +               }
> +               return xfr;
> +       }
> +
> +       return NULL;
> +}
> +
> +static void sbefifo_poll_timer(unsigned long data)
> +{
> +       static const unsigned long EOT_MASK = 0x000000ff;
> +       struct sbefifo *sbefifo = (void *)data;
> +       struct sbefifo_buf *rbuf, *wbuf;
> +       struct sbefifo_xfr *xfr = NULL;
> +       struct sbefifo_buf drain;
> +       size_t devn, bufn;
> +       int eot = 0;
> +       int ret = 0;
> +       u32 sts;
> +       int i;
> +
> +       spin_lock(&sbefifo->lock);
> +       xfr = list_first_entry_or_null(&sbefifo->xfrs, struct sbefifo_xfr,
> +                       xfrs);
> +       if (!xfr)
> +               goto out_unlock;
> +
> +       rbuf = xfr->rbuf;
> +       wbuf = xfr->wbuf;
> +
> +       if (unlikely(test_bit(SBEFIFO_XFR_CANCEL, &xfr->flags))) {
> +               /* The client left. */
> +               rbuf = &drain;
> +               wbuf = &drain;
> +               sbefifo_buf_init(&drain);
> +               if (!test_bit(SBEFIFO_XFR_RESP_PENDING, &xfr->flags))
> +                       set_bit(SBEFIFO_XFR_WRITE_DONE, &xfr->flags);
> +       }
> +
> +        /* Drain the write buffer. */
> +       while ((bufn = sbefifo_buf_nbreadable(wbuf))) {
> +               ret = sbefifo_inw(sbefifo, SBEFIFO_UP | SBEFIFO_STS,
> +                               &sts);
> +               if (ret)
> +                       goto out;
> +
> +               devn = sbefifo_dev_nwwriteable(sts);
> +               if (devn == 0) {
> +                       /* No open slot for write.  Reschedule. */
> +                       sbefifo->poll_timer.expires = jiffies +
> +                               msecs_to_jiffies(500);
> +                       add_timer(&sbefifo->poll_timer);
> +                       goto out_unlock;
> +               }
> +
> +               devn = min_t(size_t, devn, bufn >> 2);
> +               for (i = 0; i < devn; i++) {
> +                       ret = sbefifo_writew(sbefifo, *wbuf->rpos);
> +                       if (ret)
> +                               goto out;
> +
> +                       sbefifo_buf_readnb(wbuf, 1 << 2);
> +               }
> +       }
> +
> +        /* Send EOT if the writer is finished. */
> +       if (test_and_clear_bit(SBEFIFO_XFR_WRITE_DONE, &xfr->flags)) {
> +               ret = sbefifo_outw(sbefifo,
> +                               SBEFIFO_UP | SBEFIFO_EOT_RAISE,
> +                               SBEFIFO_EOT_MAGIC);
> +               if (ret)
> +                       goto out;
> +
> +               /* Inform reschedules that the writer is finished. */
> +               set_bit(SBEFIFO_XFR_RESP_PENDING, &xfr->flags);
> +       }
> +
> +       /* Nothing left to do if the writer is not finished. */
> +       if (!test_bit(SBEFIFO_XFR_RESP_PENDING, &xfr->flags))
> +               goto out;
> +
> +        /* Fill the read buffer. */
> +       while ((bufn = sbefifo_buf_nbwriteable(rbuf))) {
> +               ret = sbefifo_inw(sbefifo, SBEFIFO_DWN | SBEFIFO_STS, &sts);
> +               if (ret)
> +                       goto out;
> +
> +               devn = sbefifo_dev_nwreadable(sts);
> +               if (devn == 0) {
> +                       /* No data yet.  Reschedule. */
> +                       sbefifo->poll_timer.expires = jiffies +
> +                               msecs_to_jiffies(500);
> +                       add_timer(&sbefifo->poll_timer);
> +                       goto out_unlock;
> +               }
> +
> +               /* Fill.  The EOT word is discarded.  */
> +               devn = min_t(size_t, devn, bufn >> 2);
> +               eot = (sts & EOT_MASK) != 0;
> +               if (eot)
> +                       devn--;
> +
> +               for (i = 0; i < devn; i++) {
> +                       ret = sbefifo_readw(sbefifo, rbuf->wpos);
> +                       if (ret)
> +                               goto out;
> +
> +                       if (likely(!test_bit(SBEFIFO_XFR_CANCEL, &xfr->flags)))
> +                               sbefifo_buf_wrotenb(rbuf, 1 << 2);
> +               }
> +
> +               if (eot) {
> +                       ret = sbefifo_ack_eot(sbefifo);
> +                       if (ret)
> +                               goto out;
> +
> +                       set_bit(SBEFIFO_XFR_COMPLETE, &xfr->flags);
> +                       list_del(&xfr->xfrs);
> +                       if (unlikely(test_bit(SBEFIFO_XFR_CANCEL,
> +                                                       &xfr->flags)))
> +                               kfree(xfr);
> +                       break;
> +               }
> +       }
> +
> +out:
> +       if (unlikely(ret)) {
> +               sbefifo->rc = ret;
> +               dev_err(&sbefifo->fsi_dev->dev,
> +                               "Fatal bus access failure: %d\n", ret);
> +               list_for_each_entry(xfr, &sbefifo->xfrs, xfrs)
> +                       kfree(xfr);
> +               INIT_LIST_HEAD(&sbefifo->xfrs);
> +
> +       } else if (eot && sbefifo_next_xfr(sbefifo)) {
> +               sbefifo_get(sbefifo);
> +               sbefifo->poll_timer.expires = jiffies;
> +               add_timer(&sbefifo->poll_timer);
> +       }
> +
> +       sbefifo_put(sbefifo);
> +       wake_up(&sbefifo->wait);
> +
> +out_unlock:
> +       spin_unlock(&sbefifo->lock);
> +}
> +
> +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 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_WRITE, buf, len))
> +               return -EFAULT;
> +
> +       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,
> +                               (ret = READ_ONCE(sbefifo->rc)) ||
> +                               (n = sbefifo_buf_nbreadable(
> +                                               &client->rbuf)))) {
> +               sbefifo_put_client(client);
> +               return -ERESTARTSYS;
> +       }
> +       if (ret) {
> +               INIT_LIST_HEAD(&client->xfrs);
> +               sbefifo_put_client(client);
> +               return ret;
> +       }
> +
> +       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 (sbefifo_buf_readnb(&client->rbuf, n)) {
> +               xfr = sbefifo_client_next_xfr(client);
> +               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 {
> +                       kfree(xfr);
> +                       list_del(&xfr->client);
> +                       wake_up(&sbefifo->wait);
> +               }
> +       }
> +
> +       sbefifo_put_client(client);
> +
> +       return n;
> +}
> +
> +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;
> +       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))
> +               return -EFAULT;
> +
> +       if ((len >> 2) << 2 != len)
> +               return -EINVAL;
> +
> +       if (!len)
> +               return 0;
> +
> +       n = sbefifo_buf_nbwriteable(&client->wbuf);
> +
> +       spin_lock_irq(&sbefifo->lock);
> +       xfr = sbefifo_next_xfr(sbefifo);
> +
> +       if ((file->f_flags & O_NONBLOCK) && xfr && n < len) {
> +               spin_unlock_irq(&sbefifo->lock);
> +               return -EAGAIN;
> +       }
> +
> +       xfr = sbefifo_enq_xfr(client);
> +       if (!xfr) {
> +               spin_unlock_irq(&sbefifo->lock);
> +               return -ENOMEM;
> +       }
> +       spin_unlock_irq(&sbefifo->lock);
> +
> +       sbefifo_get_client(client);
> +
> +       /*
> +        * Partial writes are not really allowed in that EOT is sent exactly
> +        * once per write.
> +        */
> +       while (len) {
> +               if (wait_event_interruptible(sbefifo->wait,
> +                               READ_ONCE(sbefifo->rc) ||
> +                                       (sbefifo_client_next_xfr(client) == xfr &&
> +                                        (n = sbefifo_buf_nbwriteable(
> +                                                       &client->wbuf))))) {
> +                       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 -ERESTARTSYS;
> +               }
> +               if (sbefifo->rc) {
> +                       INIT_LIST_HEAD(&client->xfrs);
> +                       sbefifo_put_client(client);
> +                       return sbefifo->rc;
> +               }
> +
> +               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;
> +               }
> +
> +               sbefifo_buf_wrotenb(&client->wbuf, n);
> +               len -= n;
> +               buf += n;
> +               ret += n;
> +
> +               /*
> +                * Drain the write buffer.
> +                */
> +               sbefifo_get(sbefifo);
> +               if (mod_timer(&client->dev->poll_timer, jiffies))
> +                       sbefifo_put(sbefifo);
> +       }
> +
> +       set_bit(SBEFIFO_XFR_WRITE_DONE, &xfr->flags);
> +       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_probe(struct device *dev)
> +{
> +       struct fsi_device *fsi_dev = to_fsi_dev(dev);
> +       struct sbefifo *sbefifo;
> +       u32 sts;
> +       int ret;
> +
> +       dev_info(dev, "Found sbefifo device\n");
> +       sbefifo = kzalloc(sizeof(*sbefifo), GFP_KERNEL);
> +       if (!sbefifo)
> +               return -ENOMEM;
> +
> +       sbefifo->fsi_dev = fsi_dev;
> +
> +       ret = sbefifo_inw(sbefifo,
> +                       SBEFIFO_UP | SBEFIFO_STS, &sts);
> +       if (ret)
> +               return ret;
> +       if (!(sts & SBEFIFO_EMPTY)) {
> +               dev_err(&sbefifo->fsi_dev->dev,
> +                               "Found data in upstream fifo\n");
> +               return -EIO;
> +       }
> +
> +       ret = sbefifo_inw(sbefifo, SBEFIFO_DWN | SBEFIFO_STS, &sts);
> +       if (ret)
> +               return ret;
> +       if (!(sts & SBEFIFO_EMPTY)) {
> +               dev_err(&sbefifo->fsi_dev->dev,
> +                               "Found data in downstream fifo\n");
> +               return -EIO;
> +       }
> +
> +       sbefifo->mdev.minor = MISC_DYNAMIC_MINOR;
> +       sbefifo->mdev.fops = &sbefifo_fops;
> +       sbefifo->mdev.name = sbefifo->name;
> +       sbefifo->mdev.parent = dev;
> +       spin_lock_init(&sbefifo->lock);
> +       kref_init(&sbefifo->kref);
> +
> +       sbefifo->idx = ida_simple_get(&sbefifo_ida, 1, INT_MAX, GFP_KERNEL);
> +       snprintf(sbefifo->name, sizeof(sbefifo->name), "sbefifo%d",
> +                       sbefifo->idx);
> +       init_waitqueue_head(&sbefifo->wait);
> +       INIT_LIST_HEAD(&sbefifo->xfrs);
> +
> +       /* This bit of silicon doesn't offer any interrupts... */
> +       setup_timer(&sbefifo->poll_timer, sbefifo_poll_timer,
> +                       (unsigned long)sbefifo);
> +
> +       list_add(&sbefifo->link, &sbefifo_fifos);
> +       return misc_register(&sbefifo->mdev);
> +}
> +
> +static int sbefifo_remove(struct device *dev)
> +{
> +       struct fsi_device *fsi_dev = to_fsi_dev(dev);
> +       struct sbefifo *sbefifo, *sbefifo_tmp;
> +       struct sbefifo_xfr *xfr;
> +
> +       list_for_each_entry_safe(sbefifo, sbefifo_tmp, &sbefifo_fifos, link) {
> +               if (sbefifo->fsi_dev != fsi_dev)
> +                       continue;
> +               misc_deregister(&sbefifo->mdev);
> +               list_del(&sbefifo->link);
> +               ida_simple_remove(&sbefifo_ida, sbefifo->idx);
> +
> +               if (del_timer_sync(&sbefifo->poll_timer))
> +                       sbefifo_put(sbefifo);
> +
> +               spin_lock(&sbefifo->lock);
> +               list_for_each_entry(xfr, &sbefifo->xfrs, xfrs)
> +                       kfree(xfr);
> +               spin_unlock(&sbefifo->lock);
> +
> +               WRITE_ONCE(sbefifo->rc, -ENODEV);
> +
> +               wake_up(&sbefifo->wait);
> +               sbefifo_put(sbefifo);
> +       }
> +
> +       return 0;
> +}
> +
> +static struct fsi_device_id sbefifo_ids[] = {
> +       {
> +               .engine_type = FSI_ENGID_SBE,
> +               .version = FSI_VERSION_ANY,
> +       },
> +       { 0 }
> +};
> +
> +static struct fsi_driver sbefifo_drv = {
> +       .id_table = sbefifo_ids,
> +       .drv = {
> +               .name = DEVICE_NAME,
> +               .bus = &fsi_bus_type,
> +               .probe = sbefifo_probe,
> +               .remove = sbefifo_remove,
> +       }
> +};
> +
> +static int sbefifo_init(void)
> +{
> +       INIT_LIST_HEAD(&sbefifo_fifos);
> +       return fsi_driver_register(&sbefifo_drv);
> +}
> +
> +static void sbefifo_exit(void)
> +{
> +       fsi_driver_unregister(&sbefifo_drv);
> +}
> +
> +module_init(sbefifo_init);
> +module_exit(sbefifo_exit);
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Brad Bishop <bradleyb at fuzziesquirrel.com>");
> +MODULE_DESCRIPTION("Linux device interface to the POWER self boot engine");
> --
> 1.8.3.1
>


More information about the openbmc mailing list