[RFC 5/6] spi: spidev: Add dma-buf support
Noralf Trønnes
noralf at tronnes.org
Wed Jan 4 13:34:41 UTC 2017
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
More information about the dri-devel
mailing list