247 lines · bash
1#!/bin/sh -eu2# SPDX-License-Identifier: GPL-2.03#4# Helper script for the Linux Kernel GPIO sloppy logic analyzer5#6# Copyright (C) Wolfram Sang <wsa@sang-engineering.com>7# Copyright (C) Renesas Electronics Corporation8 9samplefreq=100000010numsamples=25000011cpusetdefaultdir='/sys/fs/cgroup'12cpusetprefix='cpuset.'13debugdir='/sys/kernel/debug'14ladirname='gpio-sloppy-logic-analyzer'15outputdir="$PWD"16neededcmds='taskset zip'17max_chans=818duration=19initcpu=20listinstances=021lainstance=22lasysfsdir=23triggerdat=24trigger_bindat=25progname="${0##*/}"26print_help()27{28 cat << EOF29$progname - helper script for the Linux Kernel Sloppy GPIO Logic Analyzer30Available options:31 -c|--cpu <n>: which CPU to isolate for sampling. Only needed once. Default <1>.32 Remember that a more powerful CPU gives you higher sampling speeds.33 Also CPU0 is not recommended as it usually does extra bookkeeping.34 -d|--duration-us <SI-n>: number of microseconds to sample. Overrides -n, no default value.35 -h|--help: print this help36 -i|--instance <str>: name of the logic analyzer in case you have multiple instances. Default37 to first instance found38 -k|--kernel-debug-dir <str>: path to the kernel debugfs mountpoint. Default: <$debugdir>39 -l|--list-instances: list all available instances40 -n|--num_samples <SI-n>: number of samples to acquire. Default <$numsamples>41 -o|--output-dir <str>: directory to put the result files. Default: current dir42 -s|--sample_freq <SI-n>: desired sampling frequency. Might be capped if too large.43 Default: <1000000>44 -t|--trigger <str>: pattern to use as trigger. <str> consists of two-char pairs. First45 char is channel number starting at "1". Second char is trigger level:46 "L" - low; "H" - high; "R" - rising; "F" - falling47 These pairs can be combined with "+", so "1H+2F" triggers when probe 148 is high while probe 2 has a falling edge. You can have multiple triggers49 combined with ",". So, "1H+2F,1H+2R" is like the example before but it50 waits for a rising edge on probe 2 while probe 1 is still high after the51 first trigger has been met.52 Trigger data will only be used for the next capture and then be erased.53 54<SI-n> is an integer value where SI units "T", "G", "M", "K" are recognized, e.g. '1M500K' is 1500000.55 56Examples:57Samples $numsamples values at 1MHz with an already prepared CPU or automatically prepares CPU1 if needed,58use the first logic analyzer instance found:59 '$progname'60Samples 50us at 2MHz waiting for a falling edge on channel 2. CPU and instance as above:61 '$progname -d 50 -s 2M -t "2F"'62 63Note that the process exits after checking all parameters but a sub-process still works in64the background. The result is only available once the sub-process finishes.65 66Result is a .sr file to be consumed with PulseView from the free Sigrok project. It is67a zip file which also contains the binary sample data which may be consumed by others.68The filename is the logic analyzer instance name plus a since-epoch timestamp.69EOF70}71 72fail()73{74 echo "$1"75 exit 176}77 78parse_si()79{80 conv_si="$(printf $1 | sed 's/[tT]+\?/*1000G+/g; s/[gG]+\?/*1000M+/g; s/[mM]+\?/*1000K+/g; s/[kK]+\?/*1000+/g; s/+$//')"81 si_val="$((conv_si))"82}83set_newmask()84{85 for f in $(find "$1" -iname "$2"); do echo "$newmask" > "$f" 2>/dev/null || true; done86}87 88init_cpu()89{90 isol_cpu="$1"91 92 [ -d "$lacpusetdir" ] || mkdir "$lacpusetdir"93 94 cur_cpu=$(cat "${lacpusetfile}cpus")95 [ "$cur_cpu" = "$isol_cpu" ] && return96 [ -z "$cur_cpu" ] || fail "CPU$isol_cpu requested but CPU$cur_cpu already isolated"97 98 echo "$isol_cpu" > "${lacpusetfile}cpus" || fail "Could not isolate CPU$isol_cpu. Does it exist?"99 echo 1 > "${lacpusetfile}cpu_exclusive"100 echo 0 > "${lacpusetfile}mems"101 102 oldmask=$(cat /proc/irq/default_smp_affinity)103 newmask=$(printf "%x" $((0x$oldmask & ~(1 << isol_cpu))))104 105 set_newmask '/proc/irq' '*smp_affinity'106 set_newmask '/sys/devices/virtual/workqueue/' 'cpumask'107 108 # Move tasks away from isolated CPU109 for p in $(ps -o pid | tail -n +2); do110 mask=$(taskset -p "$p") || continue111 # Ignore tasks with a custom mask, i.e. not equal $oldmask112 [ "${mask##*: }" = "$oldmask" ] || continue113 taskset -p "$newmask" "$p" || continue114 done 2>/dev/null >/dev/null115 116 # Big hammer! Working with 'rcu_momentary_dyntick_idle()' for a more fine-grained solution117 # still printed warnings. Same for re-enabling the stall detector after sampling.118 echo 1 > /sys/module/rcupdate/parameters/rcu_cpu_stall_suppress119 120 cpufreqgov="/sys/devices/system/cpu/cpu$isol_cpu/cpufreq/scaling_governor"121 [ -w "$cpufreqgov" ] && echo 'performance' > "$cpufreqgov" || true122}123 124parse_triggerdat()125{126 oldifs="$IFS"127 IFS=','; for trig in $1; do128 mask=0; val1=0; val2=0129 IFS='+'; for elem in $trig; do130 chan=${elem%[lhfrLHFR]}131 mode=${elem#$chan}132 # Check if we could parse something and the channel number fits133 [ "$chan" != "$elem" ] && [ "$chan" -le $max_chans ] || fail "Trigger syntax error: $elem"134 bit=$((1 << (chan - 1)))135 mask=$((mask | bit))136 case $mode in137 [hH]) val1=$((val1 | bit)); val2=$((val2 | bit));;138 [fF]) val1=$((val1 | bit));;139 [rR]) val2=$((val2 | bit));;140 esac141 done142 trigger_bindat="$trigger_bindat$(printf '\\%o\\%o' $mask $val1)"143 [ $val1 -ne $val2 ] && trigger_bindat="$trigger_bindat$(printf '\\%o\\%o' $mask $val2)"144 done145 IFS="$oldifs"146}147 148do_capture()149{150 taskset "$1" echo 1 > "$lasysfsdir"/capture || fail "Capture error! Check kernel log"151 152 srtmp=$(mktemp -d)153 echo 1 > "$srtmp"/version154 cp "$lasysfsdir"/sample_data "$srtmp"/logic-1-1155 cat > "$srtmp"/metadata << EOF156[global]157sigrok version=0.2.0158 159[device 1]160capturefile=logic-1161total probes=$(wc -l < "$lasysfsdir"/meta_data)162samplerate=${samplefreq}Hz163unitsize=1164EOF165 cat "$lasysfsdir"/meta_data >> "$srtmp"/metadata166 167 zipname="$outputdir/${lasysfsdir##*/}-$(date +%s).sr"168 zip -jq "$zipname" "$srtmp"/*169 rm -rf "$srtmp"170 delay_ack=$(cat "$lasysfsdir"/delay_ns_acquisition)171 [ "$delay_ack" -eq 0 ] && delay_ack=1172 echo "Logic analyzer done. Saved '$zipname'"173 echo "Max sample frequency this time: $((1000000000 / delay_ack))Hz."174}175 176rep=$(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 1177eval set -- "$rep"178while true; do179 case "$1" in180 -c|--cpu) initcpu="$2"; shift;;181 -d|--duration-us) parse_si $2; duration=$si_val; shift;;182 -h|--help) print_help; exit 0;;183 -i|--instance) lainstance="$2"; shift;;184 -k|--kernel-debug-dir) debugdir="$2"; shift;;185 -l|--list-instances) listinstances=1;;186 -n|--num_samples) parse_si $2; numsamples=$si_val; shift;;187 -o|--output-dir) outputdir="$2"; shift;;188 -s|--sample_freq) parse_si $2; samplefreq=$si_val; shift;;189 -t|--trigger) triggerdat="$2"; shift;;190 --) break;;191 *) fail "error parsing command line: $*";;192 esac193 shift194done195 196for f in $neededcmds; do197 command -v "$f" >/dev/null || fail "Command '$f' not found"198done199 200# print cpuset mountpoint if any, errorcode > 0 if noprefix option was found201cpusetdir=$(awk '$3 == "cgroup" && $4 ~ /cpuset/ { print $2; exit (match($4, /noprefix/) > 0) }' /proc/self/mounts) || cpusetprefix=''202if [ -z "$cpusetdir" ]; then203 cpusetdir="$cpusetdefaultdir"204 [ -d $cpusetdir ] || mkdir $cpusetdir205 mount -t cgroup -o cpuset none $cpusetdir || fail "Couldn't mount cpusets. Not in kernel or already in use?"206fi207 208lacpusetdir="$cpusetdir/$ladirname"209lacpusetfile="$lacpusetdir/$cpusetprefix"210sysfsdir="$debugdir/$ladirname"211 212[ "$samplefreq" -ne 0 ] || fail "Invalid sample frequency"213 214[ -d "$sysfsdir" ] || fail "Could not find logic analyzer root dir '$sysfsdir'. Module loaded?"215[ -x "$sysfsdir" ] || fail "Could not access logic analyzer root dir '$sysfsdir'. Need root?"216 217[ $listinstances -gt 0 ] && find "$sysfsdir" -mindepth 1 -type d | sed 's|.*/||' && exit 0218 219if [ -n "$lainstance" ]; then220 lasysfsdir="$sysfsdir/$lainstance"221else222 lasysfsdir=$(find "$sysfsdir" -mindepth 1 -type d -print -quit)223fi224[ -d "$lasysfsdir" ] || fail "Logic analyzer directory '$lasysfsdir' not found!"225[ -d "$outputdir" ] || fail "Output directory '$outputdir' not found!"226 227[ -n "$initcpu" ] && init_cpu "$initcpu"228[ -d "$lacpusetdir" ] || { echo "Auto-Isolating CPU1"; init_cpu 1; }229 230ndelay=$((1000000000 / samplefreq))231echo "$ndelay" > "$lasysfsdir"/delay_ns232 233[ -n "$duration" ] && numsamples=$((samplefreq * duration / 1000000))234echo $numsamples > "$lasysfsdir"/buf_size235 236if [ -n "$triggerdat" ]; then237 parse_triggerdat "$triggerdat"238 printf "$trigger_bindat" > "$lasysfsdir"/trigger 2>/dev/null || fail "Trigger data '$triggerdat' rejected"239fi240 241workcpu=$(cat "${lacpusetfile}effective_cpus")242[ -n "$workcpu" ] || fail "No isolated CPU found"243cpumask=$(printf '%x' $((1 << workcpu)))244instance=${lasysfsdir##*/}245echo "Setting up '$instance': $numsamples samples at ${samplefreq}Hz with ${triggerdat:-no} trigger using CPU$workcpu"246do_capture "$cpumask" &247