#!/bin/bash
# Check Arm CoreSight trace data recording and synthesized samples (exclusive)

# Uses the 'perf record' to record trace data with Arm CoreSight sinks;
# then verify if there have any branch samples and instruction samples
# are generated by CoreSight with 'perf script' and 'perf report'
# commands.

# SPDX-License-Identifier: GPL-2.0
# Leo Yan <leo.yan@linaro.org>, 2020

glb_err=0

skip_if_no_cs_etm_event() {
	perf list pmu | grep -q 'cs_etm//' && return 0

	# cs_etm event doesn't exist
	return 2
}

skip_if_no_cs_etm_event || exit 2

perfdata=$(mktemp /tmp/__perf_test.perf.data.XXXXX)
file=$(mktemp /tmp/temporary_file.XXXXX)

cleanup_files()
{
	rm -f ${perfdata}
	rm -f ${file}
	rm -f "${perfdata}.old"
	trap - EXIT TERM INT
	exit $glb_err
}

trap cleanup_files EXIT TERM INT

record_touch_file() {
	echo "Recording trace (only user mode) with path: CPU$2 => $1"
	rm -f $file
	perf record -o ${perfdata} -e cs_etm/@$1/u --per-thread \
		-- taskset -c $2 touch $file > /dev/null 2>&1
}

perf_script_branch_samples() {
	echo "Looking at perf.data file for dumping branch samples:"

	# Below is an example of the branch samples dumping:
	#   touch  6512          1         branches:u:      ffffb220824c strcmp+0xc (/lib/aarch64-linux-gnu/ld-2.27.so)
	#   touch  6512          1         branches:u:      ffffb22082e0 strcmp+0xa0 (/lib/aarch64-linux-gnu/ld-2.27.so)
	#   touch  6512          1         branches:u:      ffffb2208320 strcmp+0xe0 (/lib/aarch64-linux-gnu/ld-2.27.so)
	perf script -F,-time -i ${perfdata} 2>&1 | \
		grep -E " +$1 +[0-9]+ .* +branches:(.*:)? +" > /dev/null 2>&1
}

perf_report_branch_samples() {
	echo "Looking at perf.data file for reporting branch samples:"

	# Below is an example of the branch samples reporting:
	#   73.04%    73.04%  touch    libc-2.27.so      [.] _dl_addr
	#    7.71%     7.71%  touch    libc-2.27.so      [.] getenv
	#    2.59%     2.59%  touch    ld-2.27.so        [.] strcmp
	perf report --stdio -i ${perfdata} 2>&1 | \
		grep -E " +[0-9]+\.[0-9]+% +[0-9]+\.[0-9]+% +$1 " > /dev/null 2>&1
}

perf_report_instruction_samples() {
	echo "Looking at perf.data file for instruction samples:"

	# Below is an example of the instruction samples reporting:
	#   68.12%  touch    libc-2.27.so   [.] _dl_addr
	#    5.80%  touch    libc-2.27.so   [.] getenv
	#    4.35%  touch    ld-2.27.so     [.] _dl_fixup
	perf report --itrace=i20i --stdio -i ${perfdata} 2>&1 | \
		grep -E " +[0-9]+\.[0-9]+% +$1" > /dev/null 2>&1
}

arm_cs_report() {
	if [ $2 != 0 ]; then
		echo "$1: FAIL"
		glb_err=$2
	else
		echo "$1: PASS"
	fi
}

is_device_sink() {
	# If the node of "enable_sink" is existed under the device path, this
	# means the device is a sink device.  Need to exclude 'tpiu' since it
	# cannot support perf PMU.
	echo "$1" | grep -E -q -v "tpiu"

	if [ $? -eq 0 ] && [ -e "$1/enable_sink" ]; then

		pmu_dev="/sys/bus/event_source/devices/cs_etm/sinks/$2"

		# Warn if the device is not supported by PMU
		if ! [ -f $pmu_dev ]; then
			echo "PMU doesn't support $pmu_dev"
		fi

		return 0
	fi

	# Otherwise, it's not a sink device
	return 1
}

arm_cs_iterate_devices() {
	for dev in $1/connections/out\:*; do

		# Skip testing if it's not a directory
		! [ -d $dev ] && continue;

		# Read out its symbol link file name
		path=`readlink -f $dev`

		# Extract device name from path, e.g.
		#   path = '/sys/devices/platform/20010000.etf/tmc_etf0'
		#     `> device_name = 'tmc_etf0'
		device_name=$(basename $path)

		if is_device_sink $path $device_name; then

			record_touch_file $device_name $2 &&
			perf_script_branch_samples touch &&
			perf_report_branch_samples touch &&
			perf_report_instruction_samples touch

			err=$?
			arm_cs_report "CoreSight path testing (CPU$2 -> $device_name)" $err
		fi

		arm_cs_iterate_devices $dev $2
	done
}

arm_cs_etm_traverse_path_test() {
	# Iterate for every ETM device
	for dev in /sys/bus/event_source/devices/cs_etm/cpu*; do
		# Canonicalize the path
		dev=`readlink -f $dev`

		# Find the ETM device belonging to which CPU
		cpu=`cat $dev/cpu`

		# Use depth-first search (DFS) to iterate outputs
		arm_cs_iterate_devices $dev $cpu
	done
}

arm_cs_etm_system_wide_test() {
	echo "Recording trace with system wide mode"
	perf record -o ${perfdata} -e cs_etm// -a -- ls > /dev/null 2>&1

	# System-wide mode should include perf samples so test for that
	# instead of ls
	perf_script_branch_samples perf &&
	perf_report_branch_samples perf &&
	perf_report_instruction_samples perf

	err=$?
	arm_cs_report "CoreSight system wide testing" $err
}

arm_cs_etm_snapshot_test() {
	echo "Recording trace with snapshot mode"
	perf record -o ${perfdata} -e cs_etm// -S \
		-- dd if=/dev/zero of=/dev/null > /dev/null 2>&1 &
	PERFPID=$!

	# Wait for perf program
	sleep 1

	# Send signal to snapshot trace data
	kill -USR2 $PERFPID

	# Stop perf program
	kill $PERFPID
	wait $PERFPID

	perf_script_branch_samples dd &&
	perf_report_branch_samples dd &&
	perf_report_instruction_samples dd

	err=$?
	arm_cs_report "CoreSight snapshot testing" $err
}

arm_cs_etm_basic_test() {
	echo "Recording trace with '$*'"
	perf record -o ${perfdata} "$@" -m,8M -- ls > /dev/null 2>&1

	perf_script_branch_samples ls &&
	perf_report_branch_samples ls &&
	perf_report_instruction_samples ls

	err=$?
	arm_cs_report "CoreSight basic testing with '$*'" $err
}

arm_cs_etm_test_cpu_list() {
	echo "Testing sparse CPU list: $1"
	perf record -o ${perfdata} -e cs_etm//u -C $1 \
		-- taskset --cpu-list $1 true > /dev/null 2>&1
	perf_script_branch_samples true
	err=$?
	arm_cs_report "CoreSight sparse CPUs with '$*'" $err
}

arm_cs_etm_sparse_cpus_test() {
	# Iterate for every ETM device
	cpus=()
	for dev in /sys/bus/event_source/devices/cs_etm/cpu*; do
		# Canonicalize the path
		dev=`readlink -f $dev`

		# Find the ETM device belonging to which CPU
		cpus+=("$(cat $dev/cpu)")
	done

	mapfile -t cpus < <(printf '%s\n' "${cpus[@]}" | sort -n)
	total=${#cpus[@]}

	# Need more than 1 to test
	if [ $total -le 1 ]; then
		return 0
	fi

	half=$((total / 2))

	# First half
	first_half=$(IFS=,; echo "${cpus[*]:0:$half}")
	arm_cs_etm_test_cpu_list $first_half

	# Second half
	second_half=$(IFS=,; echo "${cpus[*]:$half}")
	arm_cs_etm_test_cpu_list $second_half

	# Odd list is the same as halves unless >= 4 CPUs
	if [ $total -lt 4 ]; then
		return 0
	fi

	# Odd indices
	odd_cpus=()
	for ((i=1; i<total; i+=2)); do
		odd_cpus+=("${cpus[$i]}")
	done
	odd_list=$(IFS=,; echo "${odd_cpus[*]}")
	arm_cs_etm_test_cpu_list $odd_list
}

arm_cs_etm_traverse_path_test
arm_cs_etm_system_wide_test
arm_cs_etm_snapshot_test

# Test all combinations of per-thread, system-wide and normal mode with
# and without timestamps
arm_cs_etm_basic_test -e cs_etm/timestamp=0/ --per-thread
arm_cs_etm_basic_test -e cs_etm/timestamp=1/ --per-thread
arm_cs_etm_basic_test -e cs_etm/timestamp=0/ -a
arm_cs_etm_basic_test -e cs_etm/timestamp=1/ -a
arm_cs_etm_basic_test -e cs_etm/timestamp=0/
arm_cs_etm_basic_test -e cs_etm/timestamp=1/

arm_cs_etm_sparse_cpus_test

exit $glb_err
