* [PATCH v3 1/8] spi: core: added spi_resource management
[not found] ` <1450106426-2277-1-git-send-email-kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
@ 2015-12-14 15:20 ` kernel-TqfNSX0MhmxHKSADF0wUEw
2015-12-14 15:20 ` [PATCH v3 2/8] spi: core: add spi_replace_transfers method kernel-TqfNSX0MhmxHKSADF0wUEw
` (6 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: kernel-TqfNSX0MhmxHKSADF0wUEw @ 2015-12-14 15:20 UTC (permalink / raw)
To: Mark Brown, Stephen Warren, Lee Jones, Eric Anholt,
linux-spi-u79uwXL29TY76Z2rM5mHXA,
linux-rpi-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
Cc: Martin Sperl
From: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
SPI resource management framework used while processing a spi_message
via the spi-core.
The basic idea is taken from devres, but as the allocation may happen
fairly frequently, some provisioning (in the form of an unused spi_device
pointer argument to spi_res_alloc) has been made so that at a later stage
we may implement reuse objects allocated earlier avoiding the repeated
allocation by keeping a cache of objects that we can reuse.
This framework can get used for:
* rewriting spi_messages
* to fullfill alignment requirements of the spi_master HW
* to fullfill transfer length requirements
(e.g: transfers need to be less than 64k)
* consolidate spi_messages with multiple transfers into a single transfer
when the total transfer length is below a threshold.
* reimplement spi_unmap_buf without explicitly needing to check if it has
been mapped
Signed-off-by: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
---
drivers/spi/spi.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++
include/linux/spi/spi.h | 36 +++++++++++++++++++
2 files changed, 127 insertions(+)
Changelog:
V2 -> V3: contains several fixes based on feedback by Andy Shevchenko
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index d004a3c..8149602 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -1025,6 +1025,8 @@ out:
if (msg->status && master->handle_err)
master->handle_err(master, msg);
+ spi_res_release(master, msg);
+
spi_finalize_current_message(master);
return ret;
@@ -2011,6 +2013,95 @@ struct spi_master *spi_busnum_to_master(u16 bus_num)
}
EXPORT_SYMBOL_GPL(spi_busnum_to_master);
+/*-------------------------------------------------------------------------*/
+
+/* Core methods for SPI resource management */
+
+/**
+ * spi_res_alloc - allocate a spi resource that is life-cycle managed
+ * during the processing of a spi_message while using
+ * spi_transfer_one
+ * @spi: the spi device for which we allocate memory
+ * @release: the release code to execute for this resource
+ * @size: size to alloc and return
+ * @gfp: GFP allocation flags
+ *
+ * Return: the pointer to the allocated data
+ *
+ * This may get enhanced in the future to allocate from a memory pool
+ * of the @spi_device or @spi_master to avoid repeated allocations.
+ */
+void *spi_res_alloc(struct spi_device *spi,
+ spi_res_release_t release,
+ size_t size, gfp_t gfp)
+{
+ struct spi_res *sres;
+
+ sres = kzalloc(sizeof(*sres) + size, gfp);
+ if (!sres)
+ return NULL;
+
+ INIT_LIST_HEAD(&sres->entry);
+ sres->release = release;
+
+ return sres->data;
+}
+EXPORT_SYMBOL_GPL(spi_res_alloc);
+
+/**
+ * spi_res_free - free an spi resource
+ * @res: pointer to the custom data of a resource
+ *
+ */
+void spi_res_free(void *res)
+{
+ struct spi_res *sres = container_of(res, struct spi_res, data);
+
+ if (!res)
+ return;
+
+ WARN_ON(!list_empty(&sres->entry));
+ kfree(sres);
+}
+EXPORT_SYMBOL_GPL(spi_res_free);
+
+/**
+ * spi_res_add - add a spi_res to the spi_message
+ * @message: the spi message
+ * @res: the spi_resource
+ */
+void spi_res_add(struct spi_message *message, void *res)
+{
+ struct spi_res *sres = container_of(res, struct spi_res, data);
+
+ WARN_ON(!list_empty(&sres->entry));
+ list_add_tail(&sres->entry, &message->resources);
+}
+EXPORT_SYMBOL_GPL(spi_res_add);
+
+/**
+ * spi_res_release - release all spi resources for this message
+ * @master: the @spi_master
+ * @message: the @spi_message
+ */
+void spi_res_release(struct spi_master *master,
+ struct spi_message *message)
+{
+ struct spi_res *res;
+
+ while (!list_empty(&message->resources)) {
+ res = list_last_entry(&message->resources,
+ struct spi_res, entry);
+
+ if (res->release)
+ res->release(master, message, res->data);
+
+ list_del(&res->entry);
+
+ kfree(res);
+ }
+}
+EXPORT_SYMBOL_GPL(spi_res_release);
/*-------------------------------------------------------------------------*/
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 9ea73eb..739a476 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -576,6 +576,37 @@ extern void spi_unregister_master(struct spi_master *master);
extern struct spi_master *spi_busnum_to_master(u16 busnum);
+/*
+ * SPI resource management while processing a SPI message
+ */
+
+/**
+ * struct spi_res - spi resource management structure
+ * @entry: list entry
+ * @release: release code called prior to freeing this resource
+ * @data: extra data allocated for the specific use-case
+ *
+ * this is based on ideas from devres, but focused on life-cycle
+ * management during spi_message processing
+ */
+typedef void (*spi_res_release_t)(struct spi_master *master,
+ struct spi_message *msg,
+ void *res);
+struct spi_res {
+ struct list_head entry;
+ spi_res_release_t release;
+ unsigned long long data[]; /* guarantee ull alignment */
+};
+
+extern void *spi_res_alloc(struct spi_device *spi,
+ spi_res_release_t release,
+ size_t size, gfp_t gfp);
+extern void spi_res_add(struct spi_message *message, void *res);
+extern void spi_res_free(void *res);
+
+extern void spi_res_release(struct spi_master *master,
+ struct spi_message *message);
+
/*---------------------------------------------------------------------------*/
/*
@@ -714,6 +745,7 @@ struct spi_transfer {
* @status: zero for success, else negative errno
* @queue: for use by whichever driver currently owns the message
* @state: for use by whichever driver currently owns the message
+ * @resources: for resource management when the spi message is processed
*
* A @spi_message is used to execute an atomic sequence of data transfers,
* each represented by a struct spi_transfer. The sequence is "atomic"
@@ -760,11 +792,15 @@ struct spi_message {
*/
struct list_head queue;
void *state;
+
+ /* list of spi_res reources when the spi message is processed */
+ struct list_head resources;
};
static inline void spi_message_init_no_memset(struct spi_message *m)
{
INIT_LIST_HEAD(&m->transfers);
+ INIT_LIST_HEAD(&m->resources);
}
static inline void spi_message_init(struct spi_message *m)
--
1.7.10.4
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 2/8] spi: core: add spi_replace_transfers method
[not found] ` <1450106426-2277-1-git-send-email-kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
2015-12-14 15:20 ` [PATCH v3 1/8] spi: core: added spi_resource management kernel-TqfNSX0MhmxHKSADF0wUEw
@ 2015-12-14 15:20 ` kernel-TqfNSX0MhmxHKSADF0wUEw
2015-12-14 15:20 ` [PATCH v3 3/8] spi: core: add spi_split_transfers_maxsize kernel-TqfNSX0MhmxHKSADF0wUEw
` (5 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: kernel-TqfNSX0MhmxHKSADF0wUEw @ 2015-12-14 15:20 UTC (permalink / raw)
To: Mark Brown, Stephen Warren, Lee Jones, Eric Anholt,
linux-spi-u79uwXL29TY76Z2rM5mHXA,
linux-rpi-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
Cc: Martin Sperl
From: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
Add the spi_replace_transfers method that can get used
to replace some spi_transfers from a spi_message with other
transfers.
Signed-off-by: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
---
drivers/spi/spi.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++
include/linux/spi/spi.h | 45 ++++++++++++++++
2 files changed, 177 insertions(+)
Changelog:
V2 -> V3: new implementation of spi_replace_transfers
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 8149602..ede3e57 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -2105,6 +2105,138 @@ EXPORT_SYMBOL_GPL(spi_res_release);
/*-------------------------------------------------------------------------*/
+/* Core methods for spi_message alterations */
+
+static void __spi_replace_transfers_release(struct spi_master *master,
+ struct spi_message *msg,
+ void *res)
+{
+ struct spi_replaced_transfers *rxfer = res;
+ size_t i;
+
+ /* call extra callback if requested */
+ if (rxfer->release)
+ rxfer->release(master, msg, res);
+
+ /* insert replaced transfers back into the message */
+ list_splice(&rxfer->replaced_transfers, rxfer->replaced_after);
+
+ /* remove the formerly inserted entries */
+ for (i = 0; i < rxfer->inserted; i++)
+ list_del(&rxfer->inserted_transfers[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_first: the first spi_transfer we want to replace
+ * @remove: number of transfers to remove
+ * @insert: the number of transfers we want to insert instead
+ * @release: extra release code necessary in some circumstances
+ * @extradatasize: extra data to allocate (with alignment guarantees
+ * of struct @spi_transfer)
+ *
+ * Returns: pointer to @spi_replaced_transfers,
+ * PTR_ERR(...) in case of errors.
+ */
+struct spi_replaced_transfers *spi_replace_transfers(
+ struct spi_message *msg,
+ struct spi_transfer *xfer_first,
+ size_t remove,
+ size_t insert,
+ spi_replaced_release_t release,
+ size_t extradatasize,
+ gfp_t gfp)
+{
+ struct spi_replaced_transfers *rxfer;
+ struct spi_transfer *xfer;
+ size_t i;
+
+ /* allocate the structure using spi_res */
+ rxfer = spi_res_alloc(msg->spi, __spi_replace_transfers_release,
+ insert * sizeof(struct spi_transfer)
+ + sizeof(struct spi_replaced_transfers)
+ + extradatasize,
+ gfp);
+ if (!rxfer)
+ return ERR_PTR(-ENOMEM);
+
+ /* the release code to invoke before running the generic release */
+ rxfer->release = release;
+
+ /* assign extradata */
+ if (extradatasize)
+ rxfer->extradata =
+ &rxfer->inserted_transfers[insert];
+
+ /* init the replaced_transfers list */
+ INIT_LIST_HEAD(&rxfer->replaced_transfers);
+
+ /* assign the list_entry after which we should reinsert
+ * the @replaced_transfers - it may be spi_message.messages!
+ */
+ rxfer->replaced_after = xfer_first->transfer_list.prev;
+
+ /* remove the requested number of transfers */
+ for (i = 0; i < remove; i++) {
+ /* if the entry after replaced_after it is msg->transfers
+ * then we have been requested to remove more transfers
+ * than are in the list
+ */
+ if (rxfer->replaced_after->next == &msg->transfers) {
+ dev_err(&msg->spi->dev,
+ "requested to remove more spi_transfers than are available\n");
+ /* insert replaced transfers back into the message */
+ list_splice(&rxfer->replaced_transfers,
+ rxfer->replaced_after);
+
+ /* free the spi_replace_transfer structure */
+ spi_res_free(rxfer);
+
+ /* and return with an error */
+ return ERR_PTR(-EINVAL);
+ }
+
+ /* remove the entry after replaced_after from list of
+ * transfers and add it to list of replaced_transfers
+ */
+ list_move_tail(rxfer->replaced_after->next,
+ &rxfer->replaced_transfers);
+ }
+
+ /* create copy of the given xfer with identical settings
+ * based on the first transfer to get removed
+ */
+ for (i = 0; i < insert; i++) {
+ /* we need to run in reverse order */
+ xfer = &rxfer->inserted_transfers[insert - 1 - i];
+
+ /* copy all spi_transfer data */
+ memcpy(xfer, xfer_first, sizeof(*xfer));
+
+ /* add to list */
+ list_add(&xfer->transfer_list, rxfer->replaced_after);
+
+ /* clear cs_change and delay_usecs for all but the last */
+ if (i) {
+ xfer->cs_change = false;
+ xfer->delay_usecs = 0;
+ }
+ }
+
+ /* set up inserted */
+ rxfer->inserted = insert;
+
+ /* and register it with spi_res/spi_message */
+ spi_res_add(msg, rxfer);
+
+ return rxfer;
+}
+EXPORT_SYMBOL_GPL(spi_replace_transfers);
+
+/*-------------------------------------------------------------------------*/
+
/* Core methods for SPI master protocol drivers. Some of the
* other core methods are currently defined as inline functions.
*/
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 739a476..13831ed 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -875,6 +875,51 @@ extern int spi_async_locked(struct spi_device *spi,
/*---------------------------------------------------------------------------*/
+/* SPI transfer replacement methods which make use of spi_res */
+
+/**
+ * struct spi_replaced_transfers - structure describing the spi_transfer
+ * replacements that have occurred
+ * so that they can get reverted
+ * @release: some extra release code to get executed prior to
+ * relasing this structure
+ * @extradata: pointer to some extra data if requested or NULL
+ * @replaced_transfers: transfers that have been replaced and which need
+ * to get restored
+ * @replaced_after: the transfer after which the @replaced_transfers
+ * are to get re-inserted
+ * @inserted: number of transfers inserted
+ * @inserted_transfers: array of spi_transfers of array-size @inserted,
+ * that have been replacing replaced_transfers
+ *
+ * note: that @extradata will point to @inserted_transfers[@inserted]
+ * if some extra allocation is requested, so alignment will be the same
+ * as for spi_transfers
+ */
+struct spi_replaced_transfers;
+typedef void (*spi_replaced_release_t)(struct spi_master *master,
+ struct spi_message *msg,
+ struct spi_replaced_transfers *res);
+struct spi_replaced_transfers {
+ spi_replaced_release_t release;
+ void *extradata;
+ struct list_head replaced_transfers;
+ struct list_head *replaced_after;
+ size_t inserted;
+ struct spi_transfer inserted_transfers[];
+};
+
+extern struct spi_replaced_transfers *spi_replace_transfers(
+ struct spi_message *msg,
+ struct spi_transfer *xfer_first,
+ size_t remove,
+ size_t insert,
+ spi_replaced_release_t release,
+ size_t extradatasize,
+ gfp_t gfp);
+
+/*---------------------------------------------------------------------------*/
+
/* All these synchronous SPI transfer routines are utilities layered
* over the core async transfer primitive. Here, "synchronous" means
* they will sleep uninterruptibly until the async transfer completes.
--
1.7.10.4
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 3/8] spi: core: add spi_split_transfers_maxsize
[not found] ` <1450106426-2277-1-git-send-email-kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
2015-12-14 15:20 ` [PATCH v3 1/8] spi: core: added spi_resource management kernel-TqfNSX0MhmxHKSADF0wUEw
2015-12-14 15:20 ` [PATCH v3 2/8] spi: core: add spi_replace_transfers method kernel-TqfNSX0MhmxHKSADF0wUEw
@ 2015-12-14 15:20 ` kernel-TqfNSX0MhmxHKSADF0wUEw
[not found] ` <1450106426-2277-4-git-send-email-kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
2015-12-14 15:20 ` [PATCH v3 4/8] spi: core: added spi_split_transfers_unaligned kernel-TqfNSX0MhmxHKSADF0wUEw
` (4 subsequent siblings)
7 siblings, 1 reply; 11+ messages in thread
From: kernel-TqfNSX0MhmxHKSADF0wUEw @ 2015-12-14 15:20 UTC (permalink / raw)
To: Mark Brown, Stephen Warren, Lee Jones, Eric Anholt,
linux-spi-u79uwXL29TY76Z2rM5mHXA,
linux-rpi-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
Cc: Martin Sperl
From: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
Add spi_split_transfers_maxsize method that splits
spi_transfers transparently into multiple transfers
that are below the given max-size.
This makes use of the spi_res framework via
spi_replace_transfers to allocate/free the extra
transfers as well as reverting back the changes applied
while processing the spi_message.
Signed-off-by: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
---
drivers/spi/spi.c | 111 +++++++++++++++++++++++++++++++++++++++++++++++
include/linux/spi/spi.h | 15 +++++++
2 files changed, 126 insertions(+)
Changelog:
V2 -> V3: split into distinct patches and rewrite
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index ede3e57..37e6507 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -145,6 +145,8 @@ 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");
+
static struct attribute *spi_dev_attrs[] = {
&dev_attr_modalias.attr,
NULL,
@@ -182,6 +184,7 @@ 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,
NULL,
};
@@ -224,6 +227,7 @@ 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,
NULL,
};
@@ -2235,6 +2239,113 @@ struct spi_replaced_transfers *spi_replace_transfers(
}
EXPORT_SYMBOL_GPL(spi_replace_transfers);
+int __spi_split_transfer_maxsize(struct spi_master *master,
+ struct spi_message *msg,
+ struct spi_transfer **xferp,
+ size_t maxsize,
+ gfp_t gfp)
+{
+ struct spi_transfer *xfer = *xferp, *xfers;
+ struct spi_replaced_transfers *srt;
+ size_t offset;
+ size_t count, i;
+
+ /* warn once about this fact that we are splitting a transfer */
+ dev_warn_once(&msg->spi->dev,
+ "spi_transfer of length %i exceed max length of %i - needed to split transfers\n",
+ xfer->len, maxsize);
+
+ /* calculate how many we have to replace */
+ count = DIV_ROUND_UP(xfer->len, maxsize);
+
+ /* create replacement */
+ srt = spi_replace_transfers(msg, xfer, 1, count, NULL, 0, gfp);
+ if (!srt)
+ return -ENOMEM;
+ xfers = srt->inserted_transfers;
+
+ /* now handle each of those newly inserted spi_transfers
+ * note that the replacements spi_transfers all are preset
+ * to the same values as *xferp, so tx_buf, rx_buf and len
+ * are all identical (as well as most others)
+ * so we just have to fix up len and the pointers.
+ *
+ * this also includes support for the depreciated
+ * spi_message.is_dma_mapped interface
+ */
+
+ /* the first transfer just needs the length modified, so we
+ * run it outside the loop
+ */
+ xfers[0].len = min(maxsize, xfer[0].len);
+
+ /* all the others need rx_buf/tx_buf also set */
+ for (i = 1, offset = maxsize; i < count; offset += maxsize, i++) {
+ /* update rx_buf, tx_buf and dma */
+ if (xfers[i].rx_buf)
+ xfers[i].rx_buf += offset;
+ if (xfers[i].rx_dma)
+ xfers[i].rx_dma += offset;
+ if (xfers[i].tx_buf)
+ xfers[i].tx_buf += offset;
+ if (xfers[i].tx_dma)
+ xfers[i].tx_dma += offset;
+
+ /* update length */
+ xfers[i].len = min(maxsize, xfers[i].len - offset);
+ }
+
+ /* we set up xferp to the last entry we have inserted,
+ * so that we skip those already split transfers
+ */
+ *xferp = &xfers[count - 1];
+
+ /* 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,
+ 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, &msg->transfers, transfer_list) {
+ if (xfer->len > maxsize) {
+ ret = __spi_split_transfer_maxsize(
+ master, msg, &xfer, maxsize, gfp);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spi_split_transfers_maxsize);
+
/*-------------------------------------------------------------------------*/
/* 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 13831ed..8ced84a 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -53,6 +53,10 @@ extern struct bus_type spi_bus_type;
*
* @transfer_bytes_histo:
* transfer bytes histogramm
+ *
+ * @transfers_split_maxsize:
+ * number of transfers that have been split because of
+ * maxsize limit
*/
struct spi_statistics {
spinlock_t lock; /* lock for the whole structure */
@@ -72,6 +76,8 @@ struct spi_statistics {
#define SPI_STATISTICS_HISTO_SIZE 17
unsigned long transfer_bytes_histo[SPI_STATISTICS_HISTO_SIZE];
+
+ unsigned long transfers_split_maxsize;
};
void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
@@ -920,6 +926,15 @@ extern struct spi_replaced_transfers *spi_replace_transfers(
/*---------------------------------------------------------------------------*/
+/* SPI transfer transformation methods */
+
+extern int spi_split_transfers_maxsize(struct spi_master *master,
+ struct spi_message *msg,
+ size_t maxsize,
+ gfp_t gfp);
+
+/*---------------------------------------------------------------------------*/
+
/* All these synchronous SPI transfer routines are utilities layered
* over the core async transfer primitive. Here, "synchronous" means
* they will sleep uninterruptibly until the async transfer completes.
--
1.7.10.4
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 4/8] spi: core: added spi_split_transfers_unaligned
[not found] ` <1450106426-2277-1-git-send-email-kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
` (2 preceding siblings ...)
2015-12-14 15:20 ` [PATCH v3 3/8] spi: core: add spi_split_transfers_maxsize kernel-TqfNSX0MhmxHKSADF0wUEw
@ 2015-12-14 15:20 ` kernel-TqfNSX0MhmxHKSADF0wUEw
[not found] ` <1450106426-2277-5-git-send-email-kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
2015-12-14 15:20 ` [PATCH v3 5/8] spi: core: add spi_merge_transfers method kernel-TqfNSX0MhmxHKSADF0wUEw
` (3 subsequent siblings)
7 siblings, 1 reply; 11+ messages in thread
From: kernel-TqfNSX0MhmxHKSADF0wUEw @ 2015-12-14 15:20 UTC (permalink / raw)
To: Mark Brown, Stephen Warren, Lee Jones, Eric Anholt,
linux-spi-u79uwXL29TY76Z2rM5mHXA,
linux-rpi-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
Cc: Martin Sperl
From: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.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-TqfNSX0MhmxHKSADF0wUEw@public.gmane.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 majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 5/8] spi: core: add spi_merge_transfers method
[not found] ` <1450106426-2277-1-git-send-email-kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
` (3 preceding siblings ...)
2015-12-14 15:20 ` [PATCH v3 4/8] spi: core: added spi_split_transfers_unaligned kernel-TqfNSX0MhmxHKSADF0wUEw
@ 2015-12-14 15:20 ` kernel-TqfNSX0MhmxHKSADF0wUEw
2015-12-14 15:20 ` [PATCH v3 6/8] spi: core: add spi_master.translate_message kernel-TqfNSX0MhmxHKSADF0wUEw
` (2 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: kernel-TqfNSX0MhmxHKSADF0wUEw @ 2015-12-14 15:20 UTC (permalink / raw)
To: Mark Brown, Stephen Warren, Lee Jones, Eric Anholt,
linux-spi-u79uwXL29TY76Z2rM5mHXA,
linux-rpi-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
Cc: Martin Sperl
From: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
Added method spi_merge_transfers which consolidates multiple
spi_transfers into a single spi_transfer making use of
spi_res for management of resources/reverting changes to the
spi_message structure.
Signed-off-by: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
---
drivers/spi/spi.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++
include/linux/spi/spi.h | 9 ++
2 files changed, 236 insertions(+)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index f276c99..fe3b18e 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -148,6 +148,7 @@ 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");
+SPI_STATISTICS_SHOW(transfers_merged, "%lu");
static struct attribute *spi_dev_attrs[] = {
&dev_attr_modalias.attr,
@@ -189,6 +190,7 @@ static struct attribute *spi_device_statistics_attrs[] = {
&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,
+ &dev_attr_spi_device_transfers_merged.attr,
NULL,
};
@@ -234,6 +236,7 @@ static struct attribute *spi_master_statistics_attrs[] = {
&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,
+ &dev_attr_spi_master_transfers_merged.attr,
NULL,
};
@@ -2552,6 +2555,230 @@ int spi_split_transfers_unaligned(struct spi_master *master,
}
EXPORT_SYMBOL_GPL(spi_split_transfers_unaligned);
+static void __spi_merge_transfers_release(struct spi_master *master,
+ struct spi_message *msg,
+ struct spi_replaced_transfers *srt)
+{
+ u8 *ptr = srt->inserted_transfers[0].rx_buf;
+ struct spi_transfer *xfer;
+
+ /* copy the data to the final locations */
+ list_for_each_entry(xfer, &srt->replaced_transfers, transfer_list) {
+ /* fill data only if rx_buf is set */
+ if (xfer->rx_buf)
+ memcpy(xfer->rx_buf, ptr, xfer->len);
+ /* update the pointer for next loop */
+ ptr += xfer->len;
+ }
+}
+
+static int __spi_merge_transfers_do(struct spi_master *master,
+ struct spi_message *msg,
+ struct spi_transfer **xfer_start,
+ struct spi_transfer *xfer_end,
+ int count,
+ size_t size,
+ gfp_t gfp)
+{
+ size_t align = master->dma_alignment ? : sizeof(int);
+ size_t align_overhead = BIT(align) - 1;
+ size_t size_to_alloc = size + align_overhead;
+ struct spi_transfer *xfer_new, *xfer;
+ struct spi_replaced_transfers *srt;
+ u8 *data_tx;
+ int i;
+
+ /* for anything but 3-wire mode we need rx/tx_buf to be set */
+ if (!(msg->spi->mode & SPI_3WIRE))
+ size_to_alloc *= 2;
+
+ /* replace count transfers starting with xfer_start
+ * and replace them with a single transfer (which is returned)
+ * the extra data requeste starts at:
+ * returned pointer + sizeof(struct_transfer)
+ */
+ srt = spi_replace_transfers(msg, *xfer_start, count, 1,
+ __spi_merge_transfers_release,
+ size_to_alloc, gfp);
+ if (!srt)
+ return -ENOMEM;
+
+ /* the inserted transfer */
+ xfer_new = srt->inserted_transfers;
+
+ /* pointer to data_tx data allocated - aligned */
+ data_tx = PTR_ALIGN(srt->extradata, align);
+
+ /* fill the transfer with the settings from the last replaced */
+ xfer_new->cs_change = xfer_end->cs_change;
+ xfer_new->delay_usecs = xfer_end->delay_usecs;
+
+ /* set the size of the transfer */
+ xfer_new->len = size;
+
+ /* now fill in the corresponding aligned pointers */
+ if (msg->spi->mode & SPI_3WIRE) {
+ /* if the first transfer has tx_buf set,
+ * then we are transmitting
+ */
+ if ((*xfer_start)->tx_buf) {
+ xfer_new->tx_buf = data_tx;
+ xfer_new->rx_buf = NULL;
+ } else {
+ xfer_new->tx_buf = NULL;
+ xfer_new->rx_buf = data_tx;
+ }
+ } else {
+ xfer_new->tx_buf = data_tx;
+ xfer_new->rx_buf = PTR_ALIGN(data_tx + size, align);
+ }
+
+ /* copy the data we need for tx (if it is set) */
+ if (xfer_new->tx_buf) {
+ for (xfer = *xfer_start, i = 0;
+ i < count;
+ i++, xfer = list_next_entry(xfer, transfer_list)) {
+ /* fill data only if tx_buf is set */
+ if (xfer->tx_buf)
+ memcpy(data_tx, xfer->tx_buf, xfer->len);
+ /* update pointer to after memcpy for next loop */
+ data_tx += xfer->len;
+ }
+ }
+
+ /* update the transfer to the one we have just put into the list */
+ *xfer_start = xfer_new;
+
+ /* increment statistics counters */
+ SPI_STATISTICS_ADD_TO_FIELD(&master->statistics,
+ transfers_merged, count);
+ SPI_STATISTICS_ADD_TO_FIELD(&msg->spi->statistics,
+ transfers_merged, count);
+
+ return 0;
+}
+
+static int __spi_merge_transfers(struct spi_master *master,
+ struct spi_message *msg,
+ struct spi_transfer **xfer_start,
+ size_t min_size,
+ size_t max_size,
+ gfp_t gfp)
+{
+ struct spi_transfer *xfer, *xfer_end;
+ int count;
+ size_t size;
+
+ /* loop transfers until we reach
+ * * the end of the list
+ * * a change in some essential parameters in spi_transfer
+ * compared to the first transfer we check
+ * (speed, bits, direction in 3 wire mode)
+ * * settings that immediately indicate we need to stop testing
+ * the next transfer (cs_change, delay_usecs)
+ */
+ for (count = 0, size = 0, xfer = *xfer_start, xfer_end = xfer;
+ !list_is_last(&xfer->transfer_list, &msg->transfers);
+ xfer = list_next_entry(xfer, transfer_list)) {
+ /* now check on total size */
+ if (size + xfer->len > max_size)
+ break;
+ /* these checks are only necessary on subsequent transfers */
+ if (count) {
+ /* check if we differ from the first transfer */
+ if (xfer->speed_hz != (*xfer_start)->speed_hz)
+ break;
+ if (xfer->tx_nbits != (*xfer_start)->tx_nbits)
+ break;
+ if (xfer->rx_nbits != (*xfer_start)->rx_nbits)
+ break;
+ if (xfer->bits_per_word !=
+ (*xfer_start)->bits_per_word)
+ break;
+
+ /* 3-wire we need to handle in a special way */
+ if (msg->spi->mode & SPI_3WIRE) {
+ /* did we switch directions in 3 wire mode ? */
+ if (xfer->tx_buf && (*xfer_start)->rx_buf)
+ break;
+ if (xfer->rx_buf && (*xfer_start)->tx_buf)
+ break;
+ }
+ }
+ /* otherwise update counters for the last few tests,
+ * that only depend on settings of the current transfer
+ */
+ count++;
+ size += xfer->len;
+ xfer_end = xfer;
+
+ /* check for conditions that would trigger a merge
+ * based only on the current transfer
+ * so we need count and size updated already...
+ */
+ if (xfer->cs_change)
+ break;
+ if (xfer->delay_usecs)
+ break;
+ }
+
+ /* merge only when we have at least 2 transfers to handle */
+ if (count < 2)
+ return 0;
+
+ /* and also only if we really have reached our min_size */
+ if (size < min_size)
+ return 0;
+
+ /* do the transformation for real now */
+ return __spi_merge_transfers_do(master, msg,
+ xfer_start, xfer_end,
+ count, size, gfp);
+}
+
+/**
+ * spi_merge_tranfers - merges multiple spi_transfers into a single one
+ * copying the corresponding data
+ * @master: the @spi_master for this transfer
+ * @message: the @spi_message to transform
+ * @max_size: the minimum total length when to apply this transform
+ * @max_size: the maximum total length when to apply this transform
+ * @gfp: gfp flags
+ *
+ * Return: status of transformation
+ */
+int spi_merge_transfers(struct spi_master *master,
+ struct spi_message *msg,
+ size_t min_size,
+ size_t max_size,
+ gfp_t gfp)
+{
+ struct spi_transfer *xfer;
+ int ret;
+
+ /* nothing to merge if the list is empty */
+ if (list_is_singular(&msg->transfers))
+ return 0;
+
+ /* if the total transfer size is too small, then skip */
+ if (msg->frame_length < min_size)
+ return 0;
+
+ /* iterate over all transfers and modify xfer if we have
+ * replaced some transfers
+ */
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ /* test if it is a merge candidate */
+ ret = __spi_merge_transfers(master, msg, &xfer,
+ min_size, max_size, gfp);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spi_merge_transfers);
+
/*-------------------------------------------------------------------------*/
/* 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 deb94a3..7172e73 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -63,6 +63,9 @@ extern struct bus_type spi_bus_type;
* @transfers_split_unaligned_copy:
* number of transfers that have been aligned by copying
* at least one buffer (typically tx)
+ * @transfers_merged:
+ * number of transfers that have been merged to allow
+ * for better efficiency using dma
*/
struct spi_statistics {
spinlock_t lock; /* lock for the whole structure */
@@ -86,6 +89,7 @@ struct spi_statistics {
unsigned long transfers_split_maxsize;
unsigned long transfers_split_unaligned;
unsigned long transfers_split_unaligned_copy;
+ unsigned long transfers_merged;
};
void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
@@ -945,6 +949,11 @@ extern int spi_split_transfers_unaligned(struct spi_master *master,
size_t min_size,
size_t alignment,
gfp_t gfp);
+extern int spi_merge_transfers(struct spi_master *master,
+ struct spi_message *msg,
+ size_t min_size,
+ size_t max_size,
+ 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 majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 6/8] spi: core: add spi_master.translate_message
[not found] ` <1450106426-2277-1-git-send-email-kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
` (4 preceding siblings ...)
2015-12-14 15:20 ` [PATCH v3 5/8] spi: core: add spi_merge_transfers method kernel-TqfNSX0MhmxHKSADF0wUEw
@ 2015-12-14 15:20 ` kernel-TqfNSX0MhmxHKSADF0wUEw
2015-12-14 15:20 ` [PATCH v3 7/8] spi: core: add spi_master.min_dma_len and supporting methods kernel-TqfNSX0MhmxHKSADF0wUEw
2015-12-14 15:20 ` [PATCH v3 8/8] spi: bcm2835: move to spi-core methods translate_message and can_dma kernel-TqfNSX0MhmxHKSADF0wUEw
7 siblings, 0 replies; 11+ messages in thread
From: kernel-TqfNSX0MhmxHKSADF0wUEw @ 2015-12-14 15:20 UTC (permalink / raw)
To: Mark Brown, Stephen Warren, Lee Jones, Eric Anholt,
linux-spi-u79uwXL29TY76Z2rM5mHXA,
linux-rpi-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
Cc: Martin Sperl
From: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
Add translate_message to spi_master structure to allow
translations of spi_messages to happen independently of
prepare messages.
This "translations" are supposed to be using spi_res to handle the
reverse transformation (see spi_replace_transfers), so no explicit
code is necessary to revert any changes.
dma_mapping of spi_messages could also become part of this processing.
The long-term view is that this translation as well as the
dma_mapping of the messages may happen in a separate thread
when we are queueing those spi_messages - this would allows
for better use of cpu resources as well as reduction of latency
in the case of queuing.
Signed-off-by: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
---
drivers/spi/spi.c | 15 +++++++++++++++
include/linux/spi/spi.h | 4 ++++
2 files changed, 19 insertions(+)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index fe3b18e..020e34d 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -1170,6 +1170,21 @@ static void __spi_pump_messages(struct spi_master *master, bool in_kthread)
trace_spi_message_start(master->cur_msg);
+ /* under some circumstances (i.e: when queuing a message) this
+ * could get moved to a separate thread to reduce latencies
+ * this could also handle the mapping of spi_transfers
+ */
+ if (master->translate_message) {
+ ret = master->translate_message(master, master->cur_msg);
+ if (ret) {
+ dev_err(&master->dev,
+ "failed to translate message: %d\n", ret);
+ master->cur_msg->status = ret;
+ spi_finalize_current_message(master);
+ return;
+ }
+ }
+
if (master->prepare_message) {
ret = master->prepare_message(master, master->cur_msg);
if (ret) {
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 7172e73..4b4c1e9 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -379,6 +379,8 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv)
* @handle_err: the subsystem calls the driver to handle an error that occurs
* in the generic implementation of transfer_one_message().
* @unprepare_message: undo any work done by prepare_message().
+ * @translate_message: apply some message translations to optimize for the
+ * spi_master
* @cs_gpios: Array of GPIOs to use as chip select lines; one per CS
* number. Any individual value may be -ENOENT for CS lines that
* are not GPIOs (driven by the SPI controller itself).
@@ -525,6 +527,8 @@ struct spi_master {
struct spi_message *message);
int (*unprepare_message)(struct spi_master *master,
struct spi_message *message);
+ int (*translate_message)(struct spi_master *master,
+ struct spi_message *message);
/*
* These hooks are for drivers that use a generic implementation
--
1.7.10.4
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 7/8] spi: core: add spi_master.min_dma_len and supporting methods
[not found] ` <1450106426-2277-1-git-send-email-kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
` (5 preceding siblings ...)
2015-12-14 15:20 ` [PATCH v3 6/8] spi: core: add spi_master.translate_message kernel-TqfNSX0MhmxHKSADF0wUEw
@ 2015-12-14 15:20 ` kernel-TqfNSX0MhmxHKSADF0wUEw
2015-12-14 15:20 ` [PATCH v3 8/8] spi: bcm2835: move to spi-core methods translate_message and can_dma kernel-TqfNSX0MhmxHKSADF0wUEw
7 siblings, 0 replies; 11+ messages in thread
From: kernel-TqfNSX0MhmxHKSADF0wUEw @ 2015-12-14 15:20 UTC (permalink / raw)
To: Mark Brown, Stephen Warren, Lee Jones, Eric Anholt,
linux-spi-u79uwXL29TY76Z2rM5mHXA,
linux-rpi-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
Cc: Martin Sperl
From: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
Added spi_master.min_dma_len plus methods requireing this information:
* spi_translate_message_size_align_merge
* spi_can_dma_min_dma_len
Signed-off-by: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
---
drivers/spi/spi.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++
include/linux/spi/spi.h | 14 +++++++++-
2 files changed, 79 insertions(+), 1 deletion(-)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 020e34d..883bfa8 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -277,6 +277,27 @@ void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
}
EXPORT_SYMBOL_GPL(spi_statistics_add_transfer_stats);
+/**
+ * spi_can_dma_min_dma_len - default implementation for spi_can_dma
+ * that only checks spi_transfer.len is bigger
+ * spi_master.min_dma_len
+ * @master: the spi_master device
+ * @spi: the spi_device
+ * @tfr: the spi_transfer
+ */
+bool spi_can_dma_min_dma_len(struct spi_master *master,
+ struct spi_device *spi,
+ struct spi_transfer *tfr)
+{
+ /* we start DMA efforts only on bigger transfers */
+ if (tfr->len < master->min_dma_len)
+ return false;
+
+ /* return OK */
+ return true;
+}
+EXPORT_SYMBOL_GPL(spi_can_dma_min_dma_len);
+
/* modalias support makes "modprobe $MODALIAS" new-style hotplug work,
* and the sysfs version makes coldplug work too.
*/
@@ -2793,6 +2814,51 @@ int spi_merge_transfers(struct spi_master *master,
return 0;
}
EXPORT_SYMBOL_GPL(spi_merge_transfers);
+/*-------------------------------------------------------------------------*/
+
+/**
+ * spi_translate_message_size_align_merge - default spi_message translation
+ * code that takes its parameters
+ * from @spi_master
+ *
+ * @master: the spi_master for which we run this translation
+ * @message: the spi_message which we need to translate
+ *
+ * Returns: status of tranformation
+ */
+int spi_translate_message_size_align_merge(
+ struct spi_master *master, struct spi_message *message)
+{
+ int ret;
+
+ /* translate the message */
+
+ /* fix alignment of transfers by splitting rx_buf/tx_buf
+ * (and worsted case copying tx_buf)
+ */
+ ret = spi_split_transfers_unaligned(master, message,
+ master->min_dma_len,
+ master->dma_alignment,
+ GFP_KERNEL);
+ if (ret)
+ return ret;
+
+ /* limit transfer length */
+ if (master->max_dma_len) {
+ ret = spi_split_transfers_maxsize(master, message,
+ master->max_dma_len,
+ GFP_KERNEL);
+ if (ret)
+ return ret;
+ }
+
+ /* merge spi_transfers up to a full page */
+ ret = spi_merge_transfers(master, message, 2, PAGE_SIZE,
+ GFP_KERNEL);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(spi_translate_message_size_align_merge);
/*-------------------------------------------------------------------------*/
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 4b4c1e9..f055a47 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -351,6 +351,9 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv)
* while the hardware is prepared, using the parent
* device for the spidev
* @max_dma_len: Maximum length of a DMA transfer for the device.
+ * @min_dma_len: Minimum length of a DMA transfer for the device.
+ * (mostly to avoid dma_mapping a buffer when dma is not used,
+ * should be multiple of dma_alignment)
* @prepare_transfer_hardware: a message will soon arrive from the queue
* so the subsystem requests the driver to prepare the transfer hardware
* by issuing this call
@@ -423,7 +426,6 @@ struct spi_master {
* buffers; let protocol drivers know about these requirements.
*/
u16 dma_alignment;
-
/* spi_device.mode flags understood by this controller driver */
u16 mode_bits;
@@ -517,6 +519,7 @@ struct spi_master {
bool cur_msg_prepared;
bool cur_msg_mapped;
struct completion xfer_completion;
+ size_t min_dma_len;
size_t max_dma_len;
int (*prepare_transfer_hardware)(struct spi_master *master);
@@ -940,6 +943,15 @@ extern struct spi_replaced_transfers *spi_replace_transfers(
size_t extradatasize,
gfp_t gfp);
+/* some default implementations that drivers may use */
+extern int spi_translate_message_size_align_merge(
+ struct spi_master *master, struct spi_message *message);
+
+/* a default implementation of can_dma */
+extern bool spi_can_dma_min_dma_len(struct spi_master *master,
+ struct spi_device *spi,
+ struct spi_transfer *tfr);
+
/*---------------------------------------------------------------------------*/
/* SPI transfer transformation methods */
--
1.7.10.4
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 8/8] spi: bcm2835: move to spi-core methods translate_message and can_dma
[not found] ` <1450106426-2277-1-git-send-email-kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
` (6 preceding siblings ...)
2015-12-14 15:20 ` [PATCH v3 7/8] spi: core: add spi_master.min_dma_len and supporting methods kernel-TqfNSX0MhmxHKSADF0wUEw
@ 2015-12-14 15:20 ` kernel-TqfNSX0MhmxHKSADF0wUEw
7 siblings, 0 replies; 11+ messages in thread
From: kernel-TqfNSX0MhmxHKSADF0wUEw @ 2015-12-14 15:20 UTC (permalink / raw)
To: Mark Brown, Stephen Warren, Lee Jones, Eric Anholt,
linux-spi-u79uwXL29TY76Z2rM5mHXA,
linux-rpi-kernel-IAPFreCvJWM7uuMidbF8XUB+6BGkLq7r
Cc: Martin Sperl
From: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
Remove all limitations in can_dma that inhibit the use of DMA
To meet the limitiations of the HW we are now using the standardized
version of spi_translate_message, that:
* splits long transfers (> 60k)
* aligns transfers (on 4 byte boundary)
* merges transfers (for optimizations)
Signed-off-by: Martin Sperl <kernel-TqfNSX0MhmxHKSADF0wUEw@public.gmane.org>
---
drivers/spi/spi-bcm2835.c | 58 +++++++++++++++------------------------------
1 file changed, 19 insertions(+), 39 deletions(-)
Changelog:
V1 -> V3: move to use spi-core methods where possible
diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c
index cf04960..75e4425 100644
--- a/drivers/spi/spi-bcm2835.c
+++ b/drivers/spi/spi-bcm2835.c
@@ -361,44 +361,8 @@ static bool bcm2835_spi_can_dma(struct spi_master *master,
if (!gpio_is_valid(spi->cs_gpio))
return false;
- /* we start DMA efforts only on bigger transfers */
- if (tfr->len < BCM2835_SPI_DMA_MIN_LENGTH)
- return false;
-
- /* BCM2835_SPI_DLEN has defined a max transfer size as
- * 16 bit, so max is 65535
- * we can revisit this by using an alternative transfer
- * method - ideally this would get done without any more
- * interaction...
- */
- if (tfr->len > 65535) {
- dev_warn_once(&spi->dev,
- "transfer size of %d too big for dma-transfer\n",
- tfr->len);
- return false;
- }
-
- /* if we run rx/tx_buf with word aligned addresses then we are OK */
- if ((((size_t)tfr->rx_buf & 3) == 0) &&
- (((size_t)tfr->tx_buf & 3) == 0))
- return true;
-
- /* otherwise we only allow transfers within the same page
- * to avoid wasting time on dma_mapping when it is not practical
- */
- if (((size_t)tfr->tx_buf & (PAGE_SIZE - 1)) + tfr->len > PAGE_SIZE) {
- dev_warn_once(&spi->dev,
- "Unaligned spi tx-transfer bridging page\n");
- return false;
- }
- if (((size_t)tfr->rx_buf & (PAGE_SIZE - 1)) + tfr->len > PAGE_SIZE) {
- dev_warn_once(&spi->dev,
- "Unaligned spi rx-transfer bridging page\n");
- return false;
- }
-
- /* return OK */
- return true;
+ /* use the default implementation */
+ return spi_can_dma_min_dma_len(master, spi, tfr);
}
static void bcm2835_dma_release(struct spi_master *master)
@@ -461,7 +425,23 @@ static void bcm2835_dma_init(struct spi_master *master, struct device *dev)
/* all went well, so set can_dma */
master->can_dma = bcm2835_spi_can_dma;
- master->max_dma_len = 65535; /* limitation by BCM2835_SPI_DLEN */
+
+ /* set up transform message using the default implementation
+ * for size, alignment and merging transfers
+ */
+ master->translate_message = spi_translate_message_size_align_merge;
+
+ /* the minimum length when we run DMA */
+ master->min_dma_len = BCM2835_SPI_DMA_MIN_LENGTH;
+
+ /* the max_dma_len limited by BCM2835_SPI_DLEN is actually 65535,
+ * but for al practical purposes we use 15 pages (60k)
+ */
+ master->max_dma_len = 15 * PAGE_SIZE;
+
+ /* dma alignment is 4 bytes */
+ master->dma_alignment = 4;
+
/* need to do TX AND RX DMA, so we need dummy buffers */
master->flags = SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX;
--
1.7.10.4
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply related [flat|nested] 11+ messages in thread