diff mbox

[v3,4/8] spi: core: added spi_split_transfers_unaligned

Message ID 1450106426-2277-5-git-send-email-kernel@martin.sperl.org (mailing list archive)
State New, archived
Headers show

Commit Message

Martin Sperl Dec. 14, 2015, 3:20 p.m. UTC
From: Martin Sperl <kernel@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 <kernel@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

--
1.7.10.4

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

Comments

Martin Sperl Dec. 16, 2015, 4:11 p.m. UTC | #1
> On 14.12.2015, at 16:20, kernel@martin.sperl.org wrote:
> +	/* the number of bytes by which we need to shift to align */
> +	alignment_shift = BIT(alignment) - alignment_to_correct;

This actually needs to be:
	alignment_shift = alignment - alignment_to_correct

> +
> +	/* calculate the values */
> +	alignment_mask = (1 << alignment) - 1;
This actually needs to be:
	 alignment_mask = alignment - 1

This was due to a misconception of @spi_master.dma_alignment...

I will wait for other feedback before creating v4 of the
patchset.

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

Patch

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);

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