Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,27 @@
perf-test-$(CONFIG_DWARF_UNWIND) += regs_load.o
perf-test-$(CONFIG_DWARF_UNWIND) += dwarf-unwind.o
perf-test-y += arch-tests.o
perf-test-y += hybrid.o
perf-test-y += intel-pt-test.o
ifeq ($(CONFIG_EXTRA_TESTS),y)
perf-test-y += insn-x86.o
endif
perf-test-$(CONFIG_X86_64) += bp-modify.o
perf-test-y += amd-ibs-via-core-pmu.o
perf-test-y += amd-ibs-period.o
perf-test-y += topdown.o
ifdef SHELLCHECK
SHELL_TESTS := gen-insn-x86-dat.sh
SHELL_TEST_LOGS := $(SHELL_TESTS:%=%.shellcheck_log)
else
SHELL_TESTS :=
SHELL_TEST_LOGS :=
endif
$(OUTPUT)%.shellcheck_log: %
$(call rule_mkdir)
$(Q)$(call echo-cmd,test)$(SHELLCHECK) "$<" > $@ || (cat $@ && rm $@ && false)
perf-test-y += $(SHELL_TEST_LOGS)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-2.0
#include "arch-tests.h"
#include "linux/perf_event.h"
#include "tests/tests.h"
#include "pmu.h"
#include "pmus.h"
#include "../perf-sys.h"
#include "debug.h"
#define NR_SUB_TESTS 5
static struct sub_tests {
int type;
unsigned long config;
bool valid;
} sub_tests[NR_SUB_TESTS] = {
{ PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES, true },
{ PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS, false },
{ PERF_TYPE_RAW, 0x076, true },
{ PERF_TYPE_RAW, 0x0C1, true },
{ PERF_TYPE_RAW, 0x012, false },
};
static int event_open(int type, unsigned long config)
{
struct perf_event_attr attr;
memset(&attr, 0, sizeof(struct perf_event_attr));
attr.type = type;
attr.size = sizeof(struct perf_event_attr);
attr.config = config;
attr.disabled = 1;
attr.precise_ip = 1;
attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID;
attr.sample_period = 100000;
return sys_perf_event_open(&attr, -1, 0, -1, 0);
}
int test__amd_ibs_via_core_pmu(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
struct perf_pmu *ibs_pmu;
int ret = TEST_OK;
int fd, i;
ibs_pmu = perf_pmus__find("ibs_op");
if (!ibs_pmu)
return TEST_SKIP;
for (i = 0; i < NR_SUB_TESTS; i++) {
fd = event_open(sub_tests[i].type, sub_tests[i].config);
pr_debug("type: 0x%x, config: 0x%lx, fd: %d - ", sub_tests[i].type,
sub_tests[i].config, fd);
if ((sub_tests[i].valid && fd == -1) ||
(!sub_tests[i].valid && fd > 0)) {
pr_debug("Fail\n");
ret = TEST_FAIL;
} else {
pr_debug("Pass\n");
}
if (fd > 0)
close(fd);
}
return ret;
}
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-2.0
#include <string.h>
#include "tests/tests.h"
#include "arch-tests.h"
#ifdef HAVE_EXTRA_TESTS
DEFINE_SUITE("x86 instruction decoder - new instructions", insn_x86);
#endif
static struct test_case intel_pt_tests[] = {
TEST_CASE("Intel PT packet decoder", intel_pt_pkt_decoder),
TEST_CASE("Intel PT hybrid CPU compatibility", intel_pt_hybrid_compat),
{ .name = NULL, }
};
struct test_suite suite__intel_pt = {
.desc = "Intel PT",
.test_cases = intel_pt_tests,
};
#if defined(__x86_64__)
DEFINE_SUITE("x86 bp modify", bp_modify);
#endif
DEFINE_SUITE("AMD IBS via core pmu", amd_ibs_via_core_pmu);
DEFINE_SUITE_EXCLUSIVE("AMD IBS sample period", amd_ibs_period);
static struct test_case hybrid_tests[] = {
TEST_CASE_REASON("x86 hybrid event parsing", hybrid, "not hybrid"),
{ .name = NULL, }
};
struct test_suite suite__hybrid = {
.desc = "x86 hybrid",
.test_cases = hybrid_tests,
};
struct test_suite *arch_tests[] = {
#ifdef HAVE_DWARF_UNWIND_SUPPORT
&suite__dwarf_unwind,
#endif
#ifdef HAVE_EXTRA_TESTS
&suite__insn_x86,
#endif
&suite__intel_pt,
#if defined(__x86_64__)
&suite__bp_modify,
#endif
&suite__amd_ibs_via_core_pmu,
&suite__amd_ibs_period,
&suite__hybrid,
&suite__x86_topdown,
NULL,
};
@@ -0,0 +1,208 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <asm/ptrace.h>
#include <errno.h>
#include "debug.h"
#include "tests/tests.h"
#include "arch-tests.h"
static noinline int bp_1(void)
{
pr_debug("in %s\n", __func__);
return 0;
}
static noinline int bp_2(void)
{
pr_debug("in %s\n", __func__);
return 0;
}
static int spawn_child(void)
{
int child = fork();
if (child == 0) {
/*
* The child sets itself for as tracee and
* waits in signal for parent to trace it,
* then it calls bp_1 and quits.
*/
int err = ptrace(PTRACE_TRACEME, 0, NULL, NULL);
if (err) {
pr_debug("failed to PTRACE_TRACEME\n");
exit(1);
}
raise(SIGCONT);
bp_1();
exit(0);
}
return child;
}
/*
* This tests creates HW breakpoint, tries to
* change it and checks it was properly changed.
*/
static int bp_modify1(void)
{
pid_t child;
int status;
unsigned long rip = 0, dr7 = 1;
child = spawn_child();
waitpid(child, &status, 0);
if (WIFEXITED(status)) {
pr_debug("tracee exited prematurely 1\n");
return TEST_FAIL;
}
/*
* The parent does following steps:
* - creates a new breakpoint (id 0) for bp_2 function
* - changes that breakpoint to bp_1 function
* - waits for the breakpoint to hit and checks
* it has proper rip of bp_1 function
* - detaches the child
*/
if (ptrace(PTRACE_POKEUSER, child,
offsetof(struct user, u_debugreg[0]), bp_2)) {
pr_debug("failed to set breakpoint, 1st time: %m\n");
goto out;
}
if (ptrace(PTRACE_POKEUSER, child,
offsetof(struct user, u_debugreg[0]), bp_1)) {
pr_debug("failed to set breakpoint, 2nd time: %m\n");
goto out;
}
if (ptrace(PTRACE_POKEUSER, child,
offsetof(struct user, u_debugreg[7]), dr7)) {
pr_debug("failed to set dr7: %m\n");
goto out;
}
if (ptrace(PTRACE_CONT, child, NULL, NULL)) {
pr_debug("failed to PTRACE_CONT: %m\n");
goto out;
}
waitpid(child, &status, 0);
if (WIFEXITED(status)) {
pr_debug("tracee exited prematurely 2\n");
return TEST_FAIL;
}
rip = ptrace(PTRACE_PEEKUSER, child,
offsetof(struct user_regs_struct, rip), NULL);
if (rip == (unsigned long) -1) {
pr_debug("failed to PTRACE_PEEKUSER: %m\n");
goto out;
}
pr_debug("rip %lx, bp_1 %p\n", rip, bp_1);
out:
if (ptrace(PTRACE_DETACH, child, NULL, NULL)) {
pr_debug("failed to PTRACE_DETACH: %m\n");
return TEST_FAIL;
}
return rip == (unsigned long) bp_1 ? TEST_OK : TEST_FAIL;
}
/*
* This tests creates HW breakpoint, tries to
* change it to bogus value and checks the original
* breakpoint is hit.
*/
static int bp_modify2(void)
{
pid_t child;
int status;
unsigned long rip = 0, dr7 = 1;
child = spawn_child();
waitpid(child, &status, 0);
if (WIFEXITED(status)) {
pr_debug("tracee exited prematurely 1\n");
return TEST_FAIL;
}
/*
* The parent does following steps:
* - creates a new breakpoint (id 0) for bp_1 function
* - tries to change that breakpoint to (-1) address
* - waits for the breakpoint to hit and checks
* it has proper rip of bp_1 function
* - detaches the child
*/
if (ptrace(PTRACE_POKEUSER, child,
offsetof(struct user, u_debugreg[0]), bp_1)) {
pr_debug("failed to set breakpoint: %m\n");
goto out;
}
if (ptrace(PTRACE_POKEUSER, child,
offsetof(struct user, u_debugreg[7]), dr7)) {
pr_debug("failed to set dr7: %m\n");
goto out;
}
if (!ptrace(PTRACE_POKEUSER, child,
offsetof(struct user, u_debugreg[0]), (unsigned long) (-1))) {
pr_debug("failed, breakpoint set to bogus address\n");
goto out;
}
if (ptrace(PTRACE_CONT, child, NULL, NULL)) {
pr_debug("failed to PTRACE_CONT: %m\n");
goto out;
}
waitpid(child, &status, 0);
if (WIFEXITED(status)) {
pr_debug("tracee exited prematurely 2\n");
return TEST_FAIL;
}
rip = ptrace(PTRACE_PEEKUSER, child,
offsetof(struct user_regs_struct, rip), NULL);
if (rip == (unsigned long) -1) {
pr_debug("failed to PTRACE_PEEKUSER: %m\n");
goto out;
}
pr_debug("rip %lx, bp_1 %p\n", rip, bp_1);
out:
if (ptrace(PTRACE_DETACH, child, NULL, NULL)) {
pr_debug("failed to PTRACE_DETACH: %m\n");
return TEST_FAIL;
}
return rip == (unsigned long) bp_1 ? TEST_OK : TEST_FAIL;
}
int test__bp_modify(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
TEST_ASSERT_VAL("modify test 1 failed\n", !bp_modify1());
TEST_ASSERT_VAL("modify test 2 failed\n", !bp_modify2());
return 0;
}
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0
#include <string.h>
#include "perf_regs.h"
#include "thread.h"
#include "map.h"
#include "maps.h"
#include "event.h"
#include "debug.h"
#include "tests/tests.h"
#define STACK_SIZE 8192
static int sample_ustack(struct perf_sample *sample,
struct thread *thread, u64 *regs)
{
struct stack_dump *stack = &sample->user_stack;
struct map *map;
unsigned long sp;
u64 stack_size, *buf;
buf = malloc(STACK_SIZE);
if (!buf) {
pr_debug("failed to allocate sample uregs data\n");
return -1;
}
sp = (unsigned long) regs[PERF_REG_X86_SP];
map = maps__find(thread__maps(thread), (u64)sp);
if (!map) {
pr_debug("failed to get stack map\n");
free(buf);
return -1;
}
stack_size = map__end(map) - sp;
map__put(map);
stack_size = stack_size > STACK_SIZE ? STACK_SIZE : stack_size;
memcpy(buf, (void *) sp, stack_size);
#ifdef MEMORY_SANITIZER
/*
* Copying the stack may copy msan poison, avoid false positives in the
* unwinder by removing the poison here.
*/
__msan_unpoison(buf, stack_size);
#endif
stack->data = (char *) buf;
stack->size = stack_size;
return 0;
}
int test__arch_unwind_sample(struct perf_sample *sample,
struct thread *thread)
{
struct regs_dump *regs = perf_sample__user_regs(sample);
u64 *buf = calloc(PERF_REGS_MAX, sizeof(u64));
if (!buf) {
pr_debug("failed to allocate sample uregs data\n");
return -1;
}
perf_regs_load(buf);
regs->abi = PERF_SAMPLE_REGS_ABI;
regs->regs = buf;
regs->mask = PERF_REGS_MASK;
return sample_ustack(sample, thread, buf);
}
@@ -0,0 +1,68 @@
#!/bin/awk -f
# SPDX-License-Identifier: GPL-2.0-only
# gen-insn-x86-dat.awk: script to convert data for the insn-x86 test
# Copyright (c) 2015, Intel Corporation.
#
BEGIN {
print "/*"
print " * Generated by gen-insn-x86-dat.sh and gen-insn-x86-dat.awk"
print " * from insn-x86-dat-src.c for inclusion by insn-x86.c"
print " * Do not change this code."
print "*/\n"
op = ""
branch = ""
rel = 0
going = 0
}
/ Start here / {
going = 1
}
/ Stop here / {
going = 0
}
/^\s*[0-9a-fA-F]+\:/ {
if (going) {
colon_pos = index($0, ":")
useful_line = substr($0, colon_pos + 1)
first_pos = match(useful_line, "[0-9a-fA-F]")
useful_line = substr(useful_line, first_pos)
gsub("\t", "\\t", useful_line)
printf "{{"
len = 0
for (i = 2; i <= NF; i++) {
if (match($i, "^[0-9a-fA-F][0-9a-fA-F]$")) {
printf "0x%s, ", $i
len += 1
} else {
break
}
}
printf "}, %d, %s, \"%s\", \"%s\",", len, rel, op, branch
printf "\n\"%s\",},\n", useful_line
op = ""
branch = ""
rel = 0
}
}
/ Expecting: / {
expecting_str = " Expecting: "
expecting_len = length(expecting_str)
expecting_pos = index($0, expecting_str)
useful_line = substr($0, expecting_pos + expecting_len)
for (i = 1; i <= NF; i++) {
if ($i == "Expecting:") {
i++
op = $i
i++
branch = $i
i++
rel = $i
break
}
}
}
+36
View File
@@ -0,0 +1,36 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
# gen-insn-x86-dat: generate data for the insn-x86 test
# Copyright (c) 2015, Intel Corporation.
#
set -e
if [ "$(uname -m)" != "x86_64" ]; then
echo "ERROR: This script only works on x86_64"
exit 1
fi
cd "$(dirname $0)"
trap 'echo "Might need a more recent version of binutils"' EXIT
echo "Compiling insn-x86-dat-src.c to 64-bit object"
gcc -g -c insn-x86-dat-src.c
objdump -dSw insn-x86-dat-src.o | awk -f gen-insn-x86-dat.awk > insn-x86-dat-64.c
rm -f insn-x86-dat-src.o
echo "Compiling insn-x86-dat-src.c to 32-bit object"
gcc -g -c -m32 insn-x86-dat-src.c
objdump -dSw insn-x86-dat-src.o | awk -f gen-insn-x86-dat.awk > insn-x86-dat-32.c
rm -f insn-x86-dat-src.o
trap - EXIT
echo "Done (use git diff to see the changes)"
@@ -0,0 +1,310 @@
// SPDX-License-Identifier: GPL-2.0
#include "arch-tests.h"
#include "debug.h"
#include "evlist.h"
#include "evsel.h"
#include "pmu.h"
#include "pmus.h"
#include "tests/tests.h"
static bool test_config(const struct evsel *evsel, __u64 expected_config)
{
return (evsel->core.attr.config & PERF_HW_EVENT_MASK) == expected_config;
}
static bool test_perf_config(const struct perf_evsel *evsel, __u64 expected_config)
{
return (evsel->attr.config & PERF_HW_EVENT_MASK) == expected_config;
}
static bool test_hybrid_type(const struct evsel *evsel, __u64 expected_config)
{
return (evsel->core.attr.config >> PERF_PMU_TYPE_SHIFT) == expected_config;
}
static int test__hybrid_hw_event_with_pmu(struct evlist *evlist)
{
struct evsel *evsel = evlist__first(evlist);
TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong hybrid type", test_hybrid_type(evsel, PERF_TYPE_RAW));
TEST_ASSERT_VAL("wrong config", test_config(evsel, PERF_COUNT_HW_CPU_CYCLES));
return TEST_OK;
}
static int test__hybrid_hw_group_event(struct evlist *evlist)
{
struct evsel *evsel, *leader;
evsel = leader = evlist__first(evlist);
TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong hybrid type", test_hybrid_type(evsel, PERF_TYPE_RAW));
TEST_ASSERT_VAL("wrong config", test_config(evsel, PERF_COUNT_HW_CPU_CYCLES));
TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader));
evsel = evsel__next(evsel);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong hybrid type", test_hybrid_type(evsel, PERF_TYPE_RAW));
TEST_ASSERT_VAL("wrong config", test_config(evsel, PERF_COUNT_HW_BRANCH_INSTRUCTIONS));
TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader));
return TEST_OK;
}
static int test__hybrid_sw_hw_group_event(struct evlist *evlist)
{
struct evsel *evsel, *leader;
evsel = leader = evlist__first(evlist);
TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_SOFTWARE == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader));
evsel = evsel__next(evsel);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong hybrid type", test_hybrid_type(evsel, PERF_TYPE_RAW));
TEST_ASSERT_VAL("wrong config", test_config(evsel, PERF_COUNT_HW_CPU_CYCLES));
TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader));
return TEST_OK;
}
static int test__hybrid_hw_sw_group_event(struct evlist *evlist)
{
struct evsel *evsel, *leader;
evsel = leader = evlist__first(evlist);
TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong hybrid type", test_hybrid_type(evsel, PERF_TYPE_RAW));
TEST_ASSERT_VAL("wrong config", test_config(evsel, PERF_COUNT_HW_CPU_CYCLES));
TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader));
evsel = evsel__next(evsel);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_SOFTWARE == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader));
return TEST_OK;
}
static int test__hybrid_group_modifier1(struct evlist *evlist)
{
struct evsel *evsel, *leader;
evsel = leader = evlist__first(evlist);
TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong hybrid type", test_hybrid_type(evsel, PERF_TYPE_RAW));
TEST_ASSERT_VAL("wrong config", test_config(evsel, PERF_COUNT_HW_CPU_CYCLES));
TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader));
TEST_ASSERT_VAL("wrong exclude_user", evsel->core.attr.exclude_user);
TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->core.attr.exclude_kernel);
evsel = evsel__next(evsel);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong hybrid type", test_hybrid_type(evsel, PERF_TYPE_RAW));
TEST_ASSERT_VAL("wrong config", test_config(evsel, PERF_COUNT_HW_BRANCH_INSTRUCTIONS));
TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader));
TEST_ASSERT_VAL("wrong exclude_user", !evsel->core.attr.exclude_user);
TEST_ASSERT_VAL("wrong exclude_kernel", evsel->core.attr.exclude_kernel);
return TEST_OK;
}
static int test__hybrid_raw1(struct evlist *evlist)
{
struct perf_evsel *evsel;
perf_evlist__for_each_evsel(&evlist->core, evsel) {
struct perf_pmu *pmu = perf_pmus__find_by_type(evsel->attr.type);
TEST_ASSERT_VAL("missing pmu", pmu);
TEST_ASSERT_VAL("unexpected pmu", !strncmp(pmu->name, "cpu_", 4));
TEST_ASSERT_VAL("wrong config", test_perf_config(evsel, 0x1a));
}
return TEST_OK;
}
static int test__hybrid_raw2(struct evlist *evlist)
{
struct evsel *evsel = evlist__first(evlist);
TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong config", test_config(evsel, 0x1a));
return TEST_OK;
}
static int test__hybrid_cache_event(struct evlist *evlist)
{
struct evsel *evsel = evlist__first(evlist);
TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_HW_CACHE == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong config", 0x2 == (evsel->core.attr.config & 0xffffffff));
return TEST_OK;
}
static int test__checkevent_pmu(struct evlist *evlist)
{
struct evsel *evsel = evlist__first(evlist);
TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->core.nr_entries);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong config", 10 == evsel->core.attr.config);
TEST_ASSERT_VAL("wrong config1", 1 == evsel->core.attr.config1);
TEST_ASSERT_VAL("wrong config2", 3 == evsel->core.attr.config2);
TEST_ASSERT_VAL("wrong config3", 0 == evsel->core.attr.config3);
/*
* The period value gets configured within evlist__config,
* while this test executes only parse events method.
*/
TEST_ASSERT_VAL("wrong period", 0 == evsel->core.attr.sample_period);
return TEST_OK;
}
static int test__hybrid_hw_group_event_2(struct evlist *evlist)
{
struct evsel *evsel, *leader;
evsel = leader = evlist__first(evlist);
TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->core.nr_entries);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong hybrid type", test_hybrid_type(evsel, PERF_TYPE_RAW));
TEST_ASSERT_VAL("wrong config", test_config(evsel, PERF_COUNT_HW_CPU_CYCLES));
TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader));
evsel = evsel__next(evsel);
TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->core.attr.type);
TEST_ASSERT_VAL("wrong config", evsel->core.attr.config == 0x3c);
TEST_ASSERT_VAL("wrong leader", evsel__has_leader(evsel, leader));
return TEST_OK;
}
struct evlist_test {
const char *name;
bool (*valid)(void);
int (*check)(struct evlist *evlist);
};
static const struct evlist_test test__hybrid_events[] = {
{
.name = "cpu_core/cycles/",
.check = test__hybrid_hw_event_with_pmu,
/* 0 */
},
{
.name = "{cpu_core/cycles/,cpu_core/branches/}",
.check = test__hybrid_hw_group_event,
/* 1 */
},
{
.name = "{cpu-clock,cpu_core/cycles/}",
.check = test__hybrid_sw_hw_group_event,
/* 2 */
},
{
.name = "{cpu_core/cycles/,cpu-clock}",
.check = test__hybrid_hw_sw_group_event,
/* 3 */
},
{
.name = "{cpu_core/cycles/k,cpu_core/branches/u}",
.check = test__hybrid_group_modifier1,
/* 4 */
},
{
.name = "r1a",
.check = test__hybrid_raw1,
/* 5 */
},
{
.name = "cpu_core/r1a/",
.check = test__hybrid_raw2,
/* 6 */
},
{
.name = "cpu_core/config=10,config1,config2=3,period=1000/u",
.check = test__checkevent_pmu,
/* 7 */
},
{
.name = "cpu_core/LLC-loads/",
.check = test__hybrid_cache_event,
/* 8 */
},
{
.name = "{cpu_core/cycles/,cpu_core/cpu-cycles/}",
.check = test__hybrid_hw_group_event_2,
/* 9 */
},
};
static int test_event(const struct evlist_test *e)
{
struct parse_events_error err;
struct evlist *evlist;
int ret;
if (e->valid && !e->valid()) {
pr_debug("... SKIP\n");
return TEST_OK;
}
evlist = evlist__new();
if (evlist == NULL) {
pr_err("Failed allocation");
return TEST_FAIL;
}
parse_events_error__init(&err);
ret = parse_events(evlist, e->name, &err);
if (ret) {
pr_debug("failed to parse event '%s', err %d\n", e->name, ret);
parse_events_error__print(&err, e->name);
ret = TEST_FAIL;
if (parse_events_error__contains(&err, "can't access trace events"))
ret = TEST_SKIP;
} else {
ret = e->check(evlist);
}
parse_events_error__exit(&err);
evlist__delete(evlist);
return ret;
}
static int combine_test_results(int existing, int latest)
{
if (existing == TEST_FAIL)
return TEST_FAIL;
if (existing == TEST_SKIP)
return latest == TEST_OK ? TEST_SKIP : latest;
return latest;
}
static int test_events(const struct evlist_test *events, int cnt)
{
int ret = TEST_OK;
for (int i = 0; i < cnt; i++) {
const struct evlist_test *e = &events[i];
int test_ret;
pr_debug("running test %d '%s'\n", i, e->name);
test_ret = test_event(e);
if (test_ret != TEST_OK) {
pr_debug("Event test failure: test %d '%s'", i, e->name);
ret = combine_test_results(ret, test_ret);
}
}
return ret;
}
int test__hybrid(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
if (perf_pmus__num_core_pmus() == 1)
return TEST_SKIP;
return test_events(test__hybrid_events, ARRAY_SIZE(test__hybrid_events));
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,191 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/types.h>
#include <string.h>
#include "debug.h"
#include "tests/tests.h"
#include "arch-tests.h"
#include "../../../../arch/x86/include/asm/insn.h"
#include "intel-pt-decoder/intel-pt-insn-decoder.h"
struct test_data {
u8 data[MAX_INSN_SIZE];
int expected_length;
int expected_rel;
const char *expected_op_str;
const char *expected_branch_str;
const char *asm_rep;
};
const struct test_data test_data_32[] = {
#include "insn-x86-dat-32.c"
{{0x0f, 0x01, 0xee}, 3, 0, NULL, NULL, "0f 01 ee \trdpkru"},
{{0x0f, 0x01, 0xef}, 3, 0, NULL, NULL, "0f 01 ef \twrpkru"},
{{0}, 0, 0, NULL, NULL, NULL},
};
const struct test_data test_data_64[] = {
#include "insn-x86-dat-64.c"
{{0x0f, 0x01, 0xee}, 3, 0, NULL, NULL, "0f 01 ee \trdpkru"},
{{0x0f, 0x01, 0xef}, 3, 0, NULL, NULL, "0f 01 ef \twrpkru"},
{{0xf2, 0x0f, 0x01, 0xca}, 4, 0, "erets", "indirect", "f2 0f 01 ca \terets"},
{{0xf3, 0x0f, 0x01, 0xca}, 4, 0, "eretu", "indirect", "f3 0f 01 ca \teretu"},
{{0}, 0, 0, NULL, NULL, NULL},
};
static int get_op(const char *op_str)
{
struct val_data {
const char *name;
int val;
} vals[] = {
{"other", INTEL_PT_OP_OTHER},
{"call", INTEL_PT_OP_CALL},
{"ret", INTEL_PT_OP_RET},
{"jcc", INTEL_PT_OP_JCC},
{"jmp", INTEL_PT_OP_JMP},
{"loop", INTEL_PT_OP_LOOP},
{"iret", INTEL_PT_OP_IRET},
{"int", INTEL_PT_OP_INT},
{"syscall", INTEL_PT_OP_SYSCALL},
{"sysret", INTEL_PT_OP_SYSRET},
{"vmentry", INTEL_PT_OP_VMENTRY},
{"erets", INTEL_PT_OP_ERETS},
{"eretu", INTEL_PT_OP_ERETU},
{NULL, 0},
};
struct val_data *val;
if (!op_str || !strlen(op_str))
return 0;
for (val = vals; val->name; val++) {
if (!strcmp(val->name, op_str))
return val->val;
}
pr_debug("Failed to get op\n");
return -1;
}
static int get_branch(const char *branch_str)
{
struct val_data {
const char *name;
int val;
} vals[] = {
{"no_branch", INTEL_PT_BR_NO_BRANCH},
{"indirect", INTEL_PT_BR_INDIRECT},
{"conditional", INTEL_PT_BR_CONDITIONAL},
{"unconditional", INTEL_PT_BR_UNCONDITIONAL},
{NULL, 0},
};
struct val_data *val;
if (!branch_str || !strlen(branch_str))
return 0;
for (val = vals; val->name; val++) {
if (!strcmp(val->name, branch_str))
return val->val;
}
pr_debug("Failed to get branch\n");
return -1;
}
static int test_data_item(const struct test_data *dat, int x86_64)
{
struct intel_pt_insn intel_pt_insn;
int op, branch, ret;
struct insn insn;
ret = insn_decode(&insn, dat->data, MAX_INSN_SIZE,
x86_64 ? INSN_MODE_64 : INSN_MODE_32);
if (ret < 0) {
pr_debug("Failed to decode: %s\n", dat->asm_rep);
return -1;
}
if (insn.length != dat->expected_length) {
pr_debug("Failed to decode length (%d vs expected %d): %s\n",
insn.length, dat->expected_length, dat->asm_rep);
return -1;
}
op = get_op(dat->expected_op_str);
branch = get_branch(dat->expected_branch_str);
if (intel_pt_get_insn(dat->data, MAX_INSN_SIZE, x86_64, &intel_pt_insn)) {
pr_debug("Intel PT failed to decode: %s\n", dat->asm_rep);
return -1;
}
if ((int)intel_pt_insn.op != op) {
pr_debug("Failed to decode 'op' value (%d vs expected %d): %s\n",
intel_pt_insn.op, op, dat->asm_rep);
return -1;
}
if ((int)intel_pt_insn.branch != branch) {
pr_debug("Failed to decode 'branch' value (%d vs expected %d): %s\n",
intel_pt_insn.branch, branch, dat->asm_rep);
return -1;
}
if (intel_pt_insn.rel != dat->expected_rel) {
pr_debug("Failed to decode 'rel' value (%#x vs expected %#x): %s\n",
intel_pt_insn.rel, dat->expected_rel, dat->asm_rep);
return -1;
}
pr_debug("Decoded ok: %s\n", dat->asm_rep);
return 0;
}
static int test_data_set(const struct test_data *dat_set, int x86_64)
{
const struct test_data *dat;
int ret = 0;
for (dat = dat_set; dat->expected_length; dat++) {
if (test_data_item(dat, x86_64))
ret = -1;
}
return ret;
}
/**
* test__insn_x86 - test x86 instruction decoder - new instructions.
*
* This function implements a test that decodes a selection of instructions and
* checks the results. The Intel PT function that further categorizes
* instructions (i.e. intel_pt_get_insn()) is also checked.
*
* The instructions are originally in insn-x86-dat-src.c which has been
* processed by scripts gen-insn-x86-dat.sh and gen-insn-x86-dat.awk to produce
* insn-x86-dat-32.c and insn-x86-dat-64.c which are included into this program.
* i.e. to add new instructions to the test, edit insn-x86-dat-src.c, run the
* gen-insn-x86-dat.sh script, make perf, and then run the test.
*
* If the test passes %0 is returned, otherwise %-1 is returned. Use the
* verbose (-v) option to see all the instructions and whether or not they
* decoded successfully.
*/
int test__insn_x86(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int ret = 0;
if (test_data_set(test_data_32, 0))
ret = -1;
if (test_data_set(test_data_64, 1))
ret = -1;
return ret;
}
@@ -0,0 +1,469 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <linux/bits.h>
#include <string.h>
#include <sched.h>
#include "intel-pt-decoder/intel-pt-pkt-decoder.h"
#include "debug.h"
#include "tests/tests.h"
#include "arch-tests.h"
#include "../util/cpuid.h"
#include "cpumap.h"
/**
* struct test_data - Test data.
* @len: number of bytes to decode
* @bytes: bytes to decode
* @ctx: packet context to decode
* @packet: expected packet
* @new_ctx: expected new packet context
* @ctx_unchanged: the packet context must not change
*/
static const struct test_data {
int len;
u8 bytes[INTEL_PT_PKT_MAX_SZ];
enum intel_pt_pkt_ctx ctx;
struct intel_pt_pkt packet;
enum intel_pt_pkt_ctx new_ctx;
int ctx_unchanged;
} data[] = {
/* Padding Packet */
{1, {0}, 0, {INTEL_PT_PAD, 0, 0}, 0, 1 },
/* Short Taken/Not Taken Packet */
{1, {4}, 0, {INTEL_PT_TNT, 1, 0}, 0, 0 },
{1, {6}, 0, {INTEL_PT_TNT, 1, 0x20ULL << 58}, 0, 0 },
{1, {0x80}, 0, {INTEL_PT_TNT, 6, 0}, 0, 0 },
{1, {0xfe}, 0, {INTEL_PT_TNT, 6, 0x3fULL << 58}, 0, 0 },
/* Long Taken/Not Taken Packet */
{8, {0x02, 0xa3, 2}, 0, {INTEL_PT_TNT, 1, 0xa302ULL << 47}, 0, 0 },
{8, {0x02, 0xa3, 3}, 0, {INTEL_PT_TNT, 1, 0x1a302ULL << 47}, 0, 0 },
{8, {0x02, 0xa3, 0, 0, 0, 0, 0, 0x80}, 0, {INTEL_PT_TNT, 47, 0xa302ULL << 1}, 0, 0 },
{8, {0x02, 0xa3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, {INTEL_PT_TNT, 47, 0xffffffffffffa302ULL << 1}, 0, 0 },
/* Target IP Packet */
{1, {0x0d}, 0, {INTEL_PT_TIP, 0, 0}, 0, 0 },
{3, {0x2d, 1, 2}, 0, {INTEL_PT_TIP, 1, 0x201}, 0, 0 },
{5, {0x4d, 1, 2, 3, 4}, 0, {INTEL_PT_TIP, 2, 0x4030201}, 0, 0 },
{7, {0x6d, 1, 2, 3, 4, 5, 6}, 0, {INTEL_PT_TIP, 3, 0x60504030201}, 0, 0 },
{7, {0x8d, 1, 2, 3, 4, 5, 6}, 0, {INTEL_PT_TIP, 4, 0x60504030201}, 0, 0 },
{9, {0xcd, 1, 2, 3, 4, 5, 6, 7, 8}, 0, {INTEL_PT_TIP, 6, 0x807060504030201}, 0, 0 },
/* Packet Generation Enable */
{1, {0x11}, 0, {INTEL_PT_TIP_PGE, 0, 0}, 0, 0 },
{3, {0x31, 1, 2}, 0, {INTEL_PT_TIP_PGE, 1, 0x201}, 0, 0 },
{5, {0x51, 1, 2, 3, 4}, 0, {INTEL_PT_TIP_PGE, 2, 0x4030201}, 0, 0 },
{7, {0x71, 1, 2, 3, 4, 5, 6}, 0, {INTEL_PT_TIP_PGE, 3, 0x60504030201}, 0, 0 },
{7, {0x91, 1, 2, 3, 4, 5, 6}, 0, {INTEL_PT_TIP_PGE, 4, 0x60504030201}, 0, 0 },
{9, {0xd1, 1, 2, 3, 4, 5, 6, 7, 8}, 0, {INTEL_PT_TIP_PGE, 6, 0x807060504030201}, 0, 0 },
/* Packet Generation Disable */
{1, {0x01}, 0, {INTEL_PT_TIP_PGD, 0, 0}, 0, 0 },
{3, {0x21, 1, 2}, 0, {INTEL_PT_TIP_PGD, 1, 0x201}, 0, 0 },
{5, {0x41, 1, 2, 3, 4}, 0, {INTEL_PT_TIP_PGD, 2, 0x4030201}, 0, 0 },
{7, {0x61, 1, 2, 3, 4, 5, 6}, 0, {INTEL_PT_TIP_PGD, 3, 0x60504030201}, 0, 0 },
{7, {0x81, 1, 2, 3, 4, 5, 6}, 0, {INTEL_PT_TIP_PGD, 4, 0x60504030201}, 0, 0 },
{9, {0xc1, 1, 2, 3, 4, 5, 6, 7, 8}, 0, {INTEL_PT_TIP_PGD, 6, 0x807060504030201}, 0, 0 },
/* Flow Update Packet */
{1, {0x1d}, 0, {INTEL_PT_FUP, 0, 0}, 0, 0 },
{3, {0x3d, 1, 2}, 0, {INTEL_PT_FUP, 1, 0x201}, 0, 0 },
{5, {0x5d, 1, 2, 3, 4}, 0, {INTEL_PT_FUP, 2, 0x4030201}, 0, 0 },
{7, {0x7d, 1, 2, 3, 4, 5, 6}, 0, {INTEL_PT_FUP, 3, 0x60504030201}, 0, 0 },
{7, {0x9d, 1, 2, 3, 4, 5, 6}, 0, {INTEL_PT_FUP, 4, 0x60504030201}, 0, 0 },
{9, {0xdd, 1, 2, 3, 4, 5, 6, 7, 8}, 0, {INTEL_PT_FUP, 6, 0x807060504030201}, 0, 0 },
/* Paging Information Packet */
{8, {0x02, 0x43, 2, 4, 6, 8, 10, 12}, 0, {INTEL_PT_PIP, 0, 0xC0A08060402}, 0, 0 },
{8, {0x02, 0x43, 3, 4, 6, 8, 10, 12}, 0, {INTEL_PT_PIP, 0, 0xC0A08060403}, 0, 0 },
/* Mode Exec Packet */
{2, {0x99, 0x00}, 0, {INTEL_PT_MODE_EXEC, 0, 16}, 0, 0 },
{2, {0x99, 0x01}, 0, {INTEL_PT_MODE_EXEC, 1, 64}, 0, 0 },
{2, {0x99, 0x02}, 0, {INTEL_PT_MODE_EXEC, 2, 32}, 0, 0 },
{2, {0x99, 0x04}, 0, {INTEL_PT_MODE_EXEC, 4, 16}, 0, 0 },
{2, {0x99, 0x05}, 0, {INTEL_PT_MODE_EXEC, 5, 64}, 0, 0 },
{2, {0x99, 0x06}, 0, {INTEL_PT_MODE_EXEC, 6, 32}, 0, 0 },
/* Mode TSX Packet */
{2, {0x99, 0x20}, 0, {INTEL_PT_MODE_TSX, 0, 0}, 0, 0 },
{2, {0x99, 0x21}, 0, {INTEL_PT_MODE_TSX, 0, 1}, 0, 0 },
{2, {0x99, 0x22}, 0, {INTEL_PT_MODE_TSX, 0, 2}, 0, 0 },
/* Trace Stop Packet */
{2, {0x02, 0x83}, 0, {INTEL_PT_TRACESTOP, 0, 0}, 0, 0 },
/* Core:Bus Ratio Packet */
{4, {0x02, 0x03, 0x12, 0}, 0, {INTEL_PT_CBR, 0, 0x12}, 0, 1 },
/* Timestamp Counter Packet */
{8, {0x19, 1, 2, 3, 4, 5, 6, 7}, 0, {INTEL_PT_TSC, 0, 0x7060504030201}, 0, 1 },
/* Mini Time Counter Packet */
{2, {0x59, 0x12}, 0, {INTEL_PT_MTC, 0, 0x12}, 0, 1 },
/* TSC / MTC Alignment Packet */
{7, {0x02, 0x73}, 0, {INTEL_PT_TMA, 0, 0}, 0, 1 },
{7, {0x02, 0x73, 1, 2}, 0, {INTEL_PT_TMA, 0, 0x201}, 0, 1 },
{7, {0x02, 0x73, 0, 0, 0, 0xff, 1}, 0, {INTEL_PT_TMA, 0x1ff, 0}, 0, 1 },
{7, {0x02, 0x73, 0x80, 0xc0, 0, 0xff, 1}, 0, {INTEL_PT_TMA, 0x1ff, 0xc080}, 0, 1 },
/* Cycle Count Packet */
{1, {0x03}, 0, {INTEL_PT_CYC, 0, 0}, 0, 1 },
{1, {0x0b}, 0, {INTEL_PT_CYC, 0, 1}, 0, 1 },
{1, {0xfb}, 0, {INTEL_PT_CYC, 0, 0x1f}, 0, 1 },
{2, {0x07, 2}, 0, {INTEL_PT_CYC, 0, 0x20}, 0, 1 },
{2, {0xff, 0xfe}, 0, {INTEL_PT_CYC, 0, 0xfff}, 0, 1 },
{3, {0x07, 1, 2}, 0, {INTEL_PT_CYC, 0, 0x1000}, 0, 1 },
{3, {0xff, 0xff, 0xfe}, 0, {INTEL_PT_CYC, 0, 0x7ffff}, 0, 1 },
{4, {0x07, 1, 1, 2}, 0, {INTEL_PT_CYC, 0, 0x80000}, 0, 1 },
{4, {0xff, 0xff, 0xff, 0xfe}, 0, {INTEL_PT_CYC, 0, 0x3ffffff}, 0, 1 },
{5, {0x07, 1, 1, 1, 2}, 0, {INTEL_PT_CYC, 0, 0x4000000}, 0, 1 },
{5, {0xff, 0xff, 0xff, 0xff, 0xfe}, 0, {INTEL_PT_CYC, 0, 0x1ffffffff}, 0, 1 },
{6, {0x07, 1, 1, 1, 1, 2}, 0, {INTEL_PT_CYC, 0, 0x200000000}, 0, 1 },
{6, {0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}, 0, {INTEL_PT_CYC, 0, 0xffffffffff}, 0, 1 },
{7, {0x07, 1, 1, 1, 1, 1, 2}, 0, {INTEL_PT_CYC, 0, 0x10000000000}, 0, 1 },
{7, {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}, 0, {INTEL_PT_CYC, 0, 0x7fffffffffff}, 0, 1 },
{8, {0x07, 1, 1, 1, 1, 1, 1, 2}, 0, {INTEL_PT_CYC, 0, 0x800000000000}, 0, 1 },
{8, {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}, 0, {INTEL_PT_CYC, 0, 0x3fffffffffffff}, 0, 1 },
{9, {0x07, 1, 1, 1, 1, 1, 1, 1, 2}, 0, {INTEL_PT_CYC, 0, 0x40000000000000}, 0, 1 },
{9, {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}, 0, {INTEL_PT_CYC, 0, 0x1fffffffffffffff}, 0, 1 },
{10, {0x07, 1, 1, 1, 1, 1, 1, 1, 1, 2}, 0, {INTEL_PT_CYC, 0, 0x2000000000000000}, 0, 1 },
{10, {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe}, 0, {INTEL_PT_CYC, 0, 0xffffffffffffffff}, 0, 1 },
/* Virtual-Machine Control Structure Packet */
{7, {0x02, 0xc8, 1, 2, 3, 4, 5}, 0, {INTEL_PT_VMCS, 5, 0x504030201}, 0, 0 },
/* Overflow Packet */
{2, {0x02, 0xf3}, 0, {INTEL_PT_OVF, 0, 0}, 0, 0 },
{2, {0x02, 0xf3}, INTEL_PT_BLK_4_CTX, {INTEL_PT_OVF, 0, 0}, 0, 0 },
{2, {0x02, 0xf3}, INTEL_PT_BLK_8_CTX, {INTEL_PT_OVF, 0, 0}, 0, 0 },
/* Packet Stream Boundary*/
{16, {0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82}, 0, {INTEL_PT_PSB, 0, 0}, 0, 0 },
{16, {0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82}, INTEL_PT_BLK_4_CTX, {INTEL_PT_PSB, 0, 0}, 0, 0 },
{16, {0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82}, INTEL_PT_BLK_8_CTX, {INTEL_PT_PSB, 0, 0}, 0, 0 },
/* PSB End Packet */
{2, {0x02, 0x23}, 0, {INTEL_PT_PSBEND, 0, 0}, 0, 0 },
/* Maintenance Packet */
{11, {0x02, 0xc3, 0x88, 1, 2, 3, 4, 5, 6, 7}, 0, {INTEL_PT_MNT, 0, 0x7060504030201}, 0, 1 },
/* Write Data to PT Packet */
{6, {0x02, 0x12, 1, 2, 3, 4}, 0, {INTEL_PT_PTWRITE, 0, 0x4030201}, 0, 0 },
{10, {0x02, 0x32, 1, 2, 3, 4, 5, 6, 7, 8}, 0, {INTEL_PT_PTWRITE, 1, 0x807060504030201}, 0, 0 },
{6, {0x02, 0x92, 1, 2, 3, 4}, 0, {INTEL_PT_PTWRITE_IP, 0, 0x4030201}, 0, 0 },
{10, {0x02, 0xb2, 1, 2, 3, 4, 5, 6, 7, 8}, 0, {INTEL_PT_PTWRITE_IP, 1, 0x807060504030201}, 0, 0 },
/* Execution Stop Packet */
{2, {0x02, 0x62}, 0, {INTEL_PT_EXSTOP, 0, 0}, 0, 1 },
{2, {0x02, 0xe2}, 0, {INTEL_PT_EXSTOP_IP, 0, 0}, 0, 1 },
/* Monitor Wait Packet */
{10, {0x02, 0xc2}, 0, {INTEL_PT_MWAIT, 0, 0}, 0, 0 },
{10, {0x02, 0xc2, 1, 2, 3, 4, 5, 6, 7, 8}, 0, {INTEL_PT_MWAIT, 0, 0x807060504030201}, 0, 0 },
{10, {0x02, 0xc2, 0xff, 2, 3, 4, 7, 6, 7, 8}, 0, {INTEL_PT_MWAIT, 0, 0x8070607040302ff}, 0, 0 },
/* Power Entry Packet */
{4, {0x02, 0x22}, 0, {INTEL_PT_PWRE, 0, 0}, 0, 1 },
{4, {0x02, 0x22, 1, 2}, 0, {INTEL_PT_PWRE, 0, 0x0201}, 0, 1 },
{4, {0x02, 0x22, 0x80, 0x34}, 0, {INTEL_PT_PWRE, 0, 0x3480}, 0, 1 },
{4, {0x02, 0x22, 0x00, 0x56}, 0, {INTEL_PT_PWRE, 0, 0x5600}, 0, 1 },
/* Power Exit Packet */
{7, {0x02, 0xa2}, 0, {INTEL_PT_PWRX, 0, 0}, 0, 1 },
{7, {0x02, 0xa2, 1, 2, 3, 4, 5}, 0, {INTEL_PT_PWRX, 0, 0x504030201}, 0, 1 },
{7, {0x02, 0xa2, 0xff, 0xff, 0xff, 0xff, 0xff}, 0, {INTEL_PT_PWRX, 0, 0xffffffffff}, 0, 1 },
/* Block Begin Packet */
{3, {0x02, 0x63, 0x00}, 0, {INTEL_PT_BBP, 0, 0}, INTEL_PT_BLK_8_CTX, 0 },
{3, {0x02, 0x63, 0x80}, 0, {INTEL_PT_BBP, 1, 0}, INTEL_PT_BLK_4_CTX, 0 },
{3, {0x02, 0x63, 0x1f}, 0, {INTEL_PT_BBP, 0, 0x1f}, INTEL_PT_BLK_8_CTX, 0 },
{3, {0x02, 0x63, 0x9f}, 0, {INTEL_PT_BBP, 1, 0x1f}, INTEL_PT_BLK_4_CTX, 0 },
/* 4-byte Block Item Packet */
{5, {0x04}, INTEL_PT_BLK_4_CTX, {INTEL_PT_BIP, 0, 0}, INTEL_PT_BLK_4_CTX, 0 },
{5, {0xfc}, INTEL_PT_BLK_4_CTX, {INTEL_PT_BIP, 0x1f, 0}, INTEL_PT_BLK_4_CTX, 0 },
{5, {0x04, 1, 2, 3, 4}, INTEL_PT_BLK_4_CTX, {INTEL_PT_BIP, 0, 0x04030201}, INTEL_PT_BLK_4_CTX, 0 },
{5, {0xfc, 1, 2, 3, 4}, INTEL_PT_BLK_4_CTX, {INTEL_PT_BIP, 0x1f, 0x04030201}, INTEL_PT_BLK_4_CTX, 0 },
/* 8-byte Block Item Packet */
{9, {0x04}, INTEL_PT_BLK_8_CTX, {INTEL_PT_BIP, 0, 0}, INTEL_PT_BLK_8_CTX, 0 },
{9, {0xfc}, INTEL_PT_BLK_8_CTX, {INTEL_PT_BIP, 0x1f, 0}, INTEL_PT_BLK_8_CTX, 0 },
{9, {0x04, 1, 2, 3, 4, 5, 6, 7, 8}, INTEL_PT_BLK_8_CTX, {INTEL_PT_BIP, 0, 0x0807060504030201}, INTEL_PT_BLK_8_CTX, 0 },
{9, {0xfc, 1, 2, 3, 4, 5, 6, 7, 8}, INTEL_PT_BLK_8_CTX, {INTEL_PT_BIP, 0x1f, 0x0807060504030201}, INTEL_PT_BLK_8_CTX, 0 },
/* Block End Packet */
{2, {0x02, 0x33}, INTEL_PT_BLK_4_CTX, {INTEL_PT_BEP, 0, 0}, 0, 0 },
{2, {0x02, 0xb3}, INTEL_PT_BLK_4_CTX, {INTEL_PT_BEP_IP, 0, 0}, 0, 0 },
{2, {0x02, 0x33}, INTEL_PT_BLK_8_CTX, {INTEL_PT_BEP, 0, 0}, 0, 0 },
{2, {0x02, 0xb3}, INTEL_PT_BLK_8_CTX, {INTEL_PT_BEP_IP, 0, 0}, 0, 0 },
/* Control Flow Event Packet */
{4, {0x02, 0x13, 0x01, 0x03}, 0, {INTEL_PT_CFE, 1, 3}, 0, 0 },
{4, {0x02, 0x13, 0x81, 0x03}, 0, {INTEL_PT_CFE_IP, 1, 3}, 0, 0 },
{4, {0x02, 0x13, 0x1f, 0x00}, 0, {INTEL_PT_CFE, 0x1f, 0}, 0, 0 },
{4, {0x02, 0x13, 0x9f, 0xff}, 0, {INTEL_PT_CFE_IP, 0x1f, 0xff}, 0, 0 },
/* */
{11, {0x02, 0x53, 0x09, 1, 2, 3, 4, 5, 6, 7}, 0, {INTEL_PT_EVD, 0x09, 0x7060504030201}, 0, 0 },
{11, {0x02, 0x53, 0x3f, 2, 3, 4, 5, 6, 7, 8}, 0, {INTEL_PT_EVD, 0x3f, 0x8070605040302}, 0, 0 },
/* Terminator */
{0, {0}, 0, {0, 0, 0}, 0, 0 },
};
static int dump_packet(const struct intel_pt_pkt *packet, const u8 *bytes, int len)
{
char desc[INTEL_PT_PKT_DESC_MAX];
int ret, i;
for (i = 0; i < len; i++)
pr_debug(" %02x", bytes[i]);
for (; i < INTEL_PT_PKT_MAX_SZ; i++)
pr_debug(" ");
pr_debug(" ");
ret = intel_pt_pkt_desc(packet, desc, INTEL_PT_PKT_DESC_MAX);
if (ret < 0) {
pr_debug("intel_pt_pkt_desc failed!\n");
return TEST_FAIL;
}
pr_debug("%s\n", desc);
return TEST_OK;
}
static void decoding_failed(const struct test_data *d)
{
pr_debug("Decoding failed!\n");
pr_debug("Decoding: ");
dump_packet(&d->packet, d->bytes, d->len);
}
static int fail(const struct test_data *d, struct intel_pt_pkt *packet, int len,
enum intel_pt_pkt_ctx new_ctx)
{
decoding_failed(d);
if (len != d->len)
pr_debug("Expected length: %d Decoded length %d\n",
d->len, len);
if (packet->type != d->packet.type)
pr_debug("Expected type: %d Decoded type %d\n",
d->packet.type, packet->type);
if (packet->count != d->packet.count)
pr_debug("Expected count: %d Decoded count %d\n",
d->packet.count, packet->count);
if (packet->payload != d->packet.payload)
pr_debug("Expected payload: 0x%llx Decoded payload 0x%llx\n",
(unsigned long long)d->packet.payload,
(unsigned long long)packet->payload);
if (new_ctx != d->new_ctx)
pr_debug("Expected packet context: %d Decoded packet context %d\n",
d->new_ctx, new_ctx);
return TEST_FAIL;
}
static int test_ctx_unchanged(const struct test_data *d, struct intel_pt_pkt *packet,
enum intel_pt_pkt_ctx ctx)
{
enum intel_pt_pkt_ctx old_ctx = ctx;
intel_pt_upd_pkt_ctx(packet, &ctx);
if (ctx != old_ctx) {
decoding_failed(d);
pr_debug("Packet context changed!\n");
return TEST_FAIL;
}
return TEST_OK;
}
static int test_one(const struct test_data *d)
{
struct intel_pt_pkt packet;
enum intel_pt_pkt_ctx ctx = d->ctx;
int ret;
memset(&packet, 0xff, sizeof(packet));
/* Decode a packet */
ret = intel_pt_get_packet(d->bytes, d->len, &packet, &ctx);
if (ret < 0 || ret > INTEL_PT_PKT_MAX_SZ) {
decoding_failed(d);
pr_debug("intel_pt_get_packet returned %d\n", ret);
return TEST_FAIL;
}
/* Some packets must always leave the packet context unchanged */
if (d->ctx_unchanged) {
int err;
err = test_ctx_unchanged(d, &packet, INTEL_PT_NO_CTX);
if (err)
return err;
err = test_ctx_unchanged(d, &packet, INTEL_PT_BLK_4_CTX);
if (err)
return err;
err = test_ctx_unchanged(d, &packet, INTEL_PT_BLK_8_CTX);
if (err)
return err;
}
/* Compare to the expected values */
if (ret != d->len || packet.type != d->packet.type ||
packet.count != d->packet.count ||
packet.payload != d->packet.payload || ctx != d->new_ctx)
return fail(d, &packet, ret, ctx);
pr_debug("Decoded ok:");
ret = dump_packet(&d->packet, d->bytes, d->len);
return ret;
}
/*
* This test feeds byte sequences to the Intel PT packet decoder and checks the
* results. Changes to the packet context are also checked.
*/
int test__intel_pt_pkt_decoder(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
const struct test_data *d = data;
int ret;
for (d = data; d->len; d++) {
ret = test_one(d);
if (ret)
return ret;
}
return TEST_OK;
}
static int setaffinity(int cpu)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(cpu, &cpu_set);
if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set)) {
pr_debug("sched_setaffinity() failed for CPU %d\n", cpu);
return -1;
}
return 0;
}
#define INTEL_PT_ADDR_FILT_CNT_MASK GENMASK(2, 0)
#define INTEL_PT_SUBLEAF_CNT 2
#define CPUID_REG_CNT 4
struct cpuid_result {
union {
struct {
unsigned int eax;
unsigned int ebx;
unsigned int ecx;
unsigned int edx;
};
unsigned int reg[CPUID_REG_CNT];
};
};
struct pt_caps {
struct cpuid_result subleaf[INTEL_PT_SUBLEAF_CNT];
};
static int get_pt_caps(int cpu, struct pt_caps *caps)
{
struct cpuid_result r;
int i;
if (setaffinity(cpu))
return -1;
memset(caps, 0, sizeof(*caps));
for (i = 0; i < INTEL_PT_SUBLEAF_CNT; i++) {
cpuid(20, i, &r.eax, &r.ebx, &r.ecx, &r.edx);
pr_debug("CPU %d CPUID leaf 20 subleaf %d\n", cpu, i);
pr_debug("eax = 0x%08x\n", r.eax);
pr_debug("ebx = 0x%08x\n", r.ebx);
pr_debug("ecx = 0x%08x\n", r.ecx);
pr_debug("edx = 0x%08x\n", r.edx);
caps->subleaf[i] = r;
}
return 0;
}
static bool is_hybrid(void)
{
unsigned int eax, ebx, ecx, edx = 0;
bool result;
cpuid(7, 0, &eax, &ebx, &ecx, &edx);
result = edx & BIT(15);
pr_debug("Is %shybrid : CPUID leaf 7 subleaf 0 edx %#x (bit-15 indicates hybrid)\n",
result ? "" : "not ", edx);
return result;
}
static int compare_caps(int cpu, struct pt_caps *caps, struct pt_caps *caps0)
{
struct pt_caps mask = { /* Mask of bits to check*/
.subleaf = {
[0] = {
.ebx = GENMASK(8, 0),
.ecx = GENMASK(3, 0),
},
[1] = {
.eax = GENMASK(31, 16),
.ebx = GENMASK(31, 0),
}
}
};
unsigned int m, reg, reg0;
int ret = 0;
int i, j;
for (i = 0; i < INTEL_PT_SUBLEAF_CNT; i++) {
for (j = 0; j < CPUID_REG_CNT; j++) {
m = mask.subleaf[i].reg[j];
reg = m & caps->subleaf[i].reg[j];
reg0 = m & caps0->subleaf[i].reg[j];
if ((reg & reg0) != reg0) {
pr_debug("CPU %d subleaf %d reg %d FAIL %#x vs %#x\n",
cpu, i, j, reg, reg0);
ret = -1;
}
}
}
m = INTEL_PT_ADDR_FILT_CNT_MASK;
reg = m & caps->subleaf[1].eax;
reg0 = m & caps0->subleaf[1].eax;
if (reg < reg0) {
pr_debug("CPU %d subleaf 1 reg 0 FAIL address filter count %#x vs %#x\n",
cpu, reg, reg0);
ret = -1;
}
if (!ret)
pr_debug("CPU %d OK\n", cpu);
return ret;
}
int test__intel_pt_hybrid_compat(struct test_suite *test, int subtest)
{
int max_cpu = cpu__max_cpu().cpu;
struct pt_caps last_caps;
struct pt_caps caps0;
int ret = TEST_OK;
int cpu;
if (!is_hybrid()) {
test->test_cases[subtest].skip_reason = "not hybrid";
return TEST_SKIP;
}
if (get_pt_caps(0, &caps0))
return TEST_FAIL;
for (cpu = 1, last_caps = caps0; cpu < max_cpu; cpu++) {
struct pt_caps caps;
if (get_pt_caps(cpu, &caps)) {
pr_debug("CPU %d not found\n", cpu);
continue;
}
if (!memcmp(&caps, &last_caps, sizeof(caps))) {
pr_debug("CPU %d same caps as previous CPU\n", cpu);
continue;
}
if (compare_caps(cpu, &caps, &caps0))
ret = TEST_FAIL;
last_caps = caps;
}
return ret;
}
@@ -0,0 +1,99 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/linkage.h>
#define AX 0
#define BX 1 * 8
#define CX 2 * 8
#define DX 3 * 8
#define SI 4 * 8
#define DI 5 * 8
#define BP 6 * 8
#define SP 7 * 8
#define IP 8 * 8
#define FLAGS 9 * 8
#define CS 10 * 8
#define SS 11 * 8
#define DS 12 * 8
#define ES 13 * 8
#define FS 14 * 8
#define GS 15 * 8
#define R8 16 * 8
#define R9 17 * 8
#define R10 18 * 8
#define R11 19 * 8
#define R12 20 * 8
#define R13 21 * 8
#define R14 22 * 8
#define R15 23 * 8
.text
#ifdef HAVE_ARCH_X86_64_SUPPORT
SYM_FUNC_START(perf_regs_load)
movq %rax, AX(%rdi)
movq %rbx, BX(%rdi)
movq %rcx, CX(%rdi)
movq %rdx, DX(%rdi)
movq %rsi, SI(%rdi)
movq %rdi, DI(%rdi)
movq %rbp, BP(%rdi)
leaq 8(%rsp), %rax /* exclude this call. */
movq %rax, SP(%rdi)
movq 0(%rsp), %rax
movq %rax, IP(%rdi)
movq $0, FLAGS(%rdi)
movq $0, CS(%rdi)
movq $0, SS(%rdi)
movq $0, DS(%rdi)
movq $0, ES(%rdi)
movq $0, FS(%rdi)
movq $0, GS(%rdi)
movq %r8, R8(%rdi)
movq %r9, R9(%rdi)
movq %r10, R10(%rdi)
movq %r11, R11(%rdi)
movq %r12, R12(%rdi)
movq %r13, R13(%rdi)
movq %r14, R14(%rdi)
movq %r15, R15(%rdi)
ret
SYM_FUNC_END(perf_regs_load)
#else
SYM_FUNC_START(perf_regs_load)
push %edi
movl 8(%esp), %edi
movl %eax, AX(%edi)
movl %ebx, BX(%edi)
movl %ecx, CX(%edi)
movl %edx, DX(%edi)
movl %esi, SI(%edi)
pop %eax
movl %eax, DI(%edi)
movl %ebp, BP(%edi)
leal 4(%esp), %eax /* exclude this call. */
movl %eax, SP(%edi)
movl 0(%esp), %eax
movl %eax, IP(%edi)
movl $0, FLAGS(%edi)
movl $0, CS(%edi)
movl $0, SS(%edi)
movl $0, DS(%edi)
movl $0, ES(%edi)
movl $0, FS(%edi)
movl $0, GS(%edi)
ret
SYM_FUNC_END(perf_regs_load)
#endif
/*
* We need to provide note.GNU-stack section, saying that we want
* NOT executable stack. Otherwise the final linking will assume that
* the ELF stack should not be restricted at all and set it RWX.
*/
.section .note.GNU-stack,"",@progbits
@@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-2.0
#include <errno.h>
#include "arch-tests.h"
#include "../util/topdown.h"
#include "debug.h"
#include "evlist.h"
#include "parse-events.h"
#include "pmu.h"
#include "pmus.h"
static int event_cb(void *state, struct pmu_event_info *info)
{
char buf[256];
struct parse_events_error parse_err;
int *ret = state, err;
struct evlist *evlist = evlist__new();
struct evsel *evsel;
if (!evlist)
return -ENOMEM;
parse_events_error__init(&parse_err);
snprintf(buf, sizeof(buf), "%s/%s/", info->pmu->name, info->name);
err = parse_events(evlist, buf, &parse_err);
if (err) {
parse_events_error__print(&parse_err, buf);
*ret = TEST_FAIL;
}
parse_events_error__exit(&parse_err);
evlist__for_each_entry(evlist, evsel) {
bool fail = false;
bool p_core_pmu = evsel->pmu->type == PERF_TYPE_RAW;
const char *name = evsel__name(evsel);
if (strcasestr(name, "uops_retired.slots") ||
strcasestr(name, "topdown.backend_bound_slots") ||
strcasestr(name, "topdown.br_mispredict_slots") ||
strcasestr(name, "topdown.memory_bound_slots") ||
strcasestr(name, "topdown.bad_spec_slots") ||
strcasestr(name, "topdown.slots_p")) {
if (arch_is_topdown_slots(evsel) || arch_is_topdown_metrics(evsel))
fail = true;
} else if (strcasestr(name, "slots")) {
if (arch_is_topdown_slots(evsel) != p_core_pmu ||
arch_is_topdown_metrics(evsel))
fail = true;
} else if (strcasestr(name, "topdown")) {
if (arch_is_topdown_slots(evsel) ||
arch_is_topdown_metrics(evsel) != p_core_pmu)
fail = true;
} else if (arch_is_topdown_slots(evsel) || arch_is_topdown_metrics(evsel)) {
fail = true;
}
if (fail) {
pr_debug("Broken topdown information for '%s'\n", evsel__name(evsel));
*ret = TEST_FAIL;
}
}
evlist__delete(evlist);
return 0;
}
static int test__x86_topdown(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
int ret = TEST_OK;
struct perf_pmu *pmu = NULL;
if (!topdown_sys_has_perf_metrics())
return TEST_OK;
while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
if (perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false, &ret, event_cb))
break;
}
return ret;
}
#define CHECK_COND(cond, text) \
do { \
if (!(cond)) { \
pr_debug("FAILED %s:%d %s\n", __FILE__, __LINE__, text); \
ret = TEST_FAIL; \
goto out_err; \
} \
} while (0)
#define CHECK_EQUAL(val, expected, text) \
do { \
if ((val) != (expected)) { \
pr_debug("FAILED %s:%d %s (%d != %d)\n", \
__FILE__, __LINE__, text, (val), (expected)); \
ret = TEST_FAIL; \
goto out_err; \
} \
} while (0)
static int test_sort(const char *str, int expected_slots_group_size,
int expected_instructions_group_size)
{
struct evlist *evlist = NULL;
struct parse_events_error err;
struct evsel *evsel;
int ret = TEST_FAIL;
bool slots_seen = false;
parse_events_error__init(&err);
evlist = evlist__new();
if (!evlist)
goto out_err;
if (parse_events(evlist, str, &err)) {
pr_debug("parse_events failed for %s\n", str);
goto out_err;
}
evlist__for_each_entry(evlist, evsel) {
if (!evsel__is_group_leader(evsel))
continue;
if (strstr(evsel__name(evsel), "slots")) {
/*
* Slots as a leader means the PMU is for a perf metric
* group as the slots event isn't present when not.
*/
slots_seen = true;
CHECK_EQUAL(evsel->core.nr_members, expected_slots_group_size,
"slots group size");
if (expected_slots_group_size == 3) {
struct evsel *next = evsel__next(evsel);
struct evsel *next2 = evsel__next(next);
CHECK_COND(strstr(evsel__name(next), "instructions") != NULL,
"slots second event is instructions");
CHECK_COND(strstr(evsel__name(next2), "topdown-retiring") != NULL,
"slots third event is topdown-retiring");
} else if (expected_slots_group_size == 2) {
struct evsel *next = evsel__next(evsel);
CHECK_COND(strstr(evsel__name(next), "topdown-retiring") != NULL,
"slots second event is topdown-retiring");
}
} else if (strstr(evsel__name(evsel), "instructions")) {
CHECK_EQUAL(evsel->core.nr_members, expected_instructions_group_size,
"instructions group size");
if (expected_instructions_group_size == 2) {
/*
* On Intel hybrid CPUs (e.g., Alder Lake/
* Raptor Lake), E-cores (cpu_atom) do not
* support/enforce the slots event. When
* parsing event groups containing slots
* across all PMUs, slots is automatically
* filtered out from cpu_atom, leaving
* {cpu_atom/instructions/,
* cpu_atom/topdown-retiring/}. On cpu_atom,
* instructions correctly leads this group of
* 2 without slots reordering.
*/
struct evsel *next = evsel__next(evsel);
CHECK_COND(strstr(evsel__name(next), "topdown-retiring") != NULL,
"instructions second event is topdown-retiring");
}
} else if (strstr(evsel__name(evsel), "topdown-retiring")) {
/*
* A perf metric event where the PMU doesn't require
* slots as a leader.
*/
CHECK_EQUAL(evsel->core.nr_members, 1, "topdown-retiring group size");
} else if (strstr(evsel__name(evsel), "cycles")) {
CHECK_EQUAL(evsel->core.nr_members, 1, "cycles group size");
}
}
CHECK_COND(slots_seen, "slots seen");
ret = TEST_OK;
out_err:
evlist__delete(evlist);
parse_events_error__exit(&err);
return ret;
}
static int test__x86_topdown_sorting(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
int ret;
if (!topdown_sys_has_perf_metrics())
return TEST_OK;
ret = test_sort("{instructions,topdown-retiring,slots}", 3, 2);
TEST_ASSERT_EQUAL("all events in a group", ret, TEST_OK);
ret = test_sort("instructions,topdown-retiring,slots", 2, 1);
TEST_ASSERT_EQUAL("all events not in a group", ret, TEST_OK);
ret = test_sort("{instructions,slots},topdown-retiring", 2, 1);
TEST_ASSERT_EQUAL("slots event in a group but topdown metrics events outside the group",
ret, TEST_OK);
ret = test_sort("{instructions,slots},{topdown-retiring}", 2, 1);
TEST_ASSERT_EQUAL("slots event and topdown metrics events in two groups",
ret, TEST_OK);
ret = test_sort("{instructions,slots},cycles,topdown-retiring", 2, 1);
TEST_ASSERT_EQUAL("slots event and metrics event are not in a group and not adjacent",
ret, TEST_OK);
return TEST_OK;
}
static int test__x86_topdown_slots_injection(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
int ret;
if (!topdown_sys_has_perf_metrics())
return TEST_OK;
ret = test_sort("{instructions,topdown-retiring}", 3, 2);
TEST_ASSERT_EQUAL("all events in a group", ret, TEST_OK);
ret = test_sort("instructions,topdown-retiring", 2, 1);
TEST_ASSERT_EQUAL("all events not in a group", ret, TEST_OK);
ret = test_sort("{instructions},topdown-retiring", 2, 1);
TEST_ASSERT_EQUAL("event in a group but topdown metrics events outside the group",
ret, TEST_OK);
ret = test_sort("{instructions},{topdown-retiring}", 2, 1);
TEST_ASSERT_EQUAL("event and topdown metrics events in two groups",
ret, TEST_OK);
ret = test_sort("{instructions},cycles,topdown-retiring", 2, 1);
TEST_ASSERT_EQUAL("event and metrics event are not in a group and not adjacent",
ret, TEST_OK);
return TEST_OK;
}
static struct test_case x86_topdown_tests[] = {
TEST_CASE("topdown events", x86_topdown),
TEST_CASE("topdown sorting", x86_topdown_sorting),
TEST_CASE("topdown slots injection", x86_topdown_slots_injection),
{ .name = NULL, }
};
struct test_suite suite__x86_topdown = {
.desc = "x86 topdown",
.test_cases = x86_topdown_tests,
};