gpio-sloppy-logic-analyzer.sh 8.7 KB

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