// SPDX-License-Identifier: GPL-2.0-or-later/* * linux/drivers/spi/spi-loopback-test.c * * (c) Martin Sperl <kernel@martin.sperl.org> * * Loopback test driver to test several typical spi_message conditions * that a spi_master driver may encounter * this can also get used for regression testing */ #include <linux/delay.h>#include <linux/kernel.h>#include <linux/ktime.h>#include <linux/list.h>#include <linux/list_sort.h>#include <linux/module.h>#include <linux/printk.h>#include <linux/vmalloc.h>#include <linux/spi/spi.h> #include "spi-test.h" /* flag to only simulate transfers */static int simulate_only;module_param(simulate_only, int, 0);MODULE_PARM_DESC(simulate_only, "if not 0 do not execute the spi message"); /* dump spi messages */static int dump_messages;module_param(dump_messages, int, 0);MODULE_PARM_DESC(dump_messages, "=1 dump the basic spi_message_structure, " \ "=2 dump the spi_message_structure including data, " \ "=3 dump the spi_message structure before and after execution");/* the device is jumpered for loopback - enabling some rx_buf tests */static int loopback;module_param(loopback, int, 0);MODULE_PARM_DESC(loopback, "if set enable loopback mode, where the rx_buf " \ "is checked to match tx_buf after the spi_message " \ "is executed"); static int loop_req;module_param(loop_req, int, 0);MODULE_PARM_DESC(loop_req, "if set controller will be asked to enable test loop mode. " \ "If controller supported it, MISO and MOSI will be connected"); static int no_cs;module_param(no_cs, int, 0);MODULE_PARM_DESC(no_cs, "if set Chip Select (CS) will not be used"); /* run tests only for a specific length */static int run_only_iter_len = -1;module_param(run_only_iter_len, int, 0);MODULE_PARM_DESC(run_only_iter_len, "only run tests for a length of this number in iterate_len list"); /* run only a specific test */static int run_only_test = -1;module_param(run_only_test, int, 0);MODULE_PARM_DESC(run_only_test, "only run the test with this number (0-based !)"); /* use vmalloc'ed buffers */static int use_vmalloc;module_param(use_vmalloc, int, 0644);MODULE_PARM_DESC(use_vmalloc, "use vmalloc'ed buffers instead of kmalloc'ed"); /* check rx ranges */static int check_ranges = 1;module_param(check_ranges, int, 0644);MODULE_PARM_DESC(check_ranges, "checks rx_buffer pattern are valid"); static unsigned int delay_ms = 100;module_param(delay_ms, uint, 0644);MODULE_PARM_DESC(delay_ms, "delay between tests, in milliseconds (default: 100)"); /* the actual tests to execute */static struct spi_test spi_tests[] = { { .description = "tx/rx-transfer - start of page", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_rx_align = ITERATE_ALIGN, .transfer_count = 1, .transfers = { { .tx_buf = TX(0), .rx_buf = RX(0), }, }, }, { .description = "tx/rx-transfer - crossing PAGE_SIZE", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_rx_align = ITERATE_ALIGN, .transfer_count = 1, .transfers = { { .tx_buf = TX(PAGE_SIZE - 4), .rx_buf = RX(PAGE_SIZE - 4), }, }, }, { .description = "tx-transfer - only", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .transfer_count = 1, .transfers = { { .tx_buf = TX(0), }, }, }, { .description = "rx-transfer - only", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_rx_align = ITERATE_ALIGN, .transfer_count = 1, .transfers = { { .rx_buf = RX(0), }, }, }, { .description = "two tx-transfers - alter both", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(0) | BIT(1), .transfer_count = 2, .transfers = { { .tx_buf = TX(0), }, { /* this is why we cant use ITERATE_MAX_LEN */ .tx_buf = TX(SPI_TEST_MAX_SIZE_HALF), }, }, }, { .description = "two tx-transfers - alter first", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(0), .transfer_count = 2, .transfers = { { .tx_buf = TX(64), }, { .len = 1, .tx_buf = TX(0), }, }, }, { .description = "two tx-transfers - alter second", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(1), .transfer_count = 2, .transfers = { { .len = 16, .tx_buf = TX(0), }, { .tx_buf = TX(64), }, }, }, { .description = "two transfers tx then rx - alter both", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(0) | BIT(1), .transfer_count = 2, .transfers = { { .tx_buf = TX(0), }, { .rx_buf = RX(0), }, }, }, { .description = "two transfers tx then rx - alter tx", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(0), .transfer_count = 2, .transfers = { { .tx_buf = TX(0), }, { .len = 1, .rx_buf = RX(0), }, }, }, { .description = "two transfers tx then rx - alter rx", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(1), .transfer_count = 2, .transfers = { { .len = 1, .tx_buf = TX(0), }, { .rx_buf = RX(0), }, }, }, { .description = "two tx+rx transfers - alter both", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(0) | BIT(1), .transfer_count = 2, .transfers = { { .tx_buf = TX(0), .rx_buf = RX(0), }, { /* making sure we align without overwrite * the reason we can not use ITERATE_MAX_LEN */ .tx_buf = TX(SPI_TEST_MAX_SIZE_HALF), .rx_buf = RX(SPI_TEST_MAX_SIZE_HALF), }, }, }, { .description = "two tx+rx transfers - alter first", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(0), .transfer_count = 2, .transfers = { { /* making sure we align without overwrite */ .tx_buf = TX(1024), .rx_buf = RX(1024), }, { .len = 1, /* making sure we align without overwrite */ .tx_buf = TX(0), .rx_buf = RX(0), }, }, }, { .description = "two tx+rx transfers - alter second", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(1), .transfer_count = 2, .transfers = { { .len = 1, .tx_buf = TX(0), .rx_buf = RX(0), }, { /* making sure we align without overwrite */ .tx_buf = TX(1024), .rx_buf = RX(1024), }, }, }, { .description = "two tx+rx transfers - delay after transfer", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_transfer_mask = BIT(0) | BIT(1), .transfer_count = 2, .transfers = { { .tx_buf = TX(0), .rx_buf = RX(0), .delay = { .value = 1000, .unit = SPI_DELAY_UNIT_USECS, }, }, { .tx_buf = TX(0), .rx_buf = RX(0), .delay = { .value = 1000, .unit = SPI_DELAY_UNIT_USECS, }, }, }, }, { .description = "three tx+rx transfers with overlapping cache lines", .fill_option = FILL_COUNT_8, /* * This should be large enough for the controller driver to * choose to transfer it with DMA. */ .iterate_len = { 512, -1 }, .iterate_transfer_mask = BIT(1), .transfer_count = 3, .transfers = { { .len = 1, .tx_buf = TX(0), .rx_buf = RX(0), }, { .tx_buf = TX(1), .rx_buf = RX(1), }, { .len = 1, .tx_buf = TX(513), .rx_buf = RX(513), }, }, }, { /* end of tests sequence */ }}; static int spi_loopback_test_probe(struct spi_device *spi){ int ret; if (loop_req || no_cs) { spi->mode |= loop_req ? SPI_LOOP : 0; spi->mode |= no_cs ? SPI_NO_CS : 0; ret = spi_setup(spi); if (ret) { dev_err(&spi->dev, "SPI setup with SPI_LOOP or SPI_NO_CS failed (%d)\n", ret); return ret; } } dev_info(&spi->dev, "Executing spi-loopback-tests\n"); ret = spi_test_run_tests(spi, spi_tests); dev_info(&spi->dev, "Finished spi-loopback-tests with return: %i\n", ret); return ret;} /* non const match table to permit to change via a module parameter */static struct of_device_id spi_loopback_test_of_match[] = { { .compatible = "linux,spi-loopback-test", }, { }}; /* allow to override the compatible string via a module_parameter */module_param_string(compatible, spi_loopback_test_of_match[0].compatible, sizeof(spi_loopback_test_of_match[0].compatible), 0000); MODULE_DEVICE_TABLE(of, spi_loopback_test_of_match); static struct spi_driver spi_loopback_test_driver = { .driver = { .name = "spi-loopback-test", .of_match_table = spi_loopback_test_of_match, }, .probe = spi_loopback_test_probe,}; module_spi_driver(spi_loopback_test_driver); MODULE_AUTHOR("Martin Sperl <kernel@martin.sperl.org>");MODULE_DESCRIPTION("test spi_driver to check core functionality");MODULE_LICENSE("GPL"); /*-------------------------------------------------------------------------*/ /* spi_test implementation */ #define RANGE_CHECK(ptr, plen, start, slen) \ ((ptr >= start) && (ptr + plen <= start + slen)) /* we allocate one page more, to allow for offsets */#define SPI_TEST_MAX_SIZE_PLUS (SPI_TEST_MAX_SIZE + PAGE_SIZE) static void spi_test_print_hex_dump(char *pre, const void *ptr, size_t len){ /* limit the hex_dump */ if (len <= 1024) { print_hex_dump(KERN_INFO, pre, DUMP_PREFIX_OFFSET, 16, 1, ptr, len, 0); return; } /* print head */ print_hex_dump(KERN_INFO, pre, DUMP_PREFIX_OFFSET, 16, 1, ptr, 512, 0); /* print tail */ pr_info("%s truncated - continuing at offset %04zx\n", pre, len - 512); print_hex_dump(KERN_INFO, pre, DUMP_PREFIX_OFFSET, 16, 1, ptr + (len - 512), 512, 0);} static void spi_test_dump_message(struct spi_device *spi, struct spi_message *msg, bool dump_data){ struct spi_transfer *xfer; int i; u8 b; dev_info(&spi->dev, " spi_msg@%p\n", msg); if (msg->status) dev_info(&spi->dev, " status: %i\n", msg->status); dev_info(&spi->dev, " frame_length: %i\n", msg->frame_length); dev_info(&spi->dev, " actual_length: %i\n", msg->actual_length); list_for_each_entry(xfer, &msg->transfers, transfer_list) { dev_info(&spi->dev, " spi_transfer@%p\n", xfer); dev_info(&spi->dev, " len: %i\n", xfer->len); dev_info(&spi->dev, " tx_buf: %p\n", xfer->tx_buf); if (dump_data && xfer->tx_buf) spi_test_print_hex_dump(" TX: ", xfer->tx_buf, xfer->len); dev_info(&spi->dev, " rx_buf: %p\n", xfer->rx_buf); if (dump_data && xfer->rx_buf) spi_test_print_hex_dump(" RX: ", xfer->rx_buf, xfer->len); /* check for unwritten test pattern on rx_buf */ if (xfer->rx_buf) { for (i = 0 ; i < xfer->len ; i++) { b = ((u8 *)xfer->rx_buf)[xfer->len - 1 - i]; if (b != SPI_TEST_PATTERN_UNWRITTEN) break; } if (i) dev_info(&spi->dev, " rx_buf filled with %02x starts at offset: %i\n", SPI_TEST_PATTERN_UNWRITTEN, xfer->len - i); } }} struct rx_ranges { struct list_head list; u8 *start; u8 *end;}; static int rx_ranges_cmp(void *priv, const struct list_head *a, const struct list_head *b){ const struct rx_ranges *rx_a = list_entry(a, struct rx_ranges, list); const struct rx_ranges *rx_b = list_entry(b, struct rx_ranges, list); if (rx_a->start > rx_b->start) return 1;Lines 1–500 of 1123. Load more
// SPDX-License-Identifier: GPL-2.0-or-later /* * linux/drivers/spi/spi-loopback-test.c * * (c) Martin Sperl <kernel@martin.sperl.org> * * Loopback test driver to test several typical spi_message conditions * that a spi_master driver may encounter * this can also get used for regression testing */ #include <linux/delay.h> #include <linux/kernel.h> #include <linux/ktime.h> #include <linux/list.h> #include <linux/list_sort.h> #include <linux/module.h> #include <linux/printk.h> #include <linux/vmalloc.h> #include <linux/spi/spi.h> #include "spi-test.h" /* flag to only simulate transfers */ static int simulate_only; module_param(simulate_only, int, 0); MODULE_PARM_DESC(simulate_only, "if not 0 do not execute the spi message"); /* dump spi messages */ static int dump_messages; module_param(dump_messages, int, 0); MODULE_PARM_DESC(dump_messages, "=1 dump the basic spi_message_structure, " \ "=2 dump the spi_message_structure including data, " \ "=3 dump the spi_message structure before and after execution"); /* the device is jumpered for loopback - enabling some rx_buf tests */ static int loopback; module_param(loopback, int, 0); MODULE_PARM_DESC(loopback, "if set enable loopback mode, where the rx_buf " \ "is checked to match tx_buf after the spi_message " \ "is executed"); static int loop_req; module_param(loop_req, int, 0); MODULE_PARM_DESC(loop_req, "if set controller will be asked to enable test loop mode. " \ "If controller supported it, MISO and MOSI will be connected"); static int no_cs; module_param(no_cs, int, 0); MODULE_PARM_DESC(no_cs, "if set Chip Select (CS) will not be used"); /* run tests only for a specific length */ static int run_only_iter_len = -1; module_param(run_only_iter_len, int, 0); MODULE_PARM_DESC(run_only_iter_len, "only run tests for a length of this number in iterate_len list"); /* run only a specific test */ static int run_only_test = -1; module_param(run_only_test, int, 0); MODULE_PARM_DESC(run_only_test, "only run the test with this number (0-based !)"); /* use vmalloc'ed buffers */ static int use_vmalloc; module_param(use_vmalloc, int, 0644); MODULE_PARM_DESC(use_vmalloc, "use vmalloc'ed buffers instead of kmalloc'ed"); /* check rx ranges */ static int check_ranges = 1; module_param(check_ranges, int, 0644); MODULE_PARM_DESC(check_ranges, "checks rx_buffer pattern are valid"); static unsigned int delay_ms = 100; module_param(delay_ms, uint, 0644); MODULE_PARM_DESC(delay_ms, "delay between tests, in milliseconds (default: 100)"); /* the actual tests to execute */ static struct spi_test spi_tests[] = { { .description = "tx/rx-transfer - start of page", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_rx_align = ITERATE_ALIGN, .transfer_count = 1, .transfers = { { .tx_buf = TX(0), .rx_buf = RX(0), }, }, }, { .description = "tx/rx-transfer - crossing PAGE_SIZE", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_rx_align = ITERATE_ALIGN, .transfer_count = 1, .transfers = { { .tx_buf = TX(PAGE_SIZE - 4), .rx_buf = RX(PAGE_SIZE - 4), }, }, }, { .description = "tx-transfer - only", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .transfer_count = 1, .transfers = { { .tx_buf = TX(0), }, }, }, { .description = "rx-transfer - only", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_rx_align = ITERATE_ALIGN, .transfer_count = 1, .transfers = { { .rx_buf = RX(0), }, }, }, { .description = "two tx-transfers - alter both", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(0) | BIT(1), .transfer_count = 2, .transfers = { { .tx_buf = TX(0), }, { /* this is why we cant use ITERATE_MAX_LEN */ .tx_buf = TX(SPI_TEST_MAX_SIZE_HALF), }, }, }, { .description = "two tx-transfers - alter first", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(0), .transfer_count = 2, .transfers = { { .tx_buf = TX(64), }, { .len = 1, .tx_buf = TX(0), }, }, }, { .description = "two tx-transfers - alter second", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(1), .transfer_count = 2, .transfers = { { .len = 16, .tx_buf = TX(0), }, { .tx_buf = TX(64), }, }, }, { .description = "two transfers tx then rx - alter both", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(0) | BIT(1), .transfer_count = 2, .transfers = { { .tx_buf = TX(0), }, { .rx_buf = RX(0), }, }, }, { .description = "two transfers tx then rx - alter tx", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(0), .transfer_count = 2, .transfers = { { .tx_buf = TX(0), }, { .len = 1, .rx_buf = RX(0), }, }, }, { .description = "two transfers tx then rx - alter rx", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(1), .transfer_count = 2, .transfers = { { .len = 1, .tx_buf = TX(0), }, { .rx_buf = RX(0), }, }, }, { .description = "two tx+rx transfers - alter both", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(0) | BIT(1), .transfer_count = 2, .transfers = { { .tx_buf = TX(0), .rx_buf = RX(0), }, { /* making sure we align without overwrite * the reason we can not use ITERATE_MAX_LEN */ .tx_buf = TX(SPI_TEST_MAX_SIZE_HALF), .rx_buf = RX(SPI_TEST_MAX_SIZE_HALF), }, }, }, { .description = "two tx+rx transfers - alter first", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(0), .transfer_count = 2, .transfers = { { /* making sure we align without overwrite */ .tx_buf = TX(1024), .rx_buf = RX(1024), }, { .len = 1, /* making sure we align without overwrite */ .tx_buf = TX(0), .rx_buf = RX(0), }, }, }, { .description = "two tx+rx transfers - alter second", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_tx_align = ITERATE_ALIGN, .iterate_transfer_mask = BIT(1), .transfer_count = 2, .transfers = { { .len = 1, .tx_buf = TX(0), .rx_buf = RX(0), }, { /* making sure we align without overwrite */ .tx_buf = TX(1024), .rx_buf = RX(1024), }, }, }, { .description = "two tx+rx transfers - delay after transfer", .fill_option = FILL_COUNT_8, .iterate_len = { ITERATE_MAX_LEN }, .iterate_transfer_mask = BIT(0) | BIT(1), .transfer_count = 2, .transfers = { { .tx_buf = TX(0), .rx_buf = RX(0), .delay = { .value = 1000, .unit = SPI_DELAY_UNIT_USECS, }, }, { .tx_buf = TX(0), .rx_buf = RX(0), .delay = { .value = 1000, .unit = SPI_DELAY_UNIT_USECS, }, }, }, }, { .description = "three tx+rx transfers with overlapping cache lines", .fill_option = FILL_COUNT_8, /* * This should be large enough for the controller driver to * choose to transfer it with DMA. */ .iterate_len = { 512, -1 }, .iterate_transfer_mask = BIT(1), .transfer_count = 3, .transfers = { { .len = 1, .tx_buf = TX(0), .rx_buf = RX(0), }, { .tx_buf = TX(1), .rx_buf = RX(1), }, { .len = 1, .tx_buf = TX(513), .rx_buf = RX(513), }, }, }, { /* end of tests sequence */ } }; static int spi_loopback_test_probe(struct spi_device *spi) { int ret; if (loop_req || no_cs) { spi->mode |= loop_req ? SPI_LOOP : 0; spi->mode |= no_cs ? SPI_NO_CS : 0; ret = spi_setup(spi); if (ret) { dev_err(&spi->dev, "SPI setup with SPI_LOOP or SPI_NO_CS failed (%d)\n", ret); return ret; } } dev_info(&spi->dev, "Executing spi-loopback-tests\n"); ret = spi_test_run_tests(spi, spi_tests); dev_info(&spi->dev, "Finished spi-loopback-tests with return: %i\n", ret); return ret; } /* non const match table to permit to change via a module parameter */ static struct of_device_id spi_loopback_test_of_match[] = { { .compatible = "linux,spi-loopback-test", }, { } }; /* allow to override the compatible string via a module_parameter */ module_param_string(compatible, spi_loopback_test_of_match[0].compatible, sizeof(spi_loopback_test_of_match[0].compatible), 0000); MODULE_DEVICE_TABLE(of, spi_loopback_test_of_match); static struct spi_driver spi_loopback_test_driver = { .driver = { .name = "spi-loopback-test", .of_match_table = spi_loopback_test_of_match, }, .probe = spi_loopback_test_probe, }; module_spi_driver(spi_loopback_test_driver); MODULE_AUTHOR("Martin Sperl <kernel@martin.sperl.org>"); MODULE_DESCRIPTION("test spi_driver to check core functionality"); MODULE_LICENSE("GPL"); /*-------------------------------------------------------------------------*/ /* spi_test implementation */ #define RANGE_CHECK(ptr, plen, start, slen) \ ((ptr >= start) && (ptr + plen <= start + slen)) /* we allocate one page more, to allow for offsets */ #define SPI_TEST_MAX_SIZE_PLUS (SPI_TEST_MAX_SIZE + PAGE_SIZE) static void spi_test_print_hex_dump(char *pre, const void *ptr, size_t len) { /* limit the hex_dump */ if (len <= 1024) { print_hex_dump(KERN_INFO, pre, DUMP_PREFIX_OFFSET, 16, 1, ptr, len, 0); return; } /* print head */ print_hex_dump(KERN_INFO, pre, DUMP_PREFIX_OFFSET, 16, 1, ptr, 512, 0); /* print tail */ pr_info("%s truncated - continuing at offset %04zx\n", pre, len - 512); print_hex_dump(KERN_INFO, pre, DUMP_PREFIX_OFFSET, 16, 1, ptr + (len - 512), 512, 0); } static void spi_test_dump_message(struct spi_device *spi, struct spi_message *msg, bool dump_data) { struct spi_transfer *xfer; int i; u8 b; dev_info(&spi->dev, " spi_msg@%p\n", msg); if (msg->status) dev_info(&spi->dev, " status: %i\n", msg->status); dev_info(&spi->dev, " frame_length: %i\n", msg->frame_length); dev_info(&spi->dev, " actual_length: %i\n", msg->actual_length); list_for_each_entry(xfer, &msg->transfers, transfer_list) { dev_info(&spi->dev, " spi_transfer@%p\n", xfer); dev_info(&spi->dev, " len: %i\n", xfer->len); dev_info(&spi->dev, " tx_buf: %p\n", xfer->tx_buf); if (dump_data && xfer->tx_buf) spi_test_print_hex_dump(" TX: ", xfer->tx_buf, xfer->len); dev_info(&spi->dev, " rx_buf: %p\n", xfer->rx_buf); if (dump_data && xfer->rx_buf) spi_test_print_hex_dump(" RX: ", xfer->rx_buf, xfer->len); /* check for unwritten test pattern on rx_buf */ if (xfer->rx_buf) { for (i = 0 ; i < xfer->len ; i++) { b = ((u8 *)xfer->rx_buf)[xfer->len - 1 - i]; if (b != SPI_TEST_PATTERN_UNWRITTEN) break; } if (i) dev_info(&spi->dev, " rx_buf filled with %02x starts at offset: %i\n", SPI_TEST_PATTERN_UNWRITTEN, xfer->len - i); } } } struct rx_ranges { struct list_head list; u8 *start; u8 *end; }; static int rx_ranges_cmp(void *priv, const struct list_head *a, const struct list_head *b) { const struct rx_ranges *rx_a = list_entry(a, struct rx_ranges, list); const struct rx_ranges *rx_b = list_entry(b, struct rx_ranges, list); if (rx_a->start > rx_b->start) return 1;