Path: blob/master/tools/gpio/gpio-sloppy-logic-analyzer.sh
26278 views
#!/bin/sh -eu1# SPDX-License-Identifier: GPL-2.02#3# Helper script for the Linux Kernel GPIO sloppy logic analyzer4#5# Copyright (C) Wolfram Sang <[email protected]>6# Copyright (C) Renesas Electronics Corporation78samplefreq=10000009numsamples=25000010cpusetdefaultdir='/sys/fs/cgroup'11cpusetprefix='cpuset.'12debugdir='/sys/kernel/debug'13ladirname='gpio-sloppy-logic-analyzer'14outputdir="$PWD"15neededcmds='taskset zip'16max_chans=817duration=18initcpu=19listinstances=020lainstance=21lasysfsdir=22triggerdat=23trigger_bindat=24progname="${0##*/}"25print_help()26{27cat << EOF28$progname - helper script for the Linux Kernel Sloppy GPIO Logic Analyzer29Available options:30-c|--cpu <n>: which CPU to isolate for sampling. Only needed once. Default <1>.31Remember that a more powerful CPU gives you higher sampling speeds.32Also CPU0 is not recommended as it usually does extra bookkeeping.33-d|--duration-us <SI-n>: number of microseconds to sample. Overrides -n, no default value.34-h|--help: print this help35-i|--instance <str>: name of the logic analyzer in case you have multiple instances. Default36to first instance found37-k|--kernel-debug-dir <str>: path to the kernel debugfs mountpoint. Default: <$debugdir>38-l|--list-instances: list all available instances39-n|--num_samples <SI-n>: number of samples to acquire. Default <$numsamples>40-o|--output-dir <str>: directory to put the result files. Default: current dir41-s|--sample_freq <SI-n>: desired sampling frequency. Might be capped if too large.42Default: <1000000>43-t|--trigger <str>: pattern to use as trigger. <str> consists of two-char pairs. First44char is channel number starting at "1". Second char is trigger level:45"L" - low; "H" - high; "R" - rising; "F" - falling46These pairs can be combined with "+", so "1H+2F" triggers when probe 147is high while probe 2 has a falling edge. You can have multiple triggers48combined with ",". So, "1H+2F,1H+2R" is like the example before but it49waits for a rising edge on probe 2 while probe 1 is still high after the50first trigger has been met.51Trigger data will only be used for the next capture and then be erased.5253<SI-n> is an integer value where SI units "T", "G", "M", "K" are recognized, e.g. '1M500K' is 1500000.5455Examples:56Samples $numsamples values at 1MHz with an already prepared CPU or automatically prepares CPU1 if needed,57use the first logic analyzer instance found:58'$progname'59Samples 50us at 2MHz waiting for a falling edge on channel 2. CPU and instance as above:60'$progname -d 50 -s 2M -t "2F"'6162Note that the process exits after checking all parameters but a sub-process still works in63the background. The result is only available once the sub-process finishes.6465Result is a .sr file to be consumed with PulseView from the free Sigrok project. It is66a zip file which also contains the binary sample data which may be consumed by others.67The filename is the logic analyzer instance name plus a since-epoch timestamp.68EOF69}7071fail()72{73echo "$1"74exit 175}7677parse_si()78{79conv_si="$(printf $1 | sed 's/[tT]+\?/*1000G+/g; s/[gG]+\?/*1000M+/g; s/[mM]+\?/*1000K+/g; s/[kK]+\?/*1000+/g; s/+$//')"80si_val="$((conv_si))"81}82set_newmask()83{84for f in $(find "$1" -iname "$2"); do echo "$newmask" > "$f" 2>/dev/null || true; done85}8687init_cpu()88{89isol_cpu="$1"9091[ -d "$lacpusetdir" ] || mkdir "$lacpusetdir"9293cur_cpu=$(cat "${lacpusetfile}cpus")94[ "$cur_cpu" = "$isol_cpu" ] && return95[ -z "$cur_cpu" ] || fail "CPU$isol_cpu requested but CPU$cur_cpu already isolated"9697echo "$isol_cpu" > "${lacpusetfile}cpus" || fail "Could not isolate CPU$isol_cpu. Does it exist?"98echo 1 > "${lacpusetfile}cpu_exclusive"99echo 0 > "${lacpusetfile}mems"100101oldmask=$(cat /proc/irq/default_smp_affinity)102newmask=$(printf "%x" $((0x$oldmask & ~(1 << isol_cpu))))103104set_newmask '/proc/irq' '*smp_affinity'105set_newmask '/sys/devices/virtual/workqueue/' 'cpumask'106107# Move tasks away from isolated CPU108for p in $(ps -o pid | tail -n +2); do109mask=$(taskset -p "$p") || continue110# Ignore tasks with a custom mask, i.e. not equal $oldmask111[ "${mask##*: }" = "$oldmask" ] || continue112taskset -p "$newmask" "$p" || continue113done 2>/dev/null >/dev/null114115# Big hammer! Working with 'rcu_momentary_eqs()' for a more fine-grained solution116# still printed warnings. Same for re-enabling the stall detector after sampling.117echo 1 > /sys/module/rcupdate/parameters/rcu_cpu_stall_suppress118119cpufreqgov="/sys/devices/system/cpu/cpu$isol_cpu/cpufreq/scaling_governor"120[ -w "$cpufreqgov" ] && echo 'performance' > "$cpufreqgov" || true121}122123parse_triggerdat()124{125oldifs="$IFS"126IFS=','; for trig in $1; do127mask=0; val1=0; val2=0128IFS='+'; for elem in $trig; do129chan=${elem%[lhfrLHFR]}130mode=${elem#$chan}131# Check if we could parse something and the channel number fits132[ "$chan" != "$elem" ] && [ "$chan" -le $max_chans ] || fail "Trigger syntax error: $elem"133bit=$((1 << (chan - 1)))134mask=$((mask | bit))135case $mode in136[hH]) val1=$((val1 | bit)); val2=$((val2 | bit));;137[fF]) val1=$((val1 | bit));;138[rR]) val2=$((val2 | bit));;139esac140done141trigger_bindat="$trigger_bindat$(printf '\\%o\\%o' $mask $val1)"142[ $val1 -ne $val2 ] && trigger_bindat="$trigger_bindat$(printf '\\%o\\%o' $mask $val2)"143done144IFS="$oldifs"145}146147do_capture()148{149taskset "$1" echo 1 > "$lasysfsdir"/capture || fail "Capture error! Check kernel log"150151srtmp=$(mktemp -d)152echo 1 > "$srtmp"/version153cp "$lasysfsdir"/sample_data "$srtmp"/logic-1-1154cat > "$srtmp"/metadata << EOF155[global]156sigrok version=0.2.0157158[device 1]159capturefile=logic-1160total probes=$(wc -l < "$lasysfsdir"/meta_data)161samplerate=${samplefreq}Hz162unitsize=1163EOF164cat "$lasysfsdir"/meta_data >> "$srtmp"/metadata165166zipname="$outputdir/${lasysfsdir##*/}-$(date +%s).sr"167zip -jq "$zipname" "$srtmp"/*168rm -rf "$srtmp"169delay_ack=$(cat "$lasysfsdir"/delay_ns_acquisition)170[ "$delay_ack" -eq 0 ] && delay_ack=1171echo "Logic analyzer done. Saved '$zipname'"172echo "Max sample frequency this time: $((1000000000 / delay_ack))Hz."173}174175rep=$(getopt -a -l cpu:,duration-us:,help,instance:,list-instances,kernel-debug-dir:,num_samples:,output-dir:,sample_freq:,trigger: -o c:d:hi:k:ln:o:s:t: -- "$@") || exit 1176eval set -- "$rep"177while true; do178case "$1" in179-c|--cpu) initcpu="$2"; shift;;180-d|--duration-us) parse_si $2; duration=$si_val; shift;;181-h|--help) print_help; exit 0;;182-i|--instance) lainstance="$2"; shift;;183-k|--kernel-debug-dir) debugdir="$2"; shift;;184-l|--list-instances) listinstances=1;;185-n|--num_samples) parse_si $2; numsamples=$si_val; shift;;186-o|--output-dir) outputdir="$2"; shift;;187-s|--sample_freq) parse_si $2; samplefreq=$si_val; shift;;188-t|--trigger) triggerdat="$2"; shift;;189--) break;;190*) fail "error parsing command line: $*";;191esac192shift193done194195for f in $neededcmds; do196command -v "$f" >/dev/null || fail "Command '$f' not found"197done198199# print cpuset mountpoint if any, errorcode > 0 if noprefix option was found200cpusetdir=$(awk '$3 == "cgroup" && $4 ~ /cpuset/ { print $2; exit (match($4, /noprefix/) > 0) }' /proc/self/mounts) || cpusetprefix=''201if [ -z "$cpusetdir" ]; then202cpusetdir="$cpusetdefaultdir"203[ -d $cpusetdir ] || mkdir $cpusetdir204mount -t cgroup -o cpuset none $cpusetdir || fail "Couldn't mount cpusets. Not in kernel or already in use?"205fi206207lacpusetdir="$cpusetdir/$ladirname"208lacpusetfile="$lacpusetdir/$cpusetprefix"209sysfsdir="$debugdir/$ladirname"210211[ "$samplefreq" -ne 0 ] || fail "Invalid sample frequency"212213[ -d "$sysfsdir" ] || fail "Could not find logic analyzer root dir '$sysfsdir'. Module loaded?"214[ -x "$sysfsdir" ] || fail "Could not access logic analyzer root dir '$sysfsdir'. Need root?"215216[ $listinstances -gt 0 ] && find "$sysfsdir" -mindepth 1 -type d | sed 's|.*/||' && exit 0217218if [ -n "$lainstance" ]; then219lasysfsdir="$sysfsdir/$lainstance"220else221lasysfsdir=$(find "$sysfsdir" -mindepth 1 -type d -print -quit)222fi223[ -d "$lasysfsdir" ] || fail "Logic analyzer directory '$lasysfsdir' not found!"224[ -d "$outputdir" ] || fail "Output directory '$outputdir' not found!"225226[ -n "$initcpu" ] && init_cpu "$initcpu"227[ -d "$lacpusetdir" ] || { echo "Auto-Isolating CPU1"; init_cpu 1; }228229ndelay=$((1000000000 / samplefreq))230echo "$ndelay" > "$lasysfsdir"/delay_ns231232[ -n "$duration" ] && numsamples=$((samplefreq * duration / 1000000))233echo $numsamples > "$lasysfsdir"/buf_size234235if [ -n "$triggerdat" ]; then236parse_triggerdat "$triggerdat"237printf "$trigger_bindat" > "$lasysfsdir"/trigger 2>/dev/null || fail "Trigger data '$triggerdat' rejected"238fi239240workcpu=$(cat "${lacpusetfile}effective_cpus")241[ -n "$workcpu" ] || fail "No isolated CPU found"242cpumask=$(printf '%x' $((1 << workcpu)))243instance=${lasysfsdir##*/}244echo "Setting up '$instance': $numsamples samples at ${samplefreq}Hz with ${triggerdat:-no} trigger using CPU$workcpu"245do_capture "$cpumask" &246247248