1#!/bin/sh -eu 2# SPDX-License-Identifier: GPL-2.0 3# 4# Helper script for the Linux Kernel GPIO sloppy logic analyzer 5# 6# Copyright (C) Wolfram Sang <wsa@sang-engineering.com> 7# Copyright (C) Renesas Electronics Corporation 8 9samplefreq=1000000 10numsamples=250000 11cpusetdefaultdir='/sys/fs/cgroup' 12cpusetprefix='cpuset.' 13debugdir='/sys/kernel/debug' 14ladirname='gpio-sloppy-logic-analyzer' 15outputdir="$PWD" 16neededcmds='taskset zip' 17max_chans=8 18duration= 19initcpu= 20listinstances=0 21lainstance= 22lasysfsdir= 23triggerdat= 24trigger_bindat= 25progname="${0##*/}" 26print_help() 27{ 28 cat << EOF 29$progname - helper script for the Linux Kernel Sloppy GPIO Logic Analyzer 30Available 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 help 36 -i|--instance <str>: name of the logic analyzer in case you have multiple instances. Default 37 to first instance found 38 -k|--kernel-debug-dir <str>: path to the kernel debugfs mountpoint. Default: <$debugdir> 39 -l|--list-instances: list all available instances 40 -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 dir 42 -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. First 45 char is channel number starting at "1". Second char is trigger level: 46 "L" - low; "H" - high; "R" - rising; "F" - falling 47 These pairs can be combined with "+", so "1H+2F" triggers when probe 1 48 is high while probe 2 has a falling edge. You can have multiple triggers 49 combined with ",". So, "1H+2F,1H+2R" is like the example before but it 50 waits for a rising edge on probe 2 while probe 1 is still high after the 51 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 in 64the 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 is 67a 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. 69EOF 70} 71 72fail() 73{ 74 echo "$1" 75 exit 1 76} 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; done 86} 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" ] && return 96 [ -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 CPU 109 for p in $(ps -o pid | tail -n +2); do 110 mask=$(taskset -p "$p") || continue 111 # Ignore tasks with a custom mask, i.e. not equal $oldmask 112 [ "${mask##*: }" = "$oldmask" ] || continue 113 taskset -p "$newmask" "$p" || continue 114 done 2>/dev/null >/dev/null 115 116 # Big hammer! Working with 'rcu_momentary_eqs()' for a more fine-grained solution 117 # still printed warnings. Same for re-enabling the stall detector after sampling. 118 echo 1 > /sys/module/rcupdate/parameters/rcu_cpu_stall_suppress 119 120 cpufreqgov="/sys/devices/system/cpu/cpu$isol_cpu/cpufreq/scaling_governor" 121 [ -w "$cpufreqgov" ] && echo 'performance' > "$cpufreqgov" || true 122} 123 124parse_triggerdat() 125{ 126 oldifs="$IFS" 127 IFS=','; for trig in $1; do 128 mask=0; val1=0; val2=0 129 IFS='+'; for elem in $trig; do 130 chan=${elem%[lhfrLHFR]} 131 mode=${elem#$chan} 132 # Check if we could parse something and the channel number fits 133 [ "$chan" != "$elem" ] && [ "$chan" -le $max_chans ] || fail "Trigger syntax error: $elem" 134 bit=$((1 << (chan - 1))) 135 mask=$((mask | bit)) 136 case $mode in 137 [hH]) val1=$((val1 | bit)); val2=$((val2 | bit));; 138 [fF]) val1=$((val1 | bit));; 139 [rR]) val2=$((val2 | bit));; 140 esac 141 done 142 trigger_bindat="$trigger_bindat$(printf '\\%o\\%o' $mask $val1)" 143 [ $val1 -ne $val2 ] && trigger_bindat="$trigger_bindat$(printf '\\%o\\%o' $mask $val2)" 144 done 145 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"/version 154 cp "$lasysfsdir"/sample_data "$srtmp"/logic-1-1 155 cat > "$srtmp"/metadata << EOF 156[global] 157sigrok version=0.2.0 158 159[device 1] 160capturefile=logic-1 161total probes=$(wc -l < "$lasysfsdir"/meta_data) 162samplerate=${samplefreq}Hz 163unitsize=1 164EOF 165 cat "$lasysfsdir"/meta_data >> "$srtmp"/metadata 166 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=1 172 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 1 177eval set -- "$rep" 178while true; do 179 case "$1" in 180 -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 esac 193 shift 194done 195 196for f in $neededcmds; do 197 command -v "$f" >/dev/null || fail "Command '$f' not found" 198done 199 200# print cpuset mountpoint if any, errorcode > 0 if noprefix option was found 201cpusetdir=$(awk '$3 == "cgroup" && $4 ~ /cpuset/ { print $2; exit (match($4, /noprefix/) > 0) }' /proc/self/mounts) || cpusetprefix='' 202if [ -z "$cpusetdir" ]; then 203 cpusetdir="$cpusetdefaultdir" 204 [ -d $cpusetdir ] || mkdir $cpusetdir 205 mount -t cgroup -o cpuset none $cpusetdir || fail "Couldn't mount cpusets. Not in kernel or already in use?" 206fi 207 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 0 218 219if [ -n "$lainstance" ]; then 220 lasysfsdir="$sysfsdir/$lainstance" 221else 222 lasysfsdir=$(find "$sysfsdir" -mindepth 1 -type d -print -quit) 223fi 224[ -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_ns 232 233[ -n "$duration" ] && numsamples=$((samplefreq * duration / 1000000)) 234echo $numsamples > "$lasysfsdir"/buf_size 235 236if [ -n "$triggerdat" ]; then 237 parse_triggerdat "$triggerdat" 238 printf "$trigger_bindat" > "$lasysfsdir"/trigger 2>/dev/null || fail "Trigger data '$triggerdat' rejected" 239fi 240 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