[PATCH] spi: SPI_MASTER_MUST_* with scatter-gather only option and avoiding realloc
kernel at martin.sperl.org
kernel at martin.sperl.org
Thu May 14 02:58:44 PDT 2015
From: Martin Sperl <kernel at martin.sperl.org>
Rewrite of spi_map_msg and spi_map_buf so that for SPI_MASTER_MUST_*:
* short transfers are handled by a page-sized buffer instead of
reallocating and freeing memory (if smaller than PAGE_SIZE)
* with an extra set of flags allows to ONLY create a scatter/gather list
that reuses the same page for all the transfers
The scatter list produced is a match of the corresponding template
scatter list (so tx-sg is the template for the rx-sg and vice versa)
It also fixes the insufficient cleanup in case __spi_map_msg returns
an error.
Signed-off-by: Martin Sperl <kernel at martin.sperl.org>
---
drivers/spi/spi-bcm2835.c | 2 +-
drivers/spi/spi.c | 174 ++++++++++++++++++++++++++++++++++++++++-----
include/linux/spi/spi.h | 10 ++-
3 files changed, 165 insertions(+), 21 deletions(-)
diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c
index ac1760e..ac74456 100644
--- a/drivers/spi/spi-bcm2835.c
+++ b/drivers/spi/spi-bcm2835.c
@@ -463,7 +463,7 @@ void bcm2835_dma_init(struct spi_master *master, struct device *dev)
master->can_dma = bcm2835_spi_can_dma;
master->max_dma_len = 65535; /* limitation by BCM2835_SPI_DLEN */
/* need to do TX AND RX DMA, so we need dummy buffers */
- master->flags = SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX;
+ master->flags = SPI_MASTER_MUST_RX_SG | SPI_MASTER_MUST_TX_SG;
return;
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index d35c1a1..c85cf58 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -471,6 +471,73 @@ static void spi_set_cs(struct spi_device *spi, bool enable)
}
#ifdef CONFIG_HAS_DMA
+static int spi_map_null(struct spi_master *master, struct device *dev,
+ struct sg_table *sgt,
+ struct sg_table *sgt_template,
+ void *buf,
+ enum dma_data_direction dir)
+{
+ struct page *vm_page;
+ unsigned long page_offset;
+ int sgs = 0;
+ int i, j, ret;
+ ssize_t len, l;
+
+ if (is_vmalloc_addr(buf)) {
+ vm_page = vmalloc_to_page(buf);
+ if (!vm_page) {
+ sg_free_table(sgt);
+ return -ENOMEM;
+ }
+ page_offset = offset_in_page(buf);
+ } else {
+ vm_page = NULL;
+ page_offset = 0;
+ }
+
+ /* count the number of sgs we will need walking the template */
+ for (i = 0; i < sgt_template->nents; i++) {
+ len = sg_dma_len(&sgt_template->sgl[i]);
+ while (len) {
+ len -= min_t(size_t, len, PAGE_SIZE);
+ sgs++;
+ }
+ }
+
+ /* now allocate it */
+ ret = sg_alloc_table(sgt, sgs, GFP_KERNEL);
+ if (ret)
+ return ret;
+
+ /* and iterate over the template to fill our own table */
+ for (i = 0, j = 0; i < sgt_template->nents; i++) {
+ len = sg_dma_len(&sgt_template->sgl[i]);
+ /* split into multiple transfers if needed */
+ while (len) {
+ l = min_t(size_t, len, PAGE_SIZE);
+ if (vm_page)
+ sg_set_page(&sgt->sgl[i], vm_page,
+ l, page_offset);
+ else
+ sg_set_buf(&sgt->sgl[j], buf, l);
+ len -= l;
+ j++;
+ }
+ }
+
+ ret = dma_map_sg(dev, sgt->sgl, sgt->nents, dir);
+ if (!ret)
+ ret = -ENOMEM;
+ if (ret < 0) {
+ sg_free_table(sgt);
+ return ret;
+ }
+
+ sgt->nents = ret;
+
+ return 0;
+}
+
static int spi_map_buf(struct spi_master *master, struct device *dev,
struct sg_table *sgt, void *buf, size_t len,
enum dma_data_direction dir)
@@ -564,6 +631,37 @@ static int __spi_map_msg(struct spi_master *master, struct spi_message *msg)
return ret;
}
}
+
+ /*
+ * handle the SPI_MASTER_MUST_*_SG
+ * note that the situation with tx_buf and rx_buf both NULL
+ * is checked and handled inside spi_transfer_one_message
+ */
+ if ((!xfer->tx_buf) && (xfer->rx_buf) &&
+ (master->flags & SPI_MASTER_MUST_TX_SG)) {
+ ret = spi_map_null(master, tx_dev,
+ &xfer->tx_sg, &xfer->rx_sg,
+ master->page_tx,
+ DMA_TO_DEVICE);
+ if (ret != 0) {
+ spi_unmap_buf(master, rx_dev, &xfer->rx_sg,
+ DMA_FROM_DEVICE);
+ return ret;
+ }
+ }
+
+ if ((!xfer->rx_buf) && (xfer->tx_buf) &&
+ (master->flags & SPI_MASTER_MUST_RX_SG)) {
+ ret = spi_map_null(master, rx_dev,
+ &xfer->rx_sg, &xfer->tx_sg,
+ master->page_rx,
+ DMA_FROM_DEVICE);
+ if (ret != 0) {
+ spi_unmap_buf(master, tx_dev, &xfer->tx_sg,
+ DMA_TO_DEVICE);
+ return ret;
+ }
+ }
}
master->cur_msg_mapped = true;
@@ -587,9 +685,11 @@ static int spi_unmap_msg(struct spi_master *master, struct spi_message *msg)
* Restore the original value of tx_buf or rx_buf if they are
* NULL.
*/
- if (xfer->tx_buf == master->dummy_tx)
+ if ((xfer->tx_buf == master->dummy_tx) ||
+ (xfer->tx_buf == master->page_tx))
xfer->tx_buf = NULL;
- if (xfer->rx_buf == master->dummy_rx)
+ if ((xfer->rx_buf == master->dummy_rx) ||
+ (xfer->rx_buf == master->page_rx))
xfer->rx_buf = NULL;
if (!master->can_dma(master, msg->spi, xfer))
@@ -618,8 +718,9 @@ static inline int spi_unmap_msg(struct spi_master *master,
static int spi_map_msg(struct spi_master *master, struct spi_message *msg)
{
struct spi_transfer *xfer;
- void *tmp;
+ void *tmp_tx, *tmp_rx;
unsigned int max_tx, max_rx;
+ int ret;
if (master->flags & (SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX)) {
max_tx = 0;
@@ -635,34 +736,53 @@ static int spi_map_msg(struct spi_master *master, struct spi_message *msg)
}
if (max_tx) {
- tmp = krealloc(master->dummy_tx, max_tx,
- GFP_KERNEL | GFP_DMA);
- if (!tmp)
- return -ENOMEM;
- master->dummy_tx = tmp;
- memset(tmp, 0, max_tx);
+ if (max_tx > PAGE_SIZE) {
+ tmp_tx = krealloc(master->dummy_tx, max_tx,
+ GFP_KERNEL | GFP_DMA);
+ if (!tmp_tx)
+ return -ENOMEM;
+ master->dummy_tx = tmp_tx;
+ memset(tmp_tx, 0, max_tx);
+ } else {
+ tmp_tx = master->page_tx;
+ }
+ } else {
+ tmp_tx = NULL;
}
- if (max_rx) {
- tmp = krealloc(master->dummy_rx, max_rx,
- GFP_KERNEL | GFP_DMA);
- if (!tmp)
- return -ENOMEM;
- master->dummy_rx = tmp;
+ if (max_rx) {
+ if (max_rx > PAGE_SIZE) {
+ tmp_rx = krealloc(master->dummy_rx, max_rx,
+ GFP_KERNEL | GFP_DMA);
+ if (!tmp_rx)
+ return -ENOMEM;
+ master->dummy_rx = tmp_rx;
+ } else {
+ tmp_rx = master->page_rx;
+ }
+ } else {
+ tmp_tx = NULL;
}
if (max_tx || max_rx) {
list_for_each_entry(xfer, &msg->transfers,
transfer_list) {
if (!xfer->tx_buf)
- xfer->tx_buf = master->dummy_tx;
+ xfer->tx_buf = tmp_tx;
if (!xfer->rx_buf)
- xfer->rx_buf = master->dummy_rx;
+ xfer->rx_buf = tmp_rx;
}
}
}
- return __spi_map_msg(master, msg);
+ /* if we fail we need to undo the parial mappings
+ * and fix up the modified rx_buf/tx_buf
+ */
+ ret = __spi_map_msg(master, msg);
+ if (ret)
+ spi_unmap_msg(master, msg);
+
+ return ret;
}
/*
@@ -1555,6 +1675,24 @@ int spi_register_master(struct spi_master *master)
if (!master->max_dma_len)
master->max_dma_len = INT_MAX;
+ /* we need to set max_dma_len to PAGESIZE for MUST_XX_SG */
+ if (master->flags & (SPI_MASTER_MUST_RX_SG | SPI_MASTER_MUST_TX_SG))
+ master->max_dma_len = min_t(size_t,
+ master->max_dma_len, PAGE_SIZE);
+ /* and allocate some buffers for dma */
+ if (master->flags & (SPI_MASTER_MUST_RX | SPI_MASTER_MUST_RX_SG)) {
+ master->page_rx = devm_kmalloc(&master->dev,
+ PAGE_SIZE, GFP_DMA);
+ if (!master->page_rx)
+ return -ENOMEM;
+ }
+ if (master->flags & (SPI_MASTER_MUST_TX | SPI_MASTER_MUST_TX_SG)) {
+ master->page_tx = devm_kzalloc(&master->dev,
+ PAGE_SIZE, GFP_DMA);
+ if (!master->page_tx)
+ return -ENOMEM;
+ }
+
/* register the device, then userspace will see it.
* registration fails if the bus ID is in use.
*/
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index d673072..1f440ff 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -353,8 +353,10 @@ struct spi_master {
#define SPI_MASTER_HALF_DUPLEX BIT(0) /* can't do full duplex */
#define SPI_MASTER_NO_RX BIT(1) /* can't do buffer read */
#define SPI_MASTER_NO_TX BIT(2) /* can't do buffer write */
-#define SPI_MASTER_MUST_RX BIT(3) /* requires rx */
-#define SPI_MASTER_MUST_TX BIT(4) /* requires tx */
+#define SPI_MASTER_MUST_RX BIT(3) /* requires rx_buf allocated */
+#define SPI_MASTER_MUST_TX BIT(4) /* requires tx_buf allocated */
+#define SPI_MASTER_MUST_RX_SG BIT(5) /* requires rx sg list */
+#define SPI_MASTER_MUST_TX_SG BIT(6) /* requires tx sg list */
/* lock and mutex for SPI bus locking */
spinlock_t bus_lock_spinlock;
@@ -459,6 +461,10 @@ struct spi_master {
/* dummy data for full duplex devices */
void *dummy_rx;
void *dummy_tx;
+
+ /* pages for dma-transfers */
+ void *page_rx;
+ void *page_tx;
};
static inline void *spi_master_get_devdata(struct spi_master *master)
--
1.7.10.4
More information about the linux-rpi-kernel
mailing list