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@xxxxxxxxxxx> --- 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 _______________________________________________ dri-devel mailing list dri-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/dri-devel