1123 lines · 27.0 KB · C
RawDownload
// 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.