* [PATCH v6 00/13] alpha/perf: add ProfileMe sampling
@ 2026-11-04 18:49 Matt Turner
2026-11-04 18:49 ` [PATCH v6 01/13] alpha/perf: pass PERFMON_CMD_LOGGING_ALL to the logging options call Matt Turner
` (12 more replies)
0 siblings, 13 replies; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
The 21264 from EV67 on, and the 21364, have a second performance counter
mode, ProfileMe, alongside the Aggregate mode that the driver uses today.
An Aggregate mode sample reports the PC where the overflow interrupt was
taken. On an EV7 that is anywhere from 15 instructions before to 12
after the one that overflowed the counter, which makes perf annotate
output close to useless. In ProfileMe mode the CPU picks one fetched
instruction, follows it down the pipeline, and reports its PC along with
what happened to it: whether it retired, whether it trapped and why,
whether it was a mispredicted branch, stalled before map, or was killed
on a wrong path, and what PCTR1 counted while it was in flight.
Patch 1 fixes a constant passed to the wrong PALcode call, with no
functional change. Patch 2 adds the sysfs format and event attributes
the perf tool needs to name raw events. Patch 3 fixes counts lost when
an event is stopped before its first start, and patch 4 stops reporting
user registers that the entry code has not saved yet. Patch 5 ignores
counter interrupts that were pending when the counter was rewritten,
and stops counting an overflow twice when a read races its interrupt.
Patch 6 adds ProfileMe sampling, selected with precise_ip or a new
profileme config bit, so cycles:p and perf record's default event use
it. Patch 7 attaches the status registers to each sample as
PERF_SAMPLE_RAW data, and patch 8 adds an option to randomize the
sampling interval so that it cannot lock onto a loop. Patches 9 and 10
report what PCTR1 counted as the sample weight and the data source of
memory accesses, DTB hits and misses. Patch 11 adds filters that keep
only retired, mispredicted or trapping instructions, and patch 12 makes
precise events sample only retired ones. Patch 13 lets ProfileMe events
exclude user or kernel samples, so that perf record works without root
at the default perf_event_paranoid.
A separate series for perf-tools-next adds a decoder for the raw data
to perf report -D and perf script -D, and a script that sums the status
by symbol or by instruction, in the style of DCPI, and a perf mem event.
The two modes cannot run at the same time, because PCTR_CTL[SL0] sets
the mode for both counters. A ProfileMe event is scheduled alone, and
perf multiplexes it with any other hardware events. perf stat does not
ask for precise events, so it keeps using Aggregate mode.
To try it:
perf record -e cycles:p -- ./workload
perf annotate
I tested this on an AlphaServer ES47 (EV7). Samples from a
16-instruction loop land on all 16 instructions rather than on 4 PCs.
DTB misses and unaligned traps are attributed to the faulting load. A
branch on random data shows up as mispredicted in 21% of its samples,
and in 0% with sorted data. A 12 minute perf record -a while building
git took 4.45 million samples with no warnings. Sampling every 10000
instructions, which used to wedge the CPU, ran 66 times without a hang.
With precise_ip, killed instructions went from 67% of the samples of a
mispredicting loop to none. On two CPUs, this version survived 62 runs
that mix sampling, filters, context switches and multiplexing, with no
warnings. As a normal user, perf record took 8763 samples, none of them
in the kernel.
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
Changes in v6:
- New patch 5: ignore an overflow interrupt for a counter that has not
overflowed since it was written, and do not count an overflow twice
when a read races its interrupt. Counters are now written at most
half their range below overflow, so the two can be told apart.
Suggested by Ian Rogers, after Sashiko found that a PCTR1 interrupt
pending when a ProfileMe event was scheduled in was taken as a
window close.
- Patch 6: drop the stopped event check and the caveat about stale
interrupts, which patch 5 now covers.
- Patch 10: also report a memory access that retired without a DTB
miss trap as a DTB hit, a load into $31 or $f31 as a prefetch, and
leave out the ldq_u UNOP. Thanks to Ian Rogers.
- Patch 12: say in the commit message that the kept samples carry the
full period, so the sample rate in frequency mode falls by the
killed fraction. Thanks to Ian Rogers.
- New patch 13: honor exclude_user and exclude_kernel for ProfileMe
events by dropping samples, so perf record works without root.
Suggested by Ian Rogers.
- Link to v5: https://patch.msgid.link/20261103-alpha-profileme-v5-0-bbe3937fbf3a@gmail.com
Changes in v5:
- Patch 5: refuse precise_ip 3, as a sample with a PALmode PMPC keeps
the interrupted PC. Thanks to Magnus Lindholm.
- New patches 8 to 11, from Ian Rogers' suggestions: the sample weight,
the data source of DTB misses, config1 filters, and retired-only
precise samples. The last changes what a default perf record shows.
- Link to v4: https://patch.msgid.link/20261102-alpha-profileme-v4-0-61e00f5e1da8@gmail.com
Changes in v4:
- Patch 4: move __entry_save_end past the r16-r18 stores in entSys,
as PAL_callsys does not save them and interrupts are still enabled
there.
- Patch 5: report the user registers when the profiled instruction is
in the entry code but the interrupt was taken after it, since
perf_get_regs_user() sees the PMPC rather than the interrupted PC.
- Link to v3: https://patch.msgid.link/20261101-alpha-profileme-v3-0-92d511b5a044@gmail.com
Changes in v3:
- New patch 3: clear PERF_HES_UPTODATE when an event starts counting,
so that a stop before the first start does not lose the count.
- New patch 4: report no user registers for a sample taken in the entry
code, where the frame at task_pt_regs() is not complete. An interrupt
nested on one taken there is still not caught.
- Patch 5: a user instruction whose interrupt is taken in the kernel now
reports its PC with no registers or user stack, as the frame at
task_pt_regs() may not be built yet. Thanks to Magnus Lindholm for
finding this and for the fix.
- Patch 5: also move the counter away from overflow when a ProfileMe
event is removed, or stopped without PERF_EF_UPDATE, as
alpha_pmu_del() does not go through alpha_pmu_stop().
- Patch 5: ignore an interrupt that was pending when the event was
stopped. Document that rewriting a counter does not cancel one.
- Link to v2: https://patch.msgid.link/20261030-alpha-profileme-v2-0-c702f1c65272@gmail.com
Changes in v2:
- Patch 1: explain why bit 0 clear means all processes, although the
Architecture Reference Manual says otherwise.
- Patch 3: fix a CPU wedge during high-rate ProfileMe sampling. The
ProfileMe counters are now written twice, and a stopped counter is
moved away from overflow, as EV67 HRM PALcode restrictions 32 and 38
require.
- Patch 3: set the profiled PC in place in the trap frame of the
instruction's mode instead of in a pt_regs copy on the stack, so
sampled registers and stack values are those of the right frame.
A kernel instruction whose interrupt is taken in user mode gets a
per-CPU frame holding only its PC, so the user frame is not changed.
- Patch 4: drop the reserved field and pack the 64-bit fields of
struct alpha_profileme_sample, so they are 8-byte aligned in the
sample record and the record needs no padding. This changes the raw
data layout.
- Patch 5: compute the jitter with mul_u64_u32_shr() and saturate the
sum, so that large periods cannot overflow.
- Link to v1: https://patch.msgid.link/20261028-alpha-profileme-v1-0-d44b52bf2500@gmail.com
---
Matt Turner (13):
alpha/perf: pass PERFMON_CMD_LOGGING_ALL to the logging options call
alpha/perf: add sysfs format and event attributes
alpha/perf: clear PERF_HES_UPTODATE when an event starts counting
alpha/perf: do not report user registers from the entry code
alpha/perf: ignore overflow interrupts the counter does not account for
alpha/perf: implement ProfileMe sampling
alpha/perf: attach ProfileMe status as PERF_SAMPLE_RAW data
alpha/perf: optionally randomize the ProfileMe sampling interval
alpha/perf: report the ProfileMe window count as the sample weight
alpha/perf: report the data source of ProfileMe memory accesses
alpha/perf: add ProfileMe sample filters
alpha/perf: sample only retired instructions for precise events
alpha/perf: honor exclude_user and exclude_kernel for ProfileMe events
arch/alpha/include/asm/perf_event.h | 2 +
arch/alpha/include/asm/ptrace.h | 12 +
arch/alpha/include/asm/wrperfmon.h | 36 ++
arch/alpha/include/uapi/asm/perf_event.h | 31 ++
arch/alpha/kernel/entry.S | 9 +
arch/alpha/kernel/perf_event.c | 660 ++++++++++++++++++++++++++++++-
arch/alpha/kernel/perf_regs.c | 13 +
7 files changed, 742 insertions(+), 21 deletions(-)
---
base-commit: 330517c63aa9b48227b6ab45f01003effac77ae4
change-id: 20261024-alpha-profileme-6e98a977cb54
Best regards,
--
Matt Turner <mattst88@gmail.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 01/13] alpha/perf: pass PERFMON_CMD_LOGGING_ALL to the logging options call
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
@ 2026-11-04 18:49 ` Matt Turner
2026-11-04 18:49 ` [PATCH v6 02/13] alpha/perf: add sysfs format and event attributes Matt Turner
` (11 subsequent siblings)
12 siblings, 0 replies; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
alpha_pmu_enable() passes EV67_PCTR_MODE_AGGREGATE to
PERFMON_CMD_LOGGING_OPTIONS. That constant is the counting mode bit for
PCTR_CTL, which belongs in the PERFMON_CMD_DESIRED_EVENTS argument. The
logging options call takes a flag in bit 0 where 0 means "count all
processes". It works only because EV67_PCTR_MODE_AGGREGATE happens to be
0.
Table E-18 of the Alpha Architecture Reference Manual gives bit 0 the
opposite sense, but the PALcode in the ES40 and ES47 firmware agrees
with the above: it sets I_CTL[SPCE], which enables counting for all
processes, unless bit 0 is set. Linux never sets the PME bit in the
PCB, so if 0 meant "selected processes only", nothing would be counted.
Add a name for that argument and pass it instead. No functional change.
Signed-off-by: Matt Turner <mattst88@gmail.com>
Reviewed-by: Magnus Lindholm <linmag7@gmail.com>
Tested-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/alpha/include/asm/wrperfmon.h | 2 ++
arch/alpha/kernel/perf_event.c | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/arch/alpha/include/asm/wrperfmon.h b/arch/alpha/include/asm/wrperfmon.h
index c97b82a1f7db..1fa9bc813263 100644
--- a/arch/alpha/include/asm/wrperfmon.h
+++ b/arch/alpha/include/asm/wrperfmon.h
@@ -23,6 +23,8 @@
#define PERFMON_CMD_I_STAT 8
#define PERFMON_CMD_PMPC 9
+/* PERFMON_CMD_LOGGING_OPTIONS arguments */
+#define PERFMON_CMD_LOGGING_ALL 0
/* EV5/EV56/PCA56 Counters */
#define EV5_PCTR_0 (1UL<<0)
diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index a3eaab094ece..2d9383425357 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -728,7 +728,7 @@ static void alpha_pmu_enable(struct pmu *pmu)
maybe_change_configuration(cpuc);
/* Start counting the desired events. */
- wrperfmon(PERFMON_CMD_LOGGING_OPTIONS, EV67_PCTR_MODE_AGGREGATE);
+ wrperfmon(PERFMON_CMD_LOGGING_OPTIONS, PERFMON_CMD_LOGGING_ALL);
wrperfmon(PERFMON_CMD_DESIRED_EVENTS, cpuc->config);
wrperfmon(PERFMON_CMD_ENABLE, cpuc->idx_mask);
}
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 02/13] alpha/perf: add sysfs format and event attributes
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
2026-11-04 18:49 ` [PATCH v6 01/13] alpha/perf: pass PERFMON_CMD_LOGGING_ALL to the logging options call Matt Turner
@ 2026-11-04 18:49 ` Matt Turner
2026-11-04 18:49 ` [PATCH v6 03/13] alpha/perf: clear PERF_HES_UPTODATE when an event starts counting Matt Turner
` (10 subsequent siblings)
12 siblings, 0 replies; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
The Alpha PMU registers with no attribute groups, so the perf tool has no
way to learn the raw config layout, and the PMU's own events can only be
named as raw hex numbers.
Describe the event field in format/event and export aliases for the four
EV67 event types. mbox-replay has no generic hardware event, and can now
be named as cpu/mbox-replay/.
Signed-off-by: Matt Turner <mattst88@gmail.com>
Reviewed-by: Magnus Lindholm <linmag7@gmail.com>
Tested-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/alpha/kernel/perf_event.c | 39 +++++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index 2d9383425357..70840c05d39b 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -212,6 +212,44 @@ static int ev67_raw_event_valid(u64 config)
};
+PMU_FORMAT_ATTR(event, "config:0-7");
+
+static struct attribute *ev67_format_attrs[] = {
+ &format_attr_event.attr,
+ NULL,
+};
+
+static const struct attribute_group ev67_format_group = {
+ .name = "format",
+ .attrs = ev67_format_attrs,
+};
+
+/* Values must match enum ev67_pmc_event_type. */
+PMU_EVENT_ATTR_STRING(cycles, ev67_event_cycles, "event=0x01");
+PMU_EVENT_ATTR_STRING(instructions, ev67_event_instructions, "event=0x02");
+PMU_EVENT_ATTR_STRING(bcache-misses, ev67_event_bcache_misses, "event=0x03");
+PMU_EVENT_ATTR_STRING(mbox-replay, ev67_event_mbox_replay, "event=0x04");
+
+static struct attribute *ev67_event_attrs[] = {
+ &ev67_event_cycles.attr.attr,
+ &ev67_event_instructions.attr.attr,
+ &ev67_event_bcache_misses.attr.attr,
+ &ev67_event_mbox_replay.attr.attr,
+ NULL,
+};
+
+static const struct attribute_group ev67_events_group = {
+ .name = "events",
+ .attrs = ev67_event_attrs,
+};
+
+static const struct attribute_group *ev67_attr_groups[] = {
+ &ev67_format_group,
+ &ev67_events_group,
+ NULL,
+};
+
+
static const struct alpha_pmu_t ev67_pmu = {
.event_map = ev67_perfmon_event_map,
.max_events = ARRAY_SIZE(ev67_perfmon_event_map),
@@ -761,6 +799,7 @@ static struct pmu pmu = {
.start = alpha_pmu_start,
.stop = alpha_pmu_stop,
.read = alpha_pmu_read,
+ .attr_groups = ev67_attr_groups,
.capabilities = PERF_PMU_CAP_NO_EXCLUDE,
};
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 03/13] alpha/perf: clear PERF_HES_UPTODATE when an event starts counting
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
2026-11-04 18:49 ` [PATCH v6 01/13] alpha/perf: pass PERFMON_CMD_LOGGING_ALL to the logging options call Matt Turner
2026-11-04 18:49 ` [PATCH v6 02/13] alpha/perf: add sysfs format and event attributes Matt Turner
@ 2026-11-04 18:49 ` Matt Turner
2026-11-04 18:49 ` [PATCH v6 04/13] alpha/perf: do not report user registers from the entry code Matt Turner
` (9 subsequent siblings)
12 siblings, 0 replies; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
alpha_pmu_add() sets PERF_HES_UPTODATE and maybe_change_configuration()
then programs and enables the counter, but nothing clears the flag until
the first alpha_pmu_start(). In that window alpha_pmu_stop() with
PERF_EF_UPDATE skips the count update, because the flag claims the count
is current. Since the next reload rewrites the counter, everything
counted since the event was added is lost.
perf_adjust_freq_unthr_events() does this on the first tick
after an event in frequency mode is scheduled in, which is the default
for perf record, and it then sees no count to adjust the period with.
Clear the flag where the event starts counting. alpha_pmu_add() cannot
do it, since the event is not programmed until the PMU is enabled, and
stopping it before then must not read the counter.
Fixes: a4eaf7f14675 ("perf: Rework the PMU methods")
Assisted-by: Claude:claude-opus-5-5
Reviewed-by: Magnus Lindholm <linmag7@gmail.com>
Tested-by: Magnus Lindholm <linmag7@gmail.com>
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
arch/alpha/kernel/perf_event.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index 70840c05d39b..ecec78c355cd 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -456,8 +456,11 @@ static void maybe_change_configuration(struct cpu_hw_events *cpuc)
cpuc->current_idx[j] = idx;
}
- if (!(hwc->state & PERF_HES_STOPPED))
+ if (!(hwc->state & PERF_HES_STOPPED)) {
+ /* The counter is live, so hwc->prev_count will go stale. */
+ hwc->state &= ~PERF_HES_UPTODATE;
cpuc->idx_mask |= (1<<cpuc->current_idx[j]);
+ }
}
cpuc->config = cpuc->event[0]->hw.config_base;
}
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 04/13] alpha/perf: do not report user registers from the entry code
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
` (2 preceding siblings ...)
2026-11-04 18:49 ` [PATCH v6 03/13] alpha/perf: clear PERF_HES_UPTODATE when an event starts counting Matt Turner
@ 2026-11-04 18:49 ` Matt Turner
2026-11-04 18:49 ` [PATCH v6 05/13] alpha/perf: ignore overflow interrupts the counter does not account for Matt Turner
` (8 subsequent siblings)
12 siblings, 0 replies; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
perf_get_regs_user() returns task_pt_regs() whenever a sample of a user
task lands in the kernel. But exception entry runs with interrupts
enabled, so the sample can land before the entry code has saved the user
registers. Only the part PALcode pushed is valid then. An interrupt
taken on the first instruction of entUna, say, pushes its own frame on
top of PALcode's, which overlaps the rest of the user frame, so r7 and r8
read as the interrupt's r23 and r24.
Report no user registers while the interrupted PC is in the entry code.
Such a sample also gets no user stack dump. The range is all of the entry
code from entInt to the end of the save in entSys, so it also drops the
registers of samples that land after they were saved there.
An interrupt that nests on one taken in the entry code still reads the
incomplete frame, as its own PC has left the entry code.
Fixes: 9280e99d87dd ("alpha: add HAVE_PERF_REGS and HAVE_PERF_USER_STACK_DUMP support")
Assisted-by: Claude:claude-opus-5-5
Reviewed-by: Magnus Lindholm <linmag7@gmail.com>
Tested-by: Magnus Lindholm <linmag7@gmail.com>
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
arch/alpha/include/asm/ptrace.h | 12 ++++++++++++
arch/alpha/kernel/entry.S | 9 +++++++++
arch/alpha/kernel/perf_regs.c | 13 +++++++++++++
3 files changed, 34 insertions(+)
diff --git a/arch/alpha/include/asm/ptrace.h b/arch/alpha/include/asm/ptrace.h
index 45d1229344b6..1400ba3e7d43 100644
--- a/arch/alpha/include/asm/ptrace.h
+++ b/arch/alpha/include/asm/ptrace.h
@@ -14,6 +14,18 @@
#define task_pt_regs(task) \
((struct pt_regs *) (task_stack_page(task) + 2*PAGE_SIZE) - 1)
+/*
+ * Whether pc is in entry code that may not have completed the frame at
+ * task_pt_regs(). See entry.S.
+ */
+static inline bool in_entry_save_code(unsigned long pc)
+{
+ extern char __entry_save_start[], __entry_save_end[];
+
+ return pc >= (unsigned long)__entry_save_start &&
+ pc < (unsigned long)__entry_save_end;
+}
+
#define current_pt_regs() \
((struct pt_regs *) ((char *)current_thread_info() + 2*PAGE_SIZE) - 1)
diff --git a/arch/alpha/kernel/entry.S b/arch/alpha/kernel/entry.S
index 790389a15c3c..b8d7804e5aa3 100644
--- a/arch/alpha/kernel/entry.S
+++ b/arch/alpha/kernel/entry.S
@@ -186,8 +186,14 @@
/*
* Non-syscall kernel entry points.
+ *
+ * Between __entry_save_start and __entry_save_end the frame at task_pt_regs()
+ * may not be complete. perf uses these to avoid reading it.
*/
+ .globl __entry_save_start
+__entry_save_start:
+
CFI_START_OSF_FRAME entInt
SAVE_ALL
lda $8, 0x3fff
@@ -479,9 +485,12 @@ entSys:
lda $8, 0x3fff
bic $sp, $8, $8
+ /* PAL_callsys does not save these, unlike the other entries. */
stq $16, SP_OFF+24($sp)
stq $17, SP_OFF+32($sp)
stq $18, SP_OFF+40($sp)
+ .globl __entry_save_end
+__entry_save_end:
mov $0, $1
lda $16, 7
diff --git a/arch/alpha/kernel/perf_regs.c b/arch/alpha/kernel/perf_regs.c
index 33ea2e831ba0..894683851b30 100644
--- a/arch/alpha/kernel/perf_regs.c
+++ b/arch/alpha/kernel/perf_regs.c
@@ -59,6 +59,19 @@ u64 perf_reg_abi(struct task_struct *task)
void perf_get_regs_user(struct perf_regs *regs_user,
struct pt_regs *regs)
{
+ /*
+ * Exception entry runs with interrupts enabled, so a sample can land
+ * before the entry code has saved the user registers. PALcode has
+ * pushed only its part of the frame, and the rest may hold an
+ * interrupt's frame. An interrupt that nests on top of one taken here
+ * is not caught, as its PC has left the entry code.
+ */
+ if (in_entry_save_code(regs->pc)) {
+ regs_user->regs = NULL;
+ regs_user->abi = PERF_SAMPLE_REGS_ABI_NONE;
+ return;
+ }
+
regs_user->regs = task_pt_regs(current);
regs_user->abi = perf_reg_abi(current);
}
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 05/13] alpha/perf: ignore overflow interrupts the counter does not account for
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
` (3 preceding siblings ...)
2026-11-04 18:49 ` [PATCH v6 04/13] alpha/perf: do not report user registers from the entry code Matt Turner
@ 2026-11-04 18:49 ` Matt Turner
2026-11-04 21:54 ` Magnus Lindholm
2026-11-04 18:49 ` [PATCH v6 06/13] alpha/perf: implement ProfileMe sampling Matt Turner
` (7 subsequent siblings)
12 siblings, 1 reply; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
The interrupt handler adds a full counter period to the count of the
event on the counter that interrupted, which goes wrong in two cases.
Rewriting a counter does not cancel its pending interrupt: wrperfmon has
no way to, and PALcode clears the request only when delivering it. An
interrupt pending when an event is reloaded, or when perf moves another
event onto the counter, is then taken as an overflow of a counter that
has not overflowed. It adds a bogus period to the count and can emit a
false sample.
And alpha_perf_event_update() already adds the period when a read finds
that the counter overflowed before its interrupt was taken. The
interrupt then adds it again.
An overflow leaves the counter just past zero, below the value it was
written with, while a counter that has not overflowed is at or above it.
Ignore an interrupt for a counter in the latter state, unless a read
has already counted the overflow, in which case add nothing. For this to
hold, write the counters at most half their range below overflow, as
armpmu_event_set_period() does. Counting events then take an interrupt
every 2^19 events rather than every 2^20.
Fixes: 979f867191f8 ("alpha: implement HW performance events on the EV67 and later CPUs")
Suggested-by: Ian Rogers <irogers@google.com>
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
arch/alpha/kernel/perf_event.c | 59 +++++++++++++++++++++++++++++++++++++-----
1 file changed, 52 insertions(+), 7 deletions(-)
diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index ecec78c355cd..dd7fc4ac1396 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -30,6 +30,9 @@
#define MAX_HWEVENTS 3
#define PMC_NO_INDEX -1
+/* hw_perf_event.flags */
+#define ALPHA_PERF_FLAG_WRAPPED 0x1
+
/* For tracking PMCs and the hw events they monitor on each CPU. */
struct cpu_hw_events {
int enabled;
@@ -315,10 +318,16 @@ static int alpha_perf_event_set_period(struct perf_event *event,
if (unlikely(left < alpha_pmu->pmc_left[idx]))
left = alpha_pmu->pmc_left[idx];
- if (left > (long)alpha_pmu->pmc_max_period[idx])
- left = alpha_pmu->pmc_max_period[idx];
+ /*
+ * Start at most half the counter's range below overflow, so that the
+ * value an overflow leaves, just past zero, is always below the value
+ * written. See alpha_perf_event_ovf().
+ */
+ if (left > (long)(alpha_pmu->pmc_max_period[idx] >> 1))
+ left = alpha_pmu->pmc_max_period[idx] >> 1;
local64_set(&hwc->prev_count, (unsigned long)(-left));
+ hwc->flags &= ~ALPHA_PERF_FLAG_WRAPPED;
alpha_write_pmc(idx, (unsigned long)(-left));
@@ -338,9 +347,9 @@ static int alpha_perf_event_set_period(struct perf_event *event,
* zero. The value passed via ovf by the interrupt handler corrects for
* overflow.
*
- * This can be racey on rare occasions -- a call to this routine can occur
- * with an overflowed counter just before the PMI service routine is called.
- * The check for delta negative hopefully always rectifies this situation.
+ * A call to this routine can occur with an overflowed counter just before
+ * the PMI service routine is called. A negative delta detects this, as the
+ * counter is always written at most half its range below overflow.
*/
static unsigned long alpha_perf_event_update(struct perf_event *event,
struct hw_perf_event *hwc, int idx, long ovf)
@@ -359,10 +368,12 @@ static unsigned long alpha_perf_event_update(struct perf_event *event,
delta = (new_raw_count - (prev_raw_count & alpha_pmu->pmc_count_mask[idx])) + ovf;
/* It is possible on very rare occasions that the PMC has overflowed
- * but the interrupt is yet to come. Detect and fix this situation.
+ * but the interrupt is yet to come. Detect and fix this situation,
+ * and tell the interrupt not to count the overflow again.
*/
if (unlikely(delta < 0)) {
delta += alpha_pmu->pmc_max_period[idx] + 1;
+ hwc->flags |= ALPHA_PERF_FLAG_WRAPPED;
}
local64_add(delta, &event->count);
@@ -835,6 +846,32 @@ void perf_event_print_debug(void)
}
+/*
+ * Return the overflow that an interrupt from counter idx adds to the count,
+ * or -1 if the counter has not overflowed since it was written.
+ *
+ * Rewriting a counter does not cancel its pending interrupt: wrperfmon has
+ * no way to, and PALcode clears the request only when delivering it. So an
+ * interrupt can arrive after the counter was reloaded or given to another
+ * event. The counter is then at or above the value written, whereas an
+ * overflow leaves it just past zero, below that value.
+ */
+static long alpha_perf_event_ovf(struct hw_perf_event *hwc, int idx)
+{
+ unsigned long prev;
+
+ /* alpha_perf_event_update() has already counted this overflow. */
+ if (hwc->flags & ALPHA_PERF_FLAG_WRAPPED)
+ return 0;
+
+ prev = local64_read(&hwc->prev_count) & alpha_pmu->pmc_count_mask[idx];
+ if (alpha_read_pmc(idx) >= prev)
+ return -1;
+
+ return alpha_pmu->pmc_max_period[idx] + 1;
+}
+
+
/*
* Performance Monitoring Interrupt Service Routine called when a PMC
* overflows. The PMC that overflowed is passed in la_ptr.
@@ -847,6 +884,7 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
struct perf_event *event;
struct hw_perf_event *hwc;
int idx, j;
+ long ovf;
__this_cpu_inc(irq_pmi_count);
cpuc = this_cpu_ptr(&cpu_hw_events);
@@ -891,7 +929,14 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
}
hwc = &event->hw;
- alpha_perf_event_update(event, hwc, idx, alpha_pmu->pmc_max_period[idx]+1);
+ ovf = alpha_perf_event_ovf(hwc, idx);
+ if (ovf < 0) {
+ /* The interrupt was pending when the counter was written. */
+ wrperfmon(PERFMON_CMD_ENABLE, cpuc->idx_mask);
+ return;
+ }
+
+ alpha_perf_event_update(event, hwc, idx, ovf);
perf_sample_data_init(&data, 0, hwc->last_period);
if (alpha_perf_event_set_period(event, hwc, idx))
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 06/13] alpha/perf: implement ProfileMe sampling
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
` (4 preceding siblings ...)
2026-11-04 18:49 ` [PATCH v6 05/13] alpha/perf: ignore overflow interrupts the counter does not account for Matt Turner
@ 2026-11-04 18:49 ` Matt Turner
2026-11-04 21:57 ` Magnus Lindholm
2026-11-04 18:49 ` [PATCH v6 07/13] alpha/perf: attach ProfileMe status as PERF_SAMPLE_RAW data Matt Turner
` (6 subsequent siblings)
12 siblings, 1 reply; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
The 21264 (EV67 and later) and 21364 have two performance counting modes,
and the driver only uses Aggregate mode. Its samples report the PC at
which the counter interrupt was taken, which lands a variable number of
instructions after the one that overflowed the counter. On an EV7 a
16-instruction user loop shows up at only 4 PCs, anywhere from 15
instructions before to 12 after the real one.
In ProfileMe mode, the instruction that overflows PCTR0 opens a window
during which PCTR1 counts, and when it closes PALcode delivers the
interrupt with that instruction's PC latched in PMPC.
Select ProfileMe with the new profileme config bit or with precise_ip,
since PMPC has no skid. PCTR_CTL[SL0] selects the mode for both
counters, so a ProfileMe event is scheduled alone and perf core
multiplexes it with other hardware events. precise_ip 3 is refused, as
AMD IBS also does: a sample whose PMPC is a PALmode address keeps the
interrupted PC, so zero skid cannot be promised. perf record falls back
to 2 when it asks for the most precise level.
The event type chooses the SL1 setting, which decides what PCTR0 samples
on and what PCTR1 counts in the window:
event PCTR0 PCTR1
instructions instructions cycles
bcache-misses instructions Bcache misses
cycles cycles inum retire delay
mbox-replay cycles Mbox replay traps
Every interrupt rearms both counters, since PCTR0 stops when the window
closes. The instruction count is corrected by the overcount I_STAT
reports. The sample is attributed to PMPC. The interrupt can be taken
in a different mode than the profiled instruction: in an unaligned
access test on EV7 that was true of three quarters of the samples. When
the modes match, the PC is set in the interrupt's frame for the overflow
call and restored afterwards, so perf's mode, register and stack
sampling all see the same frame. When they differ the registers are not
available: a kernel instruction whose interrupt is taken after the
return to user mode has no kernel state left, and a user instruction's
frame may not be built yet, as the interrupt is often taken on the first
instruction of the entry code. Those samples use a per-CPU frame holding
only the PC, and a user instruction's sample reports no registers or
user stack. PALmode PMPCs are physical addresses, so those samples keep
the interrupted PC.
PCTR0 is left stopped just past overflow at the end of every window,
which the EV67 PALcode restrictions 32 and 38 warn about: a disabled
counter near overflow keeps signaling it and blocks all other
interrupts, and a disabled counter only clears its overflow state when
written twice. So the counters are written twice when rearmed, and are
moved away from overflow when a ProfileMe event is stopped or removed
or a stray interrupt arrives for one that is gone. Without this,
sampling every 10000 instructions wedged an EV7 in 7 of 16 runs, with
no console output, until the service processor reset it. With it, 66
runs passed.
A ProfileMe event lives at index 0, so every interrupt taken while one
is scheduled goes to it, including one from an Aggregate event on PCTR1
that was pending when perf multiplexed the ProfileMe event in. That
interrupt, and one pending from before a ProfileMe event was stopped or
switched to the other mode, finds PCTR0 short of overflow, and is
ignored as the previous patch does for Aggregate events.
Three behaviors differ from Aggregate mode. perf record's default event
is cycles:P, so it now records a ProfileMe profile. In ProfileMe mode
PCTR0 counts valid fetched instructions rather than retired ones, so the
instructions count includes the wrong path, squashed NOPs, and both
halves of each CMOV: on an EV7 loop with a randomly mispredicted branch
it is about twice the Aggregate count. And instructions killed on a
wrong path are sampled at their own PC, as ProfileMe defines them.
Suggested-by: Magnus Lindholm <linmag7@gmail.com>
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
arch/alpha/include/asm/wrperfmon.h | 12 ++
arch/alpha/kernel/perf_event.c | 290 +++++++++++++++++++++++++++++++++++--
2 files changed, 290 insertions(+), 12 deletions(-)
diff --git a/arch/alpha/include/asm/wrperfmon.h b/arch/alpha/include/asm/wrperfmon.h
index 1fa9bc813263..7fbbfd74926a 100644
--- a/arch/alpha/include/asm/wrperfmon.h
+++ b/arch/alpha/include/asm/wrperfmon.h
@@ -92,5 +92,17 @@
#define EV67_PCTR_CYCLES_UNDEF (1UL<<2)
#define EV67_PCTR_INSTR_BCACHEMISS (2UL<<2)
#define EV67_PCTR_CYCLES_MBOX (3UL<<2)
+/* In ProfileMe mode, SL1 = 1 makes PCTR1 count the inum retire delay. */
+#define EV67_PCTR_CYCLES_RETIRE_DELAY (1UL<<2)
+/* Set in both SL settings where PCTR0 counts cycles. */
+#define EV67_PCTR_PCTR0_CYCLES (1UL<<2)
+
+/* ProfileMe status, from the EV67/EV68 Hardware Reference Manuals. */
+#define EV67_I_STAT_OVR_SHIFT 30
+#define EV67_I_STAT_OVR_MASK 0x7UL
+
+/* PMPC bits 63:2 hold the profiled PC. Bit 0 set means a PALmode address. */
+#define EV67_PMPC_PAL (1UL<<0)
+#define EV67_PMPC_PC_MASK (~3UL)
#endif
diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index dd7fc4ac1396..7208d09a32d1 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -16,6 +16,7 @@
#include <linux/kdebug.h>
#include <linux/mutex.h>
#include <linux/init.h>
+#include <linux/sched/task_stack.h>
#include <asm/hwrpb.h>
#include <linux/atomic.h>
@@ -30,8 +31,13 @@
#define MAX_HWEVENTS 3
#define PMC_NO_INDEX -1
+/* Layout of attr.config for PERF_TYPE_RAW events; see format/ in sysfs. */
+#define ALPHA_PERF_CONFIG_EVENT 0xffULL
+#define ALPHA_PERF_CONFIG_PROFILEME BIT_ULL(8)
+
/* hw_perf_event.flags */
#define ALPHA_PERF_FLAG_WRAPPED 0x1
+#define ALPHA_PERF_FLAG_PROFILEME 0x2
/* For tracking PMCs and the hw events they monitor on each CPU. */
struct cpu_hw_events {
@@ -148,6 +154,19 @@ static const struct ev67_mapping_t ev67_mapping[] = {
};
+/*
+ * In ProfileMe mode PCTR0 selects the profiled instruction and PCTR1 counts
+ * during its window. PCTR0 counts instructions or cycles, and each choice
+ * pairs with two PCTR1 inputs, so each event type picks one SL1 setting.
+ * Indexed by enum ev67_pmc_event_type.
+ */
+static const unsigned long ev67_profileme_sl1[] = {
+ [EV67_CYCLES] = EV67_PCTR_CYCLES_RETIRE_DELAY, /* PCTR1: retire delay */
+ [EV67_INSTRUCTIONS] = EV67_PCTR_INSTR_CYCLES, /* PCTR1: cycles */
+ [EV67_BCACHEMISS] = EV67_PCTR_INSTR_BCACHEMISS, /* PCTR1: Bcache misses */
+ [EV67_MBOXREPLAY] = EV67_PCTR_CYCLES_MBOX, /* PCTR1: Mbox replays */
+};
+
/*
* Check that a group of events can be simultaneously scheduled on to the
* EV67 PMU. Also allocate counter indices and config.
@@ -155,9 +174,21 @@ static const struct ev67_mapping_t ev67_mapping[] = {
static int ev67_check_constraints(struct perf_event **event,
unsigned long *evtype, int n_ev)
{
- int idx0;
+ int idx0, j;
unsigned long config;
+ /* A ProfileMe event uses both counters, so it must be alone. */
+ for (j = 0; j < n_ev; j++) {
+ if (!(event[j]->hw.flags & ALPHA_PERF_FLAG_PROFILEME))
+ continue;
+ if (n_ev != 1)
+ return -1;
+ event[0]->hw.idx = 0;
+ event[0]->hw.config_base = EV67_PCTR_MODE_PROFILEME |
+ ev67_profileme_sl1[evtype[0]];
+ return 0;
+ }
+
idx0 = ev67_mapping[evtype[0]-1].idx;
config = ev67_mapping[evtype[0]-1].config;
if (n_ev == 1)
@@ -216,9 +247,11 @@ static int ev67_raw_event_valid(u64 config)
PMU_FORMAT_ATTR(event, "config:0-7");
+PMU_FORMAT_ATTR(profileme, "config:8");
static struct attribute *ev67_format_attrs[] = {
&format_attr_event.attr,
+ &format_attr_profileme.attr,
NULL,
};
@@ -279,6 +312,31 @@ static inline void alpha_write_pmc(int idx, unsigned long val)
wrperfmon(PERFMON_CMD_WRITE, val);
}
+/* Write PCTR0 and clear PCTR1 in one call, as ProfileMe mode requires. */
+static inline void alpha_write_pmc_profileme(unsigned long val)
+{
+ val &= alpha_pmu->pmc_count_mask[0];
+ val <<= alpha_pmu->pmc_count_shift[0];
+ val |= EV67_PCTR_0 | EV67_PCTR_1;
+ /*
+ * PCTR0 stops when a ProfileMe window closes, and a stopped counter
+ * only clears its overflow state when written twice (EV67 HRM, PALcode
+ * restriction 38). A counter left signaling overflow blocks all
+ * other interrupts (restriction 32).
+ */
+ wrperfmon(PERFMON_CMD_WRITE, val);
+ wrperfmon(PERFMON_CMD_WRITE, val);
+}
+
+/* The counters an event occupies, for PERFMON_CMD_ENABLE and DISABLE. */
+static inline unsigned long alpha_event_idx_mask(struct hw_perf_event *hwc,
+ int idx)
+{
+ if (hwc->flags & ALPHA_PERF_FLAG_PROFILEME)
+ return EV67_PCTR_0 | EV67_PCTR_1;
+ return 1UL<<idx;
+}
+
static inline unsigned long alpha_read_pmc(int idx)
{
unsigned long val;
@@ -289,6 +347,26 @@ static inline unsigned long alpha_read_pmc(int idx)
return val;
}
+/*
+ * Move a stopped ProfileMe counter away from overflow. See
+ * alpha_write_pmc_profileme() for why. The caller has read the count, so
+ * prev_count can be rebased on the value written.
+ *
+ * A pending PMI stays pending, but alpha_perf_event_ovf() ignores it, as
+ * the counter is left no nearer zero than alpha_perf_event_set_period()
+ * would leave it.
+ */
+static void alpha_perf_event_park(struct hw_perf_event *hwc)
+{
+ long left = local64_read(&hwc->period_left);
+
+ left = clamp(left, alpha_pmu->pmc_left[0],
+ (long)(alpha_pmu->pmc_max_period[0] >> 1));
+ local64_set(&hwc->prev_count, (unsigned long)(-left));
+ hwc->flags &= ~ALPHA_PERF_FLAG_WRAPPED;
+ alpha_write_pmc_profileme((unsigned long)(-left));
+}
+
/* Set a new period to sample over */
static int alpha_perf_event_set_period(struct perf_event *event,
struct hw_perf_event *hwc, int idx)
@@ -329,7 +407,10 @@ static int alpha_perf_event_set_period(struct perf_event *event,
local64_set(&hwc->prev_count, (unsigned long)(-left));
hwc->flags &= ~ALPHA_PERF_FLAG_WRAPPED;
- alpha_write_pmc(idx, (unsigned long)(-left));
+ if (hwc->flags & ALPHA_PERF_FLAG_PROFILEME)
+ alpha_write_pmc_profileme((unsigned long)(-left));
+ else
+ alpha_write_pmc(idx, (unsigned long)(-left));
perf_event_update_userpage(event);
@@ -470,7 +551,8 @@ static void maybe_change_configuration(struct cpu_hw_events *cpuc)
if (!(hwc->state & PERF_HES_STOPPED)) {
/* The counter is live, so hwc->prev_count will go stale. */
hwc->state &= ~PERF_HES_UPTODATE;
- cpuc->idx_mask |= (1<<cpuc->current_idx[j]);
+ cpuc->idx_mask |= alpha_event_idx_mask(hwc,
+ cpuc->current_idx[j]);
}
}
cpuc->config = cpuc->event[0]->hw.config_base;
@@ -562,7 +644,10 @@ static void alpha_pmu_del(struct perf_event *event, int flags)
alpha_perf_event_update(event, hwc, idx, 0);
perf_event_update_userpage(event);
- cpuc->idx_mask &= ~(1UL<<idx);
+ if (hwc->flags & ALPHA_PERF_FLAG_PROFILEME)
+ alpha_perf_event_park(hwc);
+
+ cpuc->idx_mask &= ~alpha_event_idx_mask(hwc, idx);
cpuc->n_events--;
break;
}
@@ -587,17 +672,22 @@ static void alpha_pmu_stop(struct perf_event *event, int flags)
struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
if (!(hwc->state & PERF_HES_STOPPED)) {
- cpuc->idx_mask &= ~(1UL<<hwc->idx);
+ cpuc->idx_mask &= ~alpha_event_idx_mask(hwc, hwc->idx);
hwc->state |= PERF_HES_STOPPED;
}
- if ((flags & PERF_EF_UPDATE) && !(hwc->state & PERF_HES_UPTODATE)) {
+ /* Parking a ProfileMe event overwrites the counter, so read it first. */
+ if (((flags & PERF_EF_UPDATE) || (hwc->flags & ALPHA_PERF_FLAG_PROFILEME)) &&
+ !(hwc->state & PERF_HES_UPTODATE)) {
alpha_perf_event_update(event, hwc, hwc->idx, 0);
hwc->state |= PERF_HES_UPTODATE;
}
if (cpuc->enabled)
- wrperfmon(PERFMON_CMD_DISABLE, (1UL<<hwc->idx));
+ wrperfmon(PERFMON_CMD_DISABLE, alpha_event_idx_mask(hwc, hwc->idx));
+
+ if (hwc->flags & ALPHA_PERF_FLAG_PROFILEME)
+ alpha_perf_event_park(hwc);
}
@@ -616,9 +706,9 @@ static void alpha_pmu_start(struct perf_event *event, int flags)
hwc->state = 0;
- cpuc->idx_mask |= 1UL<<hwc->idx;
+ cpuc->idx_mask |= alpha_event_idx_mask(hwc, hwc->idx);
if (cpuc->enabled)
- wrperfmon(PERFMON_CMD_ENABLE, (1UL<<hwc->idx));
+ wrperfmon(PERFMON_CMD_ENABLE, alpha_event_idx_mask(hwc, hwc->idx));
}
@@ -658,6 +748,7 @@ static int __hw_perf_event_init(struct perf_event *event)
struct perf_event *evts[MAX_HWEVENTS];
unsigned long evtypes[MAX_HWEVENTS];
int idx_rubbish_bin[MAX_HWEVENTS];
+ bool profileme = false;
int ev;
int n;
@@ -671,9 +762,13 @@ static int __hw_perf_event_init(struct perf_event *event)
} else if (attr->type == PERF_TYPE_HW_CACHE) {
return -EOPNOTSUPP;
} else if (attr->type == PERF_TYPE_RAW) {
- if (!alpha_pmu->raw_event_valid(attr->config))
+ if (attr->config & ~(ALPHA_PERF_CONFIG_EVENT |
+ ALPHA_PERF_CONFIG_PROFILEME))
return -EINVAL;
- ev = attr->config;
+ ev = attr->config & ALPHA_PERF_CONFIG_EVENT;
+ if (!alpha_pmu->raw_event_valid(ev))
+ return -EINVAL;
+ profileme = attr->config & ALPHA_PERF_CONFIG_PROFILEME;
} else {
return -EOPNOTSUPP;
}
@@ -682,6 +777,20 @@ static int __hw_perf_event_init(struct perf_event *event)
return ev;
}
+ /*
+ * ProfileMe samples report the exact PC of the profiled instruction,
+ * so it also serves any request for precise samples. It only makes
+ * sense for sampling events. Level 3 promises no skid at all, but some
+ * samples keep the interrupted PC; see alpha_perf_event_profileme().
+ */
+ if (attr->precise_ip > 2)
+ return -EOPNOTSUPP;
+ if (attr->precise_ip)
+ profileme = true;
+ if (profileme && !is_sampling_event(event))
+ return -EINVAL;
+ hwc->flags = profileme ? ALPHA_PERF_FLAG_PROFILEME : 0;
+
/*
* We place the event type in event_base here and leave calculation
* of the codes to programme the PMU for alpha_pmu_enable() because
@@ -872,6 +981,147 @@ static long alpha_perf_event_ovf(struct hw_perf_event *hwc, int idx)
}
+/*
+ * Frame holding only the PC, for a ProfileMe interrupt taken in a different
+ * mode than the profiled instruction. Being off the stack,
+ * perf_reg_value() reports no SP.
+ */
+static DEFINE_PER_CPU(struct pt_regs, alpha_profileme_regs);
+
+/*
+ * Report the sample with none of the given PERF_SAMPLE_REGS_* registers.
+ * Having no user registers also means no user stack.
+ */
+static void alpha_profileme_drop_regs(struct perf_event *event,
+ struct perf_sample_data *data, u64 which)
+{
+ /* STACK_USER consults regs_user even without REGS_USER. */
+ if (which & PERF_SAMPLE_REGS_USER)
+ data->regs_user = (struct perf_regs){ .abi = PERF_SAMPLE_REGS_ABI_NONE };
+ if (which & PERF_SAMPLE_REGS_INTR)
+ data->regs_intr = (struct perf_regs){ .abi = PERF_SAMPLE_REGS_ABI_NONE };
+ data->sample_flags |= which;
+ /* Only the ABI word is written for each. */
+ data->dyn_size += hweight64(event->attr.sample_type & which) * sizeof(u64);
+}
+
+/* Report the user registers from a frame known to be complete. */
+static void alpha_profileme_set_regs_user(struct perf_event *event,
+ struct perf_sample_data *data)
+{
+ data->regs_user.regs = task_pt_regs(current);
+ data->regs_user.abi = perf_reg_abi(current);
+ data->sample_flags |= PERF_SAMPLE_REGS_USER;
+ /* STACK_USER consults regs_user even without REGS_USER. */
+ if (event->attr.sample_type & PERF_SAMPLE_REGS_USER)
+ data->dyn_size += (1 + hweight64(event->attr.sample_regs_user)) *
+ sizeof(u64);
+}
+
+/*
+ * Handle the close of a ProfileMe window on PCTR0.
+ *
+ * PCTR0 stops counting when the window closes and does not start again
+ * until PCTR_CTL is written, so the status must be read first and the
+ * counters then rearmed unconditionally.
+ */
+static void alpha_perf_event_profileme(struct perf_event *event,
+ struct hw_perf_event *hwc,
+ struct pt_regs *regs, long ovf)
+{
+ struct perf_sample_data data;
+ struct pt_regs *pm_regs;
+ bool pc_only = false;
+ unsigned long i_stat, pmpc, ovr, pc, saved_pc, saved_ps;
+
+ i_stat = wrperfmon(PERFMON_CMD_I_STAT, 0);
+ pmpc = wrperfmon(PERFMON_CMD_PMPC, 0);
+
+ alpha_perf_event_update(event, hwc, 0, ovf);
+
+ /*
+ * When PCTR0 counts instructions, up to 7 of them are counted after
+ * the overflow but before the window opens. I_STAT reports how many.
+ */
+ if (!(hwc->config_base & EV67_PCTR_PCTR0_CYCLES)) {
+ ovr = (i_stat >> EV67_I_STAT_OVR_SHIFT) & EV67_I_STAT_OVR_MASK;
+ local64_sub(ovr, &event->count);
+ local64_add(ovr, &hwc->period_left);
+ }
+
+ /*
+ * Attribute the sample to the profiled instruction rather than to
+ * wherever the interrupt landed. perf takes the sample's mode,
+ * registers and stack from regs, so:
+ *
+ * - Interrupt taken in the same mode: the PC is set in the
+ * interrupt's own frame, and restored afterwards.
+ * - User instruction, interrupt taken in the kernel, typically
+ * because the instruction trapped: the frame at task_pt_regs() may
+ * not be built yet. The interrupt is often taken on the first
+ * instruction of the entry code, and its own frame then overlaps
+ * that one. Report the PC alone, in a per-CPU frame.
+ * - Kernel instruction, interrupt taken just after returning to user
+ * mode: the kernel state is gone, so a per-CPU frame that holds only
+ * the PC. The user frame in regs is left alone, since perf samples
+ * the user registers from it.
+ *
+ * A PALmode PMPC is a physical address that perf cannot resolve. That
+ * happens for ITB miss, Istream access violation and interrupt
+ * redirects, and for instructions inside PALcode. Keep the
+ * interrupted PC, which for redirects is the instruction that
+ * trapped. Do the same for a user PMPC in a kernel thread, which has
+ * no user frame.
+ */
+ pm_regs = regs;
+ pc = pmpc & EV67_PMPC_PC_MASK;
+ if (pmpc & EV67_PMPC_PAL) {
+ pc = regs->pc;
+ } else if (pc < TASK_SIZE && !user_mode(regs)) {
+ if (is_user_task(current)) {
+ pc_only = true;
+ pm_regs = this_cpu_ptr(&alpha_profileme_regs);
+ memset(pm_regs, 0, sizeof(*pm_regs));
+ /* PS bit 3 is the mode bit that user_mode() tests. */
+ pm_regs->ps = 8;
+ } else {
+ pc = regs->pc;
+ }
+ } else if (pc >= TASK_SIZE && user_mode(regs)) {
+ pm_regs = this_cpu_ptr(&alpha_profileme_regs);
+ memset(pm_regs, 0, sizeof(*pm_regs));
+ }
+
+ perf_sample_data_init(&data, 0, hwc->last_period);
+ if (pc_only)
+ alpha_profileme_drop_regs(event, &data, PERF_SAMPLE_REGS_USER |
+ PERF_SAMPLE_REGS_INTR);
+ /*
+ * perf_get_regs_user() has the same check, but it will find the PMPC
+ * in regs->pc rather than where the interrupt was taken. Decide here
+ * from the interrupted PC, for either answer that would differ.
+ */
+ else if (!user_mode(regs) && in_entry_save_code(regs->pc))
+ alpha_profileme_drop_regs(event, &data, PERF_SAMPLE_REGS_USER);
+ else if (in_entry_save_code(pc) && is_user_task(current))
+ alpha_profileme_set_regs_user(event, &data);
+
+ if (!alpha_perf_event_set_period(event, hwc, 0))
+ return;
+
+ saved_pc = pm_regs->pc;
+ saved_ps = pm_regs->ps;
+ pm_regs->pc = pc;
+ /* PS bit 3 is the mode bit that user_mode() tests. */
+ if (pc >= TASK_SIZE)
+ pm_regs->ps &= ~8UL;
+
+ perf_event_overflow(event, &data, pm_regs);
+
+ pm_regs->pc = saved_pc;
+ pm_regs->ps = saved_ps;
+}
+
/*
* Performance Monitoring Interrupt Service Routine called when a PMC
* overflows. The PMC that overflowed is passed in la_ptr.
@@ -907,6 +1157,10 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
idx = la_ptr;
+ /* A ProfileMe event owns both counters but lives at index 0. */
+ if (cpuc->config & EV67_PCTR_MODE_PROFILEME)
+ idx = 0;
+
for (j = 0; j < cpuc->n_events; j++) {
if (cpuc->current_idx[j] == idx)
break;
@@ -914,6 +1168,8 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
if (unlikely(j == cpuc->n_events)) {
/* This can occur if the event is disabled right on a PMC overflow. */
+ if (cpuc->config & EV67_PCTR_MODE_PROFILEME)
+ alpha_write_pmc_profileme(-alpha_pmu->pmc_max_period[0]);
wrperfmon(PERFMON_CMD_ENABLE, cpuc->idx_mask);
return;
}
@@ -931,7 +1187,17 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
hwc = &event->hw;
ovf = alpha_perf_event_ovf(hwc, idx);
if (ovf < 0) {
- /* The interrupt was pending when the counter was written. */
+ /*
+ * The interrupt was pending when the counter was written, or
+ * is from PCTR1 and was pending when a ProfileMe event was
+ * scheduled in.
+ */
+ wrperfmon(PERFMON_CMD_ENABLE, cpuc->idx_mask);
+ return;
+ }
+
+ if (hwc->flags & ALPHA_PERF_FLAG_PROFILEME) {
+ alpha_perf_event_profileme(event, hwc, regs, ovf);
wrperfmon(PERFMON_CMD_ENABLE, cpuc->idx_mask);
return;
}
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 07/13] alpha/perf: attach ProfileMe status as PERF_SAMPLE_RAW data
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
` (5 preceding siblings ...)
2026-11-04 18:49 ` [PATCH v6 06/13] alpha/perf: implement ProfileMe sampling Matt Turner
@ 2026-11-04 18:49 ` Matt Turner
2026-11-04 18:49 ` [PATCH v6 08/13] alpha/perf: optionally randomize the ProfileMe sampling interval Matt Turner
` (5 subsequent siblings)
12 siblings, 0 replies; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
A ProfileMe sample's PC alone does not say what happened to the profiled
instruction. I_STAT and PCTR_CTL do: whether it trapped and why, was
mispredicted, stalled, or was killed on a wrong path, and what PCTR1
counted during the window.
With PERF_SAMPLE_RAW, attach PMPC, I_STAT and PCTR_CTL to each ProfileMe
sample in a versioned struct alpha_profileme_sample. PMPC is included
because a PALmode PMPC is not the sample's IP. Killed instructions can be
told apart by PM_KILLED_BM and VAL. The 64-bit fields are packed after
the 32-bit version, since the payload follows the 32-bit raw size in the
sample record: they are then 8-byte aligned in the record, which needs
no padding.
Signed-off-by: Matt Turner <mattst88@gmail.com>
Reviewed-by: Magnus Lindholm <linmag7@gmail.com>
Tested-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/alpha/include/asm/perf_event.h | 2 ++
arch/alpha/include/uapi/asm/perf_event.h | 31 +++++++++++++++++++++++++++++++
arch/alpha/kernel/perf_event.c | 22 +++++++++++++++++++---
3 files changed, 52 insertions(+), 3 deletions(-)
diff --git a/arch/alpha/include/asm/perf_event.h b/arch/alpha/include/asm/perf_event.h
index 72536566014a..f498c1d47c07 100644
--- a/arch/alpha/include/asm/perf_event.h
+++ b/arch/alpha/include/asm/perf_event.h
@@ -1,6 +1,8 @@
#ifndef __ASM_ALPHA_PERF_EVENT_H
#define __ASM_ALPHA_PERF_EVENT_H
+#include <uapi/asm/perf_event.h>
+
/*
* Software events have no trap frame, so perf_fetch_caller_regs() fills
* in a zeroed pt_regs. A zero ps already means kernel mode; only pc is
diff --git a/arch/alpha/include/uapi/asm/perf_event.h b/arch/alpha/include/uapi/asm/perf_event.h
new file mode 100644
index 000000000000..aa0eff5a917c
--- /dev/null
+++ b/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/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index 7208d09a32d1..f6546d648133 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -1029,11 +1029,14 @@ static void alpha_perf_event_profileme(struct perf_event *event,
struct hw_perf_event *hwc,
struct pt_regs *regs, long ovf)
{
+ struct alpha_profileme_sample pm;
+ struct perf_raw_record raw;
struct perf_sample_data data;
struct pt_regs *pm_regs;
bool pc_only = false;
- unsigned long i_stat, pmpc, ovr, pc, saved_pc, saved_ps;
+ unsigned long pctr_ctl, i_stat, pmpc, ovr, pc, saved_pc, saved_ps;
+ pctr_ctl = wrperfmon(PERFMON_CMD_READ, 0);
i_stat = wrperfmon(PERFMON_CMD_I_STAT, 0);
pmpc = wrperfmon(PERFMON_CMD_PMPC, 0);
@@ -1070,8 +1073,8 @@ static void alpha_perf_event_profileme(struct perf_event *event,
* happens for ITB miss, Istream access violation and interrupt
* redirects, and for instructions inside PALcode. Keep the
* interrupted PC, which for redirects is the instruction that
- * trapped. Do the same for a user PMPC in a kernel thread, which has
- * no user frame.
+ * trapped, and report the real PMPC in the raw data. Do the same
+ * for a user PMPC in a kernel thread, which has no user frame.
*/
pm_regs = regs;
pc = pmpc & EV67_PMPC_PC_MASK;
@@ -1106,6 +1109,19 @@ static void alpha_perf_event_profileme(struct perf_event *event,
else if (in_entry_save_code(pc) && is_user_task(current))
alpha_profileme_set_regs_user(event, &data);
+ pm.version = ALPHA_PROFILEME_SAMPLE_VERSION;
+ pm.pmpc = pmpc;
+ pm.i_stat = i_stat;
+ /* PALcode sign-extends bit 47 of PCTR_CTL. */
+ pm.pctr_ctl = pctr_ctl & GENMASK(47, 0);
+ raw = (struct perf_raw_record) {
+ .frag = {
+ .size = sizeof(pm),
+ .data = &pm,
+ },
+ };
+ perf_sample_save_raw_data(&data, event, &raw);
+
if (!alpha_perf_event_set_period(event, hwc, 0))
return;
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 08/13] alpha/perf: optionally randomize the ProfileMe sampling interval
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
` (6 preceding siblings ...)
2026-11-04 18:49 ` [PATCH v6 07/13] alpha/perf: attach ProfileMe status as PERF_SAMPLE_RAW data Matt Turner
@ 2026-11-04 18:49 ` Matt Turner
2026-11-04 18:49 ` [PATCH v6 09/13] alpha/perf: report the ProfileMe window count as the sample weight Matt Turner
` (4 subsequent siblings)
12 siblings, 0 replies; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
A fixed sampling interval can phase-lock with a loop whose length divides
it, so that the same few instructions are profiled over and over while
the rest of the loop is never seen. DCPI avoided this by randomizing the
interval.
Add a randomize config bit that varies each interval by up to 1/16 of
the period in either direction. Only ProfileMe events accept it, since
Aggregate mode samples already land a variable distance from the
instruction that overflowed. Counts are unaffected, because prev_count
already records the value written to the counter. The jitter
is computed with mul_u64_u32_shr() and the sum saturates, so that a
period beyond 32 bits cannot overflow.
The jitter comes from a per-CPU prandom state rather than
get_random_u32_below(), which takes the batched entropy local lock and
triggers an invalid wait context report from lockdep in the PMI.
Signed-off-by: Matt Turner <mattst88@gmail.com>
Reviewed-by: Magnus Lindholm <linmag7@gmail.com>
Tested-by: Magnus Lindholm <linmag7@gmail.com>
---
arch/alpha/kernel/perf_event.c | 37 ++++++++++++++++++++++++++++++++++++-
1 file changed, 36 insertions(+), 1 deletion(-)
diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index f6546d648133..b33677cc23ca 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -16,6 +16,9 @@
#include <linux/kdebug.h>
#include <linux/mutex.h>
#include <linux/init.h>
+#include <linux/math64.h>
+#include <linux/overflow.h>
+#include <linux/prandom.h>
#include <linux/sched/task_stack.h>
#include <asm/hwrpb.h>
@@ -34,10 +37,12 @@
/* Layout of attr.config for PERF_TYPE_RAW events; see format/ in sysfs. */
#define ALPHA_PERF_CONFIG_EVENT 0xffULL
#define ALPHA_PERF_CONFIG_PROFILEME BIT_ULL(8)
+#define ALPHA_PERF_CONFIG_RANDOMIZE BIT_ULL(9)
/* hw_perf_event.flags */
#define ALPHA_PERF_FLAG_WRAPPED 0x1
#define ALPHA_PERF_FLAG_PROFILEME 0x2
+#define ALPHA_PERF_FLAG_RANDOMIZE 0x4
/* For tracking PMCs and the hw events they monitor on each CPU. */
struct cpu_hw_events {
@@ -61,6 +66,9 @@ struct cpu_hw_events {
};
DEFINE_PER_CPU(struct cpu_hw_events, cpu_hw_events);
+/* For randomizing sample intervals from the PMI, where locks are not allowed. */
+static DEFINE_PER_CPU(struct rnd_state, alpha_perf_rnd);
+
/*
@@ -248,10 +256,12 @@ static int ev67_raw_event_valid(u64 config)
PMU_FORMAT_ATTR(event, "config:0-7");
PMU_FORMAT_ATTR(profileme, "config:8");
+PMU_FORMAT_ATTR(randomize, "config:9");
static struct attribute *ev67_format_attrs[] = {
&format_attr_event.attr,
&format_attr_profileme.attr,
+ &format_attr_randomize.attr,
NULL,
};
@@ -389,6 +399,22 @@ static int alpha_perf_event_set_period(struct perf_event *event,
ret = 1;
}
+ /*
+ * Vary each new interval by up to 1/16 of the period either way, so
+ * the samples cannot phase-lock with a loop whose length divides the
+ * period. The count stays exact because prev_count records the value
+ * written.
+ */
+ if (ret && (hwc->flags & ALPHA_PERF_FLAG_RANDOMIZE) && period >= 8) {
+ long jitter = period / 8;
+ u32 r = prandom_u32_state(this_cpu_ptr(&alpha_perf_rnd));
+ long offset = mul_u64_u32_shr(jitter, r, 32) - jitter / 2;
+
+ if (check_add_overflow(left, offset, &left))
+ left = LONG_MAX;
+ local64_set(&hwc->period_left, left);
+ }
+
/*
* Hardware restrictions require that the counters must not be
* written with values that are too close to the maximum period.
@@ -763,7 +789,8 @@ static int __hw_perf_event_init(struct perf_event *event)
return -EOPNOTSUPP;
} else if (attr->type == PERF_TYPE_RAW) {
if (attr->config & ~(ALPHA_PERF_CONFIG_EVENT |
- ALPHA_PERF_CONFIG_PROFILEME))
+ ALPHA_PERF_CONFIG_PROFILEME |
+ ALPHA_PERF_CONFIG_RANDOMIZE))
return -EINVAL;
ev = attr->config & ALPHA_PERF_CONFIG_EVENT;
if (!alpha_pmu->raw_event_valid(ev))
@@ -791,6 +818,14 @@ static int __hw_perf_event_init(struct perf_event *event)
return -EINVAL;
hwc->flags = profileme ? ALPHA_PERF_FLAG_PROFILEME : 0;
+ if (attr->type == PERF_TYPE_RAW &&
+ (attr->config & ALPHA_PERF_CONFIG_RANDOMIZE)) {
+ if (!profileme)
+ return -EINVAL;
+ prandom_init_once(&alpha_perf_rnd);
+ hwc->flags |= ALPHA_PERF_FLAG_RANDOMIZE;
+ }
+
/*
* We place the event type in event_base here and leave calculation
* of the codes to programme the PMU for alpha_pmu_enable() because
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 09/13] alpha/perf: report the ProfileMe window count as the sample weight
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
` (7 preceding siblings ...)
2026-11-04 18:49 ` [PATCH v6 08/13] alpha/perf: optionally randomize the ProfileMe sampling interval Matt Turner
@ 2026-11-04 18:49 ` Matt Turner
2026-11-04 22:11 ` Magnus Lindholm
2026-11-04 18:49 ` [PATCH v6 10/13] alpha/perf: report the data source of ProfileMe memory accesses Matt Turner
` (3 subsequent siblings)
12 siblings, 1 reply; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
What PCTR1 counted during the ProfileMe window, the inum retire delay for
cycles:p or the window length for instructions:p, is only available in
the PERF_SAMPLE_RAW data.
Report it in var1_dw of the sample weight, which the weight and
local_weight sort keys read. It is 20 bits, so it fits. As Alpha is
little-endian, this is also the right value for plain PERF_SAMPLE_WEIGHT,
which is what perf record -W asks for. Samples that fall back to the
interrupted PC get no weight, as it belongs to a different instruction.
Suggested-by: Ian Rogers <irogers@google.com>
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
arch/alpha/kernel/perf_event.c | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index b33677cc23ca..c9ab40ff76ad 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -1068,7 +1068,7 @@ static void alpha_perf_event_profileme(struct perf_event *event,
struct perf_raw_record raw;
struct perf_sample_data data;
struct pt_regs *pm_regs;
- bool pc_only = false;
+ bool pc_only = false, exact = true;
unsigned long pctr_ctl, i_stat, pmpc, ovr, pc, saved_pc, saved_ps;
pctr_ctl = wrperfmon(PERFMON_CMD_READ, 0);
@@ -1115,6 +1115,7 @@ static void alpha_perf_event_profileme(struct perf_event *event,
pc = pmpc & EV67_PMPC_PC_MASK;
if (pmpc & EV67_PMPC_PAL) {
pc = regs->pc;
+ exact = false;
} else if (pc < TASK_SIZE && !user_mode(regs)) {
if (is_user_task(current)) {
pc_only = true;
@@ -1124,6 +1125,7 @@ static void alpha_perf_event_profileme(struct perf_event *event,
pm_regs->ps = 8;
} else {
pc = regs->pc;
+ exact = false;
}
} else if (pc >= TASK_SIZE && user_mode(regs)) {
pm_regs = this_cpu_ptr(&alpha_profileme_regs);
@@ -1157,6 +1159,15 @@ static void alpha_perf_event_profileme(struct perf_event *event,
};
perf_sample_save_raw_data(&data, event, &raw);
+ /* The count belongs to the profiled instruction, not the interrupted PC. */
+ if (exact && (event->attr.sample_type & PERF_SAMPLE_WEIGHT_TYPE)) {
+ /* perf_sample_data_init() does not clear it. */
+ data.weight.full = 0;
+ data.weight.var1_dw = (pctr_ctl >> EV67_PCTR_1_COUNT_SHIFT) &
+ EV67_PCTR_1_COUNT_MASK;
+ data.sample_flags |= PERF_SAMPLE_WEIGHT_TYPE;
+ }
+
if (!alpha_perf_event_set_period(event, hwc, 0))
return;
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 10/13] alpha/perf: report the data source of ProfileMe memory accesses
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
` (8 preceding siblings ...)
2026-11-04 18:49 ` [PATCH v6 09/13] alpha/perf: report the ProfileMe window count as the sample weight Matt Turner
@ 2026-11-04 18:49 ` Matt Turner
2026-11-04 22:31 ` Magnus Lindholm
2026-11-04 18:49 ` [PATCH v6 11/13] alpha/perf: add ProfileMe sample filters Matt Turner
` (2 subsequent siblings)
12 siblings, 1 reply; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
Whether a ProfileMe sample's memory access missed the DTB is only in the
PERF_SAMPLE_RAW data, as a DTB miss trap.
Set PERF_SAMPLE_DATA_SRC for the samples of memory instructions, so that
the memory sort keys see them. One that trapped on a DTB miss gets an L1
miss in mem_dtlb. PALcode fills the DTB, so one that retired without
that trap hit it, and gets an L1 hit. mem_op comes from the opcode, as
I_STAT does not say whether the access was a load or a store. A load
into $31 or $f31 is a prefetch, except for ldq_u, a UNOP that is left
out. Samples that fall back to the interrupted PC get none.
On an EV7, a loop that loads one word from each page of 64 MB in random
order had as many DTB hit samples on the load as misses: the miss trap
aborts the load, and its refetch hits. A prefetch into $31 takes the
same trap, and its samples are reported as PFETCH misses and hits.
I_STAT[TRP] is ignored for an instruction killed before it was mapped. On
an EV7, TRP and MIS were seen set for killed instructions that are not
even branches.
Suggested-by: Ian Rogers <irogers@google.com>
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
arch/alpha/include/asm/wrperfmon.h | 12 ++++++
arch/alpha/kernel/perf_event.c | 86 ++++++++++++++++++++++++++++++++++++++
2 files changed, 98 insertions(+)
diff --git a/arch/alpha/include/asm/wrperfmon.h b/arch/alpha/include/asm/wrperfmon.h
index 7fbbfd74926a..dc24c7cb45db 100644
--- a/arch/alpha/include/asm/wrperfmon.h
+++ b/arch/alpha/include/asm/wrperfmon.h
@@ -100,6 +100,18 @@
/* ProfileMe status, from the EV67/EV68 Hardware Reference Manuals. */
#define EV67_I_STAT_OVR_SHIFT 30
#define EV67_I_STAT_OVR_MASK 0x7UL
+#define EV67_I_STAT_TRAP_TYPE_SHIFT 34
+#define EV67_I_STAT_TRAP_TYPE_MASK 0xfUL
+#define EV67_I_STAT_TRP (1UL<<39)
+#define EV67_I_STAT_MIS (1UL<<40)
+
+/* I_STAT[TRAP_TYPE] values */
+#define EV67_TRAP_DTB_DOUBLE_3 2
+#define EV67_TRAP_DTB_DOUBLE_4 3
+#define EV67_TRAP_DTB_SINGLE 6
+
+#define EV67_PCTR_VAL (1UL<<1)
+#define EV67_PCTR_PM_KILLED_BM (1UL<<26)
/* PMPC bits 63:2 hold the profiled PC. Bit 0 set means a PALmode address. */
#define EV67_PMPC_PAL (1UL<<0)
diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index c9ab40ff76ad..0723e1042145 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -20,6 +20,7 @@
#include <linux/overflow.h>
#include <linux/prandom.h>
#include <linux/sched/task_stack.h>
+#include <linux/uaccess.h>
#include <asm/hwrpb.h>
#include <linux/atomic.h>
@@ -1053,6 +1054,88 @@ static void alpha_profileme_set_regs_user(struct perf_event *event,
sizeof(u64);
}
+/* TRP, and MIS with it, are stale for an insn killed before it was mapped. */
+static bool alpha_profileme_trp(unsigned long i_stat, unsigned long pctr_ctl)
+{
+ return !(pctr_ctl & EV67_PCTR_PM_KILLED_BM) && (i_stat & EV67_I_STAT_TRP);
+}
+
+/* What the memory instruction at pc does, or PERF_MEM_OP_NA. */
+static u64 alpha_profileme_mem_op(unsigned long pc)
+{
+ u32 insn;
+ bool r31;
+
+ if (pc < TASK_SIZE) {
+ if (copy_from_user_nofault(&insn, (const void __user *)pc,
+ sizeof(insn)))
+ return PERF_MEM_OP_NA;
+ } else if (copy_from_kernel_nofault(&insn, (const void *)pc,
+ sizeof(insn))) {
+ return PERF_MEM_OP_NA;
+ }
+
+ /* A load into $31 or $f31 is a prefetch, except for ldq_u: a UNOP. */
+ r31 = ((insn >> 21) & 0x1f) == 31;
+
+ switch (insn >> 26) {
+ case 0x0b: /* ldq_u */
+ return r31 ? PERF_MEM_OP_NA : PERF_MEM_OP_LOAD;
+ case 0x0a: case 0x0c: /* ldbu, ldwu */
+ case 0x20: case 0x21: case 0x22: case 0x23: /* ldf, ldg, lds, ldt */
+ case 0x28: case 0x29: /* ldl, ldq */
+ return r31 ? PERF_MEM_OP_PFETCH : PERF_MEM_OP_LOAD;
+ case 0x2a: case 0x2b: /* ldl_l, ldq_l */
+ return PERF_MEM_OP_LOAD;
+ case 0x0d: case 0x0e: case 0x0f: /* stw, stb, stq_u */
+ case 0x24: case 0x25: case 0x26: case 0x27: /* stf, stg, sts, stt */
+ case 0x2c: case 0x2d: case 0x2e: case 0x2f: /* stl, stq, stl_c, stq_c */
+ return PERF_MEM_OP_STORE;
+ default:
+ return PERF_MEM_OP_NA;
+ }
+}
+
+/*
+ * Report the DTB access of a memory instruction. PALcode fills the DTB, so
+ * one that retired without a DTB miss trap hit it.
+ */
+static void alpha_profileme_data_src(struct perf_sample_data *data,
+ unsigned long pc, unsigned long i_stat,
+ unsigned long pctr_ctl)
+{
+ u64 op, dtlb;
+
+ if (alpha_profileme_trp(i_stat, pctr_ctl)) {
+ if (i_stat & EV67_I_STAT_MIS)
+ return;
+ switch ((i_stat >> EV67_I_STAT_TRAP_TYPE_SHIFT) &
+ EV67_I_STAT_TRAP_TYPE_MASK) {
+ case EV67_TRAP_DTB_SINGLE:
+ case EV67_TRAP_DTB_DOUBLE_3:
+ case EV67_TRAP_DTB_DOUBLE_4:
+ dtlb = PERF_MEM_TLB_L1 | PERF_MEM_TLB_MISS;
+ break;
+ default:
+ return;
+ }
+ op = alpha_profileme_mem_op(pc);
+ } else if (!(pctr_ctl & EV67_PCTR_PM_KILLED_BM) &&
+ (pctr_ctl & EV67_PCTR_VAL)) {
+ dtlb = PERF_MEM_TLB_L1 | PERF_MEM_TLB_HIT;
+ op = alpha_profileme_mem_op(pc);
+ if (op == PERF_MEM_OP_NA)
+ return;
+ } else {
+ return;
+ }
+
+ data->data_src.val = PERF_MEM_NA;
+ data->data_src.mem_op = op;
+ data->data_src.mem_dtlb = dtlb;
+ data->sample_flags |= PERF_SAMPLE_DATA_SRC;
+}
+
/*
* Handle the close of a ProfileMe window on PCTR0.
*
@@ -1168,6 +1251,9 @@ static void alpha_perf_event_profileme(struct perf_event *event,
data.sample_flags |= PERF_SAMPLE_WEIGHT_TYPE;
}
+ if (exact && (event->attr.sample_type & PERF_SAMPLE_DATA_SRC))
+ alpha_profileme_data_src(&data, pc, i_stat, pctr_ctl);
+
if (!alpha_perf_event_set_period(event, hwc, 0))
return;
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 11/13] alpha/perf: add ProfileMe sample filters
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
` (9 preceding siblings ...)
2026-11-04 18:49 ` [PATCH v6 10/13] alpha/perf: report the data source of ProfileMe memory accesses Matt Turner
@ 2026-11-04 18:49 ` Matt Turner
2026-11-04 18:49 ` [PATCH v6 12/13] alpha/perf: sample only retired instructions for precise events Matt Turner
2026-11-04 18:49 ` [PATCH v6 13/13] alpha/perf: honor exclude_user and exclude_kernel for ProfileMe events Matt Turner
12 siblings, 0 replies; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
ProfileMe samples any fetched instruction, including those killed on a
wrong path, and what happened to it is only in the PERF_SAMPLE_RAW data.
Add three config1 bits that keep only the samples of instructions that:
retired retired. VAL says so for an instruction that did not trap,
and the trap type for one that did.
mispredict were mispredicted conditional branches.
trapped trapped, mispredicts included, or caused an ITB miss,
Istream access violation or interrupt redirect.
A sample must match every bit that is set, as with Arm SPE's
event_filter. The driver drops the samples that do not, and they still
count as interrupts for throttling and frequency mode.
Suggested-by: Ian Rogers <irogers@google.com>
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
arch/alpha/include/asm/wrperfmon.h | 10 +++++
arch/alpha/kernel/perf_event.c | 83 ++++++++++++++++++++++++++++++++++++++
2 files changed, 93 insertions(+)
diff --git a/arch/alpha/include/asm/wrperfmon.h b/arch/alpha/include/asm/wrperfmon.h
index dc24c7cb45db..6526438e73ee 100644
--- a/arch/alpha/include/asm/wrperfmon.h
+++ b/arch/alpha/include/asm/wrperfmon.h
@@ -106,9 +106,14 @@
#define EV67_I_STAT_MIS (1UL<<40)
/* I_STAT[TRAP_TYPE] values */
+#define EV67_TRAP_REPLAY 0
#define EV67_TRAP_DTB_DOUBLE_3 2
#define EV67_TRAP_DTB_DOUBLE_4 3
+#define EV67_TRAP_FEN 4
+#define EV67_TRAP_UNALIGNED 5
#define EV67_TRAP_DTB_SINGLE 6
+#define EV67_TRAP_DFAULT 7
+#define EV67_TRAP_OPCDEC 8
#define EV67_PCTR_VAL (1UL<<1)
#define EV67_PCTR_PM_KILLED_BM (1UL<<26)
@@ -116,5 +121,10 @@
/* PMPC bits 63:2 hold the profiled PC. Bit 0 set means a PALmode address. */
#define EV67_PMPC_PAL (1UL<<0)
#define EV67_PMPC_PC_MASK (~3UL)
+/* PMPC[14:0] of the redirect traps, which do not set I_STAT[TRP] */
+#define EV67_PMPC_PAL_OFFSET_MASK 0x7fffUL
+#define EV67_PMPC_IACV 0x0481
+#define EV67_PMPC_ITB_MISS 0x0581
+#define EV67_PMPC_INTERRUPT 0x0681
#endif
diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index 0723e1042145..32a8b5c98381 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -39,6 +39,13 @@
#define ALPHA_PERF_CONFIG_EVENT 0xffULL
#define ALPHA_PERF_CONFIG_PROFILEME BIT_ULL(8)
#define ALPHA_PERF_CONFIG_RANDOMIZE BIT_ULL(9)
+/* attr.config1 of a ProfileMe event: keep only samples of these insns. */
+#define ALPHA_PERF_CONFIG1_RETIRED BIT_ULL(0)
+#define ALPHA_PERF_CONFIG1_MISPREDICT BIT_ULL(1)
+#define ALPHA_PERF_CONFIG1_TRAPPED BIT_ULL(2)
+#define ALPHA_PERF_CONFIG1_FILTERS (ALPHA_PERF_CONFIG1_RETIRED | \
+ ALPHA_PERF_CONFIG1_MISPREDICT | \
+ ALPHA_PERF_CONFIG1_TRAPPED)
/* hw_perf_event.flags */
#define ALPHA_PERF_FLAG_WRAPPED 0x1
@@ -258,11 +265,17 @@ static int ev67_raw_event_valid(u64 config)
PMU_FORMAT_ATTR(event, "config:0-7");
PMU_FORMAT_ATTR(profileme, "config:8");
PMU_FORMAT_ATTR(randomize, "config:9");
+PMU_FORMAT_ATTR(retired, "config1:0");
+PMU_FORMAT_ATTR(mispredict, "config1:1");
+PMU_FORMAT_ATTR(trapped, "config1:2");
static struct attribute *ev67_format_attrs[] = {
&format_attr_event.attr,
&format_attr_profileme.attr,
&format_attr_randomize.attr,
+ &format_attr_retired.attr,
+ &format_attr_mispredict.attr,
+ &format_attr_trapped.attr,
NULL,
};
@@ -819,6 +832,12 @@ static int __hw_perf_event_init(struct perf_event *event)
return -EINVAL;
hwc->flags = profileme ? ALPHA_PERF_FLAG_PROFILEME : 0;
+ if (attr->config1 & ~ALPHA_PERF_CONFIG1_FILTERS)
+ return -EINVAL;
+ if (attr->config1 && !profileme)
+ return -EINVAL;
+ hwc->config = attr->config1;
+
if (attr->type == PERF_TYPE_RAW &&
(attr->config & ALPHA_PERF_CONFIG_RANDOMIZE)) {
if (!profileme)
@@ -1060,6 +1079,62 @@ static bool alpha_profileme_trp(unsigned long i_stat, unsigned long pctr_ctl)
return !(pctr_ctl & EV67_PCTR_PM_KILLED_BM) && (i_stat & EV67_I_STAT_TRP);
}
+/*
+ * VAL says whether an instruction that did not trap retired. For one that
+ * did, the trap type decides: these faults abort it, to be replayed,
+ * refetched after a fill, or emulated. Other traps, mispredicts among
+ * them, are counted as retired.
+ */
+static bool alpha_profileme_retired(unsigned long i_stat,
+ unsigned long pctr_ctl)
+{
+ if (pctr_ctl & EV67_PCTR_PM_KILLED_BM)
+ return false;
+ if (!(i_stat & EV67_I_STAT_TRP))
+ return pctr_ctl & EV67_PCTR_VAL;
+ if (i_stat & EV67_I_STAT_MIS)
+ return true;
+
+ switch ((i_stat >> EV67_I_STAT_TRAP_TYPE_SHIFT) &
+ EV67_I_STAT_TRAP_TYPE_MASK) {
+ case EV67_TRAP_REPLAY:
+ case EV67_TRAP_DTB_DOUBLE_3:
+ case EV67_TRAP_DTB_DOUBLE_4:
+ case EV67_TRAP_FEN:
+ case EV67_TRAP_UNALIGNED:
+ case EV67_TRAP_DTB_SINGLE:
+ case EV67_TRAP_DFAULT:
+ case EV67_TRAP_OPCDEC:
+ return false;
+ default:
+ return true;
+ }
+}
+
+/* What the profiled instruction did, as config1 filter bits. */
+static u64 alpha_profileme_status(unsigned long i_stat,
+ unsigned long pctr_ctl, unsigned long pmpc)
+{
+ u64 status = 0;
+
+ if (alpha_profileme_retired(i_stat, pctr_ctl))
+ status |= ALPHA_PERF_CONFIG1_RETIRED;
+ if (alpha_profileme_trp(i_stat, pctr_ctl)) {
+ status |= ALPHA_PERF_CONFIG1_TRAPPED;
+ if (i_stat & EV67_I_STAT_MIS)
+ status |= ALPHA_PERF_CONFIG1_MISPREDICT;
+ } else if (pmpc & EV67_PMPC_PAL) {
+ switch (pmpc & EV67_PMPC_PAL_OFFSET_MASK) {
+ case EV67_PMPC_IACV:
+ case EV67_PMPC_ITB_MISS:
+ case EV67_PMPC_INTERRUPT:
+ status |= ALPHA_PERF_CONFIG1_TRAPPED;
+ break;
+ }
+ }
+ return status;
+}
+
/* What the memory instruction at pc does, or PERF_MEM_OP_NA. */
static u64 alpha_profileme_mem_op(unsigned long pc)
{
@@ -1170,6 +1245,14 @@ static void alpha_perf_event_profileme(struct perf_event *event,
local64_add(ovr, &hwc->period_left);
}
+ /* A dropped sample still counts, for throttling and frequency mode. */
+ if ((alpha_profileme_status(i_stat, pctr_ctl, pmpc) & hwc->config) !=
+ hwc->config) {
+ if (alpha_perf_event_set_period(event, hwc, 0))
+ perf_event_account_interrupt(event);
+ return;
+ }
+
/*
* Attribute the sample to the profiled instruction rather than to
* wherever the interrupt landed. perf takes the sample's mode,
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 12/13] alpha/perf: sample only retired instructions for precise events
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
` (10 preceding siblings ...)
2026-11-04 18:49 ` [PATCH v6 11/13] alpha/perf: add ProfileMe sample filters Matt Turner
@ 2026-11-04 18:49 ` Matt Turner
2026-11-04 18:49 ` [PATCH v6 13/13] alpha/perf: honor exclude_user and exclude_kernel for ProfileMe events Matt Turner
12 siblings, 0 replies; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
ProfileMe samples include instructions killed on a wrong path, which
show up as hot spots in perf report although they did no work. On an
EV7 loop with a randomly mispredicted branch, 19% of the samples were
killed before being mapped and another 48% later.
PEBS and AMD IBS op samples are of retired instructions only, so make
precise_ip imply the retired filter, unless filters are given. The
profileme bit still selects every sample. With cycles:p, both kinds of
killed sample went to 0%.
DTB miss samples, whose instruction is aborted, are then dropped too:
cycles:p reports DTB hits only, and the misses need cpu/cycles,trapped/p.
The kept samples carry the full period, as do those AMD IBS keeps when
its swfilt discards others. So in frequency mode the sample rate, and
the event count perf report estimates from the periods, fall by the
share of killed instructions: to about a third on that loop.
Suggested-by: Ian Rogers <irogers@google.com>
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
arch/alpha/kernel/perf_event.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index 32a8b5c98381..7e34eac0ea47 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -837,6 +837,11 @@ static int __hw_perf_event_init(struct perf_event *event)
if (attr->config1 && !profileme)
return -EINVAL;
hwc->config = attr->config1;
+ /* As with PEBS, precise samples are of retired instructions. */
+ if (attr->precise_ip && !attr->config1 &&
+ !(attr->type == PERF_TYPE_RAW &&
+ (attr->config & ALPHA_PERF_CONFIG_PROFILEME)))
+ hwc->config = ALPHA_PERF_CONFIG1_RETIRED;
if (attr->type == PERF_TYPE_RAW &&
(attr->config & ALPHA_PERF_CONFIG_RANDOMIZE)) {
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH v6 13/13] alpha/perf: honor exclude_user and exclude_kernel for ProfileMe events
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
` (11 preceding siblings ...)
2026-11-04 18:49 ` [PATCH v6 12/13] alpha/perf: sample only retired instructions for precise events Matt Turner
@ 2026-11-04 18:49 ` Matt Turner
12 siblings, 0 replies; 18+ messages in thread
From: Matt Turner @ 2026-11-04 18:49 UTC (permalink / raw)
To: Richard Henderson, Magnus Lindholm, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark
Cc: linux-alpha, linux-kernel, linux-perf-users, Matt Turner
The PMU sets PERF_PMU_CAP_NO_EXCLUDE, as the counters count in every
mode. perf record then fails for unprivileged users: at the default
perf_event_paranoid of 2, the tool sets exclude_kernel for them.
A ProfileMe sample says which mode the profiled instruction ran in, so
the excluded samples can be dropped, as AMD IBS does with swfilt. Do
that for ProfileMe events, counting PALmode as kernel, and ignore
exclude_hv, as there is no hypervisor. A sample that falls back to the
interrupted PC is dropped if either its PC or the profiled instruction
is in an excluded mode. Aggregate events cannot tell, so event_init
now rejects their exclude bits itself, along with exclude_idle,
exclude_guest and exclude_host for all events.
The event count, and what PCTR1 counted during a window, still include
the excluded modes, as they do for IBS.
Suggested-by: Ian Rogers <irogers@google.com>
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
arch/alpha/kernel/perf_event.c | 47 ++++++++++++++++++++++++++++++++++--------
1 file changed, 38 insertions(+), 9 deletions(-)
diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
index 7e34eac0ea47..c75d204076fa 100644
--- a/arch/alpha/kernel/perf_event.c
+++ b/arch/alpha/kernel/perf_event.c
@@ -832,6 +832,18 @@ static int __hw_perf_event_init(struct perf_event *event)
return -EINVAL;
hwc->flags = profileme ? ALPHA_PERF_FLAG_PROFILEME : 0;
+ /*
+ * The counters count in every mode, but ProfileMe samples say which
+ * mode the profiled instruction ran in, so those that are excluded can
+ * be dropped. PALmode counts as kernel, and exclude_hv is ignored, as
+ * there is no hypervisor.
+ */
+ if (attr->exclude_idle || attr->exclude_guest || attr->exclude_host)
+ return -EINVAL;
+ if (!profileme &&
+ (attr->exclude_user || attr->exclude_kernel || attr->exclude_hv))
+ return -EINVAL;
+
if (attr->config1 & ~ALPHA_PERF_CONFIG1_FILTERS)
return -EINVAL;
if (attr->config1 && !profileme)
@@ -983,7 +995,6 @@ static struct pmu pmu = {
.stop = alpha_pmu_stop,
.read = alpha_pmu_read,
.attr_groups = ev67_attr_groups,
- .capabilities = PERF_PMU_CAP_NO_EXCLUDE,
};
@@ -1140,6 +1151,24 @@ static u64 alpha_profileme_status(unsigned long i_stat,
return status;
}
+/*
+ * Whether exclude_user or exclude_kernel rules out a sample of the profiled
+ * instruction at pmpc, reported at pc. A PALmode PMPC counts as kernel. The
+ * two differ only when the sample falls back to the interrupted PC, and
+ * such a sample is then dropped if either mode is excluded.
+ */
+static bool alpha_profileme_excluded(struct perf_event *event,
+ unsigned long pmpc, unsigned long pc)
+{
+ bool pm_user = !(pmpc & EV67_PMPC_PAL) &&
+ (pmpc & EV67_PMPC_PC_MASK) < TASK_SIZE;
+ bool user = pc < TASK_SIZE;
+
+ if (event->attr.exclude_user && (pm_user || user))
+ return true;
+ return event->attr.exclude_kernel && !(pm_user && user);
+}
+
/* What the memory instruction at pc does, or PERF_MEM_OP_NA. */
static u64 alpha_profileme_mem_op(unsigned long pc)
{
@@ -1250,14 +1279,6 @@ static void alpha_perf_event_profileme(struct perf_event *event,
local64_add(ovr, &hwc->period_left);
}
- /* A dropped sample still counts, for throttling and frequency mode. */
- if ((alpha_profileme_status(i_stat, pctr_ctl, pmpc) & hwc->config) !=
- hwc->config) {
- if (alpha_perf_event_set_period(event, hwc, 0))
- perf_event_account_interrupt(event);
- return;
- }
-
/*
* Attribute the sample to the profiled instruction rather than to
* wherever the interrupt landed. perf takes the sample's mode,
@@ -1303,6 +1324,14 @@ static void alpha_perf_event_profileme(struct perf_event *event,
memset(pm_regs, 0, sizeof(*pm_regs));
}
+ /* A dropped sample still counts, for throttling and frequency mode. */
+ if ((alpha_profileme_status(i_stat, pctr_ctl, pmpc) & hwc->config) !=
+ hwc->config || alpha_profileme_excluded(event, pmpc, pc)) {
+ if (alpha_perf_event_set_period(event, hwc, 0))
+ perf_event_account_interrupt(event);
+ return;
+ }
+
perf_sample_data_init(&data, 0, hwc->last_period);
if (pc_only)
alpha_profileme_drop_regs(event, &data, PERF_SAMPLE_REGS_USER |
--
2.55.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v6 05/13] alpha/perf: ignore overflow interrupts the counter does not account for
2026-11-04 18:49 ` [PATCH v6 05/13] alpha/perf: ignore overflow interrupts the counter does not account for Matt Turner
@ 2026-11-04 21:54 ` Magnus Lindholm
0 siblings, 0 replies; 18+ messages in thread
From: Magnus Lindholm @ 2026-11-04 21:54 UTC (permalink / raw)
To: Matt Turner
Cc: Richard Henderson, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, linux-alpha, linux-kernel, linux-perf-users
Hi Matt,
On Wed, Nov 4, 2026 at 7:49 PM Matt Turner <mattst88@gmail.com> wrote:
>
> The interrupt handler adds a full counter period to the count of the
> event on the counter that interrupted, which goes wrong in two cases.
>
> Rewriting a counter does not cancel its pending interrupt: wrperfmon has
> no way to, and PALcode clears the request only when delivering it. An
> interrupt pending when an event is reloaded, or when perf moves another
> event onto the counter, is then taken as an overflow of a counter that
> has not overflowed. It adds a bogus period to the count and can emit a
> false sample.
>
> And alpha_perf_event_update() already adds the period when a read finds
> that the counter overflowed before its interrupt was taken. The
> interrupt then adds it again.
>
> An overflow leaves the counter just past zero, below the value it was
> written with, while a counter that has not overflowed is at or above it.
> Ignore an interrupt for a counter in the latter state, unless a read
> has already counted the overflow, in which case add nothing. For this to
> hold, write the counters at most half their range below overflow, as
> armpmu_event_set_period() does. Counting events then take an interrupt
> every 2^19 events rather than every 2^20.
>
> Fixes: 979f867191f8 ("alpha: implement HW performance events on the EV67 and later CPUs")
> Suggested-by: Ian Rogers <irogers@google.com>
> Assisted-by: Claude:claude-opus-5-5
> Signed-off-by: Matt Turner <mattst88@gmail.com>
> ---
> arch/alpha/kernel/perf_event.c | 59 +++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 52 insertions(+), 7 deletions(-)
>
> diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
> index ecec78c355cd..dd7fc4ac1396 100644
> --- a/arch/alpha/kernel/perf_event.c
> +++ b/arch/alpha/kernel/perf_event.c
> @@ -30,6 +30,9 @@
> #define MAX_HWEVENTS 3
> #define PMC_NO_INDEX -1
>
> +/* hw_perf_event.flags */
> +#define ALPHA_PERF_FLAG_WRAPPED 0x1
> +
> /* For tracking PMCs and the hw events they monitor on each CPU. */
> struct cpu_hw_events {
> int enabled;
> @@ -315,10 +318,16 @@ static int alpha_perf_event_set_period(struct perf_event *event,
> if (unlikely(left < alpha_pmu->pmc_left[idx]))
> left = alpha_pmu->pmc_left[idx];
>
> - if (left > (long)alpha_pmu->pmc_max_period[idx])
> - left = alpha_pmu->pmc_max_period[idx];
> + /*
> + * Start at most half the counter's range below overflow, so that the
> + * value an overflow leaves, just past zero, is always below the value
> + * written. See alpha_perf_event_ovf().
> + */
> + if (left > (long)(alpha_pmu->pmc_max_period[idx] >> 1))
> + left = alpha_pmu->pmc_max_period[idx] >> 1;
>
> local64_set(&hwc->prev_count, (unsigned long)(-left));
> + hwc->flags &= ~ALPHA_PERF_FLAG_WRAPPED;
>
> alpha_write_pmc(idx, (unsigned long)(-left));
>
> @@ -338,9 +347,9 @@ static int alpha_perf_event_set_period(struct perf_event *event,
> * zero. The value passed via ovf by the interrupt handler corrects for
> * overflow.
> *
> - * This can be racey on rare occasions -- a call to this routine can occur
> - * with an overflowed counter just before the PMI service routine is called.
> - * The check for delta negative hopefully always rectifies this situation.
> + * A call to this routine can occur with an overflowed counter just before
> + * the PMI service routine is called. A negative delta detects this, as the
> + * counter is always written at most half its range below overflow.
> */
> static unsigned long alpha_perf_event_update(struct perf_event *event,
> struct hw_perf_event *hwc, int idx, long ovf)
> @@ -359,10 +368,12 @@ static unsigned long alpha_perf_event_update(struct perf_event *event,
> delta = (new_raw_count - (prev_raw_count & alpha_pmu->pmc_count_mask[idx])) + ovf;
>
> /* It is possible on very rare occasions that the PMC has overflowed
> - * but the interrupt is yet to come. Detect and fix this situation.
> + * but the interrupt is yet to come. Detect and fix this situation,
> + * and tell the interrupt not to count the overflow again.
> */
> if (unlikely(delta < 0)) {
> delta += alpha_pmu->pmc_max_period[idx] + 1;
> + hwc->flags |= ALPHA_PERF_FLAG_WRAPPED;
> }
>
> local64_add(delta, &event->count);
> @@ -835,6 +846,32 @@ void perf_event_print_debug(void)
> }
>
>
> +/*
> + * Return the overflow that an interrupt from counter idx adds to the count,
> + * or -1 if the counter has not overflowed since it was written.
> + *
> + * Rewriting a counter does not cancel its pending interrupt: wrperfmon has
> + * no way to, and PALcode clears the request only when delivering it. So an
> + * interrupt can arrive after the counter was reloaded or given to another
> + * event. The counter is then at or above the value written, whereas an
> + * overflow leaves it just past zero, below that value.
> + */
> +static long alpha_perf_event_ovf(struct hw_perf_event *hwc, int idx)
> +{
> + unsigned long prev;
> +
> + /* alpha_perf_event_update() has already counted this overflow. */
> + if (hwc->flags & ALPHA_PERF_FLAG_WRAPPED)
> + return 0;
> +
> + prev = local64_read(&hwc->prev_count) & alpha_pmu->pmc_count_mask[idx];
> + if (alpha_read_pmc(idx) >= prev)
> + return -1;
> +
> + return alpha_pmu->pmc_max_period[idx] + 1;
> +}
> +
> +
> /*
> * Performance Monitoring Interrupt Service Routine called when a PMC
> * overflows. The PMC that overflowed is passed in la_ptr.
> @@ -847,6 +884,7 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
> struct perf_event *event;
> struct hw_perf_event *hwc;
> int idx, j;
> + long ovf;
>
> __this_cpu_inc(irq_pmi_count);
> cpuc = this_cpu_ptr(&cpu_hw_events);
> @@ -891,7 +929,14 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
> }
>
> hwc = &event->hw;
> - alpha_perf_event_update(event, hwc, idx, alpha_pmu->pmc_max_period[idx]+1);
> + ovf = alpha_perf_event_ovf(hwc, idx);
> + if (ovf < 0) {
> + /* The interrupt was pending when the counter was written. */
> + wrperfmon(PERFMON_CMD_ENABLE, cpuc->idx_mask);
> + return;
> + }
> +
> + alpha_perf_event_update(event, hwc, idx, ovf);
> perf_sample_data_init(&data, 0, hwc->last_period);
>
> if (alpha_perf_event_set_period(event, hwc, idx))
>
> --
> 2.55.0
>
The wrapped flag addresses my concern about a read accounting for an
overflow before its PMI arrives. I checked that sequence and the
stale-PMI-after-reload case with simulated counter values.
On my ES40, repeated reads while multiplexing Aggregate and ProfileMe
events passed on all three CPUs. I didn't instrument the stale PMI
itself on hardware.
Reviewed-by: Magnus Lindholm <linmag7@gmail.com>
Tested-by: Magnus Lindholm <linmag7@gmail.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v6 06/13] alpha/perf: implement ProfileMe sampling
2026-11-04 18:49 ` [PATCH v6 06/13] alpha/perf: implement ProfileMe sampling Matt Turner
@ 2026-11-04 21:57 ` Magnus Lindholm
0 siblings, 0 replies; 18+ messages in thread
From: Magnus Lindholm @ 2026-11-04 21:57 UTC (permalink / raw)
To: Matt Turner
Cc: Richard Henderson, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, linux-alpha, linux-kernel, linux-perf-users
Hi Matt,
On Wed, Nov 4, 2026 at 7:49 PM Matt Turner <mattst88@gmail.com> wrote:
>
> The 21264 (EV67 and later) and 21364 have two performance counting modes,
> and the driver only uses Aggregate mode. Its samples report the PC at
> which the counter interrupt was taken, which lands a variable number of
> instructions after the one that overflowed the counter. On an EV7 a
> 16-instruction user loop shows up at only 4 PCs, anywhere from 15
> instructions before to 12 after the real one.
>
> In ProfileMe mode, the instruction that overflows PCTR0 opens a window
> during which PCTR1 counts, and when it closes PALcode delivers the
> interrupt with that instruction's PC latched in PMPC.
>
> Select ProfileMe with the new profileme config bit or with precise_ip,
> since PMPC has no skid. PCTR_CTL[SL0] selects the mode for both
> counters, so a ProfileMe event is scheduled alone and perf core
> multiplexes it with other hardware events. precise_ip 3 is refused, as
> AMD IBS also does: a sample whose PMPC is a PALmode address keeps the
> interrupted PC, so zero skid cannot be promised. perf record falls back
> to 2 when it asks for the most precise level.
>
> The event type chooses the SL1 setting, which decides what PCTR0 samples
> on and what PCTR1 counts in the window:
>
> event PCTR0 PCTR1
> instructions instructions cycles
> bcache-misses instructions Bcache misses
> cycles cycles inum retire delay
> mbox-replay cycles Mbox replay traps
>
> Every interrupt rearms both counters, since PCTR0 stops when the window
> closes. The instruction count is corrected by the overcount I_STAT
> reports. The sample is attributed to PMPC. The interrupt can be taken
> in a different mode than the profiled instruction: in an unaligned
> access test on EV7 that was true of three quarters of the samples. When
> the modes match, the PC is set in the interrupt's frame for the overflow
> call and restored afterwards, so perf's mode, register and stack
> sampling all see the same frame. When they differ the registers are not
> available: a kernel instruction whose interrupt is taken after the
> return to user mode has no kernel state left, and a user instruction's
> frame may not be built yet, as the interrupt is often taken on the first
> instruction of the entry code. Those samples use a per-CPU frame holding
> only the PC, and a user instruction's sample reports no registers or
> user stack. PALmode PMPCs are physical addresses, so those samples keep
> the interrupted PC.
>
> PCTR0 is left stopped just past overflow at the end of every window,
> which the EV67 PALcode restrictions 32 and 38 warn about: a disabled
> counter near overflow keeps signaling it and blocks all other
> interrupts, and a disabled counter only clears its overflow state when
> written twice. So the counters are written twice when rearmed, and are
> moved away from overflow when a ProfileMe event is stopped or removed
> or a stray interrupt arrives for one that is gone. Without this,
> sampling every 10000 instructions wedged an EV7 in 7 of 16 runs, with
> no console output, until the service processor reset it. With it, 66
> runs passed.
>
> A ProfileMe event lives at index 0, so every interrupt taken while one
> is scheduled goes to it, including one from an Aggregate event on PCTR1
> that was pending when perf multiplexed the ProfileMe event in. That
> interrupt, and one pending from before a ProfileMe event was stopped or
> switched to the other mode, finds PCTR0 short of overflow, and is
> ignored as the previous patch does for Aggregate events.
>
> Three behaviors differ from Aggregate mode. perf record's default event
> is cycles:P, so it now records a ProfileMe profile. In ProfileMe mode
> PCTR0 counts valid fetched instructions rather than retired ones, so the
> instructions count includes the wrong path, squashed NOPs, and both
> halves of each CMOV: on an EV7 loop with a randomly mispredicted branch
> it is about twice the Aggregate count. And instructions killed on a
> wrong path are sampled at their own PC, as ProfileMe defines them.
>
> Suggested-by: Magnus Lindholm <linmag7@gmail.com>
> Assisted-by: Claude:claude-opus-5-5
> Signed-off-by: Matt Turner <mattst88@gmail.com>
> ---
> arch/alpha/include/asm/wrperfmon.h | 12 ++
> arch/alpha/kernel/perf_event.c | 290 +++++++++++++++++++++++++++++++++++--
> 2 files changed, 290 insertions(+), 12 deletions(-)
>
> diff --git a/arch/alpha/include/asm/wrperfmon.h b/arch/alpha/include/asm/wrperfmon.h
> index 1fa9bc813263..7fbbfd74926a 100644
> --- a/arch/alpha/include/asm/wrperfmon.h
> +++ b/arch/alpha/include/asm/wrperfmon.h
> @@ -92,5 +92,17 @@
> #define EV67_PCTR_CYCLES_UNDEF (1UL<<2)
> #define EV67_PCTR_INSTR_BCACHEMISS (2UL<<2)
> #define EV67_PCTR_CYCLES_MBOX (3UL<<2)
> +/* In ProfileMe mode, SL1 = 1 makes PCTR1 count the inum retire delay. */
> +#define EV67_PCTR_CYCLES_RETIRE_DELAY (1UL<<2)
> +/* Set in both SL settings where PCTR0 counts cycles. */
> +#define EV67_PCTR_PCTR0_CYCLES (1UL<<2)
> +
> +/* ProfileMe status, from the EV67/EV68 Hardware Reference Manuals. */
> +#define EV67_I_STAT_OVR_SHIFT 30
> +#define EV67_I_STAT_OVR_MASK 0x7UL
> +
> +/* PMPC bits 63:2 hold the profiled PC. Bit 0 set means a PALmode address. */
> +#define EV67_PMPC_PAL (1UL<<0)
> +#define EV67_PMPC_PC_MASK (~3UL)
>
> #endif
> diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
> index dd7fc4ac1396..7208d09a32d1 100644
> --- a/arch/alpha/kernel/perf_event.c
> +++ b/arch/alpha/kernel/perf_event.c
> @@ -16,6 +16,7 @@
> #include <linux/kdebug.h>
> #include <linux/mutex.h>
> #include <linux/init.h>
> +#include <linux/sched/task_stack.h>
>
> #include <asm/hwrpb.h>
> #include <linux/atomic.h>
> @@ -30,8 +31,13 @@
> #define MAX_HWEVENTS 3
> #define PMC_NO_INDEX -1
>
> +/* Layout of attr.config for PERF_TYPE_RAW events; see format/ in sysfs. */
> +#define ALPHA_PERF_CONFIG_EVENT 0xffULL
> +#define ALPHA_PERF_CONFIG_PROFILEME BIT_ULL(8)
> +
> /* hw_perf_event.flags */
> #define ALPHA_PERF_FLAG_WRAPPED 0x1
> +#define ALPHA_PERF_FLAG_PROFILEME 0x2
>
> /* For tracking PMCs and the hw events they monitor on each CPU. */
> struct cpu_hw_events {
> @@ -148,6 +154,19 @@ static const struct ev67_mapping_t ev67_mapping[] = {
> };
>
>
> +/*
> + * In ProfileMe mode PCTR0 selects the profiled instruction and PCTR1 counts
> + * during its window. PCTR0 counts instructions or cycles, and each choice
> + * pairs with two PCTR1 inputs, so each event type picks one SL1 setting.
> + * Indexed by enum ev67_pmc_event_type.
> + */
> +static const unsigned long ev67_profileme_sl1[] = {
> + [EV67_CYCLES] = EV67_PCTR_CYCLES_RETIRE_DELAY, /* PCTR1: retire delay */
> + [EV67_INSTRUCTIONS] = EV67_PCTR_INSTR_CYCLES, /* PCTR1: cycles */
> + [EV67_BCACHEMISS] = EV67_PCTR_INSTR_BCACHEMISS, /* PCTR1: Bcache misses */
> + [EV67_MBOXREPLAY] = EV67_PCTR_CYCLES_MBOX, /* PCTR1: Mbox replays */
> +};
> +
> /*
> * Check that a group of events can be simultaneously scheduled on to the
> * EV67 PMU. Also allocate counter indices and config.
> @@ -155,9 +174,21 @@ static const struct ev67_mapping_t ev67_mapping[] = {
> static int ev67_check_constraints(struct perf_event **event,
> unsigned long *evtype, int n_ev)
> {
> - int idx0;
> + int idx0, j;
> unsigned long config;
>
> + /* A ProfileMe event uses both counters, so it must be alone. */
> + for (j = 0; j < n_ev; j++) {
> + if (!(event[j]->hw.flags & ALPHA_PERF_FLAG_PROFILEME))
> + continue;
> + if (n_ev != 1)
> + return -1;
> + event[0]->hw.idx = 0;
> + event[0]->hw.config_base = EV67_PCTR_MODE_PROFILEME |
> + ev67_profileme_sl1[evtype[0]];
> + return 0;
> + }
> +
> idx0 = ev67_mapping[evtype[0]-1].idx;
> config = ev67_mapping[evtype[0]-1].config;
> if (n_ev == 1)
> @@ -216,9 +247,11 @@ static int ev67_raw_event_valid(u64 config)
>
>
> PMU_FORMAT_ATTR(event, "config:0-7");
> +PMU_FORMAT_ATTR(profileme, "config:8");
>
> static struct attribute *ev67_format_attrs[] = {
> &format_attr_event.attr,
> + &format_attr_profileme.attr,
> NULL,
> };
>
> @@ -279,6 +312,31 @@ static inline void alpha_write_pmc(int idx, unsigned long val)
> wrperfmon(PERFMON_CMD_WRITE, val);
> }
>
> +/* Write PCTR0 and clear PCTR1 in one call, as ProfileMe mode requires. */
> +static inline void alpha_write_pmc_profileme(unsigned long val)
> +{
> + val &= alpha_pmu->pmc_count_mask[0];
> + val <<= alpha_pmu->pmc_count_shift[0];
> + val |= EV67_PCTR_0 | EV67_PCTR_1;
> + /*
> + * PCTR0 stops when a ProfileMe window closes, and a stopped counter
> + * only clears its overflow state when written twice (EV67 HRM, PALcode
> + * restriction 38). A counter left signaling overflow blocks all
> + * other interrupts (restriction 32).
> + */
> + wrperfmon(PERFMON_CMD_WRITE, val);
> + wrperfmon(PERFMON_CMD_WRITE, val);
> +}
> +
> +/* The counters an event occupies, for PERFMON_CMD_ENABLE and DISABLE. */
> +static inline unsigned long alpha_event_idx_mask(struct hw_perf_event *hwc,
> + int idx)
> +{
> + if (hwc->flags & ALPHA_PERF_FLAG_PROFILEME)
> + return EV67_PCTR_0 | EV67_PCTR_1;
> + return 1UL<<idx;
> +}
> +
> static inline unsigned long alpha_read_pmc(int idx)
> {
> unsigned long val;
> @@ -289,6 +347,26 @@ static inline unsigned long alpha_read_pmc(int idx)
> return val;
> }
>
> +/*
> + * Move a stopped ProfileMe counter away from overflow. See
> + * alpha_write_pmc_profileme() for why. The caller has read the count, so
> + * prev_count can be rebased on the value written.
> + *
> + * A pending PMI stays pending, but alpha_perf_event_ovf() ignores it, as
> + * the counter is left no nearer zero than alpha_perf_event_set_period()
> + * would leave it.
> + */
> +static void alpha_perf_event_park(struct hw_perf_event *hwc)
> +{
> + long left = local64_read(&hwc->period_left);
> +
> + left = clamp(left, alpha_pmu->pmc_left[0],
> + (long)(alpha_pmu->pmc_max_period[0] >> 1));
> + local64_set(&hwc->prev_count, (unsigned long)(-left));
> + hwc->flags &= ~ALPHA_PERF_FLAG_WRAPPED;
> + alpha_write_pmc_profileme((unsigned long)(-left));
> +}
> +
> /* Set a new period to sample over */
> static int alpha_perf_event_set_period(struct perf_event *event,
> struct hw_perf_event *hwc, int idx)
> @@ -329,7 +407,10 @@ static int alpha_perf_event_set_period(struct perf_event *event,
> local64_set(&hwc->prev_count, (unsigned long)(-left));
> hwc->flags &= ~ALPHA_PERF_FLAG_WRAPPED;
>
> - alpha_write_pmc(idx, (unsigned long)(-left));
> + if (hwc->flags & ALPHA_PERF_FLAG_PROFILEME)
> + alpha_write_pmc_profileme((unsigned long)(-left));
> + else
> + alpha_write_pmc(idx, (unsigned long)(-left));
>
> perf_event_update_userpage(event);
>
> @@ -470,7 +551,8 @@ static void maybe_change_configuration(struct cpu_hw_events *cpuc)
> if (!(hwc->state & PERF_HES_STOPPED)) {
> /* The counter is live, so hwc->prev_count will go stale. */
> hwc->state &= ~PERF_HES_UPTODATE;
> - cpuc->idx_mask |= (1<<cpuc->current_idx[j]);
> + cpuc->idx_mask |= alpha_event_idx_mask(hwc,
> + cpuc->current_idx[j]);
> }
> }
> cpuc->config = cpuc->event[0]->hw.config_base;
> @@ -562,7 +644,10 @@ static void alpha_pmu_del(struct perf_event *event, int flags)
> alpha_perf_event_update(event, hwc, idx, 0);
> perf_event_update_userpage(event);
>
> - cpuc->idx_mask &= ~(1UL<<idx);
> + if (hwc->flags & ALPHA_PERF_FLAG_PROFILEME)
> + alpha_perf_event_park(hwc);
> +
> + cpuc->idx_mask &= ~alpha_event_idx_mask(hwc, idx);
> cpuc->n_events--;
> break;
> }
> @@ -587,17 +672,22 @@ static void alpha_pmu_stop(struct perf_event *event, int flags)
> struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
>
> if (!(hwc->state & PERF_HES_STOPPED)) {
> - cpuc->idx_mask &= ~(1UL<<hwc->idx);
> + cpuc->idx_mask &= ~alpha_event_idx_mask(hwc, hwc->idx);
> hwc->state |= PERF_HES_STOPPED;
> }
>
> - if ((flags & PERF_EF_UPDATE) && !(hwc->state & PERF_HES_UPTODATE)) {
> + /* Parking a ProfileMe event overwrites the counter, so read it first. */
> + if (((flags & PERF_EF_UPDATE) || (hwc->flags & ALPHA_PERF_FLAG_PROFILEME)) &&
> + !(hwc->state & PERF_HES_UPTODATE)) {
> alpha_perf_event_update(event, hwc, hwc->idx, 0);
> hwc->state |= PERF_HES_UPTODATE;
> }
>
> if (cpuc->enabled)
> - wrperfmon(PERFMON_CMD_DISABLE, (1UL<<hwc->idx));
> + wrperfmon(PERFMON_CMD_DISABLE, alpha_event_idx_mask(hwc, hwc->idx));
> +
> + if (hwc->flags & ALPHA_PERF_FLAG_PROFILEME)
> + alpha_perf_event_park(hwc);
> }
>
>
> @@ -616,9 +706,9 @@ static void alpha_pmu_start(struct perf_event *event, int flags)
>
> hwc->state = 0;
>
> - cpuc->idx_mask |= 1UL<<hwc->idx;
> + cpuc->idx_mask |= alpha_event_idx_mask(hwc, hwc->idx);
> if (cpuc->enabled)
> - wrperfmon(PERFMON_CMD_ENABLE, (1UL<<hwc->idx));
> + wrperfmon(PERFMON_CMD_ENABLE, alpha_event_idx_mask(hwc, hwc->idx));
> }
>
>
> @@ -658,6 +748,7 @@ static int __hw_perf_event_init(struct perf_event *event)
> struct perf_event *evts[MAX_HWEVENTS];
> unsigned long evtypes[MAX_HWEVENTS];
> int idx_rubbish_bin[MAX_HWEVENTS];
> + bool profileme = false;
> int ev;
> int n;
>
> @@ -671,9 +762,13 @@ static int __hw_perf_event_init(struct perf_event *event)
> } else if (attr->type == PERF_TYPE_HW_CACHE) {
> return -EOPNOTSUPP;
> } else if (attr->type == PERF_TYPE_RAW) {
> - if (!alpha_pmu->raw_event_valid(attr->config))
> + if (attr->config & ~(ALPHA_PERF_CONFIG_EVENT |
> + ALPHA_PERF_CONFIG_PROFILEME))
> return -EINVAL;
> - ev = attr->config;
> + ev = attr->config & ALPHA_PERF_CONFIG_EVENT;
> + if (!alpha_pmu->raw_event_valid(ev))
> + return -EINVAL;
> + profileme = attr->config & ALPHA_PERF_CONFIG_PROFILEME;
> } else {
> return -EOPNOTSUPP;
> }
> @@ -682,6 +777,20 @@ static int __hw_perf_event_init(struct perf_event *event)
> return ev;
> }
>
> + /*
> + * ProfileMe samples report the exact PC of the profiled instruction,
> + * so it also serves any request for precise samples. It only makes
> + * sense for sampling events. Level 3 promises no skid at all, but some
> + * samples keep the interrupted PC; see alpha_perf_event_profileme().
> + */
> + if (attr->precise_ip > 2)
> + return -EOPNOTSUPP;
> + if (attr->precise_ip)
> + profileme = true;
> + if (profileme && !is_sampling_event(event))
> + return -EINVAL;
> + hwc->flags = profileme ? ALPHA_PERF_FLAG_PROFILEME : 0;
> +
> /*
> * We place the event type in event_base here and leave calculation
> * of the codes to programme the PMU for alpha_pmu_enable() because
> @@ -872,6 +981,147 @@ static long alpha_perf_event_ovf(struct hw_perf_event *hwc, int idx)
> }
>
>
> +/*
> + * Frame holding only the PC, for a ProfileMe interrupt taken in a different
> + * mode than the profiled instruction. Being off the stack,
> + * perf_reg_value() reports no SP.
> + */
> +static DEFINE_PER_CPU(struct pt_regs, alpha_profileme_regs);
> +
> +/*
> + * Report the sample with none of the given PERF_SAMPLE_REGS_* registers.
> + * Having no user registers also means no user stack.
> + */
> +static void alpha_profileme_drop_regs(struct perf_event *event,
> + struct perf_sample_data *data, u64 which)
> +{
> + /* STACK_USER consults regs_user even without REGS_USER. */
> + if (which & PERF_SAMPLE_REGS_USER)
> + data->regs_user = (struct perf_regs){ .abi = PERF_SAMPLE_REGS_ABI_NONE };
> + if (which & PERF_SAMPLE_REGS_INTR)
> + data->regs_intr = (struct perf_regs){ .abi = PERF_SAMPLE_REGS_ABI_NONE };
> + data->sample_flags |= which;
> + /* Only the ABI word is written for each. */
> + data->dyn_size += hweight64(event->attr.sample_type & which) * sizeof(u64);
> +}
> +
> +/* Report the user registers from a frame known to be complete. */
> +static void alpha_profileme_set_regs_user(struct perf_event *event,
> + struct perf_sample_data *data)
> +{
> + data->regs_user.regs = task_pt_regs(current);
> + data->regs_user.abi = perf_reg_abi(current);
> + data->sample_flags |= PERF_SAMPLE_REGS_USER;
> + /* STACK_USER consults regs_user even without REGS_USER. */
> + if (event->attr.sample_type & PERF_SAMPLE_REGS_USER)
> + data->dyn_size += (1 + hweight64(event->attr.sample_regs_user)) *
> + sizeof(u64);
> +}
> +
> +/*
> + * Handle the close of a ProfileMe window on PCTR0.
> + *
> + * PCTR0 stops counting when the window closes and does not start again
> + * until PCTR_CTL is written, so the status must be read first and the
> + * counters then rearmed unconditionally.
> + */
> +static void alpha_perf_event_profileme(struct perf_event *event,
> + struct hw_perf_event *hwc,
> + struct pt_regs *regs, long ovf)
> +{
> + struct perf_sample_data data;
> + struct pt_regs *pm_regs;
> + bool pc_only = false;
> + unsigned long i_stat, pmpc, ovr, pc, saved_pc, saved_ps;
> +
> + i_stat = wrperfmon(PERFMON_CMD_I_STAT, 0);
> + pmpc = wrperfmon(PERFMON_CMD_PMPC, 0);
> +
> + alpha_perf_event_update(event, hwc, 0, ovf);
> +
> + /*
> + * When PCTR0 counts instructions, up to 7 of them are counted after
> + * the overflow but before the window opens. I_STAT reports how many.
> + */
> + if (!(hwc->config_base & EV67_PCTR_PCTR0_CYCLES)) {
> + ovr = (i_stat >> EV67_I_STAT_OVR_SHIFT) & EV67_I_STAT_OVR_MASK;
> + local64_sub(ovr, &event->count);
> + local64_add(ovr, &hwc->period_left);
> + }
> +
> + /*
> + * Attribute the sample to the profiled instruction rather than to
> + * wherever the interrupt landed. perf takes the sample's mode,
> + * registers and stack from regs, so:
> + *
> + * - Interrupt taken in the same mode: the PC is set in the
> + * interrupt's own frame, and restored afterwards.
> + * - User instruction, interrupt taken in the kernel, typically
> + * because the instruction trapped: the frame at task_pt_regs() may
> + * not be built yet. The interrupt is often taken on the first
> + * instruction of the entry code, and its own frame then overlaps
> + * that one. Report the PC alone, in a per-CPU frame.
> + * - Kernel instruction, interrupt taken just after returning to user
> + * mode: the kernel state is gone, so a per-CPU frame that holds only
> + * the PC. The user frame in regs is left alone, since perf samples
> + * the user registers from it.
> + *
> + * A PALmode PMPC is a physical address that perf cannot resolve. That
> + * happens for ITB miss, Istream access violation and interrupt
> + * redirects, and for instructions inside PALcode. Keep the
> + * interrupted PC, which for redirects is the instruction that
> + * trapped. Do the same for a user PMPC in a kernel thread, which has
> + * no user frame.
> + */
> + pm_regs = regs;
> + pc = pmpc & EV67_PMPC_PC_MASK;
> + if (pmpc & EV67_PMPC_PAL) {
> + pc = regs->pc;
> + } else if (pc < TASK_SIZE && !user_mode(regs)) {
> + if (is_user_task(current)) {
> + pc_only = true;
> + pm_regs = this_cpu_ptr(&alpha_profileme_regs);
> + memset(pm_regs, 0, sizeof(*pm_regs));
> + /* PS bit 3 is the mode bit that user_mode() tests. */
> + pm_regs->ps = 8;
> + } else {
> + pc = regs->pc;
> + }
> + } else if (pc >= TASK_SIZE && user_mode(regs)) {
> + pm_regs = this_cpu_ptr(&alpha_profileme_regs);
> + memset(pm_regs, 0, sizeof(*pm_regs));
> + }
> +
> + perf_sample_data_init(&data, 0, hwc->last_period);
> + if (pc_only)
> + alpha_profileme_drop_regs(event, &data, PERF_SAMPLE_REGS_USER |
> + PERF_SAMPLE_REGS_INTR);
> + /*
> + * perf_get_regs_user() has the same check, but it will find the PMPC
> + * in regs->pc rather than where the interrupt was taken. Decide here
> + * from the interrupted PC, for either answer that would differ.
> + */
> + else if (!user_mode(regs) && in_entry_save_code(regs->pc))
> + alpha_profileme_drop_regs(event, &data, PERF_SAMPLE_REGS_USER);
> + else if (in_entry_save_code(pc) && is_user_task(current))
> + alpha_profileme_set_regs_user(event, &data);
> +
> + if (!alpha_perf_event_set_period(event, hwc, 0))
> + return;
> +
> + saved_pc = pm_regs->pc;
> + saved_ps = pm_regs->ps;
> + pm_regs->pc = pc;
> + /* PS bit 3 is the mode bit that user_mode() tests. */
> + if (pc >= TASK_SIZE)
> + pm_regs->ps &= ~8UL;
> +
> + perf_event_overflow(event, &data, pm_regs);
> +
> + pm_regs->pc = saved_pc;
> + pm_regs->ps = saved_ps;
> +}
> +
> /*
> * Performance Monitoring Interrupt Service Routine called when a PMC
> * overflows. The PMC that overflowed is passed in la_ptr.
> @@ -907,6 +1157,10 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
>
> idx = la_ptr;
>
> + /* A ProfileMe event owns both counters but lives at index 0. */
> + if (cpuc->config & EV67_PCTR_MODE_PROFILEME)
> + idx = 0;
> +
> for (j = 0; j < cpuc->n_events; j++) {
> if (cpuc->current_idx[j] == idx)
> break;
> @@ -914,6 +1168,8 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
>
> if (unlikely(j == cpuc->n_events)) {
> /* This can occur if the event is disabled right on a PMC overflow. */
> + if (cpuc->config & EV67_PCTR_MODE_PROFILEME)
> + alpha_write_pmc_profileme(-alpha_pmu->pmc_max_period[0]);
> wrperfmon(PERFMON_CMD_ENABLE, cpuc->idx_mask);
> return;
> }
> @@ -931,7 +1187,17 @@ static void alpha_perf_event_irq_handler(unsigned long la_ptr,
> hwc = &event->hw;
> ovf = alpha_perf_event_ovf(hwc, idx);
> if (ovf < 0) {
> - /* The interrupt was pending when the counter was written. */
> + /*
> + * The interrupt was pending when the counter was written, or
> + * is from PCTR1 and was pending when a ProfileMe event was
> + * scheduled in.
> + */
> + wrperfmon(PERFMON_CMD_ENABLE, cpuc->idx_mask);
> + return;
> + }
> +
> + if (hwc->flags & ALPHA_PERF_FLAG_PROFILEME) {
> + alpha_perf_event_profileme(event, hwc, regs, ovf);
> wrperfmon(PERFMON_CMD_ENABLE, cpuc->idx_mask);
> return;
> }
>
> --
> 2.55.0
>
Retested on my ES40. All 1131 samples from the unaligned-access loop
kept the correct user PMPC and omitted the unavailable registers.
The aligned control passed on all three CPUs. I also confirmed that
precise_ip=3 is rejected, which addresses my earlier question.
Reviewed-by: Magnus Lindholm <linmag7@gmail.com>
Tested-by: Magnus Lindholm <linmag7@gmail.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v6 09/13] alpha/perf: report the ProfileMe window count as the sample weight
2026-11-04 18:49 ` [PATCH v6 09/13] alpha/perf: report the ProfileMe window count as the sample weight Matt Turner
@ 2026-11-04 22:11 ` Magnus Lindholm
0 siblings, 0 replies; 18+ messages in thread
From: Magnus Lindholm @ 2026-11-04 22:11 UTC (permalink / raw)
To: Matt Turner
Cc: Richard Henderson, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, linux-alpha, linux-kernel, linux-perf-users
Hi Matt,
On Wed, Nov 4, 2026 at 7:49 PM Matt Turner <mattst88@gmail.com> wrote:
>
> What PCTR1 counted during the ProfileMe window, the inum retire delay for
> cycles:p or the window length for instructions:p, is only available in
> the PERF_SAMPLE_RAW data.
>
> Report it in var1_dw of the sample weight, which the weight and
> local_weight sort keys read. It is 20 bits, so it fits. As Alpha is
> little-endian, this is also the right value for plain PERF_SAMPLE_WEIGHT,
> which is what perf record -W asks for. Samples that fall back to the
> interrupted PC get no weight, as it belongs to a different instruction.
>
> Suggested-by: Ian Rogers <irogers@google.com>
> Assisted-by: Claude:claude-opus-5-5
> Signed-off-by: Matt Turner <mattst88@gmail.com>
> ---
> arch/alpha/kernel/perf_event.c | 13 ++++++++++++-
> 1 file changed, 12 insertions(+), 1 deletion(-)
>
> diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
> index b33677cc23ca..c9ab40ff76ad 100644
> --- a/arch/alpha/kernel/perf_event.c
> +++ b/arch/alpha/kernel/perf_event.c
> @@ -1068,7 +1068,7 @@ static void alpha_perf_event_profileme(struct perf_event *event,
> struct perf_raw_record raw;
> struct perf_sample_data data;
> struct pt_regs *pm_regs;
> - bool pc_only = false;
> + bool pc_only = false, exact = true;
> unsigned long pctr_ctl, i_stat, pmpc, ovr, pc, saved_pc, saved_ps;
>
> pctr_ctl = wrperfmon(PERFMON_CMD_READ, 0);
> @@ -1115,6 +1115,7 @@ static void alpha_perf_event_profileme(struct perf_event *event,
> pc = pmpc & EV67_PMPC_PC_MASK;
> if (pmpc & EV67_PMPC_PAL) {
> pc = regs->pc;
> + exact = false;
> } else if (pc < TASK_SIZE && !user_mode(regs)) {
> if (is_user_task(current)) {
> pc_only = true;
> @@ -1124,6 +1125,7 @@ static void alpha_perf_event_profileme(struct perf_event *event,
> pm_regs->ps = 8;
> } else {
> pc = regs->pc;
> + exact = false;
> }
> } else if (pc >= TASK_SIZE && user_mode(regs)) {
> pm_regs = this_cpu_ptr(&alpha_profileme_regs);
> @@ -1157,6 +1159,15 @@ static void alpha_perf_event_profileme(struct perf_event *event,
> };
> perf_sample_save_raw_data(&data, event, &raw);
>
> + /* The count belongs to the profiled instruction, not the interrupted PC. */
> + if (exact && (event->attr.sample_type & PERF_SAMPLE_WEIGHT_TYPE)) {
> + /* perf_sample_data_init() does not clear it. */
> + data.weight.full = 0;
> + data.weight.var1_dw = (pctr_ctl >> EV67_PCTR_1_COUNT_SHIFT) &
> + EV67_PCTR_1_COUNT_MASK;
> + data.sample_flags |= PERF_SAMPLE_WEIGHT_TYPE;
> + }
> +
> if (!alpha_perf_event_set_period(event, hwc, 0))
> return;
>
>
> --
> 2.55.0
>
On my ES40 I compared the sample weight with PCTR1 in the raw record
for all four counting modes. They matched for samples attributed to
PMPC, and samples that fell back to the interrupted PC had weight zero.
Reviewed-by: Magnus Lindholm <linmag7@gmail.com>
Tested-by: Magnus Lindholm <linmag7@gmail.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH v6 10/13] alpha/perf: report the data source of ProfileMe memory accesses
2026-11-04 18:49 ` [PATCH v6 10/13] alpha/perf: report the data source of ProfileMe memory accesses Matt Turner
@ 2026-11-04 22:31 ` Magnus Lindholm
0 siblings, 0 replies; 18+ messages in thread
From: Magnus Lindholm @ 2026-11-04 22:31 UTC (permalink / raw)
To: Matt Turner
Cc: Richard Henderson, Peter Zijlstra, Ingo Molnar,
Arnaldo Carvalho de Melo, Namhyung Kim, Mark Rutland,
Alexander Shishkin, Jiri Olsa, Ian Rogers, Adrian Hunter,
James Clark, linux-alpha, linux-kernel, linux-perf-users
Hi Matt,
On Wed, Nov 4, 2026 at 7:49 PM Matt Turner <mattst88@gmail.com> wrote:
>
> Whether a ProfileMe sample's memory access missed the DTB is only in the
> PERF_SAMPLE_RAW data, as a DTB miss trap.
>
> Set PERF_SAMPLE_DATA_SRC for the samples of memory instructions, so that
> the memory sort keys see them. One that trapped on a DTB miss gets an L1
> miss in mem_dtlb. PALcode fills the DTB, so one that retired without
> that trap hit it, and gets an L1 hit. mem_op comes from the opcode, as
> I_STAT does not say whether the access was a load or a store. A load
> into $31 or $f31 is a prefetch, except for ldq_u, a UNOP that is left
> out. Samples that fall back to the interrupted PC get none.
>
> On an EV7, a loop that loads one word from each page of 64 MB in random
> order had as many DTB hit samples on the load as misses: the miss trap
> aborts the load, and its refetch hits. A prefetch into $31 takes the
> same trap, and its samples are reported as PFETCH misses and hits.
>
> I_STAT[TRP] is ignored for an instruction killed before it was mapped. On
> an EV7, TRP and MIS were seen set for killed instructions that are not
> even branches.
>
> Suggested-by: Ian Rogers <irogers@google.com>
> Assisted-by: Claude:claude-opus-5-5
> Signed-off-by: Matt Turner <mattst88@gmail.com>
> ---
> arch/alpha/include/asm/wrperfmon.h | 12 ++++++
> arch/alpha/kernel/perf_event.c | 86 ++++++++++++++++++++++++++++++++++++++
> 2 files changed, 98 insertions(+)
>
> diff --git a/arch/alpha/include/asm/wrperfmon.h b/arch/alpha/include/asm/wrperfmon.h
> index 7fbbfd74926a..dc24c7cb45db 100644
> --- a/arch/alpha/include/asm/wrperfmon.h
> +++ b/arch/alpha/include/asm/wrperfmon.h
> @@ -100,6 +100,18 @@
> /* ProfileMe status, from the EV67/EV68 Hardware Reference Manuals. */
> #define EV67_I_STAT_OVR_SHIFT 30
> #define EV67_I_STAT_OVR_MASK 0x7UL
> +#define EV67_I_STAT_TRAP_TYPE_SHIFT 34
> +#define EV67_I_STAT_TRAP_TYPE_MASK 0xfUL
> +#define EV67_I_STAT_TRP (1UL<<39)
> +#define EV67_I_STAT_MIS (1UL<<40)
> +
> +/* I_STAT[TRAP_TYPE] values */
> +#define EV67_TRAP_DTB_DOUBLE_3 2
> +#define EV67_TRAP_DTB_DOUBLE_4 3
> +#define EV67_TRAP_DTB_SINGLE 6
> +
> +#define EV67_PCTR_VAL (1UL<<1)
> +#define EV67_PCTR_PM_KILLED_BM (1UL<<26)
>
> /* PMPC bits 63:2 hold the profiled PC. Bit 0 set means a PALmode address. */
> #define EV67_PMPC_PAL (1UL<<0)
> diff --git a/arch/alpha/kernel/perf_event.c b/arch/alpha/kernel/perf_event.c
> index c9ab40ff76ad..0723e1042145 100644
> --- a/arch/alpha/kernel/perf_event.c
> +++ b/arch/alpha/kernel/perf_event.c
> @@ -20,6 +20,7 @@
> #include <linux/overflow.h>
> #include <linux/prandom.h>
> #include <linux/sched/task_stack.h>
> +#include <linux/uaccess.h>
>
> #include <asm/hwrpb.h>
> #include <linux/atomic.h>
> @@ -1053,6 +1054,88 @@ static void alpha_profileme_set_regs_user(struct perf_event *event,
> sizeof(u64);
> }
>
> +/* TRP, and MIS with it, are stale for an insn killed before it was mapped. */
> +static bool alpha_profileme_trp(unsigned long i_stat, unsigned long pctr_ctl)
> +{
> + return !(pctr_ctl & EV67_PCTR_PM_KILLED_BM) && (i_stat & EV67_I_STAT_TRP);
> +}
> +
> +/* What the memory instruction at pc does, or PERF_MEM_OP_NA. */
> +static u64 alpha_profileme_mem_op(unsigned long pc)
> +{
> + u32 insn;
> + bool r31;
> +
> + if (pc < TASK_SIZE) {
> + if (copy_from_user_nofault(&insn, (const void __user *)pc,
> + sizeof(insn)))
> + return PERF_MEM_OP_NA;
> + } else if (copy_from_kernel_nofault(&insn, (const void *)pc,
> + sizeof(insn))) {
> + return PERF_MEM_OP_NA;
> + }
> +
> + /* A load into $31 or $f31 is a prefetch, except for ldq_u: a UNOP. */
> + r31 = ((insn >> 21) & 0x1f) == 31;
> +
> + switch (insn >> 26) {
> + case 0x0b: /* ldq_u */
> + return r31 ? PERF_MEM_OP_NA : PERF_MEM_OP_LOAD;
> + case 0x0a: case 0x0c: /* ldbu, ldwu */
> + case 0x20: case 0x21: case 0x22: case 0x23: /* ldf, ldg, lds, ldt */
> + case 0x28: case 0x29: /* ldl, ldq */
> + return r31 ? PERF_MEM_OP_PFETCH : PERF_MEM_OP_LOAD;
> + case 0x2a: case 0x2b: /* ldl_l, ldq_l */
> + return PERF_MEM_OP_LOAD;
> + case 0x0d: case 0x0e: case 0x0f: /* stw, stb, stq_u */
> + case 0x24: case 0x25: case 0x26: case 0x27: /* stf, stg, sts, stt */
> + case 0x2c: case 0x2d: case 0x2e: case 0x2f: /* stl, stq, stl_c, stq_c */
> + return PERF_MEM_OP_STORE;
> + default:
> + return PERF_MEM_OP_NA;
> + }
> +}
> +
> +/*
> + * Report the DTB access of a memory instruction. PALcode fills the DTB, so
> + * one that retired without a DTB miss trap hit it.
> + */
> +static void alpha_profileme_data_src(struct perf_sample_data *data,
> + unsigned long pc, unsigned long i_stat,
> + unsigned long pctr_ctl)
> +{
> + u64 op, dtlb;
> +
> + if (alpha_profileme_trp(i_stat, pctr_ctl)) {
> + if (i_stat & EV67_I_STAT_MIS)
> + return;
> + switch ((i_stat >> EV67_I_STAT_TRAP_TYPE_SHIFT) &
> + EV67_I_STAT_TRAP_TYPE_MASK) {
> + case EV67_TRAP_DTB_SINGLE:
> + case EV67_TRAP_DTB_DOUBLE_3:
> + case EV67_TRAP_DTB_DOUBLE_4:
> + dtlb = PERF_MEM_TLB_L1 | PERF_MEM_TLB_MISS;
> + break;
> + default:
> + return;
> + }
> + op = alpha_profileme_mem_op(pc);
> + } else if (!(pctr_ctl & EV67_PCTR_PM_KILLED_BM) &&
> + (pctr_ctl & EV67_PCTR_VAL)) {
> + dtlb = PERF_MEM_TLB_L1 | PERF_MEM_TLB_HIT;
> + op = alpha_profileme_mem_op(pc);
> + if (op == PERF_MEM_OP_NA)
> + return;
> + } else {
> + return;
> + }
> +
> + data->data_src.val = PERF_MEM_NA;
> + data->data_src.mem_op = op;
> + data->data_src.mem_dtlb = dtlb;
> + data->sample_flags |= PERF_SAMPLE_DATA_SRC;
> +}
> +
> /*
> * Handle the close of a ProfileMe window on PCTR0.
> *
> @@ -1168,6 +1251,9 @@ static void alpha_perf_event_profileme(struct perf_event *event,
> data.sample_flags |= PERF_SAMPLE_WEIGHT_TYPE;
> }
>
> + if (exact && (event->attr.sample_type & PERF_SAMPLE_DATA_SRC))
> + alpha_profileme_data_src(&data, pc, i_stat, pctr_ctl);
> +
> if (!alpha_perf_event_set_period(event, hwc, 0))
> return;
>
>
> --
> 2.55.0
>
I tested this on my EV67 ES40 with a page-stride workload. At the
ldq into $31 I saw 89 PFETCH hits and 87 PFETCH misses. The load
and store sites also showed both DTB hits and misses. All 215 samples
at the ldq_u UNOP had no memory data source.
Reviewed-by: Magnus Lindholm <linmag7@gmail.com>
Tested-by: Magnus Lindholm <linmag7@gmail.com>
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-11-04 22:31 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-04 18:49 [PATCH v6 00/13] alpha/perf: add ProfileMe sampling Matt Turner
2026-11-04 18:49 ` [PATCH v6 01/13] alpha/perf: pass PERFMON_CMD_LOGGING_ALL to the logging options call Matt Turner
2026-11-04 18:49 ` [PATCH v6 02/13] alpha/perf: add sysfs format and event attributes Matt Turner
2026-11-04 18:49 ` [PATCH v6 03/13] alpha/perf: clear PERF_HES_UPTODATE when an event starts counting Matt Turner
2026-11-04 18:49 ` [PATCH v6 04/13] alpha/perf: do not report user registers from the entry code Matt Turner
2026-11-04 18:49 ` [PATCH v6 05/13] alpha/perf: ignore overflow interrupts the counter does not account for Matt Turner
2026-11-04 21:54 ` Magnus Lindholm
2026-11-04 18:49 ` [PATCH v6 06/13] alpha/perf: implement ProfileMe sampling Matt Turner
2026-11-04 21:57 ` Magnus Lindholm
2026-11-04 18:49 ` [PATCH v6 07/13] alpha/perf: attach ProfileMe status as PERF_SAMPLE_RAW data Matt Turner
2026-11-04 18:49 ` [PATCH v6 08/13] alpha/perf: optionally randomize the ProfileMe sampling interval Matt Turner
2026-11-04 18:49 ` [PATCH v6 09/13] alpha/perf: report the ProfileMe window count as the sample weight Matt Turner
2026-11-04 22:11 ` Magnus Lindholm
2026-11-04 18:49 ` [PATCH v6 10/13] alpha/perf: report the data source of ProfileMe memory accesses Matt Turner
2026-11-04 22:31 ` Magnus Lindholm
2026-11-04 18:49 ` [PATCH v6 11/13] alpha/perf: add ProfileMe sample filters Matt Turner
2026-11-04 18:49 ` [PATCH v6 12/13] alpha/perf: sample only retired instructions for precise events Matt Turner
2026-11-04 18:49 ` [PATCH v6 13/13] alpha/perf: honor exclude_user and exclude_kernel for ProfileMe events Matt Turner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®