Commit 7828b7bb authored by Wolfram Sang's avatar Wolfram Sang Committed by Bartosz Golaszewski
Browse files

gpio: add sloppy logic analyzer using polling



This is a sloppy logic analyzer using GPIOs. It comes with a script to
isolate a CPU for polling. While this is definitely not a production
level analyzer, it can be a helpful first view when remote debugging.
Read the documentation for details.

Signed-off-by: default avatarWolfram Sang <wsa+renesas@sang-engineering.com>
Reviewed-by: default avatarLinus Walleij <linus.walleij@linaro.org>
Link: https://lore.kernel.org/r/20240620094159.6785-2-wsa+renesas@sang-engineering.com


[Bartosz: moved the Kconfig entry into a different category]
Signed-off-by: default avatarBartosz Golaszewski <bartosz.golaszewski@linaro.org>
parent 6a9c1508
Loading
Loading
Loading
Loading
+93 −0
Original line number Diff line number Diff line
.. SPDX-License-Identifier: GPL-2.0

=============================================
Linux Kernel GPIO based sloppy logic analyzer
=============================================

:Author: Wolfram Sang

Introduction
============

This document briefly describes how to run the GPIO based in-kernel sloppy
logic analyzer running on an isolated CPU.

The sloppy logic analyzer will utilize a few GPIO lines in input mode on a
system to rapidly sample these digital lines, which will, if the Nyquist
criteria is met, result in a time series log with approximate waveforms as they
appeared on these lines. One way to use it is to analyze external traffic
connected to these GPIO lines with wires (i.e. digital probes), acting as a
common logic analyzer.

Another feature is to snoop on on-chip peripherals if the I/O cells of these
peripherals can be used in GPIO input mode at the same time as they are being
used as inputs or outputs for the peripheral. That means you could e.g. snoop
I2C traffic without any wiring (if your hardware supports it). In the pin
control subsystem such pin controllers are called "non-strict": a certain pin
can be used with a certain peripheral and as a GPIO input line at the same
time.

Note that this is a last resort analyzer which can be affected by latencies,
non-deterministic code paths and non-maskable interrupts. It is called 'sloppy'
for a reason. However, for e.g. remote development, it may be useful to get a
first view and aid further debugging.

Setup
=====

Your kernel must have CONFIG_DEBUG_FS and CONFIG_CPUSETS enabled. Ideally, your
runtime environment does not utilize cpusets otherwise, then isolation of a CPU
core is easiest. If you do need cpusets, check that helper script for the
sloppy logic analyzer does not interfere with your other settings.

Tell the kernel which GPIOs are used as probes. For a Device Tree based system,
you need to use the following bindings. Because these bindings are only for
debugging, there is no official schema::

    i2c-analyzer {
            compatible = "gpio-sloppy-logic-analyzer";
            probe-gpios = <&gpio6 21 GPIO_OPEN_DRAIN>, <&gpio6 4 GPIO_OPEN_DRAIN>;
            probe-names = "SCL", "SDA";
    };

Note that you must provide a name for every GPIO specified. Currently a
maximum of 8 probes are supported. 32 are likely possible but are not
implemented yet.

Usage
=====

The logic analyzer is configurable via files in debugfs. However, it is
strongly recommended to not use them directly, but to use the script
``tools/gpio/gpio-sloppy-logic-analyzer``. Besides checking parameters more
extensively, it will isolate the CPU core so you will have the least
disturbance while measuring.

The script has a help option explaining the parameters. For the above DT
snippet which analyzes an I2C bus at 400kHz on a Renesas Salvator-XS board, the
following settings are used: The isolated CPU shall be CPU1 because it is a big
core in a big.LITTLE setup. Because CPU1 is the default, we don't need a
parameter. The bus speed is 400kHz. So, the sampling theorem says we need to
sample at least at 800kHz. However, falling edges of both signals in an I2C
start condition happen faster, so we need a higher sampling frequency, e.g.
``-s 1500000`` for 1.5MHz. Also, we don't want to sample right away but wait
for a start condition on an idle bus. So, we need to set a trigger to a falling
edge on SDA while SCL stays high, i.e. ``-t 1H+2F``. Last is the duration, let
us assume 15ms here which results in the parameter ``-d 15000``. So,
altogether::

    gpio-sloppy-logic-analyzer -s 1500000 -t 1H+2F -d 15000

Note that the process will return you back to the prompt but a sub-process is
still sampling in the background. Unless this has finished, you will not find a
result file in the current or specified directory. For the above example, we
will then need to trigger I2C communication::

    i2cdetect -y -r <your bus number>

Result is a .sr file to be consumed with PulseView or sigrok-cli from the free
`sigrok`_ project. It is a zip file which also contains the binary sample data
which may be consumed by other software. The filename is the logic analyzer
instance name plus a since-epoch timestamp.

.. _sigrok: https://sigrok.org/
+1 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ Documentation/dev-tools/testing-overview.rst
   kunit/index
   ktap
   checkuapi
   gpio-sloppy-logic-analyzer


.. only::  subproject and html
+19 −0
Original line number Diff line number Diff line
@@ -1891,4 +1891,23 @@ config GPIO_SIM

endmenu

menu "GPIO Debugging utilities"

config GPIO_SLOPPY_LOGIC_ANALYZER
	tristate "Sloppy GPIO logic analyzer"
	depends on (GPIOLIB || COMPILE_TEST) && CPUSETS && DEBUG_FS && EXPERT
	help
	  This option enables support for a sloppy logic analyzer using polled
	  GPIOs. Use the 'tools/gpio/gpio-sloppy-logic-analyzer' script with
	  this driver. The script will make it easier to use and will also
	  isolate a CPU for the polling task. Note that this is a last resort
	  analyzer which can be affected by latencies, non-deterministic code
	  paths, or NMIs. However, for e.g. remote development, it may be useful
	  to get a first view and aid further debugging.

	  If this driver is built as a module it will be called
	  'gpio-sloppy-logic-analyzer'.

endmenu

endif
+1 −0
Original line number Diff line number Diff line
@@ -150,6 +150,7 @@ obj-$(CONFIG_GPIO_SIFIVE) += gpio-sifive.o
obj-$(CONFIG_GPIO_SIM)			+= gpio-sim.o
obj-$(CONFIG_GPIO_SIOX)			+= gpio-siox.o
obj-$(CONFIG_GPIO_SL28CPLD)		+= gpio-sl28cpld.o
obj-$(CONFIG_GPIO_SLOPPY_LOGIC_ANALYZER) += gpio-sloppy-logic-analyzer.o
obj-$(CONFIG_GPIO_SODAVILLE)		+= gpio-sodaville.o
obj-$(CONFIG_GPIO_SPEAR_SPICS)		+= gpio-spear-spics.o
obj-$(CONFIG_GPIO_SPRD)			+= gpio-sprd.o
+344 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Sloppy logic analyzer using GPIOs (to be run on an isolated CPU)
 *
 * Use the 'gpio-sloppy-logic-analyzer' script in the 'tools/gpio' folder for
 * easier usage and further documentation. Note that this is a last resort
 * analyzer which can be affected by latencies and non-deterministic code
 * paths. However, for e.g. remote development, it may be useful to get a first
 * view and aid further debugging.
 *
 * Copyright (C) Wolfram Sang <wsa@sang-engineering.com>
 * Copyright (C) Renesas Electronics Corporation
 */

#include <linux/ctype.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/ktime.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/sizes.h>
#include <linux/timekeeping.h>
#include <linux/types.h>
#include <linux/vmalloc.h>

#define GPIO_LA_NAME "gpio-sloppy-logic-analyzer"
#define GPIO_LA_DEFAULT_BUF_SIZE SZ_256K
/* can be increased but then we need to extend the u8 buffers */
#define GPIO_LA_MAX_PROBES 8
#define GPIO_LA_NUM_TESTS 1024

struct gpio_la_poll_priv {
	struct mutex blob_lock; /* serialize access to the blob (data) */
	u32 buf_idx;
	struct gpio_descs *descs;
	unsigned long delay_ns;
	unsigned long acq_delay;
	struct debugfs_blob_wrapper blob;
	struct dentry *debug_dir;
	struct dentry *blob_dent;
	struct debugfs_blob_wrapper meta;
	struct device *dev;
	unsigned int trig_len;
	u8 *trig_data;
};

static struct dentry *gpio_la_poll_debug_dir;

static __always_inline int gpio_la_get_array(struct gpio_descs *d, unsigned long *sptr)
{
	int ret;

	ret = gpiod_get_array_value(d->ndescs, d->desc, d->info, sptr);
	if (ret == 0 && fatal_signal_pending(current))
		ret = -EINTR;

	return ret;
}

static int fops_capture_set(void *data, u64 val)
{
	struct gpio_la_poll_priv *priv = data;
	u8 *la_buf = priv->blob.data;
	unsigned long state = 0; /* zeroed because GPIO arrays are bitfields */
	unsigned long delay;
	ktime_t start_time;
	unsigned int i;
	int ret;

	if (!val)
		return 0;

	if (!la_buf)
		return -ENOMEM;

	if (!priv->delay_ns)
		return -EINVAL;

	mutex_lock(&priv->blob_lock);
	if (priv->blob_dent) {
		debugfs_remove(priv->blob_dent);
		priv->blob_dent = NULL;
	}

	priv->buf_idx = 0;

	local_irq_disable();
	preempt_disable_notrace();

	/* Measure delay of reading GPIOs */
	start_time = ktime_get();
	for (i = 0; i < GPIO_LA_NUM_TESTS; i++) {
		ret = gpio_la_get_array(priv->descs, &state);
		if (ret)
			goto out;
	}

	priv->acq_delay = ktime_sub(ktime_get(), start_time) / GPIO_LA_NUM_TESTS;
	if (priv->delay_ns < priv->acq_delay) {
		ret = -ERANGE;
		goto out;
	}

	delay = priv->delay_ns - priv->acq_delay;

	/* Wait for triggers */
	for (i = 0; i < priv->trig_len; i += 2) {
		do {
			ret = gpio_la_get_array(priv->descs, &state);
			if (ret)
				goto out;

			ndelay(delay);
		} while ((state & priv->trig_data[i]) != priv->trig_data[i + 1]);
	}

	/* With triggers, final state is also the first sample */
	if (priv->trig_len)
		la_buf[priv->buf_idx++] = state;

	/* Sample */
	while (priv->buf_idx < priv->blob.size) {
		ret = gpio_la_get_array(priv->descs, &state);
		if (ret)
			goto out;

		la_buf[priv->buf_idx++] = state;
		ndelay(delay);
	}
out:
	preempt_enable_notrace();
	local_irq_enable();
	if (ret)
		dev_err(priv->dev, "couldn't read GPIOs: %d\n", ret);

	kfree(priv->trig_data);
	priv->trig_data = NULL;
	priv->trig_len = 0;

	priv->blob_dent = debugfs_create_blob("sample_data", 0400, priv->debug_dir, &priv->blob);
	mutex_unlock(&priv->blob_lock);

	return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_capture, NULL, fops_capture_set, "%llu\n");

static int fops_buf_size_get(void *data, u64 *val)
{
	struct gpio_la_poll_priv *priv = data;

	*val = priv->blob.size;

	return 0;
}

static int fops_buf_size_set(void *data, u64 val)
{
	struct gpio_la_poll_priv *priv = data;
	int ret = 0;
	void *p;

	if (!val)
		return -EINVAL;

	mutex_lock(&priv->blob_lock);

	vfree(priv->blob.data);
	p = vzalloc(val);
	if (!p) {
		val = 0;
		ret = -ENOMEM;
	}

	priv->blob.data = p;
	priv->blob.size = val;

	mutex_unlock(&priv->blob_lock);
	return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(fops_buf_size, fops_buf_size_get, fops_buf_size_set, "%llu\n");

static int trigger_open(struct inode *inode, struct file *file)
{
	return single_open(file, NULL, inode->i_private);
}

static ssize_t trigger_write(struct file *file, const char __user *ubuf,
			     size_t count, loff_t *offset)
{
	struct seq_file *m = file->private_data;
	struct gpio_la_poll_priv *priv = m->private;
	char *buf;

	/* upper limit is arbitrary but should be less than PAGE_SIZE */
	if (count > 2048 || count & 1)
		return -EINVAL;

	buf = memdup_user(ubuf, count);
	if (IS_ERR(buf))
		return PTR_ERR(buf);

	priv->trig_data = buf;
	priv->trig_len = count;

	return count;
}

static const struct file_operations fops_trigger = {
	.owner = THIS_MODULE,
	.open = trigger_open,
	.write = trigger_write,
	.llseek = no_llseek,
	.release = single_release,
};

static int gpio_la_poll_probe(struct platform_device *pdev)
{
	struct gpio_la_poll_priv *priv;
	struct device *dev = &pdev->dev;
	const char *devname = dev_name(dev);
	const char *gpio_names[GPIO_LA_MAX_PROBES];
	char *meta = NULL;
	unsigned int i, meta_len = 0;
	int ret;

	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	devm_mutex_init(dev, &priv->blob_lock);

	fops_buf_size_set(priv, GPIO_LA_DEFAULT_BUF_SIZE);

	priv->descs = devm_gpiod_get_array(dev, "probe", GPIOD_IN);
	if (IS_ERR(priv->descs))
		return PTR_ERR(priv->descs);

	/* artificial limit to keep 1 byte per sample for now */
	if (priv->descs->ndescs > GPIO_LA_MAX_PROBES)
		return -EFBIG;

	ret = device_property_read_string_array(dev, "probe-names", gpio_names,
						priv->descs->ndescs);
	if (ret >= 0 && ret != priv->descs->ndescs)
		ret = -EBADR;
	if (ret < 0)
		return dev_err_probe(dev, ret, "error naming the GPIOs");

	for (i = 0; i < priv->descs->ndescs; i++) {
		unsigned int add_len;
		char *new_meta, *consumer_name;

		if (gpiod_cansleep(priv->descs->desc[i]))
			return -EREMOTE;

		consumer_name = kasprintf(GFP_KERNEL, "%s: %s", devname, gpio_names[i]);
		if (!consumer_name)
			return -ENOMEM;
		gpiod_set_consumer_name(priv->descs->desc[i], consumer_name);
		kfree(consumer_name);

		/* '10' is length of 'probe00=\n\0' */
		add_len = strlen(gpio_names[i]) + 10;

		new_meta = devm_krealloc(dev, meta, meta_len + add_len, GFP_KERNEL);
		if (!new_meta)
			return -ENOMEM;

		meta = new_meta;
		meta_len += snprintf(meta + meta_len, add_len, "probe%02u=%s\n",
				     i + 1, gpio_names[i]);
	}

	platform_set_drvdata(pdev, priv);
	priv->dev = dev;

	priv->meta.data = meta;
	priv->meta.size = meta_len;
	priv->debug_dir = debugfs_create_dir(devname, gpio_la_poll_debug_dir);
	debugfs_create_blob("meta_data", 0400, priv->debug_dir, &priv->meta);
	debugfs_create_ulong("delay_ns", 0600, priv->debug_dir, &priv->delay_ns);
	debugfs_create_ulong("delay_ns_acquisition", 0400, priv->debug_dir, &priv->acq_delay);
	debugfs_create_file_unsafe("buf_size", 0600, priv->debug_dir, priv, &fops_buf_size);
	debugfs_create_file_unsafe("capture", 0200, priv->debug_dir, priv, &fops_capture);
	debugfs_create_file_unsafe("trigger", 0200, priv->debug_dir, priv, &fops_trigger);

	return 0;
}

static void gpio_la_poll_remove(struct platform_device *pdev)
{
	struct gpio_la_poll_priv *priv = platform_get_drvdata(pdev);

	mutex_lock(&priv->blob_lock);
	debugfs_remove_recursive(priv->debug_dir);
	mutex_unlock(&priv->blob_lock);
}

static const struct of_device_id gpio_la_poll_of_match[] = {
	{ .compatible = GPIO_LA_NAME },
	{ }
};
MODULE_DEVICE_TABLE(of, gpio_la_poll_of_match);

static struct platform_driver gpio_la_poll_device_driver = {
	.probe = gpio_la_poll_probe,
	.remove_new = gpio_la_poll_remove,
	.driver = {
		.name = GPIO_LA_NAME,
		.of_match_table = gpio_la_poll_of_match,
	}
};

static int __init gpio_la_poll_init(void)
{
	gpio_la_poll_debug_dir = debugfs_create_dir(GPIO_LA_NAME, NULL);

	return platform_driver_register(&gpio_la_poll_device_driver);
}
/*
 * Non-strict pin controllers can read GPIOs while being muxed to something else.
 * To support that, we need to claim GPIOs before further pinmuxing happens. So,
 * we probe early using 'late_initcall'
 */
late_initcall(gpio_la_poll_init);

static void __exit gpio_la_poll_exit(void)
{
	platform_driver_unregister(&gpio_la_poll_device_driver);
	debugfs_remove_recursive(gpio_la_poll_debug_dir);
}
module_exit(gpio_la_poll_exit);

MODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>");
MODULE_DESCRIPTION("Sloppy logic analyzer using GPIOs");
MODULE_LICENSE("GPL");
Loading