Loading...
Note: File does not exist in v3.1.
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" &