1 #!/bin/sh -eu 1 #!/bin/sh -eu 2 # SPDX-License-Identifier: GPL-2.0 2 # SPDX-License-Identifier: GPL-2.0 3 # 3 # 4 # Helper script for the Linux Kernel GPIO slop 4 # Helper script for the Linux Kernel GPIO sloppy logic analyzer 5 # 5 # 6 # Copyright (C) Wolfram Sang <wsa@sang-engineer 6 # Copyright (C) Wolfram Sang <wsa@sang-engineering.com> 7 # Copyright (C) Renesas Electronics Corporatio 7 # Copyright (C) Renesas Electronics Corporation 8 8 9 samplefreq=1000000 9 samplefreq=1000000 10 numsamples=250000 10 numsamples=250000 11 cpusetdefaultdir='/sys/fs/cgroup' 11 cpusetdefaultdir='/sys/fs/cgroup' 12 cpusetprefix='cpuset.' 12 cpusetprefix='cpuset.' 13 debugdir='/sys/kernel/debug' 13 debugdir='/sys/kernel/debug' 14 ladirname='gpio-sloppy-logic-analyzer' 14 ladirname='gpio-sloppy-logic-analyzer' 15 outputdir="$PWD" 15 outputdir="$PWD" 16 neededcmds='taskset zip' 16 neededcmds='taskset zip' 17 max_chans=8 17 max_chans=8 18 duration= 18 duration= 19 initcpu= 19 initcpu= 20 listinstances=0 20 listinstances=0 21 lainstance= 21 lainstance= 22 lasysfsdir= 22 lasysfsdir= 23 triggerdat= 23 triggerdat= 24 trigger_bindat= 24 trigger_bindat= 25 progname="${0##*/}" 25 progname="${0##*/}" 26 print_help() 26 print_help() 27 { 27 { 28 cat << EOF 28 cat << EOF 29 $progname - helper script for the Linux Kernel 29 $progname - helper script for the Linux Kernel Sloppy GPIO Logic Analyzer 30 Available options: 30 Available options: 31 -c|--cpu <n>: which CPU to isolate for 31 -c|--cpu <n>: which CPU to isolate for sampling. Only needed once. Default <1>. 32 Remember that a more pow 32 Remember that a more powerful CPU gives you higher sampling speeds. 33 Also CPU0 is not recomme 33 Also CPU0 is not recommended as it usually does extra bookkeeping. 34 -d|--duration-us <SI-n>: number of mic 34 -d|--duration-us <SI-n>: number of microseconds to sample. Overrides -n, no default value. 35 -h|--help: print this help 35 -h|--help: print this help 36 -i|--instance <str>: name of the logic 36 -i|--instance <str>: name of the logic analyzer in case you have multiple instances. Default 37 to first instance 37 to first instance found 38 -k|--kernel-debug-dir <str>: path to t 38 -k|--kernel-debug-dir <str>: path to the kernel debugfs mountpoint. Default: <$debugdir> 39 -l|--list-instances: list all availabl 39 -l|--list-instances: list all available instances 40 -n|--num_samples <SI-n>: number of sam 40 -n|--num_samples <SI-n>: number of samples to acquire. Default <$numsamples> 41 -o|--output-dir <str>: directory to pu 41 -o|--output-dir <str>: directory to put the result files. Default: current dir 42 -s|--sample_freq <SI-n>: desired sampl 42 -s|--sample_freq <SI-n>: desired sampling frequency. Might be capped if too large. 43 Default: <100 43 Default: <1000000> 44 -t|--trigger <str>: pattern to use as 44 -t|--trigger <str>: pattern to use as trigger. <str> consists of two-char pairs. First 45 char is channel nu 45 char is channel number starting at "1". Second char is trigger level: 46 "L" - low; "H" - h 46 "L" - low; "H" - high; "R" - rising; "F" - falling 47 These pairs can be 47 These pairs can be combined with "+", so "1H+2F" triggers when probe 1 48 is high while prob 48 is high while probe 2 has a falling edge. You can have multiple triggers 49 combined with ",". 49 combined with ",". So, "1H+2F,1H+2R" is like the example before but it 50 waits for a rising 50 waits for a rising edge on probe 2 while probe 1 is still high after the 51 first trigger has 51 first trigger has been met. 52 Trigger data will 52 Trigger data will only be used for the next capture and then be erased. 53 53 54 <SI-n> is an integer value where SI units "T", 54 <SI-n> is an integer value where SI units "T", "G", "M", "K" are recognized, e.g. '1M500K' is 1500000. 55 55 56 Examples: 56 Examples: 57 Samples $numsamples values at 1MHz with an alr 57 Samples $numsamples values at 1MHz with an already prepared CPU or automatically prepares CPU1 if needed, 58 use the first logic analyzer instance found: 58 use the first logic analyzer instance found: 59 '$progname' 59 '$progname' 60 Samples 50us at 2MHz waiting for a falling edg 60 Samples 50us at 2MHz waiting for a falling edge on channel 2. CPU and instance as above: 61 '$progname -d 50 -s 2M -t "2F"' 61 '$progname -d 50 -s 2M -t "2F"' 62 62 63 Note that the process exits after checking all 63 Note that the process exits after checking all parameters but a sub-process still works in 64 the background. The result is only available o 64 the background. The result is only available once the sub-process finishes. 65 65 66 Result is a .sr file to be consumed with Pulse 66 Result is a .sr file to be consumed with PulseView from the free Sigrok project. It is 67 a zip file which also contains the binary samp 67 a zip file which also contains the binary sample data which may be consumed by others. 68 The filename is the logic analyzer instance na 68 The filename is the logic analyzer instance name plus a since-epoch timestamp. 69 EOF 69 EOF 70 } 70 } 71 71 72 fail() 72 fail() 73 { 73 { 74 echo "$1" 74 echo "$1" 75 exit 1 75 exit 1 76 } 76 } 77 77 78 parse_si() 78 parse_si() 79 { 79 { 80 conv_si="$(printf $1 | sed 's/[tT]+\?/ 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))" 81 si_val="$((conv_si))" 82 } 82 } 83 set_newmask() 83 set_newmask() 84 { 84 { 85 for f in $(find "$1" -iname "$2"); do 85 for f in $(find "$1" -iname "$2"); do echo "$newmask" > "$f" 2>/dev/null || true; done 86 } 86 } 87 87 88 init_cpu() 88 init_cpu() 89 { 89 { 90 isol_cpu="$1" 90 isol_cpu="$1" 91 91 92 [ -d "$lacpusetdir" ] || mkdir "$lacpu 92 [ -d "$lacpusetdir" ] || mkdir "$lacpusetdir" 93 93 94 cur_cpu=$(cat "${lacpusetfile}cpus") 94 cur_cpu=$(cat "${lacpusetfile}cpus") 95 [ "$cur_cpu" = "$isol_cpu" ] && return 95 [ "$cur_cpu" = "$isol_cpu" ] && return 96 [ -z "$cur_cpu" ] || fail "CPU$isol_cp 96 [ -z "$cur_cpu" ] || fail "CPU$isol_cpu requested but CPU$cur_cpu already isolated" 97 97 98 echo "$isol_cpu" > "${lacpusetfile}cpu 98 echo "$isol_cpu" > "${lacpusetfile}cpus" || fail "Could not isolate CPU$isol_cpu. Does it exist?" 99 echo 1 > "${lacpusetfile}cpu_exclusive 99 echo 1 > "${lacpusetfile}cpu_exclusive" 100 echo 0 > "${lacpusetfile}mems" 100 echo 0 > "${lacpusetfile}mems" 101 101 102 oldmask=$(cat /proc/irq/default_smp_af 102 oldmask=$(cat /proc/irq/default_smp_affinity) 103 newmask=$(printf "%x" $((0x$oldmask & 103 newmask=$(printf "%x" $((0x$oldmask & ~(1 << isol_cpu)))) 104 104 105 set_newmask '/proc/irq' '*smp_affinity 105 set_newmask '/proc/irq' '*smp_affinity' 106 set_newmask '/sys/devices/virtual/work 106 set_newmask '/sys/devices/virtual/workqueue/' 'cpumask' 107 107 108 # Move tasks away from isolated CPU 108 # Move tasks away from isolated CPU 109 for p in $(ps -o pid | tail -n +2); do 109 for p in $(ps -o pid | tail -n +2); do 110 mask=$(taskset -p "$p") || con 110 mask=$(taskset -p "$p") || continue 111 # Ignore tasks with a custom m 111 # Ignore tasks with a custom mask, i.e. not equal $oldmask 112 [ "${mask##*: }" = "$oldmask" 112 [ "${mask##*: }" = "$oldmask" ] || continue 113 taskset -p "$newmask" "$p" || 113 taskset -p "$newmask" "$p" || continue 114 done 2>/dev/null >/dev/null 114 done 2>/dev/null >/dev/null 115 115 116 # Big hammer! Working with 'rcu_moment 116 # Big hammer! Working with 'rcu_momentary_dyntick_idle()' for a more fine-grained solution 117 # still printed warnings. Same for re- 117 # still printed warnings. Same for re-enabling the stall detector after sampling. 118 echo 1 > /sys/module/rcupdate/paramete 118 echo 1 > /sys/module/rcupdate/parameters/rcu_cpu_stall_suppress 119 119 120 cpufreqgov="/sys/devices/system/cpu/cp 120 cpufreqgov="/sys/devices/system/cpu/cpu$isol_cpu/cpufreq/scaling_governor" 121 [ -w "$cpufreqgov" ] && echo 'performa 121 [ -w "$cpufreqgov" ] && echo 'performance' > "$cpufreqgov" || true 122 } 122 } 123 123 124 parse_triggerdat() 124 parse_triggerdat() 125 { 125 { 126 oldifs="$IFS" 126 oldifs="$IFS" 127 IFS=','; for trig in $1; do 127 IFS=','; for trig in $1; do 128 mask=0; val1=0; val2=0 128 mask=0; val1=0; val2=0 129 IFS='+'; for elem in $trig; do 129 IFS='+'; for elem in $trig; do 130 chan=${elem%[lhfrLHFR] 130 chan=${elem%[lhfrLHFR]} 131 mode=${elem#$chan} 131 mode=${elem#$chan} 132 # Check if we could pa 132 # Check if we could parse something and the channel number fits 133 [ "$chan" != "$elem" ] 133 [ "$chan" != "$elem" ] && [ "$chan" -le $max_chans ] || fail "Trigger syntax error: $elem" 134 bit=$((1 << (chan - 1) 134 bit=$((1 << (chan - 1))) 135 mask=$((mask | bit)) 135 mask=$((mask | bit)) 136 case $mode in 136 case $mode in 137 [hH]) val1=$(( 137 [hH]) val1=$((val1 | bit)); val2=$((val2 | bit));; 138 [fF]) val1=$(( 138 [fF]) val1=$((val1 | bit));; 139 [rR]) val2=$(( 139 [rR]) val2=$((val2 | bit));; 140 esac 140 esac 141 done 141 done 142 trigger_bindat="$trigger_binda 142 trigger_bindat="$trigger_bindat$(printf '\\%o\\%o' $mask $val1)" 143 [ $val1 -ne $val2 ] && trigger 143 [ $val1 -ne $val2 ] && trigger_bindat="$trigger_bindat$(printf '\\%o\\%o' $mask $val2)" 144 done 144 done 145 IFS="$oldifs" 145 IFS="$oldifs" 146 } 146 } 147 147 148 do_capture() 148 do_capture() 149 { 149 { 150 taskset "$1" echo 1 > "$lasysfsdir"/ca 150 taskset "$1" echo 1 > "$lasysfsdir"/capture || fail "Capture error! Check kernel log" 151 151 152 srtmp=$(mktemp -d) 152 srtmp=$(mktemp -d) 153 echo 1 > "$srtmp"/version 153 echo 1 > "$srtmp"/version 154 cp "$lasysfsdir"/sample_data "$srtmp"/ 154 cp "$lasysfsdir"/sample_data "$srtmp"/logic-1-1 155 cat > "$srtmp"/metadata << EOF 155 cat > "$srtmp"/metadata << EOF 156 [global] 156 [global] 157 sigrok version=0.2.0 157 sigrok version=0.2.0 158 158 159 [device 1] 159 [device 1] 160 capturefile=logic-1 160 capturefile=logic-1 161 total probes=$(wc -l < "$lasysfsdir"/meta_data 161 total probes=$(wc -l < "$lasysfsdir"/meta_data) 162 samplerate=${samplefreq}Hz 162 samplerate=${samplefreq}Hz 163 unitsize=1 163 unitsize=1 164 EOF 164 EOF 165 cat "$lasysfsdir"/meta_data >> "$srtmp 165 cat "$lasysfsdir"/meta_data >> "$srtmp"/metadata 166 166 167 zipname="$outputdir/${lasysfsdir##*/}- 167 zipname="$outputdir/${lasysfsdir##*/}-$(date +%s).sr" 168 zip -jq "$zipname" "$srtmp"/* 168 zip -jq "$zipname" "$srtmp"/* 169 rm -rf "$srtmp" 169 rm -rf "$srtmp" 170 delay_ack=$(cat "$lasysfsdir"/delay_ns 170 delay_ack=$(cat "$lasysfsdir"/delay_ns_acquisition) 171 [ "$delay_ack" -eq 0 ] && delay_ack=1 171 [ "$delay_ack" -eq 0 ] && delay_ack=1 172 echo "Logic analyzer done. Saved '$zip 172 echo "Logic analyzer done. Saved '$zipname'" 173 echo "Max sample frequency this time: 173 echo "Max sample frequency this time: $((1000000000 / delay_ack))Hz." 174 } 174 } 175 175 176 rep=$(getopt -a -l cpu:,duration-us:,help,inst 176 rep=$(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 177 eval set -- "$rep" 177 eval set -- "$rep" 178 while true; do 178 while true; do 179 case "$1" in 179 case "$1" in 180 -c|--cpu) initcpu="$2"; shift;; 180 -c|--cpu) initcpu="$2"; shift;; 181 -d|--duration-us) parse_si $2; duratio 181 -d|--duration-us) parse_si $2; duration=$si_val; shift;; 182 -h|--help) print_help; exit 0;; 182 -h|--help) print_help; exit 0;; 183 -i|--instance) lainstance="$2"; shift; 183 -i|--instance) lainstance="$2"; shift;; 184 -k|--kernel-debug-dir) debugdir="$2"; 184 -k|--kernel-debug-dir) debugdir="$2"; shift;; 185 -l|--list-instances) listinstances=1;; 185 -l|--list-instances) listinstances=1;; 186 -n|--num_samples) parse_si $2; numsamp 186 -n|--num_samples) parse_si $2; numsamples=$si_val; shift;; 187 -o|--output-dir) outputdir="$2"; shift 187 -o|--output-dir) outputdir="$2"; shift;; 188 -s|--sample_freq) parse_si $2; samplef 188 -s|--sample_freq) parse_si $2; samplefreq=$si_val; shift;; 189 -t|--trigger) triggerdat="$2"; shift;; 189 -t|--trigger) triggerdat="$2"; shift;; 190 --) break;; 190 --) break;; 191 *) fail "error parsing command line: $ 191 *) fail "error parsing command line: $*";; 192 esac 192 esac 193 shift 193 shift 194 done 194 done 195 195 196 for f in $neededcmds; do 196 for f in $neededcmds; do 197 command -v "$f" >/dev/null || fail "Co 197 command -v "$f" >/dev/null || fail "Command '$f' not found" 198 done 198 done 199 199 200 # print cpuset mountpoint if any, errorcode > 200 # print cpuset mountpoint if any, errorcode > 0 if noprefix option was found 201 cpusetdir=$(awk '$3 == "cgroup" && $4 ~ /cpuse 201 cpusetdir=$(awk '$3 == "cgroup" && $4 ~ /cpuset/ { print $2; exit (match($4, /noprefix/) > 0) }' /proc/self/mounts) || cpusetprefix='' 202 if [ -z "$cpusetdir" ]; then 202 if [ -z "$cpusetdir" ]; then 203 cpusetdir="$cpusetdefaultdir" 203 cpusetdir="$cpusetdefaultdir" 204 [ -d $cpusetdir ] || mkdir $cpusetdir 204 [ -d $cpusetdir ] || mkdir $cpusetdir 205 mount -t cgroup -o cpuset none $cpuset 205 mount -t cgroup -o cpuset none $cpusetdir || fail "Couldn't mount cpusets. Not in kernel or already in use?" 206 fi 206 fi 207 207 208 lacpusetdir="$cpusetdir/$ladirname" 208 lacpusetdir="$cpusetdir/$ladirname" 209 lacpusetfile="$lacpusetdir/$cpusetprefix" 209 lacpusetfile="$lacpusetdir/$cpusetprefix" 210 sysfsdir="$debugdir/$ladirname" 210 sysfsdir="$debugdir/$ladirname" 211 211 212 [ "$samplefreq" -ne 0 ] || fail "Invalid sampl 212 [ "$samplefreq" -ne 0 ] || fail "Invalid sample frequency" 213 213 214 [ -d "$sysfsdir" ] || fail "Could not find log 214 [ -d "$sysfsdir" ] || fail "Could not find logic analyzer root dir '$sysfsdir'. Module loaded?" 215 [ -x "$sysfsdir" ] || fail "Could not access l 215 [ -x "$sysfsdir" ] || fail "Could not access logic analyzer root dir '$sysfsdir'. Need root?" 216 216 217 [ $listinstances -gt 0 ] && find "$sysfsdir" - 217 [ $listinstances -gt 0 ] && find "$sysfsdir" -mindepth 1 -type d | sed 's|.*/||' && exit 0 218 218 219 if [ -n "$lainstance" ]; then 219 if [ -n "$lainstance" ]; then 220 lasysfsdir="$sysfsdir/$lainstance" 220 lasysfsdir="$sysfsdir/$lainstance" 221 else 221 else 222 lasysfsdir=$(find "$sysfsdir" -mindept 222 lasysfsdir=$(find "$sysfsdir" -mindepth 1 -type d -print -quit) 223 fi 223 fi 224 [ -d "$lasysfsdir" ] || fail "Logic analyzer d 224 [ -d "$lasysfsdir" ] || fail "Logic analyzer directory '$lasysfsdir' not found!" 225 [ -d "$outputdir" ] || fail "Output directory 225 [ -d "$outputdir" ] || fail "Output directory '$outputdir' not found!" 226 226 227 [ -n "$initcpu" ] && init_cpu "$initcpu" 227 [ -n "$initcpu" ] && init_cpu "$initcpu" 228 [ -d "$lacpusetdir" ] || { echo "Auto-Isolatin 228 [ -d "$lacpusetdir" ] || { echo "Auto-Isolating CPU1"; init_cpu 1; } 229 229 230 ndelay=$((1000000000 / samplefreq)) 230 ndelay=$((1000000000 / samplefreq)) 231 echo "$ndelay" > "$lasysfsdir"/delay_ns 231 echo "$ndelay" > "$lasysfsdir"/delay_ns 232 232 233 [ -n "$duration" ] && numsamples=$((samplefreq 233 [ -n "$duration" ] && numsamples=$((samplefreq * duration / 1000000)) 234 echo $numsamples > "$lasysfsdir"/buf_size 234 echo $numsamples > "$lasysfsdir"/buf_size 235 235 236 if [ -n "$triggerdat" ]; then 236 if [ -n "$triggerdat" ]; then 237 parse_triggerdat "$triggerdat" 237 parse_triggerdat "$triggerdat" 238 printf "$trigger_bindat" > "$lasysfsdi 238 printf "$trigger_bindat" > "$lasysfsdir"/trigger 2>/dev/null || fail "Trigger data '$triggerdat' rejected" 239 fi 239 fi 240 240 241 workcpu=$(cat "${lacpusetfile}effective_cpus") 241 workcpu=$(cat "${lacpusetfile}effective_cpus") 242 [ -n "$workcpu" ] || fail "No isolated CPU fou 242 [ -n "$workcpu" ] || fail "No isolated CPU found" 243 cpumask=$(printf '%x' $((1 << workcpu))) 243 cpumask=$(printf '%x' $((1 << workcpu))) 244 instance=${lasysfsdir##*/} 244 instance=${lasysfsdir##*/} 245 echo "Setting up '$instance': $numsamples samp 245 echo "Setting up '$instance': $numsamples samples at ${samplefreq}Hz with ${triggerdat:-no} trigger using CPU$workcpu" 246 do_capture "$cpumask" & 246 do_capture "$cpumask" &
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.