From: Martin Sperl <kernel@xxxxxxxxxxxxxxxx> This implements a means to dump a spi_message or spi_transfer. spi_loop_back_test requires a means to report on failed transfers (including payload data), so it makes use of this. Such a functionality can also be helpful during development of other drivers, so it has been exposed as a general facility. Signed-off-by: Martin Sperl <kernel@xxxxxxxxxxxxxxxx> --- drivers/spi/spi.c | 169 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/spi/spi.h | 24 +++++++ 2 files changed, 193 insertions(+) Changelog: V1->V2: * changes recommended by Andy Shevchenko * slight reorganisation of code - specifically remove hex_dump_to_buffer and use %*ph instead * removal of one helper function and renaming another * change in method parameters to allow changing the amount of lines to dump at the head independently from tail diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 9964835..bf623db 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -31,6 +31,7 @@ #include <linux/of_gpio.h> #include <linux/pm_runtime.h> #include <linux/pm_domain.h> +#include <linux/printk.h> #include <linux/export.h> #include <linux/sched/rt.h> #include <linux/delay.h> @@ -718,6 +719,174 @@ int spi_register_board_info(struct spi_board_info const *info, unsigned n) return 0; } +const unsigned spi_dump_bytes_per_line = 16; +static void __spi_transfer_dump_chunk(struct spi_device *spi, char *prefix, + const void *ptr, size_t start, + size_t len) +{ + const size_t spi_dump_bytes_per_line = 16; + char hexdata[spi_dump_bytes_per_line * 3]; + size_t i, l; + + for (i = start; i < len; i += spi_dump_bytes_per_line) { + /* format the next 16 bytes */ + snprintf(hexdata, sizeof(hexdata), "%16ph", ptr + i); + + /* truncate the unnecessarily printed bytes */ + l = min_t(size_t, len - i, spi_dump_bytes_per_line) * 3 - 1; + hexdata[l] = 0; + + /* and print data including address and offset */ + dev_info(&spi->dev, "%soff=0x%.5zx ptr=0x%pK: %s\n", + prefix, i, ptr + i, hexdata); + } +} + +static void spi_transfer_dump_buffer(struct spi_device *spi, char *prefix, + const void *ptr, size_t len, + size_t head_len, size_t tail_len) +{ + size_t tail_start; + + /* dump head if requested */ + if (head_len) { + /* calculate effective head_len - aligning if necessary */ + head_len = (len <= head_len) ? + len : + roundup(head_len, spi_dump_bytes_per_line); + + /* dump the head */ + __spi_transfer_dump_chunk(spi, prefix, ptr, 0, head_len); + + /* if we dumped everything return immediately */ + if (len == head_len) + return; + } + + /* return if no tail_len is requested */ + if (!tail_len) + return; + + /* calculate real tail start offset aligning it */ + tail_start = (tail_len >= len) ? + head_len : + rounddown(len - tail_len, spi_dump_bytes_per_line); + + /* special handling needed if we have been dumping head */ + if (head_len) { + if (tail_start > head_len) + /* we are not overlapping */ + dev_info(&spi->dev, + "%struncated - continuing at offset %04x\n", + prefix, tail_start); + else + /* we are overlapping, so continue at head_len */ + tail_start = head_len; + } + + /* dump the tail */ + __spi_transfer_dump_chunk(spi, prefix, ptr, tail_start, len); +} + +/** + * spi_transfer_dump - dump all the essential information + * of a @spi_transfer, when dump_size is set, + * then hex-dump that many bytes of data + * @spi: @spi_device for which to dump this (dev_info) + * @msg: @spi_message to which xfer belongs + * @xfer: @spi_transfer to dump + * @head_len: bytes to dump from the start of the buffers + * (rounded up to multiple of 16) + * @tail_len: bytes to dump from the end of the buffers + * (rounded up so that tail dump starts 16 byte aligned) + */ +void spi_transfer_dump(struct spi_device *spi, + struct spi_message *msg, + struct spi_transfer *xfer, + size_t head_len, size_t tail_len) +{ + struct device *dev = &spi->dev; + + dev_info(dev, " spi_transfer@%pK\n", xfer); + dev_info(dev, " speed_hz: %u\n", xfer->speed_hz); + dev_info(dev, " len: %u\n", xfer->len); + dev_info(dev, " tx_nbits: %u\n", xfer->tx_nbits); + dev_info(dev, " rx_nbits: %u\n", xfer->rx_nbits); + dev_info(dev, " bits/word: %u\n", xfer->bits_per_word); + if (xfer->delay_usecs) + dev_info(dev, " delay_usecs: %u\n", + xfer->delay_usecs); + if (xfer->cs_change) + dev_info(dev, " cs_change\n"); + if (xfer->tx_buf) { + dev_info(dev, " tx_buf: %pK\n", xfer->tx_buf); + if (xfer->tx_dma) + dev_info(dev, " tx_dma: %pad\n", + &xfer->tx_dma); + if (head_len | tail_len) + spi_transfer_dump_buffer(spi, "\t", + xfer->tx_buf, xfer->len, + head_len, tail_len); + } + if (xfer->rx_buf) { + dev_info(dev, " rx_buf: %pK\n", xfer->rx_buf); + if (xfer->rx_dma) + dev_info(dev, " rx_dma: %pad\n", + &xfer->rx_dma); + if (head_len | tail_len) + spi_transfer_dump_buffer(spi, "\t", + xfer->rx_buf, xfer->len, + head_len, tail_len); + } +} +EXPORT_SYMBOL_GPL(spi_transfer_dump); + +/** + * spi_message_dump_custom - dump a spi message with ability to have + * a custom dump method per transfer + * @spi: @spi_device for which to dump this (dev_info) + * @msg: @spi_message to dump + * @head_len: bytes to dump from the start of the buffers + * (rounded up to multiple of 16) + * @tail_len: bytes to dump from the end of the buffers + * (rounded up so that tail dump starts 16 byte aligned) + * @custom: custom dump code to execute per transfer + * @context: context to pass to the custom dump code + * + * Uses dev_info() to dump the lines. + */ +void spi_message_dump_custom(struct spi_device *spi, + struct spi_message *msg, + size_t head_len, size_t tail_len, + spi_transfer_dump_custom_t custom, + void *context) +{ + struct device *dev = &spi->dev; + struct spi_transfer *xfer; + + /* dump the message */ + dev_info(dev, "spi_msg@%pK\n", msg); + if (msg->status) + dev_info(dev, " status: %d\n", msg->status); + dev_info(dev, " frame_length: %zu\n", msg->frame_length); + dev_info(dev, " actual_length: %zu\n", msg->actual_length); + if (msg->complete) + dev_info(dev, " complete: %pF\n", msg->complete); + if (msg->context) + dev_info(dev, " context: %pF\n", msg->context); + if (msg->is_dma_mapped) + dev_info(dev, " is_dma_mapped\n"); + dev_info(dev, " transfers-head: %pK\n", &msg->transfers); + + /* dump transfers themselves */ + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + spi_transfer_dump(spi, msg, xfer, head_len, tail_len); + if (custom) + custom(spi, msg, xfer, context); + } +} +EXPORT_SYMBOL_GPL(spi_message_dump_custom); + /*-------------------------------------------------------------------------*/ static void spi_set_cs(struct spi_device *spi, bool enable) diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index f055a47..13f8f06 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -897,6 +897,30 @@ extern int spi_setup(struct spi_device *spi); extern int spi_async(struct spi_device *spi, struct spi_message *message); extern int spi_async_locked(struct spi_device *spi, struct spi_message *message); +/*---------------------------------------------------------------------------*/ + +extern void spi_transfer_dump(struct spi_device *spi, + struct spi_message *msg, + struct spi_transfer *xfer, + size_t dump_head, size_t dump_tail); + +typedef void (*spi_transfer_dump_custom_t)(struct spi_device *spi, + struct spi_message *msg, + struct spi_transfer *xfer, + void *context); + +extern void spi_message_dump_custom(struct spi_device *spi, + struct spi_message *msg, + size_t dump_head, size_t dump_tail, + spi_transfer_dump_custom_t custom, + void *context); + +static inline void spi_message_dump(struct spi_device *spi, + struct spi_message *msg, + size_t dump_head, size_t dump_tail) +{ + spi_message_dump_custom(spi, msg, dump_head, dump_tail, NULL, NULL); +} /*---------------------------------------------------------------------------*/ -- 1.7.10.4 -- To unsubscribe from this list: send the line "unsubscribe linux-spi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html