[v3,-next,1/5] spi: mockup: Add SPI controller testing driver

Message ID 20231104064650.972687-2-zhangxiaoxu@huaweicloud.com
State New
Headers
Series spi: Introduce BPF based SPI mockup controller |

Commit Message

huaweicloud Nov. 4, 2023, 6:46 a.m. UTC
  From: Zhang Xiaoxu <zhangxiaoxu5@huawei.com>

This enables SPI controller Testing driver, which provides a way to
test SPI subsystem.

This is accomplished by executing the following command:

$ echo adcxx1s 0 > /sys/class/spi_master/spi0/new_device

The name of the target driver and its chip select were used to
instantiate the device.

$ ls /sys/bus/spi/devices/spi0.0/hwmon/hwmon0/ -l
 total 0
 lrwxrwxrwx 1 root root    0 Aug 10 08:58 device -> ../../../spi0.0
 drwxr-xr-x 2 root root    0 Aug 10 08:58 power
 lrwxrwxrwx 1 root root    0 Aug 10 08:58 subsystem -> ../../../../../../../../class/hwmon
 -rw-r--r-- 1 root root 4096 Aug 10 08:58 uevent

Remove target device by executing the following command:
$ echo 0 > /sys/class/spi_master/spi0/delete_device

Signed-off-by: Wei Yongjun <weiyongjun1@huawei.com>
Signed-off-by: Zhang Xiaoxu <zhangxiaoxu5@huawei.com>
---
 drivers/spi/Kconfig      |  12 +++
 drivers/spi/Makefile     |   1 +
 drivers/spi/spi-mockup.c | 211 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 224 insertions(+)
 create mode 100644 drivers/spi/spi-mockup.c
  

Comments

Mark Brown Nov. 6, 2023, 11:59 a.m. UTC | #1
On Sat, Nov 04, 2023 at 02:46:46PM +0800, Zhang Xiaoxu wrote:

> This is accomplished by executing the following command:
> 
> $ echo adcxx1s 0 > /sys/class/spi_master/spi0/new_device

That's not a valid sysfs format, sysfs requires one value per file.
configfs might be a better fit?

> +config SPI_MOCKUP
> +	tristate "SPI controller Testing Driver"
> +	depends on OF

Why would this depend on DT?  Given that any test SPI controller is a
virtual device it should never appear in DT and we probably shouldn't
require providing DT for the created devices even if we implement
support for that, only some devices might care.
`
> +++ b/drivers/spi/spi-mockup.c
> @@ -0,0 +1,211 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * SPI controller Testing Driver
> + *
> + * Copyright(c) 2022 Huawei Technologies Co., Ltd.
> + */

Please keep the entire comment a C++ one so things look more
intentional.

> +#define MOCKUP_CHIPSELECT_MAX		8

Why would we have a hard coded limit here?

> +	blank = strchr(buf, ' ');
> +	if (!blank) {
> +		dev_err(dev, "%s: Extra parameters\n", "new_device");
> +		return -EINVAL;
> +	}

There is no point in using %s to render a constant string.

> +static const struct of_device_id spi_mockup_match[] = {
> +	{ .compatible = "spi-mockup", },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, spi_mockup_match);

If we were going to instantiate this via DT we'd need a binding, but as
I indicated above since this is purely virtual and not even something
like virtual hardware provided by a VMM but rather just something kernel
internal we should probably not be using DT at all.  Providing a device
facing DT interface might be useful, but that's a second stage thing.
  
huaweicloud Nov. 18, 2023, 11:07 a.m. UTC | #2
Hi Mark,

Thanks for your review.

Most of the comments have been modified. and the v4 has been sent
to the maillist, as has the KDDV (Kernel Device Driver Verfication)
test framework based on python unittests.

Looking forward for your comments.

Thanks.


在 2023/11/6 19:59, Mark Brown 写道:
> On Sat, Nov 04, 2023 at 02:46:46PM +0800, Zhang Xiaoxu wrote:
> 
>> This is accomplished by executing the following command:
>>
>> $ echo adcxx1s 0 > /sys/class/spi_master/spi0/new_device
> 
> That's not a valid sysfs format, sysfs requires one value per file.
> configfs might be a better fit?
> 
>> +config SPI_MOCKUP
>> +	tristate "SPI controller Testing Driver"
>> +	depends on OF
> 
> Why would this depend on DT?  Given that any test SPI controller is a
> virtual device it should never appear in DT and we probably shouldn't
> require providing DT for the created devices even if we implement
> support for that, only some devices might care.
> `
>> +++ b/drivers/spi/spi-mockup.c
>> @@ -0,0 +1,211 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * SPI controller Testing Driver
>> + *
>> + * Copyright(c) 2022 Huawei Technologies Co., Ltd.
>> + */
> 
> Please keep the entire comment a C++ one so things look more
> intentional.
> 
>> +#define MOCKUP_CHIPSELECT_MAX		8
> 
> Why would we have a hard coded limit here?
When register the spi controller, we need to specify the maximun
number of chips. Modify it to U16_MAX in next version.
> 
>> +	blank = strchr(buf, ' ');
>> +	if (!blank) {
>> +		dev_err(dev, "%s: Extra parameters\n", "new_device");
>> +		return -EINVAL;
>> +	}
> 
> There is no point in using %s to render a constant string.
> 
>> +static const struct of_device_id spi_mockup_match[] = {
>> +	{ .compatible = "spi-mockup", },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(of, spi_mockup_match);
> 
> If we were going to instantiate this via DT we'd need a binding, but as
> I indicated above since this is purely virtual and not even something
> like virtual hardware provided by a VMM but rather just something kernel
> internal we should probably not be using DT at all.  Providing a device
> facing DT interface might be useful, but that's a second stage thing.
  

Patch

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 2c21d5b96fdc..9169081cfecb 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -1218,6 +1218,18 @@  config SPI_TLE62X0
 	  sysfs interface, with each line presented as a kind of GPIO
 	  exposing both switch control and diagnostic feedback.
 
+config SPI_MOCKUP
+	tristate "SPI controller Testing Driver"
+	depends on OF
+	help
+	  This enables SPI controller testing driver, which provides a way to
+	  test SPI subsystem.
+
+	  If you do build this module, be sure to read the notes and warnings
+	  in <file:Documentation/spi/spi-mockup.rst>.
+
+	  If you don't know what to do here, definitely say N.
+
 #
 # Add new SPI protocol masters in alphabetical order above this line
 #
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 6af54842b9fa..f28074e61df9 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -12,6 +12,7 @@  obj-$(CONFIG_SPI_MEM)			+= spi-mem.o
 obj-$(CONFIG_SPI_MUX)			+= spi-mux.o
 obj-$(CONFIG_SPI_SPIDEV)		+= spidev.o
 obj-$(CONFIG_SPI_LOOPBACK_TEST)		+= spi-loopback-test.o
+obj-$(CONFIG_SPI_MOCKUP)		+= spi-mockup.o
 
 # SPI master controller drivers (bus)
 obj-$(CONFIG_SPI_ALTERA)		+= spi-altera-platform.o
diff --git a/drivers/spi/spi-mockup.c b/drivers/spi/spi-mockup.c
new file mode 100644
index 000000000000..683a0fc43f0d
--- /dev/null
+++ b/drivers/spi/spi-mockup.c
@@ -0,0 +1,211 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SPI controller Testing Driver
+ *
+ * Copyright(c) 2022 Huawei Technologies Co., Ltd.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+
+#define MOCKUP_CHIPSELECT_MAX		8
+
+struct mockup_spi {
+	struct mutex lock;
+	struct spi_device *devs[MOCKUP_CHIPSELECT_MAX];
+};
+
+static struct spi_controller *to_spi_controller(struct device *dev)
+{
+	return container_of(dev, struct spi_controller, dev);
+}
+
+static ssize_t
+new_device_store(struct device *dev, struct device_attribute *attr,
+		 const char *buf, size_t count)
+{
+	struct spi_controller *ctrl = to_spi_controller(dev);
+	struct spi_board_info info;
+	struct mockup_spi *mock;
+	struct spi_device *spi;
+	char *blank, end;
+	int status;
+
+	memset(&info, 0, sizeof(struct spi_board_info));
+
+	blank = strchr(buf, ' ');
+	if (!blank) {
+		dev_err(dev, "%s: Extra parameters\n", "new_device");
+		return -EINVAL;
+	}
+
+	if (blank - buf > SPI_NAME_SIZE - 1) {
+		dev_err(dev, "%s: Invalid device name\n", "new_device");
+		return -EINVAL;
+	}
+
+	memcpy(info.modalias, buf, blank - buf);
+
+	status = sscanf(++blank, "%hi%c", &info.chip_select, &end);
+	if (status < 1) {
+		dev_err(dev, "%s: Can't parse SPI chipselect\n", "new_device");
+		return -EINVAL;
+	}
+
+	if (status > 1 && end != '\n') {
+		dev_err(dev, "%s: Extra parameters\n", "new_device");
+		return -EINVAL;
+	}
+
+	if (info.chip_select >= ctrl->num_chipselect) {
+		dev_err(dev, "%s: Invalid chip_select\n", "new_device");
+		return -EINVAL;
+	}
+
+	mock = spi_controller_get_devdata(ctrl);
+	mutex_lock(&mock->lock);
+
+	if (mock->devs[info.chip_select]) {
+		dev_err(dev, "%s: Chipselect %d already in use\n",
+			"new_device", info.chip_select);
+		mutex_unlock(&mock->lock);
+		return -EINVAL;
+	}
+
+	spi = spi_new_device(ctrl, &info);
+	if (!spi) {
+		mutex_unlock(&mock->lock);
+		return -ENOMEM;
+	}
+	mock->devs[info.chip_select] = spi;
+
+	mutex_unlock(&mock->lock);
+
+	dev_info(dev, "%s: Instantiated device %s at 0x%02x\n", "new_device",
+		 info.modalias, info.chip_select);
+
+	return count;
+}
+static DEVICE_ATTR_WO(new_device);
+
+static ssize_t
+delete_device_store(struct device *dev, struct device_attribute *attr,
+		    const char *buf, size_t count)
+{
+	struct spi_controller *ctrl = to_spi_controller(dev);
+	struct mockup_spi *mock;
+	struct spi_device *spi;
+	unsigned short chip;
+	char end;
+	int res;
+
+	/* Parse parameters, reject extra parameters */
+	res = sscanf(buf, "%hi%c", &chip, &end);
+	if (res < 1) {
+		dev_err(dev, "%s: Can't parse SPI address\n", "delete_device");
+		return -EINVAL;
+	}
+	if (res > 1  && end != '\n') {
+		dev_err(dev, "%s: Extra parameters\n", "delete_device");
+		return -EINVAL;
+	}
+
+	if (chip >= ctrl->num_chipselect) {
+		dev_err(dev, "%s: Invalid chip_select\n", "delete_device");
+		return -EINVAL;
+	}
+
+	mock = spi_controller_get_devdata(ctrl);
+	mutex_lock(&mock->lock);
+
+	spi = mock->devs[chip];
+	if (!spi) {
+		mutex_unlock(&mock->lock);
+		dev_err(dev, "%s: Invalid chip_select\n", "delete_device");
+		return -ENOENT;
+	}
+
+	dev_info(dev, "%s: Deleting device %s at 0x%02hx\n", "delete_device",
+		 dev_name(&spi->dev), chip);
+
+	spi_unregister_device(spi);
+	mock->devs[chip] = NULL;
+
+	mutex_unlock(&mock->lock);
+
+	return count;
+}
+static DEVICE_ATTR_WO(delete_device);
+
+static struct attribute *spi_mockup_attrs[] = {
+	&dev_attr_new_device.attr,
+	&dev_attr_delete_device.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(spi_mockup);
+
+static int spi_mockup_transfer(struct spi_controller *ctrl,
+			       struct spi_message *msg)
+{
+	msg->status = 0;
+	spi_finalize_current_message(ctrl);
+
+	return 0;
+}
+
+static int spi_mockup_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct mockup_spi *mock;
+	struct spi_controller *ctrl;
+
+	ctrl = spi_alloc_host(&pdev->dev, sizeof(struct mockup_spi));
+	if (!ctrl) {
+		pr_err("failed to alloc spi controller\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(pdev, ctrl);
+
+	ctrl->dev.of_node = pdev->dev.of_node;
+	ctrl->dev.groups = spi_mockup_groups;
+	ctrl->num_chipselect = MOCKUP_CHIPSELECT_MAX;
+	ctrl->mode_bits = SPI_MODE_USER_MASK;
+	ctrl->bus_num = 0;
+	ctrl->transfer_one_message = spi_mockup_transfer;
+
+	mock = spi_controller_get_devdata(ctrl);
+	mutex_init(&mock->lock);
+
+	ret = devm_spi_register_controller(&pdev->dev, ctrl);
+	if (ret) {
+		spi_controller_put(ctrl);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct of_device_id spi_mockup_match[] = {
+	{ .compatible = "spi-mockup", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, spi_mockup_match);
+
+static struct platform_driver spi_mockup_driver = {
+	.probe = spi_mockup_probe,
+	.driver = {
+		.name = "spi-mockup",
+		.of_match_table = spi_mockup_match,
+	},
+};
+module_platform_driver(spi_mockup_driver);
+
+MODULE_AUTHOR("Wei Yongjun <weiyongjun1@huawei.com>");
+MODULE_DESCRIPTION("SPI controller Testing Driver");
+MODULE_LICENSE("GPL");