Add support for using dma-buf buffers in transfers from userspace.

FIXME: Backwards compatibility needs to be taken care of somehow.

Signed-off-by: Noralf Trønnes <noralf at tronnes.org>
---
 drivers/spi/Kconfig             |   1 +
 drivers/spi/spidev.c            | 158 +++++++++++++++++++++++++++++++++++++++-
 include/uapi/linux/spi/spidev.h |   8 ++
 3 files changed, 163 insertions(+), 4 deletions(-)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index b799547..ac6bbd1 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -731,6 +731,7 @@ comment "SPI Protocol Masters"

 config SPI_SPIDEV
        tristate "User mode SPI device driver support"
+       select SG_SPLIT if DMA_SHARED_BUFFER
        help
          This supports user mode SPI protocol drivers.

diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c
index d780491..35e6377 100644
--- a/drivers/spi/spidev.c
+++ b/drivers/spi/spidev.c
@@ -21,6 +21,8 @@
 #include <linux/ioctl.h>
 #include <linux/fs.h>
 #include <linux/device.h>
+#include <linux/dma-buf.h>
+#include <linux/dmaengine.h>
 #include <linux/err.h>
 #include <linux/list.h>
 #include <linux/errno.h>
@@ -87,6 +89,16 @@ struct spidev_data {
        u32                     speed_hz;
 };

+struct spidev_dmabuf {
+       struct device *dev;
+       struct dma_buf_attachment *attach;
+       enum dma_data_direction dir;
+       struct sg_table *sgt_dmabuf;
+       void *vaddr;
+       struct scatterlist *sgl;
+       unsigned int nents;
+};
+
 static LIST_HEAD(device_list);
 static DEFINE_MUTEX(device_list_lock);

@@ -205,21 +217,122 @@ spidev_write(struct file *filp, const char __user *buf,
        return status;
 }

+#ifdef CONFIG_DMA_SHARED_BUFFER
+
+static int spidev_dmabuf_map(struct spidev_dmabuf *sdmabuf, struct device *dev,
+                            int fd, enum dma_data_direction dir,
+                            u32 offset, u32 len)
+{
+       struct dma_buf_attachment *attach;
+       struct dma_buf *dmabuf;
+       struct sg_table *sgt;
+       void *vaddr;
+       size_t sizes[1] = { len, };
+       struct scatterlist *out[1];
+       int out_nents[1];
+       int ret;
+
+       dmabuf = dma_buf_get(fd);
+       if (IS_ERR(dmabuf))
+               return PTR_ERR(dmabuf);
+
+       attach = dma_buf_attach(dmabuf, dev);
+       if (IS_ERR(attach)) {
+               ret = PTR_ERR(attach);
+               goto err_put;
+       }
+
+       sgt = dma_buf_map_attachment(attach, dir);
+       if (IS_ERR(sgt)) {
+               ret = PTR_ERR(sgt);
+               goto err_detach;
+       }
+
+       ret = sg_split(sgt->sgl, sgt->nents, offset, 1, sizes, out, out_nents, 
GFP_KERNEL);
+       if (ret) {
+               goto err_unmap;
+       }
+
+       /* A virtual address is only necessary if master can't do dma. */
+//     ret = dma_buf_begin_cpu_access(dmabuf, dir);
+//     if (ret)
+//             goto err_free_sg;
+
+       vaddr = dma_buf_vmap(dmabuf);
+       if (!vaddr) {
+               ret = -ENOMEM;
+               goto err_end_access;
+       }
+
+       sdmabuf->dev = dev;
+       sdmabuf->attach = attach;
+       sdmabuf->dir = dir;
+       sdmabuf->sgt_dmabuf = sgt;
+       sdmabuf->vaddr = vaddr;
+       sdmabuf->sgl = out[0];
+       sdmabuf->nents = out_nents[0];
+
+       return 0;
+
+err_end_access:
+//     dma_buf_end_cpu_access(dmabuf, dir);
+//err_free_sg:
+       kfree(out[0]);
+err_unmap:
+       dma_buf_unmap_attachment(attach, sgt, dir);
+err_detach:
+       dma_buf_detach(dmabuf, attach);
+err_put:
+       dma_buf_put(dmabuf);
+
+       return ret;
+}
+
+static void spidev_dmabuf_unmap(struct spidev_dmabuf *sdmabuf)
+{
+       struct dma_buf *dmabuf;
+
+       if (!sdmabuf->attach)
+               return;
+
+       dmabuf = sdmabuf->attach->dmabuf;
+       dma_buf_vunmap(dmabuf, sdmabuf->vaddr);
+//     dma_buf_end_cpu_access(dmabuf, sdmabuf->dir);
+       dma_buf_unmap_attachment(sdmabuf->attach, sdmabuf->sgt_dmabuf, 
sdmabuf->dir);
+       dma_buf_detach(dmabuf, sdmabuf->attach);
+       dma_buf_put(dmabuf);
+}
+#else
+static int spidev_dmabuf_map(struct spidev_dmabuf *sdmabuf, struct device *dev,
+                            int fd, enum dma_data_direction dir,
+                            u32 offset, u32 len)
+{
+       return -ENOTSUPP;
+}
+
+static void spidev_dmabuf_unmap(struct spidev_dmabuf *sdmabuf) {}
+#endif
+
 static int spidev_message(struct spidev_data *spidev,
                struct spi_ioc_transfer *u_xfers, unsigned n_xfers)
 {
+       struct spi_device       *spi = spidev->spi;
        struct spi_message      msg;
        struct spi_transfer     *k_xfers;
        struct spi_transfer     *k_tmp;
        struct spi_ioc_transfer *u_tmp;
+       struct spidev_dmabuf    *sdmabufs, *s_tmp;
        unsigned                n, total, tx_total, rx_total;
        u8                      *tx_buf, *rx_buf;
        int                     status = -EFAULT;

        spi_message_init(&msg);
        k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);
-       if (k_xfers == NULL)
-               return -ENOMEM;
+       sdmabufs = kcalloc(n_xfers * 2, sizeof(*s_tmp), GFP_KERNEL);
+       if (!k_xfers || !sdmabufs) {
+               status = -ENOMEM;
+               goto done;
+       }

        /* Construct spi_message, copying any tx data to bounce buffer.
         * We walk the array of user-provided transfers, using each one
@@ -230,6 +343,7 @@ static int spidev_message(struct spidev_data *spidev,
        total = 0;
        tx_total = 0;
        rx_total = 0;
+       s_tmp = sdmabufs;
        for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
                        n;
                        n--, k_tmp++, u_tmp++) {
@@ -259,7 +373,12 @@ static int spidev_message(struct spidev_data *spidev,
                                                u_tmp->len))
                                goto done;
                        rx_buf += k_tmp->len;
+               } else if (u_tmp->rx_dma_fd > 0) {
+                       /* TODO */
+                       status = -ENOTSUPP;
+                       goto done;
                }
+
                if (u_tmp->tx_buf) {
                        /* this transfer needs space in TX bounce buffer */
                        tx_total += k_tmp->len;
@@ -273,6 +392,31 @@ static int spidev_message(struct spidev_data *spidev,
                                        u_tmp->len))
                                goto done;
                        tx_buf += k_tmp->len;
+               } else if (u_tmp->tx_dma_fd > 0) {
+                       struct device *tx_dev;
+
+                       if (k_tmp->len > spi->master->max_dma_len) {
+                               status = -EMSGSIZE;
+                               goto done;
+                       }
+
+                       if (spi->master->dma_tx)
+                               tx_dev = spi->master->dma_tx->device->dev;
+                       else
+                               tx_dev = &spi->master->dev;
+
+                       status = spidev_dmabuf_map(s_tmp, tx_dev,
+                                                  u_tmp->tx_dma_fd,
+                                                  DMA_TO_DEVICE,
+                                                  u_tmp->dma_offset,
+                                                  k_tmp->len);
+                       if (status)
+                               goto done;
+
+                       k_tmp->tx_sg.sgl = s_tmp->sgl;
+                       k_tmp->tx_sg.nents = s_tmp->nents;
+                       k_tmp->tx_buf = s_tmp->vaddr + u_tmp->dma_offset;
+                       s_tmp++;
                }

                k_tmp->cs_change = !!u_tmp->cs_change;
@@ -287,8 +431,10 @@ static int spidev_message(struct spidev_data *spidev,
                dev_dbg(&spidev->spi->dev,
                        "  xfer len %u %s%s%s%dbits %u usec %uHz\n",
                        u_tmp->len,
-                       u_tmp->rx_buf ? "rx " : "",
-                       u_tmp->tx_buf ? "tx " : "",
+                       u_tmp->rx_buf ? "rx " :
+                                       u_tmp->rx_dma_fd ? "tx-dma" : "",
+                       u_tmp->tx_buf ? "tx " :
+                                       u_tmp->tx_dma_fd ? "rx-dma" : "",
                        u_tmp->cs_change ? "cs " : "",
                        u_tmp->bits_per_word ? : spidev->spi->bits_per_word,
                        u_tmp->delay_usecs,
@@ -317,6 +463,10 @@ static int spidev_message(struct spidev_data *spidev,
        status = total;

 done:
+       for (n = n_xfers, s_tmp = sdmabufs; n; n--, s_tmp++)
+               spidev_dmabuf_unmap(s_tmp);
+
+       kfree(sdmabufs);
        kfree(k_xfers);
        return status;
 }
diff --git a/include/uapi/linux/spi/spidev.h b/include/uapi/linux/spi/spidev.h
index dd5f21e..a9b6cd0 100644
--- a/include/uapi/linux/spi/spidev.h
+++ b/include/uapi/linux/spi/spidev.h
@@ -64,6 +64,9 @@
  * @delay_usecs: If nonzero, how long to delay after the last bit transfer
  *     before optionally deselecting the device before the next transfer.
  * @cs_change: True to deselect device before starting the next transfer.
+ * @tx_dma_fd: File descriptor for transmitting dma-buf buffers.
+ * @rx_dma_fd: File descriptor for receiving dma-buf buffers.
+ * @dma_offset: Offset into dma-buf buffer.
  *
  * This structure is mapped directly to the kernel spi_transfer structure;
  * the fields have the same meanings, except of course that the pointers
@@ -100,6 +103,11 @@ struct spi_ioc_transfer {
        __u8            rx_nbits;
        __u16           pad;

+       __s32           tx_dma_fd;
+       __s32           rx_dma_fd;
+       __u32           dma_offset;
+       __u32           pad2;
+
        /* If the contents of 'struct spi_ioc_transfer' ever change
         * incompatibly, then the ioctl number (currently 0) must change;
         * ioctls with constant size fields get a bit more in the way of
-- 
2.10.2

Reply via email to