This is a simple logic analyzer using GPIO polling. It comes with a script to isolate a CPU for polling. While this is definately not a production level analyzer, it can be a helpful first view when remote debugging. Read the documentation for details.
Signed-off-by: Wolfram Sang <wsa+rene...@sang-engineering.com> --- .../dev-tools/gpio-logic-analyzer.rst | 63 ++++ Documentation/dev-tools/index.rst | 1 + .../bindings/misc/gpio-logic-analyzer.yaml | 40 ++ drivers/misc/Kconfig | 12 + drivers/misc/Makefile | 1 + drivers/misc/gpio-logic-analyzer.c | 355 ++++++++++++++++++ tools/debugging/gpio-logic-analyzer | 156 ++++++++ 7 files changed, 628 insertions(+) create mode 100644 Documentation/dev-tools/gpio-logic-analyzer.rst create mode 100644 Documentation/devicetree/bindings/misc/gpio-logic-analyzer.yaml create mode 100644 drivers/misc/gpio-logic-analyzer.c create mode 100755 tools/debugging/gpio-logic-analyzer diff --git a/Documentation/dev-tools/gpio-logic-analyzer.rst b/Documentation/dev-tools/gpio-logic-analyzer.rst new file mode 100644 index 000000000000..2847260736d4 --- /dev/null +++ b/Documentation/dev-tools/gpio-logic-analyzer.rst @@ -0,0 +1,63 @@ +Linux Kernel GPIO based logic analyzer +====================================== + +:Author: Wolfram Sang + +Introduction +------------ + +This document briefly describes how to run the software based in-kernel logic +analyzer. + +Note that this is still a last resort analyzer which can be affected by +latencies and non-determinant code paths. However, for e.g. remote development, +it may be useful to get a first view and aid further debugging. + +Setup +----- + +Tell the kernel which GPIOs are used as probes. For a DT based system: + + i2c-analyzer { + compatible = "gpio-logic-analyzer"; + probe-gpios = <&gpio6 21 GPIO_OPEN_DRAIN>, <&gpio6 4 GPIO_OPEN_DRAIN>; + probe-names = "SCL", "SDA"; + }; + +The binding documentation is in the ``misc`` folder of the Kernel binding +documentation. + +Usage +----- + +The logic analyzer is configurable via files in debugfs. However, it is +strongly recommended to not use them directly, but to to use the +``gpio-logic-analyzer`` script in the ``tools/debugging`` directory. Besides +checking parameters more extensively, it will isolate a CPU core for you, so +you will have least disturbance while measuring. + +The script has a help option explaining the parameters. For the above DT +snipplet 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 of both, SDA and SCL, in a start +condition is 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, i.e. ``-t "2F"``. Last is the duration, let us assume 15ms here which +results in the parameter ``-d 15000``. So, altogether: + + gpio-logic-analyzer -s 1500000 -t "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 finished, you will not find a +result file in the current or specified directory. Please also note that +currently this sub-process is not killable! 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 from the free Sigrok project. It is +a zip file which also contains the binary sample data which may be consumed by others. +The filename is the logic analyzer instance name plus a since-epoch timestamp. diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst index 1b1cf4f5c9d9..9e0168bd3698 100644 --- a/Documentation/dev-tools/index.rst +++ b/Documentation/dev-tools/index.rst @@ -27,6 +27,7 @@ whole; patches welcome! kgdb kselftest kunit/index + gpio-logic-analyzer .. only:: subproject and html diff --git a/Documentation/devicetree/bindings/misc/gpio-logic-analyzer.yaml b/Documentation/devicetree/bindings/misc/gpio-logic-analyzer.yaml new file mode 100644 index 000000000000..e664cec85a72 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/gpio-logic-analyzer.yaml @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: GPL-2.0 +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/misc/gpio-logic-analyzer.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Bindings for a GPIO based logic analyzer + +maintainers: + - Wolfram Sang <w...@sang-engineering.com> + +properties: + compatible: + items: + - const: gpio-logic-analyzer + + probe-gpios: + description: + gpios used as probes for the logic analyzer + + probe-names: + description: + names used to distinguish the probes + + required: + - compatible + - probe-gpios + +additionalProperties: false + +examples: + - | + i2c-analyzer { + compatible = "gpio-logic-analyzer"; + + probe-gpios = <&gpio6 21 GPIO_OPEN_DRAIN>, <&gpio6 4 GPIO_OPEN_DRAIN>; + probe-names = "SCL", "SDA"; + }; + +... diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index f532c59bb59b..6b1c1c951d74 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -445,6 +445,18 @@ config HISI_HIKEY_USB switching between the dual-role USB-C port and the USB-A host ports using only one USB controller. +config GPIO_LOGIC_ANALYZER + tristate "Simple GPIO logic analyzer" + depends on GPIOLIB || COMPILE_TEST + help + This option enables support for a simple logic analyzer using polled + GPIOs. Use the 'tools/debugging/gpio-logic-analyzer' script with this + driver. The script will make using it easier and can also isolate a + CPU for the polling task. Note that this is still a last resort + analyzer which can be affected by latencies and non-determinant code + paths. However, for e.g. remote development, it may be useful to get + a first view and aid further debugging. + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 99b6f15a3c70..cff9c0a2a2fb 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -56,3 +56,4 @@ obj-$(CONFIG_HABANA_AI) += habanalabs/ obj-$(CONFIG_UACCE) += uacce/ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o +obj-$(CONFIG_GPIO_LOGIC_ANALYZER) += gpio-logic-analyzer.o diff --git a/drivers/misc/gpio-logic-analyzer.c b/drivers/misc/gpio-logic-analyzer.c new file mode 100644 index 000000000000..3c8d5e1f8489 --- /dev/null +++ b/drivers/misc/gpio-logic-analyzer.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Simple logic analyzer using GPIOs (to be run on an isolated CPU) + * + * Use the 'gpio-logic-analyzer' script in the 'tools/debugging' folder for + * easier usage and further documentation. Note that this is still a last resort + * analyzer which can be affected by latencies and non-determinant 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 <w...@sang-engineering.com> + * Copyright (C) Renesas Electronics Corporation + */ + +#include <linux/ctype.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/ktime.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/sizes.h> +#include <linux/timekeeping.h> +#include <linux/vmalloc.h> + +#define GPIO_LA_NAME "gpio-logic-analyzer" +#define GPIO_LA_DEFAULT_BUF_SIZE SZ_256K +/* can be increased if needed */ +#define GPIO_LA_MAX_PROBES 8 +#define GPIO_LA_PROBES_MASK 7 + +struct gpio_la_poll_priv { + unsigned long ndelay; + u32 buf_idx; + struct mutex lock; + struct debugfs_blob_wrapper blob; + struct gpio_descs *descs; + struct dentry *debug_dir, *blob_dent; + struct debugfs_blob_wrapper meta; + unsigned long gpio_delay; + unsigned int trigger_len; + u8 trigger_data[PAGE_SIZE]; +}; + +static struct dentry *gpio_la_poll_debug_dir; + +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; + int i, ret; + + if (!la_buf) + return -ENOMEM; + + if (val) { + mutex_lock(&priv->lock); + if (priv->blob_dent) { + debugfs_remove(priv->blob_dent); + priv->blob_dent = NULL; + } + + priv->buf_idx = 0; + + local_irq_disable(); + preempt_disable_notrace(); + + for (i = 0; i < priv->trigger_len; i++) { + u8 data = priv->trigger_data[i]; + + do { + ret = gpiod_get_array_value(priv->descs->ndescs, priv->descs->desc, + priv->descs->info, &state); + + if (ret) + goto gpio_err; + } while (!!(state & BIT(data & GPIO_LA_PROBES_MASK)) != !!(data & 0x80)); + } + + if (priv->trigger_len) { + la_buf[priv->buf_idx++] = state; + ndelay(priv->ndelay); + } + + while (priv->buf_idx < priv->blob.size && ret == 0) { + ret = gpiod_get_array_value(priv->descs->ndescs, priv->descs->desc, + priv->descs->info, &state); + la_buf[priv->buf_idx++] = state; + ndelay(priv->ndelay); + } +gpio_err: + preempt_enable_notrace(); + local_irq_enable(); + if (ret) + pr_err("%s: couldn't read GPIOs: %d\n", __func__, ret); + + priv->blob_dent = debugfs_create_blob("sample_data", 0400, priv->debug_dir, &priv->blob); + mutex_unlock(&priv->lock); + } + + return 0; +} +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->lock); + + vfree(priv->blob.data); + p = vzalloc(val); + if (!p) { + /* Try the old value again */ + val = priv->blob.size; + p = vzalloc(val); + if (!p) { + val = 0; + ret = -ENOMEM; + } + } + + priv->blob.data = p; + priv->blob.size = val; + + mutex_unlock(&priv->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; + int i, trigger_len = 0; + + priv->trigger_len = 0; + + if (count & 1) + return -EINVAL; + + buf = memdup_user(ubuf, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + for (i = 0; i < count; i += 2) { + u8 val; + + if (buf[i] < '1' || buf[i] > '0' + GPIO_LA_MAX_PROBES) + goto bail_out; + + val = buf[i] - '1'; + + switch (toupper(buf[i + 1])) { + case 'L': + priv->trigger_data[trigger_len] = val; + trigger_len++; + break; + case 'H': + priv->trigger_data[trigger_len] = val | 0x80; + trigger_len++; + break; + case 'R': + priv->trigger_data[trigger_len] = val; + priv->trigger_data[trigger_len + 1] = val | 0x80; + trigger_len += 2; + break; + case 'F': + priv->trigger_data[trigger_len] = val | 0x80; + priv->trigger_data[trigger_len + 1] = val; + trigger_len += 2; + break; + default: + goto bail_out; + } + + if (trigger_len > PAGE_SIZE) /* should never happen */ + goto bail_out; + + } + + priv->trigger_len = trigger_len; + +bail_out: + kfree(buf); + return priv->trigger_len ? count : -EINVAL; +} + +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; + char *meta = NULL; + unsigned long state; + ktime_t start_time, end_time; + int ret, i; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->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 -ERANGE; + + for (i = 0; i < priv->descs->ndescs; i++) { + const char *str, *old_meta; + char *def_name = "ProbeX\0"; + + if (gpiod_cansleep(priv->descs->desc[i])) + return -EREMOTE; + + ret = of_property_read_string_index(pdev->dev.of_node, "probe-names", + i, &str); + + /* Hacky way of providing a fallback name if none provided */ + if (ret) { + def_name[5] = i + '1'; /* assumes GPIO_LA_MAX_PROBES = 8 */ + str = def_name; + } + + gpiod_set_consumer_name(priv->descs->desc[i], str); + + old_meta = meta; + meta = devm_kasprintf(dev, GFP_KERNEL, "%sprobe%d=%s\n", + old_meta ?: "", i + 1, str); + if (!meta) + return -ENOMEM; + + devm_kfree(dev, old_meta); + } + + platform_set_drvdata(pdev, priv); + + /* Measure delay of reading GPIOs */ + local_irq_disable(); + preempt_disable_notrace(); + start_time = ktime_get(); + for (i = 0, ret = 0; i < 1024 && ret == 0; i++) + ret = gpiod_get_array_value(priv->descs->ndescs, priv->descs->desc, + priv->descs->info, &state); + end_time = ktime_get(); + preempt_enable_notrace(); + local_irq_enable(); + if (ret) { + dev_err(dev, "couldn't read GPIOs: %d\n", ret); + return ret; + } + + priv->gpio_delay = ktime_sub(end_time, start_time) / 1024; + + priv->debug_dir = debugfs_create_dir(dev_name(dev), gpio_la_poll_debug_dir); + if (IS_ERR(priv->debug_dir)) + return PTR_ERR(priv->debug_dir); + + priv->meta.data = meta; + priv->meta.size = strlen(meta); + debugfs_create_blob("meta_data", 0400, priv->debug_dir, &priv->meta); + debugfs_create_ulong("delay_ns_acquisition", 0400, priv->debug_dir, &priv->gpio_delay); + debugfs_create_ulong("delay_ns_user", 0600, priv->debug_dir, &priv->ndelay); + 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 int gpio_la_poll_remove(struct platform_device *pdev) +{ + struct gpio_la_poll_priv *priv = platform_get_drvdata(pdev); + + mutex_lock(&priv->lock); + debugfs_remove_recursive(priv->debug_dir); + mutex_unlock(&priv->lock); + + return 0; +} + +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 = 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); + if (IS_ERR(gpio_la_poll_debug_dir)) + return PTR_ERR(gpio_la_poll_debug_dir); + + return platform_driver_register(&gpio_la_poll_device_driver); +} +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 <w...@sang-engineering.com>"); +MODULE_DESCRIPTION("Simple logic analyzer using GPIOs"); +MODULE_LICENSE("GPL v2"); diff --git a/tools/debugging/gpio-logic-analyzer b/tools/debugging/gpio-logic-analyzer new file mode 100755 index 000000000000..4506f67c18bc --- /dev/null +++ b/tools/debugging/gpio-logic-analyzer @@ -0,0 +1,156 @@ +#! /bin/sh + +INITCPU= +SAMPLEFREQ=1000000 +NUMSAMPLES=250000 +LASYSFSDIR= +CPUSETDIR='/dev/cpuset' +LACPUSETDIR="$CPUSETDIR/gpio-logic-analyzer" +SYSFSDIR='/sys/kernel/debug/gpio-logic-analyzer/' +OUTPUTDIR="$PWD" +TRIGGERDAT= +NEEDEDCMDS='taskset zip' + +print_help() +{ + cat <<EOF +$0 - helper script for the Linux Kernel Simple GPIO Logic Analyzer +Available options: + -d|--duration-us <n>: number of microseconds to sample. Overrides -n, no default value. + -h|--help: print this help + -i|--init <n>: which CPU to isolate for sampling. Only needed once. Default <1>. + Remember that a more powerful CPU gives you higher sample speeds. + Also CPU0 is not recommended as it usually does extra bookkeeping. + -n|--num_samples <n>: number of samples to acquire. Default <$NUMSAMPLES> + -o|--output-dir <str>: directory to put the result files. Default: current dir + -p|--path <str>: path to Logic Analyzer dir in case you have multiple instances. + Default to first instance found. + -s|--sample_freq <n>: desired sample frequency. Might be capped if too large. Default: 1MHz. + -t|--trigger <str>: pattern to use as trigger. <str> consists of n two-char pairs. First + char is channel number starting at "1". Second char is trigger level: + "L" - low; "H" - high; "R" - rising; "F" - falling +Examples: +Samples $NUMSAMPLES at 1MHz with already prepared CPU or automatically prepare CPU1 if needed + '$0' +Samples 50us at 2MHz waiting for falling edge on channel 2. CPU usage as above. + '$0 -d 50 -s 2000000 -t "2F"' + +Note that the process exits after checking all parameters but a sub-process still works in +the background. The result is only available once the sub-process finished. As the time of +writing, the sub-process is not killable, so be extra careful that your triggers work. + +Result is a .sr file to be consumed with PulseView from the free Sigrok project. It is +a zip file which also contains the binary sample data which may be consumed by others. +The filename is the logic analyzer instance name plus a since-epoch timestamp. +EOF +} + +set_newmask() +{ + local f + for f in $(find $1 -iname "$2"); do echo $NEWMASK > $f 2>/dev/null; done +} + +init_cpu() +{ + CPU="$1" + + [ ! -d $CPUSETDIR ] && mkdir $CPUSETDIR + mount | grep -q $CPUSETDIR || mount -t cpuset cpuset $CPUSETDIR + [ ! -d $LACPUSETDIR ] && mkdir $LACPUSETDIR + + echo $CPU > $LACPUSETDIR/cpus + echo 1 > $LACPUSETDIR/cpu_exclusive + echo 0 > $LACPUSETDIR/mems + + OLDMASK=$(cat /proc/irq/default_smp_affinity) + let val="0x$OLDMASK & ~(1 << $CPU)" + NEWMASK=$(printf "%x" $val) + + set_newmask '/proc/irq' '*smp_affinity' + set_newmask '/sys/devices/virtual/workqueue/' 'cpumask' + + # Move tasks away from isolated CPU + for p in $(ps -o pid | tail -n +2); do + MASK=$(taskset -p $p) + [ "${MASK##*: }" != "$OLDMASK" ] && continue + taskset -p $NEWMASK $p + done 2>/dev/null >/dev/null + + echo 1 > /sys/module/rcupdate/parameters/rcu_cpu_stall_suppress +} + +do_capture() +{ + taskset $1 echo 1 > $LASYSFSDIR/capture + + SRTMP=$(mktemp -d) + echo 1 > $SRTMP/version + cp $LASYSFSDIR/sample_data $SRTMP/logic-1-1 + cat > $SRTMP/metadata <<EOF +[global] +sigrok version=0.2.0 + +[device 1] +capturefile=logic-1 +total probes=$(cat $LASYSFSDIR/meta_data | wc -l) +samplerate=${SAMPLEFREQ}Hz +unitsize=1 +EOF + cat $LASYSFSDIR/meta_data >> $SRTMP/metadata + + ZIPNAME="$OUTPUTDIR/${LASYSFSDIR##*/}-$(date +%s).sr" + zip -jq $ZIPNAME $SRTMP/* + rm -rf $SRTMP +} + +REP=$(getopt -a -l path:,init:,sample_freq:,num_samples:,duration-us:,trigger:,output-dir:,help -o i:s:n:d:t:o:h -- "$@") || exit 1 +eval set -- "$REP" +while true; do + case "$1" in + -d|--duration-us) DURATION="$2"; shift 2;; + -h|--help) print_help; exit 0;; + -i|--init) INITCPU="$2"; shift 2;; + -n|--num_samples) NUMSAMPLES="$2"; shift 2;; + -o|--output-dir) OUTPUTDIR="$2"; shift 2;; + -p|--path) LASYSFSDIR="$2"; shift 2;; + -s|--sample_freq) SAMPLEFREQ="$2"; shift 2;; + -t|--trigger) TRIGGERDAT="$2"; shift 2;; + --) shift; break;; + *) echo "error parsing commandline: $@"; exit 1;; + esac +done + +for f in $NEEDEDCMDS; do + command -v $f >/dev/null || { echo "Command '$f' not found"; exit 1; } +done + +[ $SAMPLEFREQ -eq 0 ] && echo "Invalid sample frequency" && exit 1 + +[ -z "$LASYSFSDIR" ] && LASYSFSDIR="$SYSFSDIR/$(ls -1 $SYSFSDIR | head -n1)" +[ ! -d "$LASYSFSDIR" ] && echo "LA directory '$LASYSFSDIR' not found!" && exit 1 + +[ -n "$INITCPU" ] && init_cpu $INITCPU +[ ! -d "$LACPUSETDIR" ] && echo "Auto-Isolating CPU1" && init_cpu 1 + +let NDELAY=1000000000/$SAMPLEFREQ +NDELAY_ACQ=$(cat $LASYSFSDIR/delay_ns_acquisition) +[ $NDELAY_ACQ -eq 0 ] && echo "Invalid acquisition delay received" && exit 1 +let NDELAY_USER=$NDELAY-$NDELAY_ACQ +let MAXFREQ=1000000000/$NDELAY_ACQ + +[ $NDELAY_USER -lt 0 ] && NDELAY_USER=0 && SAMPLEFREQ=$MAXFREQ && echo "Capping sample_freq to $MAXFREQ" +echo $NDELAY_USER > $LASYSFSDIR/delay_ns_user + +[ -n "$DURATION" ] && let NUMSAMPLES=$SAMPLEFREQ*$DURATION/1000000 +echo $NUMSAMPLES > $LASYSFSDIR/buf_size + +if [ -n "$TRIGGERDAT" ]; then + echo -n "$TRIGGERDAT" > $LASYSFSDIR/trigger 2>/dev/null + [ $? -gt 0 ] && echo "Trigger data '$TRIGGERDAT' rejected" && exit 1 +fi + +CPU=$(cat $LACPUSETDIR/effective_cpus) +[ -z "$CPU" ] && echo "No isolated CPU found" && exit 1 +let CPUMASK="1 << $CPU" +do_capture $CPUMASK & -- 2.30.0