brintos

brintos / linux-shallow public Read only

0
0
Text · 8.7 KiB · ed21a11 Raw
247 lines · bash
1#!/bin/sh -eu2# SPDX-License-Identifier: GPL-2.03#4# Helper script for the Linux Kernel GPIO sloppy logic analyzer5#6# Copyright (C) Wolfram Sang <wsa@sang-engineering.com>7# Copyright (C) Renesas Electronics Corporation8 9samplefreq=100000010numsamples=25000011cpusetdefaultdir='/sys/fs/cgroup'12cpusetprefix='cpuset.'13debugdir='/sys/kernel/debug'14ladirname='gpio-sloppy-logic-analyzer'15outputdir="$PWD"16neededcmds='taskset zip'17max_chans=818duration=19initcpu=20listinstances=021lainstance=22lasysfsdir=23triggerdat=24trigger_bindat=25progname="${0##*/}"26print_help()27{28	cat << EOF29$progname - helper script for the Linux Kernel Sloppy GPIO Logic Analyzer30Available 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 help36	-i|--instance <str>: name of the logic analyzer in case you have multiple instances. Default37			     to first instance found38	-k|--kernel-debug-dir <str>: path to the kernel debugfs mountpoint. Default: <$debugdir>39	-l|--list-instances: list all available instances40	-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 dir42	-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. First45			    char is channel number starting at "1". Second char is trigger level:46			    "L" - low; "H" - high; "R" - rising; "F" - falling47			    These pairs can be combined with "+", so "1H+2F" triggers when probe 148			    is high while probe 2 has a falling edge. You can have multiple triggers49			    combined with ",". So, "1H+2F,1H+2R" is like the example before but it50			    waits for a rising edge on probe 2 while probe 1 is still high after the51			    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 in64the 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 is67a 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.69EOF70}71 72fail()73{74	echo "$1"75	exit 176}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; done86}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" ] && return96	[ -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 CPU109	for p in $(ps -o pid | tail -n +2); do110		mask=$(taskset -p "$p") || continue111		# Ignore tasks with a custom mask, i.e. not equal $oldmask112		[ "${mask##*: }" = "$oldmask" ] || continue113		taskset -p "$newmask" "$p" || continue114	done 2>/dev/null >/dev/null115 116	# Big hammer! Working with 'rcu_momentary_dyntick_idle()' for a more fine-grained solution117	# still printed warnings. Same for re-enabling the stall detector after sampling.118	echo 1 > /sys/module/rcupdate/parameters/rcu_cpu_stall_suppress119 120	cpufreqgov="/sys/devices/system/cpu/cpu$isol_cpu/cpufreq/scaling_governor"121	[ -w "$cpufreqgov" ] && echo 'performance' > "$cpufreqgov" || true122}123 124parse_triggerdat()125{126	oldifs="$IFS"127	IFS=','; for trig in $1; do128		mask=0; val1=0; val2=0129		IFS='+'; for elem in $trig; do130			chan=${elem%[lhfrLHFR]}131			mode=${elem#$chan}132			# Check if we could parse something and the channel number fits133			[ "$chan" != "$elem" ] && [ "$chan" -le $max_chans ] || fail "Trigger syntax error: $elem"134			bit=$((1 << (chan - 1)))135			mask=$((mask | bit))136			case $mode in137				[hH]) val1=$((val1 | bit)); val2=$((val2 | bit));;138				[fF]) val1=$((val1 | bit));;139				[rR]) val2=$((val2 | bit));;140			esac141		done142		trigger_bindat="$trigger_bindat$(printf '\\%o\\%o' $mask $val1)"143		[ $val1 -ne $val2 ] && trigger_bindat="$trigger_bindat$(printf '\\%o\\%o' $mask $val2)"144	done145	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"/version154	cp "$lasysfsdir"/sample_data "$srtmp"/logic-1-1155	cat > "$srtmp"/metadata << EOF156[global]157sigrok version=0.2.0158 159[device 1]160capturefile=logic-1161total probes=$(wc -l < "$lasysfsdir"/meta_data)162samplerate=${samplefreq}Hz163unitsize=1164EOF165	cat "$lasysfsdir"/meta_data >> "$srtmp"/metadata166 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=1172	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 1177eval set -- "$rep"178while true; do179	case "$1" in180	-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	esac193	shift194done195 196for f in $neededcmds; do197	command -v "$f" >/dev/null || fail "Command '$f' not found"198done199 200# print cpuset mountpoint if any, errorcode > 0 if noprefix option was found201cpusetdir=$(awk '$3 == "cgroup" && $4 ~ /cpuset/ { print $2; exit (match($4, /noprefix/) > 0) }' /proc/self/mounts) || cpusetprefix=''202if [ -z "$cpusetdir" ]; then203	cpusetdir="$cpusetdefaultdir"204	[ -d $cpusetdir ] || mkdir $cpusetdir205	mount -t cgroup -o cpuset none $cpusetdir || fail "Couldn't mount cpusets. Not in kernel or already in use?"206fi207 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 0218 219if [ -n "$lainstance" ]; then220	lasysfsdir="$sysfsdir/$lainstance"221else222	lasysfsdir=$(find "$sysfsdir" -mindepth 1 -type d -print -quit)223fi224[ -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_ns232 233[ -n "$duration" ] && numsamples=$((samplefreq * duration / 1000000))234echo $numsamples > "$lasysfsdir"/buf_size235 236if [ -n "$triggerdat" ]; then237	parse_triggerdat "$triggerdat"238	printf "$trigger_bindat" > "$lasysfsdir"/trigger 2>/dev/null || fail "Trigger data '$triggerdat' rejected"239fi240 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