FROMLIST: KVM: arm64: Add kselftest for tracefs hyp tracefs
Add a test to validate the newly introduced tracefs interface for the pKVM hypervisor. This test covers the usage of extended timestamp and coherence of the tracing clock. Bug: 357781595 Link: https://lore.kernel.org/all/20240911093029.3279154-14-vdonnefort@google.com/ Change-Id: I07db075653410118a197b30f2e2ff9194c4726e8 Signed-off-by: Vincent Donnefort <vdonnefort@google.com>
This commit is contained in:
committed by
Keir Fraser
parent
997fe0e73b
commit
05b2da3990
@@ -92,6 +92,7 @@ enum __kvm_host_smccc_func {
|
||||
__KVM_HOST_SMCCC_FUNC___pkvm_reset_tracing,
|
||||
__KVM_HOST_SMCCC_FUNC___pkvm_swap_reader_tracing,
|
||||
__KVM_HOST_SMCCC_FUNC___pkvm_enable_event,
|
||||
__KVM_HOST_SMCCC_FUNC___pkvm_selftest_event,
|
||||
};
|
||||
|
||||
#define DECLARE_KVM_VHE_SYM(sym) extern char sym[]
|
||||
|
||||
@@ -28,4 +28,14 @@ HYP_EVENT(hyp_exit,
|
||||
),
|
||||
HE_PRINTK(" ")
|
||||
);
|
||||
|
||||
#ifdef CONFIG_PROTECTED_NVHE_TESTING
|
||||
HYP_EVENT(selftest,
|
||||
HE_PROTO(void),
|
||||
HE_STRUCT(),
|
||||
HE_ASSIGN(),
|
||||
HE_PRINTK(" ")
|
||||
);
|
||||
#endif
|
||||
|
||||
#endif /* __ARM64_KVM_HYPEVENTS_H_ */
|
||||
|
||||
@@ -83,4 +83,13 @@ config PTDUMP_STAGE2_DEBUGFS
|
||||
|
||||
If in doubt, say N.
|
||||
|
||||
config PROTECTED_NVHE_TESTING
|
||||
bool "Protected KVM hypervisor testing infrastructure"
|
||||
depends on KVM
|
||||
default n
|
||||
help
|
||||
Say Y here to enable pKVM hypervisor testing infrastructure.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
endif # VIRTUALIZATION
|
||||
|
||||
@@ -1266,6 +1266,19 @@ static void handle___pkvm_enable_event(struct kvm_cpu_context *host_ctxt)
|
||||
cpu_reg(host_ctxt, 1) = __pkvm_enable_event(id, enable);
|
||||
}
|
||||
|
||||
static void handle___pkvm_selftest_event(struct kvm_cpu_context *host_ctxt)
|
||||
{
|
||||
int smc_ret = SMCCC_RET_NOT_SUPPORTED, ret = -EOPNOTSUPP;
|
||||
|
||||
#ifdef CONFIG_PROTECTED_NVHE_TESTING
|
||||
trace_selftest();
|
||||
smc_ret = SMCCC_RET_SUCCESS;
|
||||
ret = 0;
|
||||
#endif
|
||||
cpu_reg(host_ctxt, 0) = smc_ret;
|
||||
cpu_reg(host_ctxt, 1) = ret;
|
||||
}
|
||||
|
||||
typedef void (*hcall_t)(struct kvm_cpu_context *);
|
||||
|
||||
#define HANDLE_FUNC(x) [__KVM_HOST_SMCCC_FUNC_##x] = (hcall_t)handle_##x
|
||||
@@ -1310,6 +1323,7 @@ static const hcall_t host_hcall[] = {
|
||||
HANDLE_FUNC(__pkvm_reset_tracing),
|
||||
HANDLE_FUNC(__pkvm_swap_reader_tracing),
|
||||
HANDLE_FUNC(__pkvm_enable_event),
|
||||
HANDLE_FUNC(__pkvm_selftest_event),
|
||||
};
|
||||
|
||||
static void handle_host_hcall(struct kvm_cpu_context *host_ctxt)
|
||||
|
||||
@@ -885,6 +885,35 @@ static int hyp_trace_clock_show(struct seq_file *m, void *v)
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(hyp_trace_clock);
|
||||
|
||||
#ifdef CONFIG_PROTECTED_NVHE_TESTING
|
||||
static int selftest_event_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
if (file->f_mode & FMODE_WRITE)
|
||||
return kvm_call_hyp_nvhe(__pkvm_selftest_event);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t selftest_event_write(struct file *f, const char __user *buf,
|
||||
size_t cnt, loff_t *pos)
|
||||
{
|
||||
return cnt;
|
||||
}
|
||||
|
||||
static const struct file_operations selftest_event_fops = {
|
||||
.open = selftest_event_open,
|
||||
.write = selftest_event_write,
|
||||
};
|
||||
|
||||
static void hyp_trace_init_testing_tracefs(struct dentry *root)
|
||||
{
|
||||
tracefs_create_file("selftest_event", TRACEFS_MODE_WRITE, root, NULL,
|
||||
&selftest_event_fops);
|
||||
}
|
||||
#else
|
||||
static void hyp_trace_init_testing_tracefs(struct dentry *root) { }
|
||||
#endif
|
||||
|
||||
int hyp_trace_init_tracefs(void)
|
||||
{
|
||||
struct dentry *root, *per_cpu_root;
|
||||
@@ -943,6 +972,7 @@ int hyp_trace_init_tracefs(void)
|
||||
}
|
||||
|
||||
hyp_trace_init_event_tracefs(root);
|
||||
hyp_trace_init_testing_tracefs(root);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
all:
|
||||
|
||||
TEST_PROGS := hyp-trace-test
|
||||
|
||||
include ../lib.mk
|
||||
@@ -0,0 +1,4 @@
|
||||
CONFIG_FTRACE=y
|
||||
CONFIG_ARM64=y
|
||||
CONFIG_KVM=y
|
||||
CONFIG_PROTECTED_NVHE_TESTING=y
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
#!/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."
|
||||
}
|
||||
|
||||
goto_hyp_trace
|
||||
|
||||
test_reset
|
||||
test_unloading
|
||||
test_big_bpacking
|
||||
test_ts
|
||||
test_extended_ts
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user