| #!/bin/sh -e |
| # SPDX-License-Identifier: GPL-2.0-only |
| |
| # hyp-trace-test - Tracefs for pKVM hypervisor test |
| # |
| # Copyright (C) 2024 - Google LLC |
| # Author: Vincent Donnefort <vdonnefort@google.com> |
| # |
| |
| log_and_die() |
| { |
| echo "$1" |
| |
| exit 1 |
| } |
| |
| host_clock() |
| { |
| # BOOTTIME clock |
| awk '/now/ { printf "%.6f\n", $3 / 1000000000 }' /proc/timer_list |
| } |
| |
| page_size() |
| { |
| echo "$(awk '/KernelPageSize/ {print $2; exit}' /proc/self/smaps) * 1024" | bc |
| } |
| |
| goto_hyp_trace() |
| { |
| if [ -d "/sys/kernel/debug/tracing/hypervisor" ]; then |
| cd /sys/kernel/debug/tracing/hypervisor |
| return |
| fi |
| |
| if [ -d "/sys/kernel/tracing/hypervisor" ]; then |
| cd /sys/kernel/tracing/hypervisor |
| return |
| fi |
| |
| echo "ERROR: hyp tracing folder not found!" |
| |
| exit 1 |
| } |
| |
| reset_hyp_trace() |
| { |
| echo 0 > tracing_on |
| echo 0 > trace |
| for event in events/hypervisor/*; do |
| echo 0 > $event/enable |
| done |
| } |
| |
| setup_hyp_trace() |
| { |
| reset_hyp_trace |
| |
| echo 16 > buffer_size_kb |
| echo 1 > events/hypervisor/selftest/enable |
| echo 1 > tracing_on |
| } |
| |
| stop_hyp_trace() |
| { |
| echo 0 > tracing_on |
| } |
| |
| hyp_trace_loaded() |
| { |
| grep -q "(loaded)" buffer_size_kb |
| } |
| |
| write_events() |
| { |
| local num="$1" |
| local func="$2" |
| |
| for i in $(seq 1 $num); do |
| echo 1 > selftest_event |
| [ -z "$func" -o $i -eq $num ] || eval $func |
| done |
| } |
| |
| consuming_read() |
| { |
| local output=$1 |
| |
| cat trace_pipe > $output & |
| |
| echo $! |
| } |
| |
| run_test_consuming() |
| { |
| local nr_events=$1 |
| local func=$2 |
| local tmp="$(mktemp)" |
| local start_ts=0 |
| local end_ts=0 |
| local pid=0 |
| |
| echo "Output trace file: $tmp" |
| |
| setup_hyp_trace |
| pid=$(consuming_read $tmp) |
| |
| start_ts=$(host_clock) |
| write_events $nr_events $func |
| stop_hyp_trace |
| end_ts=$(host_clock) |
| |
| kill $pid |
| validate_test $tmp $nr_events $start_ts $end_ts |
| |
| rm $tmp |
| } |
| |
| validate_test() |
| { |
| local output=$1 |
| local expected_events=$2 |
| local start_ts=$3 |
| local end_ts=$4 |
| local prev_ts=$3 |
| local ts=0 |
| local num_events=0 |
| |
| IFS=$'\n' |
| for line in $(cat $output); do |
| echo "$line" | grep -q -E "^# " && continue |
| ts=$(echo "$line" | awk '{print $2}' | cut -d ':' -f1) |
| if [ $(echo "$ts<$prev_ts" | bc) -eq 1 ]; then |
| log_and_die "Error event @$ts < $prev_ts" |
| fi |
| prev_ts=$ts |
| num_events=$((num_events + 1)) |
| done |
| |
| if [ $(echo "$ts>$end_ts" | bc) -eq 1 ]; then |
| log_and_die "Error event @$ts > $end_ts" |
| fi |
| |
| if [ $num_events -ne $expected_events ]; then |
| log_and_die "Expected $expected_events events, got $num_events" |
| fi |
| } |
| |
| test_ts() |
| { |
| echo "Test Timestamps..." |
| |
| run_test_consuming 1000 |
| |
| echo "done." |
| } |
| |
| test_extended_ts() |
| { |
| echo "Test Extended Timestamps..." |
| |
| run_test_consuming 1000 "sleep 0.1" |
| |
| echo "done." |
| } |
| |
| assert_loaded() |
| { |
| hyp_trace_loaded || log_and_die "Expected loaded buffer" |
| } |
| |
| assert_unloaded() |
| { |
| ! hyp_trace_loaded || log_and_die "Expected unloaded buffer" |
| } |
| |
| test_unloading() |
| { |
| local tmp="$(mktemp)" |
| |
| echo "Test unloading..." |
| |
| setup_hyp_trace |
| assert_loaded |
| |
| echo 0 > tracing_on |
| assert_unloaded |
| |
| pid=$(consuming_read $tmp) |
| sleep 1 |
| assert_loaded |
| kill $pid |
| assert_unloaded |
| |
| echo 1 > tracing_on |
| write_events 1 |
| echo 0 > trace |
| assert_loaded |
| echo 0 > tracing_on |
| assert_unloaded |
| |
| echo "done." |
| } |
| |
| test_reset() |
| { |
| local tmp="$(mktemp)" |
| |
| echo "Test Reset..." |
| |
| setup_hyp_trace |
| write_events 5 |
| echo 0 > trace |
| write_events 5 |
| |
| pid=$(consuming_read $tmp) |
| sleep 1 |
| stop_hyp_trace |
| kill $pid |
| |
| validate_test $tmp 5 0 $(host_clock) |
| |
| rm $tmp |
| |
| echo "done." |
| } |
| |
| test_big_bpacking() |
| { |
| local hyp_buffer_page_size=48 |
| local page_size=$(page_size) |
| local min_buf_size=$(echo "$page_size * $page_size / ($hyp_buffer_page_size * $(nproc))" | bc) |
| |
| min_buf_size=$(echo "$min_buf_size * 2 / 1024" | bc) |
| |
| echo "Test loading $min_buf_size kB buffer..." |
| |
| reset_hyp_trace |
| echo $min_buf_size > buffer_size_kb |
| echo 1 > tracing_on |
| |
| stop_hyp_trace |
| |
| echo "done." |
| } |
| |
| validate_event() |
| { |
| local line="$1" |
| local expect_event="$2" |
| local expect_arg="$3" |
| local event="$(echo "$line" | awk '{print $3}')" |
| local arg="$(echo "$line" | awk '{print $4}')" |
| |
| [ $event == $expect_event ] || log_and_die "Expected event '$expect_event', got: '$event'" |
| [ $arg == $expect_arg ] || log_and_die "Expected arg '$expect_arg', got: '$arg'" |
| } |
| |
| setup_hyp_ftrace() |
| { |
| reset_hyp_trace() |
| echo 1 > events/hypervisor/func/enable |
| echo 1 > events/hypervisor/func_ret/enable |
| } |
| |
| run_test_ftrace() |
| { |
| local output="$2" |
| |
| setup_hyp_ftrace |
| |
| echo 1 > tracing_on |
| write_events 1 |
| echo 0 > tracing_on |
| |
| pid=$(consuming_read $tmp) |
| sleep 1 |
| kill $pid |
| |
| } |
| |
| test_ftrace_nofilter() |
| { |
| local func="__kvm_nvhe_handle___pkvm_selftest_event" |
| local tmp="$(mktemp)" |
| |
| echo "Test ftrace..." |
| |
| echo "*" > set_ftrace_filter |
| |
| run_test_ftrace $tmp |
| |
| grep -qE "func *$func" $tmp || \ |
| log_and_die "Couldn't find 'func' event with arg '$func'" |
| grep -q "func_ret $func" $tmp || \ |
| log_and_die "Couldn't find 'func_ret' event with arg '$func'" |
| |
| rm $tmp |
| } |
| |
| test_ftrace_filter() |
| { |
| local func="__kvm_nvhe_handle___pkvm_selftest_event" |
| local tmp="$(mktemp)" |
| |
| echo "Test ftrace filtering..." |
| |
| echo "$func" > set_ftrace_filter |
| |
| [ "$(cat set_ftrace_filter)" == "$func" ] || \ |
| log_and_die "Failed to set set_ftrace_filter" |
| |
| run_test_ftrace $tmp |
| |
| validate_event "$(awk 'NR==1 {print}' $tmp)" "func" "$func" |
| validate_event "$(awk 'NR==2 {print}' $tmp)" "func_ret" "$func" |
| |
| rm $tmp |
| } |
| |
| goto_hyp_trace |
| |
| test_reset |
| test_unloading |
| test_big_bpacking |
| test_ts |
| test_extended_ts |
| test_ftrace_nofilter |
| test_ftrace_filter |
| |
| exit 0 |