[PATCH v4 1/3] perf alpha: Decode ProfileMe raw samples
From: Matt Turner
Date: Wed Nov 04 2026 - 15:43:21 EST
The Alpha cpu PMU attaches struct alpha_profileme_sample to each
ProfileMe sample as PERF_SAMPLE_RAW data: the profiled instruction's
PMPC along with the I_STAT and PCTR_CTL status that says what happened
to it.
Decode it in perf report -D and perf script -D the way AMD IBS raw data
is decoded, naming the trap from TRAP_TYPE when TRP is set and from the
PALcode entry offset in PMPC for redirect traps, which TRAP_TYPE does
not report. TRP is ignored for an instruction killed before it was
mapped, as an EV7 leaves it stale there. The payload is copied out of
the sample and converted from little endian, so that it decodes on
strict-alignment and big-endian hosts too.
Copy the uapi header that defines the struct into tools/arch and add it
to check-headers.sh.
Signed-off-by: Matt Turner <mattst88@xxxxxxxxx>
Reviewed-by: Magnus Lindholm <linmag7@xxxxxxxxx>
Tested-by: Magnus Lindholm <linmag7@xxxxxxxxx>
---
tools/arch/alpha/include/uapi/asm/perf_event.h | 31 +++++
tools/perf/check-headers.sh | 1 +
tools/perf/util/Build | 1 +
tools/perf/util/alpha-sample-raw.c | 156 +++++++++++++++++++++++++
tools/perf/util/sample-raw.c | 2 +
tools/perf/util/sample-raw.h | 2 +
6 files changed, 193 insertions(+)
diff --git a/tools/arch/alpha/include/uapi/asm/perf_event.h b/tools/arch/alpha/include/uapi/asm/perf_event.h
new file mode 100644
index 000000000000..aa0eff5a917c
--- /dev/null
+++ b/tools/arch/alpha/include/uapi/asm/perf_event.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_ASM_ALPHA_PERF_EVENT_H
+#define _UAPI_ASM_ALPHA_PERF_EVENT_H
+
+#include <linux/types.h>
+
+/*
+ * PERF_SAMPLE_RAW payload of a ProfileMe sample from the cpu PMU.
+ *
+ * pmpc, i_stat and pctr_ctl are the values of the PMPC, I_STAT and
+ * PCTR_CTL registers when the ProfileMe window closed. i_stat holds only
+ * bits 40:30 and pctr_ctl only bits 47:0. See the EV67/EV68 Hardware
+ * Reference Manuals for the field layout.
+ *
+ * In the sample record the payload follows the 32-bit PERF_SAMPLE_RAW
+ * size, so it starts 4 bytes past an 8-byte boundary. The 64-bit fields
+ * are packed after the 32-bit version so that they are 8-byte aligned in
+ * the record and no padding follows the payload.
+ *
+ * Fields may be added to the end in later versions.
+ */
+#define ALPHA_PROFILEME_SAMPLE_VERSION 1
+
+struct alpha_profileme_sample {
+ __u32 version;
+ __u64 pmpc __attribute__((packed));
+ __u64 i_stat __attribute__((packed));
+ __u64 pctr_ctl __attribute__((packed));
+};
+
+#endif /* _UAPI_ASM_ALPHA_PERF_EVENT_H */
diff --git a/tools/perf/check-headers.sh b/tools/perf/check-headers.sh
index 531c0e0e84df..de66bb148754 100755
--- a/tools/perf/check-headers.sh
+++ b/tools/perf/check-headers.sh
@@ -36,6 +36,7 @@ declare -a FILES=(
"arch/x86/include/asm/msr-index.h"
"arch/x86/lib/x86-opcode-map.txt"
"arch/x86/tools/gen-insn-attr-x86.awk"
+ "arch/alpha/include/uapi/asm/perf_event.h"
"arch/arm/include/uapi/asm/perf_regs.h"
"arch/arm64/include/uapi/asm/perf_regs.h"
"arch/loongarch/include/uapi/asm/perf_regs.h"
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 2c1f880c4c47..2ab63bbe4a05 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -77,6 +77,7 @@ perf-util-y += sample.o
perf-util-y += sample-raw.o
perf-util-y += s390-sample-raw.o
perf-util-y += amd-sample-raw.o
+perf-util-y += alpha-sample-raw.o
perf-util-y += ordered-events.o
perf-util-y += namespaces.o
perf-util-y += comm.o
diff --git a/tools/perf/util/alpha-sample-raw.c b/tools/perf/util/alpha-sample-raw.c
new file mode 100644
index 000000000000..6ff0566799ca
--- /dev/null
+++ b/tools/perf/util/alpha-sample-raw.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Alpha specific. Provide textual annotation for ProfileMe raw sample data.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <linux/kernel.h>
+#include <linux/perf_event.h>
+
+/* -Wpacked objects to the packed fields where __u64 is 4-byte aligned. */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpacked"
+#pragma GCC diagnostic ignored "-Wattributes"
+#include "../../arch/alpha/include/uapi/asm/perf_event.h"
+#pragma GCC diagnostic pop
+
+#include "evlist.h"
+#include "evsel.h"
+#include "sample-raw.h"
+#include "util/sample.h"
+
+/* I_STAT */
+#define I_STAT_OVR(x) (((x) >> 30) & 0x7)
+#define I_STAT_ICM(x) (((x) >> 33) & 0x1)
+#define I_STAT_TRAP_TYPE(x) (((x) >> 34) & 0xf)
+#define I_STAT_LSO(x) (((x) >> 38) & 0x1)
+#define I_STAT_TRP(x) (((x) >> 39) & 0x1)
+#define I_STAT_MIS(x) (((x) >> 40) & 0x1)
+
+/* PCTR_CTL */
+#define PCTR_CTL_TAK(x) ((x) & 0x1)
+#define PCTR_CTL_VAL(x) (((x) >> 1) & 0x1)
+#define PCTR_CTL_SL1(x) (((x) >> 2) & 0x3)
+#define PCTR_CTL_SL0(x) (((x) >> 4) & 0x1)
+#define PCTR_CTL_PCTR1(x) (((x) >> 6) & 0xfffff)
+#define PCTR_CTL_KILLED(x) (((x) >> 26) & 0x1)
+#define PCTR_CTL_STALLED(x) (((x) >> 27) & 0x1)
+#define PCTR_CTL_PCTR0(x) (((x) >> 28) & 0xfffff)
+
+/* PMPC */
+#define PMPC_PAL(x) ((x) & 0x1)
+#define PMPC_PAL_OFFSET(x) ((x) & 0x7fff)
+
+static const char * const trap_type_names[16] = {
+ [0] = "replay",
+ [2] = "DTB double miss (3 level)",
+ [3] = "DTB double miss (4 level)",
+ [4] = "FP disabled",
+ [5] = "unaligned",
+ [6] = "DTB single miss",
+ [7] = "Dstream fault",
+ [8] = "OPCDEC",
+ [10] = "machine check",
+ [12] = "arithmetic",
+ [14] = "MT_FPCR",
+ [15] = "reset",
+};
+
+/* What PCTR1 counts during the window, by SL1. */
+static const char * const pctr1_names[4] = {
+ "cycles",
+ "retire delay",
+ "bcache misses",
+ "mbox replays",
+};
+
+/* TRP, and MIS and the trap type with it, are stale for a killed insn. */
+static bool trapped(__u64 i_stat, __u64 ctl)
+{
+ return I_STAT_TRP(i_stat) && !PCTR_CTL_KILLED(ctl);
+}
+
+static const char *trap_name(__u64 i_stat, __u64 ctl, __u64 pmpc)
+{
+ const char *name;
+
+ /* Redirect traps leave TRAP_TYPE alone and are named by PMPC. */
+ if (PMPC_PAL(pmpc)) {
+ switch (PMPC_PAL_OFFSET(pmpc)) {
+ case 0x481:
+ return "Istream access violation";
+ case 0x581:
+ return "ITB miss";
+ case 0x681:
+ return "interrupt";
+ default:
+ break;
+ }
+ }
+ if (!trapped(i_stat, ctl) || I_STAT_MIS(i_stat))
+ return "none";
+ name = trap_type_names[I_STAT_TRAP_TYPE(i_stat)];
+ return name ? name : "other (see PMPC)";
+}
+
+/*
+ * Copy the payload out of the sample, since nothing guarantees its
+ * alignment in the buffer, and convert it from the little-endian byte
+ * order it was recorded in.
+ */
+static bool is_profileme_sample(struct evlist *evlist, union perf_event *event,
+ struct perf_sample *sample,
+ struct alpha_profileme_sample *pm)
+{
+ struct evsel *evsel;
+
+ if (sample->raw_size < sizeof(*pm))
+ return false;
+
+ evsel = evlist__event2evsel(evlist, event);
+ if (!evsel)
+ return false;
+ if (evsel->core.attr.type != PERF_TYPE_HARDWARE &&
+ evsel->core.attr.type != PERF_TYPE_RAW)
+ return false;
+
+ memcpy(pm, sample->raw_data, sizeof(*pm));
+ pm->version = le32_to_cpu(pm->version);
+ pm->pmpc = le64_to_cpu(pm->pmpc);
+ pm->i_stat = le64_to_cpu(pm->i_stat);
+ pm->pctr_ctl = le64_to_cpu(pm->pctr_ctl);
+
+ return pm->version >= ALPHA_PROFILEME_SAMPLE_VERSION;
+}
+
+void evlist__alpha_sample_raw(struct evlist *evlist, union perf_event *event,
+ struct perf_sample *sample)
+{
+ struct alpha_profileme_sample pm;
+ unsigned long long i_stat, ctl, pmpc;
+
+ if (event->header.type != PERF_RECORD_SAMPLE ||
+ !is_profileme_sample(evlist, event, sample, &pm))
+ return;
+
+ i_stat = pm.i_stat;
+ ctl = pm.pctr_ctl;
+ pmpc = pm.pmpc;
+
+ printf("ProfileMe PMPC:\t%016llx %s trap %s%s\n",
+ pmpc, PMPC_PAL(pmpc) ? "PAL" : (pmpc >> 63 ? "kernel" : "user"),
+ trap_name(i_stat, ctl, pmpc),
+ trapped(i_stat, ctl) && I_STAT_MIS(i_stat) ? " mispredicted" : "");
+ printf("ProfileMe I_STAT:\t%016llx TRP %d MIS %d LSO %d TrapType %d ICM %d OVR %d\n",
+ i_stat, (int)I_STAT_TRP(i_stat), (int)I_STAT_MIS(i_stat),
+ (int)I_STAT_LSO(i_stat), (int)I_STAT_TRAP_TYPE(i_stat),
+ (int)I_STAT_ICM(i_stat), (int)I_STAT_OVR(i_stat));
+ printf("ProfileMe PCTR_CTL:\t%016llx VAL %d TAK %d Stalled %d Killed %d",
+ ctl, (int)PCTR_CTL_VAL(ctl), (int)PCTR_CTL_TAK(ctl),
+ (int)PCTR_CTL_STALLED(ctl), (int)PCTR_CTL_KILLED(ctl));
+ printf(" SL0 %d SL1 %d PCTR0 %d PCTR1 %d (%s)\n",
+ (int)PCTR_CTL_SL0(ctl), (int)PCTR_CTL_SL1(ctl),
+ (int)PCTR_CTL_PCTR0(ctl), (int)PCTR_CTL_PCTR1(ctl),
+ pctr1_names[PCTR_CTL_SL1(ctl)]);
+}
diff --git a/tools/perf/util/sample-raw.c b/tools/perf/util/sample-raw.c
index f5ae9f468983..f765429a481d 100644
--- a/tools/perf/util/sample-raw.c
+++ b/tools/perf/util/sample-raw.c
@@ -19,6 +19,8 @@ void evlist__init_trace_event_sample_raw(struct evlist *evlist, struct perf_env
if (e_machine == EM_S390) {
evlist__set_trace_event_sample_raw(evlist, evlist__s390_sample_raw);
+ } else if (e_machine == EM_ALPHA) {
+ evlist__set_trace_event_sample_raw(evlist, evlist__alpha_sample_raw);
} else if (e_machine == EM_X86_64 || e_machine == EM_386) {
const char *cpuid = perf_env__cpuid(env);
diff --git a/tools/perf/util/sample-raw.h b/tools/perf/util/sample-raw.h
index c8d38c841c8c..8df0bd3d02aa 100644
--- a/tools/perf/util/sample-raw.h
+++ b/tools/perf/util/sample-raw.h
@@ -14,6 +14,8 @@ void evlist__s390_sample_raw(struct evlist *evlist, union perf_event *event,
bool evlist__has_amd_ibs(struct evlist *evlist);
void evlist__amd_sample_raw(struct evlist *evlist, union perf_event *event,
struct perf_sample *sample);
+void evlist__alpha_sample_raw(struct evlist *evlist, union perf_event *event,
+ struct perf_sample *sample);
void evlist__init_trace_event_sample_raw(struct evlist *evlist, struct perf_env *env);
#endif /* __SAMPLE_RAW_H */
--
2.55.0