[PATCH 2/3] spi: add initial set of spi_transfer transformation methods

kernel at martin.sperl.org kernel at martin.sperl.org
Mon Nov 30 05:04:53 PST 2015


From: Martin Sperl <kernel at martin.sperl.org>

added the following spi_transformation methods:
* spi_split_transfers_first_page_len_not_aligned -
  this splits spi_transfers that are not aligned on rx_buf/tx_buf
  this will create 2 or 3 transfers, where the first 1/2 transfers are
  just there to allow the last transfer to be fully aligned. These first
  transfers will have a length less than alignment.
* spi_split_transfers_maxsize
  this splits the spi_transfer into multiple independent spi_transfers
  all of which will be of size max_size or smaller.

To start these shall get used by the individual drivers in prepare_message,
but some may get moved into spi-core with the correct parametrization in
spi_master.

Signed-off-by: Martin Sperl <kernel at martin.sperl.org>
---
 drivers/spi/spi.c       |  392 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/spi/spi.h |   27 ++++
 2 files changed, 419 insertions(+)

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index eecbbe1..7576131 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -145,6 +145,9 @@ SPI_STATISTICS_TRANSFER_BYTES_HISTO(14, "16384-32767");
 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");
+
 static struct attribute *spi_dev_attrs[] = {
 	&dev_attr_modalias.attr,
 	NULL,
@@ -182,6 +185,8 @@ static struct attribute *spi_device_statistics_attrs[] = {
 	&dev_attr_spi_device_transfer_bytes_histo14.attr,
 	&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,
 	NULL,
 };
 
@@ -224,6 +229,8 @@ static struct attribute *spi_master_statistics_attrs[] = {
 	&dev_attr_spi_master_transfer_bytes_histo14.attr,
 	&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,
 	NULL,
 };
 
@@ -2103,6 +2110,391 @@ void spi_res_release(struct spi_master *master,
 	}
 }
 EXPORT_SYMBOL_GPL(spi_res_release);
+/*-------------------------------------------------------------------------*/
+
+/* Core methods for spi_message alterations */
+
+/* the spi_resource structure used */
+struct spi_res_replaced_transfers {
+	spi_res_release_t release;
+	struct list_head replaced_transfers;
+	int inserted;
+	struct spi_transfer xfers[];
+};
+
+static void __spi_replace_transfers_release(struct spi_master *master,
+					    struct spi_message *msg,
+					    void *res)
+{
+	struct spi_res_replaced_transfers *srt = res;
+	int i;
+
+	/* call extra callback */
+	if (srt->release)
+		srt->release(master, msg, res);
+
+	/* insert transfers back into the message ahead of xfers[0] */
+	list_splice(&srt->replaced_transfers, &srt->xfers[0].transfer_list);
+
+	/* remove the formerly inserted entries */
+	for (i = 0; i < srt->inserted; i++)
+		list_del(&srt->xfers[i].transfer_list);
+}
+
+/**
+ * spi_replace_transfers - replace transfers with several transfers
+ *                         and register change with spi_message.resources
+ * @msg: the spi_message we work upon
+ * @xfer: the spi_transfer we want to replace
+ * @remove: number of transfers to remove
+ * @insert: the number of transfers we want to insert
+ * @release: extra release code necessary in some circumstances
+ *
+ * Returns: pointer to array of newly inserted transfers,
+ *          NULL in case of errors
+ */
+struct spi_transfer *spi_replace_transfers(struct spi_message *msg,
+					   struct spi_transfer *xfer,
+					   int remove, int insert,
+					   spi_res_release_t release)
+{
+	int i;
+	size_t size = insert * sizeof(struct spi_transfer)
+		      + sizeof(struct spi_res_replaced_transfers);
+	struct spi_res_replaced_transfers *srt;
+	struct spi_transfer *next;
+
+	if (unlikely(insert < 1))
+		return NULL;
+
+	/* allocate the structure */
+	srt = spi_res_alloc(msg->spi, __spi_replace_transfers_release,
+			    size, 0);
+	if (unlikely(!srt))
+		return NULL;
+
+	/* the release code to use before running the generic release */
+	srt->release = release;
+
+	/* create copy of the given xfer with identical settings */
+	srt->inserted = insert;
+	for (i = 0; i < insert ; i++) {
+		/* copy all settings */
+		memcpy(&srt->xfers[i], xfer, sizeof(*xfer));
+
+		/* for anything but the last transfer, clear some settings */
+		if (i < insert - 1) {
+			srt->xfers[i].cs_change = 0;
+			srt->xfers[i].delay_usecs = 0;
+		}
+
+		/* add before the xfer to remove itself */
+		list_add_tail(&srt->xfers[i].transfer_list,
+			      &xfer->transfer_list);
+	}
+
+	/* remove the requested number of transfers */
+	for (i = 0; i < remove; i++, xfer = next) {
+		/* check for error case - we want to remove list_head...*/
+		if (&xfer->transfer_list == &msg->transfers) {
+			dev_err(&msg->spi->dev,
+				"requested to remove more spi_transfers than are available\n");
+			spi_res_free(srt);
+			return NULL;
+		}
+		/* get the next xfer for later */
+		next = list_next_entry(xfer, transfer_list);
+
+		/* and remove the current transfer from the list of transfers */
+		list_del_init(&xfer->transfer_list);
+
+		/* and add it to the list in spi_res_replaced_transfers */
+		INIT_LIST_HEAD(&srt->replaced_transfers);
+		list_add(&xfer->transfer_list, &srt->replaced_transfers);
+	}
+
+	/* and register it */
+	spi_res_add(msg, srt);
+
+	/* return the head of the list */
+	return &srt->xfers[0];
+}
+EXPORT_SYMBOL_GPL(spi_replace_transfers);
+
+/* core spi_transfer transformation */
+
+static void __spi_split_transfers_fixup_transfer_addr_and_len(
+	struct spi_transfer *xfer, int count, int shift)
+{
+	int i;
+
+	/* fix up transfer length */
+	xfer[0].len = shift;
+
+	/* shift all the addresses arround */
+	for (i = 1; i < count; i++) {
+		xfer[i].len -= shift;
+		xfer[i].tx_buf += xfer[i].tx_buf ? shift : 0;
+		xfer[i].rx_buf += xfer[i].rx_buf ? shift : 0;
+		xfer[i].tx_dma += xfer[i].tx_dma ? shift : 0;
+		xfer[i].rx_dma += xfer[i].rx_dma ? shift : 0;
+	}
+}
+
+static int __spi_split_transfers_first_page_len_not_aligned(
+	struct spi_master *master,
+	struct spi_message *message,
+	struct spi_transfer *xfer,
+	size_t alignment_mask)
+{
+	int count;
+	struct spi_transfer *xfers;
+	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 necessary values */
+	tx_start = xfer->tx_buf;
+	tx_start_page = (size_t)tx_start & (PAGE_SIZE - 1);
+	tx_start_align = ((size_t)tx_start & alignment_mask);
+	tx_end = tx_start ? &tx_start[xfer->len - 1] : NULL;
+	tx_end_page = (size_t)tx_end & (PAGE_SIZE - 1);
+
+	rx_start = xfer->rx_buf;
+	rx_start_page = (size_t)rx_start & (PAGE_SIZE - 1);
+	rx_start_align = ((size_t)rx_start & alignment_mask);
+	rx_end = rx_start ? &tx_start[xfer->len - 1] : NULL;
+	rx_end_page = (size_t)rx_end & (PAGE_SIZE - 1);
+
+	/* if we do not cross a page for either rx or tx,
+	 * then there is nothing to do...
+	 */
+	if ((tx_start_page == tx_end_page) &&
+	    (rx_start_page == rx_end_page))
+		return 0;
+
+	/* calculate how many transfers we need to replace the current */
+	count = 1;
+	if (rx_start_align)
+		count++;
+	if ((tx_start_align) &&
+	    (tx_start_align != rx_start_align) &&
+	    (tx_start != rx_start))
+		count++;
+
+	/* send a one-time warning */
+	dev_warn_once(&message->spi->dev,
+		      "unaligned spi_transfers produced by spi_device driver - please fix driver\n");
+
+	/* create replacement */
+	xfers = spi_replace_transfers(message, xfer, 1, count, NULL);
+	if (!xfers)
+		return -ENOMEM;
+
+	/* now we fix up the transfer pointer and transfer len */
+	if (count == 2) {
+		__spi_split_transfers_fixup_transfer_addr_and_len(
+			xfers, 2, max(tx_start_align, rx_start_align));
+	} else {
+		if (tx_start_align < rx_start_align) {
+			__spi_split_transfers_fixup_transfer_addr_and_len(
+				&xfers[0], 3,
+				tx_start_align);
+			__spi_split_transfers_fixup_transfer_addr_and_len(
+				&xfers[1], 2,
+				rx_start_align - tx_start_align);
+		} else {
+			__spi_split_transfers_fixup_transfer_addr_and_len(
+				&xfers[0], 3,
+				rx_start_align);
+			__spi_split_transfers_fixup_transfer_addr_and_len(
+				&xfers[1], 2,
+				tx_start_align - rx_start_align);
+		}
+	}
+
+	/* increment statistics counters */
+	SPI_STATISTICS_INCREMENT_FIELD(&master->statistics,
+				       transfers_split_unaligned);
+	SPI_STATISTICS_INCREMENT_FIELD(&message->spi->statistics,
+				       transfers_split_unaligned);
+
+	return 0;
+}
+
+/**
+ * spi_split_tranfers_first_page_len_not_aligned - split spi transfers into
+ *                                                 two transfers on the
+ *                                                 first page boundary, when
+ *                                                 the start address is not
+ *                                                 aligned
+ * @master:       the @spi_master for this transfer
+ * @message:      the @spi_message to transform
+ * @when_can_dma: true if we only should execute when we can use dma
+ * @alignment:    the requested alignment
+ * @min_size:     the minimum transfer when to apply this
+ *
+ * Return: status of transformation
+ *
+ * This implements one of the ways that (dma-enabled) HW may be constrained
+ * in this case the HW is able to do unaligned transfers, but a DMA-transfer
+ * is always of size align from a register perspective (even if it only
+ * writes < align bytes back to ram).
+ * As there is no guarantee that the next logical page is actually adjectant
+ * for the dma perspective, we need to split the first page of a transfer
+ * into separate transfers (1 or 2 depending on alignment of rx_buf and
+ * tx_buf respectively).
+ * this does not make sure that the individual transfers that are
+ * added are address, aligned, but it makes sure that each of those
+ * new transfers.len is < alignment.
+ *
+ */
+int spi_split_transfers_first_page_len_not_aligned(
+	struct spi_master *master,
+	struct spi_message *message,
+	bool when_can_dma,
+	size_t alignment)
+{
+	struct spi_device *spi = message->spi;
+	struct spi_transfer *_xfer, *xfer = NULL;
+	size_t alignment_mask = alignment - 1;
+	int ret;
+
+	/* if when_can_dma is set, but can_dma is unset,
+	 * something major is wrong...
+	 */
+	if (when_can_dma && (!master->can_dma)) {
+		dev_err(&master->dev,
+			"configured without can_dma, but requests can_dma to be set calling first_page_len_not_aligned\n");
+		return -EINVAL;
+	}
+
+	/* iterate over the transfer_list in a safe manner
+	 * there is a posibility of replacement and we need to make sure
+	 * we run over all the entries
+	 */
+	xfer = list_prepare_entry(xfer, &message->transfers, transfer_list);
+	list_for_each_entry_safe_continue(xfer, _xfer,
+					  &message->transfers,
+					  transfer_list) {
+		if (when_can_dma && (!master->can_dma(master, spi, xfer)))
+			continue;
+		if (((size_t)xfer->rx_buf & alignment_mask) ||
+		    ((size_t)xfer->tx_buf & alignment_mask)) {
+			ret = __spi_split_transfers_first_page_len_not_aligned(
+				master, message, xfer, alignment_mask);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(spi_split_transfers_first_page_len_not_aligned);
+
+int __spi_split_transfer_maxsize(struct spi_master *master,
+				 struct spi_message *msg,
+				 struct spi_transfer *xfer,
+				 size_t maxsize)
+{
+	int count = DIV_ROUND_UP(xfer->len, maxsize);
+	size_t offset = 0;
+	int i;
+	struct spi_transfer *xfers;
+
+	/* create replacement */
+	xfers = spi_replace_transfers(msg, xfer, 1, count, NULL);
+	if (!xfers)
+		return -ENOMEM;
+
+	/* now handle each of those newly inserted xfers */
+	for (i = 0; i < count; i++) {
+		/* the first transfer just needs the length modified,
+		 * so do not change the pointers
+		 */
+		if (i) {
+			/* update rx_buf if it is not tx_buf */
+			if (xfers[i].rx_buf &&
+			    (xfers[i].tx_buf != xfers[i].rx_buf)) {
+				/* modify the rx_buf */
+				xfers[i].rx_buf += offset;
+				/* this is actually not used with any
+				 * scatter_lists, but it is still there,
+				 * so we have to support it
+				 */
+				if (xfers[i].rx_dma)
+					xfers[i].rx_dma += offset;
+			}
+			/* update tx_buf */
+			if (xfers[i].tx_buf) {
+				/* modify the rx_buf */
+				xfers[i].tx_buf += offset;
+				/* this is actually not used with any
+				 * scatter_lists, but it is still there,
+				 * so we have to support it...
+				 */
+				if (xfers[i].tx_dma)
+					xfers[i].tx_dma += offset;
+			}
+		}
+		/* modify length */
+		if (i < count - 1) /* for all but the last transfer */
+			xfers[i].len = maxsize;
+		else /* the last one is most likley not max_size */
+			xfers[i].len -= offset;
+
+		/* increment offset */
+		offset += maxsize;
+	}
+
+	/* increment statistics counters */
+	SPI_STATISTICS_INCREMENT_FIELD(&master->statistics,
+				       transfers_split_maxsize);
+	SPI_STATISTICS_INCREMENT_FIELD(&msg->spi->statistics,
+				       transfers_split_maxsize);
+
+	return 0;
+}
+
+/**
+ * spi_split_tranfers_maxsize - split spi transfers into multiple transfers
+ *                              when an individual transfer exceeds a
+ *                              certain size
+ * @master:    the @spi_master for this transfer
+ * @message:   the @spi_message to transform
+ * @max_size:  the maximum when to apply this
+ *
+ * Return: status of transformation
+ */
+
+int spi_split_transfers_maxsize(struct spi_master *master,
+				struct spi_message *msg,
+				size_t maxsize)
+{
+	struct spi_transfer *_xfer, *xfer = NULL;
+	int ret;
+
+	/* iterate over the transfer_list in a safe manner
+	 * there is a posibility of replacement and we need to make sure
+	 * we run over all the entries
+	 */
+	xfer = list_prepare_entry(xfer, &msg->transfers, transfer_list);
+	list_for_each_entry_safe_continue(xfer, _xfer,
+					  &msg->transfers,
+					  transfer_list) {
+		if (xfer->len > maxsize) {
+			ret = __spi_split_transfer_maxsize(
+				master, msg, xfer, maxsize);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(spi_split_transfers_maxsize);
 
 /*-------------------------------------------------------------------------*/
 
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 7e74e0e..8075c93 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -53,6 +53,11 @@ extern struct bus_type spi_bus_type;
  *
  * @transfer_bytes_histo:
  *                 transfer bytes histogramm
+ *
+ * @transfers_split_maxsize:
+ *                 number of transfers split because of len exceeds maxsize
+ * @transfers_split_unaligned:
+ *                 number of transfers split because of unaligned transfers
  */
 struct spi_statistics {
 	spinlock_t		lock; /* lock for the whole structure */
@@ -72,6 +77,10 @@ struct spi_statistics {
 
 #define SPI_STATISTICS_HISTO_SIZE 17
 	unsigned long transfer_bytes_histo[SPI_STATISTICS_HISTO_SIZE];
+
+	unsigned long           transfers_split_maxsize;
+	unsigned long           transfers_split_unaligned;
+
 };
 
 void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
@@ -607,6 +616,24 @@ extern void spi_res_free(void *res);
 extern void spi_res_release(struct spi_master *master,
 			    struct spi_message *message);
 
+/* SPI transfer modifications using spi_res */
+
+extern struct spi_transfer *spi_replace_transfers(
+	struct spi_message *msg,
+	struct spi_transfer *xfer,
+	int remove, int insert,
+	spi_res_release_t release);
+
+extern int spi_split_transfers_first_page_len_not_aligned(
+	struct spi_master *master,
+	struct spi_message *message,
+	bool when_can_dma,
+	size_t alignment);
+
+extern int spi_split_transfers_maxsize(struct spi_master *master,
+				       struct spi_message *msg,
+				       size_t maxsize);
+
 /*---------------------------------------------------------------------------*/
 
 /*
-- 
1.7.10.4




More information about the linux-rpi-kernel mailing list