Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/tools/gpio/gpio-sloppy-logic-analyzer.sh
26278 views
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 <[email protected]>
7
# Copyright (C) Renesas Electronics Corporation
8
9
samplefreq=1000000
10
numsamples=250000
11
cpusetdefaultdir='/sys/fs/cgroup'
12
cpusetprefix='cpuset.'
13
debugdir='/sys/kernel/debug'
14
ladirname='gpio-sloppy-logic-analyzer'
15
outputdir="$PWD"
16
neededcmds='taskset zip'
17
max_chans=8
18
duration=
19
initcpu=
20
listinstances=0
21
lainstance=
22
lasysfsdir=
23
triggerdat=
24
trigger_bindat=
25
progname="${0##*/}"
26
print_help()
27
{
28
cat << EOF
29
$progname - helper script for the Linux Kernel Sloppy GPIO Logic Analyzer
30
Available 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
56
Examples:
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:
59
'$progname'
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"'
62
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 once the sub-process finishes.
65
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 sample data which may be consumed by others.
68
The filename is the logic analyzer instance name plus a since-epoch timestamp.
69
EOF
70
}
71
72
fail()
73
{
74
echo "$1"
75
exit 1
76
}
77
78
parse_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
}
83
set_newmask()
84
{
85
for f in $(find "$1" -iname "$2"); do echo "$newmask" > "$f" 2>/dev/null || true; done
86
}
87
88
init_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
124
parse_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
148
do_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]
157
sigrok version=0.2.0
158
159
[device 1]
160
capturefile=logic-1
161
total probes=$(wc -l < "$lasysfsdir"/meta_data)
162
samplerate=${samplefreq}Hz
163
unitsize=1
164
EOF
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
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"
178
while 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
194
done
195
196
for f in $neededcmds; do
197
command -v "$f" >/dev/null || fail "Command '$f' not found"
198
done
199
200
# print cpuset mountpoint if any, errorcode > 0 if noprefix option was found
201
cpusetdir=$(awk '$3 == "cgroup" && $4 ~ /cpuset/ { print $2; exit (match($4, /noprefix/) > 0) }' /proc/self/mounts) || cpusetprefix=''
202
if [ -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?"
206
fi
207
208
lacpusetdir="$cpusetdir/$ladirname"
209
lacpusetfile="$lacpusetdir/$cpusetprefix"
210
sysfsdir="$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
219
if [ -n "$lainstance" ]; then
220
lasysfsdir="$sysfsdir/$lainstance"
221
else
222
lasysfsdir=$(find "$sysfsdir" -mindepth 1 -type d -print -quit)
223
fi
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
230
ndelay=$((1000000000 / samplefreq))
231
echo "$ndelay" > "$lasysfsdir"/delay_ns
232
233
[ -n "$duration" ] && numsamples=$((samplefreq * duration / 1000000))
234
echo $numsamples > "$lasysfsdir"/buf_size
235
236
if [ -n "$triggerdat" ]; then
237
parse_triggerdat "$triggerdat"
238
printf "$trigger_bindat" > "$lasysfsdir"/trigger 2>/dev/null || fail "Trigger data '$triggerdat' rejected"
239
fi
240
241
workcpu=$(cat "${lacpusetfile}effective_cpus")
242
[ -n "$workcpu" ] || fail "No isolated CPU found"
243
cpumask=$(printf '%x' $((1 << workcpu)))
244
instance=${lasysfsdir##*/}
245
echo "Setting up '$instance': $numsamples samples at ${samplefreq}Hz with ${triggerdat:-no} trigger using CPU$workcpu"
246
do_capture "$cpumask" &
247
248