|
@@ -144,6 +144,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,
|
|
@@ -181,6 +183,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,
|
|
|
};
|
|
|
|
|
@@ -223,6 +226,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,
|
|
|
};
|
|
|
|
|
@@ -1024,6 +1028,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;
|
|
@@ -2043,6 +2049,336 @@ 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);
|
|
|
+
|
|
|
+/*-------------------------------------------------------------------------*/
|
|
|
+
|
|
|
+/* 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)
|
|
|
+ * @gfp: gfp flags
|
|
|
+ *
|
|
|
+ * 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);
|
|
|
+
|
|
|
+static 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 %zu - 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 (IS_ERR(srt))
|
|
|
+ return PTR_ERR(srt);
|
|
|
+ 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_t(size_t, 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
|
|
|
+ * @msg: the @spi_message to transform
|
|
|
+ * @maxsize: the maximum when to apply this
|
|
|
+ * @gfp: GFP allocation flags
|
|
|
+ *
|
|
|
+ * 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);
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|