Re: [PATCH v5 1/1] gpio: add sloppy logic analyzer using polling

From: Andy Shevchenko
Date: Tue Nov 23 2021 - 13:47:27 EST


On Tue, Nov 23, 2021 at 05:49:02PM +0100, Wolfram Sang wrote:
> 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.

...

> +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/

Alas, yet another tool required... (Sad thoughts since recently has installed
PicoScope software).

...

> kgdb
> kselftest
> kunit/index

> + gpio-sloppy-logic-analyzer

Above looks like ordered, do we need some groups here or so?

...

> + mutex_lock(&priv->lock);

> + if (priv->blob_dent) {

Redundant (i.e. duplicate).

> + debugfs_remove(priv->blob_dent);
> + priv->blob_dent = NULL;
> + }

...

> +gpio_err:

A bit confusing name. What about

enable_irq_and_free_data:

?

> + 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;

...

> +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;

Perhaps

unsigned int i, meta_len = 0;
char *meta = NULL;
int ret;


...

> + ret = device_property_read_string_array(dev, "probe-names", gpio_names,
> + priv->descs->ndescs);
> + if (ret >= 0 && ret != priv->descs->ndescs)

> + ret = -ENODATA;

Don't remember if we already discussed this error code, but data is there,
it's not correct. EBADSLT? EBADR? ECHRNG?

> + 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]);

Do we really need the 'probe%02u=' part? It's redundant since it may be derived
from the line number of the output (and it always in [1..ndescs+1]).

> + }

...

> + dev_info(dev, "initialized");

Is it useful?

...

> +print_help()
> +{
> + cat <<EOF

cat << EOF

is slightly easier to read.

> +EOF
> +}

...

> +set_newmask()
> +{
> + for f in $(find "$1" -iname "$2"); do echo "$newmask" > "$f" 2>/dev/null || true; done

While here it's okay, the rule of thumb is never use `for` or `while` against
the list of filenames.

> +}

...

> +init_cpu()
> +{
> + isol_cpu="$1"

> + [ -d $cpusetdir ] || mkdir $cpusetdir

`mkdir -p` and drop needless test.

> + mount | grep -q $cpusetdir || mount -t cpuset cpuset $cpusetdir

> + [ -d "$lacpusetdir" ] || mkdir "$lacpusetdir"

`mkdir -p` and drop needless test.

> + cur_cpu="$(cat "$lacpusetdir"/cpus)"
> + [ "$cur_cpu" = "$isol_cpu" ] && return
> + [ -z "$cur_cpu" ] || fail "CPU$isol_cpu requested but CPU$cur_cpu already isolated"
> +
> + echo "$isol_cpu" > "$lacpusetdir"/cpus || fail "Could not isolate CPU$isol_cpu. Does it exist?"
> + echo 1 > "$lacpusetdir"/cpu_exclusive
> + echo 0 > "$lacpusetdir"/mems
> +
> + oldmask=$(cat /proc/irq/default_smp_affinity)

> + val=$((0x$oldmask & ~(1 << isol_cpu)))
> + newmask=$(printf "%x" $val)

Can be on one line (in a single expression).

> + 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") || continue
> + # Ignore tasks with a custom mask, i.e. not equal $oldmask
> + [ "${mask##*: }" = "$oldmask" ] || continue
> + taskset -p "$newmask" "$p" || continue
> + done 2>/dev/null >/dev/null

`> /dev/null 2>&1` is idiomatic. And I think there is actually a subtle
difference between two.

> + echo 1 > /sys/module/rcupdate/parameters/rcu_cpu_stall_suppress
> +
> + cpufreqgov="/sys/devices/system/cpu/cpu$isol_cpu/cpufreq/scaling_governor"
> + [ -w "$cpufreqgov" ] && echo 'performance' > "$cpufreqgov" || true
> +}

...

> +parse_triggerdat()
> +{
> + oldifs="$IFS"
> + IFS=','; for trig in $1; do
> + mask=0; val1=0; val2=0
> + IFS='+'; for elem in $trig; do
> + chan=${elem%[lhfrLHFR]}
> + mode=${elem#$chan}
> + # Check if we could parse something and the channel number fits

> + [ "$chan" != "$elem" ] && [ "$chan" -le $max_chans ] || fail "Trigger syntax error: $elem"

No need to execute `test` twice:

[ "$chan" != "$elem" -a "$chan" -le $max_chans ] || fail "Trigger syntax error: $elem"

> + bit=$((1 << (chan - 1)))
> + mask=$((mask | bit))
> + case $mode in
> + [hH]) val1=$((val1 | bit)); val2=$((val2 | bit));;
> + [fF]) val1=$((val1 | bit));;
> + [rR]) val2=$((val2 | bit));;
> + esac
> + done

> + trigger_bindat="$trigger_bindat$(printf '\\%o\\%o' $mask $val1)"
> + [ $val1 -ne $val2 ] && trigger_bindat="$trigger_bindat$(printf '\\%o\\%o' $mask $val2)"

`printf` with arguments may be split to a separate helper function.

> + done
> + IFS="$oldifs"
> +}
> +
> +do_capture()
> +{
> + taskset "$1" echo 1 > "$lasysfsdir"/capture || fail "Capture error! Check kernel log"

Shouldn't this function setup signal TRAPs?

> + srtmp=$(mktemp -d)
> + echo 1 > "$srtmp"/version
> + cp "$lasysfsdir"/sample_data "$srtmp"/logic-1-1
> + cat > "$srtmp"/metadata <<EOF

cat > "$srtmp"/metadata << EOF

> +[global]
> +sigrok version=0.2.0
> +
> +[device 1]
> +capturefile=logic-1
> +total probes=$(wc -l < "$lasysfsdir"/meta_data)
> +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"
> + delay_ack=$(cat "$lasysfsdir"/delay_ns_acquisition)
> + [ "$delay_ack" -eq 0 ] && delay_ack=1
> + echo "Logic analyzer done. Saved '$zipname'"
> + echo "Max sample frequency this time: $((1000000000 / delay_ack))Hz."
> +}
> +
> +rep=$(getopt -a -l cpu:,duration-us:,help,instance:,kernel-debug-dir:,num_samples:,output-dir:,sample_freq:,trigger: -o c:d:hi:k:n:o:s:t: -- "$@") || exit 1
> +eval set -- "$rep"
> +while true; do
> + case "$1" in
> + -c|--cpu) initcpu="$2"; shift;;
> + -d|--duration-us) duration="$2"; shift;;
> + -h|--help) print_help; exit 0;;
> + -i|--instance) lainstance="$2"; shift;;
> + -k|--kernel-debug-dir) debugdir="$2"; shift;;
> + -n|--num_samples) numsamples="$2"; shift;;
> + -o|--output-dir) outputdir="$2"; shift;;
> + -s|--sample_freq) samplefreq="$2"; shift;;
> + -t|--trigger) triggerdat="$2"; shift;;
> + --) break;;

> + *) fail "error parsing command line: $*";;

$@ is better, actually one should never use $*.

> + esac
> + shift
> +done

...

Wondering, shouldn't be a simple validator before start that we have commands
present, such as zip?

--
With Best Regards,
Andy Shevchenko