diff mbox

[RFC,5/6] spi: spidev: Add dma-buf support

Message ID 20170104133442.4534-6-noralf@tronnes.org (mailing list archive)
State New, archived
Headers show

Commit Message

Noralf Trønnes Jan. 4, 2017, 1:34 p.m. UTC
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@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 mbox

Patch

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