From: Martin Sperl <ker...@martin.sperl.org>

Added code that splits a single spi_transfer into 2 transfers
in case either transfer-buffer is not aligned.

In the case of rx and tx_buf misaligned to different offsets,
the tx_buf is also copied to an aligned address so that the
whole transfer is aligned on both buffers.

We chose tx_buf to get copied because this may allow us to
use CPU resources (or different cores) in parallel to an
already running transfer in the future and avoids a
syncronous copy after the transfer is finished.

Signed-off-by: Martin Sperl <ker...@martin.sperl.org>
---
 drivers/spi/spi.c       |  206 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/spi/spi.h |   13 +++
 2 files changed, 219 insertions(+)

Changelog:
  V1 -> V3: split into distinct patches and rewrite to allow for copy

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 37e6507..f276c99 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -146,6 +146,8 @@ SPI_STATISTICS_TRANSFER_BYTES_HISTO(15, "32768-65535");
 SPI_STATISTICS_TRANSFER_BYTES_HISTO(16, "65536+");

 SPI_STATISTICS_SHOW(transfers_split_maxsize, "%lu");
+SPI_STATISTICS_SHOW(transfers_split_unaligned, "%lu");
+SPI_STATISTICS_SHOW(transfers_split_unaligned_copy, "%lu");

 static struct attribute *spi_dev_attrs[] = {
        &dev_attr_modalias.attr,
@@ -185,6 +187,8 @@ static struct attribute *spi_device_statistics_attrs[] = {
        &dev_attr_spi_device_transfer_bytes_histo15.attr,
        &dev_attr_spi_device_transfer_bytes_histo16.attr,
        &dev_attr_spi_device_transfers_split_maxsize.attr,
+       &dev_attr_spi_device_transfers_split_unaligned.attr,
+       &dev_attr_spi_device_transfers_split_unaligned_copy.attr,
        NULL,
 };

@@ -228,6 +232,8 @@ static struct attribute *spi_master_statistics_attrs[] = {
        &dev_attr_spi_master_transfer_bytes_histo15.attr,
        &dev_attr_spi_master_transfer_bytes_histo16.attr,
        &dev_attr_spi_master_transfers_split_maxsize.attr,
+       &dev_attr_spi_master_transfers_split_unaligned.attr,
+       &dev_attr_spi_master_transfers_split_unaligned_copy.attr,
        NULL,
 };

@@ -2346,6 +2352,206 @@ int spi_split_transfers_maxsize(struct spi_master 
*master,
 }
 EXPORT_SYMBOL_GPL(spi_split_transfers_maxsize);

+static int __spi_split_transfer_unaligned_do(
+       struct spi_master *master,
+       struct spi_message *msg,
+       struct spi_transfer **xferp,
+       size_t alignment,
+       size_t alignment_to_correct,
+       bool copy_tx,
+       gfp_t gfp)
+{
+       struct spi_transfer *xfers;
+       struct spi_replaced_transfers *srt;
+       size_t alignment_shift;
+       size_t extra = 0;
+       void *data;
+
+       /* warn once about this fact that we are splitting */
+       dev_warn_once(&msg->spi->dev,
+                     "spi_transfer with misaligned buffers - had to split 
transfers\n");
+
+       /* the number of bytes by which we need to shift to align */
+       alignment_shift = BIT(alignment) - alignment_to_correct;
+
+       /* calculate extra buffer we need to allocate to align tx by copy */
+       if (copy_tx)
+               extra = (*xferp)->len - alignment_shift + BIT(alignment);
+
+       /* replace the transfer */
+       srt = spi_replace_transfers(msg, *xferp, 1, 2, NULL, extra, gfp);
+       if (!srt)
+               return -ENOMEM;
+       xfers = srt->inserted_transfers;
+
+       /* for the first transfer we only set the length */
+       xfers[0].len = alignment_shift;
+
+       /* for the second transfer we need to shift the pointers as well
+        * as modify length (substracting the len we transfer in the first)
+        */
+       xfers[1].len -= alignment_shift;
+       if (xfers[1].tx_buf)
+               xfers[1].tx_buf += alignment_shift;
+       if (xfers[1].rx_buf)
+               xfers[1].rx_buf += alignment_shift;
+       if (xfers[1].tx_dma)
+               xfers[1].tx_dma += alignment_shift;
+       if (xfers[1].rx_dma)
+               xfers[1].rx_dma += alignment_shift;
+
+       /* handle copy_tx  */
+       if (copy_tx) {
+               data = PTR_ALIGN(srt->extradata, alignment);
+
+               /* copy data from old to new */
+               memcpy(data, xfers[1].tx_buf, xfers[1].len);
+
+               /* set up the pointer to the new buffer */
+               xfers[1].tx_buf = data;
+
+               /* force the tx_dma/rx_dma buffers to be unset */
+               xfers[1].tx_dma = 0;
+               xfers[1].rx_dma = 0;
+
+               /* warn once about this fact that we are also copying */
+               dev_warn_once(&msg->spi->dev, "spi_transfer rx/tx buffers are 
misaligned to different offsets - need to copy tx_buf to fix it\n");
+               /* increment statistics counters */
+               SPI_STATISTICS_INCREMENT_FIELD(
+                       &master->statistics,
+                       transfers_split_unaligned_copy);
+               SPI_STATISTICS_INCREMENT_FIELD(
+                       &msg->spi->statistics,
+                       transfers_split_unaligned_copy);
+       }
+
+       /* finally we can set up xferp as xfers[1] */
+       *xferp = &xfers[1];
+
+       /* increment statistics counters */
+       SPI_STATISTICS_INCREMENT_FIELD(&master->statistics,
+                                      transfers_split_unaligned);
+       SPI_STATISTICS_INCREMENT_FIELD(&msg->spi->statistics,
+                                      transfers_split_unaligned);
+
+       return 0;
+}
+
+static int __spi_split_transfer_unaligned_check(
+       struct spi_master *master,
+       struct spi_message *msg,
+       struct spi_transfer **xferp,
+       size_t alignment,
+       gfp_t gfp)
+{
+       struct spi_transfer *xfer = *xferp;
+       size_t alignment_mask;
+
+       const char *tx_start, *rx_start; /* the rx/tx_buf address */
+       const char *tx_end, *rx_end; /* the last byte of the transfer */
+       size_t tx_start_page, rx_start_page; /* the "page address" for start */
+       size_t tx_end_page, rx_end_page; /* the "page address" for end */
+       size_t tx_start_align, rx_start_align; /* alignment of buf address */
+
+       /* calculate the values */
+       alignment_mask = (1 << alignment) - 1;
+
+       tx_start = xfer->tx_buf;
+       tx_start_align = ((size_t)tx_start & alignment_mask);
+
+       rx_start = xfer->rx_buf;
+       rx_start_align = ((size_t)rx_start & alignment_mask);
+
+       /* if the start alignment is 0 for both rx and tx */
+       if ((!rx_start_align) && (!tx_start_align))
+               return 0;
+
+       /* the end pointer */
+       tx_end = tx_start ? &tx_start[xfer->len - 1] : NULL;
+       rx_end = rx_start ? &tx_start[xfer->len - 1] : NULL;
+
+       /* and the page addresses - mostly needed to see if the transfer
+        * spills over a page boundary
+        */
+       tx_start_page = (size_t)tx_start & (PAGE_SIZE - 1);
+       tx_end_page = (size_t)tx_end & (PAGE_SIZE - 1);
+       rx_start_page = (size_t)rx_start & (PAGE_SIZE - 1);
+       rx_end_page = (size_t)rx_end & (PAGE_SIZE - 1);
+
+       /* if we are on the same end-page, then there is nothing to do */
+       if ((tx_start_page == tx_end_page) &&
+           (rx_start_page == rx_end_page))
+               return 0;
+
+       /* if tx_align (not 0 means also not null) and rx_start is null */
+       if (tx_start_align && (!rx_start))
+               return __spi_split_transfer_unaligned_do(
+                       master, msg, xferp, alignment, tx_start_align,
+                       false, gfp);
+
+       /* if rx_align (not 0 means also not null) and tx_start is null */
+       if (rx_start_align && (!tx_start))
+               return __spi_split_transfer_unaligned_do(
+                       master, msg, xferp, alignment, rx_start_align,
+                       false, gfp);
+
+       /* if the alignment for both is identical */
+       if (rx_start_align == tx_start_align)
+               return __spi_split_transfer_unaligned_do(
+                       master, msg, xferp, alignment, rx_start_align,
+                       false, gfp);
+
+       /* for the final case with tx and rx of different alignment
+        * we just align rx and copy tx to an alligned transfer
+        */
+       return __spi_split_transfer_unaligned_do(
+               master, msg, xferp, alignment, rx_start_align, true, gfp);
+}
+
+/**
+ * spi_split_tranfers_unaligned - split spi transfers into multiple transfers
+ *                                when rx_buf or tx_buf are unaligned
+ *                                and the transfer size is above minimum
+ * @master:    the @spi_master for this transfer
+ * @message:   the @spi_message to transform
+ * @min_size:  the minimum size when to apply this
+ * @alignment: the alignment  that we require
+ * @gfp:       gfp flags
+ *
+ * Return: status of transformation
+ *
+ * note that the "first" transfer is always unaligned,
+ * but its length is always < (1 << alignment) - this assumes that
+ * the first transfer gets done in PIO mode
+ */
+int spi_split_transfers_unaligned(struct spi_master *master,
+                                 struct spi_message *message,
+                                 size_t min_size,
+                                 size_t alignment,
+                                 gfp_t gfp)
+{
+       struct spi_transfer *xfer;
+       int ret;
+
+       /* iterate over the transfer_list,
+        * but note that xfer is advanced to the last transfer inserted
+        * to avoid checking sizes again unnecessarily (also xfer does
+        * potentiall belong to a different list by the time the
+        * replacement has happened
+        */
+       list_for_each_entry(xfer, &message->transfers, transfer_list) {
+               if (xfer->len >= min_size) {
+                       ret = __spi_split_transfer_unaligned_check(
+                               master, message, &xfer, alignment, gfp);
+                       if (ret)
+                               return ret;
+               }
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(spi_split_transfers_unaligned);
+
 /*-------------------------------------------------------------------------*/

 /* Core methods for SPI master protocol drivers.  Some of the
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 8ced84a..deb94a3 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -57,6 +57,12 @@ extern struct bus_type spi_bus_type;
  * @transfers_split_maxsize:
  *                 number of transfers that have been split because of
  *                 maxsize limit
+ * @transfers_split_unaligned:
+ *                 number of transfers that have been split because of
+ *                 alignment issues
+ * @transfers_split_unaligned_copy:
+ *                 number of transfers that have been aligned by copying
+ *                 at least one buffer (typically tx)
  */
 struct spi_statistics {
        spinlock_t              lock; /* lock for the whole structure */
@@ -78,6 +84,8 @@ struct spi_statistics {
        unsigned long transfer_bytes_histo[SPI_STATISTICS_HISTO_SIZE];

        unsigned long transfers_split_maxsize;
+       unsigned long transfers_split_unaligned;
+       unsigned long transfers_split_unaligned_copy;
 };

 void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
@@ -932,6 +940,11 @@ extern int spi_split_transfers_maxsize(struct spi_master 
*master,
                                       struct spi_message *msg,
                                       size_t maxsize,
                                       gfp_t gfp);
+extern int spi_split_transfers_unaligned(struct spi_master *master,
+                                        struct spi_message *msg,
+                                        size_t min_size,
+                                        size_t alignment,
+                                        gfp_t gfp);

 /*---------------------------------------------------------------------------*/

--
1.7.10.4

--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to