[PATCH v3 4/8] spi: core: added spi_split_transfers_unaligned
kernel at martin.sperl.org
kernel at martin.sperl.org
Mon Dec 14 07:20:21 PST 2015
From: Martin Sperl <kernel at 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 at 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
More information about the linux-rpi-kernel
mailing list